ATV demod: full refactoring

This commit is contained in:
f4exb 2019-12-01 15:13:35 +01:00
parent 3fde47ff70
commit 0873672a74
18 changed files with 1906 additions and 2014 deletions

View File

@ -1,8 +1,11 @@
project(atv)
set(atv_SOURCES
atvdemod.cpp
atvdemod.cpp
atvdemodbaseband.cpp
atvdemodsink.cpp
atvdemodsettings.cpp
atvdemodreport.cpp
atvdemodwebapiadapter.cpp
atvdemodgui.cpp
atvdemodplugin.cpp
@ -10,8 +13,11 @@ set(atv_SOURCES
)
set(atv_HEADERS
atvdemod.h
atvdemod.h
atvdemodbaseband.h
atvdemodsink.h
atvdemodsettings.h
atvdemodreport.h
atvdemodwebapiadapter.h
atvdemodgui.h
atvdemodplugin.h

View File

@ -18,766 +18,138 @@
#include <QTime>
#include <QDebug>
#include <QThread>
#include <stdio.h>
#include <complex.h>
#include "audio/audiooutput.h"
#include "dsp/dspengine.h"
#include "dsp/downchannelizer.h"
#include "dsp/threadedbasebandsamplesink.h"
#include "device/deviceapi.h"
#include "atvdemod.h"
MESSAGE_CLASS_DEFINITION(ATVDemod::MsgConfigureATVDemod, Message)
MESSAGE_CLASS_DEFINITION(ATVDemod::MsgConfigureRFATVDemod, Message)
MESSAGE_CLASS_DEFINITION(ATVDemod::MsgReportEffectiveSampleRate, Message)
MESSAGE_CLASS_DEFINITION(ATVDemod::MsgConfigureChannelizer, Message)
MESSAGE_CLASS_DEFINITION(ATVDemod::MsgReportChannelSampleRateChanged, Message)
const QString ATVDemod::m_channelIdURI = "sdrangel.channel.demodatv";
const QString ATVDemod::m_channelId = "ATVDemod";
const int ATVDemod::m_ssbFftLen = 1024;
ATVDemod::ATVDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_scopeSink(0),
m_registeredTVScreen(0),
m_intNumberSamplePerTop(0),
m_intImageIndex(0),
m_intSynchroPoints(0),
m_blnSynchroDetected(false),
m_blnVerticalSynchroDetected(false),
m_fltAmpLineAverage(0.0f),
m_fltEffMin(2000000000.0f),
m_fltEffMax(-2000000000.0f),
m_fltAmpMin(-2000000000.0f),
m_fltAmpMax(2000000000.0f),
m_fltAmpDelta(1.0),
m_intColIndex(0),
m_intSampleIndex(0),
m_intRowIndex(0),
m_intLineIndex(0),
m_objAvgColIndex(3),
m_bfoPLL(200/1000000, 100/1000000, 0.01),
m_bfoFilter(200.0, 1000000.0, 0.9),
m_interpolatorDistance(1.0f),
m_interpolatorDistanceRemain(0.0f),
m_DSBFilter(0),
m_DSBFilterBuffer(0),
m_DSBFilterBufferIndex(0),
m_objSettingsMutex(QMutex::Recursive)
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
qDebug("ATVDemod::ATVDemod");
setObjectName(m_channelId);
//*************** ATV PARAMETERS ***************
//m_intNumberSamplePerLine=0;
m_intSynchroPoints=0;
m_intNumberOfLines=0;
m_interleaved = true;
m_thread = new QThread(this);
m_basebandSink = new ATVDemodBaseband();
m_basebandSink->moveToThread(m_thread);
m_DSBFilter = new fftfilt((2.0f * m_rfConfig.m_fltRFBandwidth) / 1000000, 2 * m_ssbFftLen); // arbitrary 1 MS/s sample rate
m_DSBFilterBuffer = new Complex[m_ssbFftLen];
memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen));
applySettings(m_settings, true);
memset((void*)m_fltBufferI,0,6*sizeof(float));
memset((void*)m_fltBufferQ,0,6*sizeof(float));
m_objPhaseDiscri.setFMScaling(1.0f);
applyStandard();
m_channelizer = new DownChannelizer(this);
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
m_deviceAPI->addChannelSink(m_threadedChannelizer);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
connect(m_channelizer, SIGNAL(inputSampleRateChanged()), this, SLOT(channelSampleRateChanged()));
}
ATVDemod::~ATVDemod()
{
qDebug("ATVDemod::~ATVDemod");
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(m_threadedChannelizer);
delete m_threadedChannelizer;
delete m_channelizer;
delete m_DSBFilter;
delete m_DSBFilterBuffer;
m_deviceAPI->removeChannelSink(this);
delete m_basebandSink;
delete m_thread;
}
void ATVDemod::setTVScreen(TVScreen *objScreen)
void ATVDemod::start()
{
m_registeredTVScreen = objScreen;
qDebug("ATVDemod::start");
if (m_basebandSampleRate != 0) {
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
}
m_basebandSink->reset();
m_thread->start();
}
void ATVDemod::configure(
MessageQueue* objMessageQueue,
float fltLineDurationUs,
float fltTopDurationUs,
float fltFramePerS,
ATVStd enmATVStandard,
int intNumberOfLines,
float fltRatioOfRowsToDisplay,
float fltVoltLevelSynchroTop,
float fltVoltLevelSynchroBlack,
bool blnHSync,
bool blnVSync,
bool blnInvertVideo,
int intVideoTabIndex)
void ATVDemod::stop()
{
Message* msgCmd = MsgConfigureATVDemod::create(
fltLineDurationUs,
fltTopDurationUs,
fltFramePerS,
enmATVStandard,
intNumberOfLines,
fltRatioOfRowsToDisplay,
fltVoltLevelSynchroTop,
fltVoltLevelSynchroBlack,
blnHSync,
blnVSync,
blnInvertVideo,
intVideoTabIndex);
objMessageQueue->push(msgCmd);
}
void ATVDemod::configureRF(
MessageQueue* objMessageQueue,
int64_t frequencyOffset,
ATVModulation enmModulation,
float fltRFBandwidth,
float fltRFOppBandwidth,
bool blnFFTFiltering,
bool blnDecimatorEnable,
float fltBFOFrequency,
float fmDeviation)
{
Message* msgCmd = MsgConfigureRFATVDemod::create(
frequencyOffset,
enmModulation,
fltRFBandwidth,
fltRFOppBandwidth,
blnFFTFiltering,
blnDecimatorEnable,
fltBFOFrequency,
fmDeviation);
objMessageQueue->push(msgCmd);
qDebug("ATVDemod::stop");
m_thread->exit();
m_thread->wait();
}
void ATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
float fltI;
float fltQ;
Complex ci;
//********** Let's rock and roll buddy ! **********
m_objSettingsMutex.lock();
//********** Accessing ATV Screen context **********
#ifdef EXTENDED_DIRECT_SAMPLE
qint16 * ptrBufferToRelease = 0;
qint16 * ptrBuffer;
qint32 intLen;
//********** Reading direct samples **********
SampleVector::const_iterator it = begin;
intLen = it->intLen;
ptrBuffer = it->ptrBuffer;
ptrBufferToRelease = ptrBuffer;
++it;
for(qint32 intInd=0; intInd<intLen-1; intInd +=2)
{
fltI= ((qint32) (*ptrBuffer)) << 4;
ptrBuffer ++;
fltQ= ((qint32) (*ptrBuffer)) << 4;
ptrBuffer ++;
#else
for (SampleVector::const_iterator it = begin; it != end; ++it /* ++it **/)
{
fltI = it->real();
fltQ = it->imag();
#endif
Complex c(fltI, fltQ);
if (m_rfRunning.m_intFrequencyOffset != 0)
{
c *= m_nco.nextIQ();
}
if (m_rfRunning.m_blndecimatorEnable)
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
demod(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else
{
demod(c);
}
}
if ((m_running.m_intVideoTabIndex == 1) && (m_scopeSink != 0)) // do only if scope tab is selected and scope is available
{
m_scopeSink->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), false); // m_ssb = positive only
m_scopeSampleBuffer.clear();
}
#ifdef EXTENDED_DIRECT_SAMPLE
if (ptrBufferToRelease != 0)
{
delete ptrBufferToRelease;
}
#endif
m_objSettingsMutex.unlock();
}
void ATVDemod::demod(Complex& c)
{
float fltDivSynchroBlack = 1.0f - m_running.m_fltVoltLevelSynchroBlack;
float fltNormI;
float fltNormQ;
float fltNorm;
float fltVal;
int intVal;
//********** FFT filtering **********
if (m_rfRunning.m_blnFFTFiltering)
{
int n_out;
fftfilt::cmplx *filtered;
n_out = m_DSBFilter->runAsym(c, &filtered, m_rfRunning.m_enmModulation != ATV_LSB); // all usb except explicitely lsb
if (n_out > 0)
{
memcpy((void *) m_DSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
m_DSBFilterBufferIndex = 0;
}
m_DSBFilterBufferIndex++;
}
//********** demodulation **********
const float& fltI = m_rfRunning.m_blnFFTFiltering ? m_DSBFilterBuffer[m_DSBFilterBufferIndex-1].real() : c.real();
const float& fltQ = m_rfRunning.m_blnFFTFiltering ? m_DSBFilterBuffer[m_DSBFilterBufferIndex-1].imag() : c.imag();
double magSq;
if ((m_rfRunning.m_enmModulation == ATV_FM1) || (m_rfRunning.m_enmModulation == ATV_FM2))
{
//Amplitude FM
magSq = fltI*fltI + fltQ*fltQ;
m_objMagSqAverage(magSq);
fltNorm = sqrt(magSq);
fltNormI= fltI/fltNorm;
fltNormQ= fltQ/fltNorm;
//-2 > 2 : 0 -> 1 volt
//0->0.3 synchro 0.3->1 image
if (m_rfRunning.m_enmModulation == ATV_FM1)
{
//YDiff Cd
fltVal = m_fltBufferI[0]*(fltNormQ - m_fltBufferQ[1]);
fltVal -= m_fltBufferQ[0]*(fltNormI - m_fltBufferI[1]);
fltVal += 2.0f;
fltVal /= 4.0f;
}
else
{
//YDiff Folded
fltVal = m_fltBufferI[2]*((m_fltBufferQ[5]-fltNormQ)/16.0f + m_fltBufferQ[1] - m_fltBufferQ[3]);
fltVal -= m_fltBufferQ[2]*((m_fltBufferI[5]-fltNormI)/16.0f + m_fltBufferI[1] - m_fltBufferI[3]);
fltVal += 2.125f;
fltVal /= 4.25f;
m_fltBufferI[5]=m_fltBufferI[4];
m_fltBufferQ[5]=m_fltBufferQ[4];
m_fltBufferI[4]=m_fltBufferI[3];
m_fltBufferQ[4]=m_fltBufferQ[3];
m_fltBufferI[3]=m_fltBufferI[2];
m_fltBufferQ[3]=m_fltBufferQ[2];
m_fltBufferI[2]=m_fltBufferI[1];
m_fltBufferQ[2]=m_fltBufferQ[1];
}
m_fltBufferI[1]=m_fltBufferI[0];
m_fltBufferQ[1]=m_fltBufferQ[0];
m_fltBufferI[0]=fltNormI;
m_fltBufferQ[0]=fltNormQ;
if (m_rfRunning.m_fmDeviation != 1.0f)
{
fltVal = ((fltVal - 0.5f) / m_rfRunning.m_fmDeviation) + 0.5f;
}
}
else if (m_rfRunning.m_enmModulation == ATV_AM)
{
//Amplitude AM
magSq = fltI*fltI + fltQ*fltQ;
m_objMagSqAverage(magSq);
fltNorm = sqrt(magSq);
fltVal = fltNorm / SDR_RX_SCALEF;
//********** Mini and Maxi Amplitude tracking **********
if(fltVal<m_fltEffMin)
{
m_fltEffMin=fltVal;
}
if(fltVal>m_fltEffMax)
{
m_fltEffMax=fltVal;
}
//Normalisation
fltVal -= m_fltAmpMin;
fltVal /=m_fltAmpDelta;
}
else if ((m_rfRunning.m_enmModulation == ATV_USB) || (m_rfRunning.m_enmModulation == ATV_LSB))
{
magSq = fltI*fltI + fltQ*fltQ;
m_objMagSqAverage(magSq);
fltNorm = sqrt(magSq);
Real bfoValues[2];
float fltFiltered = m_bfoFilter.run(fltI);
m_bfoPLL.process(fltFiltered, bfoValues);
// do the mix
float mixI = fltI * bfoValues[0] - fltQ * bfoValues[1];
float mixQ = fltI * bfoValues[1] + fltQ * bfoValues[0];
if (m_rfRunning.m_enmModulation == ATV_USB) {
fltVal = (mixI + mixQ);
} else {
fltVal = (mixI - mixQ);
}
//********** Mini and Maxi Amplitude tracking **********
if(fltVal<m_fltEffMin)
{
m_fltEffMin=fltVal;
}
if(fltVal>m_fltEffMax)
{
m_fltEffMax=fltVal;
}
//Normalisation
fltVal -= m_fltAmpMin;
fltVal /=m_fltAmpDelta;
}
else if (m_rfRunning.m_enmModulation == ATV_FM3)
{
float rawDeviation;
fltVal = m_objPhaseDiscri.phaseDiscriminatorDelta(c, magSq, rawDeviation) + 0.5f;
//fltVal = fltVal < 0.0f ? 0.0f : fltVal > 1.0f ? 1.0f : fltVal;
m_objMagSqAverage(magSq);
fltNorm = sqrt(magSq);
}
else
{
magSq = fltI*fltI + fltQ*fltQ;
m_objMagSqAverage(magSq);
fltNorm = sqrt(magSq);
fltVal = 0.0f;
}
fltVal = m_running.m_blnInvertVideo ? 1.0f - fltVal : fltVal;
fltVal = (fltVal < -1.0f) ? -1.0f : (fltVal > 1.0f) ? 1.0f : fltVal;
if ((m_running.m_intVideoTabIndex == 1) && (m_scopeSink != 0)) { // feed scope buffer only if scope is present and visible
m_scopeSampleBuffer.push_back(Sample(fltVal*SDR_RX_SCALEF, 0.0f));
}
m_fltAmpLineAverage += fltVal;
//********** gray level **********
//-0.3 -> 0.7
intVal = (int) 255.0*(fltVal - m_running.m_fltVoltLevelSynchroBlack) / fltDivSynchroBlack;
//0 -> 255
if(intVal<0)
{
intVal=0;
}
else if(intVal>255)
{
intVal=255;
}
//********** process video sample **********
if (m_registeredTVScreen) // can process only if the screen is available (set via the GUI)
{
if (m_running.m_enmATVStandard == ATVStdHSkip) {
processHSkip(fltVal, intVal);
} else {
processClassic(fltVal, intVal);
}
}
}
void ATVDemod::start()
{
//m_objTimer.start();
}
void ATVDemod::stop()
{
m_basebandSink->feed(begin, end);
}
bool ATVDemod::handleMessage(const Message& cmd)
{
qDebug() << "ATVDemod::handleMessage";
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
if (MsgConfigureATVDemod::match(cmd))
{
DownChannelizer::MsgChannelizerNotification& objNotif = (DownChannelizer::MsgChannelizerNotification&) cmd;
m_config.m_intSampleRate = objNotif.getSampleRate();
m_rfConfig.m_intFrequencyOffset = objNotif.getFrequencyOffset();
qDebug() << "ATVDemod::handleMessage: MsgChannelizerNotification:"
<< " m_intSampleRate: " << m_config.m_intSampleRate
<< " m_intFrequencyOffset: " << m_rfConfig.m_intFrequencyOffset;
applySettings();
MsgConfigureATVDemod& cfg = (MsgConfigureATVDemod&) cmd;
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgConfigureChannelizer::match(cmd))
else if (DSPSignalNotification::match(cmd))
{
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate(); // store for init at start
qDebug() << "ATVDemod::handleMessage: DSPSignalNotification" << m_basebandSampleRate;
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
m_channelizer->getInputSampleRate(),
cfg.getCenterFrequency());
// Forward to the sink
DSPSignalNotification* notifToSink = new DSPSignalNotification(notif); // make a copy
m_basebandSink->getInputMessageQueue()->push(notifToSink);
qDebug() << "ATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate()
<< " centerFrequency: " << cfg.getCenterFrequency();
// Forward to GUI
if (getMessageQueueToGUI())
{
DSPSignalNotification *notifToGUI = new DSPSignalNotification(notif);
getMessageQueueToGUI()->push(notifToGUI);
}
return true;
}
else if (MsgConfigureATVDemod::match(cmd))
{
MsgConfigureATVDemod& objCfg = (MsgConfigureATVDemod&) cmd;
m_config = objCfg.m_objMsgConfig;
qDebug() << "ATVDemod::handleMessage: MsgConfigureATVDemod:"
<< " m_fltVoltLevelSynchroBlack:" << m_config.m_fltVoltLevelSynchroBlack
<< " m_fltVoltLevelSynchroTop:" << m_config.m_fltVoltLevelSynchroTop
<< " m_fltFramePerS:" << m_config.m_fltFramePerS
<< " m_fltLineDurationUs:" << m_config.m_fltLineDuration
<< " m_fltRatioOfRowsToDisplay:" << m_config.m_fltRatioOfRowsToDisplay
<< " m_fltTopDurationUs:" << m_config.m_fltTopDuration
<< " m_blnHSync:" << m_config.m_blnHSync
<< " m_blnVSync:" << m_config.m_blnVSync;
applySettings();
return true;
}
else if (MsgConfigureRFATVDemod::match(cmd))
{
MsgConfigureRFATVDemod& objCfg = (MsgConfigureRFATVDemod&) cmd;
m_rfConfig = objCfg.m_objMsgConfig;
qDebug() << "ATVDemod::handleMessage: MsgConfigureRFATVDemod:"
<< " m_intFrequencyOffset:" << m_rfConfig.m_intFrequencyOffset
<< " m_enmModulation:" << m_rfConfig.m_enmModulation
<< " m_fltRFBandwidth:" << m_rfConfig.m_fltRFBandwidth
<< " m_fltRFOppBandwidth:" << m_rfConfig.m_fltRFOppBandwidth
<< " m_blnFFTFiltering:" << m_rfConfig.m_blnFFTFiltering
<< " m_blnDecimatorEnable:" << m_rfConfig.m_blndecimatorEnable
<< " m_fltBFOFrequency:" << m_rfConfig.m_fltBFOFrequency
<< " m_fmDeviation:" << m_rfConfig.m_fmDeviation;
applySettings();
return true;
}
else
{
if (m_scopeSink != 0)
{
return m_scopeSink->handleMessage(cmd);
}
else
{
return false;
}
}
}
void ATVDemod::applySettings()
{
if (m_config.m_intSampleRate == 0)
{
return;
}
bool forwardSampleRateChange = false;
if((m_rfConfig.m_intFrequencyOffset != m_rfRunning.m_intFrequencyOffset)
|| (m_rfConfig.m_enmModulation != m_rfRunning.m_enmModulation)
|| (m_config.m_intSampleRate != m_running.m_intSampleRate))
{
m_nco.setFreq(-m_rfConfig.m_intFrequencyOffset, m_config.m_intSampleRate);
}
if ((m_config.m_intSampleRate != m_running.m_intSampleRate)
|| (m_rfConfig.m_fltRFBandwidth != m_rfRunning.m_fltRFBandwidth)
|| (m_config.m_fltFramePerS != m_running.m_fltFramePerS)
|| (m_config.m_intNumberOfLines != m_running.m_intNumberOfLines))
{
m_objSettingsMutex.lock();
int linesPerSecond = m_config.m_intNumberOfLines * m_config.m_fltFramePerS;
int maxPoints = m_config.m_intSampleRate / linesPerSecond;
int i = maxPoints;
for (; i > 0; i--)
{
if ((i * linesPerSecond) % 10 == 0)
break;
}
int nbPointsPerRateUnit = i == 0 ? maxPoints : i;
m_configPrivate.m_intTVSampleRate = nbPointsPerRateUnit * linesPerSecond;
if (m_configPrivate.m_intTVSampleRate > 0)
{
m_interpolatorDistance = (Real) m_configPrivate.m_intTVSampleRate / (Real) m_config.m_intSampleRate;
}
else
{
m_configPrivate.m_intTVSampleRate = m_config.m_intSampleRate;
m_interpolatorDistance = 1.0f;
}
m_interpolatorDistanceRemain = 0;
m_interpolator.create(24,
m_configPrivate.m_intTVSampleRate,
m_rfConfig.m_fltRFBandwidth / getRFBandwidthDivisor(m_rfConfig.m_enmModulation),
3.0);
m_objSettingsMutex.unlock();
}
if((m_config.m_fltFramePerS != m_running.m_fltFramePerS)
|| (m_config.m_fltLineDuration != m_running.m_fltLineDuration)
|| (m_config.m_intSampleRate != m_running.m_intSampleRate)
|| (m_config.m_fltTopDuration != m_running.m_fltTopDuration)
|| (m_config.m_fltRatioOfRowsToDisplay != m_running.m_fltRatioOfRowsToDisplay)
|| (m_config.m_enmATVStandard != m_running.m_enmATVStandard)
|| (m_config.m_intNumberOfLines != m_running.m_intNumberOfLines))
{
m_objSettingsMutex.lock();
m_intNumberOfLines = m_config.m_intNumberOfLines;
applyStandard();
m_configPrivate.m_intNumberSamplePerLine = (int) (m_config.m_fltLineDuration * m_config.m_intSampleRate);
m_intNumberSamplePerTop = (int) (m_config.m_fltTopDuration * m_config.m_intSampleRate);
if (m_registeredTVScreen)
{
//m_registeredTVScreen->setRenderImmediate(!(m_config.m_fltFramePerS > 25.0f));
m_registeredTVScreen->resizeTVScreen(
m_configPrivate.m_intNumberSamplePerLine - m_intNumberSamplePerLineSignals,
m_intNumberOfLines - m_intNumberOfBlackLines);
}
qDebug() << "ATVDemod::applySettings:"
<< " m_fltLineDuration: " << m_config.m_fltLineDuration
<< " m_fltFramePerS: " << m_config.m_fltFramePerS
<< " m_intNumberOfLines: " << m_intNumberOfLines
<< " m_intNumberSamplePerLine: " << m_configPrivate.m_intNumberSamplePerLine
<< " m_intNumberOfBlackLines: " << m_intNumberOfBlackLines;
m_intImageIndex = 0;
m_intColIndex=0;
m_intRowIndex=0;
m_objSettingsMutex.unlock();
}
if ((m_configPrivate.m_intTVSampleRate != m_runningPrivate.m_intTVSampleRate)
|| (m_configPrivate.m_intNumberSamplePerLine != m_runningPrivate.m_intNumberSamplePerLine)
|| (m_config.m_intSampleRate != m_running.m_intSampleRate)
|| (m_rfConfig.m_blndecimatorEnable != m_rfRunning.m_blndecimatorEnable))
{
forwardSampleRateChange = true;
}
if ((m_configPrivate.m_intTVSampleRate != m_runningPrivate.m_intTVSampleRate)
|| (m_rfConfig.m_fltRFBandwidth != m_rfRunning.m_fltRFBandwidth)
|| (m_rfConfig.m_fltRFOppBandwidth != m_rfRunning.m_fltRFOppBandwidth))
{
m_objSettingsMutex.lock();
m_DSBFilter->create_asym_filter(m_rfConfig.m_fltRFOppBandwidth / m_configPrivate.m_intTVSampleRate,
m_rfConfig.m_fltRFBandwidth / m_configPrivate.m_intTVSampleRate);
memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen));
m_DSBFilterBufferIndex = 0;
m_objSettingsMutex.unlock();
}
if ((m_configPrivate.m_intTVSampleRate != m_runningPrivate.m_intTVSampleRate)
|| (m_rfConfig.m_fltBFOFrequency != m_rfRunning.m_fltBFOFrequency))
{
m_bfoPLL.configure(m_rfConfig.m_fltBFOFrequency / m_configPrivate.m_intTVSampleRate,
100.0 / m_configPrivate.m_intTVSampleRate,
0.01);
m_bfoFilter.setFrequencies(m_rfConfig.m_fltBFOFrequency, m_configPrivate.m_intTVSampleRate);
}
if (m_rfConfig.m_fmDeviation != m_rfRunning.m_fmDeviation)
{
m_objPhaseDiscri.setFMScaling(1.0f / m_rfConfig.m_fmDeviation);
}
m_running = m_config;
m_rfRunning = m_rfConfig;
m_runningPrivate = m_configPrivate;
if (forwardSampleRateChange && getMessageQueueToGUI())
{
int sampleRate = m_rfRunning.m_blndecimatorEnable ? m_runningPrivate.m_intTVSampleRate : m_running.m_intSampleRate;
MsgReportEffectiveSampleRate *report;
report = MsgReportEffectiveSampleRate::create(sampleRate, m_runningPrivate.m_intNumberSamplePerLine);
getMessageQueueToGUI()->push(report);
}
}
void ATVDemod::applyStandard()
{
switch(m_config.m_enmATVStandard)
{
case ATVStdHSkip:
// what is left in a line for the image
m_intNumberOfSyncLines = 0;
m_intNumberOfBlackLines = 0;
m_intNumberOfEqLines = 0; // not applicable
m_interleaved = false;
break;
case ATVStdShort:
// what is left in a line for the image
m_intNumberOfSyncLines = 4;
m_intNumberOfBlackLines = 4;
m_intNumberOfEqLines = 0;
m_interleaved = false;
break;
case ATVStdShortInterleaved:
// what is left in a line for the image
m_intNumberOfSyncLines = 4;
m_intNumberOfBlackLines = 4;
m_intNumberOfEqLines = 0;
m_interleaved = true;
break;
case ATVStd405: // Follows loosely the 405 lines standard
// what is left in a ine for the image
m_intNumberOfSyncLines = 24; // (15+7)*2 - 20
m_intNumberOfBlackLines = 28; // above + 4
m_intNumberOfEqLines = 3;
m_interleaved = true;
break;
case ATVStdPAL525: // Follows PAL-M standard
// what is left in a 64/1.008 us line for the image
m_intNumberOfSyncLines = 40; // (15+15)*2 - 20
m_intNumberOfBlackLines = 44; // above + 4
m_intNumberOfEqLines = 3;
m_interleaved = true;
break;
case ATVStdPAL625: // Follows PAL-B/G/H standard
default:
// what is left in a 64 us line for the image
m_intNumberOfSyncLines = 44; // (15+17)*2 - 20
m_intNumberOfBlackLines = 48; // above + 4
m_intNumberOfEqLines = 3;
m_interleaved = true;
}
// for now all standards apply this
m_intNumberSamplePerLineSignals = (int) ((12.0f/64.0f)*m_config.m_fltLineDuration * m_config.m_intSampleRate); // 12.0 = 7.3 + 4.7
m_intNumberSaplesPerHSync = (int) ((9.6f/64.0f)*m_config.m_fltLineDuration * m_config.m_intSampleRate); // 9.4 = 4.7 + 4.7
}
int ATVDemod::getSampleRate()
{
return m_running.m_intSampleRate;
}
int ATVDemod::getEffectiveSampleRate()
{
return m_rfRunning.m_blndecimatorEnable ? m_runningPrivate.m_intTVSampleRate : m_running.m_intSampleRate;
}
bool ATVDemod::getBFOLocked()
{
if ((m_rfRunning.m_enmModulation == ATV_USB) || (m_rfRunning.m_enmModulation == ATV_LSB))
{
return m_bfoPLL.locked();
}
else
{
return false;
}
}
float ATVDemod::getRFBandwidthDivisor(ATVModulation modulation)
void ATVDemod::applySettings(const ATVDemodSettings& settings, bool force)
{
switch(modulation)
{
case ATV_USB:
case ATV_LSB:
return 1.05f;
break;
case ATV_FM1:
case ATV_FM2:
case ATV_AM:
default:
return 2.2f;
}
}
qDebug() << "ATVDemod::applySettings:"
<< "m_inputFrequencyOffset:" << settings.m_inputFrequencyOffset
<< "m_forceDecimator:" << settings.m_forceDecimator
<< "m_bfoFrequency:" << settings.m_bfoFrequency
<< "m_atvModulation:" << settings.m_atvModulation
<< "m_fmDeviation:" << settings.m_fmDeviation
<< "m_fftFiltering:" << settings.m_fftFiltering
<< "m_fftOppBandwidth:" << settings.m_fftOppBandwidth
<< "m_fftBandwidth:" << settings.m_fftBandwidth
<< "m_nbLines:" << settings.m_nbLines
<< "m_fps:" << settings.m_fps
<< "m_atvStd:" << settings.m_atvStd
<< "m_hSync:" << settings.m_hSync
<< "m_vSync:" << settings.m_vSync
<< "m_invertVideo:" << settings.m_invertVideo
<< "m_halfFrames:" << settings.m_halfFrames
<< "m_levelSynchroTop:" << settings.m_levelSynchroTop
<< "m_levelBlack:" << settings.m_levelBlack
<< "m_lineTimeFactor:" << settings.m_lineTimeFactor
<< "m_topTimeFactor:" << settings.m_topTimeFactor
<< "m_rgbColor:" << settings.m_rgbColor
<< "m_title:" << settings.m_title
<< "m_udpAddress:" << settings.m_udpAddress
<< "m_udpPort:" << settings.m_udpPort
<< "force:" << force;
void ATVDemod::channelSampleRateChanged()
{
qDebug("ATVDemod::channelSampleRateChanged");
if (getMessageQueueToGUI())
{
MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(m_channelizer->getInputSampleRate());
getMessageQueueToGUI()->push(msg);
}
}
ATVDemodBaseband::MsgConfigureATVDemodBaseband *msg = ATVDemodBaseband::MsgConfigureATVDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
m_settings = settings;
}

