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();