/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2017 F4HKW // // for F4EXB / SDRAngel // // // // 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 // // // // This program is distributed in the hope that it will be useful, // // but WITHOUT ANY WARRANTY; without even the implied warranty of // // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // // GNU General Public License V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #include "atvdemod.h" #include #include #include #include #include "audio/audiooutput.h" #include "dsp/dspengine.h" #include "dsp/pidcontroller.h" MESSAGE_CLASS_DEFINITION(ATVDemod::MsgConfigureATVDemod, Message) MESSAGE_CLASS_DEFINITION(ATVDemod::MsgConfigureRFATVDemod, Message) MESSAGE_CLASS_DEFINITION(ATVDemod::MsgReportEffectiveSampleRate, Message) const float ATVDemod::m_fltSecondToUs = 1000000.0f; const int ATVDemod::m_ssbFftLen = 1024; ATVDemod::ATVDemod(BasebandSampleSink* objScopeSink) : m_objScopeSink(objScopeSink), m_objSettingsMutex(QMutex::Recursive), m_objRegisteredATVScreen(NULL), m_intImageIndex(0), m_intColIndex(0), m_intRowIndex(0), m_intSynchroPoints(0), m_blnSynchroDetected(false), m_blnLineSynchronized(false), m_blnVerticalSynchroDetected(false), m_intRowsLimit(0), m_blnImageDetecting(false), m_fltEffMin(2000000000.0f), m_fltEffMax(-2000000000.0f), m_fltAmpMin(-2000000000.0f), m_fltAmpMax(2000000000.0f), m_fltAmpDelta(1.0), m_fltAmpLineAverage(0.0f), m_intNumberSamplePerTop(0), m_bfoPLL(200/1000000, 100/1000000, 0.01), m_bfoFilter(200.0, 1000000.0, 0.9), m_interpolatorDistanceRemain(0.0f), m_interpolatorDistance(1.0f), m_DSBFilter(0), m_DSBFilterBuffer(0), m_DSBFilterBufferIndex(0) { setObjectName("ATVDemod"); //*************** ATV PARAMETERS *************** m_intNumberSamplePerLine=0; m_intSynchroPoints=0; m_intNumberOfLines=0; m_intNumberOfRowsToDisplay=0; m_objMagSqAverage.resize(32, 1.0); m_DSBFilter = new fftfilt((2.0f * m_objRFConfig.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)); memset((void*)m_fltBufferI,0,6*sizeof(float)); memset((void*)m_fltBufferQ,0,6*sizeof(float)); } ATVDemod::~ATVDemod() { } void ATVDemod::setATVScreen(ATVScreen *objScreen) { m_objRegisteredATVScreen = objScreen; } void ATVDemod::configure( MessageQueue* objMessageQueue, float fltLineDurationUs, float fltTopDurationUs, float fltFramePerS, float fltRatioOfRowsToDisplay, float fltVoltLevelSynchroTop, float fltVoltLevelSynchroBlack, bool blnHSync, bool blnVSync, bool blnInvertVideo) { Message* msgCmd = MsgConfigureATVDemod::create( fltLineDurationUs, fltTopDurationUs, fltFramePerS, fltRatioOfRowsToDisplay, fltVoltLevelSynchroTop, fltVoltLevelSynchroBlack, blnHSync, blnVSync, blnInvertVideo); objMessageQueue->push(msgCmd); } void ATVDemod::configureRF( MessageQueue* objMessageQueue, ATVModulation enmModulation, float fltRFBandwidth, float fltRFOppBandwidth, bool blnFFTFiltering, bool blnDecimatorEnable, float fltBFOFrequency) { Message* msgCmd = MsgConfigureRFATVDemod::create( enmModulation, fltRFBandwidth, fltRFOppBandwidth, blnFFTFiltering, blnDecimatorEnable, fltBFOFrequency); objMessageQueue->push(msgCmd); } void ATVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) { float fltDivSynchroBlack = 1.0f - m_objRunning.m_fltVoltLevelSynchroBlack; float fltI; float fltQ; float fltNormI; float fltNormQ; Complex ci; float fltNorm=0.00f; float fltVal; int intVal; qint16 * ptrBufferToRelease = 0; bool blnComputeImage=false; int intSynchroTimeSamples= (3*m_intNumberSamplePerLine)/4; float fltSynchroTrameLevel = 0.5f*((float)intSynchroTimeSamples) * m_objRunning.m_fltVoltLevelSynchroBlack; //********** Let's rock and roll buddy ! ********** m_objSettingsMutex.lock(); //********** Accessing ATV Screen context ********** if(m_intImageIndex==0) { if(m_intNumberOfLines%2==1) { m_intRowsLimit = m_intNumberOfLines; } else { m_intRowsLimit = m_intNumberOfLines-2; } } #ifdef EXTENDED_DIRECT_SAMPLE 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; intIndreal(); fltQ = it->imag(); #endif Complex c(fltI, fltQ); if (m_objRFRunning.m_intFrequencyOffset != 0) { c *= m_nco.nextIQ(); } if (m_objRFRunning.m_blndecimatorEnable) { if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) { demod(ci); m_interpolatorDistanceRemain += m_interpolatorDistance; } } else { demod(c); } } if(m_objScopeSink != 0) { m_objScopeSink->feed(m_objScopeSampleBuffer.begin(), m_objScopeSampleBuffer.end(), false); // m_ssb = positive only } m_objScopeSampleBuffer.clear(); if (ptrBufferToRelease != 0) { delete ptrBufferToRelease; } m_objSettingsMutex.unlock(); } void ATVDemod::demod(Complex& c) { float fltDivSynchroBlack = 1.0f - m_objRunning.m_fltVoltLevelSynchroBlack; int intSynchroTimeSamples= (3*m_intNumberSamplePerLine)/4; float fltSynchroTrameLevel = 0.5f*((float)intSynchroTimeSamples) * m_objRunning.m_fltVoltLevelSynchroBlack; float fltNormI; float fltNormQ; float fltNorm; float fltVal; int intVal; //********** FFT filtering ********** if (m_objRFRunning.m_blnFFTFiltering) { int n_out; fftfilt::cmplx *filtered; n_out = m_DSBFilter->runAsym(c, &filtered, m_objRFRunning.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 ********** #if defined(_WINDOWS_) float fltI = m_objRFRunning.m_blnFFTFiltering ? m_DSBFilterBuffer[m_DSBFilterBufferIndex-1].real() : c.real(); float fltQ = m_objRFRunning.m_blnFFTFiltering ? m_DSBFilterBuffer[m_DSBFilterBufferIndex-1].imag() : c.imag(); #else float& fltI = m_objRFRunning.m_blnFFTFiltering ? m_DSBFilterBuffer[m_DSBFilterBufferIndex-1].real() : c.real(); float& fltQ = m_objRFRunning.m_blnFFTFiltering ? m_DSBFilterBuffer[m_DSBFilterBufferIndex-1].imag() : c.imag(); #endif double magSq = fltI*fltI + fltQ*fltQ; m_objMagSqAverage.feed(magSq); fltNorm = sqrt(magSq); if ((m_objRFRunning.m_enmModulation == ATV_FM1) || (m_objRFRunning.m_enmModulation == ATV_FM2)) { //Amplitude FM fltNormI= fltI/fltNorm; fltNormQ= fltQ/fltNorm; //-2 > 2 : 0 -> 1 volt //0->0.3 synchro 0.3->1 image if (m_objRFRunning.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; } else if (m_objRFRunning.m_enmModulation == ATV_AM) { //Amplitude AM fltVal = fltNorm; //********** Mini and Maxi Amplitude tracking ********** if(fltValm_fltEffMax) { m_fltEffMax=fltVal; } //Normalisation fltVal -= m_fltAmpMin; fltVal /=m_fltAmpDelta; } else if ((m_objRFRunning.m_enmModulation == ATV_USB) || (m_objRFRunning.m_enmModulation == ATV_LSB)) { 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_objRFRunning.m_enmModulation == ATV_USB) { fltVal = (mixI + mixQ); } else { fltVal = (mixI - mixQ); } //********** Mini and Maxi Amplitude tracking ********** if(fltValm_fltEffMax) { m_fltEffMax=fltVal; } //Normalisation fltVal -= m_fltAmpMin; fltVal /=m_fltAmpDelta; } else { fltVal = 0.0f; } m_objScopeSampleBuffer.push_back(Sample(fltVal*32768.0f, 0.0f)); fltVal = m_objRunning.m_blnInvertVideo ? 1.0f - fltVal : fltVal; m_fltAmpLineAverage += fltVal; //********** gray level ********** //-0.3 -> 0.7 intVal = (int) 255.0*(fltVal - m_objRunning.m_fltVoltLevelSynchroBlack) / fltDivSynchroBlack; //0 -> 255 if(intVal<0) { intVal=0; } else if(intVal>255) { intVal=255; } //********** Filling pixels ********** bool blnComputeImage = (m_objRunning.m_fltRatioOfRowsToDisplay != 0.5f); if (!blnComputeImage) { blnComputeImage = ((m_intImageIndex/2) % 2 == 0); } if (blnComputeImage) { m_objRegisteredATVScreen->setDataColor(m_intColIndex,intVal, intVal, intVal); } m_intColIndex++; ////////////////////// m_blnSynchroDetected=false; if((m_objRunning.m_blnHSync) && (m_intRowIndex>1)) { //********** Line Synchro 0-0-0 -> 0.3-0.3 0.3 ********** if(m_blnImageDetecting==false) { //Floor Detection 0 if (fltVal <= m_objRunning.m_fltVoltLevelSynchroTop) { m_intSynchroPoints ++; } else { m_intSynchroPoints=0; } if(m_intSynchroPoints>=m_intNumberSamplePerTop) { m_blnSynchroDetected=true; m_blnImageDetecting=true; m_intSynchroPoints=0; } } else { //Image detection Sub Black 0.3 if (fltVal >= m_objRunning.m_fltVoltLevelSynchroBlack) { m_intSynchroPoints ++; } else { m_intSynchroPoints=0; } if(m_intSynchroPoints>=m_intNumberSamplePerTop) { m_blnSynchroDetected=false; m_blnImageDetecting=false; m_intSynchroPoints=0; } } } //********** Rendering if necessary ********** // Vertical Synchro : 3/4 a line necessary if(!m_blnVerticalSynchroDetected && m_objRunning.m_blnVSync) { if(m_intColIndex>=intSynchroTimeSamples) { if(m_fltAmpLineAverage<=fltSynchroTrameLevel) //(m_fltLevelSynchroBlack*(float)(m_intColIndex-((m_intNumberSamplePerLine*12)/64)))) //75 { m_blnVerticalSynchroDetected=true; m_intRowIndex=m_intImageIndex%2; if(blnComputeImage) { m_objRegisteredATVScreen->selectRow(m_intRowIndex); } } } } //Horizontal Synchro if((m_intColIndex>=m_intNumberSamplePerLine) || (m_blnSynchroDetected==true)) { m_blnSynchroDetected=false; m_blnImageDetecting=true; m_intColIndex=0; if((m_blnSynchroDetected==false) || (m_blnLineSynchronized==true)) { //New line + Interleaving m_intRowIndex ++; m_intRowIndex ++; if(m_intRowIndexselectRow(m_intRowIndex); } m_blnLineSynchronized=false; } else { m_blnLineSynchronized=m_blnSynchroDetected; } m_fltAmpLineAverage=0.0f; } ////////////////////// if(m_intRowIndex>=m_intRowsLimit) { m_blnVerticalSynchroDetected=false; m_fltAmpLineAverage=0.0f; //Interleave Odd/Even images m_intRowIndex=m_intImageIndex%2; m_intColIndex=0; if(blnComputeImage) { m_objRegisteredATVScreen->selectRow(m_intRowIndex); } //Rendering when odd image processed if(m_intImageIndex%2==1) { //interleave if(blnComputeImage) { m_objRegisteredATVScreen->renderImage(NULL); } m_intRowsLimit = m_intNumberOfLines-1; if (m_objRFRunning.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; } } else { if(m_intNumberOfLines%2==1) { m_intRowsLimit = m_intNumberOfLines; } else { m_intRowsLimit = m_intNumberOfLines-2; } } m_intImageIndex ++; } } void ATVDemod::start() { //m_objTimer.start(); } void ATVDemod::stop() { } bool ATVDemod::handleMessage(const Message& cmd) { qDebug() << "ATVDemod::handleMessage"; if (DownChannelizer::MsgChannelizerNotification::match(cmd)) { DownChannelizer::MsgChannelizerNotification& objNotif = (DownChannelizer::MsgChannelizerNotification&) cmd; m_objConfig.m_intSampleRate = objNotif.getSampleRate(); m_objRFConfig.m_intFrequencyOffset = objNotif.getFrequencyOffset(); qDebug() << "ATVDemod::handleMessage: MsgChannelizerNotification:" << " m_intSampleRate: " << m_objConfig.m_intSampleRate << " m_intFrequencyOffset: " << m_objRFConfig.m_intFrequencyOffset; applySettings(); return true; } else if (MsgConfigureATVDemod::match(cmd)) { MsgConfigureATVDemod& objCfg = (MsgConfigureATVDemod&) cmd; m_objConfig = objCfg.m_objMsgConfig; qDebug() << "ATVDemod::handleMessage: MsgConfigureATVDemod:" << " m_fltVoltLevelSynchroBlack:" << m_objConfig.m_fltVoltLevelSynchroBlack << " m_fltVoltLevelSynchroTop:" << m_objConfig.m_fltVoltLevelSynchroTop << " m_fltFramePerS:" << m_objConfig.m_fltFramePerS << " m_fltLineDurationUs:" << m_objConfig.m_fltLineDuration << " m_fltRatioOfRowsToDisplay:" << m_objConfig.m_fltRatioOfRowsToDisplay << " m_fltTopDurationUs:" << m_objConfig.m_fltTopDurationUs << " m_blnHSync:" << m_objConfig.m_blnHSync << " m_blnVSync:" << m_objConfig.m_blnVSync; applySettings(); return true; } else if (MsgConfigureRFATVDemod::match(cmd)) { MsgConfigureRFATVDemod& objCfg = (MsgConfigureRFATVDemod&) cmd; m_objRFConfig = objCfg.m_objMsgConfig; qDebug() << "ATVDemod::handleMessage: MsgConfigureRFATVDemod:" << " m_enmModulation:" << m_objRFConfig.m_enmModulation << " m_fltRFBandwidth:" << m_objRFConfig.m_fltRFBandwidth << " m_fltRFOppBandwidth:" << m_objRFConfig.m_fltRFOppBandwidth << " m_blnFFTFiltering:" << m_objRFConfig.m_blnFFTFiltering << " m_blnDecimatorEnable:" << m_objRFConfig.m_blndecimatorEnable << " m_fltBFOFrequency:" << m_objRFConfig.m_fltBFOFrequency; applySettings(); return true; } else { if (m_objScopeSink != 0) { return m_objScopeSink->handleMessage(cmd); } else { return false; } } } void ATVDemod::applySettings() { if (m_objConfig.m_intSampleRate == 0) { return; } if((m_objRFConfig.m_intFrequencyOffset != m_objRFRunning.m_intFrequencyOffset) || (m_objRFConfig.m_enmModulation != m_objRFRunning.m_enmModulation) || (m_objConfig.m_intSampleRate != m_objRunning.m_intSampleRate)) { m_nco.setFreq(-m_objRFConfig.m_intFrequencyOffset, m_objConfig.m_intSampleRate); } if ((m_objConfig.m_intSampleRate != m_objRunning.m_intSampleRate) || (m_objRFConfig.m_fltRFBandwidth != m_objRFRunning.m_fltRFBandwidth)) { m_objSettingsMutex.lock(); m_objConfigPrivate.m_intTVSampleRate = (m_objConfig.m_intSampleRate / 1000000) * 1000000; // make sure working sample rate is a multiple of rate units if (m_objConfigPrivate.m_intTVSampleRate > 0) { m_interpolatorDistance = (Real) m_objConfigPrivate.m_intTVSampleRate / (Real) m_objConfig.m_intSampleRate; } else { m_objConfigPrivate.m_intTVSampleRate = m_objConfig.m_intSampleRate; m_interpolatorDistance = 1.0f; } m_interpolatorDistanceRemain = 0; m_interpolator.create(24, m_objConfigPrivate.m_intTVSampleRate, m_objRFConfig.m_fltRFBandwidth / getRFBandwidthDivisor(m_objRFConfig.m_enmModulation), 3.0); m_objSettingsMutex.unlock(); } if((m_objConfig.m_fltFramePerS != m_objRunning.m_fltFramePerS) || (m_objConfig.m_fltLineDuration != m_objRunning.m_fltLineDuration) || (m_objConfig.m_intSampleRate != m_objRunning.m_intSampleRate) || (m_objConfig.m_fltTopDurationUs != m_objRunning.m_fltTopDurationUs) || (m_objConfig.m_fltRatioOfRowsToDisplay != m_objRunning.m_fltRatioOfRowsToDisplay)) { m_objSettingsMutex.lock(); m_intNumberOfLines = (int) (1.0f / (m_objConfig.m_fltLineDuration * m_objConfig.m_fltFramePerS)); m_intNumberSamplePerLine = (int) (m_objConfig.m_fltLineDuration * m_objConfig.m_intSampleRate); m_intNumberOfRowsToDisplay = (int) (m_objConfig.m_fltRatioOfRowsToDisplay * m_objConfig.m_fltLineDuration * m_objConfig.m_intSampleRate); m_intNumberSamplePerTop = (int) ((m_objConfig.m_fltTopDurationUs * m_objConfig.m_intSampleRate) / m_fltSecondToUs); m_objRegisteredATVScreen->resizeATVScreen(m_intNumberSamplePerLine, m_intNumberOfLines); m_intRowsLimit = m_intNumberOfLines-1; m_intImageIndex = 0; m_intColIndex=0; m_intRowIndex=0; m_intRowsLimit=0; m_objSettingsMutex.unlock(); } if ((m_objConfigPrivate.m_intTVSampleRate != m_objRunningPrivate.m_intTVSampleRate) || (m_objConfig.m_intSampleRate != m_objRunning.m_intSampleRate) || (m_objRFConfig.m_blndecimatorEnable != m_objRFRunning.m_blndecimatorEnable)) { int sampleRate = m_objRFConfig.m_blndecimatorEnable ? m_objConfigPrivate.m_intTVSampleRate : m_objConfig.m_intSampleRate; MsgReportEffectiveSampleRate *report; report = MsgReportEffectiveSampleRate::create(sampleRate); getOutputMessageQueue()->push(report); } if ((m_objConfigPrivate.m_intTVSampleRate != m_objRunningPrivate.m_intTVSampleRate) || (m_objRFConfig.m_fltRFBandwidth != m_objRFRunning.m_fltRFBandwidth) || (m_objRFConfig.m_fltRFOppBandwidth != m_objRFRunning.m_fltRFOppBandwidth)) { m_objSettingsMutex.lock(); m_DSBFilter->create_asym_filter(m_objRFConfig.m_fltRFOppBandwidth / m_objConfigPrivate.m_intTVSampleRate, m_objRFConfig.m_fltRFBandwidth / m_objConfigPrivate.m_intTVSampleRate); memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen)); m_DSBFilterBufferIndex = 0; m_objSettingsMutex.unlock(); } if ((m_objConfigPrivate.m_intTVSampleRate != m_objRunningPrivate.m_intTVSampleRate) || (m_objRFConfig.m_fltBFOFrequency != m_objRFRunning.m_fltBFOFrequency)) { m_bfoPLL.configure(m_objRFConfig.m_fltBFOFrequency / m_objConfigPrivate.m_intTVSampleRate, 100.0 / m_objConfigPrivate.m_intTVSampleRate, 0.01); m_bfoFilter.setFrequencies(m_objRFConfig.m_fltBFOFrequency, m_objConfigPrivate.m_intTVSampleRate); } m_objRunning = m_objConfig; m_objRFRunning = m_objRFConfig; m_objRunningPrivate = m_objConfigPrivate; } int ATVDemod::getSampleRate() { return m_objRunning.m_intSampleRate; } int ATVDemod::getEffectiveSampleRate() { return m_objRFRunning.m_blndecimatorEnable ? m_objRunningPrivate.m_intTVSampleRate : m_objRunning.m_intSampleRate; } bool ATVDemod::getBFOLocked() { if ((m_objRFRunning.m_enmModulation == ATV_USB) || (m_objRFRunning.m_enmModulation == ATV_LSB)) { return m_bfoPLL.locked(); } else { return false; } } float ATVDemod::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; } }