View File

@ -19,7 +19,6 @@
#ifndef INCLUDE_ATVDEMOD_H
#define INCLUDE_ATVDEMOD_H
#include <QMutex>
#include <QElapsedTimer>
#include <vector>
@ -28,198 +27,43 @@
#include "dsp/devicesamplesource.h"
#include "dsp/dspcommands.h"
#include "dsp/downchannelizer.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "util/movingaverage.h"
#include "dsp/fftfilt.h"
#include "dsp/agc.h"
#include "dsp/phaselock.h"
#include "dsp/recursivefilters.h"
#include "dsp/phasediscri.h"
#include "audio/audiofifo.h"
#include "util/message.h"
#include "gui/tvscreen.h"
#include "atvdemodbaseband.h"
class QThread;
class DeviceAPI;
class ThreadedBasebandSampleSink;
class DownChannelizer;
class ATVDemod : public BasebandSampleSink, public ChannelAPI
{
Q_OBJECT
public:
enum ATVStd
{
ATVStdPAL625,
ATVStdPAL525,
ATVStd405,
ATVStdShortInterleaved,
ATVStdShort,
ATVStdHSkip
};
enum ATVModulation {
ATV_FM1, //!< Classical frequency modulation with discriminator #1
ATV_FM2, //!< Classical frequency modulation with discriminator #2
ATV_FM3, //!< Classical frequency modulation with phase derivative discriminator
ATV_AM, //!< Classical amplitude modulation
ATV_USB, //!< AM with vestigial lower side band (main signal is in the upper side)
ATV_LSB, //!< AM with vestigial upper side band (main signal is in the lower side)
ATV_NONE //!< No modulation just produces zeros
};
struct ATVConfig
{
int m_intSampleRate;
ATVStd m_enmATVStandard;
int m_intNumberOfLines;
float m_fltLineDuration;
float m_fltTopDuration;
float m_fltFramePerS;
float m_fltRatioOfRowsToDisplay;
float m_fltVoltLevelSynchroTop;
float m_fltVoltLevelSynchroBlack;
bool m_blnHSync;
bool m_blnVSync;
bool m_blnInvertVideo;
int m_intVideoTabIndex;
ATVConfig() :
m_intSampleRate(0),
m_enmATVStandard(ATVStdPAL625),
m_intNumberOfLines(625),
m_fltLineDuration(0.0f),
m_fltTopDuration(0.0f),
m_fltFramePerS(25.0f),
m_fltRatioOfRowsToDisplay(0.0f),
m_fltVoltLevelSynchroTop(0.0f),
m_fltVoltLevelSynchroBlack(1.0f),
m_blnHSync(false),
m_blnVSync(false),
m_blnInvertVideo(false),
m_intVideoTabIndex(0)
{
}
};
struct ATVRFConfig
{
int64_t m_intFrequencyOffset;
ATVModulation m_enmModulation;
float m_fltRFBandwidth;
float m_fltRFOppBandwidth;
bool m_blnFFTFiltering;
bool m_blndecimatorEnable;
float m_fltBFOFrequency;
float m_fmDeviation;
ATVRFConfig() :
m_intFrequencyOffset(0),
m_enmModulation(ATV_FM1),
m_fltRFBandwidth(0),
m_fltRFOppBandwidth(0),
m_blnFFTFiltering(false),
m_blndecimatorEnable(false),
m_fltBFOFrequency(0.0f),
m_fmDeviation(1.0f)
{
}
};
class MsgConfigureChannelizer : public Message {
class MsgConfigureATVDemod : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getCenterFrequency() const { return m_centerFrequency; }
const ATVDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureChannelizer* create(int centerFrequency)
static MsgConfigureATVDemod* create(const ATVDemodSettings& settings, bool force)
{
return new MsgConfigureChannelizer(centerFrequency);
return new MsgConfigureATVDemod(settings, force);
}
private:
int m_centerFrequency;
ATVDemodSettings m_settings;
bool m_force;
MsgConfigureChannelizer(int centerFrequency) :
MsgConfigureATVDemod(const ATVDemodSettings& settings, bool force) :
Message(),
m_centerFrequency(centerFrequency)
{ }
};
class MsgReportEffectiveSampleRate : public Message
{
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
int getNbPointsPerLine() const { return m_nbPointsPerLine; }
static MsgReportEffectiveSampleRate* create(int sampleRate, int nbPointsPerLine)
{
return new MsgReportEffectiveSampleRate(sampleRate, nbPointsPerLine);
}
protected:
int m_sampleRate;
int m_nbPointsPerLine;
MsgReportEffectiveSampleRate(int sampleRate, int nbPointsPerLine) :
Message(),
m_sampleRate(sampleRate),
m_nbPointsPerLine(nbPointsPerLine)
{ }
};
class MsgReportChannelSampleRateChanged : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
static MsgReportChannelSampleRateChanged* create(int sampleRate)
{
return new MsgReportChannelSampleRateChanged(sampleRate);
}
private:
int m_sampleRate;
MsgReportChannelSampleRateChanged(int sampleRate) :
Message(),
m_sampleRate(sampleRate)
m_settings(settings),
m_force(force)
{ }
};
ATVDemod(DeviceAPI *deviceAPI);
~ATVDemod();
virtual void destroy() { delete this; }
void setScopeSink(BasebandSampleSink* scopeSink) { m_scopeSink = scopeSink; }
void configure(MessageQueue* objMessageQueue,
float fltLineDurationUs,
float fltTopDurationUs,
float fltFramePerS,
ATVStd enmATVStandard,
int intNumberOfLines,
float fltRatioOfRowsToDisplay,
float fltVoltLevelSynchroTop,
float fltVoltLevelSynchroBlack,
bool blnHSync,
bool blnVSync,
bool blnInvertVideo,
int intVideoTabIndex);
void configureRF(MessageQueue* objMessageQueue,
int64_t frequencyOffset,
ATVModulation enmModulation,
float fltRFBandwidth,
float fltRFOppBandwidth,
bool blnFFTFiltering,
bool blndecimatorEnable,
float fltBFOFrequency,
float fmDeviation);
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
@ -228,7 +72,7 @@ public:
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual void getTitle(QString& title) { title = objectName(); }
virtual qint64 getCenterFrequency() const { return m_rfRunning.m_intFrequencyOffset; }
virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; }
virtual QByteArray serialize() const { return QByteArray(); }
virtual bool deserialize(const QByteArray& data) { (void) data; return false; }
@ -240,14 +84,15 @@ public:
{
(void) streamIndex;
(void) sinkElseSource;
return m_rfRunning.m_intFrequencyOffset;
return m_settings.m_inputFrequencyOffset;
}
void setTVScreen(TVScreen *objScreen); //!< set by the GUI
int getSampleRate();
int getEffectiveSampleRate();
double getMagSq() const { return m_objMagSqAverage; } //!< Beware this is scaled to 2^30
bool getBFOLocked();
void setScopeSink(BasebandSampleSink* scopeSink) { m_basebandSink->setScopeSink(scopeSink); }
void setTVScreen(TVScreen *tvScreen) { m_basebandSink->setTVScreen(tvScreen); }; //!< set by the GUI
double getMagSq() const { return m_basebandSink->getMagSq(); } //!< Beware this is scaled to 2^30
bool getBFOLocked() { return m_basebandSink->getBFOLocked(); }
void setVideoTabIndex(int videoTabIndex) { m_basebandSink->setVideoTabIndex(videoTabIndex); }
void propagateMessageQueueToGUI() { m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI()); }
static const QString m_channelIdURI;
static const QString m_channelId;
@ -256,504 +101,13 @@ private slots:
void channelSampleRateChanged();
private:
struct ATVConfigPrivate
{
int m_intTVSampleRate;
int m_intNumberSamplePerLine;
ATVConfigPrivate() :
m_intTVSampleRate(0),
m_intNumberSamplePerLine(0)
{}
};
class MsgConfigureATVDemod : public Message
{
MESSAGE_CLASS_DECLARATION
public:
static MsgConfigureATVDemod* create(
float fltLineDurationUs,
float fltTopDurationUs,
float fltFramePerS,
ATVStd enmATVStandard,
int intNumberOfLines,
float fltRatioOfRowsToDisplay,
float fltVoltLevelSynchroTop,
float fltVoltLevelSynchroBlack,
bool blnHSync,
bool blnVSync,
bool blnInvertVideo,
int intVideoTabIndex)
{
return new MsgConfigureATVDemod(
fltLineDurationUs,
fltTopDurationUs,
fltFramePerS,
enmATVStandard,
intNumberOfLines,
fltRatioOfRowsToDisplay,
fltVoltLevelSynchroTop,
fltVoltLevelSynchroBlack,
blnHSync,
blnVSync,
blnInvertVideo,
intVideoTabIndex);
}
ATVConfig m_objMsgConfig;
private:
MsgConfigureATVDemod(
float fltLineDurationUs,
float fltTopDurationUs,
float fltFramePerS,
ATVStd enmATVStandard,
int intNumberOfLines,
float flatRatioOfRowsToDisplay,
float fltVoltLevelSynchroTop,
float fltVoltLevelSynchroBlack,
bool blnHSync,
bool blnVSync,
bool blnInvertVideo,
int intVideoTabIndex) :
Message()
{
m_objMsgConfig.m_fltVoltLevelSynchroBlack = fltVoltLevelSynchroBlack;
m_objMsgConfig.m_fltVoltLevelSynchroTop = fltVoltLevelSynchroTop;
m_objMsgConfig.m_fltFramePerS = fltFramePerS;
m_objMsgConfig.m_enmATVStandard = enmATVStandard;
m_objMsgConfig.m_intNumberOfLines = intNumberOfLines;
m_objMsgConfig.m_fltLineDuration = fltLineDurationUs;
m_objMsgConfig.m_fltTopDuration = fltTopDurationUs;
m_objMsgConfig.m_fltRatioOfRowsToDisplay = flatRatioOfRowsToDisplay;
m_objMsgConfig.m_blnHSync = blnHSync;
m_objMsgConfig.m_blnVSync = blnVSync;
m_objMsgConfig.m_blnInvertVideo = blnInvertVideo;
m_objMsgConfig.m_intVideoTabIndex = intVideoTabIndex;
}
};
class MsgConfigureRFATVDemod : public Message
{
MESSAGE_CLASS_DECLARATION
public:
static MsgConfigureRFATVDemod* create(
int64_t frequencyOffset,
ATVModulation enmModulation,
float fltRFBandwidth,
float fltRFOppBandwidth,
bool blnFFTFiltering,
bool blndecimatorEnable,
int intBFOFrequency,
float fmDeviation)
{
return new MsgConfigureRFATVDemod(
frequencyOffset,
enmModulation,
fltRFBandwidth,
fltRFOppBandwidth,
blnFFTFiltering,
blndecimatorEnable,
intBFOFrequency,
fmDeviation);
}
ATVRFConfig m_objMsgConfig;
private:
MsgConfigureRFATVDemod(
int64_t frequencyOffset,
ATVModulation enmModulation,
float fltRFBandwidth,
float fltRFOppBandwidth,
bool blnFFTFiltering,
bool blndecimatorEnable,
float fltBFOFrequency,
float fmDeviation) :
Message()
{
m_objMsgConfig.m_intFrequencyOffset = frequencyOffset;
m_objMsgConfig.m_enmModulation = enmModulation;
m_objMsgConfig.m_fltRFBandwidth = fltRFBandwidth;
m_objMsgConfig.m_fltRFOppBandwidth = fltRFOppBandwidth;
m_objMsgConfig.m_blnFFTFiltering = blnFFTFiltering;
m_objMsgConfig.m_blndecimatorEnable = blndecimatorEnable;
m_objMsgConfig.m_fltBFOFrequency = fltBFOFrequency;
m_objMsgConfig.m_fmDeviation = fmDeviation;
}
};
/**
* Exponential average using integers and alpha as the inverse of a power of two
*/
class AvgExpInt
{
public:
AvgExpInt(int log2Alpha) : m_log2Alpha(log2Alpha), m_m1(0), m_start(true) {}
void reset() { m_start = true; }
int run(int m0)
{
if (m_start)
{
m_m1 = m0;
m_start = false;
return m0;
}
else
{
m_m1 = m0 + m_m1 - (m_m1>>m_log2Alpha);
return m_m1>>m_log2Alpha;
}
}
private:
int m_log2Alpha;
int m_m1;
bool m_start;
};
DeviceAPI* m_deviceAPI;
ThreadedBasebandSampleSink* m_threadedChannelizer;
DownChannelizer* m_channelizer;
QThread *m_thread;
ATVDemodBaseband* m_basebandSink;
ATVDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
//*************** SCOPE ***************
BasebandSampleSink* m_scopeSink;
SampleVector m_scopeSampleBuffer;
//*************** ATV PARAMETERS ***************
TVScreen *m_registeredTVScreen;
//int m_intNumberSamplePerLine;
int m_intNumberSamplePerTop;
int m_intNumberOfLines;
int m_intNumberOfSyncLines; //!< this is the number of non displayable lines at the start of a frame. First displayable row comes next.
int m_intNumberOfBlackLines; //!< this is the total number of lines not part of the image and is used for vertical screen size
int m_intNumberOfEqLines; //!< number of equalizing lines both whole and partial
int m_intNumberSamplePerLineSignals; //!< number of samples in the non image part of the line (signals)
int m_intNumberSaplesPerHSync; //!< number of samples per horizontal synchronization pattern (pulse + back porch)
bool m_interleaved; //!< interleaved image
//*************** PROCESSING ***************
int m_intImageIndex;
int m_intSynchroPoints;
bool m_blnSynchroDetected;
bool m_blnVerticalSynchroDetected;
float m_fltAmpLineAverage;
float m_fltEffMin;
float m_fltEffMax;
float m_fltAmpMin;
float m_fltAmpMax;
float m_fltAmpDelta;
float m_fltBufferI[6];
float m_fltBufferQ[6];
int m_intColIndex;
int m_intSampleIndex;
int m_intRowIndex;
int m_intLineIndex;
AvgExpInt m_objAvgColIndex;
int m_intAvgColIndex;
SampleVector m_sampleBuffer;
//*************** RF ***************
MovingAverageUtil<double, double, 32> m_objMagSqAverage;
NCO m_nco;
SimplePhaseLock m_bfoPLL;
SecondOrderRecursiveFilter m_bfoFilter;
// Interpolator group for decimation and/or double sideband RF filtering
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
// Used for vestigial SSB with asymmetrical filtering (needs double sideband scheme)
fftfilt* m_DSBFilter;
Complex* m_DSBFilterBuffer;
int m_DSBFilterBufferIndex;
static const int m_ssbFftLen;
// Used for FM
PhaseDiscriminators m_objPhaseDiscri;
//QElapsedTimer m_objTimer;
ATVConfig m_running;
ATVConfig m_config;
ATVRFConfig m_rfRunning;
ATVRFConfig m_rfConfig;
ATVConfigPrivate m_runningPrivate;
ATVConfigPrivate m_configPrivate;
QMutex m_objSettingsMutex;
void applySettings();
void applyStandard();
void demod(Complex& c);
static float getRFBandwidthDivisor(ATVModulation modulation);
inline void processHSkip(float& fltVal, int& intVal)
{
m_registeredTVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop, intVal, intVal, intVal);
// Horizontal Synchro detection
// Floor Detection 0
if (fltVal < m_running.m_fltVoltLevelSynchroTop)
{
m_intSynchroPoints++;
}
// Black detection 0.3
else if (fltVal > m_running.m_fltVoltLevelSynchroBlack)
{
m_intSynchroPoints = 0;
}
// sync pulse
m_blnSynchroDetected = (m_intSynchroPoints == m_intNumberSamplePerTop);
if (m_blnSynchroDetected)
{
if (m_intSampleIndex >= (3 * m_runningPrivate.m_intNumberSamplePerLine)/2) // first after skip
{
//qDebug("VSync: %d %d %d", m_intColIndex, m_intSampleIndex, m_intLineIndex);
m_intAvgColIndex = m_intColIndex;
m_registeredTVScreen->renderImage(0);
m_intImageIndex++;
m_intLineIndex = 0;
m_intRowIndex = 0;
}
m_intSampleIndex = 0;
}
else
{
m_intSampleIndex++;
}
if (m_intColIndex < m_runningPrivate.m_intNumberSamplePerLine + m_intNumberSamplePerTop - 1)
{
m_intColIndex++;
}
else
{
if (m_running.m_blnHSync && (m_intLineIndex == 0))
{
//qDebug("HCorr: %d", m_intAvgColIndex);
m_intColIndex = m_intNumberSamplePerTop + (m_runningPrivate.m_intNumberSamplePerLine - m_intAvgColIndex)/2; // amortizing factor 1/2
}
else
{
m_intColIndex = m_intNumberSamplePerTop;
}
if ((m_rfRunning.m_enmModulation == ATV_AM)
|| (m_rfRunning.m_enmModulation == ATV_USB)
|| (m_rfRunning.m_enmModulation == ATV_LSB))
{
m_fltAmpMin = m_fltEffMin;
m_fltAmpMax = m_fltEffMax;
m_fltAmpDelta = m_fltEffMax-m_fltEffMin;
if(m_fltAmpDelta<=0.0)
{
m_fltAmpDelta=1.0f;
}
//Reset extrema
m_fltEffMin = 2000000.0f;
m_fltEffMax = -2000000.0f;
}
m_registeredTVScreen->selectRow(m_intRowIndex);
m_intLineIndex++;
m_intRowIndex++;
}
}
inline void processClassic(float& fltVal, int& intVal)
{
int intSynchroTimeSamples= (3 * m_runningPrivate.m_intNumberSamplePerLine)/4;
float fltSynchroTrameLevel = 0.5f*((float)intSynchroTimeSamples) * m_running.m_fltVoltLevelSynchroBlack;
// Horizontal Synchro detection
// Floor Detection 0
if (fltVal < m_running.m_fltVoltLevelSynchroTop)
{
m_intSynchroPoints++;
}
// Black detection 0.3
else if (fltVal > m_running.m_fltVoltLevelSynchroBlack)
{
m_intSynchroPoints = 0;
}
m_blnSynchroDetected = (m_intSynchroPoints == m_intNumberSamplePerTop);
//Horizontal Synchro processing
bool blnNewLine = false;
if (m_blnSynchroDetected)
{
m_intAvgColIndex = m_intSampleIndex - m_intColIndex - (m_intColIndex < m_runningPrivate.m_intNumberSamplePerLine/2 ? 150 : 0);
//qDebug("HSync: %d %d %d", m_intSampleIndex, m_intColIndex, m_intAvgColIndex);
m_intSampleIndex = 0;
}
else
{
m_intSampleIndex++;
}
if (!m_running.m_blnHSync && (m_intColIndex >= m_runningPrivate.m_intNumberSamplePerLine)) // H Sync not active
{
m_intColIndex = 0;
blnNewLine = true;
}
else if (m_intColIndex >= m_runningPrivate.m_intNumberSamplePerLine + m_intNumberSamplePerTop) // No valid H sync
{
if (m_running.m_blnHSync && (m_intLineIndex == 0))
{
//qDebug("HSync: %d %d", m_intColIndex, m_intAvgColIndex);
m_intColIndex = m_intNumberSamplePerTop + m_intAvgColIndex/4; // amortizing 1/4
}
else
{
m_intColIndex = m_intNumberSamplePerTop;
}
blnNewLine = true;
}
if (blnNewLine)
{
if ((m_rfRunning.m_enmModulation == ATV_AM)
|| (m_rfRunning.m_enmModulation == ATV_USB)
|| (m_rfRunning.m_enmModulation == ATV_LSB))
{
m_fltAmpMin = m_fltEffMin;
m_fltAmpMax = m_fltEffMax;
m_fltAmpDelta = m_fltEffMax-m_fltEffMin;
if(m_fltAmpDelta<=0.0)
{
m_fltAmpDelta=1.0f;
}
//Reset extrema
m_fltEffMin = 2000000.0f;
m_fltEffMax = -2000000.0f;
}
m_fltAmpLineAverage=0.0f;
//New line + Interleaving
m_intRowIndex += m_interleaved ? 2 : 1;
if (m_intRowIndex < m_intNumberOfLines)
{
m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines);
}
m_intLineIndex++;
}
// Filling pixels
// +4 is to compensate shift due to hsync amortizing factor of 1/4
m_registeredTVScreen->setDataColor(m_intColIndex - m_intNumberSaplesPerHSync + m_intNumberSamplePerTop + 4, intVal, intVal, intVal);
m_intColIndex++;
// Vertical sync and image rendering
if ((m_running.m_blnVSync) && (m_intLineIndex < m_intNumberOfLines)) // VSync activated and lines in range
{
if (m_intColIndex >= intSynchroTimeSamples)
{
if (m_fltAmpLineAverage <= fltSynchroTrameLevel)
{
m_fltAmpLineAverage = 0.0f;
if (!m_blnVerticalSynchroDetected) // not yet
{
m_blnVerticalSynchroDetected = true; // prevent repetition
if ((m_intLineIndex % 2 == 0) || !m_interleaved) // even => odd image
{
m_registeredTVScreen->renderImage(0);
m_intRowIndex = 1;
}
else
{
m_intRowIndex = 0;
}
m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines);
m_intLineIndex = 0;
m_intImageIndex++;
}
}
else
{
m_blnVerticalSynchroDetected = false; // reset
}
}
}
else // no VSync or lines out of range => arbitrary
{
if (m_intLineIndex >= m_intNumberOfLines/2)
{
if (m_intImageIndex % 2 == 1) // odd image
{
m_registeredTVScreen->renderImage(0);
if (m_rfRunning.m_enmModulation == ATV_AM)
{
m_fltAmpMin = m_fltEffMin;
m_fltAmpMax = m_fltEffMax;
m_fltAmpDelta = m_fltEffMax-m_fltEffMin;
if(m_fltAmpDelta<=0.0)
{
m_fltAmpDelta=1.0f;
}
//Reset extrema
m_fltEffMin = 2000000.0f;
m_fltEffMax = -2000000.0f;
}
m_intRowIndex = 1;
}
else
{
m_intRowIndex = 0;
}
m_registeredTVScreen->selectRow(m_intRowIndex - m_intNumberOfSyncLines);
m_intLineIndex = 0;
m_intImageIndex++;
}
}
}
void applySettings(const ATVDemodSettings& settings, bool force = false);
};
#endif // INCLUDE_ATVDEMOD_H

