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 @@
1
+
+ ButtonSwitch
+ QToolButton
+
+
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