NFM demod: revised AF squelch completely

This commit is contained in:
f4exb 2015-09-13 11:56:14 +02:00
parent 34942340a3
commit 4246fb6381
8 changed files with 64 additions and 38 deletions

View File

@ -55,8 +55,8 @@ set(sdrbase_SOURCES
sdrbase/audio/audiofifo.cpp
sdrbase/audio/audiooutput.cpp
sdrbase/dsp/agc.cpp
sdrbase/dsp/afsquelch.cpp
sdrbase/dsp/agc.cpp
sdrbase/dsp/channelizer.cpp
sdrbase/dsp/channelmarker.cpp
sdrbase/dsp/ctcssdetector.cpp
@ -127,6 +127,7 @@ set(sdrbase_HEADERS
include-gpl/audio/audiofifo.h
include-gpl/audio/audiooutput.h
include-gpl/dsp/afsquelch.h
include-gpl/dsp/channelizer.h
include/dsp/channelmarker.h
include-gpl/dsp/complex.h

View File

@ -18,6 +18,7 @@
#define INCLUDE_GPL_DSP_AFSQUELCH_H_
#include "dsp/dsptypes.h"
#include "dsp/movingaverage.h"
/** AFSquelch: AF squelch class based on the Modified Goertzel
* algorithm.
@ -28,26 +29,24 @@ public:
AFSquelch();
// allows user defined tone pair
AFSquelch(unsigned int nbTones,
const Real *tones,
int samplesAttack = 0,
int samplesDecay = 0);
const Real *tones);
virtual ~AFSquelch();
// setup the basic parameters and coefficients
void setCoefficients(
int N, // the algorithm "block" size
int SampleRate, // input signal sample rate
int _samplesAttack, // number of results before squelch opens
int _samplesDecay); // number of results keeping squelch open
int N, //!< the algorithm "block" size
unsigned int nbAvg, //!< averaging size
int SampleRate, //!< input signal sample rate
int _samplesAttack, //!< number of results before squelch opens
int _samplesDecay); //!< number of results keeping squelch open
// set the detection threshold
void setThreshold(double _threshold) {
m_threshold = _threshold;
}
void setThreshold(double _threshold);
// analyze a sample set and optionally filter
// the tone frequencies.
bool analyze(Real *sample); // input signal sample
bool analyze(Real sample); // input signal sample
bool evaluate(); // evaluate result
// get the tone set
const Real *getToneSet() const
@ -64,9 +63,9 @@ public:
protected:
void feedback(Real sample);
void feedForward();
void evaluate();
private:
unsigned int m_nbAvg; //!< number of power samples taken for moving average
int m_N;
int m_sampleRate;
int m_samplesProcessed;
@ -84,6 +83,7 @@ private:
double *m_u0;
double *m_u1;
double *m_power;
std::vector<MovingAverage<Real> > m_movingAverages;
};

View File

@ -51,6 +51,11 @@ public:
return m_sum / (float) m_history.size();
}
Type sum() const
{
return m_sum;
}
protected:
std::vector<Type> m_history;
Type m_sum;

View File

@ -33,7 +33,7 @@ MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureNFMDemod, Message)
NFMDemod::NFMDemod() :
m_ctcssIndex(0),
m_sampleCount(0),
m_squelchOpen(false),
m_afSquelch(2, afSqTones),
m_audioFifo(4, 48000),
m_settingsMutex(QMutex::Recursive)
{
@ -53,11 +53,11 @@ NFMDemod::NFMDemod() :
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_movingAverage.resize(240, 0);
m_agcLevel = 1.0;
m_AGC.resize(240, m_agcLevel);
m_ctcssDetector.setCoefficients(3000, 6000.0); // 0.5s / 2 Hz resolution
m_afSquelch.setCoefficients(24, 1200, 48000.0, 4, 0); // 4000 Hz span, 250us
DSPEngine::instance()->addAudioSink(&m_audioFifo);
}
@ -114,6 +114,7 @@ Real angleDist(Real a, Real b)
void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
bool squelchOpen;
Complex ci;
m_settingsMutex.lock();
@ -169,7 +170,13 @@ void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVecto
// AF processing
if (m_AGC.getAverage() > m_squelchLevel)
if (m_afSquelch.analyze(demod))
{
squelchOpen = m_afSquelch.evaluate();
}
if (squelchOpen)
//if (m_AGC.getAverage() > m_squelchLevel)
{
if (m_running.m_ctcssOn)
{
@ -343,10 +350,9 @@ void NFMDemod::apply()
if (m_config.m_squelch != m_running.m_squelch)
{
// input is a power level in dB
// m_squelchLevel = pow(10.0, m_config.m_squelch / 10.0);
m_squelchLevel = pow(10.0, m_config.m_squelch / 20.0); // to magnitude
m_squelchLevel = pow(10.0, m_config.m_squelch / 10.0);
//m_squelchLevel *= m_squelchLevel;
m_afSquelch.setThreshold(m_squelchLevel);
}
m_running.m_inputSampleRate = m_config.m_inputSampleRate;

View File

@ -25,7 +25,7 @@
#include "dsp/interpolator.h"
#include "dsp/lowpass.h"
#include "dsp/bandpass.h"
#include "dsp/movingaverage.h"
#include "dsp/afsquelch.h"
#include "dsp/agc.h"
#include "dsp/ctcssdetector.h"
#include "dsp/afsquelch.h"
@ -155,14 +155,12 @@ private:
int m_sampleCount;
double m_squelchLevel;
//int m_squelchState;
bool m_squelchOpen;
Real m_lastArgument;
Complex m_m1Sample;
Complex m_m2Sample;
MovingAverage<Real> m_movingAverage;
MagAGC m_AGC;
AFSquelch m_afSquelch;
Real m_agcLevel; // AGC will aim to this level
Real m_agcFloor; // AGC will not go below this level

View File

@ -131,7 +131,7 @@
<item row="4" column="4">
<widget class="QSlider" name="squelch">
<property name="minimum">
<number>-100</number>
<number>-30</number>
</property>
<property name="maximum">
<number>0</number>
@ -140,7 +140,7 @@
<number>1</number>
</property>
<property name="value">
<number>-40</number>
<number>-20</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>

View File

@ -131,7 +131,7 @@ bool AirspyInput::start(int device)
stop();
}
if (!m_sampleFifo.setSize(96000 * 4))
if (!m_sampleFifo.setSize(1<<19))
{
qCritical("AirspyInput::start: could not allocate SampleFifo");
return false;

View File

@ -18,6 +18,7 @@
#include "dsp/afsquelch.h"
AFSquelch::AFSquelch() :
m_nbAvg(128),
m_N(0),
m_sampleRate(0),
m_samplesProcessed(0),
@ -36,20 +37,22 @@ AFSquelch::AFSquelch() :
m_u0 = new double[m_nTones];
m_u1 = new double[m_nTones];
m_power = new double[m_nTones];
m_movingAverages.resize(m_nTones, MovingAverage<Real>(m_nbAvg, 0.0));
m_toneSet[0] = 2000.0;
m_toneSet[1] = 10000.0;
}
AFSquelch::AFSquelch(unsigned int nbTones, const Real *tones, int samplesAttack, int samplesDecay) :
AFSquelch::AFSquelch(unsigned int nbTones, const Real *tones) :
m_N(0),
m_nbAvg(0),
m_sampleRate(0),
m_samplesProcessed(0),
m_maxPowerIndex(0),
m_nTones(nbTones),
m_samplesAttack(samplesAttack),
m_samplesAttack(0),
m_attackCount(0),
m_samplesDecay(samplesDecay),
m_samplesDecay(0),
m_decayCount(0),
m_isOpen(false),
m_threshold(0.0)
@ -79,12 +82,14 @@ AFSquelch::~AFSquelch()
}
void AFSquelch::setCoefficients(int _N, int _samplerate, int _samplesAttack, int _samplesDecay )
void AFSquelch::setCoefficients(int _N, unsigned int nbAvg, int _samplerate, int _samplesAttack, int _samplesDecay )
{
m_N = _N; // save the basic parameters for use during analysis
m_nbAvg = nbAvg;
m_sampleRate = _samplerate;
m_samplesAttack = _samplesAttack;
m_samplesDecay = _samplesDecay;
m_movingAverages.resize(m_nTones, MovingAverage<Real>(m_nbAvg, 0.0));
// for each of the frequencies (tones) of interest calculate
// k and the associated filter coefficient as per the Goertzel
@ -102,10 +107,10 @@ void AFSquelch::setCoefficients(int _N, int _samplerate, int _samplesAttack, int
// Analyze an input signal
bool AFSquelch::analyze(Real *sample)
bool AFSquelch::analyze(Real sample)
{
feedback(*sample); // Goertzel feedback
feedback(sample); // Goertzel feedback
m_samplesProcessed += 1;
if (m_samplesProcessed == m_N) // completed a block of N
@ -140,6 +145,7 @@ void AFSquelch::feedForward()
for (int j = 0; j < m_nTones; ++j)
{
m_power[j] = (m_u0[j] * m_u0[j]) + (m_u1[j] * m_u1[j]) - (m_coef[j] * m_u0[j] * m_u1[j]);
m_movingAverages[j].feed(m_power[j]);
m_u0[j] = m_u1[j] = 0.0; // reset for next block.
}
@ -152,6 +158,7 @@ void AFSquelch::reset()
for (int j = 0; j < m_nTones; ++j)
{
m_power[j] = m_u0[j] = m_u1[j] = 0.0; // reset
m_movingAverages[j].fill(0.0);
}
m_samplesProcessed = 0;
@ -160,7 +167,7 @@ void AFSquelch::reset()
}
void AFSquelch::evaluate()
bool AFSquelch::evaluate()
{
double maxPower = 0.0;
double minPower;
@ -168,8 +175,9 @@ void AFSquelch::evaluate()
for (int j = 0; j < m_nTones; ++j)
{
if (m_power[j] > maxPower) {
maxPower = m_power[j];
if (m_movingAverages[j].sum() > maxPower)
{
maxPower = m_movingAverages[j].sum();
maxIndex = j;
}
}
@ -178,14 +186,14 @@ void AFSquelch::evaluate()
for (int j = 0; j < m_nTones; ++j)
{
if (m_power[j] < minPower) {
minPower = m_power[j];
if (m_movingAverages[j].sum() < minPower) {
minPower = m_movingAverages[j].sum();
minIndex = j;
}
}
// principle is to open if power is uneven because noise gives even power
bool open = ((maxPower - minPower) > m_threshold); // && (minIndex > maxIndex);
bool open = (minPower/maxPower < m_threshold) && (minIndex > maxIndex);
if (open)
{
@ -213,4 +221,12 @@ void AFSquelch::evaluate()
m_attackCount = 0;
}
}
return m_isOpen;
}
void AFSquelch::setThreshold(double threshold)
{
qDebug("AFSquelch::setThreshold: threshold: %f", threshold);
m_threshold = threshold;
}