From ac6c3b08f29b2f82768b582ee3e3f786c15e6bf7 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 29 Apr 2020 17:41:17 +0200 Subject: [PATCH] Websocket spectrum: first implementation --- plugins/channelrx/demodbfm/bfmdemodgui.cpp | 2 + plugins/channelrx/udpsink/udpsinkgui.cpp | 2 + plugins/channeltx/udpsource/udpsourcegui.cpp | 2 + sdrbase/CMakeLists.txt | 7 +- sdrbase/dsp/spectrumvis.cpp | 340 +++++++++++++++++-- sdrbase/dsp/spectrumvis.h | 46 ++- sdrbase/websockets/wsspectrum.cpp | 164 +++++++++ sdrbase/websockets/wsspectrum.h | 83 +++++ sdrgui/device/deviceuiset.cpp | 2 +- sdrgui/gui/glspectrumgui.cpp | 6 + sdrgui/mainwindow.h | 2 - 11 files changed, 623 insertions(+), 33 deletions(-) create mode 100644 sdrbase/websockets/wsspectrum.cpp create mode 100644 sdrbase/websockets/wsspectrum.h diff --git a/plugins/channelrx/demodbfm/bfmdemodgui.cpp b/plugins/channelrx/demodbfm/bfmdemodgui.cpp index beda6123f..2cad95cab 100644 --- a/plugins/channelrx/demodbfm/bfmdemodgui.cpp +++ b/plugins/channelrx/demodbfm/bfmdemodgui.cpp @@ -399,6 +399,8 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban m_spectrumVis->configure( m_spectrumVis->getInputMessageQueue(), 64, // FFT size + 0, // Ref level (dB) + 100, // Power range (dB) 10, // overlapping % 0, // number of averaging samples SpectrumVis::AvgModeNone, // no averaging diff --git a/plugins/channelrx/udpsink/udpsinkgui.cpp b/plugins/channelrx/udpsink/udpsinkgui.cpp index 4985d17e1..79bee4979 100644 --- a/plugins/channelrx/udpsink/udpsinkgui.cpp +++ b/plugins/channelrx/udpsink/udpsinkgui.cpp @@ -189,6 +189,8 @@ UDPSinkGUI::UDPSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS ui->glSpectrum->setDisplayMaxHold(true); m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, // FFT size + 0, // Ref level (dB) + 100, // Power range (dB) 10, // overlapping % 0, // number of averaging samples SpectrumVis::AvgModeNone, // no averaging diff --git a/plugins/channeltx/udpsource/udpsourcegui.cpp b/plugins/channeltx/udpsource/udpsourcegui.cpp index dd28b6951..e5aa44a2e 100644 --- a/plugins/channeltx/udpsource/udpsourcegui.cpp +++ b/plugins/channeltx/udpsource/udpsourcegui.cpp @@ -146,6 +146,8 @@ UDPSourceGUI::UDPSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb ui->glSpectrum->setDisplayMaxHold(true); m_spectrumVis->configure(m_spectrumVis->getInputMessageQueue(), 64, // FFT size + 0, // Ref level (dB) + 100, // Power range (dB) 10, // overlapping % 0, // number of averaging samples SpectrumVis::AvgModeNone, // no averaging diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 8348681b3..0cb40c14b 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -163,6 +163,8 @@ set(sdrbase_SOURCES webapi/webapirequestmapper.cpp webapi/webapiserver.cpp + websockets/wsspectrum.cpp + mainparser.cpp resources/webapi.qrc @@ -310,7 +312,9 @@ set(sdrbase_HEADERS webapi/webapiadapterbase.h webapi/webapiadapterinterface.h webapi/webapirequestmapper.h - webapi/webapiserver + webapi/webapiserver.h + + websockets/wsspectrum.h mainparser.h ) @@ -343,6 +347,7 @@ target_link_libraries(sdrbase ${sdrbase_LIMERFE_LIB} Qt5::Core Qt5::Multimedia + Qt5::WebSockets httpserver qrtplib swagger diff --git a/sdrbase/dsp/spectrumvis.cpp b/sdrbase/dsp/spectrumvis.cpp index 5de0d409f..5931d129f 100644 --- a/sdrbase/dsp/spectrumvis.cpp +++ b/sdrbase/dsp/spectrumvis.cpp @@ -36,6 +36,7 @@ inline double log2f(double n) #endif MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureSpectrumVis, Message) +MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureDSP, Message) MESSAGE_CLASS_DEFINITION(SpectrumVis::MsgConfigureScalingFactor, Message) const Real SpectrumVis::m_mult = (10.0f / log2f(10.0f)); @@ -53,12 +54,15 @@ SpectrumVis::SpectrumVis(Real scalef, GLSpectrumInterface* glSpectrum) : m_averageNb(0), m_avgMode(AvgModeNone), m_linear(false), + m_centerFrequency(0), + m_sampleRate(48000), m_ofs(0), m_powFFTDiv(1.0), m_mutex(QMutex::Recursive) { setObjectName("SpectrumVis"); - handleConfigure(1024, 0, 0, AvgModeNone, FFTWindow::BlackmanHarris, false); + handleConfigure(1024, 0, 100, 0, 0, AvgModeNone, FFTWindow::BlackmanHarris, false); + m_wsSpectrum.openSocket(); // FIXME: conditional } SpectrumVis::~SpectrumVis() @@ -69,17 +73,34 @@ SpectrumVis::~SpectrumVis() void SpectrumVis::configure(MessageQueue* msgQueue, int fftSize, + float refLevel, + float powerRange, int overlapPercent, unsigned int averagingNb, AvgMode averagingMode, FFTWindow::Function window, bool linear) { - MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis(fftSize, overlapPercent, averagingNb, averagingMode, window, linear); + MsgConfigureSpectrumVis* cmd = new MsgConfigureSpectrumVis( + fftSize, + refLevel, + powerRange, + overlapPercent, + averagingNb, + averagingMode, + window, + linear + ); msgQueue->push(cmd); } -void SpectrumVis::setScalef(MessageQueue* msgQueue, Real scalef) +void SpectrumVis::configureDSP(uint64_t centerFrequency, int sampleRate) +{ + MsgConfigureDSP* cmd = new MsgConfigureDSP(centerFrequency, sampleRate); + getInputMessageQueue()->push(cmd); +} + +void SpectrumVis::setScalef(Real scalef) { MsgConfigureScalingFactor* cmd = new MsgConfigureScalingFactor(scalef); getInputMessageQueue()->push(cmd); @@ -106,11 +127,189 @@ void SpectrumVis::feedTriggered(const SampleVector::const_iterator& triggerPoint }*/ } +void SpectrumVis::feed(const Complex *begin, unsigned int length) +{ + if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) { + return; + } + + if (!m_mutex.tryLock(0)) { // prevent conflicts with configuration process + return; + } + + Complex c; + Real v; + + if (m_avgMode == AvgModeNone) + { + for (unsigned int i = 0; i < m_fftSize; i++) + { + if (i < length) { + c = begin[i]; + } else { + c = Complex{0,0}; + } + + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; + m_powerSpectrum[i] = v; + } + + // send new data to visualisation + if (m_glSpectrum) { + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_fftSize, + m_refLevel, + m_powerRange, + m_centerFrequency, + m_sampleRate, + m_linear + ); + } + } + else if (m_avgMode == AvgModeMovingAvg) + { + for (unsigned int i = 0; i < m_fftSize; i++) + { + if (i < length) { + c = begin[i]; + } else { + c = Complex{0,0}; + } + + v = c.real() * c.real() + c.imag() * c.imag(); + v = m_movingAverage.storeAndGetAvg(v, i); + v = m_linear ? v/m_powFFTDiv : m_mult * log2f(v) + m_ofs; + m_powerSpectrum[i] = v; + } + + // send new data to visualisation + if (m_glSpectrum) { + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_fftSize, + m_refLevel, + m_powerRange, + m_centerFrequency, + m_sampleRate, + m_linear + ); + } + + m_movingAverage.nextAverage(); + } + else if (m_avgMode == AvgModeFixedAvg) + { + double avg; + + for (unsigned int i = 0; i < m_fftSize; i++) + { + if (i < length) { + c = begin[i]; + } else { + c = Complex{0,0}; + } + + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_fixedAverage.storeAndGetAvg(avg, v, i)) + { + avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; + m_powerSpectrum[i] = avg; + } + } + + // result available + if (m_fixedAverage.nextAverage()) + { + // send new data to visualisation + if (m_glSpectrum) { + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_fftSize, + m_refLevel, + m_powerRange, + m_centerFrequency, + m_sampleRate, + m_linear + ); + } + } + } + else if (m_avgMode == AvgModeMax) + { + double max; + + for (unsigned int i = 0; i < m_fftSize; i++) + { + if (i < length) { + c = begin[i]; + } else { + c = Complex{0,0}; + } + + v = c.real() * c.real() + c.imag() * c.imag(); + + // result available + if (m_max.storeAndGetMax(max, v, i)) + { + max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; + m_powerSpectrum[i] = max; + } + } + + // result available + if (m_max.nextMax()) + { + // send new data to visualisation + if (m_glSpectrum) { + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_fftSize, + m_refLevel, + m_powerRange, + m_centerFrequency, + m_sampleRate, + m_linear + ); + } + } + } + + m_mutex.unlock(); +} + void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleVector::const_iterator& end, bool positiveOnly) { // if no visualisation is set, send the samples to /dev/null - if (m_glSpectrum == 0) { + if (!m_glSpectrum && !m_wsSpectrum.socketOpened()) { return; } @@ -177,7 +376,23 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV } // send new data to visualisation - m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + if (m_glSpectrum) { + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_fftSize, + m_refLevel, + m_powerRange, + m_centerFrequency, + m_sampleRate, + m_linear + ); + } } else if (m_avgMode == AvgModeMovingAvg) { @@ -212,7 +427,24 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV } // send new data to visualisation - m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + if (m_glSpectrum) { + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_fftSize, + m_refLevel, + m_powerRange, + m_centerFrequency, + m_sampleRate, + m_linear + ); + } + m_movingAverage.nextAverage(); } else if (m_avgMode == AvgModeFixedAvg) @@ -241,8 +473,9 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV c = fftOut[i + halfSize]; v = c.real() * c.real() + c.imag() * c.imag(); + // result available if (m_fixedAverage.storeAndGetAvg(avg, v, i+halfSize)) - { // result available + { avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; m_powerSpectrum[i] = avg; } @@ -250,16 +483,36 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV c = fftOut[i]; v = c.real() * c.real() + c.imag() * c.imag(); + // result available if (m_fixedAverage.storeAndGetAvg(avg, v, i)) - { // result available + { avg = m_linear ? avg/m_powFFTDiv : m_mult * log2f(avg) + m_ofs; m_powerSpectrum[i + halfSize] = avg; } } } - if (m_fixedAverage.nextAverage()) { // result available - m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); // send new data to visualisation + // result available + if (m_fixedAverage.nextAverage()) + { + // send new data to visualisation + if (m_glSpectrum) { + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_fftSize, + m_refLevel, + m_powerRange, + m_centerFrequency, + m_sampleRate, + m_linear + ); + } } } else if (m_avgMode == AvgModeMax) @@ -288,8 +541,9 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV c = fftOut[i + halfSize]; v = c.real() * c.real() + c.imag() * c.imag(); + // result available if (m_max.storeAndGetMax(max, v, i+halfSize)) - { // result available + { max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; m_powerSpectrum[i] = max; } @@ -297,16 +551,36 @@ void SpectrumVis::feed(const SampleVector::const_iterator& cbegin, const SampleV c = fftOut[i]; v = c.real() * c.real() + c.imag() * c.imag(); + // result available if (m_max.storeAndGetMax(max, v, i)) - { // result available + { max = m_linear ? max/m_powFFTDiv : m_mult * log2f(max) + m_ofs; m_powerSpectrum[i + halfSize] = max; } } } - if (m_max.nextMax()) { // result available - m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); // send new data to visualisation + // result available + if (m_max.nextMax()) + { + // send new data to visualisation + if (m_glSpectrum) { + m_glSpectrum->newSpectrum(m_powerSpectrum, m_fftSize); + } + + // web socket spectrum connections + if (m_wsSpectrum.socketOpened()) + { + m_wsSpectrum.newSpectrum( + m_powerSpectrum, + m_fftSize, + m_refLevel, + m_powerRange, + m_centerFrequency, + m_sampleRate, + m_linear + ); + } } } @@ -347,6 +621,8 @@ bool SpectrumVis::handleMessage(const Message& message) { MsgConfigureSpectrumVis& conf = (MsgConfigureSpectrumVis&) message; handleConfigure(conf.getFFTSize(), + conf.getRefLevel(), + conf.getPowerRange(), conf.getOverlapPercent(), conf.getAverageNb(), conf.getAvgMode(), @@ -354,6 +630,12 @@ bool SpectrumVis::handleMessage(const Message& message) conf.getLinear()); return true; } + else if (MsgConfigureDSP::match(message)) + { + MsgConfigureDSP& conf = (MsgConfigureDSP&) message; + handleConfigureDSP(conf.getCenterFrequency(), conf.getSampleRate()); + return true; + } else if (MsgConfigureScalingFactor::match(message)) { MsgConfigureScalingFactor& conf = (MsgConfigureScalingFactor&) message; @@ -367,6 +649,8 @@ bool SpectrumVis::handleMessage(const Message& message) } void SpectrumVis::handleConfigure(int fftSize, + float refLevel, + float powerRange, int overlapPercent, unsigned int averageNb, AvgMode averagingMode, @@ -377,25 +661,20 @@ void SpectrumVis::handleConfigure(int fftSize, // fftSize, overlapPercent, averageNb, (int) averagingMode, (int) window, linear ? "true" : "false"); QMutexLocker mutexLocker(&m_mutex); - if (fftSize > MAX_FFT_SIZE) - { + if (fftSize > MAX_FFT_SIZE) { fftSize = MAX_FFT_SIZE; - } - else if (fftSize < 64) - { + } else if (fftSize < 64) { fftSize = 64; } - if (overlapPercent > 100) - { + m_refLevel = refLevel; + m_powerRange = powerRange; + + if (overlapPercent > 100) { m_overlapPercent = 100; - } - else if (overlapPercent < 0) - { + } else if (overlapPercent < 0) { m_overlapPercent = 0; - } - else - { + } else { m_overlapPercent = overlapPercent; } @@ -417,6 +696,13 @@ void SpectrumVis::handleConfigure(int fftSize, m_powFFTDiv = m_fftSize*m_fftSize; } +void SpectrumVis::handleConfigureDSP(uint64_t centerFrequency, int sampleRate) +{ + QMutexLocker mutexLocker(&m_mutex); + m_centerFrequency = centerFrequency; + m_sampleRate = sampleRate; +} + void SpectrumVis::handleScalef(Real scalef) { QMutexLocker mutexLocker(&m_mutex); diff --git a/sdrbase/dsp/spectrumvis.h b/sdrbase/dsp/spectrumvis.h index 819c3f53c..1159b185f 100644 --- a/sdrbase/dsp/spectrumvis.h +++ b/sdrbase/dsp/spectrumvis.h @@ -21,8 +21,9 @@ #ifndef INCLUDE_SPECTRUMVIS_H #define INCLUDE_SPECTRUMVIS_H -#include #include + +#include "dsp/basebandsamplesink.h" #include "dsp/fftengine.h" #include "dsp/fftwindow.h" #include "export.h" @@ -30,6 +31,7 @@ #include "util/movingaverage2d.h" #include "util/fixedaverage2d.h" #include "util/max2d.h" +#include "websockets/wsspectrum.h" class GLSpectrumInterface; class MessageQueue; @@ -51,6 +53,8 @@ public: public: MsgConfigureSpectrumVis( int fftSize, + float refLevel, + float powerRange, int overlapPercent, unsigned int averageNb, AvgMode avgMode, @@ -59,6 +63,8 @@ public: Message(), m_fftSize(fftSize), m_overlapPercent(overlapPercent), + m_refLevel(refLevel), + m_powerRange(powerRange), m_averageNb(averageNb), m_avgMode(avgMode), m_window(window), @@ -66,6 +72,8 @@ public: {} int getFFTSize() const { return m_fftSize; } + float getRefLevel() const { return m_refLevel; } + float getPowerRange() const { return m_powerRange; } int getOverlapPercent() const { return m_overlapPercent; } unsigned int getAverageNb() const { return m_averageNb; } SpectrumVis::AvgMode getAvgMode() const { return m_avgMode; } @@ -75,12 +83,33 @@ public: private: int m_fftSize; int m_overlapPercent; + float m_refLevel; + float m_powerRange; unsigned int m_averageNb; SpectrumVis::AvgMode m_avgMode; FFTWindow::Function m_window; bool m_linear; }; + class MsgConfigureDSP : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + MsgConfigureDSP(uint64_t centerFrequency, int sampleRate) : + Message(), + m_centerFrequency(centerFrequency), + m_sampleRate(sampleRate) + {} + + uint64_t getCenterFrequency() const { return m_centerFrequency; } + int getSampleRate() const { return m_sampleRate; } + + private: + uint64_t m_centerFrequency; + int m_sampleRate; + }; + class MsgConfigureScalingFactor : public Message { MESSAGE_CLASS_DECLARATION @@ -102,12 +131,15 @@ public: void configure(MessageQueue* msgQueue, int fftSize, + float refLevel, + float powerRange, int overlapPercent, unsigned int averagingNb, AvgMode averagingMode, FFTWindow::Function window, bool m_linear); - void setScalef(MessageQueue* msgQueue, Real scalef); + void configureDSP(uint64_t centerFrequency, int sampleRate); + void setScalef(Real scalef); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); void feedTriggered(const SampleVector::const_iterator& triggerPoint, const SampleVector::const_iterator& end, bool positiveOnly); @@ -124,6 +156,8 @@ private: std::vector m_powerSpectrum; std::size_t m_fftSize; + float m_refLevel; + float m_powerRange; std::size_t m_overlapPercent; std::size_t m_overlapSize; std::size_t m_refillSize; @@ -132,6 +166,7 @@ private: Real m_scalef; GLSpectrumInterface* m_glSpectrum; + WSSpectrum m_wsSpectrum; MovingAverage2D m_movingAverage; FixedAverage2D m_fixedAverage; Max2D m_max; @@ -139,6 +174,9 @@ private: AvgMode m_avgMode; bool m_linear; + uint64_t m_centerFrequency; + int m_sampleRate; + Real m_ofs; Real m_powFFTDiv; static const Real m_mult; @@ -146,11 +184,15 @@ private: QMutex m_mutex; void handleConfigure(int fftSize, + float refLevel, + float powerRange, int overlapPercent, unsigned int averageNb, AvgMode averagingMode, FFTWindow::Function window, bool linear); + void handleConfigureDSP(uint64_t centerFrequency, + int sampleRate); void handleScalef(Real scalef); }; diff --git a/sdrbase/websockets/wsspectrum.cpp b/sdrbase/websockets/wsspectrum.cpp new file mode 100644 index 000000000..b1d6e8045 --- /dev/null +++ b/sdrbase/websockets/wsspectrum.cpp @@ -0,0 +1,164 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 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 "wsspectrum.h" + +WSSpectrum::WSSpectrum(QObject *parent) : + QObject(parent), + m_listeningAddress(QHostAddress::LocalHost), + m_port(8887), + m_webSocketServer(nullptr) +{ + m_timer.start(); +} + +WSSpectrum::~WSSpectrum() +{ + closeSocket(); +} + +void WSSpectrum::openSocket() +{ + m_webSocketServer = new QWebSocketServer( + QStringLiteral("Spectrum Server"), + QWebSocketServer::NonSecureMode, + this); + + if (m_webSocketServer->listen(m_listeningAddress, m_port)) + { + qDebug() << "WSSpectrum::openSocket: spectrum server listening at " << m_listeningAddress.toString() << " on port " << m_port; + connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &WSSpectrum::onNewConnection); + } + else + { + qInfo("WSSpectrum::openSocket: cannot start spectrum server at %s on port %u", qPrintable(m_listeningAddress.toString()), m_port); + } +} + +void WSSpectrum::closeSocket() +{ + if (m_webSocketServer) + { + delete m_webSocketServer; + m_webSocketServer = nullptr; + } +} + +bool WSSpectrum::socketOpened() +{ + return m_webSocketServer && m_webSocketServer->isListening(); +} + +QString WSSpectrum::getWebSocketIdentifier(QWebSocket *peer) +{ + return QStringLiteral("%1:%2").arg(peer->peerAddress().toString(), QString::number(peer->peerPort())); +} + +void WSSpectrum::onNewConnection() +{ + auto pSocket = m_webSocketServer->nextPendingConnection(); + qDebug() << " WSSpectrum::onNewConnection: " << getWebSocketIdentifier(pSocket) << " connected"; + pSocket->setParent(this); + + connect(pSocket, &QWebSocket::textMessageReceived, this, &WSSpectrum::processClientMessage); + connect(pSocket, &QWebSocket::disconnected, this, &WSSpectrum::socketDisconnected); + + m_clients << pSocket; +} + +void WSSpectrum::processClientMessage(const QString &message) +{ + qDebug() << "WSSpectrum::processClientMessage: " << message; +} + +void WSSpectrum::socketDisconnected() +{ + QWebSocket *pClient = qobject_cast(sender()); + qDebug() << getWebSocketIdentifier(pClient) << " disconnected"; + + if (pClient) + { + m_clients.removeAll(pClient); + pClient->deleteLater(); + } +} + +void WSSpectrum::newSpectrum( + const std::vector& spectrum, + int fftSize, + float refLevel, + float powerRange, + uint64_t centerFrequency, + int bandwidth, + bool linear +) +{ + if (m_timer.elapsed() < 200) { // Max 5 frames per second + return; + } + + qint64 elapsed = m_timer.restart(); + QWebSocket *pSender = qobject_cast(sender()); + QByteArray payload; + + buildPayload( + payload, + spectrum, + fftSize, + elapsed, + refLevel, + powerRange, + centerFrequency, + bandwidth, + linear + ); + //qDebug() << "WSSpectrum::newSpectrum: " << payload.size() << " bytes in " << elapsed << " ms"; + + for (QWebSocket *pClient : qAsConst(m_clients)) { + pClient->sendBinaryMessage(payload); + } +} + +void WSSpectrum::buildPayload( + QByteArray& bytes, + const std::vector& spectrum, + int fftSize, + int64_t fftTimeMs, + float refLevel, + float powerRange, + uint64_t centerFrequency, + int bandwidth, + bool linear +) +{ + QBuffer buffer(&bytes); + buffer.open(QIODevice::WriteOnly); + buffer.write((char*) &fftSize, sizeof(int)); + buffer.write((char*) &fftTimeMs, sizeof(int64_t)); + buffer.write((char*) &refLevel, sizeof(float)); + buffer.write((char*) &powerRange, sizeof(float)); + buffer.write((char*) ¢erFrequency, sizeof(uint64_t)); + buffer.write((char*) &bandwidth, sizeof(int)); + int linearInt = linear ? 1 : 0; + buffer.write((char*) &linearInt, sizeof(int)); + buffer.write((char*) spectrum.data(), fftSize*sizeof(Real)); + buffer.close(); +} diff --git a/sdrbase/websockets/wsspectrum.h b/sdrbase/websockets/wsspectrum.h new file mode 100644 index 000000000..d1b131982 --- /dev/null +++ b/sdrbase/websockets/wsspectrum.h @@ -0,0 +1,83 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 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 SDRBASE_WEBSOCKETS_WSSPECTRUM_H_ +#define SDRBASE_WEBSOCKETS_WSSPECTRUM_H_ + +#include + +#include +#include +#include +#include + +#include "dsp/dsptypes.h" + +#include "export.h" + +class QWebSocketServer; +class QWebSocket; + +class SDRBASE_API WSSpectrum : public QObject +{ + Q_OBJECT +public: + explicit WSSpectrum(QObject *parent = nullptr); + ~WSSpectrum() override; + + void openSocket(); + void closeSocket(); + bool socketOpened(); + void setListeningAddress(const QString& address) { m_listeningAddress.setAddress(address); } + void setPort(quint16 port) { m_port = port; } + void newSpectrum( + const std::vector& spectrum, + int fftSize, + float refLevel, + float powerRange, + uint64_t centerFrequency, + int bandwidth, + bool linear + ); + +private slots: + void onNewConnection(); + void processClientMessage(const QString &message); + void socketDisconnected(); + +private: + QHostAddress m_listeningAddress; + quint16 m_port; + QWebSocketServer* m_webSocketServer; + QList m_clients; + QElapsedTimer m_timer; + + static QString getWebSocketIdentifier(QWebSocket *peer); + void buildPayload( + QByteArray& bytes, + const std::vector& spectrum, + int fftSize, + int64_t fftTimeMs, + float refLevel, + float powerRange, + uint64_t centerFrequency, + int bandwidth, + bool linear + ); +}; + +#endif // SDRBASE_WEBSOCKETS_WSSPECTRUM_H_ diff --git a/sdrgui/device/deviceuiset.cpp b/sdrgui/device/deviceuiset.cpp index 6decb780f..5fe086f9c 100644 --- a/sdrgui/device/deviceuiset.cpp +++ b/sdrgui/device/deviceuiset.cpp @@ -74,7 +74,7 @@ DeviceUISet::~DeviceUISet() void DeviceUISet::setSpectrumScalingFactor(float scalef) { - m_spectrumVis->setScalef(m_spectrumVis->getInputMessageQueue(), scalef); + m_spectrumVis->setScalef(scalef); } void DeviceUISet::addChannelMarker(ChannelMarker* channelMarker) diff --git a/sdrgui/gui/glspectrumgui.cpp b/sdrgui/gui/glspectrumgui.cpp index 2158094ae..8ffbbb26f 100644 --- a/sdrgui/gui/glspectrumgui.cpp +++ b/sdrgui/gui/glspectrumgui.cpp @@ -231,6 +231,8 @@ void GLSpectrumGUI::applySettingsVis() { m_spectrumVis->configure(m_messageQueueToVis, m_fftSize, + m_refLevel, + m_powerRange, m_fftOverlap, m_averagingNb, (SpectrumVis::AvgMode) m_averagingMode, @@ -338,6 +340,8 @@ void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index) m_glSpectrum->setReferenceLevel(refLevel); m_glSpectrum->setPowerRange(powerRange); } + + applySettingsVis(); } void GLSpectrumGUI::on_levelRange_currentIndexChanged(int index) @@ -352,6 +356,8 @@ void GLSpectrumGUI::on_levelRange_currentIndexChanged(int index) m_glSpectrum->setReferenceLevel(refLevel); m_glSpectrum->setPowerRange(powerRange); } + + applySettingsVis(); } void GLSpectrumGUI::on_decay_valueChanged(int index) diff --git a/sdrgui/mainwindow.h b/sdrgui/mainwindow.h index c620bdde6..a967ab0e7 100644 --- a/sdrgui/mainwindow.h +++ b/sdrgui/mainwindow.h @@ -37,8 +37,6 @@ class DSPEngine; class DSPDeviceSourceEngine; class DSPDeviceSinkEngine; class Indicator; -class SpectrumVis; -class GLSpectrum; class GLSpectrumGUI; class ChannelWindow; class PluginAPI;