diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index 4ff81f7be..b979ae7b8 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -1,6 +1,7 @@ project(samplesink) add_subdirectory(filesink) +add_subdirectory(testsink) add_subdirectory(localoutput) if(CM256CC_FOUND) diff --git a/plugins/samplesink/testsink/CMakeLists.txt b/plugins/samplesink/testsink/CMakeLists.txt new file mode 100644 index 000000000..16af57085 --- /dev/null +++ b/plugins/samplesink/testsink/CMakeLists.txt @@ -0,0 +1,55 @@ +project(testsink) + +set(testsink_SOURCES + testsinkoutput.cpp + testsinkplugin.cpp + testsinksettings.cpp + testsinkthread.cpp +) + +set(testsink_HEADERS + testsinkoutput.h + testsinkplugin.h + testsinksettings.h + testsinkthread.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +if(NOT SERVER_MODE) + set(testsink_SOURCES + ${testsink_SOURCES} + testsinkgui.cpp + testsinkgui.ui + ) + set(testsink_HEADERS + ${testsink_HEADERS} + testsinkgui.h + ) + + set(TARGET_NAME outputtestsink) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME outputtestsinksrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${testsink_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} + swagger +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/samplesink/testsink/testsinkgui.cpp b/plugins/samplesink/testsink/testsinkgui.cpp new file mode 100644 index 000000000..07a1eda13 --- /dev/null +++ b/plugins/samplesink/testsink/testsinkgui.cpp @@ -0,0 +1,274 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// 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 "ui_testsinkgui.h" +#include "plugin/pluginapi.h" +#include "gui/colormapper.h" +#include "gui/glspectrum.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" + +#include "mainwindow.h" + +#include "device/deviceapi.h" +#include "device/deviceuiset.h" +#include "testsinkgui.h" + +TestSinkGui::TestSinkGui(DeviceUISet *deviceUISet, QWidget* parent) : + QWidget(parent), + ui(new Ui::TestSinkGui), + m_deviceUISet(deviceUISet), + m_doApplySettings(true), + m_forceSettings(true), + m_settings(), + m_deviceSampleSink(0), + m_sampleRate(0), + m_generation(false), + m_samplesCount(0), + m_tickCount(0), + m_lastEngineState(DeviceAPI::StNotStarted) +{ + ui->setupUi(this); + + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequency->setValueRange(7, 0, pow(10,7)); + + ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGreenYellow)); + ui->sampleRate->setValueRange(7, 32000U, 9000000U); + + connect(&(m_deviceUISet->m_deviceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + displaySettings(); + + m_deviceSampleSink = (TestSinkOutput*) m_deviceUISet->m_deviceAPI->getSampleSink(); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); +} + +TestSinkGui::~TestSinkGui() +{ + delete ui; +} + +void TestSinkGui::destroy() +{ + delete this; +} + +void TestSinkGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString TestSinkGui::getName() const +{ + return objectName(); +} + +void TestSinkGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 TestSinkGui::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +void TestSinkGui::setCenterFrequency(qint64 centerFrequency) +{ + m_settings.m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray TestSinkGui::serialize() const +{ + return m_settings.serialize(); +} + +bool TestSinkGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool TestSinkGui::handleMessage(const Message& message) +{ + if (TestSinkOutput::MsgConfigureTestSink::match(message)) + { + qDebug("TestSinkGui::handleMessage: message: MsgConfigureTestSink"); + const TestSinkOutput::MsgConfigureTestSink& cfg = (TestSinkOutput::MsgConfigureTestSink&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (TestSinkOutput::MsgStartStop::match(message)) + { + TestSinkOutput::MsgStartStop& notif = (TestSinkOutput::MsgStartStop&) message; + qDebug("TestSinkOutput::handleMessage: message: MsgStartStop: %s", notif.getStartStop() ? "start" : "stop"); + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void TestSinkGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + qDebug("FileSinkGui::handleInputMessages: DSPSignalNotification: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency()); + m_sampleRate = notif->getSampleRate(); + m_deviceCenterFrequency = notif->getCenterFrequency(); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void TestSinkGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency); + ui->deviceRateText->setText(tr("%1k").arg((float)(m_sampleRate*(1<centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + ui->sampleRate->setValue(m_settings.m_sampleRate); +} + +void TestSinkGui::sendSettings() +{ + if (!m_updateTimer.isActive()) { + m_updateTimer.start(100); + } +} + + +void TestSinkGui::updateHardware() +{ + qDebug() << "TestSinkGui::updateHardware"; + TestSinkOutput::MsgConfigureTestSink* message = TestSinkOutput::MsgConfigureTestSink::create(m_settings, m_forceSettings); + m_deviceSampleSink->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); +} + +void TestSinkGui::updateStatus() +{ + int state = m_deviceUISet->m_deviceAPI->state(); + + if(m_lastEngineState != state) + { + switch(state) + { + case DeviceAPI::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DeviceAPI::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DeviceAPI::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DeviceAPI::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + +void TestSinkGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void TestSinkGui::on_sampleRate_changed(quint64 value) +{ + m_settings.m_sampleRate = value; + sendSettings(); +} + +void TestSinkGui::on_interp_currentIndexChanged(int index) +{ + if (index < 0) { + return; + } + + m_settings.m_log2Interp = index; + updateSampleRateAndFrequency(); + sendSettings(); +} + +void TestSinkGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + TestSinkOutput::MsgStartStop *message = TestSinkOutput::MsgStartStop::create(checked); + m_deviceSampleSink->getInputMessageQueue()->push(message); + } +} + +void TestSinkGui::tick() +{ +} diff --git a/plugins/samplesink/testsink/testsinkgui.h b/plugins/samplesink/testsink/testsinkgui.h new file mode 100644 index 000000000..98e9fed71 --- /dev/null +++ b/plugins/samplesink/testsink/testsinkgui.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// 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_TESTSINKGUI_H +#define INCLUDE_TESTSINKGUI_H + +#include +#include +#include + +#include "util/messagequeue.h" + +#include "testsinkoutput.h" +#include "testsinksettings.h" + + +class DeviceSampleSink; +class DeviceUISet; + +namespace Ui { + class TestSinkGui; +} + +class TestSinkGui : public QWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + explicit TestSinkGui(DeviceUISet *deviceUISet, QWidget* parent = nullptr); + virtual ~TestSinkGui(); + 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::TestSinkGui* ui; + + DeviceUISet* m_deviceUISet; + bool m_doApplySettings; + bool m_forceSettings; + TestSinkSettings m_settings; + QTimer m_updateTimer; + QTimer m_statusTimer; + DeviceSampleSink* m_deviceSampleSink; + int m_sampleRate; + quint64 m_deviceCenterFrequency; //!< Center frequency in device + bool m_generation; + int m_samplesCount; + std::size_t m_tickCount; + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void displayTime(); + void sendSettings(); + void configureFileName(); + void updateSampleRateAndFrequency(); + +private slots: + void handleInputMessages(); + void on_centerFrequency_changed(quint64 value); + void on_sampleRate_changed(quint64 value); + void on_startStop_toggled(bool checked); + void on_interp_currentIndexChanged(int index); + void updateHardware(); + void updateStatus(); + void tick(); +}; + +#endif // INCLUDE_TESTSINKGUI_H diff --git a/plugins/samplesink/testsink/testsinkgui.ui b/plugins/samplesink/testsink/testsinkgui.ui new file mode 100644 index 000000000..db86a4268 --- /dev/null +++ b/plugins/samplesink/testsink/testsinkgui.ui @@ -0,0 +1,331 @@ + + + TestSinkGui + + + + 0 + 0 + 350 + 190 + + + + + 0 + 0 + + + + + 350 + 190 + + + + + Liberation Sans + 9 + + + + Test Sink + + + Test Sink + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + + + + start/stop generation + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + + 50 + 0 + + + + I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + true + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 20 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Record center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + 4 + + + + + Int + + + + + + + Interpolation + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + SR + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + + + + + S/s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
+ + + + +
diff --git a/plugins/samplesink/testsink/testsinkoutput.cpp b/plugins/samplesink/testsink/testsinkoutput.cpp new file mode 100644 index 000000000..54642525d --- /dev/null +++ b/plugins/samplesink/testsink/testsinkoutput.cpp @@ -0,0 +1,253 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 // +// (at your option) any later version. // +// // +// 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 "util/simpleserializer.h" +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" + +#include "device/deviceapi.h" + +#include "testsinkoutput.h" +#include "testsinkthread.h" + +MESSAGE_CLASS_DEFINITION(TestSinkOutput::MsgConfigureTestSink, Message) +MESSAGE_CLASS_DEFINITION(TestSinkOutput::MsgStartStop, Message) + +TestSinkOutput::TestSinkOutput(DeviceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_testSinkThread(nullptr), + m_deviceDescription("TestSink"), + m_masterTimer(deviceAPI->getMasterTimer()) +{ + m_deviceAPI->setNbSinkStreams(1); +} + +TestSinkOutput::~TestSinkOutput() +{ + stop(); +} + +void TestSinkOutput::destroy() +{ + delete this; +} + +void TestSinkOutput::init() +{ + applySettings(m_settings, true); +} + +bool TestSinkOutput::start() +{ + QMutexLocker mutexLocker(&m_mutex); + qDebug() << "TestSinkOutput::start"; + + m_testSinkThread = new TestSinkThread(&m_sampleSourceFifo); + m_testSinkThread->setSamplerate(m_settings.m_sampleRate); + m_testSinkThread->setLog2Interpolation(m_settings.m_log2Interp); + m_testSinkThread->connectTimer(m_masterTimer); + m_testSinkThread->startWork(); + mutexLocker.unlock(); + + qDebug("TestSinkOutput::start: started"); + return true; +} + +void TestSinkOutput::stop() +{ + qDebug() << "TestSinkOutput::stop"; + QMutexLocker mutexLocker(&m_mutex); + + if(m_testSinkThread != 0) + { + m_testSinkThread->stopWork(); + delete m_testSinkThread; + m_testSinkThread = nullptr; + } +} + +QByteArray TestSinkOutput::serialize() const +{ + return m_settings.serialize(); +} + +bool TestSinkOutput::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureTestSink* message = MsgConfigureTestSink::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureTestSink* messageToGUI = MsgConfigureTestSink::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& TestSinkOutput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int TestSinkOutput::getSampleRate() const +{ + return m_settings.m_sampleRate; +} + +quint64 TestSinkOutput::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +void TestSinkOutput::setCenterFrequency(qint64 centerFrequency) +{ + TestSinkSettings settings = m_settings; + settings.m_centerFrequency = centerFrequency; + + MsgConfigureTestSink* message = MsgConfigureTestSink::create(settings, false); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureTestSink* messageToGUI = MsgConfigureTestSink::create(settings, false); + m_guiMessageQueue->push(messageToGUI); + } +} + +bool TestSinkOutput::handleMessage(const Message& message) +{ + if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "TestSinkOutput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initDeviceEngine()) { + m_deviceAPI->startDeviceEngine(); + } + } + else + { + m_deviceAPI->stopDeviceEngine(); + } + + return true; + } + else if (MsgConfigureTestSink::match(message)) + { + qDebug() << "TestSinkOutput::handleMessage: MsgConfigureTestSink"; + MsgConfigureTestSink& conf = (MsgConfigureTestSink&) message; + applySettings(conf.getSettings(), conf.getForce()); + return true; + } + else + { + return false; + } +} + +void TestSinkOutput::applySettings(const TestSinkSettings& settings, bool force) +{ + QMutexLocker mutexLocker(&m_mutex); + bool forwardChange = false; + + if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency)) + { + m_settings.m_centerFrequency = settings.m_centerFrequency; + forwardChange = true; + } + + if (force || (m_settings.m_sampleRate != settings.m_sampleRate)) + { + m_settings.m_sampleRate = settings.m_sampleRate; + + if (m_testSinkThread) { + m_testSinkThread->setSamplerate(m_settings.m_sampleRate); + } + + forwardChange = true; + } + + if (force || (m_settings.m_log2Interp != settings.m_log2Interp)) + { + m_settings.m_log2Interp = settings.m_log2Interp; + + if (m_testSinkThread) { + m_testSinkThread->setLog2Interpolation(m_settings.m_log2Interp); + } + + forwardChange = true; + } + + if (forwardChange) + { + qDebug("TestSinkOutput::applySettings: forward: m_centerFrequency: %llu m_sampleRate: %llu m_log2Interp: %d", + m_settings.m_centerFrequency, + m_settings.m_sampleRate, + m_settings.m_log2Interp); + DSPSignalNotification *notif = new DSPSignalNotification(m_settings.m_sampleRate, m_settings.m_centerFrequency); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } +} + +int TestSinkOutput::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int TestSinkOutput::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgStartStop *messagetoGui = MsgStartStop::create(run); + m_guiMessageQueue->push(messagetoGui); + } + + return 200; +} + + diff --git a/plugins/samplesink/testsink/testsinkoutput.h b/plugins/samplesink/testsink/testsinkoutput.h new file mode 100644 index 000000000..06f855b3f --- /dev/null +++ b/plugins/samplesink/testsink/testsinkoutput.h @@ -0,0 +1,118 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// 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_TESTSINKOUTPUT_H +#define INCLUDE_TESTSINKOUTPUT_H + +#include +#include +#include +#include +#include + +#include "dsp/devicesamplesink.h" +#include "testsinksettings.h" + +class TestSinkThread; +class DeviceAPI; + +class TestSinkOutput : public DeviceSampleSink { +public: + class MsgConfigureTestSink : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const TestSinkSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureTestSink* create(const TestSinkSettings& settings, bool force) + { + return new MsgConfigureTestSink(settings, force); + } + + private: + TestSinkSettings m_settings; + bool m_force; + + MsgConfigureTestSink(const TestSinkSettings& 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) + { } + }; + + TestSinkOutput(DeviceAPI *deviceAPI); + virtual ~TestSinkOutput(); + 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 void setSampleRate(int sampleRate) { (void) sampleRate; } + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + virtual bool handleMessage(const Message& message); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + +private: + DeviceAPI *m_deviceAPI; + QMutex m_mutex; + TestSinkSettings m_settings; + std::ofstream m_ofstream; + TestSinkThread* m_testSinkThread; + QString m_deviceDescription; + const QTimer& m_masterTimer; + + void applySettings(const TestSinkSettings& settings, bool force = false); +}; + +#endif // INCLUDE_TESTSINKOUTPUT_H diff --git a/plugins/samplesink/testsink/testsinkplugin.cpp b/plugins/samplesink/testsink/testsinkplugin.cpp new file mode 100644 index 000000000..8680aa21b --- /dev/null +++ b/plugins/samplesink/testsink/testsinkplugin.cpp @@ -0,0 +1,143 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// 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" + +#ifdef SERVER_MODE +#include "testsinkoutput.h" +#else +#include "testsinkgui.h" +#endif +#include "testsinkplugin.h" + +const PluginDescriptor TestSinkPlugin::m_pluginDescriptor = { + QString("Test sink output"), + QString("4.11.12"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString TestSinkPlugin::m_hardwareID = "TestSink"; +const QString TestSinkPlugin::m_deviceTypeID = TESTSINK_DEVICE_TYPE_ID; + +TestSinkPlugin::TestSinkPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& TestSinkPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void TestSinkPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSink(m_deviceTypeID, this); +} + +void TestSinkPlugin::enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices) +{ + if (listedHwIds.contains(m_hardwareID)) { // check if it was done + return; + } + + originDevices.append(OriginDevice( + "TestSink", + m_hardwareID, + QString::null, + 0, // Sequence + 0, // nb Rx + 1 // nb Tx + )); + + listedHwIds.append(m_hardwareID); +} + +PluginInterface::SamplingDevices TestSinkPlugin::enumSampleSinks(const OriginDevices& originDevices) +{ + SamplingDevices result; + + for (OriginDevices::const_iterator it = originDevices.begin(); it != originDevices.end(); ++it) + { + if (it->hardwareId == m_hardwareID) + { + result.append(SamplingDevice( + it->displayableName, + it->hardwareId, + m_deviceTypeID, + it->serial, + it->sequence, + PluginInterface::SamplingDevice::BuiltInDevice, + PluginInterface::SamplingDevice::StreamSingleTx, + 1, + 0 + )); + } + } + + return result; +} + +#ifdef SERVER_MODE +PluginInstanceGUI* TestSinkPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + (void) sinkId; + (void) widget; + (void) deviceUISet; + return 0; +} +#else +PluginInstanceGUI* TestSinkPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if(sinkId == m_deviceTypeID) + { + TestSinkGui* gui = new TestSinkGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return 0; + } +} +#endif + +DeviceSampleSink* TestSinkPlugin::createSampleSinkPluginInstance(const QString& sinkId, DeviceAPI *deviceAPI) +{ + if(sinkId == m_deviceTypeID) + { + TestSinkOutput* output = new TestSinkOutput(deviceAPI); + return output; + } + else + { + return 0; + } + +} + diff --git a/plugins/samplesink/testsink/testsinkplugin.h b/plugins/samplesink/testsink/testsinkplugin.h new file mode 100644 index 000000000..cd55873b8 --- /dev/null +++ b/plugins/samplesink/testsink/testsinkplugin.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// 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_TESTSINKPLUGIN_H +#define INCLUDE_TESTSINKPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +#define TESTSINK_DEVICE_TYPE_ID "sdrangel.samplesink.testsink" + +class PluginAPI; +class DeviceAPI; + +class TestSinkPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID TESTSINK_DEVICE_TYPE_ID) + +public: + explicit TestSinkPlugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices); + virtual SamplingDevices enumSampleSinks(const OriginDevices& originDevices); + virtual PluginInstanceGUI* createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleSink* createSampleSinkPluginInstance(const QString& sinkId, DeviceAPI *deviceAPI); + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif // INCLUDE_TESTSINKPLUGIN_H diff --git a/plugins/samplesink/testsink/testsinksettings.cpp b/plugins/samplesink/testsink/testsinksettings.cpp new file mode 100644 index 000000000..a73ffff14 --- /dev/null +++ b/plugins/samplesink/testsink/testsinksettings.cpp @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// 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 "testsinksettings.h" + +TestSinkSettings::TestSinkSettings() +{ + resetToDefaults(); +} + +void TestSinkSettings::resetToDefaults() +{ + m_centerFrequency = 435000*1000; + m_sampleRate = 48000; + m_log2Interp = 0; +} + +QByteArray TestSinkSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeU64(1, m_sampleRate); + s.writeU32(2, m_log2Interp); + + return s.final(); +} + +bool TestSinkSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + d.readU64(1, &m_sampleRate, 48000); + d.readU32(2, &m_log2Interp, 0); + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/samplesink/testsink/testsinksettings.h b/plugins/samplesink/testsink/testsinksettings.h new file mode 100644 index 000000000..42fb1a327 --- /dev/null +++ b/plugins/samplesink/testsink/testsinksettings.h @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// 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_TESTSINK_TESTSINKSETTINGS_H_ +#define PLUGINS_SAMPLESINK_TESTSINK_TESTSINKSETTINGS_H_ + +#include + +struct TestSinkSettings { + quint64 m_centerFrequency; + quint64 m_sampleRate; + quint32 m_log2Interp; + + TestSinkSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* PLUGINS_SAMPLESINK_TESTSINK_TESTSINKSETTINGS_H_ */ diff --git a/plugins/samplesink/testsink/testsinkthread.cpp b/plugins/samplesink/testsink/testsinkthread.cpp new file mode 100644 index 000000000..8c43feef7 --- /dev/null +++ b/plugins/samplesink/testsink/testsinkthread.cpp @@ -0,0 +1,219 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// 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/samplesourcefifo.h" +#include "testsinkthread.h" + +TestSinkThread::TestSinkThread(SampleSourceFifo* sampleFifo, QObject* parent) : + QThread(parent), + m_running(false), + m_bufsize(0), + m_samplesChunkSize(0), + m_sampleFifo(sampleFifo), + m_samplesCount(0), + m_samplerate(0), + m_log2Interpolation(0), + m_throttlems(TESTSINK_THROTTLE_MS), + m_maxThrottlems(50), + m_throttleToggle(false), + m_buf(0) +{ +} + +TestSinkThread::~TestSinkThread() +{ + if (m_running) { + stopWork(); + } + + if (m_buf) delete[] m_buf; +} + +void TestSinkThread::startWork() +{ + qDebug() << "TestSinkThread::startWork: "; + m_maxThrottlems = 0; + m_startWaitMutex.lock(); + m_elapsedTimer.start(); + start(); + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + m_startWaitMutex.unlock(); +} + +void TestSinkThread::stopWork() +{ + qDebug() << "TestSinkThread::stopWork"; + m_running = false; + wait(); +} + +void TestSinkThread::setSamplerate(int samplerate) +{ + if (samplerate != m_samplerate) + { + qDebug() << "TestSinkThread::setSamplerate:" + << " new:" << samplerate + << " old:" << m_samplerate; + + bool wasRunning = false; + + if (m_running) + { + stopWork(); + wasRunning = true; + } + + // resize sample FIFO + if (m_sampleFifo) { + m_sampleFifo->resize(samplerate); // 1s buffer + } + + // resize output buffer + if (m_buf) delete[] m_buf; + m_buf = new int16_t[samplerate*(1< 6)) { + return; + } + + if (log2Interpolation != m_log2Interpolation) + { + qDebug() << "TestSinkThread::setLog2Interpolation:" + << " new:" << log2Interpolation + << " old:" << m_log2Interpolation; + + bool wasRunning = false; + + if (m_running) + { + stopWork(); + wasRunning = true; + } + + // resize output buffer + if (m_buf) delete[] m_buf; + m_buf = new int16_t[m_samplerate*(1< m_maxThrottlems) +// { +// qDebug("FileSinkThread::tick: m_maxThrottlems: %d", m_maxThrottlems); +// m_maxThrottlems = m_throttlems; +// } + + SampleVector::iterator readUntil; + + m_sampleFifo->readAdvance(readUntil, m_samplesChunkSize); + SampleVector::iterator beginRead = readUntil - m_samplesChunkSize; + m_samplesCount += m_samplesChunkSize; + int chunkSize = std::min((int) m_samplesChunkSize, m_samplerate); + + if (m_log2Interpolation == 0) + { + m_interpolators.interpolate1(&beginRead, m_buf, 2*chunkSize); + //m_ofstream->write(reinterpret_cast(&(*beginRead)), m_samplesChunkSize*sizeof(Sample)); + } + else + { + + switch (m_log2Interpolation) + { + case 1: + m_interpolators.interpolate2_cen(&beginRead, m_buf, chunkSize*(1<write(reinterpret_cast(m_buf), m_samplesChunkSize*(1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_TESTSINKTHREAD_H +#define INCLUDE_TESTSINKTHREAD_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dsp/inthalfbandfilter.h" +#include "dsp/interpolators.h" + +#define TESTSINK_THROTTLE_MS 50 + +class SampleSourceFifo; + +class TestSinkThread : public QThread { + Q_OBJECT + +public: + TestSinkThread(SampleSourceFifo* sampleFifo, QObject* parent = nullptr); + ~TestSinkThread(); + + void startWork(); + void stopWork(); + void setSamplerate(int samplerate); + void setLog2Interpolation(int log2Interpolation); + void setBuffer(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; } + + void connectTimer(const QTimer& timer); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + volatile bool m_running; + + std::size_t m_bufsize; + unsigned int m_samplesChunkSize; + SampleSourceFifo* m_sampleFifo; + std::size_t m_samplesCount; + + int m_samplerate; + int m_log2Interpolation; + int m_throttlems; + int m_maxThrottlems; + QElapsedTimer m_elapsedTimer; + bool m_throttleToggle; + + Interpolators m_interpolators; + int16_t *m_buf; + + void run(); + +private slots: + void tick(); +}; + +#endif // INCLUDE_TESTSINKTHREAD_H