View File

@ -0,0 +1,169 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "atvdemodbaseband.h"
MESSAGE_CLASS_DEFINITION(ATVDemodBaseband::MsgConfigureATVDemodBaseband, Message)
MESSAGE_CLASS_DEFINITION(ATVDemodBaseband::MsgConfigureChannelizer, Message)
ATVDemodBaseband::ATVDemodBaseband() :
m_mutex(QMutex::Recursive)
{
qDebug("ATVDemodBaseband::ATVDemodBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownSampleChannelizer(&m_sink);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&ATVDemodBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
ATVDemodBaseband::~ATVDemodBaseband()
{
delete m_channelizer;
}
void ATVDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleFifo.reset();
}
void ATVDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void ATVDemodBaseband::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 ATVDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool ATVDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureATVDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureATVDemodBaseband& cfg = (MsgConfigureATVDemodBaseband&) cmd;
qDebug() << "ATVDemodBaseband::handleMessage: MsgConfigureATVDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgConfigureChannelizer::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
qDebug() << "ATVDemodBaseband::handleMessage: MsgConfigureChannelizer"
<< "(requested) sinkSampleRate: " << cfg.getSinkSampleRate()
<< "(requested) sinkCenterFrequency: " << cfg.getSinkCenterFrequency();
m_channelizer->setChannelization(cfg.getSinkSampleRate(), cfg.getSinkCenterFrequency());
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "ATVDemodBaseband::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
{
return false;
}
}
void ATVDemodBaseband::applySettings(const ATVDemodSettings& settings, bool force)
{
qDebug("ATVDemodBaseband::applySettings");
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)|| force)
{
unsigned int desiredSampleRate = m_channelizer->getBasebandSampleRate();
m_channelizer->setChannelization(desiredSampleRate, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
int ATVDemodBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
void ATVDemodBaseband::setBasebandSampleRate(int sampleRate)
{
qDebug("ATVDemodBaseband::setBasebandSampleRate: %d", sampleRate);
m_channelizer->setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}

View File

@ -0,0 +1,111 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_ATVDEMODBASEBAND_H
#define INCLUDE_ATVDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "atvdemodsink.h"
class DownSampleChannelizer;
class ATVDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureATVDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const ATVDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureATVDemodBaseband* create(const ATVDemodSettings& settings, bool force)
{
return new MsgConfigureATVDemodBaseband(settings, force);
}
private:
ATVDemodSettings m_settings;
bool m_force;
MsgConfigureATVDemodBaseband(const ATVDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgConfigureChannelizer : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSinkSampleRate() const { return m_sinkSampleRate; }
int getSinkCenterFrequency() const { return m_sinkCenterFrequency; }
static MsgConfigureChannelizer* create(int sinkSampleRate, int sinkCenterFrequency) {
return new MsgConfigureChannelizer(sinkSampleRate, sinkCenterFrequency);
}
private:
int m_sinkSampleRate;
int m_sinkCenterFrequency;
MsgConfigureChannelizer(int sinkSampleRate, int sinkCenterFrequency) :
Message(),
m_sinkSampleRate(sinkSampleRate),
m_sinkCenterFrequency(sinkCenterFrequency)
{ }
};
ATVDemodBaseband();
~ATVDemodBaseband();
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;
double getMagSq() const { return m_sink.getMagSq(); }
void setScopeSink(BasebandSampleSink* scopeSink) { m_sink.setScopeSink(scopeSink); }
void setTVScreen(TVScreen *tvScreen) { m_sink.setTVScreen(tvScreen); }
bool getBFOLocked() { return m_sink.getBFOLocked(); }
void setVideoTabIndex(int videoTabIndex) { m_sink.setVideoTabIndex(videoTabIndex); }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_sink.setMessageQueueToGUI(messageQueue); }
void setBasebandSampleRate(int sampleRate); //!< To be used when supporting thread is stopped
private:
SampleSinkFifo m_sampleFifo;
DownSampleChannelizer *m_channelizer;
ATVDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
ATVDemodSettings m_settings;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const ATVDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_CHANNELANALYZERBASEBAND_H

View File

@ -33,6 +33,7 @@
#include "dsp/dspengine.h"
#include "mainwindow.h"
#include "atvdemodreport.h"
#include "atvdemod.h"
ATVDemodGUI* ATVDemodGUI::create(PluginAPI* objPluginAPI,
@ -66,167 +67,127 @@ qint64 ATVDemodGUI::getCenterFrequency() const
void ATVDemodGUI::setCenterFrequency(qint64 intCenterFrequency)
{
m_channelMarker.setCenterFrequency(intCenterFrequency);
m_settings.m_inputFrequencyOffset = intCenterFrequency;
applySettings();
}
void ATVDemodGUI::resetToDefaults()
{
blockApplySettings(true);
//********** ATV Default values **********
ui->synchLevel->setValue(100);
ui->blackLevel->setValue(310);
ui->lineTime->setValue(0);
ui->topTime->setValue(3);
ui->modulation->setCurrentIndex(0);
ui->fps->setCurrentIndex(0);
ui->hSync->setChecked(true);
ui->vSync->setChecked(true);
ui->halfImage->setChecked(false);
ui->invertVideo->setChecked(false);
ui->standard->setCurrentIndex(1);
//********** RF Default values **********
ui->decimatorEnable->setChecked(false);
ui->rfFiltering->setChecked(false);
ui->rfBW->setValue(10);
ui->rfOppBW->setValue(10);
ui->bfo->setValue(0);
ui->fmDeviation->setValue(250);
blockApplySettings(false);
lineTimeUpdate();
topTimeUpdate();
applySettings();
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray ATVDemodGUI::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_channelMarker.getCenterFrequency());
s.writeU32(2, m_channelMarker.getColor().rgb());
s.writeS32(3, ui->synchLevel->value());
s.writeS32(4, ui->blackLevel->value());
s.writeS32(5, ui->lineTime->value());
s.writeS32(6, ui->topTime->value());
s.writeS32(7, ui->modulation->currentIndex());
s.writeS32(8, ui->fps->currentIndex());
s.writeBool(9, ui->hSync->isChecked());
s.writeBool(10, ui->vSync->isChecked());
s.writeBool(11, ui->halfImage->isChecked());
s.writeS32(12, ui->rfBW->value());
s.writeS32(13, ui->rfOppBW->value());
s.writeS32(14, ui->bfo->value());
s.writeBool(15, ui->invertVideo->isChecked());
s.writeS32(16, ui->nbLines->currentIndex());
s.writeS32(17, ui->fmDeviation->value());
s.writeS32(18, ui->standard->currentIndex());
return s.final();
return m_settings.serialize();
}
bool ATVDemodGUI::deserialize(const QByteArray& arrData)
bool ATVDemodGUI::deserialize(const QByteArray& data)
{
SimpleDeserializer d(arrData);
if (!d.isValid())
if(m_settings.deserialize(data))
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
QByteArray bytetmp;
int tmp;
bool booltmp;
blockApplySettings(true);
m_channelMarker.blockSignals(true);
d.readS32(1, &tmp, 0);
m_channelMarker.setCenterFrequency(tmp);
// if (d.readU32(2, &u32tmp)) {
// m_objChannelMarker.setColor(u32tmp);
// } else {
// m_objChannelMarker.setColor(Qt::white);
// }
d.readS32(3, &tmp, 100);
ui->synchLevel->setValue(tmp);
d.readS32(4, &tmp, 310);
ui->blackLevel->setValue(tmp);
d.readS32(5, &tmp, 0);
ui->lineTime->setValue(tmp);
d.readS32(6, &tmp, 3);
ui->topTime->setValue(tmp);
d.readS32(7, &tmp, 0);
ui->modulation->setCurrentIndex(tmp);
d.readS32(8, &tmp, 0);
ui->fps->setCurrentIndex(tmp);
d.readBool(9, &booltmp, true);
ui->hSync->setChecked(booltmp);
d.readBool(10, &booltmp, true);
ui->vSync->setChecked(booltmp);
d.readBool(11, &booltmp, false);
ui->halfImage->setChecked(booltmp);
d.readS32(12, &tmp, 10);
ui->rfBW->setValue(tmp);
d.readS32(13, &tmp, 10);
ui->rfOppBW->setValue(tmp);
d.readS32(14, &tmp, 10);
ui->bfo->setValue(tmp);
d.readBool(15, &booltmp, true);
ui->invertVideo->setChecked(booltmp);
d.readS32(16, &tmp, 0);
ui->nbLines->setCurrentIndex(tmp);
d.readS32(17, &tmp, 250);
ui->fmDeviation->setValue(tmp);
d.readS32(18, &tmp, 1);
ui->standard->setCurrentIndex(tmp);
blockApplySettings(false);
m_channelMarker.blockSignals(false);
m_channelMarker.emitChangedByAPI();
lineTimeUpdate();
topTimeUpdate();
applySettings();
displaySettings();
applySettings(true); // will have true
return true;
}
else
{
resetToDefaults();
m_settings.resetToDefaults();
displaySettings();
applySettings(true); // will have true
return false;
}
}
bool ATVDemodGUI::handleMessage(const Message& objMessage)
void ATVDemodGUI::displaySettings()
{
if (ATVDemod::MsgReportEffectiveSampleRate::match(objMessage))
{
int sampleRate = ((ATVDemod::MsgReportEffectiveSampleRate&)objMessage).getSampleRate();
int nbPointsPerLine = ((ATVDemod::MsgReportEffectiveSampleRate&)objMessage).getNbPointsPerLine();
ui->channelSampleRateText->setText(tr("%1k").arg(sampleRate/1000.0f, 0, 'f', 2));
ui->nbPointsPerLineText->setText(tr("%1p").arg(nbPointsPerLine));
m_scopeVis->setLiveRate(sampleRate);
setRFFiltersSlidersRange(sampleRate);
lineTimeUpdate();
topTimeUpdate();
m_channelMarker.blockSignals(true);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle(m_settings.m_title);
setChannelMarkerBandwidth();
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
return true;
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
displayStreamIndex();
blockApplySettings(true);
//********** ATV values **********
ui->synchLevel->setValue((int) (m_settings.m_levelSynchroTop * 1000.0f));
ui->synchLevelText->setText(QString("%1 mV").arg((int) (m_settings.m_levelSynchroTop * 1000.0f)));
ui->blackLevel->setValue((int) (m_settings.m_levelBlack * 1000.0f));
ui->blackLevelText->setText(QString("%1 mV").arg((int) (m_settings.m_levelBlack * 1000.0f)));
ui->lineTime->setValue(m_settings.m_lineTimeFactor);
ui->topTime->setValue(m_settings.m_topTimeFactor);
ui->modulation->setCurrentIndex((int) m_settings.m_atvModulation);
ui->fps->setCurrentIndex(ATVDemodSettings::getFpsIndex(m_settings.m_fps));
ui->nbLines->setCurrentIndex(ATVDemodSettings::getNumberOfLinesIndex(m_settings.m_nbLines));
ui->hSync->setChecked(m_settings.m_hSync);
ui->vSync->setChecked(m_settings.m_vSync);
ui->halfImage->setChecked(m_settings.m_halfFrames);
ui->invertVideo->setChecked(m_settings.m_invertVideo);
ui->standard->setCurrentIndex((int) m_settings.m_atvStd);
lineTimeUpdate();
topTimeUpdate();
//********** RF values **********
ui->decimatorEnable->setChecked(m_settings.m_forceDecimator);
ui->rfFiltering->setChecked(m_settings.m_fftFiltering);
ui->bfo->setValue(m_settings.m_bfoFrequency);
ui->bfoText->setText(QString("%1").arg(m_settings.m_bfoFrequency * 1.0, 0, 'f', 0));
ui->fmDeviation->setValue((int) (m_settings.m_fmDeviation * 1000.0f));
ui->fmDeviationText->setText(QString("%1").arg(m_settings.m_fmDeviation * 100.0, 0, 'f', 1));
blockApplySettings(false);
applyTVSampleRate();
}
void ATVDemodGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
else if (ATVDemod::MsgReportChannelSampleRateChanged::match(objMessage))
}
void ATVDemodGUI::displayRFBandwidths()
{
int sliderPosition = m_settings.m_fftBandwidth / m_rfSliderDivisor;
sliderPosition < 1 ? 1 : sliderPosition > 100 ? 100 : sliderPosition;
ui->rfBW->setValue(sliderPosition);
ui->rfBWText->setText(QString("%1k").arg((sliderPosition * m_rfSliderDivisor) / 1000.0, 0, 'f', 0));
sliderPosition = m_settings.m_fftOppBandwidth / m_rfSliderDivisor;
sliderPosition < 0 ? 0 : sliderPosition > 100 ? 100 : sliderPosition;
ui->rfOppBW->setValue(sliderPosition);
ui->rfOppBWText->setText(QString("%1k").arg((sliderPosition * m_rfSliderDivisor) / 1000.0, 0, 'f', 0));
}
void ATVDemodGUI::applyTVSampleRate()
{
blockApplySettings(true);
unsigned int nbPointsPerLine;
ATVDemodSettings::getBaseValues(m_basebandSampleRate, m_settings.m_fps*m_settings.m_nbLines, m_tvSampleRate, nbPointsPerLine);
ui->tvSampleRateText->setText(tr("%1k").arg(m_tvSampleRate/1000.0f, 0, 'f', 2));
ui->nbPointsPerLineText->setText(tr("%1p").arg(nbPointsPerLine));
m_scopeVis->setLiveRate(m_tvSampleRate);
setRFFiltersSlidersRange(m_tvSampleRate);
displayRFBandwidths();
lineTimeUpdate();
topTimeUpdate();
blockApplySettings(false);
}
bool ATVDemodGUI::handleMessage(const Message& message)
{
if (DSPSignalNotification::match(message))
{
ATVDemod::MsgReportChannelSampleRateChanged report = (ATVDemod::MsgReportChannelSampleRateChanged&) objMessage;
m_inputSampleRate = report.getSampleRate();
qDebug("ATVDemodGUI::handleMessage: MsgReportChannelSampleRateChanged: %d", m_inputSampleRate);
applySettings();
applyRFSettings();
DSPSignalNotification& notif = (DSPSignalNotification&) message;
m_basebandSampleRate = notif.getSampleRate();
applyTVSampleRate();
return true;
}
@ -240,9 +201,8 @@ void ATVDemodGUI::channelMarkerChangedByCursor()
{
qDebug("ATVDemodGUI::channelMarkerChangedByCursor");
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
applyRFSettings();
}
void ATVDemodGUI::channelMarkerHighlightedByCursor()
@ -277,7 +237,8 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base
m_channelMarker(this),
m_blnDoApplySettings(true),
m_intTickCount(0),
m_inputSampleRate(48000)
m_basebandSampleRate(48000),
m_tvSampleRate(48000)
{
ui->setupUi(this);
ui->screenTV->setColor(false);
@ -287,6 +248,7 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base
m_scopeVis = new ScopeVis(ui->glScope);
m_atvDemod = (ATVDemod*) rxChannel; //new ATVDemod(m_deviceUISet->m_deviceSourceAPI);
m_atvDemod->setMessageQueueToGUI(getInputMessageQueue());
m_atvDemod->propagateMessageQueueToGUI();
m_atvDemod->setScopeSink(m_scopeVis);
m_atvDemod->setTVScreen(ui->screenTV);
@ -350,47 +312,12 @@ void ATVDemodGUI::blockApplySettings(bool blnBlock)
m_blnDoApplySettings = !blnBlock;
}
void ATVDemodGUI::applySettings()
void ATVDemodGUI::applySettings(bool force)
{
if (m_blnDoApplySettings)
{
ATVDemod::MsgConfigureChannelizer *msgChan = ATVDemod::MsgConfigureChannelizer::create(
m_channelMarker.getCenterFrequency());
m_atvDemod->getInputMessageQueue()->push(msgChan);
m_atvDemod->configure(m_atvDemod->getInputMessageQueue(),
getNominalLineTime(ui->nbLines->currentIndex(), ui->fps->currentIndex()) + ui->lineTime->value() * m_fltLineTimeMultiplier,
getNominalLineTime(ui->nbLines->currentIndex(), ui->fps->currentIndex()) * (4.7f / 64.0f) + ui->topTime->value() * m_fltTopTimeMultiplier,
getFps(ui->fps->currentIndex()),
(ATVDemod::ATVStd) ui->standard->currentIndex(),
getNumberOfLines(ui->nbLines->currentIndex()),
(ui->halfImage->checkState() == Qt::Checked) ? 0.5f : 1.0f,
ui->synchLevel->value() / 1000.0f,
ui->blackLevel->value() / 1000.0f,
ui->hSync->isChecked(),
ui->vSync->isChecked(),
ui->invertVideo->isChecked(),
ui->screenTabWidget->currentIndex());
qDebug() << "ATVDemodGUI::applySettings:"
<< " m_objChannelizer.inputSampleRate: " << m_inputSampleRate
<< " m_objATVDemod.sampleRate: " << m_atvDemod->getSampleRate();
}
}
void ATVDemodGUI::applyRFSettings()
{
if (m_blnDoApplySettings)
{
m_atvDemod->configureRF(m_atvDemod->getInputMessageQueue(),
m_channelMarker.getCenterFrequency(),
(ATVDemod::ATVModulation) ui->modulation->currentIndex(),
ui->rfBW->value() * m_rfSliderDivisor * 1.0f,
ui->rfOppBW->value() * m_rfSliderDivisor * 1.0f,
ui->rfFiltering->isChecked(),
ui->decimatorEnable->isChecked(),
ui->bfo->value(),
ui->fmDeviation->value() / 500.0f);
ATVDemod::MsgConfigureATVDemod *msg = ATVDemod::MsgConfigureATVDemod::create(m_settings, force);
m_atvDemod->getInputMessageQueue()->push(msg);
}
}
@ -404,9 +331,9 @@ void ATVDemodGUI::setChannelMarkerBandwidth()
m_channelMarker.setBandwidth(ui->rfBW->value()*m_rfSliderDivisor);
m_channelMarker.setOppositeBandwidth(ui->rfOppBW->value()*m_rfSliderDivisor);
if (ui->modulation->currentIndex() == (int) ATVDemod::ATV_LSB) {
if (ui->modulation->currentIndex() == (int) ATVDemodSettings::ATV_LSB) {
m_channelMarker.setSidebands(ChannelMarker::vlsb);
} else if (ui->modulation->currentIndex() == (int) ATVDemod::ATV_USB) {
} else if (ui->modulation->currentIndex() == (int) ATVDemodSettings::ATV_USB) {
m_channelMarker.setSidebands(ChannelMarker::vusb);
} else {
m_channelMarker.setSidebands(ChannelMarker::vusb);
@ -414,10 +341,10 @@ void ATVDemodGUI::setChannelMarkerBandwidth()
}
else
{
if (ui->decimatorEnable->isChecked()) {
m_channelMarker.setBandwidth(ui->rfBW->value()*m_rfSliderDivisor);
if ((m_basebandSampleRate == m_tvSampleRate) && (!m_settings.m_forceDecimator)) {
m_channelMarker.setBandwidth(m_basebandSampleRate);
} else {
m_channelMarker.setBandwidth(m_inputSampleRate);
m_channelMarker.setBandwidth(ui->rfBW->value()*m_rfSliderDivisor);
}
m_channelMarker.setSidebands(ChannelMarker::dsb);
@ -492,19 +419,22 @@ void ATVDemodGUI::tick()
void ATVDemodGUI::on_synchLevel_valueChanged(int value)
{
applySettings();
ui->synchLevelText->setText(QString("%1 mV").arg(value));
m_settings.m_levelSynchroTop = value / 1000.0f;
applySettings();
}
void ATVDemodGUI::on_blackLevel_valueChanged(int value)
{
applySettings();
ui->blackLevelText->setText(QString("%1 mV").arg(value));
m_settings.m_levelBlack = value / 1000.0f;
applySettings();
}
void ATVDemodGUI::on_lineTime_valueChanged(int value)
{
ui->lineTime->setToolTip(QString("Line length adjustment (%1)").arg(value));
m_settings.m_lineTimeFactor = value;
lineTimeUpdate();
applySettings();
}
@ -512,49 +442,52 @@ void ATVDemodGUI::on_lineTime_valueChanged(int value)
void ATVDemodGUI::on_topTime_valueChanged(int value)
{
ui->topTime->setToolTip(QString("Horizontal sync pulse length adjustment (%1)").arg(value));
m_settings.m_topTimeFactor = value;
topTimeUpdate();
applySettings();
}
void ATVDemodGUI::on_hSync_clicked()
{
m_settings.m_hSync = ui->hSync->isChecked();
applySettings();
}
void ATVDemodGUI::on_vSync_clicked()
{
m_settings.m_vSync = ui->vSync->isChecked();
applySettings();
}
void ATVDemodGUI::on_invertVideo_clicked()
{
m_settings.m_invertVideo = ui->invertVideo->isChecked();
applySettings();
}
void ATVDemodGUI::on_halfImage_clicked()
{
m_settings.m_halfFrames = ui->halfImage->isChecked();
applySettings();
}
void ATVDemodGUI::on_nbLines_currentIndexChanged(int index)
{
(void) index;
lineTimeUpdate();
topTimeUpdate();
m_settings.m_nbLines = ATVDemodSettings::getNumberOfLines(index);
applyTVSampleRate();
applySettings();
}
void ATVDemodGUI::on_fps_currentIndexChanged(int index)
{
(void) index;
lineTimeUpdate();
topTimeUpdate();
m_settings.m_fps = ATVDemodSettings::getFps(index);
applyTVSampleRate();
applySettings();
}
void ATVDemodGUI::on_standard_currentIndexChanged(int index)
{
(void) index;
m_settings.m_atvStd = (ATVDemodSettings::ATVStd) index;
applySettings();
}
@ -566,74 +499,78 @@ void ATVDemodGUI::on_reset_clicked(bool checked)
void ATVDemodGUI::on_modulation_currentIndexChanged(int index)
{
(void) index;
setRFFiltersSlidersRange(m_atvDemod->getEffectiveSampleRate());
m_settings.m_atvModulation = (ATVDemodSettings::ATVModulation) index;
setRFFiltersSlidersRange(m_tvSampleRate);
setChannelMarkerBandwidth();
applyRFSettings();
applySettings();
}
void ATVDemodGUI::on_rfBW_valueChanged(int value)
{
m_settings.m_fftBandwidth = value * m_rfSliderDivisor;
ui->rfBWText->setText(QString("%1k").arg((value * m_rfSliderDivisor) / 1000.0, 0, 'f', 0));
setChannelMarkerBandwidth();
applyRFSettings();
applySettings();
}
void ATVDemodGUI::on_rfOppBW_valueChanged(int value)
{
m_settings.m_fftOppBandwidth = value * m_rfSliderDivisor;
ui->rfOppBWText->setText(QString("%1k").arg((value * m_rfSliderDivisor) / 1000.0, 0, 'f', 0));
setChannelMarkerBandwidth();
applyRFSettings();
applySettings();
}
void ATVDemodGUI::on_rfFiltering_toggled(bool checked)
{
(void) checked;
setRFFiltersSlidersRange(m_atvDemod->getEffectiveSampleRate());
m_settings.m_fftFiltering = checked;
setRFFiltersSlidersRange(m_tvSampleRate);
setChannelMarkerBandwidth();
applyRFSettings();
applySettings();
}
void ATVDemodGUI::on_decimatorEnable_toggled(bool checked)
{
(void) checked;
m_settings.m_forceDecimator = checked;
setChannelMarkerBandwidth();
applyRFSettings();
applySettings();
}
void ATVDemodGUI::on_deltaFrequency_changed(qint64 value)
{
m_settings.m_inputFrequencyOffset = value;
m_channelMarker.setCenterFrequency(value);
applyRFSettings();
applySettings();
}
void ATVDemodGUI::on_bfo_valueChanged(int value)
{
m_settings.m_bfoFrequency = value;
ui->bfoText->setText(QString("%1").arg(value * 1.0, 0, 'f', 0));
applyRFSettings();
applySettings();
}
void ATVDemodGUI::on_fmDeviation_valueChanged(int value)
{
m_settings.m_fmDeviation = value / 1000.0f;
ui->fmDeviationText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
applyRFSettings();
applySettings();
}
void ATVDemodGUI::on_screenTabWidget_currentChanged(int index)
{
(void) index;
applySettings();
m_atvDemod->setVideoTabIndex(index);
}
void ATVDemodGUI::lineTimeUpdate()
{
float nominalLineTime = getNominalLineTime(ui->nbLines->currentIndex(), ui->fps->currentIndex());
float nominalLineTime = ATVDemodSettings::getNominalLineTime(m_settings.m_nbLines, m_settings.m_fps);
int lineTimeScaleFactor = (int) std::log10(nominalLineTime);
if (m_atvDemod->getEffectiveSampleRate() == 0) {
if (m_tvSampleRate == 0) {
m_fltLineTimeMultiplier = std::pow(10.0, lineTimeScaleFactor-3);
} else {
m_fltLineTimeMultiplier = 1.0f / m_atvDemod->getEffectiveSampleRate();
m_fltLineTimeMultiplier = 1.0f / m_tvSampleRate;
}
float lineTime = nominalLineTime + m_fltLineTimeMultiplier * ui->lineTime->value();
@ -652,13 +589,13 @@ void ATVDemodGUI::lineTimeUpdate()
void ATVDemodGUI::topTimeUpdate()
{
float nominalTopTime = getNominalLineTime(ui->nbLines->currentIndex(), ui->fps->currentIndex()) * (4.7f / 64.0f);
float nominalTopTime = ATVDemodSettings::getNominalLineTime(m_settings.m_nbLines, m_settings.m_fps) * (4.7f / 64.0f);
int topTimeScaleFactor = (int) std::log10(nominalTopTime);
if (m_atvDemod->getEffectiveSampleRate() == 0) {
if (m_tvSampleRate == 0) {
m_fltTopTimeMultiplier = std::pow(10.0, topTimeScaleFactor-3);
} else {
m_fltTopTimeMultiplier = 1.0f / m_atvDemod->getEffectiveSampleRate();
m_fltTopTimeMultiplier = 1.0f / m_tvSampleRate;
}
float topTime = nominalTopTime + m_fltTopTimeMultiplier * ui->topTime->value();
@ -674,96 +611,3 @@ void ATVDemodGUI::topTimeUpdate()
else
ui->topTimeText->setText(tr("%1 s").arg(topTime * 1.0, 0, 'f', 2));
}
float ATVDemodGUI::getFps(int fpsIndex)
{
switch(fpsIndex)
{
case 0:
return 30.0f;
break;
case 2:
return 20.0f;
break;
case 3:
return 16.0f;
break;
case 4:
return 12.0f;
break;
case 5:
return 10.0f;
break;
case 6:
return 8.0f;
break;
case 7:
return 5.0f;
break;
case 8:
return 2.0f;
break;
case 9:
return 1.0f;
break;
case 1:
default:
return 25.0f;
break;
}
}
float ATVDemodGUI::getNominalLineTime(int nbLinesIndex, int fpsIndex)
{
float fps = getFps(fpsIndex);
int nbLines = getNumberOfLines(nbLinesIndex);
return 1.0f / (nbLines * fps);
}
int ATVDemodGUI::getNumberOfLines(int nbLinesIndex)
{
switch(nbLinesIndex)
{
case 0:
return 640;
break;
case 2:
return 525;
break;
case 3:
return 480;
break;
case 4:
return 405;
break;
case 5:
return 360;
break;
case 6:
return 343;
break;
case 7:
return 240;
break;
case 8:
return 180;
break;
case 9:
return 120;
break;
case 10:
return 90;
break;
case 11:
return 60;
break;
case 12:
return 32;
break;
case 1:
default:
return 625;
break;
}
}

View File

@ -25,6 +25,8 @@
#include "util/movingaverage.h"
#include "util/messagequeue.h"
#include "atvdemodsettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
@ -65,6 +67,7 @@ private:
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
ATVDemod* m_atvDemod;
ATVDemodSettings m_settings;
bool m_blnDoApplySettings;
@ -76,22 +79,23 @@ private:
float m_fltLineTimeMultiplier;
float m_fltTopTimeMultiplier;
int m_rfSliderDivisor;
int m_inputSampleRate;
int m_basebandSampleRate;
int m_tvSampleRate;
MessageQueue m_inputMessageQueue;
explicit ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* objParent = 0);
virtual ~ATVDemodGUI();
void blockApplySettings(bool blnBlock);
void applySettings();
void applyRFSettings();
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
void displayRFBandwidths();
void applyTVSampleRate();
void setChannelMarkerBandwidth();
void setRFFiltersSlidersRange(int sampleRate);
void lineTimeUpdate();
void topTimeUpdate();
static float getFps(int fpsIndex);
static float getNominalLineTime(int nbLinesIndex, int fpsIndex);
static int getNumberOfLines(int nbLinesIndex);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);

View File

@ -132,7 +132,7 @@
</widget>
</item>
<item>
<widget class="QLabel" name="channelSampleRateText">
<widget class="QLabel" name="tvSampleRateText">
<property name="minimumSize">
<size>
<width>66</width>
@ -140,7 +140,7 @@
</size>
</property>
<property name="toolTip">
<string>Effective channel sample rate (kS/s)</string>
<string>TV line based sample rate (kS/s)</string>
</property>
<property name="text">
<string> 00000.00k</string>
@ -666,12 +666,6 @@
</item>
<item>
<widget class="QComboBox" name="standard">
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>TV standard scheme</string>
</property>
@ -692,12 +686,12 @@
</item>
<item>
<property name="text">
<string>ShI</string>
<string>ShortI</string>
</property>
</item>
<item>
<property name="text">
<string>ShNI</string>
<string>ShortNI</string>
</property>
</item>
<item>

View File

@ -29,7 +29,7 @@
const PluginDescriptor ATVDemodPlugin::m_ptrPluginDescriptor =
{
QString("ATV Demodulator"),
QString("4.11.6"),
QString("4.12.2"),
QString("(c) F4HKW for F4EXB / SDRAngel"),
QString("https://github.com/f4exb/sdrangel"),
true,

View File

@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "atvdemodreport.h"
MESSAGE_CLASS_DEFINITION(ATVDemodReport::MsgReportEffectiveSampleRate, Message)
ATVDemodReport::ATVDemodReport()
{ }
ATVDemodReport::~ATVDemodReport()
{ }

View File

@ -0,0 +1,57 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_ATVDEMODREPORT_H
#define INCLUDE_ATVDEMODREPORT_H
#include <QObject>
#include "util/message.h"
class ATVDemodReport : public QObject
{
Q_OBJECT
public:
class MsgReportEffectiveSampleRate : public Message
{
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
int getNbPointsPerLine() const { return m_nbPointsPerLine; }
static MsgReportEffectiveSampleRate* create(int sampleRate, int nbPointsPerLine)
{
return new MsgReportEffectiveSampleRate(sampleRate, nbPointsPerLine);
}
protected:
int m_sampleRate;
int m_nbPointsPerLine;
MsgReportEffectiveSampleRate(int sampleRate, int nbPointsPerLine) :
Message(),
m_sampleRate(sampleRate),
m_nbPointsPerLine(nbPointsPerLine)
{ }
};
ATVDemodReport();
~ATVDemodReport();
};
#endif // INCLUDE_ATVDEMODREPORT_H

View File

@ -30,77 +30,61 @@ ATVDemodSettings::ATVDemodSettings() :
void ATVDemodSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_forceDecimator = false;
m_bfoFrequency = 0.0f;
m_atvModulation = ATV_FM1;
m_fmDeviation = 0.5f;
m_fftFiltering = false;
m_fftOppBandwidth = 0;
m_fftBandwidth = 6000;
m_nbLines = 625;
m_fps = 25;
m_atvStd = ATVStdPAL625;
m_hSync = false;
m_vSync = false;
m_invertVideo = false;
m_halfFrames = false; // m_fltRatioOfRowsToDisplay = 1.0
m_levelSynchroTop = 0.1f;
m_levelBlack = 0.3f;
m_lineTimeFactor = 0;
m_topTimeFactor = 0;
m_fpsIndex = 1; // 25 FPS
m_halfImage = false; // m_fltRatioOfRowsToDisplay = 1.0
m_RFBandwidthFactor = 10;
m_OppBandwidthFactor = 10;
m_nbLinesIndex = 1; // 625 lines
m_intFrequencyOffset = 0;
m_enmModulation = ATV_FM1;
m_fltRFBandwidth = 0;
m_fltRFOppBandwidth = 0;
m_blnFFTFiltering = false;
m_blndecimatorEnable = false;
m_fltBFOFrequency = 0.0f;
m_fmDeviation = 0.5f;
m_intSampleRate = 0;
m_enmATVStandard = ATVStdPAL625;
m_intNumberOfLines = 625;
m_fltLineDuration = 0.0f;
m_fltTopDuration = 0.0f;
m_fltFramePerS = 25.0f;
m_fltRatioOfRowsToDisplay = 1.0f;
m_fltVoltLevelSynchroTop = 0.0f;
m_fltVoltLevelSynchroBlack = 1.0f;
m_blnHSync = false;
m_blnVSync = false;
m_blnInvertVideo = false;
m_intVideoTabIndex = 0;
m_fltLineTimeMultiplier = 1.0f;
m_fltTopTimeMultiplier = 1.0f;
m_rfSliderDivisor = 1;
m_intTVSampleRate = 0;
m_intNumberSamplePerLine = 0;
m_rgbColor = QColor(255, 255, 255).rgb();
m_title = "ATV Demodulator";
m_udpAddress = "127.0.0.1";
m_udpPort = 9999;
m_streamIndex = 0;
}
QByteArray ATVDemodSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_intFrequencyOffset);
s.writeS64(1, m_inputFrequencyOffset);
s.writeU32(2, m_rgbColor);
s.writeS32(3, roundf(m_fltVoltLevelSynchroTop*1000.0)); // mV
s.writeS32(4, roundf(m_fltVoltLevelSynchroBlack*1000.0)); // mV
s.writeS32(5, m_lineTimeFactor); // added UI
s.writeS32(6, m_topTimeFactor); // added UI
s.writeS32(7, m_enmModulation);
s.writeS32(8, m_fpsIndex); // added UI
s.writeBool(9, m_blnHSync);
s.writeBool(10,m_blnVSync);
s.writeBool(11, m_halfImage); // added UI
s.writeS32(12, m_RFBandwidthFactor); // added UI
s.writeS32(13, m_OppBandwidthFactor); // added UI
s.writeS32(14, roundf(m_fltBFOFrequency));
s.writeBool(15, m_blnInvertVideo);
s.writeS32(16, m_nbLinesIndex); // added UI
s.writeS32(3, roundf(m_levelSynchroTop*1000.0)); // mV
s.writeS32(4, roundf(m_levelBlack*1000.0)); // mV
s.writeS32(5, m_lineTimeFactor);
s.writeS32(6, m_topTimeFactor);
s.writeS32(7, m_atvModulation);
s.writeS32(8, m_fps);
s.writeBool(9, m_hSync);
s.writeBool(10,m_vSync);
s.writeBool(11, m_halfFrames);
s.writeU32(12, m_fftBandwidth);
s.writeU32(13, m_fftOppBandwidth);
s.writeS32(14, m_bfoFrequency);
s.writeBool(15, m_invertVideo);
s.writeS32(16, m_nbLines);
s.writeS32(17, roundf(m_fmDeviation * 500.0));
s.writeS32(18, m_enmATVStandard);
s.writeS32(18, m_atvStd);
if (m_channelMarker) {
s.writeBlob(19, m_channelMarker->serialize());
}
s.writeString(20, m_title);
s.writeS32(21, m_streamIndex);
return s.final();
}
@ -120,32 +104,34 @@ bool ATVDemodSettings::deserialize(const QByteArray& arrData)
QByteArray bytetmp;
int tmp;
d.readS32(1, &m_intFrequencyOffset, 0);
d.readS64(1, &m_inputFrequencyOffset, 0);
// TODO: rgb color
d.readS32(3, &tmp, 100);
m_fltVoltLevelSynchroTop = tmp / 1000.0f;
m_levelSynchroTop = tmp / 1000.0f;
d.readS32(4, &tmp, 310);
m_fltVoltLevelSynchroBlack = tmp / 1000.0f;
d.readS32(5, &m_lineTimeFactor, 0); // added UI
d.readS32(6, &m_topTimeFactor, 0); // added UI
m_levelBlack = tmp / 1000.0f;
d.readS32(5, &m_lineTimeFactor, 0);
d.readS32(6, &m_topTimeFactor, 0);
d.readS32(7, &tmp, 0);
m_enmModulation = static_cast<ATVModulation>(tmp);
d.readS32(8, &m_fpsIndex, 1); // added UI
d.readBool(9, &m_blnHSync, false);
d.readBool(10, &m_blnVSync, false);
d.readBool(11, &m_halfImage, false); // added UI
d.readS32(12, &m_RFBandwidthFactor, 10); // added UI
d.readS32(13, &m_OppBandwidthFactor, 10); // added UI
d.readS32(14, &tmp, 10);
m_fltBFOFrequency = static_cast<float>(tmp);
d.readBool(15, &m_blnInvertVideo, false);
d.readS32(16, &m_nbLinesIndex, 1); // added UI
m_atvModulation = static_cast<ATVModulation>(tmp);
d.readS32(8, &tmp, 25);
int fpsIndex = getFpsIndex(tmp);
m_fps = getFps(fpsIndex);
d.readBool(9, &m_hSync, false);
d.readBool(10, &m_vSync, false);
d.readBool(11, &m_halfFrames, false);
d.readU32(12, &m_fftBandwidth, 6000);
d.readU32(13, &m_fftOppBandwidth, 0);
d.readS32(14, &m_bfoFrequency, 0);
d.readBool(15, &m_invertVideo, false);
d.readS32(16, &tmp, 625);
int nbLinesIndex = getNumberOfLinesIndex(tmp);
m_nbLines = getNumberOfLines(nbLinesIndex);
d.readS32(17, &tmp, 250);
m_fmDeviation = tmp / 500.0f;
d.readS32(18, &tmp, 1);
m_enmATVStandard = static_cast<ATVStd>(tmp);
// TODO: calculate values from UI values
m_atvStd = static_cast<ATVStd>(tmp);
d.readS32(21, &m_streamIndex, 0);
return true;
}
@ -156,55 +142,47 @@ bool ATVDemodSettings::deserialize(const QByteArray& arrData)
}
}
int ATVDemodSettings::getEffectiveSampleRate()
{
return m_blndecimatorEnable ? m_intTVSampleRate : m_intSampleRate;
}
float ATVDemodSettings::getFps(int fpsIndex)
int ATVDemodSettings::getFps(int fpsIndex)
{
switch(fpsIndex)
{
case 0:
return 30.0f;
return 30;
break;
case 2:
return 20.0f;
return 20;
break;
case 3:
return 16.0f;
return 16;
break;
case 4:
return 12.0f;
return 12;
break;
case 5:
return 10.0f;
return 10;
break;
case 6:
return 8.0f;
return 8;
break;
case 7:
return 5.0f;
return 5;
break;
case 8:
return 2.0f;
return 2;
break;
case 9:
return 1.0f;
return 1;
break;
case 1:
default:
return 25.0f;
return 25;
break;
}
}
int ATVDemodSettings::getFpsIndex(float fps)
int ATVDemodSettings::getFpsIndex(int fps)
{
int fpsi = roundf(fps);
if (fpsi <= 1) {
if (fps <= 1) {
return 9;
} else if (fps <= 2) {
return 8;
@ -305,92 +283,88 @@ int ATVDemodSettings::getNumberOfLinesIndex(int nbLines)
}
}
float ATVDemodSettings::getNominalLineTime(int nbLinesIndex, int fpsIndex)
float ATVDemodSettings::getNominalLineTime(int nbLines, int fps)
{
float fps = getFps(fpsIndex);
int nbLines = getNumberOfLines(nbLinesIndex);
return 1.0f / (nbLines * fps);
return 1.0f / ((float) nbLines * (float) fps);
}
/**
* calculates m_fltLineTimeMultiplier
*/
void ATVDemodSettings::lineTimeUpdate()
void ATVDemodSettings::lineTimeUpdate(unsigned int sampleRate)
{
float nominalLineTime = getNominalLineTime(m_nbLinesIndex, m_fpsIndex);
float nominalLineTime = getNominalLineTime(m_nbLines, m_fps);
int lineTimeScaleFactor = (int) std::log10(nominalLineTime);
if (getEffectiveSampleRate() == 0) {
if (sampleRate == 0) {
m_fltLineTimeMultiplier = std::pow(10.0, lineTimeScaleFactor-3);
} else {
m_fltLineTimeMultiplier = 1.0f / getEffectiveSampleRate();
m_fltLineTimeMultiplier = 1.0f / sampleRate;
}
}
float ATVDemodSettings::getLineTime()
float ATVDemodSettings::getLineTime(unsigned int sampleRate)
{
return getNominalLineTime(m_nbLinesIndex, m_fpsIndex) + m_fltLineTimeMultiplier * m_lineTimeFactor;
}
int ATVDemodSettings::getLineTimeFactor()
{
return roundf((m_fltLineDuration - getNominalLineTime(m_nbLinesIndex, m_fpsIndex)) / m_fltLineTimeMultiplier);
lineTimeUpdate(sampleRate);
float nominalLineTime = 1.0f / ((float) m_nbLines * (float) m_fps);
return nominalLineTime + m_fltLineTimeMultiplier * m_lineTimeFactor;
}
/**
* calculates m_fltTopTimeMultiplier
*/
void ATVDemodSettings::topTimeUpdate()
void ATVDemodSettings::topTimeUpdate(unsigned int sampleRate)
{
float nominalTopTime = getNominalLineTime(m_nbLinesIndex, m_fpsIndex) * (4.7f / 64.0f);
float nominalTopTime = getNominalLineTime(m_nbLines, m_fps) * (4.7f / 64.0f);
int topTimeScaleFactor = (int) std::log10(nominalTopTime);
if (getEffectiveSampleRate() == 0) {
if (sampleRate == 0) {
m_fltTopTimeMultiplier = std::pow(10.0, topTimeScaleFactor-3);
} else {
m_fltTopTimeMultiplier = 1.0f / getEffectiveSampleRate();
m_fltTopTimeMultiplier = 1.0f / sampleRate;
}
}
float ATVDemodSettings::getTopTime()
float ATVDemodSettings::getTopTime(unsigned int sampleRate)
{
return getNominalLineTime(m_nbLinesIndex, m_fpsIndex) * (4.7f / 64.0f) + m_fltTopTimeMultiplier * m_topTimeFactor;
topTimeUpdate(sampleRate);
return getNominalLineTime(m_nbLines, m_fps) * (4.7f / 64.0f) + m_fltTopTimeMultiplier * m_topTimeFactor;
}
int ATVDemodSettings::getTopTimeFactor()
int ATVDemodSettings::getRFSliderDivisor(unsigned int sampleRate)
{
return roundf((m_fltTopDuration - getNominalLineTime(m_nbLinesIndex, m_fpsIndex) * (4.7f / 64.0f)) / m_fltLineTimeMultiplier);
}
void ATVDemodSettings::convertFromUIValues()
{
lineTimeUpdate();
m_fltLineDuration = getLineTime();
topTimeUpdate();
m_fltTopDuration = getTopTime();
m_fltRFBandwidth = m_RFBandwidthFactor * getRFSliderDivisor() * 1.0f;
m_fltRFOppBandwidth = m_OppBandwidthFactor * getRFSliderDivisor() * 1.0f;
m_fltFramePerS = getFps(m_fpsIndex);
m_intNumberOfLines = getNumberOfLines(m_nbLinesIndex);
}
void ATVDemodSettings::convertToUIValues()
{
m_lineTimeFactor = getTopTimeFactor();
m_topTimeFactor = getTopTimeFactor();
m_RFBandwidthFactor = roundf(m_fltRFBandwidth / getRFSliderDivisor());
m_RFBandwidthFactor = m_RFBandwidthFactor < 1 ? 1 : m_RFBandwidthFactor;
m_OppBandwidthFactor = roundf(m_fltRFOppBandwidth / getRFSliderDivisor());
m_fpsIndex = getFpsIndex(m_fltFramePerS);
m_nbLinesIndex = getNumberOfLinesIndex(m_intNumberOfLines);
}
int ATVDemodSettings::getRFSliderDivisor()
{
int sampleRate = getEffectiveSampleRate();
int scaleFactor = (int) std::log10(sampleRate/2);
return std::pow(10.0, scaleFactor-1);
}
float ATVDemodSettings::getRFBandwidthDivisor(ATVModulation modulation)
{
switch(modulation)
{
case ATV_USB:
case ATV_LSB:
return 1.05f;
break;
case ATV_FM1:
case ATV_FM2:
case ATV_AM:
default:
return 2.2f;
}
}
void ATVDemodSettings::getBaseValues(int sampleRate, int linesPerSecond, int& tvSampleRate, uint32_t& nbPointsPerLine)
{
int maxPoints = sampleRate / linesPerSecond;
int i = maxPoints;
for (; i > 0; i--)
{
if ((i * linesPerSecond) % 10 == 0) {
break;
}
}
nbPointsPerLine = i == 0 ? maxPoints : i;
tvSampleRate = nbPointsPerLine * linesPerSecond;
}

View File

@ -45,50 +45,36 @@ struct ATVDemodSettings
ATVStdHSkip
};
// Added fields that correspond directly to UI inputs
int m_lineTimeFactor; //!< added: +/- 100 something
int m_topTimeFactor; //!< added: +/- 30 something
int m_fpsIndex; //!< added: FPS list index
bool m_halfImage; //!< added: true => m_fltRatioOfRowsToDisplay = 0.5, false => m_fltRatioOfRowsToDisplay = 1.0
int m_RFBandwidthFactor; //!< added: [1:100]
int m_OppBandwidthFactor; //!< added: [0:100]
int m_nbLinesIndex; //!< added: #lines list index
// RF settings
qint64 m_inputFrequencyOffset; //!< Offset from baseband center frequency
bool m_forceDecimator; //!< Force use of rational decimator when channel sample rate matches TV sample rate
int m_bfoFrequency; //!< BFO frequency (Hz)
ATVModulation m_atvModulation; //!< RF modulation type
float m_fmDeviation; //!< Expected FM deviation
bool m_fftFiltering; //!< Toggle FFT filter
unsigned int m_fftOppBandwidth; //!< FFT filter lower frequency cutoff (Hz)
unsigned int m_fftBandwidth; //!< FFT filter high frequency cutoff (Hz)
// Former RF
int m_intFrequencyOffset;
ATVModulation m_enmModulation;
float m_fltRFBandwidth;
float m_fltRFOppBandwidth;
bool m_blnFFTFiltering;
bool m_blndecimatorEnable;
float m_fltBFOFrequency;
float m_fmDeviation;
// ATV settings
int m_nbLines; //!< Number of lines per full frame
int m_fps; //!< Number of frames per second
ATVStd m_atvStd; //!< Standard
bool m_hSync; //!< Enable/disable horizontal sybchronization
bool m_vSync; //!< Enable/disable vertical synchronization
bool m_invertVideo; //!< Toggle invert video
bool m_halfFrames; //!< Toggle half frames processing
float m_levelSynchroTop; //!< Horizontal synchronization top level (0.0 to 1.0 scale)
float m_levelBlack; //!< Black level (0.0 to 1.0 scale)
int m_lineTimeFactor; //!< added: +/- 100 something
int m_topTimeFactor; //!< added: +/- 30 something
// Former <none>
int m_intSampleRate;
ATVStd m_enmATVStandard;
int m_intNumberOfLines;
float m_fltLineDuration;
float m_fltTopDuration;
float m_fltFramePerS;
float m_fltRatioOfRowsToDisplay;
float m_fltVoltLevelSynchroTop;
float m_fltVoltLevelSynchroBlack;
bool m_blnHSync;
bool m_blnVSync;
bool m_blnInvertVideo;
int m_intVideoTabIndex;
// Former private
int m_intTVSampleRate;
int m_intNumberSamplePerLine;
// new
// common channel settings
quint32 m_rgbColor;
QString m_title;
QString m_udpAddress;
uint16_t m_udpPort;
Serializable *m_channelMarker;
int m_streamIndex;
ATVDemodSettings();
void resetToDefaults();
@ -96,31 +82,25 @@ struct ATVDemodSettings
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
int getEffectiveSampleRate();
float getLineTime();
int getLineTimeFactor();
float getTopTime();
int getTopTimeFactor();
int getRFSliderDivisor();
float getLineTime(unsigned int sampleRate);
float getTopTime(unsigned int sampleRate);
int getRFSliderDivisor(unsigned int sampleRate);
void convertFromUIValues();
void convertToUIValues();
static float getFps(int fpsIndex);
static int getFps(int fpsIndex);
static int getFpsIndex(int fps);
static int getNumberOfLines(int nbLinesIndex);
static int getFpsIndex(float fps);
static int getNumberOfLinesIndex(int nbLines);
static float getNominalLineTime(int nbLinesIndex, int fpsIndex);
static float getNominalLineTime(int nbLines, int fps);
static float getRFBandwidthDivisor(ATVModulation modulation);
static void getBaseValues(int sampleRate, int linesPerSecond, int& tvSampleRate, uint32_t& nbPointsPerLine);
private:
void lineTimeUpdate();
void topTimeUpdate();
void lineTimeUpdate(unsigned int sampleRate);
void topTimeUpdate(unsigned int sampleRate);
float m_fltLineTimeMultiplier;
float m_fltTopTimeMultiplier;
int m_rfSliderDivisor;
};
#endif /* PLUGINS_CHANNELRX_DEMODATV_ATVDEMODSETTINGS_H_ */

View File

@ -0,0 +1,595 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QTime>
#include <QDebug>
#include <stdio.h>
#include <complex.h>
#include "audio/audiooutput.h"
#include "atvdemodsink.h"
const int ATVDemodSink::m_ssbFftLen = 1024;
ATVDemodSink::ATVDemodSink() :
m_channelSampleRate(1000000),
m_channelFrequencyOffset(0),
m_tvSampleRate(1000000),
m_samplesPerLine(100),
m_videoTabIndex(0),
m_scopeSink(nullptr),
m_registeredTVScreen(nullptr),
m_numberSamplesPerHTop(0),
m_imageIndex(0),
m_synchroSamples(0),
m_horizontalSynchroDetected(false),
m_verticalSynchroDetected(false),
m_ampLineSum(0.0f),
m_ampLineAvg(0.0f),
m_effMin(2000000.0f),
m_effMax(-2000000.0f),
m_ampMin(0.0f),
m_ampMax(0.3f),
m_ampDelta(0.3f),
m_ampSample(0.0f),
m_colIndex(0),
m_sampleIndex(0),
m_amSampleIndex(0),
m_rowIndex(0),
m_lineIndex(0),
m_objAvgColIndex(3),
m_bfoPLL(200/1000000, 100/1000000, 0.01),
m_bfoFilter(200.0, 1000000.0, 0.9),
m_interpolatorDistance(1.0f),
m_interpolatorDistanceRemain(0.0f),
m_DSBFilter(nullptr),
m_DSBFilterBuffer(nullptr),
m_DSBFilterBufferIndex(0),
m_messageQueueToGUI(nullptr)
{
qDebug("ATVDemodSink::ATVDemodSink");
//*************** ATV PARAMETERS ***************
//m_intNumberSamplePerLine=0;
m_synchroSamples=0;
m_interleaved = true;
m_firstRowIndexEven = 0;
m_firstRowIndexOdd = 0;
m_DSBFilter = new fftfilt(m_settings.m_fftBandwidth / (float) m_tvSampleRate, 2*m_ssbFftLen); // arbitrary cutoff
m_DSBFilterBuffer = new Complex[m_ssbFftLen];
std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer + m_ssbFftLen, Complex{0.0, 0.0});
std::fill(m_fltBufferI, m_fltBufferI+6, 0.0f);
std::fill(m_fltBufferQ, m_fltBufferQ+6, 0.0f);
m_objPhaseDiscri.setFMScaling(1.0f);
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
ATVDemodSink::~ATVDemodSink()
{
delete m_DSBFilter;
delete[] m_DSBFilterBuffer;
}
void ATVDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
float fltI;
float fltQ;
Complex ci;
//********** Let's rock and roll buddy ! **********
//********** Accessing ATV Screen context **********
for (SampleVector::const_iterator it = begin; it != end; ++it /* ++it **/)
{
fltI = it->real();
fltQ = it->imag();
Complex c(fltI, fltQ);
if (m_settings.m_inputFrequencyOffset != 0) {
c *= m_nco.nextIQ();
}
if ((m_tvSampleRate == m_channelSampleRate) && (!m_settings.m_forceDecimator)) // no decimation
{
demod(c);
}
else
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
demod(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
if ((m_videoTabIndex == 1) && (m_scopeSink)) // do only if scope tab is selected and scope is available
{
m_scopeSink->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), false); // m_ssb = positive only
m_scopeSampleBuffer.clear();
}
}
void ATVDemodSink::demod(Complex& c)
{
float divSynchroBlack = 1.0f - m_settings.m_levelBlack;
float sampleNormI;
float sampleNormQ;
float sampleNorm;
float sample;
int sampleVideo;
//********** FFT filtering **********
if (m_settings.m_fftFiltering)
{
int n_out;
Complex *filtered;
n_out = m_DSBFilter->runAsym(c, &filtered, m_settings.m_atvModulation != ATVDemodSettings::ATV_LSB); // all usb except explicitely lsb
if (n_out > 0)
{
std::copy(filtered, filtered + n_out, m_DSBFilterBuffer);
m_DSBFilterBufferIndex = 0;
}
else if (m_DSBFilterBufferIndex < m_ssbFftLen - 1) // safe
{
m_DSBFilterBufferIndex++;
}
}
//********** demodulation **********
float fftScale = 1.0f;
const float& fltI = m_settings.m_fftFiltering ? m_DSBFilterBuffer[m_DSBFilterBufferIndex].real() : c.real();
const float& fltQ = m_settings.m_fftFiltering ? m_DSBFilterBuffer[m_DSBFilterBufferIndex].imag() : c.imag();
double magSq;
if ((m_settings.m_atvModulation == ATVDemodSettings::ATV_FM1) || (m_settings.m_atvModulation == ATVDemodSettings::ATV_FM2))
{
//Amplitude FM
magSq = fltI*fltI + fltQ*fltQ;
m_objMagSqAverage(magSq);
sampleNorm = sqrt(magSq);
sampleNormI = fltI/sampleNorm;
sampleNormQ = fltQ/sampleNorm;
//-2 > 2 : 0 -> 1 volt
//0->0.3 synchro 0.3->1 image
if (m_settings.m_atvModulation == ATVDemodSettings::ATV_FM1)
{
//YDiff Cd
sample = m_fltBufferI[0]*(sampleNormQ - m_fltBufferQ[1]);
sample -= m_fltBufferQ[0]*(sampleNormI - m_fltBufferI[1]);
sample += 2.0f;
sample /= 4.0f;
}
else
{
//YDiff Folded
sample = m_fltBufferI[2]*((m_fltBufferQ[5]-sampleNormQ)/16.0f + m_fltBufferQ[1] - m_fltBufferQ[3]);
sample -= m_fltBufferQ[2]*((m_fltBufferI[5]-sampleNormI)/16.0f + m_fltBufferI[1] - m_fltBufferI[3]);
sample += 2.125f;
sample /= 4.25f;
m_fltBufferI[5] = m_fltBufferI[4];
m_fltBufferQ[5] = m_fltBufferQ[4];
m_fltBufferI[4] = m_fltBufferI[3];
m_fltBufferQ[4] = m_fltBufferQ[3];
m_fltBufferI[3] = m_fltBufferI[2];
m_fltBufferQ[3] = m_fltBufferQ[2];
m_fltBufferI[2] = m_fltBufferI[1];
m_fltBufferQ[2] = m_fltBufferQ[1];
}
m_fltBufferI[1] = m_fltBufferI[0];
m_fltBufferQ[1] = m_fltBufferQ[0];
m_fltBufferI[0] = sampleNormI;
m_fltBufferQ[0] = sampleNormQ;
if (m_settings.m_fmDeviation != 1.0f)
{
sample = ((sample - 0.5f) / m_settings.m_fmDeviation) + 0.5f;
}
}
else if (m_settings.m_atvModulation == ATVDemodSettings::ATV_AM)
{
//Amplitude AM
magSq = fltI*fltI + fltQ*fltQ;
m_objMagSqAverage(magSq);
sampleNorm = sqrt(magSq);
sample = sampleNorm / SDR_RX_SCALEF;
}
else if ((m_settings.m_atvModulation == ATVDemodSettings::ATV_USB) || (m_settings.m_atvModulation == ATVDemodSettings::ATV_LSB))
{
magSq = fltI*fltI + fltQ*fltQ;
m_objMagSqAverage(magSq);
sampleNorm = sqrt(magSq);
Real bfoValues[2];
float fltFiltered = m_bfoFilter.run(fltI);
m_bfoPLL.process(fltFiltered, bfoValues);
// do the mix
float mixI = fltI * bfoValues[0] - fltQ * bfoValues[1];
float mixQ = fltI * bfoValues[1] + fltQ * bfoValues[0];
if (m_settings.m_atvModulation == ATVDemodSettings::ATV_USB) {
sample = (mixI + mixQ);
} else {
sample = (mixI - mixQ);
}
}
else if (m_settings.m_atvModulation == ATVDemodSettings::ATV_FM3)
{
float rawDeviation;
sample = m_objPhaseDiscri.phaseDiscriminatorDelta(c, magSq, rawDeviation) + 0.5f;
m_objMagSqAverage(magSq);
sampleNorm = sqrt(magSq);
}
else
{
magSq = fltI*fltI + fltQ*fltQ;
m_objMagSqAverage(magSq);
sampleNorm = sqrt(magSq);
sample = 0.0f;
}
//********** AM sample normalization and coarse scale estimation **********
if ((m_settings.m_atvModulation == ATVDemodSettings::ATV_AM)
|| (m_settings.m_atvModulation == ATVDemodSettings::ATV_USB)
|| (m_settings.m_atvModulation == ATVDemodSettings::ATV_LSB))
{
// Mini and Maxi Amplitude tracking
if (sample < m_effMin) {
m_effMin = sample;
}
if (sample > m_effMax) {
m_effMax = sample;
}
if (m_amSampleIndex < m_samplesPerLine * m_settings.m_nbLines) // do not extend estimation period past a full image
{
m_amSampleIndex++;
}
else
{
// scale signal based on extrema on the estimation period
m_ampMin = m_effMin;
m_ampMax = m_effMax;
m_ampDelta = (m_ampMax - m_ampMin) * 0.3f;
m_ampSample = 0.3f; // allow passing to fine scale estimation
if (m_ampDelta <= 0.0) {
m_ampDelta = 0.3f;
}
qDebug("ATVDemod::demod: m_ampMin: %f m_ampMax: %f m_ampDelta: %f", m_ampMin, m_ampMax, m_ampDelta);
//Reset extrema
m_effMin = 2000000.0f;
m_effMax = -2000000.0;
m_amSampleIndex = 0;
}
//Normalisation of current sample
sample -= m_ampMin;
sample /= (m_ampDelta * 3.1f);
}
sample = m_settings.m_invertVideo ? 1.0f - sample : sample;
// 0.0 -> 1.0
sample = (sample < 0.0f) ? 0.0f : (sample > 1.0f) ? 1.0f : sample;
if ((m_videoTabIndex == 1) && (m_scopeSink != 0)) { // feed scope buffer only if scope is present and visible
m_scopeSampleBuffer.push_back(Sample(sample*SDR_RX_SCALEF, 0.0f));
}
//********** gray level **********
// -0.3 -> 0.7 / 0.7
sampleVideo = (int) (255.0*(sample - m_settings.m_levelBlack) / (1.0f - m_settings.m_levelBlack));
// 0 -> 255
sampleVideo = (sampleVideo < 0) ? 0 : (sampleVideo > 255) ? 255 : sampleVideo;
//********** process video sample **********
if (m_registeredTVScreen) // can process only if the screen is available (set via the GUI)
{
if (m_settings.m_atvStd == ATVDemodSettings::ATVStdHSkip) {
processHSkip(sample, sampleVideo);
} else {
processClassic(sample, sampleVideo);
}
}
}
void ATVDemodSink::applyStandard(int sampleRate, const ATVDemodSettings& settings, float lineDuration)
{
switch(settings.m_atvStd)
{
case ATVDemodSettings::ATVStdHSkip:
// what is left in a line for the image
m_numberOfSyncLines = 0;
m_numberOfBlackLines = 0;
m_numberOfEqLines = 0; // not applicable
m_numberSamplesHSyncCrop = (int) (0.09f * lineDuration * sampleRate); // 9% of full line empirically
m_interleaved = false; // irrelevant
m_firstRowIndexEven = 0; // irrelevant
m_firstRowIndexOdd = 0; // irrelevant
break;
case ATVDemodSettings::ATVStdShort:
// what is left in a line for the image
m_numberOfSyncLines = 4;
m_numberOfBlackLines = 5;
m_numberOfEqLines = 0;
m_numberSamplesHSyncCrop = (int) (0.085f * lineDuration * sampleRate); // 8.5% of full line empirically
m_interleaved = false;
m_firstRowIndexEven = 0; // irrelevant
m_firstRowIndexOdd = 0; // irrelevant
break;
case ATVDemodSettings::ATVStdShortInterleaved:
// what is left in a line for the image
m_numberOfSyncLines = 4;
m_numberOfBlackLines = 7;
m_numberOfEqLines = 0;
m_numberSamplesHSyncCrop = (int) (0.085f * lineDuration * sampleRate); // 8.5% of full line empirically
m_interleaved = true;
m_firstRowIndexEven = 0;
m_firstRowIndexOdd = 1;
break;
case ATVDemodSettings::ATVStd405: // Follows loosely the 405 lines standard
// what is left in a ine for the image
m_numberOfSyncLines = 24; // (15+7)*2 - 20
m_numberOfBlackLines = 30; // above + 6
m_numberOfEqLines = 3;
m_numberSamplesHSyncCrop = (int) (0.085f * lineDuration * sampleRate); // 8.5% of full line empirically
m_interleaved = true;
m_firstRowIndexEven = 0;
m_firstRowIndexOdd = 3;
break;
case ATVDemodSettings::ATVStdPAL525: // Follows PAL-M standard
// what is left in a 64/1.008 us line for the image
m_numberOfSyncLines = 40; // (15+15)*2 - 20
m_numberOfBlackLines = 46; // above + 6
m_numberOfEqLines = 3;
m_numberSamplesHSyncCrop = (int) (0.085f * lineDuration * sampleRate); // 8.5% of full line empirically
m_interleaved = true;
m_firstRowIndexEven = 0;
m_firstRowIndexOdd = 3;
break;
case ATVDemodSettings::ATVStdPAL625: // Follows PAL-B/G/H standard
default:
// what is left in a 64 us line for the image
m_numberOfSyncLines = 44; // (15+17)*2 - 20
m_numberOfBlackLines = 50; // above + 6
m_numberOfEqLines = 3;
m_numberSamplesHSyncCrop = (int) (0.085f * lineDuration * sampleRate); // 8.5% of full line empirically
m_interleaved = true;
m_firstRowIndexEven = 0;
m_firstRowIndexOdd = 3;
}
// for now all standards apply this
m_numberSamplesPerLineSignals = (int) ((12.0f/64.0f) * lineDuration * sampleRate); // 12.0 = 2.6 + 4.7 + 4.7 : front porch + horizontal sync pulse + back porch
m_numberSamplesPerHSync = (int) ((9.6f/64.0f) * lineDuration * sampleRate); // 9.4 = 4.7 + 4.7 : horizontal sync pulse + back porch
m_numberSamplesPerHTopNom = (int) ((4.7f/64.0f) * lineDuration * sampleRate); // 4.7 : horizontal sync pulse (ultra black) nominal value
m_numberSamplesPerHTop = m_numberSamplesPerHTopNom + settings.m_topTimeFactor; // adjust the value used in the system
}
bool ATVDemodSink::getBFOLocked()
{
if ((m_settings.m_atvModulation == ATVDemodSettings::ATV_USB) || (m_settings.m_atvModulation == ATVDemodSettings::ATV_LSB)) {
return m_bfoPLL.locked();
} else {
return false;
}
}
void ATVDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "ATVDemodSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if (channelSampleRate == 0)
{
qDebug("ATVDemodSink::applyChannelSettings: aborting");
return;
}
if ((channelFrequencyOffset != m_channelFrequencyOffset) ||
(channelSampleRate != m_channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((channelSampleRate != m_channelSampleRate) || force)
{
ATVDemodSettings::getBaseValues(channelSampleRate, m_settings.m_nbLines * m_settings.m_fps, m_tvSampleRate, m_samplesPerLineNom);
m_samplesPerLine = m_samplesPerLineNom + m_settings.m_lineTimeFactor;
qDebug() << "ATVDemodSink::applyChannelSettings:"
<< " m_tvSampleRate: " << m_tvSampleRate
<< " m_fftBandwidth: " << m_settings.m_fftBandwidth
<< " m_fftOppBandwidth:" << m_settings.m_fftOppBandwidth
<< " m_bfoFrequency: " << m_settings.m_bfoFrequency;
if (m_tvSampleRate > 0)
{
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_tvSampleRate / (Real) channelSampleRate;
m_interpolator.create(24,
m_tvSampleRate,
m_settings.m_fftBandwidth / ATVDemodSettings::getRFBandwidthDivisor(m_settings.m_atvModulation),
3.0
);
}
else
{
m_tvSampleRate = channelSampleRate;
}
m_DSBFilter->create_asym_filter(
m_settings.m_fftOppBandwidth / (float) m_tvSampleRate,
m_settings.m_fftBandwidth / (float) m_tvSampleRate
);
std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer + m_ssbFftLen, Complex{0.0, 0.0});
m_DSBFilterBufferIndex = 0;
m_bfoPLL.configure((float) m_settings.m_bfoFrequency / (float) m_tvSampleRate,
100.0 / m_tvSampleRate,
0.01);
m_bfoFilter.setFrequencies(m_tvSampleRate, m_settings.m_bfoFrequency);
}
applyStandard(m_tvSampleRate, m_settings, ATVDemodSettings::getNominalLineTime(m_settings.m_nbLines, m_settings.m_fps));
if (m_registeredTVScreen)
{
m_registeredTVScreen->resizeTVScreen(
m_samplesPerLine - m_numberSamplesPerLineSignals,
m_settings.m_nbLines - m_numberOfBlackLines
);
}
m_imageIndex = 0;
m_colIndex = 0;
m_rowIndex = 0;
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void ATVDemodSink::applySettings(const ATVDemodSettings& settings, bool force)
{
qDebug() << "ATVDemodSink::applySettings:"
<< "m_inputFrequencyOffset:" << settings.m_inputFrequencyOffset
<< "m_forceDecimator:" << settings.m_forceDecimator
<< "m_bfoFrequency:" << settings.m_bfoFrequency
<< "m_atvModulation:" << settings.m_atvModulation
<< "m_fmDeviation:" << settings.m_fmDeviation
<< "m_fftFiltering:" << settings.m_fftFiltering
<< "m_fftOppBandwidth:" << settings.m_fftOppBandwidth
<< "m_fftBandwidth:" << settings.m_fftBandwidth
<< "m_nbLines:" << settings.m_nbLines
<< "m_fps:" << settings.m_fps
<< "m_atvStd:" << settings.m_atvStd
<< "m_hSync:" << settings.m_hSync
<< "m_vSync:" << settings.m_vSync
<< "m_invertVideo:" << settings.m_invertVideo
<< "m_halfFrames:" << settings.m_halfFrames
<< "m_levelSynchroTop:" << settings.m_levelSynchroTop
<< "m_levelBlack:" << settings.m_levelBlack
<< "m_lineTimeFactor:" << settings.m_lineTimeFactor
<< "m_topTimeFactor:" << settings.m_topTimeFactor
<< "m_rgbColor:" << settings.m_rgbColor
<< "m_title:" << settings.m_title
<< "m_udpAddress:" << settings.m_udpAddress
<< "m_udpPort:" << settings.m_udpPort
<< "force:" << force;
if ((settings.m_nbLines != m_settings.m_nbLines)
|| (settings.m_fps != m_settings.m_fps)
|| (settings.m_atvStd != m_settings.m_atvStd)
|| (settings.m_atvModulation != m_settings.m_atvModulation)
|| (settings.m_fftBandwidth != m_settings.m_fftBandwidth)
|| (settings.m_fftOppBandwidth != m_settings.m_fftOppBandwidth)
|| (settings.m_atvStd != m_settings.m_atvStd)
|| (settings.m_lineTimeFactor != m_settings.m_lineTimeFactor) || force)
{
ATVDemodSettings::getBaseValues(m_channelSampleRate, settings.m_nbLines * settings.m_fps, m_tvSampleRate, m_samplesPerLineNom);
m_samplesPerLine = m_samplesPerLineNom + settings.m_lineTimeFactor;
qDebug() << "ATVDemodSink::applySettings:"
<< " m_tvSampleRate: " << m_tvSampleRate
<< " m_fftBandwidth: " << settings.m_fftBandwidth
<< " m_fftOppBandwidth:" << settings.m_fftOppBandwidth
<< " m_bfoFrequency: " << settings.m_bfoFrequency;
if (m_tvSampleRate > 0)
{
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_tvSampleRate / (Real) m_channelSampleRate;
m_interpolator.create(24,
m_tvSampleRate,
settings.m_fftBandwidth / ATVDemodSettings::getRFBandwidthDivisor(settings.m_atvModulation),
3.0
);
}
else
{
m_tvSampleRate = m_channelSampleRate;
}
m_DSBFilter->create_asym_filter(
settings.m_fftOppBandwidth / (float) m_tvSampleRate,
settings.m_fftBandwidth / (float) m_tvSampleRate
);
std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer + m_ssbFftLen, Complex{0.0, 0.0});
m_DSBFilterBufferIndex = 0;
m_bfoPLL.configure((float) settings.m_bfoFrequency / (float) m_tvSampleRate,
100.0 / m_tvSampleRate,
0.01);
m_bfoFilter.setFrequencies(m_tvSampleRate, settings.m_bfoFrequency);
applyStandard(m_tvSampleRate, settings, ATVDemodSettings::getNominalLineTime(settings.m_nbLines, settings.m_fps));
if (m_registeredTVScreen)
{
m_registeredTVScreen->resizeTVScreen(
m_samplesPerLine - m_numberSamplesPerLineSignals,
m_settings.m_nbLines - m_numberOfBlackLines
);
}
m_imageIndex = 0;
m_colIndex = 0;
m_rowIndex = 0;
}
if ((settings.m_topTimeFactor != m_settings.m_topTimeFactor) || force) {
m_numberSamplesPerHTop = m_numberSamplesPerHTopNom + settings.m_topTimeFactor;
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
m_objPhaseDiscri.setFMScaling(1.0f / settings.m_fmDeviation);
}
m_settings = settings;
}

