From cf52553304ee4b69b3a2c1468ffdb07eedbc7e65 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 20 Oct 2019 20:05:01 +0200 Subject: [PATCH] MIMO: MO operation fixes --- plugins/samplemimo/testmi/testmi.cpp | 23 +- plugins/samplemimo/testmi/testmi.h | 6 +- sdrbase/device/deviceapi.cpp | 20 +- sdrbase/device/deviceapi.h | 10 +- sdrbase/dsp/devicesamplemimo.h | 6 +- sdrbase/dsp/dspdevicemimoengine.cpp | 624 +++++++++++++++++---------- sdrbase/dsp/dspdevicemimoengine.h | 39 +- sdrbase/dsp/mimochannel.h | 6 +- sdrbase/dsp/samplemofifo.cpp | 9 +- sdrbase/util/incrementalvector.h | 10 + sdrgui/mainwindow.cpp | 3 +- 11 files changed, 481 insertions(+), 275 deletions(-) diff --git a/plugins/samplemimo/testmi/testmi.cpp b/plugins/samplemimo/testmi/testmi.cpp index e0cd9aae9..645a1b190 100644 --- a/plugins/samplemimo/testmi/testmi.cpp +++ b/plugins/samplemimo/testmi/testmi.cpp @@ -63,7 +63,7 @@ TestMI::~TestMI() delete m_networkManager; if (m_running) { - stop(); + stopRx(); } std::vector::iterator it = m_fileSinks.begin(); @@ -91,13 +91,13 @@ void TestMI::init() applySettings(m_settings, true); } -bool TestMI::start() +bool TestMI::startRx() { - qDebug("TestMI::start"); + qDebug("TestMI::startRx"); QMutexLocker mutexLocker(&m_mutex); if (m_running) { - stop(); + stopRx(); } m_testSourceThreads.push_back(new TestMIThread(&m_sampleMIFifo, 0)); @@ -116,9 +116,15 @@ bool TestMI::start() return true; } -void TestMI::stop() +bool TestMI::startTx() { - qDebug("TestMI::stop"); + qDebug("TestMI::startTx"); + return false; +} + +void TestMI::stopRx() +{ + qDebug("TestMI::stopRx"); QMutexLocker mutexLocker(&m_mutex); std::vector::iterator it = m_testSourceThreads.begin(); @@ -133,6 +139,11 @@ void TestMI::stop() m_running = false; } +void TestMI::stopTx() +{ + qDebug("TestMI::stopTx"); +} + QByteArray TestMI::serialize() const { return m_settings.serialize(); diff --git a/plugins/samplemimo/testmi/testmi.h b/plugins/samplemimo/testmi/testmi.h index 2247c3146..885ee63d9 100644 --- a/plugins/samplemimo/testmi/testmi.h +++ b/plugins/samplemimo/testmi/testmi.h @@ -104,8 +104,10 @@ public: virtual void destroy(); virtual void init(); - virtual bool start(); - virtual void stop(); + virtual bool startRx(); + virtual void stopRx(); + virtual bool startTx(); + virtual void stopTx(); virtual QByteArray serialize() const; virtual bool deserialize(const QByteArray& data); diff --git a/sdrbase/device/deviceapi.cpp b/sdrbase/device/deviceapi.cpp index 22fc7043d..e58101c23 100644 --- a/sdrbase/device/deviceapi.cpp +++ b/sdrbase/device/deviceapi.cpp @@ -239,64 +239,64 @@ DeviceSampleMIMO *DeviceAPI::getSampleMIMO() } } -bool DeviceAPI::initDeviceEngine() +bool DeviceAPI::initDeviceEngine(int subsystemIndex) { if (m_deviceSourceEngine) { return m_deviceSourceEngine->initAcquisition(); } else if (m_deviceSinkEngine) { return m_deviceSinkEngine->initGeneration(); } else if (m_deviceMIMOEngine) { - return m_deviceMIMOEngine->initProcess(); + return m_deviceMIMOEngine->initProcess(subsystemIndex); } else { return false; } } -bool DeviceAPI::startDeviceEngine() +bool DeviceAPI::startDeviceEngine(int subsystemIndex) { if (m_deviceSourceEngine) { return m_deviceSourceEngine->startAcquisition(); } else if (m_deviceSinkEngine) { return m_deviceSinkEngine->startGeneration(); } else if (m_deviceMIMOEngine) { - return m_deviceMIMOEngine->startProcess(); + return m_deviceMIMOEngine->startProcess(subsystemIndex); } else { return false; } } -void DeviceAPI::stopDeviceEngine() +void DeviceAPI::stopDeviceEngine(int subsystemIndex) { if (m_deviceSourceEngine) { m_deviceSourceEngine->stopAcquistion(); } else if (m_deviceSinkEngine) { m_deviceSinkEngine->stopGeneration(); } else if (m_deviceMIMOEngine) { - m_deviceMIMOEngine->stopProcess(); + m_deviceMIMOEngine->stopProcess(subsystemIndex); } } -DeviceAPI::EngineState DeviceAPI::state() const +DeviceAPI::EngineState DeviceAPI::state(int subsystemIndex) const { if (m_deviceSourceEngine) { return (DeviceAPI::EngineState) m_deviceSourceEngine->state(); } else if (m_deviceSinkEngine) { return (DeviceAPI::EngineState) m_deviceSinkEngine->state(); } else if (m_deviceMIMOEngine) { - return (DeviceAPI::EngineState) m_deviceMIMOEngine->state(); + return (DeviceAPI::EngineState) m_deviceMIMOEngine->state(subsystemIndex); } else { return StError; } } -QString DeviceAPI::errorMessage() +QString DeviceAPI::errorMessage(int subsystemIndex) { if (m_deviceSourceEngine) { return m_deviceSourceEngine->errorMessage(); } else if (m_deviceSinkEngine) { return m_deviceSinkEngine->errorMessage(); } else if (m_deviceMIMOEngine) { - return m_deviceMIMOEngine->errorMessage(); + return m_deviceMIMOEngine->errorMessage(subsystemIndex); } else { return "Not implemented"; } diff --git a/sdrbase/device/deviceapi.h b/sdrbase/device/deviceapi.h index b8e756e19..b886a2f2b 100644 --- a/sdrbase/device/deviceapi.h +++ b/sdrbase/device/deviceapi.h @@ -92,11 +92,11 @@ public: DeviceSampleSink *getSampleSink(); //!< Return pointer to the device sample sink (single Tx) or nullptr DeviceSampleMIMO *getSampleMIMO(); //!< Return pointer to the device sample MIMO or nullptr - bool initDeviceEngine(); //!< Init the device engine corresponding to the stream type - bool startDeviceEngine(); //!< Start the device engine corresponding to the stream type - void stopDeviceEngine(); //!< Stop the device engine corresponding to the stream type - EngineState state() const; //!< Return the state of the device engine corresponding to the stream type - QString errorMessage(); //!< Last error message from the device engine + bool initDeviceEngine(int subsystemIndex = 0); //!< Init the device engine corresponding to the stream type + bool startDeviceEngine(int subsystemIndex = 0); //!< Start the device engine corresponding to the stream type + void stopDeviceEngine(int subsystemIndex = 0); //!< Stop the device engine corresponding to the stream type + EngineState state(int subsystemIndex = 0) const; //!< Return the state of the device engine corresponding to the stream type + QString errorMessage(int subsystemIndex = 0); //!< Last error message from the device engine uint getDeviceUID() const; //!< Return the current device engine unique ID MessageQueue *getDeviceEngineInputMessageQueue(); //!< Device engine message queue diff --git a/sdrbase/dsp/devicesamplemimo.h b/sdrbase/dsp/devicesamplemimo.h index 793bdecd7..023a80015 100644 --- a/sdrbase/dsp/devicesamplemimo.h +++ b/sdrbase/dsp/devicesamplemimo.h @@ -56,8 +56,10 @@ public: virtual void destroy() = 0; virtual void init() = 0; //!< initializations to be done when all collaborating objects are created and possibly connected - virtual bool start() = 0; - virtual void stop() = 0; + virtual bool startRx() = 0; + virtual void stopRx() = 0; + virtual bool startTx() = 0; + virtual void stopTx() = 0; virtual QByteArray serialize() const = 0; virtual bool deserialize(const QByteArray& data) = 0; diff --git a/sdrbase/dsp/dspdevicemimoengine.cpp b/sdrbase/dsp/dspdevicemimoengine.cpp index 05edb30e9..aa34a9746 100644 --- a/sdrbase/dsp/dspdevicemimoengine.cpp +++ b/sdrbase/dsp/dspdevicemimoengine.cpp @@ -45,7 +45,8 @@ MESSAGE_CLASS_DEFINITION(DSPDeviceMIMOEngine::SetSpectrumSinkInput, Message) DSPDeviceMIMOEngine::DSPDeviceMIMOEngine(uint32_t uid, QObject* parent) : QThread(parent), m_uid(uid), - m_state(StNotStarted), + m_stateRx(StNotStarted), + m_stateTx(StNotStarted), m_deviceSampleMIMO(nullptr), m_spectrumInputSourceElseSink(true), m_spectrumInputIndex(0) @@ -65,7 +66,8 @@ DSPDeviceMIMOEngine::~DSPDeviceMIMOEngine() void DSPDeviceMIMOEngine::run() { qDebug() << "DSPDeviceMIMOEngine::run"; - m_state = StIdle; + m_stateRx = StIdle; + m_stateTx = StIdle; exec(); } @@ -78,32 +80,59 @@ void DSPDeviceMIMOEngine::start() void DSPDeviceMIMOEngine::stop() { qDebug() << "DSPDeviceMIMOEngine::stop"; - gotoIdle(); - m_state = StNotStarted; + gotoIdle(0); // Rx + gotoIdle(1); // Tx + m_stateRx = StNotStarted; + m_stateTx = StNotStarted; QThread::exit(); } -bool DSPDeviceMIMOEngine::initProcess() +bool DSPDeviceMIMOEngine::initProcess(int subsystemIndex) { - qDebug() << "DSPDeviceMIMOEngine::initProcess"; - DSPGenerationInit cmd; + qDebug() << "DSPDeviceMIMOEngine::initProcess: subsystemIndex: " << subsystemIndex; - return m_syncMessenger.sendWait(cmd) == StReady; + if (subsystemIndex == 0) // Rx side + { + DSPAcquisitionInit cmd; + return m_syncMessenger.sendWait(cmd) == StReady; + } + else if (subsystemIndex == 1) // Tx side + { + DSPGenerationInit cmd; + return m_syncMessenger.sendWait(cmd) == StReady; + } } -bool DSPDeviceMIMOEngine::startProcess() +bool DSPDeviceMIMOEngine::startProcess(int subsystemIndex) { - qDebug() << "DSPDeviceMIMOEngine::startProcess"; - DSPGenerationStart cmd; - - return m_syncMessenger.sendWait(cmd) == StRunning; + qDebug() << "DSPDeviceMIMOEngine::startProcess: subsystemIndex: " << subsystemIndex; + if (subsystemIndex == 0) // Rx side + { + DSPAcquisitionStart cmd; + return m_syncMessenger.sendWait(cmd) == StRunning; + } + else if (subsystemIndex == 1) // Tx side + { + DSPGenerationStart cmd; + return m_syncMessenger.sendWait(cmd) == StRunning; + } } -void DSPDeviceMIMOEngine::stopProcess() +void DSPDeviceMIMOEngine::stopProcess(int subsystemIndex) { - qDebug() << "DSPDeviceMIMOEngine::stopProcess"; - DSPGenerationStop cmd; - m_syncMessenger.storeMessage(cmd); + qDebug() << "DSPDeviceMIMOEngine::stopProcess: subsystemIndex: " << subsystemIndex; + + if (subsystemIndex == 0) // Rx side + { + DSPAcquisitionStop cmd; + m_syncMessenger.storeMessage(cmd); + } + else if (subsystemIndex == 1) // Tx side + { + DSPGenerationStop cmd; + m_syncMessenger.storeMessage(cmd); + } + handleSynchronousMessages(); } @@ -219,10 +248,10 @@ void DSPDeviceMIMOEngine::setSpectrumSinkInput(bool sourceElseSink, int index) m_syncMessenger.sendWait(cmd); } -QString DSPDeviceMIMOEngine::errorMessage() +QString DSPDeviceMIMOEngine::errorMessage(int subsystemIndex) { - qDebug() << "DSPDeviceMIMOEngine::errorMessage"; - GetErrorMessage cmd; + qDebug() << "DSPDeviceMIMOEngine::errorMessage: subsystemIndex:" << subsystemIndex; + GetErrorMessage cmd(subsystemIndex); m_syncMessenger.sendWait(cmd); return cmd.getErrorMessage(); } @@ -281,15 +310,18 @@ void DSPDeviceMIMOEngine::workSampleSourceFifos() std::vector vbegin; vbegin.resize(sampleFifo->getNbStreams()); + unsigned int amount = sampleFifo->remainderSync(); - while ((sampleFifo->remainderSync() > 0) && (m_inputMessageQueue.size() == 0)) + while ((amount > 0) && (m_inputMessageQueue.size() == 0)) { // pull remainderSync() samples from the sources by stream for (unsigned int streamIndex = 0; streamIndex < sampleFifo->getNbStreams(); streamIndex++) { - workSamplesSource(vbegin[streamIndex], sampleFifo->remainderSync(), streamIndex); + workSamplesSource(vbegin[streamIndex], amount, streamIndex); } // write pulled samples to FIFO - sampleFifo->writeSync(vbegin, sampleFifo->remainderSync()); + sampleFifo->writeSync(vbegin, amount); + // get new amount + amount = sampleFifo->remainderSync(); } } @@ -331,13 +363,16 @@ void DSPDeviceMIMOEngine::workSampleSourceFifo(unsigned int streamIndex) } SampleVector::const_iterator begin; + unsigned int amount = sampleFifo->remainderAsync(streamIndex); - while ((sampleFifo->remainderAsync(streamIndex) > 0) && (m_inputMessageQueue.size() == 0)) + while ((amount > 0) && (m_inputMessageQueue.size() == 0)) { // pull remainderAsync() samples from the sources stream - workSamplesSource(begin, sampleFifo->remainderAsync(streamIndex), streamIndex); + workSamplesSource(begin, amount, streamIndex); // write pulled samples to FIFO's corresponding stream - sampleFifo->writeAsync(begin, sampleFifo->remainderAsync(streamIndex), streamIndex); + sampleFifo->writeAsync(begin, amount, streamIndex); + // get new amount + amount = sampleFifo->remainderAsync(streamIndex); } } @@ -382,13 +417,8 @@ void DSPDeviceMIMOEngine::workSamplesSource(SampleVector::const_iterator& begin, { if (m_threadedBasebandSampleSources[streamIndex].size() == 0) { - m_sourceSampleBuffers[streamIndex].allocate(nbSamples); - std::fill( - m_sourceSampleBuffers[streamIndex].m_vector.begin(), - m_sourceSampleBuffers[streamIndex].m_vector.begin()+nbSamples, - Sample{0,0} - ); - begin = m_sourceSampleBuffers[streamIndex].m_vector.begin(); + m_sourceZeroBuffers[streamIndex].allocate(nbSamples, Sample{0,0}); + begin = m_sourceZeroBuffers[streamIndex].m_vector.begin(); } else if (m_threadedBasebandSampleSources[streamIndex].size() == 1) { @@ -431,68 +461,96 @@ void DSPDeviceMIMOEngine::workSamplesSource(SampleVector::const_iterator& begin, // ^ | // +-----------------------+ -DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoIdle() +DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoIdle(int subsystemIndex) { - qDebug() << "DSPDeviceMIMOEngine::gotoIdle"; - - switch(m_state) { - case StNotStarted: - return StNotStarted; - - case StIdle: - case StError: - return StIdle; - - case StReady: - case StRunning: - break; - } + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: subsystemIndex:" << subsystemIndex; if (!m_deviceSampleMIMO) { return StIdle; } - // stop everything - - m_deviceSampleMIMO->stop(); - - std::vector::const_iterator vbit = m_basebandSampleSinks.begin(); - - for (; vbit != m_basebandSampleSinks.end(); ++vbit) - { - for (BasebandSampleSinks::const_iterator it = vbit->begin(); it != vbit->end(); ++it) - { - qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping BasebandSampleSink: " << (*it)->objectName().toStdString().c_str(); - (*it)->stop(); - } - } - - std::vector::const_iterator vtSinkIt = m_threadedBasebandSampleSinks.begin(); - - for (; vtSinkIt != m_threadedBasebandSampleSinks.end(); vtSinkIt++) + if (subsystemIndex == 0) // Rx { - for (ThreadedBasebandSampleSinks::const_iterator it = vtSinkIt->begin(); it != vtSinkIt->end(); ++it) + switch (m_stateRx) { + case StNotStarted: + return StNotStarted; + + case StIdle: + case StError: + return StIdle; + + case StReady: + case StRunning: + break; + } + + m_deviceSampleMIMO->stopRx(); // stop everything + + std::vector::const_iterator vbit = m_basebandSampleSinks.begin(); + + for (; vbit != m_basebandSampleSinks.end(); ++vbit) { - qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping ThreadedBasebandSampleSource(" << (*it)->getSampleSinkObjectName().toStdString().c_str() << ")"; - (*it)->stop(); + for (BasebandSampleSinks::const_iterator it = vbit->begin(); it != vbit->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping BasebandSampleSink: " << (*it)->objectName().toStdString().c_str(); + (*it)->stop(); + } + } + + std::vector::const_iterator vtSinkIt = m_threadedBasebandSampleSinks.begin(); + + for (; vtSinkIt != m_threadedBasebandSampleSinks.end(); vtSinkIt++) + { + for (ThreadedBasebandSampleSinks::const_iterator it = vtSinkIt->begin(); it != vtSinkIt->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping ThreadedBasebandSampleSource(" << (*it)->getSampleSinkObjectName().toStdString().c_str() << ")"; + (*it)->stop(); + } + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping MIMOChannel sinks: " << (*it)->objectName().toStdString().c_str(); + (*it)->stopSinks(); } } - - std::vector::const_iterator vtSourceIt = m_threadedBasebandSampleSources.begin(); - - for (; vtSourceIt != m_threadedBasebandSampleSources.end(); vtSourceIt++) + else if (subsystemIndex == 1) // Tx { - for (ThreadedBasebandSampleSources::const_iterator it = vtSourceIt->begin(); it != vtSourceIt->end(); ++it) + switch (m_stateTx) { + case StNotStarted: + return StNotStarted; + + case StIdle: + case StError: + return StIdle; + + case StReady: + case StRunning: + break; + } + + m_deviceSampleMIMO->stopTx(); // stop everything + + std::vector::const_iterator vtSourceIt = m_threadedBasebandSampleSources.begin(); + + for (; vtSourceIt != m_threadedBasebandSampleSources.end(); vtSourceIt++) { - qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping ThreadedBasebandSampleSource(" << (*it)->getSampleSourceObjectName().toStdString().c_str() << ")"; - (*it)->stop(); + for (ThreadedBasebandSampleSources::const_iterator it = vtSourceIt->begin(); it != vtSourceIt->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping ThreadedBasebandSampleSource(" << (*it)->getSampleSourceObjectName().toStdString().c_str() << ")"; + (*it)->stop(); + } + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping MIMOChannel sources: " << (*it)->objectName().toStdString().c_str(); + (*it)->stopSources(); } } - - for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + else { - qDebug() << "DSPDeviceMIMOEngine::gotoIdle: stopping MIMOChannel: " << (*it)->objectName().toStdString().c_str(); - (*it)->stop(); + return StIdle; } m_deviceDescription.clear(); @@ -500,162 +558,212 @@ DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoIdle() return StIdle; } -DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoInit() +DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoInit(int subsystemIndex) { - switch(m_state) { - case StNotStarted: - return StNotStarted; - - case StRunning: // FIXME: assumes it goes first through idle state. Could we get back to init from running directly? - return StRunning; - - case StReady: - return StReady; - - case StIdle: - case StError: - break; - } - if (!m_deviceSampleMIMO) { - return gotoError("No sample MIMO configured"); + return gotoError(subsystemIndex, "No sample MIMO configured"); } - // init: pass sample rate and center frequency to all sample rate and/or center frequency dependent sinks and wait for completion - - m_deviceDescription = m_deviceSampleMIMO->getDeviceDescription(); qDebug() << "DSPDeviceMIMOEngine::gotoInit:" + << "subsystemIndex: " << subsystemIndex << "m_deviceDescription: " << m_deviceDescription.toStdString().c_str(); - // Rx - - for (unsigned int isource = 0; isource < m_deviceSampleMIMO->getNbSinkFifos(); isource++) + if (subsystemIndex == 0) // Rx { - if (isource < m_sourcesCorrections.size()) - { - m_sourcesCorrections[isource].m_iOffset = 0; - m_sourcesCorrections[isource].m_qOffset = 0; - m_sourcesCorrections[isource].m_iRange = 1 << 16; - m_sourcesCorrections[isource].m_qRange = 1 << 16; + switch(m_stateRx) { + case StNotStarted: + return StNotStarted; + + case StRunning: // FIXME: assumes it goes first through idle state. Could we get back to init from running directly? + return StRunning; + + case StReady: + return StReady; + + case StIdle: + case StError: + break; } - quint64 sourceCenterFrequency = m_deviceSampleMIMO->getSourceCenterFrequency(isource); - int sourceStreamSampleRate = m_deviceSampleMIMO->getSourceSampleRate(isource); - - qDebug("DSPDeviceMIMOEngine::gotoInit: m_sourceCenterFrequencies[%d] = %llu", isource, sourceCenterFrequency); - qDebug("DSPDeviceMIMOEngine::gotoInit: m_sourceStreamSampleRates[%d] = %d", isource, sourceStreamSampleRate); - - DSPSignalNotification notif(sourceStreamSampleRate, sourceCenterFrequency); - - if (isource < m_basebandSampleSinks.size()) + // init: pass sample rate and center frequency to all sample rate and/or center frequency dependent sinks and wait for completion + for (unsigned int isource = 0; isource < m_deviceSampleMIMO->getNbSourceStreams(); isource++) { - for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks[isource].begin(); it != m_basebandSampleSinks[isource].end(); ++it) + if (isource < m_sourcesCorrections.size()) { - qDebug() << "DSPDeviceMIMOEngine::gotoInit: initializing " << (*it)->objectName().toStdString().c_str(); - (*it)->handleMessage(notif); + m_sourcesCorrections[isource].m_iOffset = 0; + m_sourcesCorrections[isource].m_qOffset = 0; + m_sourcesCorrections[isource].m_iRange = 1 << 16; + m_sourcesCorrections[isource].m_qRange = 1 << 16; + } + + quint64 sourceCenterFrequency = m_deviceSampleMIMO->getSourceCenterFrequency(isource); + int sourceStreamSampleRate = m_deviceSampleMIMO->getSourceSampleRate(isource); + + qDebug("DSPDeviceMIMOEngine::gotoInit: m_sourceCenterFrequencies[%d] = %llu", isource, sourceCenterFrequency); + qDebug("DSPDeviceMIMOEngine::gotoInit: m_sourceStreamSampleRates[%d] = %d", isource, sourceStreamSampleRate); + + DSPSignalNotification notif(sourceStreamSampleRate, sourceCenterFrequency); + + if (isource < m_basebandSampleSinks.size()) + { + for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks[isource].begin(); it != m_basebandSampleSinks[isource].end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoInit: initializing " << (*it)->objectName().toStdString().c_str(); + (*it)->handleMessage(notif); + } + } + + if (isource < m_threadedBasebandSampleSinks.size()) + { + for (ThreadedBasebandSampleSinks::const_iterator it = m_threadedBasebandSampleSinks[isource].begin(); it != m_threadedBasebandSampleSinks[isource].end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoInit: initializing ThreadedSampleSink(" << (*it)->getSampleSinkObjectName().toStdString().c_str() << ")"; + (*it)->handleSinkMessage(notif); + } } } - - if (isource < m_threadedBasebandSampleSinks.size()) - { - for (ThreadedBasebandSampleSinks::const_iterator it = m_threadedBasebandSampleSinks[isource].begin(); it != m_threadedBasebandSampleSinks[isource].end(); ++it) - { - qDebug() << "DSPDeviceMIMOEngine::gotoInit: initializing ThreadedSampleSink(" << (*it)->getSampleSinkObjectName().toStdString().c_str() << ")"; - (*it)->handleSinkMessage(notif); - } - } - - // Probably not necessary - // // possibly forward to spectrum sink - // if ((m_spectrumSink) && (m_spectrumInputSourceElseSink) && (isource == m_spectrumInputIndex)) { - // m_spectrumSink->handleMessage(notif); - // } - - // // forward changes to MIMO GUI input queue - // MessageQueue *guiMessageQueue = m_deviceSampleMIMO->getMessageQueueToGUI(); - - // if (guiMessageQueue) { - // DSPMIMOSignalNotification* rep = new DSPMIMOSignalNotification(sourceStreamSampleRate, sourceCenterFrequency, true, isource); // make a copy for the MIMO GUI - // guiMessageQueue->push(rep); - // } } + else if (subsystemIndex == 1) // Tx + { + switch(m_stateTx) { + case StNotStarted: + return StNotStarted; - //TODO: Tx + case StRunning: // FIXME: assumes it goes first through idle state. Could we get back to init from running directly? + return StRunning; + + case StReady: + return StReady; + + case StIdle: + case StError: + break; + } + + for (unsigned int isink = 0; isink < m_deviceSampleMIMO->getNbSinkStreams(); isink++) + { + quint64 sinkCenterFrequency = m_deviceSampleMIMO->getSinkCenterFrequency(isink); + int sinkStreamSampleRate = m_deviceSampleMIMO->getSinkSampleRate(isink); + + qDebug("DSPDeviceMIMOEngine::gotoInit: m_sinkCenterFrequencies[%d] = %llu", isink, sinkCenterFrequency); + qDebug("DSPDeviceMIMOEngine::gotoInit: m_sinkStreamSampleRates[%d] = %d", isink, sinkStreamSampleRate); + + DSPSignalNotification notif(sinkStreamSampleRate, sinkCenterFrequency); + + if (isink < m_threadedBasebandSampleSources.size()) + { + for (ThreadedBasebandSampleSources::const_iterator it = m_threadedBasebandSampleSources[isink].begin(); it != m_threadedBasebandSampleSources[isink].end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoInit: initializing ThreadedSampleSource(" << (*it)->getSampleSourceObjectName().toStdString().c_str() << ")"; + (*it)->handleSourceMessage(notif); + } + } + } + } return StReady; } -DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoRunning() +DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoRunning(int subsystemIndex) { - qDebug() << "DSPDeviceMIMOEngine::gotoRunning"; - - switch(m_state) - { - case StNotStarted: - return StNotStarted; - - case StIdle: - return StIdle; - - case StRunning: - return StRunning; - - case StReady: - case StError: - break; - } + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: subsystemIndex:" << subsystemIndex; if (!m_deviceSampleMIMO) { - return gotoError("DSPDeviceMIMOEngine::gotoRunning: No sample source configured"); + return gotoError(subsystemIndex, "DSPDeviceMIMOEngine::gotoRunning: No sample source configured"); } qDebug() << "DSPDeviceMIMOEngine::gotoRunning:" << m_deviceDescription.toStdString().c_str() << "started"; - // Start everything - - if (!m_deviceSampleMIMO->start()) { - return gotoError("Could not start sample source"); - } - - std::vector::const_iterator vbit = m_basebandSampleSinks.begin(); - - for (; vbit != m_basebandSampleSinks.end(); ++vbit) - { - for (BasebandSampleSinks::const_iterator it = vbit->begin(); it != vbit->end(); ++it) - { - qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting BasebandSampleSink: " << (*it)->objectName().toStdString().c_str(); - (*it)->start(); - } - } - - std::vector::const_iterator vtSinkIt = m_threadedBasebandSampleSinks.begin(); - - for (; vtSinkIt != m_threadedBasebandSampleSinks.end(); vtSinkIt++) + if (subsystemIndex == 0) // Rx { - for (ThreadedBasebandSampleSinks::const_iterator it = vtSinkIt->begin(); it != vtSinkIt->end(); ++it) + switch (m_stateRx) { - qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting ThreadedBasebandSampleSink(" << (*it)->getSampleSinkObjectName().toStdString().c_str() << ")"; - (*it)->start(); + case StNotStarted: + return StNotStarted; + + case StIdle: + return StIdle; + + case StRunning: + return StRunning; + + case StReady: + case StError: + break; + } + + if (!m_deviceSampleMIMO->startRx()) { // Start everything + return gotoError(0, "Could not start sample source"); + } + + std::vector::const_iterator vbit = m_basebandSampleSinks.begin(); + + for (; vbit != m_basebandSampleSinks.end(); ++vbit) + { + for (BasebandSampleSinks::const_iterator it = vbit->begin(); it != vbit->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting BasebandSampleSink: " << (*it)->objectName().toStdString().c_str(); + (*it)->start(); + } + } + + std::vector::const_iterator vtSinkIt = m_threadedBasebandSampleSinks.begin(); + + for (; vtSinkIt != m_threadedBasebandSampleSinks.end(); vtSinkIt++) + { + for (ThreadedBasebandSampleSinks::const_iterator it = vtSinkIt->begin(); it != vtSinkIt->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting ThreadedBasebandSampleSink(" << (*it)->getSampleSinkObjectName().toStdString().c_str() << ")"; + (*it)->start(); + } + } + + std::vector::const_iterator vtSourceIt = m_threadedBasebandSampleSources.begin(); + + for (; vtSourceIt != m_threadedBasebandSampleSources.end(); vtSourceIt++) + { + for (ThreadedBasebandSampleSources::const_iterator it = vtSourceIt->begin(); it != vtSourceIt->end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting ThreadedBasebandSampleSource(" << (*it)->getSampleSourceObjectName().toStdString().c_str() << ")"; + (*it)->start(); + } + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting MIMOChannel sinks: " << (*it)->objectName().toStdString().c_str(); + (*it)->startSinks(); } } - - std::vector::const_iterator vtSourceIt = m_threadedBasebandSampleSources.begin(); - - for (; vtSourceIt != m_threadedBasebandSampleSources.end(); vtSourceIt++) + else if (subsystemIndex == 1) // Tx { - for (ThreadedBasebandSampleSources::const_iterator it = vtSourceIt->begin(); it != vtSourceIt->end(); ++it) + switch (m_stateTx) { - qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting ThreadedBasebandSampleSource(" << (*it)->getSampleSourceObjectName().toStdString().c_str() << ")"; - (*it)->start(); - } - } + case StNotStarted: + return StNotStarted; - for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) - { - qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting MIMOChannel: " << (*it)->objectName().toStdString().c_str(); - (*it)->start(); + case StIdle: + return StIdle; + + case StRunning: + return StRunning; + + case StReady: + case StError: + break; + } + + if (!m_deviceSampleMIMO->startTx()) { // Start everything + return gotoError(1, "Could not start sample sink"); + } + + for (MIMOChannels::const_iterator it = m_mimoChannels.begin(); it != m_mimoChannels.end(); ++it) + { + qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting MIMOChannel sources: " << (*it)->objectName().toStdString().c_str(); + (*it)->startSources(); + } } qDebug() << "DSPDeviceMIMOEngine::gotoRunning:input message queue pending: " << m_inputMessageQueue.size(); @@ -663,40 +771,51 @@ DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoRunning() return StRunning; } -DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoError(const QString& errorMessage) +DSPDeviceMIMOEngine::State DSPDeviceMIMOEngine::gotoError(int subsystemIndex, const QString& errorMessage) { - qDebug() << "DSPDeviceMIMOEngine::gotoError: " << errorMessage; + qDebug() << "DSPDeviceMIMOEngine::gotoError: " + << " subsystemIndex: " << subsystemIndex + << " errorMessage: " << errorMessage; - m_errorMessage = errorMessage; - m_deviceDescription.clear(); - m_state = StError; - return StError; + + if (subsystemIndex == 0) + { + m_errorMessageRx = errorMessage; + m_stateRx = StError; + } + else if (subsystemIndex == 1) + { + m_errorMessageTx = errorMessage; + m_stateTx = StError; + } + + return StError; } void DSPDeviceMIMOEngine::handleDataRxSync() { - if (m_state == StRunning) { + if (m_stateRx == StRunning) { workSampleSinkFifos(); } } void DSPDeviceMIMOEngine::handleDataRxAsync(int streamIndex) { - if (m_state == StRunning) { + if (m_stateRx == StRunning) { workSampleSinkFifo(streamIndex); } } void DSPDeviceMIMOEngine::handleDataTxSync() { - if (m_state == StRunning) { + if (m_stateTx == StRunning) { workSampleSourceFifos(); } } void DSPDeviceMIMOEngine::handleDataTxAsync(int streamIndex) { - if (m_state == StRunning) { + if (m_stateTx == StRunning) { workSampleSourceFifo(streamIndex); } } @@ -720,6 +839,7 @@ void DSPDeviceMIMOEngine::handleSetMIMO(DeviceSampleMIMO* mimo) { m_threadedBasebandSampleSources.push_back(ThreadedBasebandSampleSources()); m_sourceSampleBuffers.push_back(IncrementalVector()); + m_sourceZeroBuffers.push_back(IncrementalVector()); } if (m_deviceSampleMIMO->getMIMOType() == DeviceSampleMIMO::MIMOHalfSynchronous) // synchronous FIFOs on Rx and not with Tx @@ -770,40 +890,75 @@ void DSPDeviceMIMOEngine::handleSetMIMO(DeviceSampleMIMO* mimo) // ); } } - - // TODO: Tx } void DSPDeviceMIMOEngine::handleSynchronousMessages() { Message *message = m_syncMessenger.getMessage(); qDebug() << "DSPDeviceMIMOEngine::handleSynchronousMessages: " << message->getIdentifier(); + State returnState = StNotStarted; - if (DSPGenerationInit::match(*message)) + if (DSPAcquisitionInit::match(*message)) { - m_state = gotoIdle(); + m_stateRx = gotoIdle(0); - if(m_state == StIdle) { - m_state = gotoInit(); // State goes ready if init is performed + if (m_stateRx == StIdle) { + m_stateRx = gotoInit(0); // State goes ready if init is performed } + + returnState = m_stateRx; + } + else if (DSPAcquisitionStart::match(*message)) + { + if (m_stateRx == StReady) { + m_stateRx = gotoRunning(0); + } + + returnState = m_stateRx; + } + else if (DSPAcquisitionStop::match(*message)) + { + m_stateRx = gotoIdle(0); + returnState = m_stateRx; + } + else if (DSPGenerationInit::match(*message)) + { + m_stateTx = gotoIdle(1); + + if (m_stateTx == StIdle) { + m_stateTx = gotoInit(1); // State goes ready if init is performed + } + + returnState = m_stateTx; } else if (DSPGenerationStart::match(*message)) { - if(m_state == StReady) { - m_state = gotoRunning(); + if (m_stateTx == StReady) { + m_stateTx = gotoRunning(1); } + + returnState = m_stateTx; } else if (DSPGenerationStop::match(*message)) { - m_state = gotoIdle(); + m_stateTx = gotoIdle(1); + returnState = m_stateTx; } else if (GetMIMODeviceDescription::match(*message)) { ((GetMIMODeviceDescription*) message)->setDeviceDescription(m_deviceDescription); } - else if (DSPGetErrorMessage::match(*message)) + else if (GetErrorMessage::match(*message)) { - ((DSPGetErrorMessage*) message)->setErrorMessage(m_errorMessage); + GetErrorMessage *cmd = (GetErrorMessage *) message; + int subsystemIndex = cmd->getSubsystemIndex(); + if (subsystemIndex == 0) { + cmd->setErrorMessage(m_errorMessageRx); + } else if (subsystemIndex == 1) { + cmd->setErrorMessage(m_errorMessageTx); + } else { + cmd->setErrorMessage("Not implemented"); + } } else if (SetSampleMIMO::match(*message)) { handleSetMIMO(((SetSampleMIMO*) message)->getSampleMIMO()); @@ -823,7 +978,7 @@ void DSPDeviceMIMOEngine::handleSynchronousMessages() DSPSignalNotification msg(sourceStreamSampleRate, sourceCenterFrequency); sink->handleMessage(msg); // start the sink: - if(m_state == StRunning) { + if (m_stateRx == StRunning) { sink->start(); } } @@ -836,7 +991,7 @@ void DSPDeviceMIMOEngine::handleSynchronousMessages() if (isource < m_basebandSampleSinks.size()) { - if(m_state == StRunning) { + if (m_stateRx == StRunning) { sink->stop(); } @@ -858,7 +1013,7 @@ void DSPDeviceMIMOEngine::handleSynchronousMessages() DSPSignalNotification msg(sourceStreamSampleRate, sourceCenterFrequency); threadedSink->handleSinkMessage(msg); // start the sink: - if(m_state == StRunning) { + if (m_stateRx == StRunning) { threadedSink->start(); } } @@ -890,7 +1045,7 @@ void DSPDeviceMIMOEngine::handleSynchronousMessages() DSPSignalNotification msg(sinkStreamSampleRate, sinkCenterFrequency); threadedSource->handleSourceMessage(msg); // start the sink: - if(m_state == StRunning) { + if (m_stateTx == StRunning) { threadedSource->start(); } } @@ -927,23 +1082,28 @@ void DSPDeviceMIMOEngine::handleSynchronousMessages() for (int isink = 0; isink < m_deviceSampleMIMO->getNbSinkStreams(); isink++) { DSPMIMOSignalNotification notif( - m_deviceSampleMIMO->getSourceSampleRate(isink), - m_deviceSampleMIMO->getSourceCenterFrequency(isink), + m_deviceSampleMIMO->getSinkSampleRate(isink), + m_deviceSampleMIMO->getSinkCenterFrequency(isink), false, isink ); channel->handleMessage(notif); } - if (m_state == StRunning) { - channel->start(); + if (m_stateRx == StRunning) { + channel->startSinks(); + } + + if (m_stateTx == StRunning) { + channel->startSources(); } } else if (RemoveMIMOChannel::match(*message)) { const RemoveMIMOChannel *msg = (RemoveMIMOChannel *) message; MIMOChannel *channel = msg->getChannel(); - channel->stop(); + channel->stopSinks(); + channel->stopSources(); m_mimoChannels.remove(channel); } else if (AddSpectrumSink::match(*message)) @@ -1004,7 +1164,7 @@ void DSPDeviceMIMOEngine::handleSynchronousMessages() } } - m_syncMessenger.done(m_state); + m_syncMessenger.done(returnState); } void DSPDeviceMIMOEngine::handleInputMessages() @@ -1086,7 +1246,7 @@ void DSPDeviceMIMOEngine::handleInputMessages() { for (BasebandSampleSinks::const_iterator it = m_basebandSampleSinks[istream].begin(); it != m_basebandSampleSinks[istream].end(); ++it) { - qDebug() << "DSPDeviceMIMOEngine::gotoRunning: starting " << (*it)->objectName().toStdString().c_str(); + qDebug() << "DSPDeviceMIMOEngine::handleInputMessages: starting " << (*it)->objectName().toStdString().c_str(); (*it)->handleMessage(*message); } } diff --git a/sdrbase/dsp/dspdevicemimoengine.h b/sdrbase/dsp/dspdevicemimoengine.h index 6c05ede61..a7026a1e1 100644 --- a/sdrbase/dsp/dspdevicemimoengine.h +++ b/sdrbase/dsp/dspdevicemimoengine.h @@ -184,9 +184,14 @@ public: class GetErrorMessage : public Message { MESSAGE_CLASS_DECLARATION public: + GetErrorMessage(unsigned int subsystemIndex) : + m_subsystemIndex(subsystemIndex) + {} void setErrorMessage(const QString& text) { m_errorMessage = text; } + int getSubsystemIndex() const { return m_subsystemIndex; } const QString& getErrorMessage() const { return m_errorMessage; } private: + int m_subsystemIndex; QString m_errorMessage; }; @@ -247,9 +252,9 @@ public: void start(); //!< This thread start void stop(); //!< This thread stop - bool initProcess(); //!< Initialize process sequence - bool startProcess(); //!< Start process sequence - void stopProcess(); //!< Stop process sequence + bool initProcess(int subsystemIndex); //!< Initialize process sequence + bool startProcess(int subsystemIndex); //!< Start process sequence + void stopProcess(int subsystemIndex); //!< Stop process sequence void setMIMO(DeviceSampleMIMO* mimo); //!< Set the sample MIMO type DeviceSampleMIMO *getMIMO() { return m_deviceSampleMIMO; } @@ -270,9 +275,18 @@ public: void removeSpectrumSink(BasebandSampleSink* spectrumSink); //!< Add a spectrum vis baseband sample sink void setSpectrumSinkInput(bool sourceElseSink, int index); - State state() const { return m_state; } //!< Return DSP engine current state + State state(int subsystemIndex) const //!< Return DSP engine current state + { + if (subsystemIndex == 0) { + return m_stateRx; + } else if (subsystemIndex == 1) { + return m_stateTx; + } else { + return StNotStarted; + } + } - QString errorMessage(); //!< Return the current error message + QString errorMessage(int subsystemIndex); //!< Return the current error message QString deviceDescription(); //!< Return the device description void configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection, int isource); //!< Configure source DSP corrections @@ -329,9 +343,11 @@ private: }; uint32_t m_uid; //!< unique ID - State m_state; + State m_stateRx; + State m_stateTx; - QString m_errorMessage; + QString m_errorMessageRx; + QString m_errorMessageTx; QString m_deviceDescription; DeviceSampleMIMO* m_deviceSampleMIMO; @@ -349,6 +365,7 @@ private: typedef std::list ThreadedBasebandSampleSources; std::vector m_threadedBasebandSampleSources; //!< channel sample sources on their own threads (per output stream) std::vector> m_sourceSampleBuffers; + std::vector> m_sourceZeroBuffers; typedef std::list MIMOChannels; MIMOChannels m_mimoChannels; //!< MIMO channels @@ -367,10 +384,10 @@ private: void workSampleSourceFifo(unsigned int streamIndex); //!< transfer samples of one source stream (async mode) void workSamplesSource(SampleVector::const_iterator& begin, unsigned int nbSamples, unsigned int streamIndex); - State gotoIdle(); //!< Go to the idle state - State gotoInit(); //!< Go to the acquisition init state from idle - State gotoRunning(); //!< Go to the running state from ready state - State gotoError(const QString& errorMsg); //!< Go to an error state + State gotoIdle(int subsystemIndex); //!< Go to the idle state + State gotoInit(int subsystemIndex); //!< Go to the acquisition init state from idle + State gotoRunning(int subsystemIndex); //!< Go to the running state from ready state + State gotoError(int subsystemIndex, const QString& errorMsg); //!< Go to an error state void handleSetMIMO(DeviceSampleMIMO* mimo); //!< Manage MIMO device setting void iqCorrections(SampleVector::iterator begin, SampleVector::iterator end, int isource, bool imbalanceCorrection); diff --git a/sdrbase/dsp/mimochannel.h b/sdrbase/dsp/mimochannel.h index 48264e5dd..fc56fbcd9 100644 --- a/sdrbase/dsp/mimochannel.h +++ b/sdrbase/dsp/mimochannel.h @@ -33,8 +33,10 @@ public: MIMOChannel(); virtual ~MIMOChannel(); - virtual void start() = 0; - virtual void stop() = 0; + virtual void startSinks() = 0; + virtual void stopSinks() = 0; + virtual void startSources() = 0; + virtual void stopSources() = 0; virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex) = 0; virtual void pull(Sample& sample, unsigned int sourceIndex) = 0; virtual bool handleMessage(const Message& cmd) = 0; //!< Processing of a message. Returns true if message has actually been processed diff --git a/sdrbase/dsp/samplemofifo.cpp b/sdrbase/dsp/samplemofifo.cpp index ddee4aa19..45c65f8a1 100644 --- a/sdrbase/dsp/samplemofifo.cpp +++ b/sdrbase/dsp/samplemofifo.cpp @@ -108,11 +108,11 @@ void SampleMOFifo::writeSync(const std::vector& vb if (amount <= spaceLeft) { - for (unsigned int stream = 0; stream < m_nbStreams; stream++) - { + for (unsigned int stream = 0; stream < m_nbStreams; stream++) { std::copy(vbegin[stream], vbegin[stream] + amount, m_data[stream].begin() + m_writeHead); - m_writeHead += amount; } + + m_writeHead += amount; } else { @@ -122,8 +122,9 @@ void SampleMOFifo::writeSync(const std::vector& vb { std::copy(vbegin[stream], vbegin[stream] + spaceLeft, m_data[stream].begin() + m_writeHead); std::copy(vbegin[stream] + spaceLeft, vbegin[stream] + amount, m_data[stream].begin()); - m_writeHead = remaining; } + + m_writeHead = remaining; } } diff --git a/sdrbase/util/incrementalvector.h b/sdrbase/util/incrementalvector.h index 77471df52..1e97fe9f1 100644 --- a/sdrbase/util/incrementalvector.h +++ b/sdrbase/util/incrementalvector.h @@ -31,6 +31,7 @@ public: ~IncrementalVector(); void allocate(uint32_t size); + void allocate(uint32_t size, const T& init); private: uint32_t m_size; @@ -55,4 +56,13 @@ void IncrementalVector::allocate(uint32_t size) m_size = size; } +template +void IncrementalVector::allocate(uint32_t size, const T& init) +{ + if (size <= m_size) { return; } + m_vector.resize(size); + std::fill(m_vector.begin(), m_vector.end(), init); + m_size = size; +} + #endif /* SDRBASE_UTIL_INCREMENTALVECTOR_H_ */ diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index 657e7e310..b59deeb68 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -595,7 +595,8 @@ void MainWindow::removeLastDevice() else if (m_deviceUIs.back()->m_deviceMIMOEngine) // MIMO tab { DSPDeviceMIMOEngine *lastDeviceEngine = m_deviceUIs.back()->m_deviceMIMOEngine; - lastDeviceEngine->stopProcess(); + lastDeviceEngine->stopProcess(1); // Tx side + lastDeviceEngine->stopProcess(0); // Rx side lastDeviceEngine->removeSpectrumSink(m_deviceUIs.back()->m_spectrumVis); ui->tabSpectraGUI->removeTab(ui->tabSpectraGUI->count() - 1);