From 91b24a7c90e2f2bcd5beee09ab817cbd8a5c9353 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 8 Dec 2019 10:09:24 +0100 Subject: [PATCH] Rx plgins: refactoring of classes (3) --- plugins/channelrx/demoddatv/datvdemod.h | 2 - plugins/channelrx/demoddatv/datvdemodgui.h | 3 - plugins/channelrx/demodfreedv/CMakeLists.txt | 6 +- plugins/channelrx/demodfreedv/freedvdemod.cpp | 715 ++---------------- plugins/channelrx/demodfreedv/freedvdemod.h | 314 +------- .../demodfreedv/freedvdemodbaseband.cpp | 203 +++++ .../demodfreedv/freedvdemodbaseband.h | 120 +++ .../channelrx/demodfreedv/freedvdemodgui.cpp | 7 +- .../channelrx/demodfreedv/freedvdemodsink.cpp | 565 ++++++++++++++ .../channelrx/demodfreedv/freedvdemodsink.h | 211 ++++++ .../channelrx/demodfreedv/freedvplugin.cpp | 2 +- plugins/channelrx/localsink/CMakeLists.txt | 60 +- plugins/channelrx/localsink/localsink.cpp | 205 ++--- plugins/channelrx/localsink/localsink.h | 67 +- .../channelrx/localsink/localsinkbaseband.cpp | 181 +++++ .../channelrx/localsink/localsinkbaseband.h | 147 ++++ plugins/channelrx/localsink/localsinkgui.cpp | 62 +- plugins/channelrx/localsink/localsinkgui.h | 5 +- plugins/channelrx/localsink/localsinkgui.ui | 20 + .../channelrx/localsink/localsinkplugin.cpp | 2 +- .../channelrx/localsink/localsinksettings.cpp | 1 + .../channelrx/localsink/localsinksettings.h | 1 + plugins/channelrx/localsink/localsinksink.cpp | 99 +++ plugins/channelrx/localsink/localsinksink.h | 58 ++ plugins/channelrx/udpsink/CMakeLists.txt | 8 +- plugins/channelrx/udpsink/udpsink.cpp | 588 ++------------ plugins/channelrx/udpsink/udpsink.h | 265 +------ plugins/channelrx/udpsink/udpsinkbaseband.cpp | 174 +++++ plugins/channelrx/udpsink/udpsinkbaseband.h | 108 +++ plugins/channelrx/udpsink/udpsinkgui.cpp | 17 +- plugins/channelrx/udpsink/udpsinkplugin.cpp | 2 +- plugins/channelrx/udpsink/udpsinksink.cpp | 510 +++++++++++++ plugins/channelrx/udpsink/udpsinksink.h | 232 ++++++ 33 files changed, 2960 insertions(+), 2000 deletions(-) create mode 100644 plugins/channelrx/demodfreedv/freedvdemodbaseband.cpp create mode 100644 plugins/channelrx/demodfreedv/freedvdemodbaseband.h create mode 100644 plugins/channelrx/demodfreedv/freedvdemodsink.cpp create mode 100644 plugins/channelrx/demodfreedv/freedvdemodsink.h create mode 100644 plugins/channelrx/localsink/localsinkbaseband.cpp create mode 100644 plugins/channelrx/localsink/localsinkbaseband.h create mode 100644 plugins/channelrx/localsink/localsinksink.cpp create mode 100644 plugins/channelrx/localsink/localsinksink.h create mode 100644 plugins/channelrx/udpsink/udpsinkbaseband.cpp create mode 100644 plugins/channelrx/udpsink/udpsinkbaseband.h create mode 100644 plugins/channelrx/udpsink/udpsinksink.cpp create mode 100644 plugins/channelrx/udpsink/udpsinksink.h diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h index 6d519c866..4ad8acdad 100644 --- a/plugins/channelrx/demoddatv/datvdemod.h +++ b/plugins/channelrx/demoddatv/datvdemod.h @@ -21,8 +21,6 @@ #define INCLUDE_DATVDEMOD_H class DeviceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; #include "channel/channelapi.h" #include "dsp/basebandsamplesink.h" diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h index 5b8ff48dc..2033dd20a 100644 --- a/plugins/channelrx/demoddatv/datvdemodgui.h +++ b/plugins/channelrx/demoddatv/datvdemodgui.h @@ -33,7 +33,6 @@ class PluginAPI; class DeviceUISet; class BasebandSampleSink; -class DownChannelizer; namespace Ui { @@ -104,8 +103,6 @@ private: DeviceUISet* m_deviceUISet; ChannelMarker m_objChannelMarker; - ThreadedBasebandSampleSink* m_objThreadedChannelizer; - DownChannelizer* m_objChannelizer; DATVDemod* m_objDATVDemod; MessageQueue m_inputMessageQueue; int m_intCenterFrequency; diff --git a/plugins/channelrx/demodfreedv/CMakeLists.txt b/plugins/channelrx/demodfreedv/CMakeLists.txt index e11d3cb67..160b8893f 100644 --- a/plugins/channelrx/demodfreedv/CMakeLists.txt +++ b/plugins/channelrx/demodfreedv/CMakeLists.txt @@ -1,7 +1,9 @@ project(demodfreedv) set(freedv_SOURCES - freedvdemod.cpp + freedvdemod.cpp + freedvdemodbaseband.cpp + freedvdemodsink.cpp freedvdemodsettings.cpp freedvdemodwebapiadapter.cpp freedvplugin.cpp @@ -9,6 +11,8 @@ set(freedv_SOURCES set(freedv_HEADERS freedvdemod.h + freedvdemodbaseband.h + freedvdemodsink.h freedvdemodsettings.h freedvdemodwebapiadapter.h freedvplugin.h diff --git a/plugins/channelrx/demodfreedv/freedvdemod.cpp b/plugins/channelrx/demodfreedv/freedvdemod.cpp index af2c02934..29e1ebf12 100644 --- a/plugins/channelrx/demodfreedv/freedvdemod.cpp +++ b/plugins/channelrx/demodfreedv/freedvdemod.cpp @@ -15,26 +15,18 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include - -#include #include #include #include #include - -#include "codec2/freedv_api.h" -#include "codec2/modem_stats.h" +#include #include "SWGChannelSettings.h" #include "SWGFreeDVDemodSettings.h" #include "SWGChannelReport.h" #include "SWGFreeDVDemodReport.h" -#include "audio/audiooutput.h" #include "dsp/dspengine.h" -#include "dsp/downchannelizer.h" -#include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" #include "dsp/devicesamplemimo.h" #include "device/deviceapi.h" @@ -44,213 +36,37 @@ MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureFreeDVDemod, Message) MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgResyncFreeDVDemod, Message) -MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureFreeDVDemodPrivate, Message) -MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureChannelizer, Message) const QString FreeDVDemod::m_channelIdURI = "sdrangel.channel.freedvdemod"; const QString FreeDVDemod::m_channelId = "FreeDVDemod"; -FreeDVDemod::FreeDVStats::FreeDVStats() -{ - init(); -} - -void FreeDVDemod::FreeDVStats::init() -{ - m_sync = false; - m_snrEst = -20; - m_clockOffset = 0; - m_freqOffset = 0; - m_syncMetric = 0; - m_totalBitErrors = 0; - m_lastTotalBitErrors = 0; - m_ber = 0; - m_frameCount = 0; - m_berFrameCount = 0; - m_fps = 1; -} - -void FreeDVDemod::FreeDVStats::collect(struct freedv *freeDV) -{ - struct MODEM_STATS stats; - - freedv_get_modem_extended_stats(freeDV, &stats); - m_totalBitErrors = freedv_get_total_bit_errors(freeDV); - m_clockOffset = stats.clock_offset; - m_freqOffset = stats.foff; - m_syncMetric = stats.sync_metric; - m_sync = stats.sync != 0; - m_snrEst = stats.snr_est; - - if (m_berFrameCount >= m_fps) - { - m_ber = m_totalBitErrors - m_lastTotalBitErrors; - m_ber = m_ber < 0 ? 0 : m_ber; - m_berFrameCount = 0; - m_lastTotalBitErrors = m_totalBitErrors; -// qDebug("FreeDVStats::collect: demod sync: %s sync metric: %f demod snr: %3.2f dB BER: %d clock offset: %f freq offset: %f", -// m_sync ? "ok" : "ko", m_syncMetric, m_snrEst, m_ber, m_clockOffset, m_freqOffset); - } - - m_berFrameCount++; - m_frameCount++; -} - -FreeDVDemod::FreeDVSNR::FreeDVSNR() -{ - m_sum = 0.0f; - m_peak = 0.0f; - m_n = 0; - m_reset = true; -} - -void FreeDVDemod::FreeDVSNR::accumulate(float snrdB) -{ - if (m_reset) - { - m_sum = CalcDb::powerFromdB(snrdB); - m_peak = snrdB; - m_n = 1; - m_reset = false; - } - else - { - m_sum += CalcDb::powerFromdB(snrdB); - m_peak = std::max(m_peak, snrdB); - m_n++; - } -} - -FreeDVDemod::LevelRMS::LevelRMS() -{ - m_sum = 0.0f; - m_peak = 0.0f; - m_n = 0; - m_reset = true; -} - -void FreeDVDemod::LevelRMS::accumulate(float level) -{ - if (m_reset) - { - m_sum = level * level; - m_peak = std::fabs(level); - m_n = 1; - m_reset = false; - } - else - { - m_sum += level * level; - m_peak = std::max(m_peak, std::fabs(level)); - m_n++; - } -} - FreeDVDemod::FreeDVDemod(DeviceAPI *deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), - m_deviceAPI(deviceAPI), - m_hiCutoff(6000), - m_lowCutoff(0), - m_volume(2), - m_spanLog2(3), - m_sum(fftfilt::cmplx{0,0}), - m_inputSampleRate(48000), - m_modemSampleRate(48000), - m_speechSampleRate(8000), // fixed 8 kS/s - m_inputFrequencyOffset(0), - m_audioMute(false), - m_simpleAGC(0.003f, 0.0f, 1e-6f), - m_agcActive(false), - m_squelchDelayLine(2*48000), - m_audioActive(false), - m_sampleSink(0), - m_audioFifo(24000), - m_freeDV(0), - m_nSpeechSamples(0), - m_nMaxModemSamples(0), - m_iSpeech(0), - m_iModem(0), - m_speechOut(0), - m_modIn(0), - m_levelInNbSamples(480), // 10ms @ 48 kS/s - m_enable(true), - m_settingsMutex(QMutex::Recursive) + m_deviceAPI(deviceAPI) { setObjectName(m_channelId); - DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); - uint32_t audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); - applyAudioSampleRate(audioSampleRate); + m_thread = new QThread(this); + m_basebandSink = new FreeDVDemodBaseband(); + m_basebandSink->moveToThread(m_thread); - m_audioBuffer.resize(1<<14); - m_audioBufferFill = 0; - m_undersampleCount = 0; + applySettings(m_settings, true); - m_magsq = 0.0f; - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - - m_simpleAGC.resizeNew(m_modemSampleRate/10, 0.003); - - SSBFilter = new fftfilt(m_lowCutoff / m_modemSampleRate, m_hiCutoff / m_modemSampleRate, ssbFftLen); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addChannelSink(m_threadedChannelizer); + m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); m_networkManager = new QNetworkAccessManager(); connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); - connect(&m_timer, SIGNAL(timeout()), this, SLOT(timerHandlerFunction())); } FreeDVDemod::~FreeDVDemod() { disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); delete m_networkManager; - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); - m_deviceAPI->removeChannelSinkAPI(this); - m_deviceAPI->removeChannelSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; - delete SSBFilter; -} - -void FreeDVDemod::configure(MessageQueue* messageQueue, - Real Bandwidth, - Real LowCutoff, - Real volume, - int spanLog2, - bool audioBinaural, - bool audioFlipChannel, - bool dsb, - bool audioMute, - bool agc, - bool agcClamping, - int agcTimeLog2, - int agcPowerThreshold, - int agcThresholdGate) -{ - Message* cmd = MsgConfigureFreeDVDemodPrivate::create( - Bandwidth, - LowCutoff, - volume, - spanLog2, - audioBinaural, - audioFlipChannel, - dsb, - audioMute, - agc, - agcClamping, - agcTimeLog2, - agcPowerThreshold, - agcThresholdGate); - messageQueue->push(cmd); + m_deviceAPI->removeChannelSink(this); + delete m_basebandSink; + delete m_thread; } uint32_t FreeDVDemod::getNumberOfDeviceStreams() const @@ -261,157 +77,31 @@ uint32_t FreeDVDemod::getNumberOfDeviceStreams() const void FreeDVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) { (void) positiveOnly; - - if (!m_freeDV) { - return; - } - - Complex ci; - fftfilt::cmplx *sideband; - int n_out; - - m_settingsMutex.lock(); - - if (!m_enable) - { - m_settingsMutex.unlock(); - return; - } - - int decim = 1<<(m_spanLog2 - 1); - unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) - - for(SampleVector::const_iterator it = begin; it < end; ++it) - { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_interpolatorDistance < 1.0f) // interpolate - { - while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - else - { - if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - } - - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - - if (res != m_audioBufferFill) - { - qDebug("FreeDVDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); - } - - m_audioBufferFill = 0; - - if (m_sampleSink != 0) - { - m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true); - } - - m_sampleBuffer.clear(); - - m_settingsMutex.unlock(); -} - -void FreeDVDemod::processOneSample(Complex &ci) -{ - fftfilt::cmplx *sideband; - int n_out = 0; - int decim = 1<<(m_spanLog2 - 1); - unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) - - n_out = SSBFilter->runSSB(ci, &sideband, true); // always USB side - - for (int i = 0; i < n_out; i++) - { - // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display - // smart decimation with bit gain using float arithmetic (23 bits significand) - - m_sum += sideband[i]; - - if (!(m_undersampleCount++ & decim_mask)) - { - Real avgr = m_sum.real() / decim; - Real avgi = m_sum.imag() / decim; - m_magsq = (avgr * avgr + avgi * avgi) / (SDR_RX_SCALED*SDR_RX_SCALED); - - m_magsqSum += m_magsq; - - if (m_magsq > m_magsqPeak) - { - m_magsqPeak = m_magsq; - } - - m_magsqCount++; - m_sampleBuffer.push_back(Sample(avgr, avgi)); - m_sum.real(0.0); - m_sum.imag(0.0); - } - - // fftfilt::cmplx z = sideband[i]; - // Real demod = (z.real() + z.imag()) * 0.7; - Real demod = sideband[i].real(); // works as good - - if (m_agcActive) - { - m_simpleAGC.feed(demod); - demod *= (m_settings.m_volumeIn * 3276.8f) / m_simpleAGC.getValue(); // provision for peak to average ratio (here 10) compensated by m_volumeIn - // if (i == 0) { - // qDebug("FreeDVDemod::feed: m_simpleAGC: %f", m_simpleAGC.getValue()); - // } - } - else - { - demod *= m_settings.m_volumeIn; - } - - pushSampleToDV((qint16) demod); - } + m_basebandSink->feed(begin, end); } void FreeDVDemod::start() { - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + qDebug() << "FreeDVDemod::start"; + + if (m_basebandSampleRate != 0) { + m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); + } + + m_basebandSink->reset(); + m_thread->start(); } void FreeDVDemod::stop() { + qDebug() << "FreeDVDemod::stop"; + m_thread->exit(); + m_thread->wait(); } bool FreeDVDemod::handleMessage(const Message& cmd) { - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - qDebug("FreeDVDemod::handleMessage: MsgChannelizerNotification: m_sampleRate"); - - applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug() << "FreeDVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate() - << " centerFrequency: " << cfg.getCenterFrequency(); - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - - return true; - } - else if (MsgConfigureFreeDVDemod::match(cmd)) + if (MsgConfigureFreeDVDemod::match(cmd)) { MsgConfigureFreeDVDemod& cfg = (MsgConfigureFreeDVDemod&) cmd; qDebug("FreeDVDemod::handleMessage: MsgConfigureFreeDVDemod"); @@ -423,292 +113,28 @@ bool FreeDVDemod::handleMessage(const Message& cmd) else if (MsgResyncFreeDVDemod::match(cmd)) { qDebug("FreeDVDemod::handleMessage: MsgResyncFreeDVDemod"); - m_settingsMutex.lock(); - freedv_set_sync(m_freeDV, FREEDV_SYNC_UNSYNC); - m_settingsMutex.unlock(); - return true; - } - else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) - { - BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; - const QThread *thread = cfg.getThread(); - qDebug("FreeDVDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); - return true; - } - else if (DSPConfigureAudio::match(cmd)) - { - DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; - uint32_t sampleRate = cfg.getSampleRate(); - - qDebug() << "FreeDVDemod::handleMessage: DSPConfigureAudio:" - << " sampleRate: " << sampleRate; - - if (sampleRate != m_audioSampleRate) { - applyAudioSampleRate(sampleRate); - } + FreeDVDemodBaseband::MsgResyncFreeDVDemod *msg = FreeDVDemodBaseband::MsgResyncFreeDVDemod::create(); + m_basebandSink->getInputMessageQueue()->push(msg); return true; } else if (DSPSignalNotification::match(cmd)) { + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + m_basebandSampleRate = notif.getSampleRate(); + // Forward to the sink + DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "FreeDVDemod::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + return true; } else { - if(m_sampleSink != 0) - { - return m_sampleSink->handleMessage(cmd); - } - else - { - return false; - } + return false; } } -void FreeDVDemod::pushSampleToDV(int16_t sample) -{ - qint16 audioSample; - - if (m_levelIn.m_n >= m_levelInNbSamples) - { - qreal rmsLevel = sqrt(m_levelIn.m_sum / m_levelInNbSamples); - // qDebug("FreeDVDemod::pushSampleToDV: rmsLevel: %f m_peak: %f m_levelInNbSamples: %d", - // rmsLevel, m_levelIn.m_peak, m_levelInNbSamples); - emit levelInChanged(rmsLevel, m_levelIn.m_peak, m_levelInNbSamples); - m_levelIn.m_reset = true; - } - - m_levelIn.accumulate(sample/29491.2f); // scale on 90% (0.9 * 32768.0) - - if (m_iModem == m_nin) - { - int nout = freedv_rx(m_freeDV, m_speechOut, m_modIn); - m_freeDVStats.collect(m_freeDV); - m_freeDVSNR.accumulate(m_freeDVStats.m_snrEst); - - if (m_settings.m_audioMute) - { - for (uint32_t i = 0; i < nout * m_audioResampler.getDecimation(); i++) { - pushSampleToAudio(0); - } - } - else - { - for (int i = 0; i < nout; i++) - { - while (!m_audioResampler.upSample(m_speechOut[i], audioSample)) { - pushSampleToAudio(audioSample); - } - - pushSampleToAudio(audioSample); - } - } - - m_iModem = 0; - m_iSpeech = 0; - } - - m_modIn[m_iModem++] = sample; -} - -void FreeDVDemod::pushSampleToAudio(int16_t sample) -{ - m_audioBuffer[m_audioBufferFill].l = sample * m_volume; - m_audioBuffer[m_audioBufferFill].r = sample * m_volume; - ++m_audioBufferFill; - - if (m_audioBufferFill >= m_audioBuffer.size()) - { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - - if (res != m_audioBufferFill) { - qDebug("FreeDVDemod::pushSampleToAudio: %u/%u samples written", res, m_audioBufferFill); - } - - m_audioBufferFill = 0; - } -} - -void FreeDVDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) -{ - qDebug() << "FreeDVDemod::applyChannelSettings:" - << " inputSampleRate: " << inputSampleRate - << " inputFrequencyOffset: " << inputFrequencyOffset; - - if ((m_inputFrequencyOffset != inputFrequencyOffset) || - (m_inputSampleRate != inputSampleRate) || force) - { - m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); - } - - if ((m_inputSampleRate != inputSampleRate) || force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, inputSampleRate, m_hiCutoff * 1.5f, 2.0f); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_modemSampleRate; - m_settingsMutex.unlock(); - } - - m_inputSampleRate = inputSampleRate; - m_inputFrequencyOffset = inputFrequencyOffset; -} - -void FreeDVDemod::applyAudioSampleRate(int sampleRate) -{ - qDebug("FreeDVDemod::applyAudioSampleRate: %d", sampleRate); - - m_settingsMutex.lock(); - m_audioFifo.setSize(sampleRate); - m_audioResampler.setDecimation(sampleRate / m_speechSampleRate); - m_audioResampler.setAudioFilters(sampleRate, sampleRate, 250, 3300, 4.0f); - m_settingsMutex.unlock(); - - m_audioSampleRate = sampleRate; -} - -void FreeDVDemod::applyFreeDVMode(FreeDVDemodSettings::FreeDVMode mode) -{ - m_hiCutoff = FreeDVDemodSettings::getHiCutoff(mode); - m_lowCutoff = FreeDVDemodSettings::getLowCutoff(mode); - uint32_t modemSampleRate = FreeDVDemodSettings::getModSampleRate(mode); - - m_settingsMutex.lock(); - SSBFilter->create_filter(m_lowCutoff / (float) modemSampleRate, m_hiCutoff / (float) modemSampleRate); - - // baseband interpolator - if (modemSampleRate != m_modemSampleRate) - { - MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - modemSampleRate, m_settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(channelConfigMsg); - - m_interpolatorDistanceRemain = 0; - //m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) modemSampleRate; - m_interpolator.create(16, m_inputSampleRate, m_hiCutoff * 1.5f, 2.0f); - m_modemSampleRate = modemSampleRate; - - m_simpleAGC.resizeNew(modemSampleRate/10, 0.003); - m_levelInNbSamples = m_modemSampleRate / 100; // 10ms - - if (getMessageQueueToGUI()) - { - DSPConfigureAudio *cfg = new DSPConfigureAudio(m_modemSampleRate, DSPConfigureAudio::AudioOutput); - getMessageQueueToGUI()->push(cfg); - } - } - - // FreeDV object - - if (m_freeDV) { - freedv_close(m_freeDV); - } - - int fdv_mode = -1; - - switch(mode) - { - case FreeDVDemodSettings::FreeDVMode700C: - fdv_mode = FREEDV_MODE_700C; - break; - case FreeDVDemodSettings::FreeDVMode700D: - fdv_mode = FREEDV_MODE_700D; - break; - case FreeDVDemodSettings::FreeDVMode800XA: - fdv_mode = FREEDV_MODE_800XA; - break; - case FreeDVDemodSettings::FreeDVMode1600: - fdv_mode = FREEDV_MODE_1600; - break; - case FreeDVDemodSettings::FreeDVMode2400A: - default: - fdv_mode = FREEDV_MODE_2400A; - break; - } - - if (fdv_mode == FREEDV_MODE_700D) - { - struct freedv_advanced adv; - adv.interleave_frames = 1; - m_freeDV = freedv_open_advanced(fdv_mode, &adv); - } - else - { - m_freeDV = freedv_open(fdv_mode); - } - - if (m_freeDV) - { - freedv_set_test_frames(m_freeDV, 0); - freedv_set_snr_squelch_thresh(m_freeDV, -100.0); - freedv_set_squelch_en(m_freeDV, 0); - freedv_set_clip(m_freeDV, 0); - freedv_set_ext_vco(m_freeDV, 0); - freedv_set_sync(m_freeDV, FREEDV_SYNC_MANUAL); - - freedv_set_callback_txt(m_freeDV, nullptr, nullptr, nullptr); - freedv_set_callback_protocol(m_freeDV, nullptr, nullptr, nullptr); - freedv_set_callback_data(m_freeDV, nullptr, nullptr, nullptr); - - int nSpeechSamples = freedv_get_n_speech_samples(m_freeDV); - int nMaxModemSamples = freedv_get_n_max_modem_samples(m_freeDV); - int Fs = freedv_get_modem_sample_rate(m_freeDV); - int Rs = freedv_get_modem_symbol_rate(m_freeDV); - m_freeDVStats.init(); - - if (nSpeechSamples > m_nSpeechSamples) - { - if (m_speechOut) { - delete[] m_speechOut; - } - - m_speechOut = new int16_t[nSpeechSamples]; - m_nSpeechSamples = nSpeechSamples; - } - - if (nMaxModemSamples > m_nMaxModemSamples) - { - if (m_modIn) { - delete[] m_modIn; - } - - m_modIn = new int16_t[nMaxModemSamples]; - m_nMaxModemSamples = nMaxModemSamples; - } - - m_iSpeech = 0; - m_iModem = 0; - m_nin = freedv_nin(m_freeDV); - - if (m_nin > 0) { - m_freeDVStats.m_fps = m_modemSampleRate / m_nin; - } - - qDebug() << "FreeDVMod::applyFreeDVMode:" - << " fdv_mode: " << fdv_mode - << " m_modemSampleRate: " << m_modemSampleRate - << " m_lowCutoff: " << m_lowCutoff - << " m_hiCutoff: " << m_hiCutoff - << " Fs: " << Fs - << " Rs: " << Rs - << " m_nSpeechSamples: " << m_nSpeechSamples - << " m_nMaxModemSamples: " << m_nMaxModemSamples - << " m_nin: " << m_nin - << " FPS: " << m_freeDVStats.m_fps; - } - else - { - qCritical("FreeDVMod::applyFreeDVMode: m_freeDV was not allocated"); - } - - m_enable = false; - m_timer.setSingleShot(true); - m_timer.start(2000); - - m_settingsMutex.unlock(); -} void FreeDVDemod::applySettings(const FreeDVDemodSettings& settings, bool force) { @@ -734,32 +160,15 @@ void FreeDVDemod::applySettings(const FreeDVDemodSettings& settings, bool force) if((m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset) || force) { reverseAPIKeys.append("inputFrequencyOffset"); } - - if ((m_settings.m_volume != settings.m_volume) || force) - { + if ((m_settings.m_volume != settings.m_volume) || force) { reverseAPIKeys.append("volume"); - m_volume = settings.m_volume; - m_volume /= 4.0; // for 3276.8 } - - if ((m_settings.m_volumeIn != settings.m_volumeIn) || force) - { + if ((m_settings.m_volumeIn != settings.m_volumeIn) || force) { reverseAPIKeys.append("volumeIn"); } - - if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) - { + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { reverseAPIKeys.append("audioDeviceName"); - AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); - int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); - audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); - uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); - - if (m_audioSampleRate != audioSampleRate) { - applyAudioSampleRate(audioSampleRate); - } } - if ((m_settings.m_spanLog2 != settings.m_spanLog2) || force) { reverseAPIKeys.append("spanLog2"); } @@ -769,30 +178,22 @@ void FreeDVDemod::applySettings(const FreeDVDemodSettings& settings, bool force) if ((m_settings.m_agc != settings.m_agc) || force) { reverseAPIKeys.append("agc"); } - - if ((settings.m_freeDVMode != m_settings.m_freeDVMode) || force) { - applyFreeDVMode(settings.m_freeDVMode); - } - - m_spanLog2 = settings.m_spanLog2; - m_audioMute = settings.m_audioMute; - m_agcActive = settings.m_agc; - if (m_settings.m_streamIndex != settings.m_streamIndex) { if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only { m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); - m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); - m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSink(this, settings.m_streamIndex); m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); - // apply stream sample rate to itself - applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); } reverseAPIKeys.append("streamIndex"); } + FreeDVDemodBaseband::MsgConfigureFreeDVDemodBaseband *msg = FreeDVDemodBaseband::MsgConfigureFreeDVDemodBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + if (settings.m_useReverseAPI) { bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || @@ -806,23 +207,6 @@ void FreeDVDemod::applySettings(const FreeDVDemodSettings& settings, bool force) m_settings = settings; } -void FreeDVDemod::getSNRLevels(double& avg, double& peak, int& nbSamples) -{ - if (m_freeDVSNR.m_n > 0) - { - avg = CalcDb::dbPower(m_freeDVSNR.m_sum / m_freeDVSNR.m_n); - peak = m_freeDVSNR.m_peak; - nbSamples = m_freeDVSNR.m_n; - m_freeDVSNR.m_reset = true; - } - else - { - avg = 0.0; - peak = 0.0; - nbSamples = 1; - } -} - QByteArray FreeDVDemod::serialize() const { return m_settings.serialize(); @@ -866,13 +250,6 @@ int FreeDVDemod::webapiSettingsPutPatch( FreeDVDemodSettings settings = m_settings; webapiUpdateChannelSettings(settings, channelSettingsKeys, response); - if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) - { - MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - m_modemSampleRate, settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(channelConfigMsg); - } - MsgConfigureFreeDVDemod *msg = MsgConfigureFreeDVDemod::create(settings, force); m_inputMessageQueue.push(msg); @@ -999,9 +376,9 @@ void FreeDVDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respo getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); response.getFreeDvDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); - response.getFreeDvDemodReport()->setSquelch(m_audioActive ? 1 : 0); - response.getFreeDvDemodReport()->setAudioSampleRate(m_audioSampleRate); - response.getFreeDvDemodReport()->setChannelSampleRate(m_inputSampleRate); + response.getFreeDvDemodReport()->setSquelch(getAudioActive() ? 1 : 0); + response.getFreeDvDemodReport()->setAudioSampleRate(getAudioSampleRate()); + response.getFreeDvDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate()); } void FreeDVDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force) @@ -1091,7 +468,7 @@ void FreeDVDemod::networkManagerFinished(QNetworkReply *reply) reply->deleteLater(); } -void FreeDVDemod::timerHandlerFunction() +void FreeDVDemod::setLevelMeter(QObject *levelMeter) { - m_enable = true; + connect(m_basebandSink, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int))); } \ No newline at end of file diff --git a/plugins/channelrx/demodfreedv/freedvdemod.h b/plugins/channelrx/demodfreedv/freedvdemod.h index 954b1ddfa..b52c17379 100644 --- a/plugins/channelrx/demodfreedv/freedvdemod.h +++ b/plugins/channelrx/demodfreedv/freedvdemod.h @@ -20,33 +20,19 @@ #include -#include -#include #include #include "dsp/basebandsamplesink.h" #include "channel/channelapi.h" -#include "dsp/ncof.h" -#include "dsp/interpolator.h" -#include "dsp/fftfilt.h" -#include "dsp/agc.h" -#include "audio/audiofifo.h" -#include "audio/audioresampler.h" #include "util/message.h" #include "util/doublebufferfifo.h" -#include "freedvdemodsettings.h" - -#define ssbFftLen 1024 -#define agcTarget 3276.8 // -10 dB amplitude => -20 dB power: center of normal signal +#include "freedvdemodbaseband.h" class QNetworkAccessManager; class QNetworkReply; class DeviceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; - -struct freedv; +class QThread; class FreeDVDemod : public BasebandSampleSink, public ChannelAPI { Q_OBJECT @@ -87,48 +73,10 @@ public: {} }; - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - FreeDVDemod(DeviceAPI *deviceAPI); virtual ~FreeDVDemod(); virtual void destroy() { delete this; } - void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } - - void configure(MessageQueue* messageQueue, - Real Bandwidth, - Real LowCutoff, - Real volume, - int spanLog2, - bool audioBinaural, - bool audioFlipChannels, - bool dsb, - bool audioMute, - bool agc, - bool agcClamping, - int agcTimeLog2, - int agcPowerThreshold, - int agcThresholdGate); + void setSampleSink(BasebandSampleSink* spectrumSink) { m_basebandSink->setSpectrumSink(spectrumSink); } virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); @@ -152,33 +100,16 @@ public: return m_settings.m_inputFrequencyOffset; } - uint32_t getAudioSampleRate() const { return m_audioSampleRate; } - uint32_t getModemSampleRate() const { return m_modemSampleRate; } - double getMagSq() const { return m_magsq; } - bool getAudioActive() const { return m_audioActive; } - - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - if (m_magsqCount > 0) - { - m_magsq = m_magsqSum / m_magsqCount; - m_magSqLevelStore.m_magsq = m_magsq; - m_magSqLevelStore.m_magsqPeak = m_magsqPeak; - } - - avg = m_magSqLevelStore.m_magsq; - peak = m_magSqLevelStore.m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - } - - void getSNRLevels(double& avg, double& peak, int& nbSamples); - int getBER() const { return m_freeDVStats.m_ber; } - float getFrequencyOffset() const { return m_freeDVStats.m_freqOffset; } - bool isSync() const { return m_freeDVStats.m_sync; } + uint32_t getAudioSampleRate() const { return m_basebandSink->getAudioSampleRate(); } + uint32_t getModemSampleRate() const { return m_basebandSink->getModemSampleRate(); } + double getMagSq() const { return m_basebandSink->getMagSq(); } + bool getAudioActive() const { return m_basebandSink->getAudioActive(); } + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); } + void getSNRLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getSNRLevels(avg, peak, nbSamples); } + int getBER() const { return m_basebandSink->getBER(); } + float getFrequencyOffset() const { return m_basebandSink->getFrequencyOffset(); } + bool isSync() const { return m_basebandSink->isSync(); } + void propagateMessageQueueToGUI() { m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI()); } virtual int webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, @@ -204,238 +135,27 @@ public: SWGSDRangel::SWGChannelSettings& response); uint32_t getNumberOfDeviceStreams() const; + void setLevelMeter(QObject *levelMeter); static const QString m_channelIdURI; static const QString m_channelId; -signals: - /** - * Level changed - * \param rmsLevel RMS level in range 0.0 - 1.0 - * \param peakLevel Peak level in range 0.0 - 1.0 - * \param numSamples Number of audio samples analyzed - */ - void levelInChanged(qreal rmsLevel, qreal peakLevel, int numSamples); - private: - struct MagSqLevelsStore - { - MagSqLevelsStore() : - m_magsq(1e-12), - m_magsqPeak(1e-12) - {} - double m_magsq; - double m_magsqPeak; - }; - - struct FreeDVStats - { - FreeDVStats(); - void init(); - void collect(struct freedv *freedv); - - bool m_sync; - float m_snrEst; - float m_clockOffset; - float m_freqOffset; - float m_syncMetric; - int m_totalBitErrors; - int m_lastTotalBitErrors; - int m_ber; //!< estimated BER (b/s) - uint32_t m_frameCount; - uint32_t m_berFrameCount; //!< count of frames for BER estimation - uint32_t m_fps; //!< frames per second - }; - - struct FreeDVSNR - { - FreeDVSNR(); - void accumulate(float snrdB); - - double m_sum; - float m_peak; - int m_n; - bool m_reset; - }; - - struct LevelRMS - { - LevelRMS(); - void accumulate(float fsample); - - double m_sum; - float m_peak; - int m_n; - bool m_reset; - }; - - class MsgConfigureFreeDVDemodPrivate : public Message { - MESSAGE_CLASS_DECLARATION - - public: - Real getBandwidth() const { return m_Bandwidth; } - Real getLoCutoff() const { return m_LowCutoff; } - Real getVolume() const { return m_volume; } - int getSpanLog2() const { return m_spanLog2; } - bool getAudioBinaural() const { return m_audioBinaural; } - bool getAudioFlipChannels() const { return m_audioFlipChannels; } - bool getDSB() const { return m_dsb; } - bool getAudioMute() const { return m_audioMute; } - bool getAGC() const { return m_agc; } - bool getAGCClamping() const { return m_agcClamping; } - int getAGCTimeLog2() const { return m_agcTimeLog2; } - int getAGCPowerThershold() const { return m_agcPowerThreshold; } - int getAGCThersholdGate() const { return m_agcThresholdGate; } - - static MsgConfigureFreeDVDemodPrivate* create(Real Bandwidth, - Real LowCutoff, - Real volume, - int spanLog2, - bool audioBinaural, - bool audioFlipChannels, - bool dsb, - bool audioMute, - bool agc, - bool agcClamping, - int agcTimeLog2, - int agcPowerThreshold, - int agcThresholdGate) - { - return new MsgConfigureFreeDVDemodPrivate( - Bandwidth, - LowCutoff, - volume, - spanLog2, - audioBinaural, - audioFlipChannels, - dsb, - audioMute, - agc, - agcClamping, - agcTimeLog2, - agcPowerThreshold, - agcThresholdGate); - } - - private: - Real m_Bandwidth; - Real m_LowCutoff; - Real m_volume; - int m_spanLog2; - bool m_audioBinaural; - bool m_audioFlipChannels; - bool m_dsb; - bool m_audioMute; - bool m_agc; - bool m_agcClamping; - int m_agcTimeLog2; - int m_agcPowerThreshold; - int m_agcThresholdGate; - - MsgConfigureFreeDVDemodPrivate(Real Bandwidth, - Real LowCutoff, - Real volume, - int spanLog2, - bool audioBinaural, - bool audioFlipChannels, - bool dsb, - bool audioMute, - bool agc, - bool agcClamping, - int agcTimeLog2, - int agcPowerThreshold, - int agcThresholdGate) : - Message(), - m_Bandwidth(Bandwidth), - m_LowCutoff(LowCutoff), - m_volume(volume), - m_spanLog2(spanLog2), - m_audioBinaural(audioBinaural), - m_audioFlipChannels(audioFlipChannels), - m_dsb(dsb), - m_audioMute(audioMute), - m_agc(agc), - m_agcClamping(agcClamping), - m_agcTimeLog2(agcTimeLog2), - m_agcPowerThreshold(agcPowerThreshold), - m_agcThresholdGate(agcThresholdGate) - { } - }; - DeviceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; + QThread *m_thread; + FreeDVDemodBaseband *m_basebandSink; FreeDVDemodSettings m_settings; - - Real m_hiCutoff; - Real m_lowCutoff; - Real m_volume; - int m_spanLog2; - fftfilt::cmplx m_sum; - int m_undersampleCount; - int m_inputSampleRate; - uint32_t m_modemSampleRate; - uint32_t m_speechSampleRate; - uint32_t m_audioSampleRate; - int m_inputFrequencyOffset; - bool m_audioMute; - double m_magsq; - double m_magsqSum; - double m_magsqPeak; - int m_magsqCount; - MagSqLevelsStore m_magSqLevelStore; - SimpleAGC<4800> m_simpleAGC; - bool m_agcActive; - DoubleBufferFIFO m_squelchDelayLine; - bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold) - - NCOF m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - fftfilt* SSBFilter; - - BasebandSampleSink* m_sampleSink; - SampleVector m_sampleBuffer; - - AudioVector m_audioBuffer; - uint m_audioBufferFill; - AudioFifo m_audioFifo; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; - struct freedv *m_freeDV; - int m_nSpeechSamples; - int m_nMaxModemSamples; - int m_nin; - int m_iSpeech; - int m_iModem; - int16_t *m_speechOut; - int16_t *m_modIn; - AudioResampler m_audioResampler; - FreeDVStats m_freeDVStats; - FreeDVSNR m_freeDVSNR; - LevelRMS m_levelIn; - int m_levelInNbSamples; - - QTimer m_timer; - bool m_enable; - QMutex m_settingsMutex; - - void pushSampleToDV(int16_t sample); - void pushSampleToAudio(int16_t sample); - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const FreeDVDemodSettings& settings, bool force = false); - void applyAudioSampleRate(int sampleRate); - void applyFreeDVMode(FreeDVDemodSettings::FreeDVMode mode); - void processOneSample(Complex &ci); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); void webapiReverseSendSettings(QList& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force); private slots: void networkManagerFinished(QNetworkReply *reply); - void timerHandlerFunction(); }; #endif // INCLUDE_FREEDVDEMOD_H diff --git a/plugins/channelrx/demodfreedv/freedvdemodbaseband.cpp b/plugins/channelrx/demodfreedv/freedvdemodbaseband.cpp new file mode 100644 index 000000000..0ae6c0fd8 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodbaseband.cpp @@ -0,0 +1,203 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downsamplechannelizer.h" + +#include "freedvdemodbaseband.h" + +MESSAGE_CLASS_DEFINITION(FreeDVDemodBaseband::MsgConfigureFreeDVDemodBaseband, Message) +MESSAGE_CLASS_DEFINITION(FreeDVDemodBaseband::MsgResyncFreeDVDemod, Message) + +FreeDVDemodBaseband::FreeDVDemodBaseband() : + m_mutex(QMutex::Recursive), + m_messageQueueToGUI(nullptr) +{ + qDebug("FreeDVDemodBaseband::FreeDVDemodBaseband"); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownSampleChannelizer(&m_sink); + + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &FreeDVDemodBaseband::handleData, + Qt::QueuedConnection + ); + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue()); + m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +FreeDVDemodBaseband::~FreeDVDemodBaseband() +{ + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo()); + delete m_channelizer; +} + +void FreeDVDemodBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.reset(); +} + +void FreeDVDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void FreeDVDemodBaseband::handleData() +{ + QMutexLocker mutexLocker(&m_mutex); + + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } + + qreal rmsLevel, peakLevel; + int numSamples; + m_sink.getLevels(rmsLevel, peakLevel, numSamples); + emit levelChanged(rmsLevel, peakLevel, numSamples); +} + +void FreeDVDemodBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool FreeDVDemodBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureFreeDVDemodBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + qDebug() << "FreeDVDemodBaseband::handleMessage: MsgConfigureFreeDVDemodBaseband"; + MsgConfigureFreeDVDemodBaseband& cfg = (MsgConfigureFreeDVDemodBaseband&) cmd; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "DSDDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + m_channelizer->setBasebandSampleRate(notif.getSampleRate()); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + + return true; + } + else if (MsgResyncFreeDVDemod::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + qDebug() << "FreeDVDemodBaseband::handleMessage: MsgResyncFreeDVDemod"; + m_sink.resyncFreeDV(); + + return true; + } + else + { + return false; + } +} + +void FreeDVDemodBaseband::applySettings(const FreeDVDemodSettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + { + m_channelizer->setChannelization(m_sink.getModemSampleRate(), settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_sink.getAudioSampleRate() != audioSampleRate) { + m_sink.applyAudioSampleRate(audioSampleRate); + } + } + + if ((settings.m_freeDVMode != m_settings.m_freeDVMode) || force) + { + uint32_t modemSampleRate = FreeDVDemodSettings::getModSampleRate(settings.m_freeDVMode); + + if (m_sink.getModemSampleRate() != modemSampleRate) + { + m_channelizer->setChannelization(m_sink.getModemSampleRate(), settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + + if (getMessageQueueToGUI()) + { + DSPConfigureAudio *cfg = new DSPConfigureAudio(modemSampleRate, DSPConfigureAudio::AudioOutput); + getMessageQueueToGUI()->push(cfg); + } + } + + m_sink.applyFreeDVMode(settings.m_freeDVMode); + } + + m_sink.applySettings(settings, force); + m_settings = settings; +} + +int FreeDVDemodBaseband::getChannelSampleRate() const +{ + return m_channelizer->getChannelSampleRate(); +} + + +void FreeDVDemodBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); +} \ No newline at end of file diff --git a/plugins/channelrx/demodfreedv/freedvdemodbaseband.h b/plugins/channelrx/demodfreedv/freedvdemodbaseband.h new file mode 100644 index 000000000..56f01f8c1 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodbaseband.h @@ -0,0 +1,120 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_FREEDVDEMODBASEBAND_H +#define INCLUDE_FREEDVDEMODBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "freedvdemodsink.h" + +class DownSampleChannelizer; + +class FreeDVDemodBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureFreeDVDemodBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const FreeDVDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureFreeDVDemodBaseband* create(const FreeDVDemodSettings& settings, bool force) + { + return new MsgConfigureFreeDVDemodBaseband(settings, force); + } + + private: + FreeDVDemodSettings m_settings; + bool m_force; + + MsgConfigureFreeDVDemodBaseband(const FreeDVDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgResyncFreeDVDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgResyncFreeDVDemod* create() { + return new MsgResyncFreeDVDemod(); + } + + private: + MsgResyncFreeDVDemod() + {} + }; + + FreeDVDemodBaseband(); + ~FreeDVDemodBaseband(); + void reset(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + int getChannelSampleRate() const; + unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); } + double getMagSq() { return m_sink.getMagSq(); } + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); } + void setBasebandSampleRate(int sampleRate); + void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; } + + void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_sink.setSpectrumSink(spectrumSink); } + uint32_t getModemSampleRate() const { return m_sink.getModemSampleRate(); } + double getMagSq() const { return m_sink.getMagSq(); } + bool getAudioActive() const { return m_sink.getAudioActive(); } + void getSNRLevels(double& avg, double& peak, int& nbSamples) { m_sink.getSNRLevels(avg, peak, nbSamples); } + int getBER() const { return m_sink.getBER(); } + float getFrequencyOffset() const { return m_sink.getFrequencyOffset(); } + bool isSync() const { return m_sink.isSync(); } + +signals: + /** + * Level changed + * \param rmsLevel RMS level in range 0.0 - 1.0 + * \param peakLevel Peak level in range 0.0 - 1.0 + * \param numSamples Number of samples analyzed + */ + void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples); + +private: + SampleSinkFifo m_sampleFifo; + DownSampleChannelizer *m_channelizer; + FreeDVDemodSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + FreeDVDemodSettings m_settings; + QMutex m_mutex; + MessageQueue *m_messageQueueToGUI; + + MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; } + bool handleMessage(const Message& cmd); + void applySettings(const FreeDVDemodSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_DSDDEMODBASEBAND_H \ No newline at end of file diff --git a/plugins/channelrx/demodfreedv/freedvdemodgui.cpp b/plugins/channelrx/demodfreedv/freedvdemodgui.cpp index be741cca9..6e47a5f65 100644 --- a/plugins/channelrx/demodfreedv/freedvdemodgui.cpp +++ b/plugins/channelrx/demodfreedv/freedvdemodgui.cpp @@ -279,6 +279,7 @@ FreeDVDemodGUI::FreeDVDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B m_freeDVDemod = (FreeDVDemod*) rxChannel; m_freeDVDemod->setSampleSink(m_spectrumVis); m_freeDVDemod->setMessageQueueToGUI(getInputMessageQueue()); + m_freeDVDemod->propagateMessageQueueToGUI(); resetToDefaults(); @@ -314,7 +315,7 @@ FreeDVDemodGUI::FreeDVDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); - connect(m_freeDVDemod, SIGNAL(levelInChanged(qreal, qreal, int)), ui->volumeInMeter, SLOT(levelChanged(qreal, qreal, int))); + m_freeDVDemod->setLevelMeter(ui->volumeInMeter); ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); @@ -346,10 +347,6 @@ void FreeDVDemodGUI::applySettings(bool force) { if (m_doApplySettings) { - FreeDVDemod::MsgConfigureChannelizer* channelConfigMsg = FreeDVDemod::MsgConfigureChannelizer::create( - m_freeDVDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency()); - m_freeDVDemod->getInputMessageQueue()->push(channelConfigMsg); - FreeDVDemod::MsgConfigureFreeDVDemod* message = FreeDVDemod::MsgConfigureFreeDVDemod::create( m_settings, force); m_freeDVDemod->getInputMessageQueue()->push(message); } diff --git a/plugins/channelrx/demodfreedv/freedvdemodsink.cpp b/plugins/channelrx/demodfreedv/freedvdemodsink.cpp new file mode 100644 index 000000000..fbb6ed27e --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodsink.cpp @@ -0,0 +1,565 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "codec2/freedv_api.h" +#include "codec2/modem_stats.h" + +#include "dsp/basebandsamplesink.h" +#include "audio/audiooutput.h" +#include "util/db.h" + +#include "freedvdemodsink.h" + +const unsigned int FreeDVDemodSink::m_ssbFftLen = 1024; +const float FreeDVDemodSink::m_agcTarget = 3276.8f; // -10 dB amplitude => -20 dB power: center of normal signal + +FreeDVDemodSink::FreeDVStats::FreeDVStats() +{ + init(); +} + +void FreeDVDemodSink::FreeDVStats::init() +{ + m_sync = false; + m_snrEst = -20; + m_clockOffset = 0; + m_freqOffset = 0; + m_syncMetric = 0; + m_totalBitErrors = 0; + m_lastTotalBitErrors = 0; + m_ber = 0; + m_frameCount = 0; + m_berFrameCount = 0; + m_fps = 1; +} + +void FreeDVDemodSink::FreeDVStats::collect(struct freedv *freeDV) +{ + struct MODEM_STATS stats; + + freedv_get_modem_extended_stats(freeDV, &stats); + m_totalBitErrors = freedv_get_total_bit_errors(freeDV); + m_clockOffset = stats.clock_offset; + m_freqOffset = stats.foff; + m_syncMetric = stats.sync_metric; + m_sync = stats.sync != 0; + m_snrEst = stats.snr_est; + + if (m_berFrameCount >= m_fps) + { + m_ber = m_totalBitErrors - m_lastTotalBitErrors; + m_ber = m_ber < 0 ? 0 : m_ber; + m_berFrameCount = 0; + m_lastTotalBitErrors = m_totalBitErrors; +// qDebug("FreeDVStats::collect: demod sync: %s sync metric: %f demod snr: %3.2f dB BER: %d clock offset: %f freq offset: %f", +// m_sync ? "ok" : "ko", m_syncMetric, m_snrEst, m_ber, m_clockOffset, m_freqOffset); + } + + m_berFrameCount++; + m_frameCount++; +} + +FreeDVDemodSink::FreeDVSNR::FreeDVSNR() +{ + m_sum = 0.0f; + m_peak = 0.0f; + m_n = 0; + m_reset = true; +} + +void FreeDVDemodSink::FreeDVSNR::accumulate(float snrdB) +{ + if (m_reset) + { + m_sum = CalcDb::powerFromdB(snrdB); + m_peak = snrdB; + m_n = 1; + m_reset = false; + } + else + { + m_sum += CalcDb::powerFromdB(snrdB); + m_peak = std::max(m_peak, snrdB); + m_n++; + } +} + +FreeDVDemodSink::LevelRMS::LevelRMS() +{ + m_sum = 0.0f; + m_peak = 0.0f; + m_n = 0; + m_reset = true; +} + +void FreeDVDemodSink::LevelRMS::accumulate(float level) +{ + if (m_reset) + { + m_sum = level * level; + m_peak = std::fabs(level); + m_n = 1; + m_reset = false; + } + else + { + m_sum += level * level; + m_peak = std::max(m_peak, std::fabs(level)); + m_n++; + } +} + +FreeDVDemodSink::FreeDVDemodSink() : + m_hiCutoff(6000), + m_lowCutoff(0), + m_volume(2), + m_spanLog2(3), + m_sum(fftfilt::cmplx{0,0}), + m_channelSampleRate(48000), + m_modemSampleRate(48000), + m_speechSampleRate(8000), // fixed 8 kS/s + m_channelFrequencyOffset(0), + m_audioMute(false), + m_simpleAGC(0.003f, 0.0f, 1e-6f), + m_agcActive(false), + m_squelchDelayLine(2*48000), + m_audioActive(false), + m_spectrumSink(0), + m_audioFifo(24000), + m_freeDV(0), + m_nSpeechSamples(0), + m_nMaxModemSamples(0), + m_iSpeech(0), + m_iModem(0), + m_speechOut(0), + m_modIn(0), + m_levelInNbSamples(480) // 10ms @ 48 kS/s +{ + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; + m_undersampleCount = 0; + + m_magsq = 0.0f; + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + + m_simpleAGC.resizeNew(m_modemSampleRate/10, 0.003); + + SSBFilter = new fftfilt(m_lowCutoff / m_modemSampleRate, m_hiCutoff / m_modemSampleRate, m_ssbFftLen); + m_SSBFilterBuffer = new fftfilt::cmplx[m_ssbFftLen]; + std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer + m_ssbFftLen, fftfilt::cmplx{0.0f, 0.0f}); + m_SSBFilterBufferIndex = 0; + + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); + applySettings(m_settings, true); +} + +FreeDVDemodSink::~FreeDVDemodSink() +{ + delete SSBFilter; + delete[] m_SSBFilterBuffer; +} + +void FreeDVDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + if (!m_freeDV) { + return; + } + + Complex ci; + fftfilt::cmplx *sideband; + int n_out; + + int decim = 1<<(m_spanLog2 - 1); + unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) + + for(SampleVector::const_iterator it = begin; it < end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_interpolatorDistance < 1.0f) // interpolate + { + while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + } + + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("FreeDVDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); + } + + m_audioBufferFill = 0; + + if (m_spectrumSink) + { + m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true); + } + + m_sampleBuffer.clear(); +} + +void FreeDVDemodSink::processOneSample(Complex &ci) +{ + fftfilt::cmplx *sideband; + int n_out = 0; + int decim = 1<<(m_spanLog2 - 1); + unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) + + m_sum += m_SSBFilterBuffer[m_SSBFilterBufferIndex]; + + if (!(m_undersampleCount++ & decim_mask)) + { + Real avgr = m_sum.real() / decim; + Real avgi = m_sum.imag() / decim; + m_magsq = (avgr*avgr + avgi*avgi) / (SDR_RX_SCALED*SDR_RX_SCALED); + + m_magsqSum += m_magsq; + + if (m_magsq > m_magsqPeak) + { + m_magsqPeak = m_magsq; + } + + m_magsqCount++; + m_sampleBuffer.push_back(Sample(avgr, avgi)); + m_sum.real(0.0); + m_sum.imag(0.0); + } + + fftfilt::cmplx z = m_SSBFilterBuffer[m_SSBFilterBufferIndex]; + Real demod = (z.real() + z.imag()) * 0.7; + // Real demod = m_SSBFilterBuffer[m_SSBFilterBufferIndex].real(); // works as good + + if (m_agcActive) + { + m_simpleAGC.feed(demod); + demod *= (m_settings.m_volumeIn * 3276.8f) / m_simpleAGC.getValue(); // provision for peak to average ratio (here 10) compensated by m_volumeIn + } + else + { + demod *= m_settings.m_volumeIn; + } + + pushSampleToDV((qint16) demod); + n_out = SSBFilter->runSSB(ci, &sideband, true); // always USB side + + if (n_out > 0) + { + std::copy(sideband, sideband + n_out, m_SSBFilterBuffer); + m_SSBFilterBufferIndex = 0; + } + else if (m_SSBFilterBufferIndex < m_ssbFftLen - 1) + { + m_SSBFilterBufferIndex++; + } +} + +void FreeDVDemodSink::pushSampleToDV(int16_t sample) +{ + qint16 audioSample; + + calculateLevel(sample); + + if (m_iModem == m_nin) + { + int nout = freedv_rx(m_freeDV, m_speechOut, m_modIn); + m_freeDVStats.collect(m_freeDV); + m_freeDVSNR.accumulate(m_freeDVStats.m_snrEst); + + if (m_settings.m_audioMute) + { + for (uint32_t i = 0; i < nout * m_audioResampler.getDecimation(); i++) { + pushSampleToAudio(0); + } + } + else + { + for (int i = 0; i < nout; i++) + { + while (!m_audioResampler.upSample(m_speechOut[i], audioSample)) { + pushSampleToAudio(audioSample); + } + + pushSampleToAudio(audioSample); + } + } + + m_iModem = 0; + m_iSpeech = 0; + } + + m_modIn[m_iModem++] = sample; +} + +void FreeDVDemodSink::calculateLevel(int16_t& sample) +{ + if (m_levelIn.m_n >= m_levelInNbSamples) + { + m_rmsLevel = sqrt(m_levelIn.m_sum / m_levelInNbSamples); + m_peakLevel = m_levelIn.m_peak; + m_levelIn.m_reset = true; + } + + m_levelIn.accumulate(sample/29491.2f); // scale on 90% (0.9 * 32768.0) +} + +void FreeDVDemodSink::pushSampleToAudio(int16_t sample) +{ + m_audioBuffer[m_audioBufferFill].l = sample * m_volume; + m_audioBuffer[m_audioBufferFill].r = sample * m_volume; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) { + qDebug("FreeDVDemodSink::pushSampleToAudio: %u/%u samples written", res, m_audioBufferFill); + } + + m_audioBufferFill = 0; + } +} + +void FreeDVDemodSink::applyChannelSettings(int channelSampleRate, int channnelFrequencyOffset, bool force) +{ + qDebug() << "FreeDVDemodSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " inputFrequencyOffset: " << channnelFrequencyOffset; + + if ((m_channelFrequencyOffset != channnelFrequencyOffset) || + (m_channelSampleRate != channelSampleRate) || force) + { + m_nco.setFreq(-channnelFrequencyOffset, channelSampleRate); + } + + if ((m_channelSampleRate != channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, m_hiCutoff * 1.5f, 2.0f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) channelSampleRate / (Real) m_modemSampleRate; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channnelFrequencyOffset; +} + +void FreeDVDemodSink::applyAudioSampleRate(int sampleRate) +{ + qDebug("FreeDVDemodSink::applyAudioSampleRate: %d", sampleRate); + + m_audioFifo.setSize(sampleRate); + m_audioResampler.setDecimation(sampleRate / m_speechSampleRate); + m_audioResampler.setAudioFilters(sampleRate, sampleRate, 250, 3300, 4.0f); + m_audioSampleRate = sampleRate; +} + +void FreeDVDemodSink::applyFreeDVMode(FreeDVDemodSettings::FreeDVMode mode) +{ + m_hiCutoff = FreeDVDemodSettings::getHiCutoff(mode); + m_lowCutoff = FreeDVDemodSettings::getLowCutoff(mode); + uint32_t modemSampleRate = FreeDVDemodSettings::getModSampleRate(mode); + + SSBFilter->create_filter(m_lowCutoff / (float) modemSampleRate, m_hiCutoff / (float) modemSampleRate); + + // baseband interpolator + if (modemSampleRate != m_modemSampleRate) + { + m_interpolatorDistanceRemain = 0; + //m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) modemSampleRate; + m_interpolator.create(16, m_channelSampleRate, m_hiCutoff * 1.5f, 2.0f); + m_modemSampleRate = modemSampleRate; + + m_simpleAGC.resizeNew(modemSampleRate/10, 0.003); + m_levelInNbSamples = m_modemSampleRate / 100; // 10ms + } + + // FreeDV object + + if (m_freeDV) { + freedv_close(m_freeDV); + } + + int fdv_mode = -1; + + switch(mode) + { + case FreeDVDemodSettings::FreeDVMode700C: + fdv_mode = FREEDV_MODE_700C; + break; + case FreeDVDemodSettings::FreeDVMode700D: + fdv_mode = FREEDV_MODE_700D; + break; + case FreeDVDemodSettings::FreeDVMode800XA: + fdv_mode = FREEDV_MODE_800XA; + break; + case FreeDVDemodSettings::FreeDVMode1600: + fdv_mode = FREEDV_MODE_1600; + break; + case FreeDVDemodSettings::FreeDVMode2400A: + default: + fdv_mode = FREEDV_MODE_2400A; + break; + } + + if (fdv_mode == FREEDV_MODE_700D) + { + struct freedv_advanced adv; + adv.interleave_frames = 1; + m_freeDV = freedv_open_advanced(fdv_mode, &adv); + } + else + { + m_freeDV = freedv_open(fdv_mode); + } + + if (m_freeDV) + { + freedv_set_test_frames(m_freeDV, 0); + freedv_set_snr_squelch_thresh(m_freeDV, -100.0); + freedv_set_squelch_en(m_freeDV, 0); + freedv_set_clip(m_freeDV, 0); + freedv_set_ext_vco(m_freeDV, 0); + freedv_set_sync(m_freeDV, FREEDV_SYNC_MANUAL); + + freedv_set_callback_txt(m_freeDV, nullptr, nullptr, nullptr); + freedv_set_callback_protocol(m_freeDV, nullptr, nullptr, nullptr); + freedv_set_callback_data(m_freeDV, nullptr, nullptr, nullptr); + + int nSpeechSamples = freedv_get_n_speech_samples(m_freeDV); + int nMaxModemSamples = freedv_get_n_max_modem_samples(m_freeDV); + int Fs = freedv_get_modem_sample_rate(m_freeDV); + int Rs = freedv_get_modem_symbol_rate(m_freeDV); + m_freeDVStats.init(); + + if (nSpeechSamples > m_nSpeechSamples) + { + if (m_speechOut) { + delete[] m_speechOut; + } + + m_speechOut = new int16_t[nSpeechSamples]; + m_nSpeechSamples = nSpeechSamples; + } + + if (nMaxModemSamples > m_nMaxModemSamples) + { + if (m_modIn) { + delete[] m_modIn; + } + + m_modIn = new int16_t[nMaxModemSamples]; + m_nMaxModemSamples = nMaxModemSamples; + } + + m_iSpeech = 0; + m_iModem = 0; + m_nin = freedv_nin(m_freeDV); + + if (m_nin > 0) { + m_freeDVStats.m_fps = m_modemSampleRate / m_nin; + } + + qDebug() << "FreeDVDemodSink::applyFreeDVMode:" + << " fdv_mode: " << fdv_mode + << " m_modemSampleRate: " << m_modemSampleRate + << " m_lowCutoff: " << m_lowCutoff + << " m_hiCutoff: " << m_hiCutoff + << " Fs: " << Fs + << " Rs: " << Rs + << " m_nSpeechSamples: " << m_nSpeechSamples + << " m_nMaxModemSamples: " << m_nMaxModemSamples + << " m_nin: " << m_nin + << " FPS: " << m_freeDVStats.m_fps; + } + else + { + qCritical("FreeDVDemodSink::applyFreeDVMode: m_freeDV was not allocated"); + } +} + +void FreeDVDemodSink::applySettings(const FreeDVDemodSettings& settings, bool force) +{ + qDebug() << "FreeDVDemodSink::applySettings:" + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_freeDVMode: " << (int) settings.m_freeDVMode + << " m_volume: " << settings.m_volume + << " m_volumeIn: " << settings.m_volumeIn + << " m_spanLog2: " << settings.m_spanLog2 + << " m_audioMute: " << settings.m_audioMute + << " m_agcActive: " << settings.m_agc + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " m_streamIndex: " << settings.m_streamIndex + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex + << " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex + << " force: " << force; + + if ((m_settings.m_volume != settings.m_volume) || force) + { + m_volume = settings.m_volume; + m_volume /= 4.0; // for 3276.8 + } + + m_spanLog2 = settings.m_spanLog2; + m_audioMute = settings.m_audioMute; + m_agcActive = settings.m_agc; + m_settings = settings; +} + +void FreeDVDemodSink::getSNRLevels(double& avg, double& peak, int& nbSamples) +{ + if (m_freeDVSNR.m_n > 0) + { + avg = CalcDb::dbPower(m_freeDVSNR.m_sum / m_freeDVSNR.m_n); + peak = m_freeDVSNR.m_peak; + nbSamples = m_freeDVSNR.m_n; + m_freeDVSNR.m_reset = true; + } + else + { + avg = 0.0; + peak = 0.0; + nbSamples = 1; + } +} + +void FreeDVDemodSink::resyncFreeDV() +{ + freedv_set_sync(m_freeDV, FREEDV_SYNC_UNSYNC); +} \ No newline at end of file diff --git a/plugins/channelrx/demodfreedv/freedvdemodsink.h b/plugins/channelrx/demodfreedv/freedvdemodsink.h new file mode 100644 index 000000000..f31405134 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodsink.h @@ -0,0 +1,211 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_FREEDVDEMODSINK_H +#define INCLUDE_FREEDVDEMODSINK_H + +#include + +#include + +#include "dsp/channelsamplesink.h" +#include "dsp/ncof.h" +#include "dsp/interpolator.h" +#include "dsp/fftfilt.h" +#include "dsp/agc.h" +#include "audio/audiofifo.h" +#include "audio/audioresampler.h" +#include "util/doublebufferfifo.h" + +#include "freedvdemodsettings.h" + +struct freedv; +class BasebandSampleSink; + +class FreeDVDemodSink : public ChannelSampleSink { +public: + FreeDVDemodSink(); + ~FreeDVDemodSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); + void applySettings(const FreeDVDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); + void applyFreeDVMode(FreeDVDemodSettings::FreeDVMode mode); + AudioFifo *getAudioFifo() { return &m_audioFifo; } + void resyncFreeDV(); + + void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_spectrumSink = spectrumSink; } + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } + uint32_t getModemSampleRate() const { return m_modemSampleRate; } + double getMagSq() const { return m_magsq; } + bool getAudioActive() const { return m_audioActive; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + void getSNRLevels(double& avg, double& peak, int& nbSamples); + int getBER() const { return m_freeDVStats.m_ber; } + float getFrequencyOffset() const { return m_freeDVStats.m_freqOffset; } + bool isSync() const { return m_freeDVStats.m_sync; } + + /** + * Level changed + * \param rmsLevel RMS level in range 0.0 - 1.0 + * \param peakLevel Peak level in range 0.0 - 1.0 + * \param numSamples Number of samples analyzed + */ + void getLevels(qreal& rmsLevel, qreal& peakLevel, int& numSamples) + { + rmsLevel = m_rmsLevel; + peakLevel = m_peakLevel; + numSamples = m_levelInNbSamples; + } + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + struct FreeDVStats + { + FreeDVStats(); + void init(); + void collect(struct freedv *freedv); + + bool m_sync; + float m_snrEst; + float m_clockOffset; + float m_freqOffset; + float m_syncMetric; + int m_totalBitErrors; + int m_lastTotalBitErrors; + int m_ber; //!< estimated BER (b/s) + uint32_t m_frameCount; + uint32_t m_berFrameCount; //!< count of frames for BER estimation + uint32_t m_fps; //!< frames per second + }; + + struct FreeDVSNR + { + FreeDVSNR(); + void accumulate(float snrdB); + + double m_sum; + float m_peak; + int m_n; + bool m_reset; + }; + + struct LevelRMS + { + LevelRMS(); + void accumulate(float fsample); + + double m_sum; + float m_peak; + int m_n; + bool m_reset; + }; + + FreeDVDemodSettings m_settings; + + Real m_hiCutoff; + Real m_lowCutoff; + Real m_volume; + int m_spanLog2; + fftfilt::cmplx m_sum; + int m_undersampleCount; + int m_channelSampleRate; + uint32_t m_modemSampleRate; + uint32_t m_speechSampleRate; + uint32_t m_audioSampleRate; + int m_channelFrequencyOffset; + bool m_audioMute; + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + SimpleAGC<4800> m_simpleAGC; + bool m_agcActive; + DoubleBufferFIFO m_squelchDelayLine; + bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold) + + NCOF m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + fftfilt* SSBFilter; + fftfilt::cmplx* m_SSBFilterBuffer; + unsigned int m_SSBFilterBufferIndex; + + BasebandSampleSink* m_spectrumSink; + SampleVector m_sampleBuffer; + + AudioVector m_audioBuffer; + uint m_audioBufferFill; + AudioFifo m_audioFifo; + + struct freedv *m_freeDV; + int m_nSpeechSamples; + int m_nMaxModemSamples; + int m_nin; + int m_iSpeech; + int m_iModem; + int16_t *m_speechOut; + int16_t *m_modIn; + AudioResampler m_audioResampler; + FreeDVStats m_freeDVStats; + FreeDVSNR m_freeDVSNR; + LevelRMS m_levelIn; + int m_levelInNbSamples; + Real m_rmsLevel; + Real m_peakLevel; + + static const unsigned int m_ssbFftLen; + static const float m_agcTarget; + + void pushSampleToDV(int16_t sample); + void pushSampleToAudio(int16_t sample); + void processOneSample(Complex &ci); + void calculateLevel(int16_t& sample); +}; + +#endif // INCLUDE_FREEDVDEMODSINK_H diff --git a/plugins/channelrx/demodfreedv/freedvplugin.cpp b/plugins/channelrx/demodfreedv/freedvplugin.cpp index e9ffabe44..d565da3f6 100644 --- a/plugins/channelrx/demodfreedv/freedvplugin.cpp +++ b/plugins/channelrx/demodfreedv/freedvplugin.cpp @@ -27,7 +27,7 @@ const PluginDescriptor FreeDVPlugin::m_pluginDescriptor = { QString("FreeDV Demodulator"), - QString("4.11.6"), + QString("4.12.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/localsink/CMakeLists.txt b/plugins/channelrx/localsink/CMakeLists.txt index 4ab92d520..fa07e38d2 100644 --- a/plugins/channelrx/localsink/CMakeLists.txt +++ b/plugins/channelrx/localsink/CMakeLists.txt @@ -1,59 +1,61 @@ project(localsink) set(localsink_SOURCES - localsink.cpp - localsinksettings.cpp - localsinkwebapiadapter.cpp - localsinkthread.cpp - localsinkplugin.cpp + localsink.cpp + localsinkbaseband.cpp + localsinksink.cpp + localsinksettings.cpp + localsinkwebapiadapter.cpp + localsinkthread.cpp + localsinkplugin.cpp ) set(localsink_HEADERS localsink.h + localsinkbaseband.h + localsinksink.h localsinksettings.h localsinkwebapiadapter.h localsinkthread.h localsinkplugin.h - ) +) include_directories( ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${Boost_INCLUDE_DIR} - ) +) if(NOT SERVER_MODE) - set(localsink_SOURCES - ${localsink_SOURCES} - localsinkgui.cpp - - localsinkgui.ui + set(localsink_SOURCES + ${localsink_SOURCES} + localsinkgui.cpp + localsinkgui.ui ) - set(localsink_HEADERS - ${localsink_HEADERS} - localsinkgui.h + set(localsink_HEADERS + ${localsink_HEADERS} + localsinkgui.h ) - - set(TARGET_NAME localsink) - set(TARGET_LIB "Qt5::Widgets") - set(TARGET_LIB_GUI "sdrgui") - set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) + set(TARGET_NAME localsink) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) else() - set(TARGET_NAME localsinksrv) - set(TARGET_LIB "") - set(TARGET_LIB_GUI "") - set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) + set(TARGET_NAME localsinksrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) endif() add_library(${TARGET_NAME} SHARED - ${localsink_SOURCES} - ) + ${localsink_SOURCES} +) target_link_libraries(${TARGET_NAME} - Qt5::Core - ${TARGET_LIB} + Qt5::Core + ${TARGET_LIB} sdrbase ${TARGET_LIB_GUI} - swagger + swagger ) install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/channelrx/localsink/localsink.cpp b/plugins/channelrx/localsink/localsink.cpp index 957732503..cc7d653f6 100644 --- a/plugins/channelrx/localsink/localsink.cpp +++ b/plugins/channelrx/localsink/localsink.cpp @@ -15,7 +15,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "localsink.h" #include #include @@ -37,11 +36,11 @@ #include "dsp/devicesamplemimo.h" #include "device/deviceapi.h" -#include "localsinkthread.h" +#include "localsinkbaseband.h" +#include "localsink.h" MESSAGE_CLASS_DEFINITION(LocalSink::MsgConfigureLocalSink, Message) -MESSAGE_CLASS_DEFINITION(LocalSink::MsgSampleRateNotification, Message) -MESSAGE_CLASS_DEFINITION(LocalSink::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(LocalSink::MsgBasebandSampleRateNotification, Message) const QString LocalSink::m_channelIdURI = "sdrangel.channel.localsink"; const QString LocalSink::m_channelId = "LocalSink"; @@ -49,18 +48,19 @@ const QString LocalSink::m_channelId = "LocalSink"; LocalSink::LocalSink(DeviceAPI *deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), m_deviceAPI(deviceAPI), - m_running(false), - m_sinkThread(0), m_centerFrequency(0), m_frequencyOffset(0), - m_sampleRate(48000), - m_deviceSampleRate(48000) + m_basebandSampleRate(48000) { setObjectName(m_channelId); - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addChannelSink(m_threadedChannelizer); + m_thread = new QThread(this); + m_basebandSink = new LocalSinkBaseband(); + m_basebandSink->moveToThread(m_thread); + + applySettings(m_settings, true); + + m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); m_networkManager = new QNetworkAccessManager(); @@ -72,9 +72,9 @@ LocalSink::~LocalSink() disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); delete m_networkManager; m_deviceAPI->removeChannelSinkAPI(this); - m_deviceAPI->removeChannelSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; + m_deviceAPI->removeChannelSink(this); + delete m_basebandSink; + delete m_thread; } uint32_t LocalSink::getNumberOfDeviceStreams() const @@ -85,71 +85,26 @@ uint32_t LocalSink::getNumberOfDeviceStreams() const void LocalSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) { (void) firstOfBurst; - emit samplesAvailable((const quint8*) &(*begin), (end-begin)*sizeof(Sample)); + m_basebandSink->feed(begin, end); } void LocalSink::start() { - qDebug("LocalSink::start"); - - if (m_running) { - stop(); - } - - m_sinkThread = new LocalSinkThread(); - DeviceSampleSource *deviceSource = getLocalDevice(m_settings.m_localDeviceIndex); - - if (deviceSource) { - m_sinkThread->setSampleFifo(deviceSource->getSampleFifo()); - } - - connect(this, - SIGNAL(samplesAvailable(const quint8*, uint)), - m_sinkThread, - SLOT(processSamples(const quint8*, uint)), - Qt::QueuedConnection); - - m_sinkThread->startStop(true); - m_running = true; + qDebug("LocalSink::start"); + m_basebandSink->reset(); + m_thread->start(); } void LocalSink::stop() { qDebug("LocalSink::stop"); - - disconnect(this, - SIGNAL(samplesAvailable(const quint8*, uint)), - m_sinkThread, - SLOT(processSamples(const quint8*, uint))); - - if (m_sinkThread != 0) - { - m_sinkThread->startStop(false); - m_sinkThread->deleteLater(); - m_sinkThread = 0; - } - - m_running = false; + m_thread->exit(); + m_thread->wait(); } bool LocalSink::handleMessage(const Message& cmd) { - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - qDebug() << "LocalSink::handleMessage: MsgChannelizerNotification:" - << " channelSampleRate: " << notif.getSampleRate() - << " offsetFrequency: " << notif.getFrequencyOffset(); - - if (notif.getSampleRate() > 0) - { - setSampleRate(notif.getSampleRate()); - } - - return true; - } - else if (DSPSignalNotification::match(cmd)) + if (DSPSignalNotification::match(cmd)) { DSPSignalNotification& notif = (DSPSignalNotification&) cmd; @@ -157,20 +112,19 @@ bool LocalSink::handleMessage(const Message& cmd) << " inputSampleRate: " << notif.getSampleRate() << " centerFrequency: " << notif.getCenterFrequency(); - setCenterFrequency(notif.getCenterFrequency()); - m_deviceSampleRate = notif.getSampleRate(); - calculateFrequencyOffset(); // This is when device sample rate changes - propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex); + m_basebandSampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); - // Redo the channelizer stuff with the new sample rate to re-synchronize everything - m_channelizer->set(m_channelizer->getInputMessageQueue(), - m_settings.m_log2Decim, - m_settings.m_filterChainHash); + calculateFrequencyOffset(m_settings.m_log2Decim, m_settings.m_filterChainHash); // This is when device sample rate changes + propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex, m_settings.m_log2Decim); - if (m_guiMessageQueue) + MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(notif.getSampleRate()); + m_basebandSink->getInputMessageQueue()->push(msg); + + if (getMessageQueueToGUI()) { - MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate()); - m_guiMessageQueue->push(msg); + MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(notif.getSampleRate()); + getMessageQueueToGUI()->push(msg); } return true; @@ -183,25 +137,6 @@ bool LocalSink::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - m_settings.m_log2Decim = cfg.getLog2Decim(); - m_settings.m_filterChainHash = cfg.getFilterChainHash(); - - qDebug() << "LocalSink::handleMessage: MsgConfigureChannelizer:" - << " log2Decim: " << m_settings.m_log2Decim - << " filterChainHash: " << m_settings.m_filterChainHash; - - m_channelizer->set(m_channelizer->getInputMessageQueue(), - m_settings.m_log2Decim, - m_settings.m_filterChainHash); - - calculateFrequencyOffset(); // This is when decimation or filter chain changes - propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex); - - return true; - } else { return false; @@ -279,43 +214,68 @@ DeviceSampleSource *LocalSink::getLocalDevice(uint32_t index) return nullptr; } -void LocalSink::propagateSampleRateAndFrequency(uint32_t index) +void LocalSink::propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Decim) { + qDebug() << "LocalSink::propagateSampleRateAndFrequency:" + << " index: " << index + << " baseband_freq: " << m_basebandSampleRate + << " log2Decim: " << log2Decim + << " frequency: " << m_centerFrequency + m_frequencyOffset; + DeviceSampleSource *deviceSource = getLocalDevice(index); if (deviceSource) { - deviceSource->setSampleRate(m_deviceSampleRate / (1<setSampleRate(m_basebandSampleRate / (1 << log2Decim)); deviceSource->setCenterFrequency(m_centerFrequency + m_frequencyOffset); } + else + { + qDebug("LocalSink::propagateSampleRateAndFrequency: no suitable device at index %u", index); + } } void LocalSink::applySettings(const LocalSinkSettings& settings, bool force) { qDebug() << "LocalSink::applySettings:" - << " m_localDeviceIndex: " << settings.m_localDeviceIndex - << " m_streamIndex: " << settings.m_streamIndex - << " force: " << force; + << "m_localDeviceIndex: " << settings.m_localDeviceIndex + << "m_streamIndex: " << settings.m_streamIndex + << "m_play:" << settings.m_play + << "force: " << force; QList reverseAPIKeys; + if ((settings.m_log2Decim != m_settings.m_log2Decim) || force) { + reverseAPIKeys.append("log2Decim"); + } + if ((settings.m_filterChainHash != m_settings.m_filterChainHash) || force) { + reverseAPIKeys.append("filterChainHash"); + } + if ((settings.m_localDeviceIndex != m_settings.m_localDeviceIndex) || force) { reverseAPIKeys.append("localDeviceIndex"); + propagateSampleRateAndFrequency(settings.m_localDeviceIndex, settings.m_log2Decim); DeviceSampleSource *deviceSource = getLocalDevice(settings.m_localDeviceIndex); + LocalSinkBaseband::MsgConfigureLocalDeviceSampleSource *msg = + LocalSinkBaseband::MsgConfigureLocalDeviceSampleSource::create(deviceSource); + m_basebandSink->getInputMessageQueue()->push(msg); + } - if (deviceSource) - { - if (m_sinkThread) { - m_sinkThread->setSampleFifo(deviceSource->getSampleFifo()); - } + if ((settings.m_log2Decim != m_settings.m_log2Decim) + || (settings.m_filterChainHash != m_settings.m_filterChainHash) || force) + { + calculateFrequencyOffset(settings.m_log2Decim, settings.m_filterChainHash); + propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex, settings.m_log2Decim); + } - propagateSampleRateAndFrequency(settings.m_localDeviceIndex); - } - else - { - qWarning("LocalSink::applySettings: invalid local device for index %u", settings.m_localDeviceIndex); - } + if ((settings.m_play != m_settings.m_play) || force) + { + reverseAPIKeys.append("play"); + LocalSinkBaseband::MsgConfigureLocalSinkWork *msg = LocalSinkBaseband::MsgConfigureLocalSinkWork::create( + settings.m_play + ); + m_basebandSink->getInputMessageQueue()->push(msg); } if (m_settings.m_streamIndex != settings.m_streamIndex) @@ -323,16 +283,17 @@ void LocalSink::applySettings(const LocalSinkSettings& settings, bool force) if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only { m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); - m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); - m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); - // apply stream sample rate to itself - //applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); } reverseAPIKeys.append("streamIndex"); } + LocalSinkBaseband::MsgConfigureLocalSinkBaseband *msg = LocalSinkBaseband::MsgConfigureLocalSinkBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + if ((settings.m_useReverseAPI) && (reverseAPIKeys.size() != 0)) { bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || @@ -357,10 +318,10 @@ void LocalSink::validateFilterChainHash(LocalSinkSettings& settings) settings.m_filterChainHash = settings.m_filterChainHash >= s ? s-1 : settings.m_filterChainHash; } -void LocalSink::calculateFrequencyOffset() +void LocalSink::calculateFrequencyOffset(uint32_t log2Decim, uint32_t filterChainHash) { - double shiftFactor = HBFilterChainConverter::getShiftFactor(m_settings.m_log2Decim, m_settings.m_filterChainHash); - m_frequencyOffset = m_deviceSampleRate * shiftFactor; + double shiftFactor = HBFilterChainConverter::getShiftFactor(log2Decim, filterChainHash); + m_frequencyOffset = m_basebandSampleRate * shiftFactor; } int LocalSink::webapiSettingsGet( @@ -387,12 +348,6 @@ int LocalSink::webapiSettingsPutPatch( MsgConfigureLocalSink *msg = MsgConfigureLocalSink::create(settings, force); m_inputMessageQueue.push(msg); - if ((settings.m_log2Decim != m_settings.m_log2Decim) || (settings.m_filterChainHash != m_settings.m_filterChainHash) || force) - { - MsgConfigureChannelizer *msg = MsgConfigureChannelizer::create(settings.m_log2Decim, settings.m_filterChainHash); - m_inputMessageQueue.push(msg); - } - qDebug("LocalSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); if (m_guiMessageQueue) // forward to GUI if any { diff --git a/plugins/channelrx/localsink/localsink.h b/plugins/channelrx/localsink/localsink.h index bbde80277..d0431aab2 100644 --- a/plugins/channelrx/localsink/localsink.h +++ b/plugins/channelrx/localsink/localsink.h @@ -26,13 +26,13 @@ #include "channel/channelapi.h" #include "localsinksettings.h" -class DeviceAPI; -class DeviceSampleSource; -class ThreadedBasebandSampleSink; -class DownChannelizer; -class LocalSinkThread; class QNetworkAccessManager; class QNetworkReply; +class QThread; + +class DeviceAPI; +class DeviceSampleSource; +class LocalSinkBaseband; class LocalSink : public BasebandSampleSink, public ChannelAPI { Q_OBJECT @@ -60,19 +60,19 @@ public: { } }; - class MsgSampleRateNotification : public Message { + class MsgBasebandSampleRateNotification : public Message { MESSAGE_CLASS_DECLARATION public: - static MsgSampleRateNotification* create(int sampleRate) { - return new MsgSampleRateNotification(sampleRate); + static MsgBasebandSampleRateNotification* create(int sampleRate) { + return new MsgBasebandSampleRateNotification(sampleRate); } int getSampleRate() const { return m_sampleRate; } private: - MsgSampleRateNotification(int sampleRate) : + MsgBasebandSampleRateNotification(int sampleRate) : Message(), m_sampleRate(sampleRate) { } @@ -80,28 +80,6 @@ public: int m_sampleRate; }; - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getLog2Decim() const { return m_log2Decim; } - int getFilterChainHash() const { return m_filterChainHash; } - - static MsgConfigureChannelizer* create(unsigned int log2Decim, unsigned int filterChainHash) { - return new MsgConfigureChannelizer(log2Decim, filterChainHash); - } - - private: - unsigned int m_log2Decim; - unsigned int m_filterChainHash; - - MsgConfigureChannelizer(unsigned int log2Decim, unsigned int filterChainHash) : - Message(), - m_log2Decim(log2Decim), - m_filterChainHash(filterChainHash) - { } - }; - LocalSink(DeviceAPI *deviceAPI); virtual ~LocalSink(); virtual void destroy() { delete this; } @@ -147,44 +125,31 @@ public: const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response); - /** Set center frequency given in Hz */ - void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency; } - - /** Set sample rate given in Hz */ - void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; } - - void setChannelizer(unsigned int log2Decim, unsigned int filterChainHash); void getLocalDevices(std::vector& indexes); uint32_t getNumberOfDeviceStreams() const; static const QString m_channelIdURI; static const QString m_channelId; -signals: - void samplesAvailable(const quint8* data, uint count); - private: DeviceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - bool m_running; - + QThread *m_thread; + LocalSinkBaseband *m_basebandSink; LocalSinkSettings m_settings; - LocalSinkThread *m_sinkThread; uint64_t m_centerFrequency; int64_t m_frequencyOffset; - uint32_t m_sampleRate; - uint32_t m_deviceSampleRate; + uint32_t m_basebandSampleRate; QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; void applySettings(const LocalSinkSettings& settings, bool force = false); - DeviceSampleSource *getLocalDevice(uint32_t index); - void propagateSampleRateAndFrequency(uint32_t index); + void propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Decim); static void validateFilterChainHash(LocalSinkSettings& settings); - void calculateFrequencyOffset(); + void calculateFrequencyOffset(uint32_t log2Decim, uint32_t filterChainHash); + DeviceSampleSource *getLocalDevice(uint32_t index); + void webapiReverseSendSettings(QList& channelSettingsKeys, const LocalSinkSettings& settings, bool force); private slots: diff --git a/plugins/channelrx/localsink/localsinkbaseband.cpp b/plugins/channelrx/localsink/localsinkbaseband.cpp new file mode 100644 index 000000000..4eeb9e8f5 --- /dev/null +++ b/plugins/channelrx/localsink/localsinkbaseband.cpp @@ -0,0 +1,181 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "dsp/downsamplechannelizer.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" + +#include "localsinkbaseband.h" + +MESSAGE_CLASS_DEFINITION(LocalSinkBaseband::MsgConfigureLocalSinkBaseband, Message) +MESSAGE_CLASS_DEFINITION(LocalSinkBaseband::MsgConfigureLocalSinkWork, Message) +MESSAGE_CLASS_DEFINITION(LocalSinkBaseband::MsgBasebandSampleRateNotification, Message) +MESSAGE_CLASS_DEFINITION(LocalSinkBaseband::MsgConfigureLocalDeviceSampleSource, Message) + +LocalSinkBaseband::LocalSinkBaseband() : + m_localSampleSource(nullptr), + m_mutex(QMutex::Recursive) +{ + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownSampleChannelizer(&m_sink); + + qDebug("LocalSinkBaseband::LocalSinkBaseband"); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &LocalSinkBaseband::handleData, + Qt::QueuedConnection + ); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +LocalSinkBaseband::~LocalSinkBaseband() +{ + delete m_channelizer; +} + +void LocalSinkBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.reset(); +} + +void LocalSinkBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void LocalSinkBaseband::handleData() +{ + QMutexLocker mutexLocker(&m_mutex); + + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void LocalSinkBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool LocalSinkBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureLocalSinkBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureLocalSinkBaseband& cfg = (MsgConfigureLocalSinkBaseband&) cmd; + qDebug() << "LocalSinkBaseband::handleMessage: MsgConfigureLocalSinkBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (MsgBasebandSampleRateNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgBasebandSampleRateNotification& notif = (MsgBasebandSampleRateNotification&) cmd; + qDebug() << "LocalSinkBaseband::handleMessage: MsgBasebandSampleRateNotification: basebandSampleRate: " << notif.getBasebandSampleRate(); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getBasebandSampleRate())); + m_channelizer->setBasebandSampleRate(notif.getBasebandSampleRate()); + + return true; + } + else if (MsgConfigureLocalSinkWork::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureLocalSinkWork& conf = (MsgConfigureLocalSinkWork&) cmd; + qDebug() << "LocalSinkBaseband::handleMessage: MsgConfigureLocalSinkWork: " << conf.isWorking(); + + if (conf.isWorking()) { + m_sink.start(m_localSampleSource); + } else { + m_sink.stop(); + } + + return true; + } + else if (MsgConfigureLocalDeviceSampleSource::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureLocalDeviceSampleSource& notif = (MsgConfigureLocalDeviceSampleSource&) cmd; + qDebug() << "LocalSinkBaseband::handleMessage: MsgConfigureLocalDeviceSampleSource: " << notif.getDeviceSampleSource(); + m_localSampleSource = notif.getDeviceSampleSource(); + + if (m_sink.isRunning()) { + m_sink.start(m_localSampleSource); + } + + return true; + } + else + { + return false; + } +} + +void LocalSinkBaseband::applySettings(const LocalSinkSettings& settings, bool force) +{ + qDebug() << "LocalSinkBaseband::applySettings:" + << "m_localDeviceIndex:" << settings.m_localDeviceIndex + << "m_log2Decim:" << settings.m_log2Decim + << "m_filterChainHash:" << settings.m_filterChainHash + << " force: " << force; + + if ((settings.m_log2Decim != m_settings.m_log2Decim) + || (settings.m_filterChainHash != m_settings.m_filterChainHash) || force) + { + m_channelizer->setDecimation(settings.m_log2Decim, settings.m_filterChainHash); + } + + //m_source.applySettings(settings, force); + m_settings = settings; +} + +int LocalSinkBaseband::getChannelSampleRate() const +{ + return m_channelizer->getChannelSampleRate(); +} diff --git a/plugins/channelrx/localsink/localsinkbaseband.h b/plugins/channelrx/localsink/localsinkbaseband.h new file mode 100644 index 000000000..9ee03cd43 --- /dev/null +++ b/plugins/channelrx/localsink/localsinkbaseband.h @@ -0,0 +1,147 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_LOCALSINKBASEBAND_H +#define INCLUDE_LOCALSINKBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "localsinksink.h" +#include "localsinksettings.h" + +class DownSampleChannelizer; + +class LocalSinkBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureLocalSinkBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const LocalSinkSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureLocalSinkBaseband* create(const LocalSinkSettings& settings, bool force) + { + return new MsgConfigureLocalSinkBaseband(settings, force); + } + + private: + LocalSinkSettings m_settings; + bool m_force; + + MsgConfigureLocalSinkBaseband(const LocalSinkSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureLocalSinkWork : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isWorking() const { return m_working; } + + static MsgConfigureLocalSinkWork* create(bool working) + { + return new MsgConfigureLocalSinkWork(working); + } + + private: + bool m_working; + + MsgConfigureLocalSinkWork(bool working) : + Message(), + m_working(working) + { } + }; + + class MsgBasebandSampleRateNotification : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgBasebandSampleRateNotification* create(int sampleRate) { + return new MsgBasebandSampleRateNotification(sampleRate); + } + + int getBasebandSampleRate() const { return m_sampleRate; } + + private: + + MsgBasebandSampleRateNotification(int sampleRate) : + Message(), + m_sampleRate(sampleRate) + { } + + int m_sampleRate; + }; + + class MsgConfigureLocalDeviceSampleSource : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgConfigureLocalDeviceSampleSource* create(DeviceSampleSource *deviceSampleSource) { + return new MsgConfigureLocalDeviceSampleSource(deviceSampleSource); + } + + DeviceSampleSource *getDeviceSampleSource() const { return m_deviceSampleSource; } + + private: + + MsgConfigureLocalDeviceSampleSource(DeviceSampleSource *deviceSampleSource) : + Message(), + m_deviceSampleSource(deviceSampleSource) + { } + + DeviceSampleSource *m_deviceSampleSource; + }; + + LocalSinkBaseband(); + ~LocalSinkBaseband(); + void reset(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + int getChannelSampleRate() const; + void startSource() { m_sink.start(m_localSampleSource); } + void stopSource() { m_sink.stop(); } + +private: + SampleSinkFifo m_sampleFifo; + DownSampleChannelizer *m_channelizer; + LocalSinkSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + LocalSinkSettings m_settings; + DeviceSampleSource *m_localSampleSource; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void applySettings(const LocalSinkSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + + +#endif // INCLUDE_LOCALSINKBASEBAND_H diff --git a/plugins/channelrx/localsink/localsinkgui.cpp b/plugins/channelrx/localsink/localsinkgui.cpp index fa85111cd..8c0bfc902 100644 --- a/plugins/channelrx/localsink/localsinkgui.cpp +++ b/plugins/channelrx/localsink/localsinkgui.cpp @@ -71,11 +71,16 @@ QByteArray LocalSinkGUI::serialize() const bool LocalSinkGUI::deserialize(const QByteArray& data) { - if(m_settings.deserialize(data)) { + updateLocalDevices(); + + if (m_settings.deserialize(data)) + { displaySettings(); applySettings(true); return true; - } else { + } + else + { resetToDefaults(); return false; } @@ -83,11 +88,11 @@ bool LocalSinkGUI::deserialize(const QByteArray& data) bool LocalSinkGUI::handleMessage(const Message& message) { - if (LocalSink::MsgSampleRateNotification::match(message)) + if (LocalSink::MsgBasebandSampleRateNotification::match(message)) { - LocalSink::MsgSampleRateNotification& notif = (LocalSink::MsgSampleRateNotification&) message; + LocalSink::MsgBasebandSampleRateNotification& notif = (LocalSink::MsgBasebandSampleRateNotification&) message; //m_channelMarker.setBandwidth(notif.getSampleRate()); - m_sampleRate = notif.getSampleRate(); + m_basebandSampleRate = notif.getSampleRate(); displayRateAndShift(); return true; } @@ -111,7 +116,7 @@ LocalSinkGUI::LocalSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb ui(new Ui::LocalSinkGUI), m_pluginAPI(pluginAPI), m_deviceUISet(deviceUISet), - m_sampleRate(0), + m_basebandSampleRate(0), m_tickCount(0) { ui->setupUi(this); @@ -168,23 +173,12 @@ void LocalSinkGUI::applySettings(bool force) } } -void LocalSinkGUI::applyChannelSettings() -{ - if (m_doApplySettings) - { - LocalSink::MsgConfigureChannelizer *msgChan = LocalSink::MsgConfigureChannelizer::create( - m_settings.m_log2Decim, - m_settings.m_filterChainHash); - m_localSink->getInputMessageQueue()->push(msgChan); - } -} - void LocalSinkGUI::displaySettings() { m_channelMarker.blockSignals(true); m_channelMarker.setCenterFrequency(0); m_channelMarker.setTitle(m_settings.m_title); - m_channelMarker.setBandwidth(m_sampleRate); // TODO + m_channelMarker.setBandwidth(m_basebandSampleRate / (1<decimationFactor->setCurrentIndex(m_settings.m_log2Decim); applyDecimation(); displayStreamIndex(); + blockApplySettings(false); } @@ -217,8 +212,8 @@ void LocalSinkGUI::displayStreamIndex() void LocalSinkGUI::displayRateAndShift() { - int shift = m_shiftFrequencyFactor * m_sampleRate; - double channelSampleRate = ((double) m_sampleRate) / (1<offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift))); ui->channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5))); @@ -238,6 +233,20 @@ void LocalSinkGUI::updateLocalDevices() } } +int LocalSinkGUI::getLocalDeviceIndexInCombo(int localDeviceIndex) +{ + int index = 0; + + for (; index < ui->localDevice->count(); index++) + { + if (localDeviceIndex == ui->localDevice->itemData(index).toInt()) { + return index; + } + } + + return -1; +} + void LocalSinkGUI::leaveEvent(QEvent*) { m_channelMarker.setHighlighted(false); @@ -334,6 +343,17 @@ void LocalSinkGUI::on_localDevicesRefresh_clicked(bool checked) { (void) checked; updateLocalDevices(); + int index = getLocalDeviceIndexInCombo(m_settings.m_localDeviceIndex); + + if (index >= 0) { + ui->localDevice->setCurrentIndex(index); + } +} + +void LocalSinkGUI::on_localDevicePlay_toggled(bool checked) +{ + m_settings.m_play = checked; + applySettings(); } void LocalSinkGUI::applyDecimation() @@ -358,7 +378,7 @@ void LocalSinkGUI::applyPosition() ui->filterChainText->setText(s); displayRateAndShift(); - applyChannelSettings(); + applySettings(); } void LocalSinkGUI::tick() diff --git a/plugins/channelrx/localsink/localsinkgui.h b/plugins/channelrx/localsink/localsinkgui.h index 123d8c101..7de6502b0 100644 --- a/plugins/channelrx/localsink/localsinkgui.h +++ b/plugins/channelrx/localsink/localsinkgui.h @@ -62,7 +62,7 @@ private: DeviceUISet* m_deviceUISet; ChannelMarker m_channelMarker; LocalSinkSettings m_settings; - int m_sampleRate; + int m_basebandSampleRate; double m_shiftFrequencyFactor; //!< Channel frequency shift factor bool m_doApplySettings; @@ -77,11 +77,11 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); - void applyChannelSettings(); void displaySettings(); void displayStreamIndex(); void displayRateAndShift(); void updateLocalDevices(); + int getLocalDeviceIndexInCombo(int localDeviceIndex); void leaveEvent(QEvent*); void enterEvent(QEvent*); @@ -95,6 +95,7 @@ private slots: void on_position_valueChanged(int value); void on_localDevice_currentIndexChanged(int index); void on_localDevicesRefresh_clicked(bool checked); + void on_localDevicePlay_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void tick(); diff --git a/plugins/channelrx/localsink/localsinkgui.ui b/plugins/channelrx/localsink/localsinkgui.ui index ad2562ec4..d633245e0 100644 --- a/plugins/channelrx/localsink/localsinkgui.ui +++ b/plugins/channelrx/localsink/localsinkgui.ui @@ -298,6 +298,21 @@ + + + + Start/Stop sink + + + + + + + :/play.png + :/pause.png:/play.png + + + @@ -323,6 +338,11 @@
gui/rollupwidget.h
1 + + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
diff --git a/plugins/channelrx/localsink/localsinkplugin.cpp b/plugins/channelrx/localsink/localsinkplugin.cpp index 9e54a5038..61faa5329 100644 --- a/plugins/channelrx/localsink/localsinkplugin.cpp +++ b/plugins/channelrx/localsink/localsinkplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor LocalSinkPlugin::m_pluginDescriptor = { QString("Local channel sink"), - QString("4.11.6"), + QString("4.12.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/localsink/localsinksettings.cpp b/plugins/channelrx/localsink/localsinksettings.cpp index bdda42e29..4f9772913 100644 --- a/plugins/channelrx/localsink/localsinksettings.cpp +++ b/plugins/channelrx/localsink/localsinksettings.cpp @@ -36,6 +36,7 @@ void LocalSinkSettings::resetToDefaults() m_log2Decim = 0; m_filterChainHash = 0; m_channelMarker = nullptr; + m_play = false; m_streamIndex = 0; m_useReverseAPI = false; m_reverseAPIAddress = "127.0.0.1"; diff --git a/plugins/channelrx/localsink/localsinksettings.h b/plugins/channelrx/localsink/localsinksettings.h index a3b5189b6..71e1f4c17 100644 --- a/plugins/channelrx/localsink/localsinksettings.h +++ b/plugins/channelrx/localsink/localsinksettings.h @@ -30,6 +30,7 @@ struct LocalSinkSettings QString m_title; uint32_t m_log2Decim; uint32_t m_filterChainHash; + bool m_play; int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx). bool m_useReverseAPI; QString m_reverseAPIAddress; diff --git a/plugins/channelrx/localsink/localsinksink.cpp b/plugins/channelrx/localsink/localsinksink.cpp new file mode 100644 index 000000000..359cbaf3d --- /dev/null +++ b/plugins/channelrx/localsink/localsinksink.cpp @@ -0,0 +1,99 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "dsp/devicesamplesource.h" +#include "dsp/hbfilterchainconverter.h" + +#include "localsinkthread.h" +#include "localsinksink.h" + +LocalSinkSink::LocalSinkSink() : + m_sinkThread(nullptr), + m_running(false), + m_centerFrequency(0), + m_frequencyOffset(0), + m_sampleRate(48000), + m_deviceSampleRate(48000) +{ + applySettings(m_settings, true); +} + +LocalSinkSink::~LocalSinkSink() +{ +} + +void LocalSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + emit samplesAvailable((const quint8*) &(*begin), (end-begin)*sizeof(Sample)); +} + +void LocalSinkSink::start(DeviceSampleSource *deviceSource) +{ + qDebug("LocalSinkSink::start"); + + if (m_running) { + stop(); + } + + m_sinkThread = new LocalSinkThread(); + + if (deviceSource) { + m_sinkThread->setSampleFifo(deviceSource->getSampleFifo()); + } + + connect(this, + SIGNAL(samplesAvailable(const quint8*, uint)), + m_sinkThread, + SLOT(processSamples(const quint8*, uint)), + Qt::QueuedConnection); + + m_sinkThread->startStop(true); + m_running = true; +} + +void LocalSinkSink::stop() +{ + qDebug("LocalSinkSink::stop"); + + disconnect(this, + SIGNAL(samplesAvailable(const quint8*, uint)), + m_sinkThread, + SLOT(processSamples(const quint8*, uint))); + + if (m_sinkThread != 0) + { + m_sinkThread->startStop(false); + m_sinkThread->deleteLater(); + m_sinkThread = 0; + } + + m_running = false; +} + +void LocalSinkSink::applySettings(const LocalSinkSettings& settings, bool force) +{ + qDebug() << "LocalSinkSink::applySettings:" + << " m_localDeviceIndex: " << settings.m_localDeviceIndex + << " m_streamIndex: " << settings.m_streamIndex + << " force: " << force; + + m_settings = settings; +} diff --git a/plugins/channelrx/localsink/localsinksink.h b/plugins/channelrx/localsink/localsinksink.h new file mode 100644 index 000000000..27901c250 --- /dev/null +++ b/plugins/channelrx/localsink/localsinksink.h @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_LOCALSINKSINK_H_ +#define INCLUDE_LOCALSINKSINK_H_ + +#include + +#include "dsp/channelsamplesink.h" + +#include "localsinksettings.h" + +class DeviceSampleSource; +class LocalSinkThread; + +class LocalSinkSink : public QObject, public ChannelSampleSink { + Q_OBJECT +public: + LocalSinkSink(); + ~LocalSinkSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applySettings(const LocalSinkSettings& settings, bool force = false); + void start(DeviceSampleSource *deviceSource); + void stop(); + bool isRunning() const { return m_running; } + +signals: + void samplesAvailable(const quint8* data, uint count); + +private: + LocalSinkSettings m_settings; + LocalSinkThread *m_sinkThread; + bool m_running; + + uint64_t m_centerFrequency; + int64_t m_frequencyOffset; + uint32_t m_sampleRate; + uint32_t m_deviceSampleRate; + +}; + +#endif // INCLUDE_LOCALSINKSINK_H_ diff --git a/plugins/channelrx/udpsink/CMakeLists.txt b/plugins/channelrx/udpsink/CMakeLists.txt index 924fc2a8d..42b046f00 100644 --- a/plugins/channelrx/udpsink/CMakeLists.txt +++ b/plugins/channelrx/udpsink/CMakeLists.txt @@ -1,14 +1,18 @@ project(udpsink) set(udpsink_SOURCES - udpsink.cpp + udpsink.cpp + udpsinksink.cpp + udpsinkbaseband.cpp udpsinkplugin.cpp udpsinksettings.cpp udpsinkwebapiadapter.cpp ) set(udpsink_HEADERS - udpsink.h + udpsink.h + udpsinksink.h + udpsinkbaseband.h udpsinkplugin.h udpsinksettings.h udpsinkwebapiadapter.h diff --git a/plugins/channelrx/udpsink/udpsink.cpp b/plugins/channelrx/udpsink/udpsink.cpp index 594c77a4d..c0f727017 100644 --- a/plugins/channelrx/udpsink/udpsink.cpp +++ b/plugins/channelrx/udpsink/udpsink.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "SWGChannelSettings.h" #include "SWGUDPSinkSettings.h" @@ -37,11 +38,7 @@ #include "udpsink.h" -const Real UDPSink::m_agcTarget = 16384.0f; - -MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSource, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSinkSpectrum, Message) +MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSink, Message) const QString UDPSink::m_channelIdURI = "sdrangel.channel.udpsink"; const QString UDPSink::m_channelId = "UDPSink"; @@ -49,71 +46,18 @@ const QString UDPSink::m_channelId = "UDPSink"; UDPSink::UDPSink(DeviceAPI *deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), m_deviceAPI(deviceAPI), - m_inputSampleRate(48000), - m_inputFrequencyOffset(0), - m_outMovingAverage(480, 1e-10), - m_inMovingAverage(480, 1e-10), - m_amMovingAverage(1200, 1e-10), - m_audioFifo(24000), - m_spectrum(0), - m_squelch(1e-6), - m_squelchOpen(false), - m_squelchOpenCount(0), - m_squelchCloseCount(0), - m_squelchGate(4800), - m_squelchRelease(4800), - m_agc(9600, m_agcTarget, 1e-6), - m_settingsMutex(QMutex::Recursive) + m_channelSampleRate(48000), + m_channelFrequencyOffset(0) { setObjectName(m_channelId); - m_udpBuffer16 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); - m_udpBufferMono16 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); - m_udpBuffer24 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); - m_audioSocket = new QUdpSocket(this); - m_udpAudioBuf = new char[m_udpAudioPayloadSize]; + m_thread = new QThread(this); + m_basebandSink = new UDPSinkBaseband(); + m_basebandSink->moveToThread(m_thread); - m_audioBuffer.resize(1<<9); - m_audioBufferFill = 0; + applySettings(m_settings, true); - m_nco.setFreq(0, m_inputSampleRate); - m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.0); - m_sampleDistanceRemain = m_inputSampleRate / m_settings.m_outputSampleRate; - m_spectrumEnabled = false; - m_nextSSBId = 0; - m_nextS16leId = 0; - - m_last = 0; - m_this = 0; - m_scale = 0; - m_magsq = 0; - m_inMagsq = 0; - - UDPFilter = new fftfilt(0.0, (m_settings.m_rfBandwidth / 2.0) / m_settings.m_outputSampleRate, udpBlockSize); - - m_phaseDiscri.setFMScaling((float) m_settings. m_outputSampleRate / (2.0f * m_settings.m_fmDeviation)); - - if (m_audioSocket->bind(QHostAddress::LocalHost, m_settings.m_audioPort)) - { - qDebug("UDPSink::UDPSink: bind audio socket to port %d", m_settings.m_audioPort); - connect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead()), Qt::QueuedConnection); - } - else - { - qWarning("UDPSink::UDPSink: cannot bind audio port"); - } - - m_agc.setClampMax(SDR_RX_SCALED*SDR_RX_SCALED); - m_agc.setClamping(true); - - //DSPEngine::instance()->addAudioSink(&m_audioFifo); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - applySettings(m_settings, true); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addChannelSink(m_threadedChannelizer); + m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); m_networkManager = new QNetworkAccessManager(); @@ -124,23 +68,10 @@ UDPSink::~UDPSink() { disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); delete m_networkManager; - delete m_audioSocket; - delete m_udpBuffer24; - delete m_udpBuffer16; - delete m_udpBufferMono16; - delete[] m_udpAudioBuf; - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); - m_deviceAPI->removeChannelSinkAPI(this); - m_deviceAPI->removeChannelSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; - delete UDPFilter; -} - -void UDPSink::setSpectrum(MessageQueue* messageQueue, bool enabled) -{ - Message* cmd = MsgUDPSinkSpectrum::create(enabled); - messageQueue->push(cmd); + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this); + delete m_basebandSink; + delete m_thread; } uint32_t UDPSink::getNumberOfDeviceStreams() const @@ -150,355 +81,57 @@ uint32_t UDPSink::getNumberOfDeviceStreams() const void UDPSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) { - Complex ci; - fftfilt::cmplx* sideband; - double l, r; - - m_sampleBuffer.clear(); - m_settingsMutex.lock(); - - for(SampleVector::const_iterator it = begin; it < end; ++it) - { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if(m_interpolator.decimate(&m_sampleDistanceRemain, c, &ci)) - { - double inMagSq; - double agcFactor = 1.0; - - if ((m_settings.m_agc) && - (m_settings.m_sampleFormat != UDPSinkSettings::FormatNFM) && - (m_settings.m_sampleFormat != UDPSinkSettings::FormatNFMMono) && - (m_settings.m_sampleFormat != UDPSinkSettings::FormatIQ16) && - (m_settings.m_sampleFormat != UDPSinkSettings::FormatIQ24)) - { - agcFactor = m_agc.feedAndGetValue(ci); - inMagSq = m_agc.getMagSq(); - } - else - { - inMagSq = ci.real()*ci.real() + ci.imag()*ci.imag(); - } - - m_inMovingAverage.feed(inMagSq / (SDR_RX_SCALED*SDR_RX_SCALED)); - m_inMagsq = m_inMovingAverage.average(); - - Sample ss(ci.real(), ci.imag()); - m_sampleBuffer.push_back(ss); - - m_sampleDistanceRemain += m_inputSampleRate / m_settings.m_outputSampleRate; - - calculateSquelch(m_inMagsq); - - if (m_settings.m_sampleFormat == UDPSinkSettings::FormatLSB) // binaural LSB - { - ci *= agcFactor; - int n_out = UDPFilter->runSSB(ci, &sideband, false); - - if (n_out) - { - for (int i = 0; i < n_out; i++) - { - l = m_squelchOpen ? sideband[i].real() * m_settings.m_gain : 0; - r = m_squelchOpen ? sideband[i].imag() * m_settings.m_gain : 0; - udpWrite(l, r); - m_outMovingAverage.feed((l*l + r*r) / (SDR_RX_SCALED*SDR_RX_SCALED)); - } - } - } - if (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB) // binaural USB - { - ci *= agcFactor; - int n_out = UDPFilter->runSSB(ci, &sideband, true); - - if (n_out) - { - for (int i = 0; i < n_out; i++) - { - l = m_squelchOpen ? sideband[i].real() * m_settings.m_gain : 0; - r = m_squelchOpen ? sideband[i].imag() * m_settings.m_gain : 0; - udpWrite(l, r); - m_outMovingAverage.feed((l*l + r*r) / (SDR_RX_SCALED*SDR_RX_SCALED)); - } - } - } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFM) - { - Real discri = m_squelchOpen ? m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_gain : 0; - udpWriteNorm(discri, discri); - m_outMovingAverage.feed(discri*discri); - } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFMMono) - { - Real discri = m_squelchOpen ? m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_gain : 0; - udpWriteNormMono(discri); - m_outMovingAverage.feed(discri*discri); - } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) // Monaural LSB - { - ci *= agcFactor; - int n_out = UDPFilter->runSSB(ci, &sideband, false); - - if (n_out) - { - for (int i = 0; i < n_out; i++) - { - l = m_squelchOpen ? (sideband[i].real() + sideband[i].imag()) * 0.7 * m_settings.m_gain : 0; - udpWriteMono(l); - m_outMovingAverage.feed((l * l) / (SDR_RX_SCALED*SDR_RX_SCALED)); - } - } - } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono) // Monaural USB - { - ci *= agcFactor; - int n_out = UDPFilter->runSSB(ci, &sideband, true); - - if (n_out) - { - for (int i = 0; i < n_out; i++) - { - l = m_squelchOpen ? (sideband[i].real() + sideband[i].imag()) * 0.7 * m_settings.m_gain : 0; - udpWriteMono(l); - m_outMovingAverage.feed((l * l) / (SDR_RX_SCALED*SDR_RX_SCALED)); - } - } - } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMMono) - { - Real amplitude = m_squelchOpen ? sqrt(inMagSq) * agcFactor * m_settings.m_gain : 0; - FixReal demod = (FixReal) amplitude; - udpWriteMono(demod); - m_outMovingAverage.feed((amplitude/SDR_RX_SCALEF)*(amplitude/SDR_RX_SCALEF)); - } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMNoDCMono) - { - if (m_squelchOpen) - { - double demodf = sqrt(inMagSq); - m_amMovingAverage.feed(demodf); - Real amplitude = (demodf - m_amMovingAverage.average()) * agcFactor * m_settings.m_gain; - FixReal demod = (FixReal) amplitude; - udpWriteMono(demod); - m_outMovingAverage.feed((amplitude/SDR_RX_SCALEF)*(amplitude/SDR_RX_SCALEF)); - } - else - { - udpWriteMono(0); - m_outMovingAverage.feed(0); - } - } - else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMBPFMono) - { - if (m_squelchOpen) - { - double demodf = sqrt(inMagSq); - demodf = m_bandpass.filter(demodf); - demodf /= 301.0; - Real amplitude = demodf * agcFactor * m_settings.m_gain; - FixReal demod = (FixReal) amplitude; - udpWriteMono(demod); - m_outMovingAverage.feed((amplitude/SDR_RX_SCALEF)*(amplitude/SDR_RX_SCALEF)); - } - else - { - udpWriteMono(0); - m_outMovingAverage.feed(0); - } - } - else // Raw I/Q samples - { - if (m_squelchOpen) - { - udpWrite(ci.real() * m_settings.m_gain, ci.imag() * m_settings.m_gain); - m_outMovingAverage.feed((inMagSq*m_settings.m_gain*m_settings.m_gain) / (SDR_RX_SCALED*SDR_RX_SCALED)); - } - else - { - udpWrite(0, 0); - m_outMovingAverage.feed(0); - } - } - - m_magsq = m_outMovingAverage.average(); - } - } - - //qDebug() << "UDPSink::feed: " << m_sampleBuffer.size() * 4; - - if((m_spectrum != 0) && (m_spectrumEnabled)) - { - m_spectrum->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), positiveOnly); - } - - m_settingsMutex.unlock(); + (void) positiveOnly; + m_basebandSink->feed(begin, end); } void UDPSink::start() { - m_phaseDiscri.reset(); - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + qDebug() << "UDPSink::start"; + + if (m_basebandSampleRate != 0) { + m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); + } + + m_basebandSink->reset(); + m_thread->start(); } void UDPSink::stop() { + qDebug() << "UDPSink::stop"; + m_thread->exit(); + m_thread->wait(); } bool UDPSink::handleMessage(const Message& cmd) { - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "UDPSink::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << notif.getSampleRate() - << " frequencyOffset: " << notif.getFrequencyOffset(); - - applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); - - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) + if (MsgConfigureUDPSink::match(cmd)) { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug() << "UDPSink::handleMessage: MsgConfigureChannelizer:" - << " sampleRate: " << cfg.getSampleRate() - << " centerFrequency: " << cfg.getCenterFrequency(); - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - - return true; - } - else if (MsgConfigureUDPSource::match(cmd)) - { - MsgConfigureUDPSource& cfg = (MsgConfigureUDPSource&) cmd; - qDebug("UDPSink::handleMessage: MsgConfigureUDPSource"); + MsgConfigureUDPSink& cfg = (MsgConfigureUDPSink&) cmd; + qDebug("UDPSink::handleMessage: MsgConfigureUDPSink"); applySettings(cfg.getSettings(), cfg.getForce()); return true; } - else if (MsgUDPSinkSpectrum::match(cmd)) - { - MsgUDPSinkSpectrum& spc = (MsgUDPSinkSpectrum&) cmd; - - m_spectrumEnabled = spc.getEnabled(); - - qDebug() << "UDPSink::handleMessage: MsgUDPSinkSpectrum: m_spectrumEnabled: " << m_spectrumEnabled; - - return true; - } else if (DSPSignalNotification::match(cmd)) { + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + m_basebandSampleRate = notif.getSampleRate(); + // Forward to the sink + DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "UDPSink::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + return true; } else { - if(m_spectrum != 0) - { - return m_spectrum->handleMessage(cmd); - } - else - { - return false; - } + return false; } } -void UDPSink::audioReadyRead() -{ - while (m_audioSocket->hasPendingDatagrams()) - { - qint64 pendingDataSize = m_audioSocket->pendingDatagramSize(); - qint64 udpReadBytes = m_audioSocket->readDatagram(m_udpAudioBuf, pendingDataSize, 0, 0); - //qDebug("UDPSink::audioReadyRead: %lld", udpReadBytes); - - if (m_settings.m_audioActive) - { - if (m_settings.m_audioStereo) - { - for (int i = 0; i < udpReadBytes - 3; i += 4) - { - qint16 l_sample = (qint16) *(&m_udpAudioBuf[i]); - qint16 r_sample = (qint16) *(&m_udpAudioBuf[i+2]); - m_audioBuffer[m_audioBufferFill].l = l_sample * m_settings.m_volume; - m_audioBuffer[m_audioBufferFill].r = r_sample * m_settings.m_volume; - ++m_audioBufferFill; - - if (m_audioBufferFill >= m_audioBuffer.size()) - { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - - if (res != m_audioBufferFill) - { - qDebug("UDPSink::audioReadyRead: (stereo) lost %u samples", m_audioBufferFill - res); - } - - m_audioBufferFill = 0; - } - } - } - else - { - for (int i = 0; i < udpReadBytes - 1; i += 2) - { - qint16 sample = (qint16) *(&m_udpAudioBuf[i]); - m_audioBuffer[m_audioBufferFill].l = sample * m_settings.m_volume; - m_audioBuffer[m_audioBufferFill].r = sample * m_settings.m_volume; - ++m_audioBufferFill; - - if (m_audioBufferFill >= m_audioBuffer.size()) - { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - - if (res != m_audioBufferFill) - { - qDebug("UDPSink::audioReadyRead: (mono) lost %u samples", m_audioBufferFill - res); - } - - m_audioBufferFill = 0; - } - } - } - - if (m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill) != m_audioBufferFill) - { - qDebug("UDPSink::audioReadyRead: lost samples"); - } - - m_audioBufferFill = 0; - } - } - - //qDebug("UDPSink::audioReadyRead: done"); -} - -void UDPSink::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) -{ - qDebug() << "UDPSink::applyChannelSettings:" - << " inputSampleRate: " << inputSampleRate - << " inputFrequencyOffset: " << inputFrequencyOffset; - - if((inputFrequencyOffset != m_inputFrequencyOffset) || - (inputSampleRate != m_inputSampleRate) || force) - { - m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); - } - - if ((inputSampleRate != m_inputSampleRate) || force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.0); - m_sampleDistanceRemain = inputSampleRate / m_settings.m_outputSampleRate; - m_settingsMutex.unlock(); - } - - m_inputSampleRate = inputSampleRate; - m_inputFrequencyOffset = inputFrequencyOffset; -} - void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) { qDebug() << "UDPSink::applySettings:" @@ -577,133 +210,22 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force) reverseAPIKeys.append("audioPort"); } - m_settingsMutex.lock(); - - if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || - (settings.m_rfBandwidth != m_settings.m_rfBandwidth) || - (settings.m_outputSampleRate != m_settings.m_outputSampleRate) || force) - { - m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.0); - m_sampleDistanceRemain = m_inputSampleRate / settings.m_outputSampleRate; - - if ((settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || - (settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) || - (settings.m_sampleFormat == UDPSinkSettings::FormatUSB) || - (settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono)) - { - m_squelchGate = settings.m_outputSampleRate * 0.05; - } - else - { - m_squelchGate = (settings.m_outputSampleRate * settings.m_squelchGate) / 100; - } - - m_squelchRelease = (settings.m_outputSampleRate * settings.m_squelchGate) / 100; - initSquelch(m_squelchOpen); - m_agc.resize(settings.m_outputSampleRate/5, settings.m_outputSampleRate/20, m_agcTarget); // Fixed 200 ms - int stepDownDelay = (settings.m_outputSampleRate * (settings.m_squelchGate == 0 ? 1 : settings.m_squelchGate))/100; - m_agc.setStepDownDelay(stepDownDelay); - m_agc.setGate(settings.m_outputSampleRate * 0.05); - - m_bandpass.create(301, settings.m_outputSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); - - m_inMovingAverage.resize(settings.m_outputSampleRate * 0.01, 1e-10); // 10 ms - m_amMovingAverage.resize(settings.m_outputSampleRate * 0.005, 1e-10); // 5 ms - m_outMovingAverage.resize(settings.m_outputSampleRate * 0.01, 1e-10); // 10 ms - } - - if ((settings.m_audioActive != m_settings.m_audioActive) || force) - { - if (settings.m_audioActive) - { - m_audioBufferFill = 0; - DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); - } - else - { - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); - } - } - - if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) - { - if ((settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || - (settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) || - (settings.m_sampleFormat == UDPSinkSettings::FormatUSB) || - (settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono)) - { - m_squelchGate = settings.m_outputSampleRate * 0.05; - } - else - { - m_squelchGate = (settings.m_outputSampleRate * settings.m_squelchGate)/100; - } - - m_squelchRelease = (settings.m_outputSampleRate * settings.m_squelchGate)/100; - initSquelch(m_squelchOpen); - int stepDownDelay = (settings.m_outputSampleRate * (settings.m_squelchGate == 0 ? 1 : settings.m_squelchGate))/100; - m_agc.setStepDownDelay(stepDownDelay); // same delay for up and down - } - - if ((settings.m_squelchdB != m_settings.m_squelchdB) || force) - { - m_squelch = CalcDb::powerFromdB(settings.m_squelchdB); - m_agc.setThreshold(m_squelch*(1<<23)); - } - - if ((settings.m_udpAddress != m_settings.m_udpAddress) || force) - { - m_udpBuffer16->setAddress(const_cast(settings.m_udpAddress)); - m_udpBufferMono16->setAddress(const_cast(settings.m_udpAddress)); - m_udpBuffer24->setAddress(const_cast(settings.m_udpAddress)); - } - - if ((settings.m_udpPort != m_settings.m_udpPort) || force) - { - m_udpBuffer16->setPort(settings.m_udpPort); - m_udpBufferMono16->setPort(settings.m_udpPort); - m_udpBuffer24->setPort(settings.m_udpPort); - } - - if ((settings.m_audioPort != m_settings.m_audioPort) || force) - { - disconnect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead())); - delete m_audioSocket; - m_audioSocket = new QUdpSocket(this); - - if (m_audioSocket->bind(QHostAddress::LocalHost, settings.m_audioPort)) - { - connect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead()), Qt::QueuedConnection); - qDebug("UDPSink::handleMessage: audio socket bound to port %d", settings.m_audioPort); - } - else - { - qWarning("UDPSink::handleMessage: cannot bind audio socket"); - } - } - - if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) - { - m_phaseDiscri.setFMScaling((float) settings.m_outputSampleRate / (2.0f * settings.m_fmDeviation)); - } - - m_settingsMutex.unlock(); - if (m_settings.m_streamIndex != settings.m_streamIndex) { if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only { m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); - m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); - m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSink(this, settings.m_streamIndex); m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); - // apply stream sample rate to itself - applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); } reverseAPIKeys.append("streamIndex"); } + UDPSinkBaseband::MsgConfigureUDPSinkBaseband *msg = UDPSinkBaseband::MsgConfigureUDPSinkBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + if (settings.m_useReverseAPI) { bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || @@ -726,14 +248,14 @@ bool UDPSink::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { - MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); + MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(m_settings, true); m_inputMessageQueue.push(msg); return true; } else { m_settings.resetToDefaults(); - MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true); + MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(m_settings, true); m_inputMessageQueue.push(msg); return false; } @@ -760,21 +282,13 @@ int UDPSink::webapiSettingsPutPatch( UDPSinkSettings settings = m_settings; webapiUpdateChannelSettings(settings, channelSettingsKeys, response); - if (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset) - { - UDPSink::MsgConfigureChannelizer *msgChan = UDPSink::MsgConfigureChannelizer::create( - (int) settings.m_outputSampleRate, - (int) settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(msgChan); - } - - MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(settings, force); + MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(settings, force); m_inputMessageQueue.push(msg); qDebug("getUdpSinkSettings::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); if (m_guiMessageQueue) // forward to GUI if any { - MsgConfigureUDPSource *msgToGUI = MsgConfigureUDPSource::create(settings, force); + MsgConfigureUDPSink *msgToGUI = MsgConfigureUDPSink::create(settings, force); m_guiMessageQueue->push(msgToGUI); } @@ -927,8 +441,8 @@ void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getInMagSq())); response.getUdpSinkReport()->setOutputPowerDb(CalcDb::dbPower(getMagSq())); - response.getUdpSinkReport()->setSquelch(m_squelchOpen ? 1 : 0); - response.getUdpSinkReport()->setInputSampleRate(m_inputSampleRate); + response.getUdpSinkReport()->setSquelch(getSquelchOpen() ? 1 : 0); + response.getUdpSinkReport()->setInputSampleRate(m_channelSampleRate); } void UDPSink::webapiReverseSendSettings(QList& channelSettingsKeys, const UDPSinkSettings& settings, bool force) @@ -1044,3 +558,9 @@ void UDPSink::networkManagerFinished(QNetworkReply *reply) reply->deleteLater(); } + +void UDPSink::enableSpectrum(bool enable) +{ + UDPSinkBaseband::MsgEnableSpectrum *msg = UDPSinkBaseband::MsgEnableSpectrum::create(enable); + m_basebandSink->getInputMessageQueue()->push(msg); +} \ No newline at end of file diff --git a/plugins/channelrx/udpsink/udpsink.h b/plugins/channelrx/udpsink/udpsink.h index 27e4a9ebe..0151de81d 100644 --- a/plugins/channelrx/udpsink/udpsink.h +++ b/plugins/channelrx/udpsink/udpsink.h @@ -16,56 +16,43 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_UDPSRC_H -#define INCLUDE_UDPSRC_H +#ifndef INCLUDE_UDPSINK_H +#define INCLUDE_UDPSINK_H #include -#include #include #include "dsp/basebandsamplesink.h" #include "channel/channelapi.h" -#include "dsp/nco.h" -#include "dsp/fftfilt.h" -#include "dsp/interpolator.h" -#include "dsp/phasediscri.h" -#include "dsp/movingaverage.h" -#include "dsp/agc.h" -#include "dsp/bandpass.h" -#include "util/udpsinkutil.h" #include "util/message.h" -#include "audio/audiofifo.h" -#include "udpsinksettings.h" +#include "udpsinkbaseband.h" class QNetworkAccessManager; class QNetworkReply; -class QUdpSocket; class DeviceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; class UDPSink : public BasebandSampleSink, public ChannelAPI { Q_OBJECT public: - class MsgConfigureUDPSource : public Message { + class MsgConfigureUDPSink : public Message { MESSAGE_CLASS_DECLARATION public: const UDPSinkSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureUDPSource* create(const UDPSinkSettings& settings, bool force) + static MsgConfigureUDPSink* create(const UDPSinkSettings& settings, bool force) { - return new MsgConfigureUDPSource(settings, force); + return new MsgConfigureUDPSink(settings, force); } private: UDPSinkSettings m_settings; bool m_force; - MsgConfigureUDPSource(const UDPSinkSettings& settings, bool force) : + MsgConfigureUDPSink(const UDPSinkSettings& settings, bool force) : Message(), m_settings(settings), m_force(force) @@ -73,38 +60,16 @@ public: } }; - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - UDPSink(DeviceAPI *deviceAPI); virtual ~UDPSink(); virtual void destroy() { delete this; } - void setSpectrum(BasebandSampleSink* spectrum) { m_spectrum = spectrum; } - void setSpectrum(MessageQueue* messageQueue, bool enabled); - double getMagSq() const { return m_magsq; } - double getInMagSq() const { return m_inMagsq; } - bool getSquelchOpen() const { return m_squelchOpen; } + void setSpectrum(BasebandSampleSink* spectrum) { m_basebandSink->setSpectrum(spectrum); } + void enableSpectrum(bool enable); + void setSpectrumPositiveOnly(bool positiveOnly) { m_basebandSink->setSpectrumPositiveOnly(positiveOnly); } + double getMagSq() const { return m_basebandSink->getMagSq(); } + double getInMagSq() const { return m_basebandSink->getInMagSq(); } + bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); } virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); virtual void start(); @@ -164,212 +129,22 @@ private slots: void networkManagerFinished(QNetworkReply *reply); protected: - class MsgUDPSinkSpectrum : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getEnabled() const { return m_enabled; } - - static MsgUDPSinkSpectrum* create(bool enabled) - { - return new MsgUDPSinkSpectrum(enabled); - } - - private: - bool m_enabled; - - MsgUDPSinkSpectrum(bool enabled) : - Message(), - m_enabled(enabled) - { } - }; - - struct Sample16 - { - Sample16() : m_r(0), m_i(0) {} - Sample16(int16_t r, int16_t i) : m_r(r), m_i(i) {} - int16_t m_r; - int16_t m_i; - }; - - struct Sample24 - { - Sample24() : m_r(0), m_i(0) {} - Sample24(int32_t r, int32_t i) : m_r(r), m_i(i) {} - int32_t m_r; - int32_t m_i; - }; - - DeviceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; + DeviceAPI* m_deviceAPI; + QThread *m_thread; + UDPSinkBaseband* m_basebandSink; UDPSinkSettings m_settings; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink - QUdpSocket *m_audioSocket; - - double m_magsq; - double m_inMagsq; - MovingAverage m_outMovingAverage; - MovingAverage m_inMovingAverage; - MovingAverage m_amMovingAverage; - - Real m_scale; - Complex m_last, m_this; - - NCO m_nco; - Interpolator m_interpolator; - Real m_sampleDistanceRemain; - fftfilt* UDPFilter; - - SampleVector m_sampleBuffer; - UDPSinkUtil *m_udpBuffer16; - UDPSinkUtil *m_udpBufferMono16; - UDPSinkUtil *m_udpBuffer24; - - AudioVector m_audioBuffer; - uint m_audioBufferFill; - AudioFifo m_audioFifo; - - BasebandSampleSink* m_spectrum; - bool m_spectrumEnabled; - - quint32 m_nextSSBId; - quint32 m_nextS16leId; - - char *m_udpAudioBuf; - static const int m_udpAudioPayloadSize = 8192; //!< UDP audio samples buffer. No UDP block on Earth is larger than this - static const Real m_agcTarget; - - PhaseDiscriminators m_phaseDiscri; - - double m_squelch; - bool m_squelchOpen; - int m_squelchOpenCount; - int m_squelchCloseCount; - int m_squelchGate; //!< number of samples computed from given gate - int m_squelchRelease; - - MagAGC m_agc; - Bandpass m_bandpass; + int m_channelSampleRate; + int m_channelFrequencyOffset; QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; - QMutex m_settingsMutex; - - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = true); void applySettings(const UDPSinkSettings& settings, bool force = false); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); void webapiReverseSendSettings(QList& channelSettingsKeys, const UDPSinkSettings& settings, bool force); - - inline void calculateSquelch(double value) - { - if ((!m_settings.m_squelchEnabled) || (value > m_squelch)) - { - if (m_squelchGate == 0) - { - m_squelchOpen = true; - } - else - { - if (m_squelchOpenCount < m_squelchGate) - { - m_squelchOpenCount++; - } - else - { - m_squelchCloseCount = m_squelchRelease; - m_squelchOpen = true; - } - } - } - else - { - if (m_squelchGate == 0) - { - m_squelchOpen = false; - } - else - { - if (m_squelchCloseCount > 0) - { - m_squelchCloseCount--; - } - else - { - m_squelchOpenCount = 0; - m_squelchOpen = false; - } - } - } - } - - inline void initSquelch(bool open) - { - if (open) - { - m_squelchOpen = true; - m_squelchOpenCount = m_squelchGate; - m_squelchCloseCount = m_squelchRelease; - } - else - { - m_squelchOpen = false; - m_squelchOpenCount = 0; - m_squelchCloseCount = 0; - } - } - - void udpWrite(FixReal real, FixReal imag) - { - if (SDR_RX_SAMP_SZ == 16) - { - if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ16) { - m_udpBuffer16->write(Sample16(real, imag)); - } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ24) { - m_udpBuffer24->write(Sample24(real<<8, imag<<8)); - } else { - m_udpBuffer16->write(Sample16(real, imag)); - } - } - else if (SDR_RX_SAMP_SZ == 24) - { - if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ16) { - m_udpBuffer16->write(Sample16(real>>8, imag>>8)); - } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ24) { - m_udpBuffer24->write(Sample24(real, imag)); - } else { - m_udpBuffer16->write(Sample16(real>>8, imag>>8)); - } - } - } - - void udpWriteMono(FixReal sample) - { - if (SDR_RX_SAMP_SZ == 16) - { - m_udpBufferMono16->write(sample); - } - else if (SDR_RX_SAMP_SZ == 24) - { - m_udpBufferMono16->write(sample>>8); - } - } - - void udpWriteNorm(Real real, Real imag) - { - m_udpBuffer16->write(Sample16(real*32768.0, imag*32768.0)); - } - - void udpWriteNormMono(Real sample) - { - m_udpBufferMono16->write(sample*32768.0); - } - }; -#endif // INCLUDE_UDPSRC_H +#endif // INCLUDE_UDPSINK_H diff --git a/plugins/channelrx/udpsink/udpsinkbaseband.cpp b/plugins/channelrx/udpsink/udpsinkbaseband.cpp new file mode 100644 index 000000000..17f052a5a --- /dev/null +++ b/plugins/channelrx/udpsink/udpsinkbaseband.cpp @@ -0,0 +1,174 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downsamplechannelizer.h" + +#include "udpsinkbaseband.h" + +MESSAGE_CLASS_DEFINITION(UDPSinkBaseband::MsgConfigureUDPSinkBaseband, Message) +MESSAGE_CLASS_DEFINITION(UDPSinkBaseband::MsgEnableSpectrum, Message) + +UDPSinkBaseband::UDPSinkBaseband() : + m_mutex(QMutex::Recursive) +{ + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownSampleChannelizer(&m_sink); + + qDebug("WFMDemodBaseband::WFMDemodBaseband"); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &UDPSinkBaseband::handleData, + Qt::QueuedConnection + ); + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue()); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +UDPSinkBaseband::~UDPSinkBaseband() +{ + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo()); + delete m_channelizer; +} + +void UDPSinkBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.reset(); +} + +void UDPSinkBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void UDPSinkBaseband::handleData() +{ + QMutexLocker mutexLocker(&m_mutex); + + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void UDPSinkBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool UDPSinkBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureUDPSinkBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureUDPSinkBaseband& cfg = (MsgConfigureUDPSinkBaseband&) cmd; + qDebug() << "UDPSinkBaseband::handleMessage: MsgConfigureUDPSinkBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "UDPSinkBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + m_channelizer->setBasebandSampleRate(notif.getSampleRate()); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + + return true; + } + else if (MsgEnableSpectrum::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgEnableSpectrum& notif = (MsgEnableSpectrum&) cmd; + qDebug() << "UDPSinkBaseband::MsgEnableSpectrum: enable:" << notif.getEnabled(); + m_sink.enableSpectrum(notif.getEnabled()); + + return true; + } + else + { + return false; + } +} + +void UDPSinkBaseband::applySettings(const UDPSinkSettings& settings, bool force) +{ + if ((settings.m_audioActive != m_settings.m_audioActive) || force) + { + if (settings.m_audioActive) { + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue()); + } else { + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo()); + } + } + + if ((settings.m_outputSampleRate != m_settings.m_outputSampleRate) + || (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + { + m_channelizer->setChannelization(settings.m_outputSampleRate, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + m_sink.applySettings(settings, force); + m_settings = settings; +} + +int UDPSinkBaseband::getChannelSampleRate() const +{ + return m_channelizer->getChannelSampleRate(); +} + + +void UDPSinkBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); +} diff --git a/plugins/channelrx/udpsink/udpsinkbaseband.h b/plugins/channelrx/udpsink/udpsinkbaseband.h new file mode 100644 index 000000000..4f65b9236 --- /dev/null +++ b/plugins/channelrx/udpsink/udpsinkbaseband.h @@ -0,0 +1,108 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_UDPSINKBASEBAND_H +#define INCLUDE_UDPSINKBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "udpsinksink.h" + +class DownSampleChannelizer; + +class UDPSinkBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureUDPSinkBaseband : public Message { + MESSAGE_CLASS_DECLARATION + public: + const UDPSinkSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureUDPSinkBaseband* create(const UDPSinkSettings& settings, bool force) + { + return new MsgConfigureUDPSinkBaseband(settings, force); + } + + private: + UDPSinkSettings m_settings; + bool m_force; + + MsgConfigureUDPSinkBaseband(const UDPSinkSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { + } + }; + + class MsgEnableSpectrum : public Message { + MESSAGE_CLASS_DECLARATION + public: + bool getEnabled() { return m_enable; } + + static MsgEnableSpectrum* create(bool enable) { + return new MsgEnableSpectrum(enable); + } + + private: + bool m_enable; + + MsgEnableSpectrum(bool enable) : + Message(), + m_enable(enable) + {} + }; + + UDPSinkBaseband(); + ~UDPSinkBaseband(); + void reset(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + int getChannelSampleRate() const; + void setBasebandSampleRate(int sampleRate); + + void setSpectrum(BasebandSampleSink* spectrum) { m_sink.setSpectrum(spectrum); } + void enableSpectrum(bool enable) { m_sink.enableSpectrum(enable); } + void setSpectrumPositiveOnly(bool positiveOnly) { m_sink.setSpectrumPositiveOnly(positiveOnly); } + double getMagSq() const { return m_sink.getMagSq(); } + double getInMagSq() const { return m_sink.getInMagSq(); } + bool getSquelchOpen() const { return m_sink.getSquelchOpen(); } + +private: + SampleSinkFifo m_sampleFifo; + DownSampleChannelizer *m_channelizer; + UDPSinkSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + UDPSinkSettings m_settings; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void applySettings(const UDPSinkSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_UDPSINKBASEBAND_H diff --git a/plugins/channelrx/udpsink/udpsinkgui.cpp b/plugins/channelrx/udpsink/udpsinkgui.cpp index 442439871..4985d17e1 100644 --- a/plugins/channelrx/udpsink/udpsinkgui.cpp +++ b/plugins/channelrx/udpsink/udpsinkgui.cpp @@ -91,9 +91,9 @@ bool UDPSinkGUI::deserialize(const QByteArray& data) bool UDPSinkGUI::handleMessage(const Message& message ) { - if (UDPSink::MsgConfigureUDPSource::match(message)) + if (UDPSink::MsgConfigureUDPSink::match(message)) { - const UDPSink::MsgConfigureUDPSource& cfg = (UDPSink::MsgConfigureUDPSource&) message; + const UDPSink::MsgConfigureUDPSink& cfg = (UDPSink::MsgConfigureUDPSink&) message; m_settings = cfg.getSettings(); blockApplySettings(true); displaySettings(); @@ -403,7 +403,7 @@ void UDPSinkGUI::applySettingsImmediate(bool force) { if (m_doApplySettings) { - UDPSink::MsgConfigureUDPSource* message = UDPSink::MsgConfigureUDPSource::create( m_settings, force); + UDPSink::MsgConfigureUDPSink* message = UDPSink::MsgConfigureUDPSink::create( m_settings, force); m_udpSink->getInputMessageQueue()->push(message); } } @@ -412,11 +412,7 @@ void UDPSinkGUI::applySettings(bool force) { if (m_doApplySettings) { - UDPSink::MsgConfigureChannelizer* channelConfigMsg = UDPSink::MsgConfigureChannelizer::create( - m_settings.m_outputSampleRate, m_channelMarker.getCenterFrequency()); - m_udpSink->getInputMessageQueue()->push(channelConfigMsg); - - UDPSink::MsgConfigureUDPSource* message = UDPSink::MsgConfigureUDPSource::create( m_settings, force); + UDPSink::MsgConfigureUDPSink* message = UDPSink::MsgConfigureUDPSink::create( m_settings, force); m_udpSink->getInputMessageQueue()->push(message); ui->applyBtn->setEnabled(false); @@ -618,9 +614,8 @@ void UDPSinkGUI::on_squelchGate_valueChanged(int value) void UDPSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown) { - if ((widget == ui->spectrumBox) && (m_udpSink != 0)) - { - m_udpSink->setSpectrum(m_udpSink->getInputMessageQueue(), rollDown); + if ((widget == ui->spectrumBox) && (m_udpSink)) { + m_udpSink->enableSpectrum(rollDown); } } diff --git a/plugins/channelrx/udpsink/udpsinkplugin.cpp b/plugins/channelrx/udpsink/udpsinkplugin.cpp index 07df3ea6b..108e1344e 100644 --- a/plugins/channelrx/udpsink/udpsinkplugin.cpp +++ b/plugins/channelrx/udpsink/udpsinkplugin.cpp @@ -28,7 +28,7 @@ const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = { QString("UDP Channel Sink"), - QString("4.11.6"), + QString("4.12.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/udpsink/udpsinksink.cpp b/plugins/channelrx/udpsink/udpsinksink.cpp new file mode 100644 index 000000000..fa0dede04 --- /dev/null +++ b/plugins/channelrx/udpsink/udpsinksink.cpp @@ -0,0 +1,510 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "SWGChannelSettings.h" +#include "SWGUDPSinkSettings.h" +#include "SWGChannelReport.h" +#include "SWGUDPSinkReport.h" + +#include "dsp/basebandsamplesink.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/devicesamplemimo.h" +#include "device/deviceapi.h" +#include "util/db.h" + +#include "udpsinksink.h" + +const Real UDPSinkSink::m_agcTarget = 16384.0f; + +UDPSinkSink::UDPSinkSink() : + m_channelSampleRate(48000), + m_channelFrequencyOffset(0), + m_outMovingAverage(480, 1e-10), + m_inMovingAverage(480, 1e-10), + m_amMovingAverage(1200, 1e-10), + m_audioFifo(24000), + m_spectrum(nullptr), + m_spectrumEnabled(false), + m_spectrumPositiveOnly(false), + m_squelch(1e-6), + m_squelchOpen(false), + m_squelchOpenCount(0), + m_squelchCloseCount(0), + m_squelchGate(4800), + m_squelchRelease(4800), + m_agc(9600, m_agcTarget, 1e-6) +{ + m_udpBuffer16 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); + m_udpBufferMono16 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); + m_udpBuffer24 = new UDPSinkUtil(this, udpBlockSize, m_settings.m_udpPort); + m_audioSocket = new QUdpSocket(this); + m_udpAudioBuf = new char[m_udpAudioPayloadSize]; + + m_audioBuffer.resize(1<<9); + m_audioBufferFill = 0; + + m_nco.setFreq(0, m_channelSampleRate); + m_interpolator.create(16, m_channelSampleRate, m_settings.m_rfBandwidth / 2.0); + m_sampleDistanceRemain = m_channelSampleRate / m_settings.m_outputSampleRate; + m_spectrumEnabled = false; + m_nextSSBId = 0; + m_nextS16leId = 0; + + m_last = 0; + m_this = 0; + m_scale = 0; + m_magsq = 0; + m_inMagsq = 0; + + UDPFilter = new fftfilt(0.0, (m_settings.m_rfBandwidth / 2.0) / m_settings.m_outputSampleRate, udpBlockSize); + + m_phaseDiscri.setFMScaling((float) m_settings. m_outputSampleRate / (2.0f * m_settings.m_fmDeviation)); + + if (m_audioSocket->bind(QHostAddress::LocalHost, m_settings.m_audioPort)) + { + qDebug("UDPSinkSink::UDPSinkSink: bind audio socket to port %d", m_settings.m_audioPort); + connect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead()), Qt::QueuedConnection); + } + else + { + qWarning("UDPSinkSink::UDPSinkSink: cannot bind audio port"); + } + + m_agc.setClampMax(SDR_RX_SCALED*SDR_RX_SCALED); + m_agc.setClamping(true); + + //DSPEngine::instance()->addAudioSink(&m_audioFifo); + + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); + applySettings(m_settings, true); +} + +UDPSinkSink::~UDPSinkSink() +{ + delete m_audioSocket; + delete m_udpBuffer24; + delete m_udpBuffer16; + delete m_udpBufferMono16; + delete[] m_udpAudioBuf; + delete UDPFilter; +} + +void UDPSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + Complex ci; + fftfilt::cmplx* sideband; + double l, r; + + m_sampleBuffer.clear(); + + for(SampleVector::const_iterator it = begin; it < end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if(m_interpolator.decimate(&m_sampleDistanceRemain, c, &ci)) + { + double inMagSq; + double agcFactor = 1.0; + + if ((m_settings.m_agc) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatNFM) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatNFMMono) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatIQ16) && + (m_settings.m_sampleFormat != UDPSinkSettings::FormatIQ24)) + { + agcFactor = m_agc.feedAndGetValue(ci); + inMagSq = m_agc.getMagSq(); + } + else + { + inMagSq = ci.real()*ci.real() + ci.imag()*ci.imag(); + } + + m_inMovingAverage.feed(inMagSq / (SDR_RX_SCALED*SDR_RX_SCALED)); + m_inMagsq = m_inMovingAverage.average(); + + Sample ss(ci.real(), ci.imag()); + m_sampleBuffer.push_back(ss); + + m_sampleDistanceRemain += m_channelSampleRate / m_settings.m_outputSampleRate; + + calculateSquelch(m_inMagsq); + + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatLSB) // binaural LSB + { + ci *= agcFactor; + int n_out = UDPFilter->runSSB(ci, &sideband, false); + + if (n_out) + { + for (int i = 0; i < n_out; i++) + { + l = m_squelchOpen ? sideband[i].real() * m_settings.m_gain : 0; + r = m_squelchOpen ? sideband[i].imag() * m_settings.m_gain : 0; + udpWrite(l, r); + m_outMovingAverage.feed((l*l + r*r) / (SDR_RX_SCALED*SDR_RX_SCALED)); + } + } + } + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSB) // binaural USB + { + ci *= agcFactor; + int n_out = UDPFilter->runSSB(ci, &sideband, true); + + if (n_out) + { + for (int i = 0; i < n_out; i++) + { + l = m_squelchOpen ? sideband[i].real() * m_settings.m_gain : 0; + r = m_squelchOpen ? sideband[i].imag() * m_settings.m_gain : 0; + udpWrite(l, r); + m_outMovingAverage.feed((l*l + r*r) / (SDR_RX_SCALED*SDR_RX_SCALED)); + } + } + } + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFM) + { + Real discri = m_squelchOpen ? m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_gain : 0; + udpWriteNorm(discri, discri); + m_outMovingAverage.feed(discri*discri); + } + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatNFMMono) + { + Real discri = m_squelchOpen ? m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_gain : 0; + udpWriteNormMono(discri); + m_outMovingAverage.feed(discri*discri); + } + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) // Monaural LSB + { + ci *= agcFactor; + int n_out = UDPFilter->runSSB(ci, &sideband, false); + + if (n_out) + { + for (int i = 0; i < n_out; i++) + { + l = m_squelchOpen ? (sideband[i].real() + sideband[i].imag()) * 0.7 * m_settings.m_gain : 0; + udpWriteMono(l); + m_outMovingAverage.feed((l * l) / (SDR_RX_SCALED*SDR_RX_SCALED)); + } + } + } + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono) // Monaural USB + { + ci *= agcFactor; + int n_out = UDPFilter->runSSB(ci, &sideband, true); + + if (n_out) + { + for (int i = 0; i < n_out; i++) + { + l = m_squelchOpen ? (sideband[i].real() + sideband[i].imag()) * 0.7 * m_settings.m_gain : 0; + udpWriteMono(l); + m_outMovingAverage.feed((l * l) / (SDR_RX_SCALED*SDR_RX_SCALED)); + } + } + } + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMMono) + { + Real amplitude = m_squelchOpen ? sqrt(inMagSq) * agcFactor * m_settings.m_gain : 0; + FixReal demod = (FixReal) amplitude; + udpWriteMono(demod); + m_outMovingAverage.feed((amplitude/SDR_RX_SCALEF)*(amplitude/SDR_RX_SCALEF)); + } + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMNoDCMono) + { + if (m_squelchOpen) + { + double demodf = sqrt(inMagSq); + m_amMovingAverage.feed(demodf); + Real amplitude = (demodf - m_amMovingAverage.average()) * agcFactor * m_settings.m_gain; + FixReal demod = (FixReal) amplitude; + udpWriteMono(demod); + m_outMovingAverage.feed((amplitude/SDR_RX_SCALEF)*(amplitude/SDR_RX_SCALEF)); + } + else + { + udpWriteMono(0); + m_outMovingAverage.feed(0); + } + } + else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatAMBPFMono) + { + if (m_squelchOpen) + { + double demodf = sqrt(inMagSq); + demodf = m_bandpass.filter(demodf); + demodf /= 301.0; + Real amplitude = demodf * agcFactor * m_settings.m_gain; + FixReal demod = (FixReal) amplitude; + udpWriteMono(demod); + m_outMovingAverage.feed((amplitude/SDR_RX_SCALEF)*(amplitude/SDR_RX_SCALEF)); + } + else + { + udpWriteMono(0); + m_outMovingAverage.feed(0); + } + } + else // Raw I/Q samples + { + if (m_squelchOpen) + { + udpWrite(ci.real() * m_settings.m_gain, ci.imag() * m_settings.m_gain); + m_outMovingAverage.feed((inMagSq*m_settings.m_gain*m_settings.m_gain) / (SDR_RX_SCALED*SDR_RX_SCALED)); + } + else + { + udpWrite(0, 0); + m_outMovingAverage.feed(0); + } + } + + m_magsq = m_outMovingAverage.average(); + } + } + + //qDebug() << "UDPSink::feed: " << m_sampleBuffer.size() * 4; + + if ((m_spectrum != 0) && (m_spectrumEnabled)) { + m_spectrum->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), m_spectrumPositiveOnly); + } +} + +void UDPSinkSink::audioReadyRead() +{ + while (m_audioSocket->hasPendingDatagrams()) + { + qint64 pendingDataSize = m_audioSocket->pendingDatagramSize(); + qint64 udpReadBytes = m_audioSocket->readDatagram(m_udpAudioBuf, pendingDataSize, 0, 0); + //qDebug("UDPSink::audioReadyRead: %lld", udpReadBytes); + + if (m_settings.m_audioActive) + { + if (m_settings.m_audioStereo) + { + for (int i = 0; i < udpReadBytes - 3; i += 4) + { + qint16 l_sample = (qint16) *(&m_udpAudioBuf[i]); + qint16 r_sample = (qint16) *(&m_udpAudioBuf[i+2]); + m_audioBuffer[m_audioBufferFill].l = l_sample * m_settings.m_volume; + m_audioBuffer[m_audioBufferFill].r = r_sample * m_settings.m_volume; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) { + qDebug("UDPSinkSink::audioReadyRead: (stereo) lost %u samples", m_audioBufferFill - res); + } + + m_audioBufferFill = 0; + } + } + } + else + { + for (int i = 0; i < udpReadBytes - 1; i += 2) + { + qint16 sample = (qint16) *(&m_udpAudioBuf[i]); + m_audioBuffer[m_audioBufferFill].l = sample * m_settings.m_volume; + m_audioBuffer[m_audioBufferFill].r = sample * m_settings.m_volume; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) { + qDebug("UDPSinkSink::audioReadyRead: (mono) lost %u samples", m_audioBufferFill - res); + } + + m_audioBufferFill = 0; + } + } + } + + if (m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill) != m_audioBufferFill) { + qDebug("UDPSinkSink::audioReadyRead: lost samples"); + } + + m_audioBufferFill = 0; + } + } +} + +void UDPSinkSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "UDPSinkSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " channelFrequencyOffset: " << channelFrequencyOffset; + + if((channelFrequencyOffset != m_channelFrequencyOffset) || + (channelSampleRate != m_channelSampleRate) || force) + { + m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); + } + + if ((channelSampleRate != m_channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.0); + m_sampleDistanceRemain = channelSampleRate / m_settings.m_outputSampleRate; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + +void UDPSinkSink::applySettings(const UDPSinkSettings& settings, bool force) +{ + qDebug() << "UDPSinkSink::applySettings:" + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_audioActive: " << settings.m_audioActive + << " m_audioStereo: " << settings.m_audioStereo + << " m_gain: " << settings.m_gain + << " m_volume: " << settings.m_volume + << " m_squelchEnabled: " << settings.m_squelchEnabled + << " m_squelchdB: " << settings.m_squelchdB + << " m_squelchGate" << settings.m_squelchGate + << " m_agc" << settings.m_agc + << " m_sampleFormat: " << settings.m_sampleFormat + << " m_outputSampleRate: " << settings.m_outputSampleRate + << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_fmDeviation: " << settings.m_fmDeviation + << " m_udpAddressStr: " << settings.m_udpAddress + << " m_udpPort: " << settings.m_udpPort + << " m_audioPort: " << settings.m_audioPort + << " m_streamIndex: " << settings.m_streamIndex + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex + << " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex + << " force: " << force; + + if ((settings.m_audioActive != m_settings.m_audioActive) || force) + { + if (settings.m_audioActive) { + m_audioBufferFill = 0; + } + } + + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || + (settings.m_rfBandwidth != m_settings.m_rfBandwidth) || + (settings.m_outputSampleRate != m_settings.m_outputSampleRate) || force) + { + m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.0); + m_sampleDistanceRemain = m_channelSampleRate / settings.m_outputSampleRate; + + if ((settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono)) + { + m_squelchGate = settings.m_outputSampleRate * 0.05; + } + else + { + m_squelchGate = (settings.m_outputSampleRate * settings.m_squelchGate) / 100; + } + + m_squelchRelease = (settings.m_outputSampleRate * settings.m_squelchGate) / 100; + initSquelch(m_squelchOpen); + m_agc.resize(settings.m_outputSampleRate/5, settings.m_outputSampleRate/20, m_agcTarget); // Fixed 200 ms + int stepDownDelay = (settings.m_outputSampleRate * (settings.m_squelchGate == 0 ? 1 : settings.m_squelchGate))/100; + m_agc.setStepDownDelay(stepDownDelay); + m_agc.setGate(settings.m_outputSampleRate * 0.05); + + m_bandpass.create(301, settings.m_outputSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); + + m_inMovingAverage.resize(settings.m_outputSampleRate * 0.01, 1e-10); // 10 ms + m_amMovingAverage.resize(settings.m_outputSampleRate * 0.005, 1e-10); // 5 ms + m_outMovingAverage.resize(settings.m_outputSampleRate * 0.01, 1e-10); // 10 ms + } + + if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) + { + if ((settings.m_sampleFormat == UDPSinkSettings::FormatLSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatLSBMono) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSB) || + (settings.m_sampleFormat == UDPSinkSettings::FormatUSBMono)) + { + m_squelchGate = settings.m_outputSampleRate * 0.05; + } + else + { + m_squelchGate = (settings.m_outputSampleRate * settings.m_squelchGate)/100; + } + + m_squelchRelease = (settings.m_outputSampleRate * settings.m_squelchGate)/100; + initSquelch(m_squelchOpen); + int stepDownDelay = (settings.m_outputSampleRate * (settings.m_squelchGate == 0 ? 1 : settings.m_squelchGate))/100; + m_agc.setStepDownDelay(stepDownDelay); // same delay for up and down + } + + if ((settings.m_squelchdB != m_settings.m_squelchdB) || force) + { + m_squelch = CalcDb::powerFromdB(settings.m_squelchdB); + m_agc.setThreshold(m_squelch*(1<<23)); + } + + if ((settings.m_udpAddress != m_settings.m_udpAddress) || force) + { + m_udpBuffer16->setAddress(const_cast(settings.m_udpAddress)); + m_udpBufferMono16->setAddress(const_cast(settings.m_udpAddress)); + m_udpBuffer24->setAddress(const_cast(settings.m_udpAddress)); + } + + if ((settings.m_udpPort != m_settings.m_udpPort) || force) + { + m_udpBuffer16->setPort(settings.m_udpPort); + m_udpBufferMono16->setPort(settings.m_udpPort); + m_udpBuffer24->setPort(settings.m_udpPort); + } + + if ((settings.m_audioPort != m_settings.m_audioPort) || force) + { + disconnect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead())); + delete m_audioSocket; + m_audioSocket = new QUdpSocket(this); + + if (m_audioSocket->bind(QHostAddress::LocalHost, settings.m_audioPort)) + { + connect(m_audioSocket, SIGNAL(readyRead()), this, SLOT(audioReadyRead()), Qt::QueuedConnection); + qDebug("UDPSinkSink::handleMessage: audio socket bound to port %d", settings.m_audioPort); + } + else + { + qWarning("UDPSinkSink::handleMessage: cannot bind audio socket"); + } + } + + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { + m_phaseDiscri.setFMScaling((float) settings.m_outputSampleRate / (2.0f * settings.m_fmDeviation)); + } + + m_settings = settings; +} diff --git a/plugins/channelrx/udpsink/udpsinksink.h b/plugins/channelrx/udpsink/udpsinksink.h new file mode 100644 index 000000000..deab6855b --- /dev/null +++ b/plugins/channelrx/udpsink/udpsinksink.h @@ -0,0 +1,232 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_UDPSINKSINK_H +#define INCLUDE_UDPSINKSINK_H + +#include + +#include "dsp/channelsamplesink.h" +#include "dsp/nco.h" +#include "dsp/fftfilt.h" +#include "dsp/interpolator.h" +#include "dsp/phasediscri.h" +#include "dsp/movingaverage.h" +#include "dsp/agc.h" +#include "dsp/bandpass.h" +#include "util/udpsinkutil.h" +#include "audio/audiofifo.h" + +#include "udpsinksettings.h" + +class QUdpSocket; +class BasebandSampleSink; + +class UDPSinkSink : public QObject, public ChannelSampleSink { + Q_OBJECT +public: + UDPSinkSink(); + ~UDPSinkSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = true); + void applySettings(const UDPSinkSettings& settings, bool force = false); + + AudioFifo *getAudioFifo() { return &m_audioFifo; } + void setSpectrum(BasebandSampleSink* spectrum) { m_spectrum = spectrum; } + void enableSpectrum(bool enable) { m_spectrumEnabled = enable; } + void setSpectrumPositiveOnly(bool positiveOnly) { m_spectrumPositiveOnly = positiveOnly; } + double getMagSq() const { return m_magsq; } + double getInMagSq() const { return m_inMagsq; } + bool getSquelchOpen() const { return m_squelchOpen; } + + static const int udpBlockSize = 512; // UDP block size in number of bytes + +private slots: + void audioReadyRead(); + +private: + struct Sample16 + { + Sample16() : m_r(0), m_i(0) {} + Sample16(int16_t r, int16_t i) : m_r(r), m_i(i) {} + int16_t m_r; + int16_t m_i; + }; + + struct Sample24 + { + Sample24() : m_r(0), m_i(0) {} + Sample24(int32_t r, int32_t i) : m_r(r), m_i(i) {} + int32_t m_r; + int32_t m_i; + }; + + int m_channelSampleRate; + int m_channelFrequencyOffset; + UDPSinkSettings m_settings; + + QUdpSocket *m_audioSocket; + + double m_magsq; + double m_inMagsq; + MovingAverage m_outMovingAverage; + MovingAverage m_inMovingAverage; + MovingAverage m_amMovingAverage; + + Real m_scale; + Complex m_last, m_this; + + NCO m_nco; + Interpolator m_interpolator; + Real m_sampleDistanceRemain; + fftfilt* UDPFilter; + + SampleVector m_sampleBuffer; + UDPSinkUtil *m_udpBuffer16; + UDPSinkUtil *m_udpBufferMono16; + UDPSinkUtil *m_udpBuffer24; + + AudioVector m_audioBuffer; + uint m_audioBufferFill; + AudioFifo m_audioFifo; + + BasebandSampleSink* m_spectrum; + bool m_spectrumEnabled; + bool m_spectrumPositiveOnly; + + quint32 m_nextSSBId; + quint32 m_nextS16leId; + + char *m_udpAudioBuf; + static const int m_udpAudioPayloadSize = 8192; //!< UDP audio samples buffer. No UDP block on Earth is larger than this + static const Real m_agcTarget; + + PhaseDiscriminators m_phaseDiscri; + + double m_squelch; + bool m_squelchOpen; + int m_squelchOpenCount; + int m_squelchCloseCount; + int m_squelchGate; //!< number of samples computed from given gate + int m_squelchRelease; + + MagAGC m_agc; + Bandpass m_bandpass; + + inline void calculateSquelch(double value) + { + if ((!m_settings.m_squelchEnabled) || (value > m_squelch)) + { + if (m_squelchGate == 0) + { + m_squelchOpen = true; + } + else + { + if (m_squelchOpenCount < m_squelchGate) + { + m_squelchOpenCount++; + } + else + { + m_squelchCloseCount = m_squelchRelease; + m_squelchOpen = true; + } + } + } + else + { + if (m_squelchGate == 0) + { + m_squelchOpen = false; + } + else + { + if (m_squelchCloseCount > 0) + { + m_squelchCloseCount--; + } + else + { + m_squelchOpenCount = 0; + m_squelchOpen = false; + } + } + } + } + + inline void initSquelch(bool open) + { + if (open) + { + m_squelchOpen = true; + m_squelchOpenCount = m_squelchGate; + m_squelchCloseCount = m_squelchRelease; + } + else + { + m_squelchOpen = false; + m_squelchOpenCount = 0; + m_squelchCloseCount = 0; + } + } + + void udpWrite(FixReal real, FixReal imag) + { + if (SDR_RX_SAMP_SZ == 16) + { + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ16) { + m_udpBuffer16->write(Sample16(real, imag)); + } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ24) { + m_udpBuffer24->write(Sample24(real<<8, imag<<8)); + } else { + m_udpBuffer16->write(Sample16(real, imag)); + } + } + else if (SDR_RX_SAMP_SZ == 24) + { + if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ16) { + m_udpBuffer16->write(Sample16(real>>8, imag>>8)); + } else if (m_settings.m_sampleFormat == UDPSinkSettings::FormatIQ24) { + m_udpBuffer24->write(Sample24(real, imag)); + } else { + m_udpBuffer16->write(Sample16(real>>8, imag>>8)); + } + } + } + + void udpWriteMono(FixReal sample) + { + if (SDR_RX_SAMP_SZ == 16) { + m_udpBufferMono16->write(sample); + } else if (SDR_RX_SAMP_SZ == 24) { + m_udpBufferMono16->write(sample>>8); + } + } + + void udpWriteNorm(Real real, Real imag) { + m_udpBuffer16->write(Sample16(real*32768.0, imag*32768.0)); + } + + void udpWriteNormMono(Real sample) { + m_udpBufferMono16->write(sample*32768.0); + } +}; + +#endif // INCLUDE_UDPSINKSINK_H