View File

@ -0,0 +1,433 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_ATVDEMODSINK_H
#define INCLUDE_ATVDEMODSINK_H
#include <QElapsedTimer>
#include <vector>
#include "dsp/channelsamplesink.h"
#include "dsp/basebandsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/fftfilt.h"
#include "dsp/agc.h"
#include "dsp/phaselock.h"
#include "dsp/recursivefilters.h"
#include "dsp/phasediscri.h"
#include "audio/audiofifo.h"
#include "util/movingaverage.h"
#include "gui/tvscreen.h"
#include "atvdemodsettings.h"
class ATVDemodSink : public ChannelSampleSink {
public:
ATVDemodSink();
~ATVDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setScopeSink(BasebandSampleSink* scopeSink) { m_scopeSink = scopeSink; }
void setTVScreen(TVScreen *tvScreen) { m_registeredTVScreen = tvScreen; } //!< set by the GUI
double getMagSq() const { return m_objMagSqAverage; } //!< Beware this is scaled to 2^30
bool getBFOLocked();
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
void setVideoTabIndex(int videoTabIndex) { m_videoTabIndex = videoTabIndex; }
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const ATVDemodSettings& settings, bool force = false);
private:
struct ATVConfigPrivate
{
int m_intTVSampleRate;
int m_intNumberSamplePerLine;
ATVConfigPrivate() :
m_intTVSampleRate(0),
m_intNumberSamplePerLine(0)
{}
};
/**
* Exponential average using integers and alpha as the inverse of a power of two
*/
class AvgExpInt
{
public:
AvgExpInt(int log2Alpha) : m_log2Alpha(log2Alpha), m_m1(0), m_start(true) {}
void reset() { m_start = true; }
int run(int m0)
{
if (m_start)
{
m_m1 = m0;
m_start = false;
return m0;
}
else
{
m_m1 = m0 + m_m1 - (m_m1>>m_log2Alpha);
return m_m1>>m_log2Alpha;
}
}
private:
int m_log2Alpha;
int m_m1;
bool m_start;
};
int m_channelSampleRate;
int m_channelFrequencyOffset;
int m_tvSampleRate;
unsigned int m_samplesPerLineNom; //!< number of samples per complete line (includes sync signals) - nominal value
unsigned int m_samplesPerLine; //!< number of samples per complete line (includes sync signals) - adusted value
ATVDemodSettings m_settings;
int m_videoTabIndex;
//*************** SCOPE ***************
BasebandSampleSink* m_scopeSink;
SampleVector m_scopeSampleBuffer;
//*************** ATV PARAMETERS ***************
TVScreen *m_registeredTVScreen;
//int m_intNumberSamplePerLine;
int m_numberSamplesPerHTopNom; //!< number of samples per horizontal synchronization pulse (pulse in ultra-black) - nominal value
int m_numberSamplesPerHTop; //!< number of samples per horizontal synchronization pulse (pulse in ultra-black) - adusted value
int m_numberOfSyncLines; //!< this is the number of non displayable lines at the start of a frame. First displayable row comes next.
int m_numberOfBlackLines; //!< this is the total number of lines not part of the image and is used for vertical screen size
int m_numberOfEqLines; //!< number of equalizing lines both whole and partial
int m_numberSamplesPerLineSignals; //!< number of samples in the non image part of the line (signals = front porch + pulse + back porch)
int m_numberSamplesPerHSync; //!< number of samples per horizontal synchronization pattern (pulse + back porch)
int m_numberSamplesHSyncCrop; //!< number of samples to crop from start of horizontal synchronization
bool m_interleaved; //!< interleaved image
int m_firstRowIndexEven; //!< index of the first row of an even image
int m_firstRowIndexOdd; //!< index of the first row of an even image
//*************** PROCESSING ***************
int m_imageIndex;
int m_synchroSamples;
bool m_horizontalSynchroDetected;
bool m_verticalSynchroDetected;
float m_ampLineSum;
float m_ampLineAvg;
float m_effMin;
float m_effMax;
float m_ampMin;
float m_ampMax;
float m_ampDelta; //!< calculated amplitude of HSync pulse (should be ~0.3f)
float m_ampSample;
float m_fltBufferI[6];
float m_fltBufferQ[6];
int m_colIndex;
int m_sampleIndex;
unsigned int m_amSampleIndex;
int m_rowIndex;
int m_lineIndex;
AvgExpInt m_objAvgColIndex;
int m_avgColIndex;
SampleVector m_sampleBuffer;
//*************** RF ***************
MovingAverageUtil<double, double, 32> m_objMagSqAverage;
NCO m_nco;
SimplePhaseLock m_bfoPLL;
SecondOrderRecursiveFilter m_bfoFilter;
// Interpolator group for decimation and/or double sideband RF filtering
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
// Used for vestigial SSB with asymmetrical filtering (needs double sideband scheme)
fftfilt* m_DSBFilter;
Complex* m_DSBFilterBuffer;
int m_DSBFilterBufferIndex;
static const int m_ssbFftLen;
// Used for FM
PhaseDiscriminators m_objPhaseDiscri;
//QElapsedTimer m_objTimer;
MessageQueue *m_messageQueueToGUI;
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }
void demod(Complex& c);
void applyStandard(int sampleRate, const ATVDemodSettings& settings, float lineDuration);
// Vertical sync is obtained by skipping horizontal sync on the line that triggers vertical sync (new frame)
inline void processHSkip(float& sample, int& sampleVideo)
{
// Fill pixel on the current line - column index 0 is reference at start of sync remove only sync length empirically
m_registeredTVScreen->setDataColor(m_colIndex - m_numberSamplesHSyncCrop, sampleVideo, sampleVideo, sampleVideo);
// Horizontal Synchro detection
// Floor Detection (0.1 nominal)
if (sample < m_settings.m_levelSynchroTop)
{
if (m_synchroSamples == 0) // AM scale reset on transition if within range
{
m_effMin = 2000000.0f;
m_effMax = -2000000.0f;
m_amSampleIndex = 0;
}
m_synchroSamples++;
}
// Black detection (0.3 nominal)
else if (sample > m_settings.m_levelBlack) {
m_synchroSamples = 0;
}
// Refine AM scale estimation on HSync pulse sequence
if (m_amSampleIndex == (3*m_numberSamplesPerHTop)/2)
{
m_ampMin = m_effMin;
m_ampMax = m_effMax;
m_ampDelta = (m_ampMax - m_ampMin);
if (m_ampDelta <= 0.0) {
m_ampDelta = 0.3f;
}
}
// H sync pulse
m_horizontalSynchroDetected = (m_synchroSamples == m_numberSamplesPerHTop);
if (m_horizontalSynchroDetected)
{
// Vertical sync and image rendering
if ((m_sampleIndex >= (3*m_samplesPerLine) / 2) // Vertical sync is first horizontal sync after skip (count at least 1.5 line length)
|| (!m_settings.m_vSync && (m_lineIndex >= m_settings.m_nbLines))) // Vsync ignored and reached nominal number of lines per frame
{
// qDebug("ATVDemodSink::processHSkip: %sVSync: co: %d sa: %d li: %d",
// (m_settings.m_vSync ? "" : "no "), m_colIndex, m_sampleIndex, m_lineIndex);
m_avgColIndex = m_colIndex;
m_registeredTVScreen->renderImage(0);
m_imageIndex++;
m_lineIndex = 0;
m_rowIndex = 0;
m_registeredTVScreen->selectRow(m_rowIndex);
}
m_sampleIndex = 0; // reset after H sync
}
else
{
m_sampleIndex++;
}
if (m_colIndex < m_samplesPerLine + m_numberSamplesPerHTop - 1) // increment until full line + next horizontal pulse
{
m_colIndex++;
}
else // full line + next horizontal pulse => start of screen reference line
{
// set column index to start a new line
if (m_settings.m_hSync && (m_lineIndex == 0)) { // start of a new frame - readjust sync position
m_colIndex = m_numberSamplesPerHTop + (m_samplesPerLine - m_avgColIndex) / 2; // amortizing factor 1/2
} else { // reset column index at end of sync pulse normally
m_colIndex = m_numberSamplesPerHTop;
}
m_lineIndex++; // new line
m_rowIndex++; // new row
if (m_rowIndex < m_settings.m_nbLines) {
m_registeredTVScreen->selectRow(m_rowIndex);
}
}
}
// Vertical sync is obtained when the average level of signal on a line is below a certain threshold. This is obtained by lowering signal to ultra black during at least 3/4th of the line
// We use directly the sum of line sample values
inline void processClassic(float& sample, int& sampleVideo)
{
// Filling pixel on the current line - reference index 0 at start of sync pulse
// remove only sync pulse empirically, +4 is to compensate shift due to hsync amortizing factor of 1/4
m_registeredTVScreen->setDataColor(m_colIndex - m_numberSamplesHSyncCrop, sampleVideo, sampleVideo, sampleVideo);
int synchroTimeSamples = (3 * m_samplesPerLine) / 4; // count 3/4 line globally
float synchroTrameLevel = 0.5f * ((float) synchroTimeSamples) * m_settings.m_levelBlack; // threshold is half the black value over 3/4th of line samples
// Horizontal Synchro detection
// Floor Detection 0
if (sample < m_settings.m_levelSynchroTop)
{
if ((m_synchroSamples == 0) && (m_ampSample > 0.25f) && (m_ampSample < 0.35f)) // AM scale reset on transition
{
m_effMin = 2000000.0f;;
m_effMax = -2000000.0f;;
m_amSampleIndex = 0;
}
m_synchroSamples++;
}
// Black detection 0.3
else if (sample > m_settings.m_levelBlack) {
m_synchroSamples = 0;
}
// Refine AM scale estimation on HSync pulse sequence
if ((m_amSampleIndex == (3*m_numberSamplesPerHTop)/2) && (sample > 0.25f) && (sample < 0.35f))
{
m_ampSample = sample;
m_ampMin = m_effMin;
m_ampMax = m_effMax;
m_ampDelta = (m_ampMax - m_ampMin);
if (m_ampDelta <= 0.0) {
m_ampDelta = 0.3f;
}
}
// H sync pulse
m_horizontalSynchroDetected = (m_synchroSamples == m_numberSamplesPerHTop) && (m_sampleIndex > (m_samplesPerLine/2) + m_numberSamplesPerLineSignals);
//Horizontal Synchro processing
if (m_horizontalSynchroDetected)
{
m_avgColIndex = m_sampleIndex - m_colIndex - (m_colIndex < m_samplesPerLine/2 ? 150 : 0);
//qDebug("HSync: %d %d %d", m_sampleIndex, m_colIndex, m_avgColIndex);
m_sampleIndex = 0;
}
else
{
m_sampleIndex++;
}
if (m_colIndex < m_samplesPerLine + m_numberSamplesPerHTop - 1) // increment until full line + next horizontal pulse
{
m_colIndex++;
if (m_colIndex < (m_samplesPerLine/2)) { // count on first half of line for better separation between black and ultra black
m_ampLineSum += sample;
}
}
else // full line + next horizontal pulse => start of screen reference line
{
m_ampLineAvg = m_ampLineSum / ((m_samplesPerLine/2) - m_numberSamplesPerHTop); // avg length is half line less horizontal top
m_ampLineSum = 0.0f;
// set column index to start a new line
if (m_settings.m_hSync && (m_lineIndex == 0)) {
m_colIndex = m_numberSamplesPerHTop + m_avgColIndex/4; // amortizing 1/4
} else {
m_colIndex = m_numberSamplesPerHTop;
}
// process line
m_lineIndex++; // new line
m_rowIndex += m_interleaved ? 2 : 1; // new row considering interleaving
if (m_rowIndex < m_settings.m_nbLines) {
m_registeredTVScreen->selectRow(m_rowIndex - m_numberOfSyncLines);
}
}
// Vertical sync and image rendering
if (m_lineIndex > m_numberOfBlackLines) {
m_verticalSynchroDetected = false; // reset trigger when detection zone is left
}
if ((m_settings.m_vSync) && (m_lineIndex <= m_settings.m_nbLines)) // VSync activated and lines in range
{
if (m_colIndex >= synchroTimeSamples)
{
if (m_ampLineAvg < 0.15f) // ultra black detection
{
if (!m_verticalSynchroDetected) // not yet
{
m_verticalSynchroDetected = true; // prevent repetition
// Odd frame or not interleaved
if ((m_imageIndex % 2 == 1) || !m_interleaved) {
m_registeredTVScreen->renderImage(0);
}
if (m_lineIndex > m_settings.m_nbLines/2) { // long frame done (even)
m_imageIndex = m_firstRowIndexOdd; // next is odd
} else {
m_imageIndex = m_firstRowIndexEven; // next is even
}
if (m_interleaved) {
m_rowIndex = m_imageIndex;
} else {
m_rowIndex = 0; // just the first line
}
// qDebug("ATVDemodSink::processClassic: m_lineIndex: %d m_imageIndex: %d m_rowIndex: %d",
// m_lineIndex, m_imageIndex, m_rowIndex);
m_registeredTVScreen->selectRow(m_rowIndex - m_numberOfSyncLines);
m_lineIndex = 0;
m_imageIndex++;
}
}
}
}
else // no VSync or lines out of range => set new image arbitrarily
{
if (m_lineIndex >= m_settings.m_nbLines/2)
{
if (m_lineIndex > m_settings.m_nbLines/2) { // long frame done (even)
m_imageIndex = m_firstRowIndexOdd; // next is odd
} else {
m_imageIndex = m_firstRowIndexEven; // next is even
}
if (m_interleaved) {
m_rowIndex = m_imageIndex;
} else {
m_rowIndex = 0; // just the first line
}
m_registeredTVScreen->selectRow(m_rowIndex - m_numberOfSyncLines);
m_lineIndex = 0;
m_imageIndex++;
}
}
}
};
#endif // INCLUDE_ATVDEMODSINK_H

