mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-09-27 15:26:33 -04:00
Rx plgins: refactoring of classes (3)
This commit is contained in:
parent
f1112f64d0
commit
91b24a7c90
@ -21,8 +21,6 @@
|
|||||||
#define INCLUDE_DATVDEMOD_H
|
#define INCLUDE_DATVDEMOD_H
|
||||||
|
|
||||||
class DeviceAPI;
|
class DeviceAPI;
|
||||||
class ThreadedBasebandSampleSink;
|
|
||||||
class DownChannelizer;
|
|
||||||
|
|
||||||
#include "channel/channelapi.h"
|
#include "channel/channelapi.h"
|
||||||
#include "dsp/basebandsamplesink.h"
|
#include "dsp/basebandsamplesink.h"
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
class PluginAPI;
|
class PluginAPI;
|
||||||
class DeviceUISet;
|
class DeviceUISet;
|
||||||
class BasebandSampleSink;
|
class BasebandSampleSink;
|
||||||
class DownChannelizer;
|
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
@ -104,8 +103,6 @@ private:
|
|||||||
DeviceUISet* m_deviceUISet;
|
DeviceUISet* m_deviceUISet;
|
||||||
|
|
||||||
ChannelMarker m_objChannelMarker;
|
ChannelMarker m_objChannelMarker;
|
||||||
ThreadedBasebandSampleSink* m_objThreadedChannelizer;
|
|
||||||
DownChannelizer* m_objChannelizer;
|
|
||||||
DATVDemod* m_objDATVDemod;
|
DATVDemod* m_objDATVDemod;
|
||||||
MessageQueue m_inputMessageQueue;
|
MessageQueue m_inputMessageQueue;
|
||||||
int m_intCenterFrequency;
|
int m_intCenterFrequency;
|
||||||
|
@ -2,6 +2,8 @@ project(demodfreedv)
|
|||||||
|
|
||||||
set(freedv_SOURCES
|
set(freedv_SOURCES
|
||||||
freedvdemod.cpp
|
freedvdemod.cpp
|
||||||
|
freedvdemodbaseband.cpp
|
||||||
|
freedvdemodsink.cpp
|
||||||
freedvdemodsettings.cpp
|
freedvdemodsettings.cpp
|
||||||
freedvdemodwebapiadapter.cpp
|
freedvdemodwebapiadapter.cpp
|
||||||
freedvplugin.cpp
|
freedvplugin.cpp
|
||||||
@ -9,6 +11,8 @@ set(freedv_SOURCES
|
|||||||
|
|
||||||
set(freedv_HEADERS
|
set(freedv_HEADERS
|
||||||
freedvdemod.h
|
freedvdemod.h
|
||||||
|
freedvdemodbaseband.h
|
||||||
|
freedvdemodsink.h
|
||||||
freedvdemodsettings.h
|
freedvdemodsettings.h
|
||||||
freedvdemodwebapiadapter.h
|
freedvdemodwebapiadapter.h
|
||||||
freedvplugin.h
|
freedvplugin.h
|
||||||
|
@ -15,26 +15,18 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include <QTime>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
|
#include <QThread>
|
||||||
#include "codec2/freedv_api.h"
|
|
||||||
#include "codec2/modem_stats.h"
|
|
||||||
|
|
||||||
#include "SWGChannelSettings.h"
|
#include "SWGChannelSettings.h"
|
||||||
#include "SWGFreeDVDemodSettings.h"
|
#include "SWGFreeDVDemodSettings.h"
|
||||||
#include "SWGChannelReport.h"
|
#include "SWGChannelReport.h"
|
||||||
#include "SWGFreeDVDemodReport.h"
|
#include "SWGFreeDVDemodReport.h"
|
||||||
|
|
||||||
#include "audio/audiooutput.h"
|
|
||||||
#include "dsp/dspengine.h"
|
#include "dsp/dspengine.h"
|
||||||
#include "dsp/downchannelizer.h"
|
|
||||||
#include "dsp/threadedbasebandsamplesink.h"
|
|
||||||
#include "dsp/dspcommands.h"
|
#include "dsp/dspcommands.h"
|
||||||
#include "dsp/devicesamplemimo.h"
|
#include "dsp/devicesamplemimo.h"
|
||||||
#include "device/deviceapi.h"
|
#include "device/deviceapi.h"
|
||||||
@ -44,213 +36,37 @@
|
|||||||
|
|
||||||
MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureFreeDVDemod, Message)
|
MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureFreeDVDemod, Message)
|
||||||
MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgResyncFreeDVDemod, 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_channelIdURI = "sdrangel.channel.freedvdemod";
|
||||||
const QString FreeDVDemod::m_channelId = "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) :
|
FreeDVDemod::FreeDVDemod(DeviceAPI *deviceAPI) :
|
||||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||||
m_deviceAPI(deviceAPI),
|
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)
|
|
||||||
{
|
{
|
||||||
setObjectName(m_channelId);
|
setObjectName(m_channelId);
|
||||||
|
|
||||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
|
m_thread = new QThread(this);
|
||||||
uint32_t audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
|
m_basebandSink = new FreeDVDemodBaseband();
|
||||||
applyAudioSampleRate(audioSampleRate);
|
m_basebandSink->moveToThread(m_thread);
|
||||||
|
|
||||||
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, ssbFftLen);
|
|
||||||
|
|
||||||
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
|
|
||||||
applySettings(m_settings, true);
|
applySettings(m_settings, true);
|
||||||
|
|
||||||
m_channelizer = new DownChannelizer(this);
|
m_deviceAPI->addChannelSink(this);
|
||||||
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
|
|
||||||
m_deviceAPI->addChannelSink(m_threadedChannelizer);
|
|
||||||
m_deviceAPI->addChannelSinkAPI(this);
|
m_deviceAPI->addChannelSinkAPI(this);
|
||||||
|
|
||||||
m_networkManager = new QNetworkAccessManager();
|
m_networkManager = new QNetworkAccessManager();
|
||||||
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||||
connect(&m_timer, SIGNAL(timeout()), this, SLOT(timerHandlerFunction()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FreeDVDemod::~FreeDVDemod()
|
FreeDVDemod::~FreeDVDemod()
|
||||||
{
|
{
|
||||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||||
delete m_networkManager;
|
delete m_networkManager;
|
||||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo);
|
|
||||||
|
|
||||||
m_deviceAPI->removeChannelSinkAPI(this);
|
m_deviceAPI->removeChannelSinkAPI(this);
|
||||||
m_deviceAPI->removeChannelSink(m_threadedChannelizer);
|
m_deviceAPI->removeChannelSink(this);
|
||||||
delete m_threadedChannelizer;
|
delete m_basebandSink;
|
||||||
delete m_channelizer;
|
delete m_thread;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t FreeDVDemod::getNumberOfDeviceStreams() const
|
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 FreeDVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
|
||||||
{
|
{
|
||||||
(void) positiveOnly;
|
(void) positiveOnly;
|
||||||
|
m_basebandSink->feed(begin, end);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreeDVDemod::start()
|
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()
|
void FreeDVDemod::stop()
|
||||||
{
|
{
|
||||||
|
qDebug() << "FreeDVDemod::stop";
|
||||||
|
m_thread->exit();
|
||||||
|
m_thread->wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FreeDVDemod::handleMessage(const Message& cmd)
|
bool FreeDVDemod::handleMessage(const Message& cmd)
|
||||||
{
|
{
|
||||||
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
|
if (MsgConfigureFreeDVDemod::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))
|
|
||||||
{
|
{
|
||||||
MsgConfigureFreeDVDemod& cfg = (MsgConfigureFreeDVDemod&) cmd;
|
MsgConfigureFreeDVDemod& cfg = (MsgConfigureFreeDVDemod&) cmd;
|
||||||
qDebug("FreeDVDemod::handleMessage: MsgConfigureFreeDVDemod");
|
qDebug("FreeDVDemod::handleMessage: MsgConfigureFreeDVDemod");
|
||||||
@ -423,292 +113,28 @@ bool FreeDVDemod::handleMessage(const Message& cmd)
|
|||||||
else if (MsgResyncFreeDVDemod::match(cmd))
|
else if (MsgResyncFreeDVDemod::match(cmd))
|
||||||
{
|
{
|
||||||
qDebug("FreeDVDemod::handleMessage: MsgResyncFreeDVDemod");
|
qDebug("FreeDVDemod::handleMessage: MsgResyncFreeDVDemod");
|
||||||
m_settingsMutex.lock();
|
FreeDVDemodBaseband::MsgResyncFreeDVDemod *msg = FreeDVDemodBaseband::MsgResyncFreeDVDemod::create();
|
||||||
freedv_set_sync(m_freeDV, FREEDV_SYNC_UNSYNC);
|
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (DSPSignalNotification::match(cmd))
|
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;
|
return true;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(m_sampleSink != 0)
|
|
||||||
{
|
|
||||||
return m_sampleSink->handleMessage(cmd);
|
|
||||||
}
|
}
|
||||||
else
|
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)
|
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) {
|
if((m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset) || force) {
|
||||||
reverseAPIKeys.append("inputFrequencyOffset");
|
reverseAPIKeys.append("inputFrequencyOffset");
|
||||||
}
|
}
|
||||||
|
if ((m_settings.m_volume != settings.m_volume) || force) {
|
||||||
if ((m_settings.m_volume != settings.m_volume) || force)
|
|
||||||
{
|
|
||||||
reverseAPIKeys.append("volume");
|
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");
|
reverseAPIKeys.append("volumeIn");
|
||||||
}
|
}
|
||||||
|
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
|
||||||
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
|
|
||||||
{
|
|
||||||
reverseAPIKeys.append("audioDeviceName");
|
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) {
|
if ((m_settings.m_spanLog2 != settings.m_spanLog2) || force) {
|
||||||
reverseAPIKeys.append("spanLog2");
|
reverseAPIKeys.append("spanLog2");
|
||||||
}
|
}
|
||||||
@ -769,30 +178,22 @@ void FreeDVDemod::applySettings(const FreeDVDemodSettings& settings, bool force)
|
|||||||
if ((m_settings.m_agc != settings.m_agc) || force) {
|
if ((m_settings.m_agc != settings.m_agc) || force) {
|
||||||
reverseAPIKeys.append("agc");
|
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_settings.m_streamIndex != settings.m_streamIndex)
|
||||||
{
|
{
|
||||||
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
|
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
|
||||||
{
|
{
|
||||||
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
|
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
|
||||||
m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex);
|
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
|
||||||
m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex);
|
m_deviceAPI->addChannelSink(this, 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");
|
reverseAPIKeys.append("streamIndex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FreeDVDemodBaseband::MsgConfigureFreeDVDemodBaseband *msg = FreeDVDemodBaseband::MsgConfigureFreeDVDemodBaseband::create(settings, force);
|
||||||
|
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||||
|
|
||||||
if (settings.m_useReverseAPI)
|
if (settings.m_useReverseAPI)
|
||||||
{
|
{
|
||||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && 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;
|
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
|
QByteArray FreeDVDemod::serialize() const
|
||||||
{
|
{
|
||||||
return m_settings.serialize();
|
return m_settings.serialize();
|
||||||
@ -866,13 +250,6 @@ int FreeDVDemod::webapiSettingsPutPatch(
|
|||||||
FreeDVDemodSettings settings = m_settings;
|
FreeDVDemodSettings settings = m_settings;
|
||||||
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
|
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);
|
MsgConfigureFreeDVDemod *msg = MsgConfigureFreeDVDemod::create(settings, force);
|
||||||
m_inputMessageQueue.push(msg);
|
m_inputMessageQueue.push(msg);
|
||||||
|
|
||||||
@ -999,9 +376,9 @@ void FreeDVDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respo
|
|||||||
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
|
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
|
||||||
|
|
||||||
response.getFreeDvDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
|
response.getFreeDvDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
|
||||||
response.getFreeDvDemodReport()->setSquelch(m_audioActive ? 1 : 0);
|
response.getFreeDvDemodReport()->setSquelch(getAudioActive() ? 1 : 0);
|
||||||
response.getFreeDvDemodReport()->setAudioSampleRate(m_audioSampleRate);
|
response.getFreeDvDemodReport()->setAudioSampleRate(getAudioSampleRate());
|
||||||
response.getFreeDvDemodReport()->setChannelSampleRate(m_inputSampleRate);
|
response.getFreeDvDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreeDVDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force)
|
void FreeDVDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force)
|
||||||
@ -1091,7 +468,7 @@ void FreeDVDemod::networkManagerFinished(QNetworkReply *reply)
|
|||||||
reply->deleteLater();
|
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)));
|
||||||
}
|
}
|
@ -20,33 +20,19 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QMutex>
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "dsp/basebandsamplesink.h"
|
#include "dsp/basebandsamplesink.h"
|
||||||
#include "channel/channelapi.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/message.h"
|
||||||
#include "util/doublebufferfifo.h"
|
#include "util/doublebufferfifo.h"
|
||||||
|
|
||||||
#include "freedvdemodsettings.h"
|
#include "freedvdemodbaseband.h"
|
||||||
|
|
||||||
#define ssbFftLen 1024
|
|
||||||
#define agcTarget 3276.8 // -10 dB amplitude => -20 dB power: center of normal signal
|
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class DeviceAPI;
|
class DeviceAPI;
|
||||||
class ThreadedBasebandSampleSink;
|
class QThread;
|
||||||
class DownChannelizer;
|
|
||||||
|
|
||||||
struct freedv;
|
|
||||||
|
|
||||||
class FreeDVDemod : public BasebandSampleSink, public ChannelAPI {
|
class FreeDVDemod : public BasebandSampleSink, public ChannelAPI {
|
||||||
Q_OBJECT
|
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);
|
FreeDVDemod(DeviceAPI *deviceAPI);
|
||||||
virtual ~FreeDVDemod();
|
virtual ~FreeDVDemod();
|
||||||
virtual void destroy() { delete this; }
|
virtual void destroy() { delete this; }
|
||||||
void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; }
|
void setSampleSink(BasebandSampleSink* spectrumSink) { m_basebandSink->setSpectrumSink(spectrumSink); }
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
|
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
|
||||||
virtual void start();
|
virtual void start();
|
||||||
@ -152,33 +100,16 @@ public:
|
|||||||
return m_settings.m_inputFrequencyOffset;
|
return m_settings.m_inputFrequencyOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
|
uint32_t getAudioSampleRate() const { return m_basebandSink->getAudioSampleRate(); }
|
||||||
uint32_t getModemSampleRate() const { return m_modemSampleRate; }
|
uint32_t getModemSampleRate() const { return m_basebandSink->getModemSampleRate(); }
|
||||||
double getMagSq() const { return m_magsq; }
|
double getMagSq() const { return m_basebandSink->getMagSq(); }
|
||||||
bool getAudioActive() const { return m_audioActive; }
|
bool getAudioActive() const { return m_basebandSink->getAudioActive(); }
|
||||||
|
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); }
|
||||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
|
void getSNRLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getSNRLevels(avg, peak, nbSamples); }
|
||||||
{
|
int getBER() const { return m_basebandSink->getBER(); }
|
||||||
if (m_magsqCount > 0)
|
float getFrequencyOffset() const { return m_basebandSink->getFrequencyOffset(); }
|
||||||
{
|
bool isSync() const { return m_basebandSink->isSync(); }
|
||||||
m_magsq = m_magsqSum / m_magsqCount;
|
void propagateMessageQueueToGUI() { m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI()); }
|
||||||
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; }
|
|
||||||
|
|
||||||
virtual int webapiSettingsGet(
|
virtual int webapiSettingsGet(
|
||||||
SWGSDRangel::SWGChannelSettings& response,
|
SWGSDRangel::SWGChannelSettings& response,
|
||||||
@ -204,238 +135,27 @@ public:
|
|||||||
SWGSDRangel::SWGChannelSettings& response);
|
SWGSDRangel::SWGChannelSettings& response);
|
||||||
|
|
||||||
uint32_t getNumberOfDeviceStreams() const;
|
uint32_t getNumberOfDeviceStreams() const;
|
||||||
|
void setLevelMeter(QObject *levelMeter);
|
||||||
|
|
||||||
static const QString m_channelIdURI;
|
static const QString m_channelIdURI;
|
||||||
static const QString m_channelId;
|
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:
|
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;
|
DeviceAPI *m_deviceAPI;
|
||||||
ThreadedBasebandSampleSink* m_threadedChannelizer;
|
QThread *m_thread;
|
||||||
DownChannelizer* m_channelizer;
|
FreeDVDemodBaseband *m_basebandSink;
|
||||||
FreeDVDemodSettings m_settings;
|
FreeDVDemodSettings m_settings;
|
||||||
|
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
|
||||||
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<fftfilt::cmplx> 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;
|
|
||||||
|
|
||||||
QNetworkAccessManager *m_networkManager;
|
QNetworkAccessManager *m_networkManager;
|
||||||
QNetworkRequest m_networkRequest;
|
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 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 webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||||
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force);
|
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void networkManagerFinished(QNetworkReply *reply);
|
void networkManagerFinished(QNetworkReply *reply);
|
||||||
void timerHandlerFunction();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // INCLUDE_FREEDVDEMOD_H
|
#endif // INCLUDE_FREEDVDEMOD_H
|
||||||
|
203
plugins/channelrx/demodfreedv/freedvdemodbaseband.cpp
Normal file
203
plugins/channelrx/demodfreedv/freedvdemodbaseband.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#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());
|
||||||
|
}
|
120
plugins/channelrx/demodfreedv/freedvdemodbaseband.h
Normal file
120
plugins/channelrx/demodfreedv/freedvdemodbaseband.h
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef INCLUDE_FREEDVDEMODBASEBAND_H
|
||||||
|
#define INCLUDE_FREEDVDEMODBASEBAND_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
|
#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
|
@ -279,6 +279,7 @@ FreeDVDemodGUI::FreeDVDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
|
|||||||
m_freeDVDemod = (FreeDVDemod*) rxChannel;
|
m_freeDVDemod = (FreeDVDemod*) rxChannel;
|
||||||
m_freeDVDemod->setSampleSink(m_spectrumVis);
|
m_freeDVDemod->setSampleSink(m_spectrumVis);
|
||||||
m_freeDVDemod->setMessageQueueToGUI(getInputMessageQueue());
|
m_freeDVDemod->setMessageQueueToGUI(getInputMessageQueue());
|
||||||
|
m_freeDVDemod->propagateMessageQueueToGUI();
|
||||||
|
|
||||||
resetToDefaults();
|
resetToDefaults();
|
||||||
|
|
||||||
@ -314,7 +315,7 @@ FreeDVDemodGUI::FreeDVDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
|
|||||||
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
|
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
|
||||||
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
|
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
|
||||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
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);
|
ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum);
|
||||||
|
|
||||||
@ -346,10 +347,6 @@ void FreeDVDemodGUI::applySettings(bool force)
|
|||||||
{
|
{
|
||||||
if (m_doApplySettings)
|
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);
|
FreeDVDemod::MsgConfigureFreeDVDemod* message = FreeDVDemod::MsgConfigureFreeDVDemod::create( m_settings, force);
|
||||||
m_freeDVDemod->getInputMessageQueue()->push(message);
|
m_freeDVDemod->getInputMessageQueue()->push(message);
|
||||||
}
|
}
|
||||||
|
565
plugins/channelrx/demodfreedv/freedvdemodsink.cpp
Normal file
565
plugins/channelrx/demodfreedv/freedvdemodsink.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
211
plugins/channelrx/demodfreedv/freedvdemodsink.h
Normal file
211
plugins/channelrx/demodfreedv/freedvdemodsink.h
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef INCLUDE_FREEDVDEMODSINK_H
|
||||||
|
#define INCLUDE_FREEDVDEMODSINK_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#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<fftfilt::cmplx> 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
|
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
const PluginDescriptor FreeDVPlugin::m_pluginDescriptor = {
|
const PluginDescriptor FreeDVPlugin::m_pluginDescriptor = {
|
||||||
QString("FreeDV Demodulator"),
|
QString("FreeDV Demodulator"),
|
||||||
QString("4.11.6"),
|
QString("4.12.2"),
|
||||||
QString("(c) Edouard Griffiths, F4EXB"),
|
QString("(c) Edouard Griffiths, F4EXB"),
|
||||||
QString("https://github.com/f4exb/sdrangel"),
|
QString("https://github.com/f4exb/sdrangel"),
|
||||||
true,
|
true,
|
||||||
|
@ -2,6 +2,8 @@ project(localsink)
|
|||||||
|
|
||||||
set(localsink_SOURCES
|
set(localsink_SOURCES
|
||||||
localsink.cpp
|
localsink.cpp
|
||||||
|
localsinkbaseband.cpp
|
||||||
|
localsinksink.cpp
|
||||||
localsinksettings.cpp
|
localsinksettings.cpp
|
||||||
localsinkwebapiadapter.cpp
|
localsinkwebapiadapter.cpp
|
||||||
localsinkthread.cpp
|
localsinkthread.cpp
|
||||||
@ -10,6 +12,8 @@ set(localsink_SOURCES
|
|||||||
|
|
||||||
set(localsink_HEADERS
|
set(localsink_HEADERS
|
||||||
localsink.h
|
localsink.h
|
||||||
|
localsinkbaseband.h
|
||||||
|
localsinksink.h
|
||||||
localsinksettings.h
|
localsinksettings.h
|
||||||
localsinkwebapiadapter.h
|
localsinkwebapiadapter.h
|
||||||
localsinkthread.h
|
localsinkthread.h
|
||||||
@ -25,14 +29,12 @@ if(NOT SERVER_MODE)
|
|||||||
set(localsink_SOURCES
|
set(localsink_SOURCES
|
||||||
${localsink_SOURCES}
|
${localsink_SOURCES}
|
||||||
localsinkgui.cpp
|
localsinkgui.cpp
|
||||||
|
|
||||||
localsinkgui.ui
|
localsinkgui.ui
|
||||||
)
|
)
|
||||||
set(localsink_HEADERS
|
set(localsink_HEADERS
|
||||||
${localsink_HEADERS}
|
${localsink_HEADERS}
|
||||||
localsinkgui.h
|
localsinkgui.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TARGET_NAME localsink)
|
set(TARGET_NAME localsink)
|
||||||
set(TARGET_LIB "Qt5::Widgets")
|
set(TARGET_LIB "Qt5::Widgets")
|
||||||
set(TARGET_LIB_GUI "sdrgui")
|
set(TARGET_LIB_GUI "sdrgui")
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#include "localsink.h"
|
|
||||||
|
|
||||||
#include <boost/crc.hpp>
|
#include <boost/crc.hpp>
|
||||||
#include <boost/cstdint.hpp>
|
#include <boost/cstdint.hpp>
|
||||||
@ -37,11 +36,11 @@
|
|||||||
#include "dsp/devicesamplemimo.h"
|
#include "dsp/devicesamplemimo.h"
|
||||||
#include "device/deviceapi.h"
|
#include "device/deviceapi.h"
|
||||||
|
|
||||||
#include "localsinkthread.h"
|
#include "localsinkbaseband.h"
|
||||||
|
#include "localsink.h"
|
||||||
|
|
||||||
MESSAGE_CLASS_DEFINITION(LocalSink::MsgConfigureLocalSink, Message)
|
MESSAGE_CLASS_DEFINITION(LocalSink::MsgConfigureLocalSink, Message)
|
||||||
MESSAGE_CLASS_DEFINITION(LocalSink::MsgSampleRateNotification, Message)
|
MESSAGE_CLASS_DEFINITION(LocalSink::MsgBasebandSampleRateNotification, Message)
|
||||||
MESSAGE_CLASS_DEFINITION(LocalSink::MsgConfigureChannelizer, Message)
|
|
||||||
|
|
||||||
const QString LocalSink::m_channelIdURI = "sdrangel.channel.localsink";
|
const QString LocalSink::m_channelIdURI = "sdrangel.channel.localsink";
|
||||||
const QString LocalSink::m_channelId = "LocalSink";
|
const QString LocalSink::m_channelId = "LocalSink";
|
||||||
@ -49,18 +48,19 @@ const QString LocalSink::m_channelId = "LocalSink";
|
|||||||
LocalSink::LocalSink(DeviceAPI *deviceAPI) :
|
LocalSink::LocalSink(DeviceAPI *deviceAPI) :
|
||||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||||
m_deviceAPI(deviceAPI),
|
m_deviceAPI(deviceAPI),
|
||||||
m_running(false),
|
|
||||||
m_sinkThread(0),
|
|
||||||
m_centerFrequency(0),
|
m_centerFrequency(0),
|
||||||
m_frequencyOffset(0),
|
m_frequencyOffset(0),
|
||||||
m_sampleRate(48000),
|
m_basebandSampleRate(48000)
|
||||||
m_deviceSampleRate(48000)
|
|
||||||
{
|
{
|
||||||
setObjectName(m_channelId);
|
setObjectName(m_channelId);
|
||||||
|
|
||||||
m_channelizer = new DownChannelizer(this);
|
m_thread = new QThread(this);
|
||||||
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
|
m_basebandSink = new LocalSinkBaseband();
|
||||||
m_deviceAPI->addChannelSink(m_threadedChannelizer);
|
m_basebandSink->moveToThread(m_thread);
|
||||||
|
|
||||||
|
applySettings(m_settings, true);
|
||||||
|
|
||||||
|
m_deviceAPI->addChannelSink(this);
|
||||||
m_deviceAPI->addChannelSinkAPI(this);
|
m_deviceAPI->addChannelSinkAPI(this);
|
||||||
|
|
||||||
m_networkManager = new QNetworkAccessManager();
|
m_networkManager = new QNetworkAccessManager();
|
||||||
@ -72,9 +72,9 @@ LocalSink::~LocalSink()
|
|||||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||||
delete m_networkManager;
|
delete m_networkManager;
|
||||||
m_deviceAPI->removeChannelSinkAPI(this);
|
m_deviceAPI->removeChannelSinkAPI(this);
|
||||||
m_deviceAPI->removeChannelSink(m_threadedChannelizer);
|
m_deviceAPI->removeChannelSink(this);
|
||||||
delete m_threadedChannelizer;
|
delete m_basebandSink;
|
||||||
delete m_channelizer;
|
delete m_thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t LocalSink::getNumberOfDeviceStreams() const
|
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 LocalSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
|
||||||
{
|
{
|
||||||
(void) firstOfBurst;
|
(void) firstOfBurst;
|
||||||
emit samplesAvailable((const quint8*) &(*begin), (end-begin)*sizeof(Sample));
|
m_basebandSink->feed(begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalSink::start()
|
void LocalSink::start()
|
||||||
{
|
{
|
||||||
qDebug("LocalSink::start");
|
qDebug("LocalSink::start");
|
||||||
|
m_basebandSink->reset();
|
||||||
if (m_running) {
|
m_thread->start();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalSink::stop()
|
void LocalSink::stop()
|
||||||
{
|
{
|
||||||
qDebug("LocalSink::stop");
|
qDebug("LocalSink::stop");
|
||||||
|
m_thread->exit();
|
||||||
disconnect(this,
|
m_thread->wait();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LocalSink::handleMessage(const Message& cmd)
|
bool LocalSink::handleMessage(const Message& cmd)
|
||||||
{
|
{
|
||||||
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
|
if (DSPSignalNotification::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))
|
|
||||||
{
|
{
|
||||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||||
|
|
||||||
@ -157,20 +112,19 @@ bool LocalSink::handleMessage(const Message& cmd)
|
|||||||
<< " inputSampleRate: " << notif.getSampleRate()
|
<< " inputSampleRate: " << notif.getSampleRate()
|
||||||
<< " centerFrequency: " << notif.getCenterFrequency();
|
<< " centerFrequency: " << notif.getCenterFrequency();
|
||||||
|
|
||||||
setCenterFrequency(notif.getCenterFrequency());
|
m_basebandSampleRate = notif.getSampleRate();
|
||||||
m_deviceSampleRate = notif.getSampleRate();
|
m_centerFrequency = notif.getCenterFrequency();
|
||||||
calculateFrequencyOffset(); // This is when device sample rate changes
|
|
||||||
propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex);
|
|
||||||
|
|
||||||
// Redo the channelizer stuff with the new sample rate to re-synchronize everything
|
calculateFrequencyOffset(m_settings.m_log2Decim, m_settings.m_filterChainHash); // This is when device sample rate changes
|
||||||
m_channelizer->set(m_channelizer->getInputMessageQueue(),
|
propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex, m_settings.m_log2Decim);
|
||||||
m_settings.m_log2Decim,
|
|
||||||
m_settings.m_filterChainHash);
|
|
||||||
|
|
||||||
if (m_guiMessageQueue)
|
MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(notif.getSampleRate());
|
||||||
|
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||||
|
|
||||||
|
if (getMessageQueueToGUI())
|
||||||
{
|
{
|
||||||
MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate());
|
MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(notif.getSampleRate());
|
||||||
m_guiMessageQueue->push(msg);
|
getMessageQueueToGUI()->push(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -183,25 +137,6 @@ bool LocalSink::handleMessage(const Message& cmd)
|
|||||||
|
|
||||||
return true;
|
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
|
else
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -279,15 +214,25 @@ DeviceSampleSource *LocalSink::getLocalDevice(uint32_t index)
|
|||||||
return nullptr;
|
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);
|
DeviceSampleSource *deviceSource = getLocalDevice(index);
|
||||||
|
|
||||||
if (deviceSource)
|
if (deviceSource)
|
||||||
{
|
{
|
||||||
deviceSource->setSampleRate(m_deviceSampleRate / (1<<m_settings.m_log2Decim));
|
deviceSource->setSampleRate(m_basebandSampleRate / (1 << log2Decim));
|
||||||
deviceSource->setCenterFrequency(m_centerFrequency + m_frequencyOffset);
|
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)
|
void LocalSink::applySettings(const LocalSinkSettings& settings, bool force)
|
||||||
@ -295,27 +240,42 @@ void LocalSink::applySettings(const LocalSinkSettings& settings, bool force)
|
|||||||
qDebug() << "LocalSink::applySettings:"
|
qDebug() << "LocalSink::applySettings:"
|
||||||
<< "m_localDeviceIndex: " << settings.m_localDeviceIndex
|
<< "m_localDeviceIndex: " << settings.m_localDeviceIndex
|
||||||
<< "m_streamIndex: " << settings.m_streamIndex
|
<< "m_streamIndex: " << settings.m_streamIndex
|
||||||
|
<< "m_play:" << settings.m_play
|
||||||
<< "force: " << force;
|
<< "force: " << force;
|
||||||
|
|
||||||
QList<QString> reverseAPIKeys;
|
QList<QString> 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)
|
if ((settings.m_localDeviceIndex != m_settings.m_localDeviceIndex) || force)
|
||||||
{
|
{
|
||||||
reverseAPIKeys.append("localDeviceIndex");
|
reverseAPIKeys.append("localDeviceIndex");
|
||||||
|
propagateSampleRateAndFrequency(settings.m_localDeviceIndex, settings.m_log2Decim);
|
||||||
DeviceSampleSource *deviceSource = getLocalDevice(settings.m_localDeviceIndex);
|
DeviceSampleSource *deviceSource = getLocalDevice(settings.m_localDeviceIndex);
|
||||||
|
LocalSinkBaseband::MsgConfigureLocalDeviceSampleSource *msg =
|
||||||
if (deviceSource)
|
LocalSinkBaseband::MsgConfigureLocalDeviceSampleSource::create(deviceSource);
|
||||||
{
|
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||||
if (m_sinkThread) {
|
|
||||||
m_sinkThread->setSampleFifo(deviceSource->getSampleFifo());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
propagateSampleRateAndFrequency(settings.m_localDeviceIndex);
|
if ((settings.m_log2Decim != m_settings.m_log2Decim)
|
||||||
}
|
|| (settings.m_filterChainHash != m_settings.m_filterChainHash) || force)
|
||||||
else
|
|
||||||
{
|
{
|
||||||
qWarning("LocalSink::applySettings: invalid local device for index %u", settings.m_localDeviceIndex);
|
calculateFrequencyOffset(settings.m_log2Decim, settings.m_filterChainHash);
|
||||||
|
propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex, settings.m_log2Decim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
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
|
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
|
||||||
{
|
{
|
||||||
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
|
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
|
||||||
m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex);
|
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
|
||||||
m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex);
|
m_deviceAPI->addChannelSinkAPI(this, 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");
|
reverseAPIKeys.append("streamIndex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocalSinkBaseband::MsgConfigureLocalSinkBaseband *msg = LocalSinkBaseband::MsgConfigureLocalSinkBaseband::create(settings, force);
|
||||||
|
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||||
|
|
||||||
if ((settings.m_useReverseAPI) && (reverseAPIKeys.size() != 0))
|
if ((settings.m_useReverseAPI) && (reverseAPIKeys.size() != 0))
|
||||||
{
|
{
|
||||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
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;
|
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);
|
double shiftFactor = HBFilterChainConverter::getShiftFactor(log2Decim, filterChainHash);
|
||||||
m_frequencyOffset = m_deviceSampleRate * shiftFactor;
|
m_frequencyOffset = m_basebandSampleRate * shiftFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
int LocalSink::webapiSettingsGet(
|
int LocalSink::webapiSettingsGet(
|
||||||
@ -387,12 +348,6 @@ int LocalSink::webapiSettingsPutPatch(
|
|||||||
MsgConfigureLocalSink *msg = MsgConfigureLocalSink::create(settings, force);
|
MsgConfigureLocalSink *msg = MsgConfigureLocalSink::create(settings, force);
|
||||||
m_inputMessageQueue.push(msg);
|
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);
|
qDebug("LocalSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
|
||||||
if (m_guiMessageQueue) // forward to GUI if any
|
if (m_guiMessageQueue) // forward to GUI if any
|
||||||
{
|
{
|
||||||
|
@ -26,13 +26,13 @@
|
|||||||
#include "channel/channelapi.h"
|
#include "channel/channelapi.h"
|
||||||
#include "localsinksettings.h"
|
#include "localsinksettings.h"
|
||||||
|
|
||||||
class DeviceAPI;
|
|
||||||
class DeviceSampleSource;
|
|
||||||
class ThreadedBasebandSampleSink;
|
|
||||||
class DownChannelizer;
|
|
||||||
class LocalSinkThread;
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
|
class QThread;
|
||||||
|
|
||||||
|
class DeviceAPI;
|
||||||
|
class DeviceSampleSource;
|
||||||
|
class LocalSinkBaseband;
|
||||||
|
|
||||||
class LocalSink : public BasebandSampleSink, public ChannelAPI {
|
class LocalSink : public BasebandSampleSink, public ChannelAPI {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -60,19 +60,19 @@ public:
|
|||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
class MsgSampleRateNotification : public Message {
|
class MsgBasebandSampleRateNotification : public Message {
|
||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static MsgSampleRateNotification* create(int sampleRate) {
|
static MsgBasebandSampleRateNotification* create(int sampleRate) {
|
||||||
return new MsgSampleRateNotification(sampleRate);
|
return new MsgBasebandSampleRateNotification(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
int getSampleRate() const { return m_sampleRate; }
|
int getSampleRate() const { return m_sampleRate; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
MsgSampleRateNotification(int sampleRate) :
|
MsgBasebandSampleRateNotification(int sampleRate) :
|
||||||
Message(),
|
Message(),
|
||||||
m_sampleRate(sampleRate)
|
m_sampleRate(sampleRate)
|
||||||
{ }
|
{ }
|
||||||
@ -80,28 +80,6 @@ public:
|
|||||||
int m_sampleRate;
|
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);
|
LocalSink(DeviceAPI *deviceAPI);
|
||||||
virtual ~LocalSink();
|
virtual ~LocalSink();
|
||||||
virtual void destroy() { delete this; }
|
virtual void destroy() { delete this; }
|
||||||
@ -147,44 +125,31 @@ public:
|
|||||||
const QStringList& channelSettingsKeys,
|
const QStringList& channelSettingsKeys,
|
||||||
SWGSDRangel::SWGChannelSettings& response);
|
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<uint32_t>& indexes);
|
void getLocalDevices(std::vector<uint32_t>& indexes);
|
||||||
uint32_t getNumberOfDeviceStreams() const;
|
uint32_t getNumberOfDeviceStreams() const;
|
||||||
|
|
||||||
static const QString m_channelIdURI;
|
static const QString m_channelIdURI;
|
||||||
static const QString m_channelId;
|
static const QString m_channelId;
|
||||||
|
|
||||||
signals:
|
|
||||||
void samplesAvailable(const quint8* data, uint count);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeviceAPI *m_deviceAPI;
|
DeviceAPI *m_deviceAPI;
|
||||||
ThreadedBasebandSampleSink* m_threadedChannelizer;
|
QThread *m_thread;
|
||||||
DownChannelizer* m_channelizer;
|
LocalSinkBaseband *m_basebandSink;
|
||||||
bool m_running;
|
|
||||||
|
|
||||||
LocalSinkSettings m_settings;
|
LocalSinkSettings m_settings;
|
||||||
LocalSinkThread *m_sinkThread;
|
|
||||||
|
|
||||||
uint64_t m_centerFrequency;
|
uint64_t m_centerFrequency;
|
||||||
int64_t m_frequencyOffset;
|
int64_t m_frequencyOffset;
|
||||||
uint32_t m_sampleRate;
|
uint32_t m_basebandSampleRate;
|
||||||
uint32_t m_deviceSampleRate;
|
|
||||||
|
|
||||||
QNetworkAccessManager *m_networkManager;
|
QNetworkAccessManager *m_networkManager;
|
||||||
QNetworkRequest m_networkRequest;
|
QNetworkRequest m_networkRequest;
|
||||||
|
|
||||||
void applySettings(const LocalSinkSettings& settings, bool force = false);
|
void applySettings(const LocalSinkSettings& settings, bool force = false);
|
||||||
DeviceSampleSource *getLocalDevice(uint32_t index);
|
void propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Decim);
|
||||||
void propagateSampleRateAndFrequency(uint32_t index);
|
|
||||||
static void validateFilterChainHash(LocalSinkSettings& settings);
|
static void validateFilterChainHash(LocalSinkSettings& settings);
|
||||||
void calculateFrequencyOffset();
|
void calculateFrequencyOffset(uint32_t log2Decim, uint32_t filterChainHash);
|
||||||
|
DeviceSampleSource *getLocalDevice(uint32_t index);
|
||||||
|
|
||||||
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const LocalSinkSettings& settings, bool force);
|
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const LocalSinkSettings& settings, bool force);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
181
plugins/channelrx/localsink/localsinkbaseband.cpp
Normal file
181
plugins/channelrx/localsink/localsinkbaseband.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#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();
|
||||||
|
}
|
147
plugins/channelrx/localsink/localsinkbaseband.h
Normal file
147
plugins/channelrx/localsink/localsinkbaseband.h
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef INCLUDE_LOCALSINKBASEBAND_H
|
||||||
|
#define INCLUDE_LOCALSINKBASEBAND_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
|
#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
|
@ -71,11 +71,16 @@ QByteArray LocalSinkGUI::serialize() const
|
|||||||
|
|
||||||
bool LocalSinkGUI::deserialize(const QByteArray& data)
|
bool LocalSinkGUI::deserialize(const QByteArray& data)
|
||||||
{
|
{
|
||||||
if(m_settings.deserialize(data)) {
|
updateLocalDevices();
|
||||||
|
|
||||||
|
if (m_settings.deserialize(data))
|
||||||
|
{
|
||||||
displaySettings();
|
displaySettings();
|
||||||
applySettings(true);
|
applySettings(true);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
resetToDefaults();
|
resetToDefaults();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -83,11 +88,11 @@ bool LocalSinkGUI::deserialize(const QByteArray& data)
|
|||||||
|
|
||||||
bool LocalSinkGUI::handleMessage(const Message& message)
|
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_channelMarker.setBandwidth(notif.getSampleRate());
|
||||||
m_sampleRate = notif.getSampleRate();
|
m_basebandSampleRate = notif.getSampleRate();
|
||||||
displayRateAndShift();
|
displayRateAndShift();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -111,7 +116,7 @@ LocalSinkGUI::LocalSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
|||||||
ui(new Ui::LocalSinkGUI),
|
ui(new Ui::LocalSinkGUI),
|
||||||
m_pluginAPI(pluginAPI),
|
m_pluginAPI(pluginAPI),
|
||||||
m_deviceUISet(deviceUISet),
|
m_deviceUISet(deviceUISet),
|
||||||
m_sampleRate(0),
|
m_basebandSampleRate(0),
|
||||||
m_tickCount(0)
|
m_tickCount(0)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
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()
|
void LocalSinkGUI::displaySettings()
|
||||||
{
|
{
|
||||||
m_channelMarker.blockSignals(true);
|
m_channelMarker.blockSignals(true);
|
||||||
m_channelMarker.setCenterFrequency(0);
|
m_channelMarker.setCenterFrequency(0);
|
||||||
m_channelMarker.setTitle(m_settings.m_title);
|
m_channelMarker.setTitle(m_settings.m_title);
|
||||||
m_channelMarker.setBandwidth(m_sampleRate); // TODO
|
m_channelMarker.setBandwidth(m_basebandSampleRate / (1<<m_settings.m_log2Decim));
|
||||||
m_channelMarker.setMovable(false); // do not let user move the center arbitrarily
|
m_channelMarker.setMovable(false); // do not let user move the center arbitrarily
|
||||||
m_channelMarker.blockSignals(false);
|
m_channelMarker.blockSignals(false);
|
||||||
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
|
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
|
||||||
@ -203,6 +197,7 @@ void LocalSinkGUI::displaySettings()
|
|||||||
ui->decimationFactor->setCurrentIndex(m_settings.m_log2Decim);
|
ui->decimationFactor->setCurrentIndex(m_settings.m_log2Decim);
|
||||||
applyDecimation();
|
applyDecimation();
|
||||||
displayStreamIndex();
|
displayStreamIndex();
|
||||||
|
|
||||||
blockApplySettings(false);
|
blockApplySettings(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,8 +212,8 @@ void LocalSinkGUI::displayStreamIndex()
|
|||||||
|
|
||||||
void LocalSinkGUI::displayRateAndShift()
|
void LocalSinkGUI::displayRateAndShift()
|
||||||
{
|
{
|
||||||
int shift = m_shiftFrequencyFactor * m_sampleRate;
|
int shift = m_shiftFrequencyFactor * m_basebandSampleRate;
|
||||||
double channelSampleRate = ((double) m_sampleRate) / (1<<m_settings.m_log2Decim);
|
double channelSampleRate = ((double) m_basebandSampleRate) / (1<<m_settings.m_log2Decim);
|
||||||
QLocale loc;
|
QLocale loc;
|
||||||
ui->offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift)));
|
ui->offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift)));
|
||||||
ui->channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5)));
|
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*)
|
void LocalSinkGUI::leaveEvent(QEvent*)
|
||||||
{
|
{
|
||||||
m_channelMarker.setHighlighted(false);
|
m_channelMarker.setHighlighted(false);
|
||||||
@ -334,6 +343,17 @@ void LocalSinkGUI::on_localDevicesRefresh_clicked(bool checked)
|
|||||||
{
|
{
|
||||||
(void) checked;
|
(void) checked;
|
||||||
updateLocalDevices();
|
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()
|
void LocalSinkGUI::applyDecimation()
|
||||||
@ -358,7 +378,7 @@ void LocalSinkGUI::applyPosition()
|
|||||||
ui->filterChainText->setText(s);
|
ui->filterChainText->setText(s);
|
||||||
|
|
||||||
displayRateAndShift();
|
displayRateAndShift();
|
||||||
applyChannelSettings();
|
applySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalSinkGUI::tick()
|
void LocalSinkGUI::tick()
|
||||||
|
@ -62,7 +62,7 @@ private:
|
|||||||
DeviceUISet* m_deviceUISet;
|
DeviceUISet* m_deviceUISet;
|
||||||
ChannelMarker m_channelMarker;
|
ChannelMarker m_channelMarker;
|
||||||
LocalSinkSettings m_settings;
|
LocalSinkSettings m_settings;
|
||||||
int m_sampleRate;
|
int m_basebandSampleRate;
|
||||||
double m_shiftFrequencyFactor; //!< Channel frequency shift factor
|
double m_shiftFrequencyFactor; //!< Channel frequency shift factor
|
||||||
bool m_doApplySettings;
|
bool m_doApplySettings;
|
||||||
|
|
||||||
@ -77,11 +77,11 @@ private:
|
|||||||
|
|
||||||
void blockApplySettings(bool block);
|
void blockApplySettings(bool block);
|
||||||
void applySettings(bool force = false);
|
void applySettings(bool force = false);
|
||||||
void applyChannelSettings();
|
|
||||||
void displaySettings();
|
void displaySettings();
|
||||||
void displayStreamIndex();
|
void displayStreamIndex();
|
||||||
void displayRateAndShift();
|
void displayRateAndShift();
|
||||||
void updateLocalDevices();
|
void updateLocalDevices();
|
||||||
|
int getLocalDeviceIndexInCombo(int localDeviceIndex);
|
||||||
|
|
||||||
void leaveEvent(QEvent*);
|
void leaveEvent(QEvent*);
|
||||||
void enterEvent(QEvent*);
|
void enterEvent(QEvent*);
|
||||||
@ -95,6 +95,7 @@ private slots:
|
|||||||
void on_position_valueChanged(int value);
|
void on_position_valueChanged(int value);
|
||||||
void on_localDevice_currentIndexChanged(int index);
|
void on_localDevice_currentIndexChanged(int index);
|
||||||
void on_localDevicesRefresh_clicked(bool checked);
|
void on_localDevicesRefresh_clicked(bool checked);
|
||||||
|
void on_localDevicePlay_toggled(bool checked);
|
||||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||||
void onMenuDialogCalled(const QPoint& p);
|
void onMenuDialogCalled(const QPoint& p);
|
||||||
void tick();
|
void tick();
|
||||||
|
@ -298,6 +298,21 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="ButtonSwitch" name="localDevicePlay">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Start/Stop sink</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||||
|
<normaloff>:/play.png</normaloff>
|
||||||
|
<normalon>:/pause.png</normalon>:/play.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -323,6 +338,11 @@
|
|||||||
<header>gui/rollupwidget.h</header>
|
<header>gui/rollupwidget.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>ButtonSwitch</class>
|
||||||
|
<extends>QToolButton</extends>
|
||||||
|
<header>gui/buttonswitch.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
const PluginDescriptor LocalSinkPlugin::m_pluginDescriptor = {
|
const PluginDescriptor LocalSinkPlugin::m_pluginDescriptor = {
|
||||||
QString("Local channel sink"),
|
QString("Local channel sink"),
|
||||||
QString("4.11.6"),
|
QString("4.12.2"),
|
||||||
QString("(c) Edouard Griffiths, F4EXB"),
|
QString("(c) Edouard Griffiths, F4EXB"),
|
||||||
QString("https://github.com/f4exb/sdrangel"),
|
QString("https://github.com/f4exb/sdrangel"),
|
||||||
true,
|
true,
|
||||||
|
@ -36,6 +36,7 @@ void LocalSinkSettings::resetToDefaults()
|
|||||||
m_log2Decim = 0;
|
m_log2Decim = 0;
|
||||||
m_filterChainHash = 0;
|
m_filterChainHash = 0;
|
||||||
m_channelMarker = nullptr;
|
m_channelMarker = nullptr;
|
||||||
|
m_play = false;
|
||||||
m_streamIndex = 0;
|
m_streamIndex = 0;
|
||||||
m_useReverseAPI = false;
|
m_useReverseAPI = false;
|
||||||
m_reverseAPIAddress = "127.0.0.1";
|
m_reverseAPIAddress = "127.0.0.1";
|
||||||
|
@ -30,6 +30,7 @@ struct LocalSinkSettings
|
|||||||
QString m_title;
|
QString m_title;
|
||||||
uint32_t m_log2Decim;
|
uint32_t m_log2Decim;
|
||||||
uint32_t m_filterChainHash;
|
uint32_t m_filterChainHash;
|
||||||
|
bool m_play;
|
||||||
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
|
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
|
||||||
bool m_useReverseAPI;
|
bool m_useReverseAPI;
|
||||||
QString m_reverseAPIAddress;
|
QString m_reverseAPIAddress;
|
||||||
|
99
plugins/channelrx/localsink/localsinksink.cpp
Normal file
99
plugins/channelrx/localsink/localsinksink.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <boost/crc.hpp>
|
||||||
|
#include <boost/cstdint.hpp>
|
||||||
|
#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;
|
||||||
|
}
|
58
plugins/channelrx/localsink/localsinksink.h
Normal file
58
plugins/channelrx/localsink/localsinksink.h
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef INCLUDE_LOCALSINKSINK_H_
|
||||||
|
#define INCLUDE_LOCALSINKSINK_H_
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#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_
|
@ -2,6 +2,8 @@ project(udpsink)
|
|||||||
|
|
||||||
set(udpsink_SOURCES
|
set(udpsink_SOURCES
|
||||||
udpsink.cpp
|
udpsink.cpp
|
||||||
|
udpsinksink.cpp
|
||||||
|
udpsinkbaseband.cpp
|
||||||
udpsinkplugin.cpp
|
udpsinkplugin.cpp
|
||||||
udpsinksettings.cpp
|
udpsinksettings.cpp
|
||||||
udpsinkwebapiadapter.cpp
|
udpsinkwebapiadapter.cpp
|
||||||
@ -9,6 +11,8 @@ set(udpsink_SOURCES
|
|||||||
|
|
||||||
set(udpsink_HEADERS
|
set(udpsink_HEADERS
|
||||||
udpsink.h
|
udpsink.h
|
||||||
|
udpsinksink.h
|
||||||
|
udpsinkbaseband.h
|
||||||
udpsinkplugin.h
|
udpsinkplugin.h
|
||||||
udpsinksettings.h
|
udpsinksettings.h
|
||||||
udpsinkwebapiadapter.h
|
udpsinkwebapiadapter.h
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
#include "SWGChannelSettings.h"
|
#include "SWGChannelSettings.h"
|
||||||
#include "SWGUDPSinkSettings.h"
|
#include "SWGUDPSinkSettings.h"
|
||||||
@ -37,11 +38,7 @@
|
|||||||
|
|
||||||
#include "udpsink.h"
|
#include "udpsink.h"
|
||||||
|
|
||||||
const Real UDPSink::m_agcTarget = 16384.0f;
|
MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSink, Message)
|
||||||
|
|
||||||
MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureUDPSource, Message)
|
|
||||||
MESSAGE_CLASS_DEFINITION(UDPSink::MsgConfigureChannelizer, Message)
|
|
||||||
MESSAGE_CLASS_DEFINITION(UDPSink::MsgUDPSinkSpectrum, Message)
|
|
||||||
|
|
||||||
const QString UDPSink::m_channelIdURI = "sdrangel.channel.udpsink";
|
const QString UDPSink::m_channelIdURI = "sdrangel.channel.udpsink";
|
||||||
const QString UDPSink::m_channelId = "UDPSink";
|
const QString UDPSink::m_channelId = "UDPSink";
|
||||||
@ -49,71 +46,18 @@ const QString UDPSink::m_channelId = "UDPSink";
|
|||||||
UDPSink::UDPSink(DeviceAPI *deviceAPI) :
|
UDPSink::UDPSink(DeviceAPI *deviceAPI) :
|
||||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||||
m_deviceAPI(deviceAPI),
|
m_deviceAPI(deviceAPI),
|
||||||
m_inputSampleRate(48000),
|
m_channelSampleRate(48000),
|
||||||
m_inputFrequencyOffset(0),
|
m_channelFrequencyOffset(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)
|
|
||||||
{
|
{
|
||||||
setObjectName(m_channelId);
|
setObjectName(m_channelId);
|
||||||
|
|
||||||
m_udpBuffer16 = new UDPSinkUtil<Sample16>(this, udpBlockSize, m_settings.m_udpPort);
|
m_thread = new QThread(this);
|
||||||
m_udpBufferMono16 = new UDPSinkUtil<int16_t>(this, udpBlockSize, m_settings.m_udpPort);
|
m_basebandSink = new UDPSinkBaseband();
|
||||||
m_udpBuffer24 = new UDPSinkUtil<Sample24>(this, udpBlockSize, m_settings.m_udpPort);
|
m_basebandSink->moveToThread(m_thread);
|
||||||
m_audioSocket = new QUdpSocket(this);
|
|
||||||
m_udpAudioBuf = new char[m_udpAudioPayloadSize];
|
|
||||||
|
|
||||||
m_audioBuffer.resize(1<<9);
|
|
||||||
m_audioBufferFill = 0;
|
|
||||||
|
|
||||||
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);
|
applySettings(m_settings, true);
|
||||||
|
|
||||||
m_channelizer = new DownChannelizer(this);
|
m_deviceAPI->addChannelSink(this);
|
||||||
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
|
|
||||||
m_deviceAPI->addChannelSink(m_threadedChannelizer);
|
|
||||||
m_deviceAPI->addChannelSinkAPI(this);
|
m_deviceAPI->addChannelSinkAPI(this);
|
||||||
|
|
||||||
m_networkManager = new QNetworkAccessManager();
|
m_networkManager = new QNetworkAccessManager();
|
||||||
@ -124,23 +68,10 @@ UDPSink::~UDPSink()
|
|||||||
{
|
{
|
||||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||||
delete m_networkManager;
|
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->removeChannelSinkAPI(this);
|
||||||
m_deviceAPI->removeChannelSink(m_threadedChannelizer);
|
m_deviceAPI->removeChannelSink(this);
|
||||||
delete m_threadedChannelizer;
|
delete m_basebandSink;
|
||||||
delete m_channelizer;
|
delete m_thread;
|
||||||
delete UDPFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UDPSink::setSpectrum(MessageQueue* messageQueue, bool enabled)
|
|
||||||
{
|
|
||||||
Message* cmd = MsgUDPSinkSpectrum::create(enabled);
|
|
||||||
messageQueue->push(cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t UDPSink::getNumberOfDeviceStreams() const
|
uint32_t UDPSink::getNumberOfDeviceStreams() const
|
||||||
@ -150,354 +81,56 @@ uint32_t UDPSink::getNumberOfDeviceStreams() const
|
|||||||
|
|
||||||
void UDPSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
|
void UDPSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
|
||||||
{
|
{
|
||||||
Complex ci;
|
(void) positiveOnly;
|
||||||
fftfilt::cmplx* sideband;
|
m_basebandSink->feed(begin, end);
|
||||||
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 UDPSink::start()
|
void UDPSink::start()
|
||||||
{
|
{
|
||||||
m_phaseDiscri.reset();
|
qDebug() << "UDPSink::start";
|
||||||
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
|
|
||||||
|
if (m_basebandSampleRate != 0) {
|
||||||
|
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_basebandSink->reset();
|
||||||
|
m_thread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDPSink::stop()
|
void UDPSink::stop()
|
||||||
{
|
{
|
||||||
|
qDebug() << "UDPSink::stop";
|
||||||
|
m_thread->exit();
|
||||||
|
m_thread->wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UDPSink::handleMessage(const Message& cmd)
|
bool UDPSink::handleMessage(const Message& cmd)
|
||||||
{
|
{
|
||||||
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
|
if (MsgConfigureUDPSink::match(cmd))
|
||||||
{
|
{
|
||||||
DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
|
MsgConfigureUDPSink& cfg = (MsgConfigureUDPSink&) cmd;
|
||||||
qDebug() << "UDPSink::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << notif.getSampleRate()
|
qDebug("UDPSink::handleMessage: MsgConfigureUDPSink");
|
||||||
<< " frequencyOffset: " << notif.getFrequencyOffset();
|
|
||||||
|
|
||||||
applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (MsgConfigureChannelizer::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");
|
|
||||||
|
|
||||||
applySettings(cfg.getSettings(), cfg.getForce());
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
else if (DSPSignalNotification::match(cmd))
|
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;
|
return true;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(m_spectrum != 0)
|
|
||||||
{
|
|
||||||
return m_spectrum->handleMessage(cmd);
|
|
||||||
}
|
}
|
||||||
else
|
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)
|
void UDPSink::applySettings(const UDPSinkSettings& settings, bool force)
|
||||||
{
|
{
|
||||||
@ -577,133 +210,22 @@ void UDPSink::applySettings(const UDPSinkSettings& settings, bool force)
|
|||||||
reverseAPIKeys.append("audioPort");
|
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<QString&>(settings.m_udpAddress));
|
|
||||||
m_udpBufferMono16->setAddress(const_cast<QString&>(settings.m_udpAddress));
|
|
||||||
m_udpBuffer24->setAddress(const_cast<QString&>(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_settings.m_streamIndex != settings.m_streamIndex)
|
||||||
{
|
{
|
||||||
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
|
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
|
||||||
{
|
{
|
||||||
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
|
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
|
||||||
m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex);
|
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
|
||||||
m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex);
|
m_deviceAPI->addChannelSink(this, 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");
|
reverseAPIKeys.append("streamIndex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UDPSinkBaseband::MsgConfigureUDPSinkBaseband *msg = UDPSinkBaseband::MsgConfigureUDPSinkBaseband::create(settings, force);
|
||||||
|
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||||
|
|
||||||
if (settings.m_useReverseAPI)
|
if (settings.m_useReverseAPI)
|
||||||
{
|
{
|
||||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && 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))
|
if (m_settings.deserialize(data))
|
||||||
{
|
{
|
||||||
MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true);
|
MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(m_settings, true);
|
||||||
m_inputMessageQueue.push(msg);
|
m_inputMessageQueue.push(msg);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_settings.resetToDefaults();
|
m_settings.resetToDefaults();
|
||||||
MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(m_settings, true);
|
MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(m_settings, true);
|
||||||
m_inputMessageQueue.push(msg);
|
m_inputMessageQueue.push(msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -760,21 +282,13 @@ int UDPSink::webapiSettingsPutPatch(
|
|||||||
UDPSinkSettings settings = m_settings;
|
UDPSinkSettings settings = m_settings;
|
||||||
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
|
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
|
||||||
|
|
||||||
if (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)
|
MsgConfigureUDPSink *msg = MsgConfigureUDPSink::create(settings, force);
|
||||||
{
|
|
||||||
UDPSink::MsgConfigureChannelizer *msgChan = UDPSink::MsgConfigureChannelizer::create(
|
|
||||||
(int) settings.m_outputSampleRate,
|
|
||||||
(int) settings.m_inputFrequencyOffset);
|
|
||||||
m_inputMessageQueue.push(msgChan);
|
|
||||||
}
|
|
||||||
|
|
||||||
MsgConfigureUDPSource *msg = MsgConfigureUDPSource::create(settings, force);
|
|
||||||
m_inputMessageQueue.push(msg);
|
m_inputMessageQueue.push(msg);
|
||||||
|
|
||||||
qDebug("getUdpSinkSettings::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
|
qDebug("getUdpSinkSettings::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
|
||||||
if (m_guiMessageQueue) // forward to GUI if any
|
if (m_guiMessageQueue) // forward to GUI if any
|
||||||
{
|
{
|
||||||
MsgConfigureUDPSource *msgToGUI = MsgConfigureUDPSource::create(settings, force);
|
MsgConfigureUDPSink *msgToGUI = MsgConfigureUDPSink::create(settings, force);
|
||||||
m_guiMessageQueue->push(msgToGUI);
|
m_guiMessageQueue->push(msgToGUI);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -927,8 +441,8 @@ void UDPSink::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
|
|||||||
{
|
{
|
||||||
response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getInMagSq()));
|
response.getUdpSinkReport()->setChannelPowerDb(CalcDb::dbPower(getInMagSq()));
|
||||||
response.getUdpSinkReport()->setOutputPowerDb(CalcDb::dbPower(getMagSq()));
|
response.getUdpSinkReport()->setOutputPowerDb(CalcDb::dbPower(getMagSq()));
|
||||||
response.getUdpSinkReport()->setSquelch(m_squelchOpen ? 1 : 0);
|
response.getUdpSinkReport()->setSquelch(getSquelchOpen() ? 1 : 0);
|
||||||
response.getUdpSinkReport()->setInputSampleRate(m_inputSampleRate);
|
response.getUdpSinkReport()->setInputSampleRate(m_channelSampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDPSink::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const UDPSinkSettings& settings, bool force)
|
void UDPSink::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const UDPSinkSettings& settings, bool force)
|
||||||
@ -1044,3 +558,9 @@ void UDPSink::networkManagerFinished(QNetworkReply *reply)
|
|||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UDPSink::enableSpectrum(bool enable)
|
||||||
|
{
|
||||||
|
UDPSinkBaseband::MsgEnableSpectrum *msg = UDPSinkBaseband::MsgEnableSpectrum::create(enable);
|
||||||
|
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||||
|
}
|
@ -16,56 +16,43 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#ifndef INCLUDE_UDPSRC_H
|
#ifndef INCLUDE_UDPSINK_H
|
||||||
#define INCLUDE_UDPSRC_H
|
#define INCLUDE_UDPSINK_H
|
||||||
|
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QHostAddress>
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "dsp/basebandsamplesink.h"
|
#include "dsp/basebandsamplesink.h"
|
||||||
#include "channel/channelapi.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 "util/message.h"
|
||||||
#include "audio/audiofifo.h"
|
|
||||||
|
|
||||||
#include "udpsinksettings.h"
|
#include "udpsinkbaseband.h"
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class QUdpSocket;
|
|
||||||
class DeviceAPI;
|
class DeviceAPI;
|
||||||
class ThreadedBasebandSampleSink;
|
|
||||||
class DownChannelizer;
|
|
||||||
|
|
||||||
class UDPSink : public BasebandSampleSink, public ChannelAPI {
|
class UDPSink : public BasebandSampleSink, public ChannelAPI {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class MsgConfigureUDPSource : public Message {
|
class MsgConfigureUDPSink : public Message {
|
||||||
MESSAGE_CLASS_DECLARATION
|
MESSAGE_CLASS_DECLARATION
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const UDPSinkSettings& getSettings() const { return m_settings; }
|
const UDPSinkSettings& getSettings() const { return m_settings; }
|
||||||
bool getForce() const { return m_force; }
|
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:
|
private:
|
||||||
UDPSinkSettings m_settings;
|
UDPSinkSettings m_settings;
|
||||||
bool m_force;
|
bool m_force;
|
||||||
|
|
||||||
MsgConfigureUDPSource(const UDPSinkSettings& settings, bool force) :
|
MsgConfigureUDPSink(const UDPSinkSettings& settings, bool force) :
|
||||||
Message(),
|
Message(),
|
||||||
m_settings(settings),
|
m_settings(settings),
|
||||||
m_force(force)
|
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);
|
UDPSink(DeviceAPI *deviceAPI);
|
||||||
virtual ~UDPSink();
|
virtual ~UDPSink();
|
||||||
virtual void destroy() { delete this; }
|
virtual void destroy() { delete this; }
|
||||||
void setSpectrum(BasebandSampleSink* spectrum) { m_spectrum = spectrum; }
|
|
||||||
|
|
||||||
void setSpectrum(MessageQueue* messageQueue, bool enabled);
|
void setSpectrum(BasebandSampleSink* spectrum) { m_basebandSink->setSpectrum(spectrum); }
|
||||||
double getMagSq() const { return m_magsq; }
|
void enableSpectrum(bool enable);
|
||||||
double getInMagSq() const { return m_inMagsq; }
|
void setSpectrumPositiveOnly(bool positiveOnly) { m_basebandSink->setSpectrumPositiveOnly(positiveOnly); }
|
||||||
bool getSquelchOpen() const { return m_squelchOpen; }
|
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 feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
|
||||||
virtual void start();
|
virtual void start();
|
||||||
@ -164,212 +129,22 @@ private slots:
|
|||||||
void networkManagerFinished(QNetworkReply *reply);
|
void networkManagerFinished(QNetworkReply *reply);
|
||||||
|
|
||||||
protected:
|
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;
|
DeviceAPI* m_deviceAPI;
|
||||||
ThreadedBasebandSampleSink* m_threadedChannelizer;
|
QThread *m_thread;
|
||||||
DownChannelizer* m_channelizer;
|
UDPSinkBaseband* m_basebandSink;
|
||||||
|
|
||||||
int m_inputSampleRate;
|
|
||||||
int m_inputFrequencyOffset;
|
|
||||||
UDPSinkSettings m_settings;
|
UDPSinkSettings m_settings;
|
||||||
|
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
|
||||||
|
|
||||||
QUdpSocket *m_audioSocket;
|
int m_channelSampleRate;
|
||||||
|
int m_channelFrequencyOffset;
|
||||||
double m_magsq;
|
|
||||||
double m_inMagsq;
|
|
||||||
MovingAverage<double> m_outMovingAverage;
|
|
||||||
MovingAverage<double> m_inMovingAverage;
|
|
||||||
MovingAverage<double> 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<Sample16> *m_udpBuffer16;
|
|
||||||
UDPSinkUtil<int16_t> *m_udpBufferMono16;
|
|
||||||
UDPSinkUtil<Sample24> *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<double> m_bandpass;
|
|
||||||
|
|
||||||
QNetworkAccessManager *m_networkManager;
|
QNetworkAccessManager *m_networkManager;
|
||||||
QNetworkRequest m_networkRequest;
|
QNetworkRequest m_networkRequest;
|
||||||
|
|
||||||
QMutex m_settingsMutex;
|
|
||||||
|
|
||||||
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = true);
|
|
||||||
void applySettings(const UDPSinkSettings& settings, bool force = false);
|
void applySettings(const UDPSinkSettings& settings, bool force = false);
|
||||||
|
|
||||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||||
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const UDPSinkSettings& settings, bool force);
|
void webapiReverseSendSettings(QList<QString>& 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
|
||||||
|
174
plugins/channelrx/udpsink/udpsinkbaseband.cpp
Normal file
174
plugins/channelrx/udpsink/udpsinkbaseband.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#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());
|
||||||
|
}
|
108
plugins/channelrx/udpsink/udpsinkbaseband.h
Normal file
108
plugins/channelrx/udpsink/udpsinkbaseband.h
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef INCLUDE_UDPSINKBASEBAND_H
|
||||||
|
#define INCLUDE_UDPSINKBASEBAND_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
|
#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
|
@ -91,9 +91,9 @@ bool UDPSinkGUI::deserialize(const QByteArray& data)
|
|||||||
|
|
||||||
bool UDPSinkGUI::handleMessage(const Message& message )
|
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();
|
m_settings = cfg.getSettings();
|
||||||
blockApplySettings(true);
|
blockApplySettings(true);
|
||||||
displaySettings();
|
displaySettings();
|
||||||
@ -403,7 +403,7 @@ void UDPSinkGUI::applySettingsImmediate(bool force)
|
|||||||
{
|
{
|
||||||
if (m_doApplySettings)
|
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);
|
m_udpSink->getInputMessageQueue()->push(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -412,11 +412,7 @@ void UDPSinkGUI::applySettings(bool force)
|
|||||||
{
|
{
|
||||||
if (m_doApplySettings)
|
if (m_doApplySettings)
|
||||||
{
|
{
|
||||||
UDPSink::MsgConfigureChannelizer* channelConfigMsg = UDPSink::MsgConfigureChannelizer::create(
|
UDPSink::MsgConfigureUDPSink* message = UDPSink::MsgConfigureUDPSink::create( m_settings, force);
|
||||||
m_settings.m_outputSampleRate, m_channelMarker.getCenterFrequency());
|
|
||||||
m_udpSink->getInputMessageQueue()->push(channelConfigMsg);
|
|
||||||
|
|
||||||
UDPSink::MsgConfigureUDPSource* message = UDPSink::MsgConfigureUDPSource::create( m_settings, force);
|
|
||||||
m_udpSink->getInputMessageQueue()->push(message);
|
m_udpSink->getInputMessageQueue()->push(message);
|
||||||
|
|
||||||
ui->applyBtn->setEnabled(false);
|
ui->applyBtn->setEnabled(false);
|
||||||
@ -618,9 +614,8 @@ void UDPSinkGUI::on_squelchGate_valueChanged(int value)
|
|||||||
|
|
||||||
void UDPSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
void UDPSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
||||||
{
|
{
|
||||||
if ((widget == ui->spectrumBox) && (m_udpSink != 0))
|
if ((widget == ui->spectrumBox) && (m_udpSink)) {
|
||||||
{
|
m_udpSink->enableSpectrum(rollDown);
|
||||||
m_udpSink->setSpectrum(m_udpSink->getInputMessageQueue(), rollDown);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = {
|
const PluginDescriptor UDPSinkPlugin::m_pluginDescriptor = {
|
||||||
QString("UDP Channel Sink"),
|
QString("UDP Channel Sink"),
|
||||||
QString("4.11.6"),
|
QString("4.12.2"),
|
||||||
QString("(c) Edouard Griffiths, F4EXB"),
|
QString("(c) Edouard Griffiths, F4EXB"),
|
||||||
QString("https://github.com/f4exb/sdrangel"),
|
QString("https://github.com/f4exb/sdrangel"),
|
||||||
true,
|
true,
|
||||||
|
510
plugins/channelrx/udpsink/udpsinksink.cpp
Normal file
510
plugins/channelrx/udpsink/udpsinksink.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include <QUdpSocket>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QBuffer>
|
||||||
|
|
||||||
|
#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<Sample16>(this, udpBlockSize, m_settings.m_udpPort);
|
||||||
|
m_udpBufferMono16 = new UDPSinkUtil<int16_t>(this, udpBlockSize, m_settings.m_udpPort);
|
||||||
|
m_udpBuffer24 = new UDPSinkUtil<Sample24>(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<QString&>(settings.m_udpAddress));
|
||||||
|
m_udpBufferMono16->setAddress(const_cast<QString&>(settings.m_udpAddress));
|
||||||
|
m_udpBuffer24->setAddress(const_cast<QString&>(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;
|
||||||
|
}
|
232
plugins/channelrx/udpsink/udpsinksink.h
Normal file
232
plugins/channelrx/udpsink/udpsinksink.h
Normal file
@ -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 <http://www.gnu.org/licenses/>. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef INCLUDE_UDPSINKSINK_H
|
||||||
|
#define INCLUDE_UDPSINKSINK_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#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<double> m_outMovingAverage;
|
||||||
|
MovingAverage<double> m_inMovingAverage;
|
||||||
|
MovingAverage<double> 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<Sample16> *m_udpBuffer16;
|
||||||
|
UDPSinkUtil<int16_t> *m_udpBufferMono16;
|
||||||
|
UDPSinkUtil<Sample24> *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<double> 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
|
Loading…
Reference in New Issue
Block a user