View File

@ -51,35 +51,26 @@ void ATVDemodWebAPIAdapter::webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const ATVDemodSettings& settings)
{
response.getAtvDemodSettings()->setBlndecimatorEnable(settings.m_blndecimatorEnable ? 1 : 0);
response.getAtvDemodSettings()->setBlnFftFiltering(settings.m_blnFFTFiltering ? 1 : 0);
response.getAtvDemodSettings()->setBlnHSync(settings.m_blnHSync ? 1 : 0);
response.getAtvDemodSettings()->setBlnInvertVideo(settings.m_blnInvertVideo ? 1 : 0);
response.getAtvDemodSettings()->setBlnVSync(settings.m_blnVSync ? 1 : 0);
response.getAtvDemodSettings()->setEnmAtvStandard((int) settings.m_enmATVStandard);
response.getAtvDemodSettings()->setEnmModulation((int) settings.m_enmModulation);
response.getAtvDemodSettings()->setFltBfoFrequency(settings.m_fltBFOFrequency);
response.getAtvDemodSettings()->setFltFramePerS(settings.m_fltFramePerS);
response.getAtvDemodSettings()->setFltLineDuration(settings.m_fltLineDuration);
response.getAtvDemodSettings()->setFltRatioOfRowsToDisplay(settings.m_fltRatioOfRowsToDisplay);
response.getAtvDemodSettings()->setFltRfBandwidth(settings.m_fltRFBandwidth);
response.getAtvDemodSettings()->setFltRfOppBandwidth(settings.m_fltRFOppBandwidth);
response.getAtvDemodSettings()->setFltTopDuration(settings.m_fltTopDuration);
response.getAtvDemodSettings()->setFltVoltLevelSynchroBlack(settings.m_fltVoltLevelSynchroBlack);
response.getAtvDemodSettings()->setFltVoltLevelSynchroTop(settings.m_fltVoltLevelSynchroTop);
response.getAtvDemodSettings()->setBlndecimatorEnable(settings.m_forceDecimator ? 1 : 0);
response.getAtvDemodSettings()->setBlnFftFiltering(settings.m_fftFiltering ? 1 : 0);
response.getAtvDemodSettings()->setBlnHSync(settings.m_hSync ? 1 : 0);
response.getAtvDemodSettings()->setBlnInvertVideo(settings.m_invertVideo ? 1 : 0);
response.getAtvDemodSettings()->setBlnVSync(settings.m_vSync ? 1 : 0);
response.getAtvDemodSettings()->setEnmAtvStandard((int) settings.m_atvStd);
response.getAtvDemodSettings()->setEnmModulation((int) settings.m_atvModulation);
response.getAtvDemodSettings()->setFltBfoFrequency(settings.m_bfoFrequency);
response.getAtvDemodSettings()->setFltFramePerS(settings.m_fps);
response.getAtvDemodSettings()->setFltRfBandwidth(settings.m_fftBandwidth);
response.getAtvDemodSettings()->setFltRfOppBandwidth(settings.m_fftOppBandwidth);
response.getAtvDemodSettings()->setFltVoltLevelSynchroBlack(settings.m_levelBlack);
response.getAtvDemodSettings()->setFltVoltLevelSynchroTop(settings.m_levelSynchroTop);
response.getAtvDemodSettings()->setFmDeviation(settings.m_fmDeviation);
response.getAtvDemodSettings()->setFpsIndex(settings.m_fpsIndex);
response.getAtvDemodSettings()->setHalfImage(settings.m_halfImage ? 1 : 0);
response.getAtvDemodSettings()->setIntFrequencyOffset(settings.m_intFrequencyOffset);
response.getAtvDemodSettings()->setIntNumberOfLines(settings.m_intNumberOfLines);
response.getAtvDemodSettings()->setIntNumberSamplePerLine(settings.m_intNumberSamplePerLine);
response.getAtvDemodSettings()->setIntSampleRate(settings.m_intSampleRate);
response.getAtvDemodSettings()->setIntTvSampleRate(settings.m_intTVSampleRate);
response.getAtvDemodSettings()->setIntVideoTabIndex(settings.m_intVideoTabIndex);
response.getAtvDemodSettings()->setFpsIndex(ATVDemodSettings::getFpsIndex(settings.m_fps));
response.getAtvDemodSettings()->setHalfImage(settings.m_halfFrames ? 1 : 0);
response.getAtvDemodSettings()->setIntFrequencyOffset(settings.m_inputFrequencyOffset);
response.getAtvDemodSettings()->setIntNumberOfLines(settings.m_nbLines);
response.getAtvDemodSettings()->setLineTimeFactor(settings.m_lineTimeFactor);
response.getAtvDemodSettings()->setNbLinesIndex(settings.m_nbLinesIndex);
response.getAtvDemodSettings()->setOppBandwidthFactor(settings.m_OppBandwidthFactor);
response.getAtvDemodSettings()->setRfBandwidthFactor(settings.m_RFBandwidthFactor);
response.getAtvDemodSettings()->setNbLinesIndex(ATVDemodSettings::getNumberOfLinesIndex(settings.m_nbLines));
response.getAtvDemodSettings()->setRgbColor(settings.m_rgbColor);
response.getAtvDemodSettings()->setTitle(new QString(settings.m_title));
response.getAtvDemodSettings()->setTopTimeFactor(settings.m_topTimeFactor);
@ -93,92 +84,59 @@ void ATVDemodWebAPIAdapter::webapiUpdateChannelSettings(
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("blndecimatorEnable")) {
settings.m_blndecimatorEnable = response.getAtvDemodSettings()->getBlndecimatorEnable() != 0;
settings.m_forceDecimator = response.getAtvDemodSettings()->getBlndecimatorEnable() != 0;
}
if (channelSettingsKeys.contains("blnFFTFiltering")) {
settings.m_blnFFTFiltering = response.getAtvDemodSettings()->getBlnFftFiltering() != 0;
settings.m_fftFiltering = response.getAtvDemodSettings()->getBlnFftFiltering() != 0;
}
if (channelSettingsKeys.contains("blnHSync")) {
settings.m_blnHSync = response.getAtvDemodSettings()->getBlnHSync() != 0;
settings.m_hSync = response.getAtvDemodSettings()->getBlnHSync() != 0;
}
if (channelSettingsKeys.contains("blnInvertVideo")) {
settings.m_blnInvertVideo = response.getAtvDemodSettings()->getBlnInvertVideo() != 0;
settings.m_invertVideo = response.getAtvDemodSettings()->getBlnInvertVideo() != 0;
}
if (channelSettingsKeys.contains("blnVSync")) {
settings.m_blnVSync = response.getAtvDemodSettings()->getBlnVSync() != 0;
settings.m_vSync = response.getAtvDemodSettings()->getBlnVSync() != 0;
}
if (channelSettingsKeys.contains("enmATVStandard")) {
settings.m_enmATVStandard = (ATVDemodSettings::ATVStd) response.getAtvDemodSettings()->getEnmAtvStandard();
settings.m_atvStd = (ATVDemodSettings::ATVStd) response.getAtvDemodSettings()->getEnmAtvStandard();
}
if (channelSettingsKeys.contains("enmModulation")) {
settings.m_enmModulation = (ATVDemodSettings::ATVModulation) response.getAtvDemodSettings()->getEnmModulation();
settings.m_atvModulation = (ATVDemodSettings::ATVModulation) response.getAtvDemodSettings()->getEnmModulation();
}
if (channelSettingsKeys.contains("fltBFOFrequency")) {
settings.m_fltBFOFrequency = response.getAtvDemodSettings()->getFltBfoFrequency();
settings.m_bfoFrequency = response.getAtvDemodSettings()->getFltBfoFrequency();
}
if (channelSettingsKeys.contains("fltFramePerS")) {
settings.m_fltFramePerS = response.getAtvDemodSettings()->getFltFramePerS();
}
if (channelSettingsKeys.contains("fltLineDuration")) {
settings.m_fltLineDuration = response.getAtvDemodSettings()->getFltLineDuration();
}
if (channelSettingsKeys.contains("fltRatioOfRowsToDisplay")) {
settings.m_fltRatioOfRowsToDisplay = response.getAtvDemodSettings()->getFltRatioOfRowsToDisplay();
settings.m_fps = response.getAtvDemodSettings()->getFltFramePerS();
}
if (channelSettingsKeys.contains("fltRFBandwidth")) {
settings.m_fltRFBandwidth = response.getAtvDemodSettings()->getFltRfBandwidth();
settings.m_fftBandwidth = response.getAtvDemodSettings()->getFltRfBandwidth();
}
if (channelSettingsKeys.contains("fltRFOppBandwidth")) {
settings.m_fltRFOppBandwidth = response.getAtvDemodSettings()->getFltRfOppBandwidth();
}
if (channelSettingsKeys.contains("fltTopDuration")) {
settings.m_fltTopDuration = response.getAtvDemodSettings()->getFltTopDuration();
settings.m_fftOppBandwidth = response.getAtvDemodSettings()->getFltRfOppBandwidth();
}
if (channelSettingsKeys.contains("fltVoltLevelSynchroBlack")) {
settings.m_fltVoltLevelSynchroBlack = response.getAtvDemodSettings()->getFltVoltLevelSynchroBlack();
settings.m_levelBlack = response.getAtvDemodSettings()->getFltVoltLevelSynchroBlack();
}
if (channelSettingsKeys.contains("fltVoltLevelSynchroTop")) {
settings.m_fltVoltLevelSynchroTop = response.getAtvDemodSettings()->getFltVoltLevelSynchroTop();
settings.m_levelSynchroTop = response.getAtvDemodSettings()->getFltVoltLevelSynchroTop();
}
if (channelSettingsKeys.contains("fmDeviation")) {
settings.m_fmDeviation = response.getAtvDemodSettings()->getFmDeviation();
}
if (channelSettingsKeys.contains("fpsIndex")) {
settings.m_fpsIndex = response.getAtvDemodSettings()->getFpsIndex();
}
if (channelSettingsKeys.contains("halfImage")) {
settings.m_halfImage = response.getAtvDemodSettings()->getHalfImage() != 0;
settings.m_halfFrames = response.getAtvDemodSettings()->getHalfImage() != 0;
}
if (channelSettingsKeys.contains("intFrequencyOffset")) {
settings.m_intFrequencyOffset = response.getAtvDemodSettings()->getIntFrequencyOffset();
settings.m_inputFrequencyOffset = response.getAtvDemodSettings()->getIntFrequencyOffset();
}
if (channelSettingsKeys.contains("intNumberOfLines")) {
settings.m_intNumberOfLines = response.getAtvDemodSettings()->getIntNumberOfLines();
}
if (channelSettingsKeys.contains("intNumberSamplePerLine")) {
settings.m_intNumberSamplePerLine = response.getAtvDemodSettings()->getIntNumberSamplePerLine();
}
if (channelSettingsKeys.contains("intSampleRate")) {
settings.m_intSampleRate = response.getAtvDemodSettings()->getIntSampleRate();
}
if (channelSettingsKeys.contains("intTVSampleRate")) {
settings.m_intTVSampleRate = response.getAtvDemodSettings()->getIntTvSampleRate();
}
if (channelSettingsKeys.contains("intVideoTabIndex")) {
settings.m_intVideoTabIndex = response.getAtvDemodSettings()->getIntVideoTabIndex();
settings.m_nbLines = response.getAtvDemodSettings()->getIntNumberOfLines();
}
if (channelSettingsKeys.contains("lineTimeFactor")) {
settings.m_lineTimeFactor = response.getAtvDemodSettings()->getLineTimeFactor();
}
if (channelSettingsKeys.contains("nbLinesIndex")) {
settings.m_nbLinesIndex = response.getAtvDemodSettings()->getNbLinesIndex();
}
if (channelSettingsKeys.contains("OppBandwidthFactor")) {
settings.m_OppBandwidthFactor = response.getAtvDemodSettings()->getOppBandwidthFactor();
}
if (channelSettingsKeys.contains("RFBandwidthFactor")) {
settings.m_RFBandwidthFactor = response.getAtvDemodSettings()->getRfBandwidthFactor();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAtvDemodSettings()->getRgbColor();
}

View File

@ -4,7 +4,7 @@
This plugin can be used to view amateur analog television transmissions a.k.a ATV. The transmitted video signal can be black and white or color (PAL, NTSC) but only the black and white level (luminance) is retained and hence image is black and white. There is no provision to demodulate the audio subcarrier either. The modulation can be either AM or FM (SSB with carrier is only experimental). A plugin supporting audio can be used in the same passband to demodulate an audio carrier but this does not work for a subcarrier which excludes FM.
An optional rational downsampler with lowpass filtering can be used but its ratio is always 1.0 so in fact only the filter functionality is retained. This is a provision for future developments. When this downsampler is not engaged (button A.3) the source feeds the channel directly. A standard image quality for broadcast standard modes requires a sample rate of at least 4 MS/s. The Airspy Mini 3 MS/s mode may still be acceptable.
An optional rational downsampler with lowpass filtering can be used but its ratio is always 1.0 so in fact only the filter functionality is retained. This is a provision for future developments. When this downsampler is not engaged (button A.3) the source feeds the channel directly. A standard image quality for broadcast standard modes requires a sample rate of at least 4 MS/s. The Airspy Mini 3 MS/s mode may still be acceptable.
Experimental modes with smaller number of lines and FPS values can be used in conjunction with the [ATV Modulator plugin](https://github.com/f4exb/sdrangel/tree/master/plugins/channeltx/modatv) to reduce sample rate and occupied bandwidth. Very low line frequencies and thus small bandwidths are reachable with degraded image quality. This is known as NBTV see: [Wikipedia article](https://en.wikipedia.org/wiki/Narrow-bandwidth_television) and [NBTV.org](http://www.nbtv.org/)
@ -28,33 +28,39 @@ Each part is detailed next
Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows.Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
<h3>3: Rational downsampler toggle</h3>
<h3>3: Force use of the rational downsampler</h3>
Use this toggle button to enable or disable the rational downsampler and its FIR filter. Without downsampling the source plugin feeds the channel directly.
Use this toggle button to enable the rational downsampler and its FIR filter even when TV and baseband sample rate match (see next 4). The expected effect is then to engage the FIR filter so that the signal is lowpass filtered and the cutoff frequency can be adjusted with the in band filter cutoff slider (13).
When the downsampler is engaged the signal is lowpass filtered and the cutoff frequency can be adjusted with the in band filter cutoff slider (13).
<h3>4: TV sample rate</h3>
<h3>4: Channel sample rate</h3>
This is the nominal sample rate in kS/s derived from the line frequency so that an integer number of points can fit exactly into one line. This number of (virtual) points is chosen so that the resulting sample rate is the closest 10 S/s multiple to the baseband frequency.
This is the channel sample rate in kS/s. For good horizontal synchronization you should aim at a sample rate (given by the source) that yields an integer number of points per line. Sample rate (S), number of lines (l) and frames per second (F) should yield an integer number of points (N) using this formula:
&#9888; In practice you should adjust the baseband frequency with the device controls so that baseband and TV sample rates are equal. The rational interpolator does not work very well with video signals for which exact timings are critical.
N = S / (l &#215; F)
Sb - 10 < St <= Sb
St = n &#215; l &#215; F
Where:
- Sb is the baseband sample rate
- St is the TV sample rate
- n is the number of (virtual) points
- l is the number of lines
- F is the frame rate in frames/second
&#9758; The number of points should be a bit larger than the number of lines up to 240 lines then it can be a bit smaller to give an acceptable image quality.
When the rational downsampler is engaged (3) the resulting sample rate is calculated as the closest 10 S/s multiple to the source sample rate to fit an integer number of line points.
The example taken in the screenshot is from a 405 lines × 20 FPS video signal:
- source sample rate is 1500 kS/s
- line frequency is 8100 Hz
- 186 points fit in 8100 × 186 = 1506.6 kS/s
- 185 points fit in 8100 × 185 = 1498.5 kS/s
- therefore the closest sample rate is 1498.5 kS/s for 185 points per line
- therefore the closest sample rate is 1498.5 kS/s for 185 points per line
<h3>5: Number of points (or samples) per line</h3>
<h3>5: Number of virtual points or samples per line</h3>
This is the number of points or samples per complete line including sync and padding. This is derived from the sample rate and line frequency as the ratio of the two. For example with a 625 lines &#215; 25 FPS signal the line frequency is 15625 Hz. If the channel sample rate is 1500 kS/s this yields 1500000/15625 = 96 points. If the ratio is not an integer then the integer part is taken.
This is the number of (virtual) points or samples per complete line including sync and padding. Calculation of this number is explained above (4)
<h3>6: BFO PLL lock indicator</h3>
@ -76,28 +82,30 @@ Average total power in dB relative to a &#177;1.0 amplitude signal generated in
<h3>9: Modulation</h3>
- FM1: this is Frequency Modulation with approximative demodulation algorithm not using atan2
- FM2: this is Frequency Modulation with less approximative demodulation algorithm still not using atan2
- FM3: this is Frequency Modulation with atan2 approximation for phase calculation and then a discrete differentiation is applied
- AM: this is Amplitude Modulation
- USB: &#9888; USB demodulation synchronous to the carrier (experimental)
- LSB: &#9888; LSB demodulation synchronous to the carrier (experimental)
For FM choose the algorithm that best suits your conditions.
- **FM1**: this is Frequency Modulation with approximative demodulation algorithm not using atan2
- **FM2**: this is Frequency Modulation with less approximative demodulation algorithm still not using atan2
- **FM3**: this is Frequency Modulation with atan2 approximation for phase calculation and then a discrete differentiation is applied
- **AM**: this is Amplitude Modulation. It can be used for vestigial sideband transmissions in conjunction with the asymetrical filter (11, 12, 13)
- **USB**: &#9888; USB demodulation synchronous to the carrier (experimental)
- **LSB**: &#9888; LSB demodulation synchronous to the carrier (experimental)
For FM choose the algorithm that best suits your conditions.
&#9758; only FM3 is accurate with regard to FM deviation (see 10).
&#9888; in AM modes (also USB and LSB) there is an automatic amplitude scaling so that the video signal fits in the 0.0 to 1.0 range. For synchronziation standards other than HSkip (see B.3) it may be difficult for the system to find the appropriate level when the standard changes or the signal power changes abruptly. In this case you have to select HSkip (B.3) until the video signal reaches the 0.0 to 1.0 range approximately (use scope tab see C) before returning to the desired standard.
&#9888; USB and LSB modes are experimental and do not show good results for sample rates greater than 1 MS/s. Adjusting the BFO can be picky and unstable.
<h3>10: FM deviation adjustment</h3>
Using this button you can adjust the nominal FM deviation as a percentage of the channel bandwidth that is displayed on the right of the button. When a signal with this deviation is received the demodulated signal is in the range -0.5/+0.5 which is shifted to a 0/1 range video signal.
&#9758; The value is accurate only with the atan2 differential demodulator i.e. FM3. With FM1 and FM2 you will have to adjust it for best image results. You can use the scope as an aid to try to fit the video signal in the 0/1 range.
&#9758; The value is accurate only with the atan2 differential demodulator i.e. FM3. With FM1 and FM2 you will have to adjust it for best image results. You can use the scope as an aid to try to fit the video signal in the 0/1 range.
<h3>11: FFT asymmetrical filter toggle</h3>
Use this button to enable/disable the FFT asymmetrical filter. Use this filter when you want to optimize the reception of vestigial sideband AM signals.
Use this button to enable/disable the FFT asymmetrical filter. Use this filter when you want to optimize the reception of vestigial sideband AM signals.
<h3>12: FFT asymmetrical filter opposite band cutoff frequency</h3>
@ -133,12 +141,12 @@ This combo lets you chose between a 30, 25, 20, 16, 12, 10, 8, 5, 2 and 1 FPS. T
This combo lets you set the TV standard type. This sets the number of lines per complete image, frame synchronization parameters and number of blank (black) lines. Choice is between:
- PAL625: this is based on the classical 625 lines PAL system. It uses 7 or 8 synchronization lines depending on the half frame (field). It has also 17 black lines on the top of each half frame.
- PAL525: the only difference with PAL625 is the number of black lines which is down to 15
- PAL405: this is not the British standard. It just follows the same scheme as the two above but with only 7 black lines per half frame
- ShI: this is an experimental mode that uses the least possible vertical sync lines as possible. That is one line for a long synchronization pulse and one line at a higher level (0.7) to reset the vertical sync condition. Thus only 2 lines are consumed for vertical sync and the rest is left to the image. In this mode the frames are interleaved
- ShNI: this is the same as above but with non interleaved frames.
- HSkip: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pulse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines.
- **PAL625**: this is based on the classical 625 lines PAL system. It uses 7 or 8 synchronization lines depending on the half frame (field). It has also 17 black lines on the top of each half frame.
- **PAL525**: the only difference with PAL625 is the number of black lines which is down to 15
- **PAL405**: this is not the British standard. It just follows the same scheme as the two above but with only 7 black lines per half frame
- **ShortI**: this is an experimental mode that uses the least possible vertical sync lines as possible. That is one line for a long synchronization pulse and one line at a higher level (0.7) to reset the vertical sync condition. Thus only 2 lines are consumed for vertical sync and the rest is left to the image. In this mode the frames are interleaved
- **ShortNI**: this is the same as above but with non interleaved frames.
- **HSkip**: this is the horizontal sync skip technique for vertical synchronization. This has been in use in the first TV experiments with a small number of lines. This method just skips one horizontal synchronization pulse to mark the last or the first line (here it is the last). This method does not use any full line for vertical sync and all lines can be used for the image thus it suits the modes with a small number of lines. With more lines however the risk of missing pulses gets higher in adverse conditions because the pulses get shorter and may get swallowed by a stray pulse or a stray pulse can be taken for a valid one. In this case two images might get out of sync instead of just two lines. In practice this is suitable up to 90~120 lines.
When the standard chosen matches the standard of transmission the image should appear in full size and proper aspect ratio.
@ -153,7 +161,7 @@ When the standard chosen matches the standard of transmission the image should a
</tr>
<tr>
<td>640</td>
<td>ShNI</td>
<td>ShortNI</td>
</tr>
<tr>
<td>625</td>
@ -165,35 +173,35 @@ When the standard chosen matches the standard of transmission the image should a
</tr>
<tr>
<td>480</td>
<td>ShNI</td>
<td>ShortNI</td>
</tr>
<tr>
<td>405</td>
<td>PAL405, ShI, ShNI</td>
<td>PAL405, ShortI, ShortNI</td>
</tr>
<tr>
<td>360</td>
<td>ShNI</td>
<td>ShortNI</td>
</tr>
<tr>
<td>343</td>
<td>ShI, ShNI</td>
<td>ShortI, ShortNI</td>
</tr>
<tr>
<td>240</td>
<td>ShNI</td>
<td>ShortNI</td>
</tr>
<tr>
<td>180</td>
<td>ShNI</td>
<td>ShortNI</td>
</tr>
<tr>
<td>120</td>
<td>ShNI, HSkip</td>
<td>ShortNI, HSkip</td>
</tr>
<tr>
<td>90</td>
<td>ShNI, HSkip</td>
<td>ShortNI, HSkip</td>
</tr>
<tr>
<td>60</td>
@ -236,18 +244,20 @@ Use this push button to reset values to a standard setting:
- 310 mV black level
- 64 microsecond line length (middle)
- 4.7 microsecond sync pulse length (middle)
<h3>9: Synchronization level</h3>
Use this slider to adjust the top level of the synchronization pulse on a 0 to 1V scale. The value in mV appears on the right of the slider. Nominal value: 100 mV.
Use this slider to adjust the trigger level of the synchronization pulse on a 0 to 1V scale. The value in mV appears on the right of the slider. Nominal value: 100 mV.
<h3>10: Black level</h3>
Use this slider to adjust the black level of the video signal on a 0 to 1V scale. The value in mV appears on the right of the slider. Nominal value: 310 mV.
Use this slider to adjust the black level of the video signal on a 0 to 1V scale. This also triggers the end of horizontal synchronization pulse. The value in mV appears on the right of the slider. Nominal value: 310 mV.
<h3>11: Line length</h3>
This is the line length in time units. The value appears on the right of the slider. Nominal value depends on the nominal line frequency. For example with 405 lines and 20 FPS. The line frequency is 405 &#215; 20 = 8100 Hz thus the nominal line time is the inverse of this value that is &#8776;123.45 &mu;s
&#9888; Do not change the default setting (no adjustment or 0) in most cases. This is to be used only with signals that do not have any horizontal synchronization so is highly experimental.
This is the line length in time units. The value appears on the right of the slider. Nominal value depends on the nominal line frequency. For example with 405 lines and 20 FPS. The line frequency is 405 &#215; 20 = 8100 Hz thus the nominal line time is the inverse of this value that is &#8776;123.45 &mu;s
The slider step is set to a sample period in order to ensure that the adjustment is done with the best possible precision. For example at 1500 kS/s sample rate this will be the inverse of this value that is &#8776;666.67 ns. The middle position of the slider sets the nominal value and the slider step appears in the tooltip.
@ -257,7 +267,7 @@ This is the length in time units of a horizontal or line synchronization pulse.
Similarly to the line length slider the slider step is set to a sample period in order to ensure that the adjustment is done with the best possible precision. The middle position of the slider sets the nominal value and the slider step appears in the tooltip.
&#9758; You can move this control back and forth in case you have synchronizing issues as it can help the synchronization system to get back into pace.
&#9758; You can move this control back and forth in case you have synchronizing issues as it can help the synchronization system to get back into pace. In practice the value should always be negative (shorter pulse than nominal).
<h2>C: Image</h2>
@ -267,7 +277,7 @@ Select monitor with the monitor tab on the left side.
![ATV Demodulator plugin GUI Video monitor](../../../doc/img/ATVDemod_pluginC_monitor.png)
This is where the TV image appears. Yes on the screenshot this is the famous [Lenna](https://en.wikipedia.org/wiki/Lenna). The original image is 512 &#215; 512 pixels so it has been cropped to fit the 4:3 format. The screen geometry ratio is fixed to 4:3 format. You will have to choose the standard (B.3) matching the transmission to ensure that the transmitted image fits perfectly.
This is where the TV image appears. Yes on the screenshot this is the famous [Lenna](https://en.wikipedia.org/wiki/Lenna). The original image is 512 &#215; 512 pixels so it has been cropped to fit the 4:3 format. The screen geometry ratio is fixed to 4:3 format. You will have to choose the standard (B.3) matching the transmission to ensure that the transmitted image fits perfectly.
<h3>Scope</h3>

View File

@ -16,6 +16,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SDRBASE_DSP_PHASELOCK
#define INCLUDE_SDRBASE_DSP_PHASELOCK
#include <vector>
#include "dsp/dsptypes.h"
#include "export.h"
@ -180,3 +183,5 @@ protected:
samples_out[3] = m_phase; // Pilot phase
}
};
#endif // INCLUDE_SDRBASE_DSP_PHASELOCK