330 WIP - P25 C4FM decoder fully functional using DQPSK decoder and

new symbol timing error detector.  Each component now has a subclass
that supports instrumentation for use with the DemodulatorViewerFX
application.
This commit is contained in:
Denny
2018-02-25 08:56:34 -05:00
parent a0630102e6
commit 148ddd0bd5
23 changed files with 623 additions and 1302 deletions

View File

@@ -20,20 +20,16 @@ import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter;
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
import io.github.dsheirer.dsp.psk.DQPSKDemodulator;
import io.github.dsheirer.dsp.psk.DQPSKSymbolPhaseErrorCalculator;
import io.github.dsheirer.dsp.psk.IQPSKSymbolDecoder;
import io.github.dsheirer.dsp.psk.ISymbolPhaseErrorCalculator;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
import io.github.dsheirer.dsp.psk.QPSKStarSlicer;
import io.github.dsheirer.dsp.psk.QPSKSymbolDecoder;
import io.github.dsheirer.dsp.psk.SymbolDecisionData2;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.Tracking;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter;
import io.github.dsheirer.sample.complex.IComplexBufferListener;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import org.slf4j.Logger;
@@ -46,36 +42,21 @@ import java.util.concurrent.ScheduledExecutorService;
public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListener, Listener<ComplexBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(P25_C4FMDecoder2.class);
private static final double SYMBOL_RATE = 4800;
private double mSampleRate;
protected static final double SYMBOL_RATE = 4800;
protected ISymbolPhaseErrorCalculator mSymbolPhaseErrorCalculator = new DQPSKSymbolPhaseErrorCalculator();
protected IQPSKSymbolDecoder mSymbolDecoder = new QPSKStarSlicer();
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
protected DQPSKDemodulator mQPSKDemodulator;
protected CostasLoop mCostasLoop;
protected double mSampleRate;
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
private ComplexFIRFilter mBasebandFilter;
private Map<Double,float[]> mSymbolFilters = new HashMap<>();
private ComplexFIRFilter mSymbolFilter;
private DQPSKDemodulator mQPSKDemodulator;
private CostasLoop mCostasLoop;
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
private QPSKSymbolDecoder mQPSKSlicer = new QPSKSymbolDecoder();
private P25MessageFramer mMessageFramer;
private ISymbolPhaseErrorCalculator mSymbolPhaseErrorCalculator = new DQPSKSymbolPhaseErrorCalculator();
private IQPSKSymbolDecoder mSymbolDecoder = new QPSKStarSlicer();
private Listener<Complex> mDEBUGSymbolListener;
private Listener<Double> mDEBUGPLLPhaseErrorListener;
private Listener<Double> mDEBUGPLLFrequencyListener;
private Listener<SymbolDecisionData2> mDEBUGSymbolDecisionDataListener;
private Listener<Double> mDEBUGSamplesPerSymbolListener;
private Listener<ComplexBuffer> mFilteredSymbolListener;
public P25_C4FMDecoder2(AliasList aliasList)
{
super(aliasList);
setSampleRate(48600.0);
mStreamConverter.setListener(mAGC);
setSampleRate(48000.0);
}
public void setSampleRate(double sampleRate)
@@ -87,19 +68,12 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen
mSampleRate = sampleRate;
mBasebandFilter = new ComplexFIRFilter(getBasebandFilter(), 1.0f);
mSymbolFilter = new ComplexFIRFilter(getSymbolFilter(), 1.0f);
mCostasLoop = new CostasLoop(mSampleRate, SYMBOL_RATE);
mCostasLoop.setTracking(Tracking.FINE);
mCostasLoop.setAutomaticTracking(false);
mInterpolatingSampleBuffer = new InterpolatingSampleBufferInstrumented((float)(sampleRate / SYMBOL_RATE));
mQPSKDemodulator = new DQPSKDemodulator(mCostasLoop, mSymbolPhaseErrorCalculator, mSymbolDecoder,
mSampleRate, SYMBOL_RATE);
mQPSKDemodulator.setSymbolListener(mDEBUGSymbolListener);
mQPSKDemodulator.setPLLErrorListener(mDEBUGPLLPhaseErrorListener);
mQPSKDemodulator.setPLLFrequencyListener(mDEBUGPLLFrequencyListener);
mQPSKDemodulator.setSymbolDecisionDataListener(mDEBUGSymbolDecisionDataListener);
mQPSKDemodulator.setSamplesPerSymbolListener(mDEBUGSamplesPerSymbolListener);
mInterpolatingSampleBuffer);
//Message framer can trigger a symbol-inversion correction to the PLL when detected
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
@@ -107,54 +81,16 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen
mQPSKDemodulator.setDibitListener(mMessageFramer);
}
public void setDEBUGSymbolListener(Listener<Complex> listener)
{
mDEBUGSymbolListener = listener;
mQPSKDemodulator.setSymbolListener(listener);
}
public void setDEBUGPLLPhaseErrorListener(Listener<Double> listener)
{
mDEBUGPLLPhaseErrorListener = listener;
mQPSKDemodulator.setPLLErrorListener(listener);
}
public void setDEBUGPLLFrequencyListener(Listener<Double> listener)
{
mDEBUGPLLFrequencyListener = listener;
mQPSKDemodulator.setPLLFrequencyListener(listener);
}
public void setDEBUGFilteredBufferListener(Listener<ComplexBuffer> listener)
{
mFilteredSymbolListener = listener;
}
public void setDEBUGSymbolDecisionDataListener(Listener<SymbolDecisionData2> listener)
{
mDEBUGSymbolDecisionDataListener = listener;
mQPSKDemodulator.setSymbolDecisionDataListener(listener);
}
public void setDEBUGSamplesPerSymbolListener(Listener<Double> listener)
{
mDEBUGSamplesPerSymbolListener = listener;
mQPSKDemodulator.setSamplesPerSymbolListener(listener);
}
@Override
public void receive(ComplexBuffer complexBuffer)
{
ComplexBuffer basebandFiltered = mBasebandFilter.filter(complexBuffer);
// ComplexBuffer symbolFiltered = mSymbolFilter.filter(complexBuffer);
// ComplexBuffer symbolFiltered = complexBuffer;
ComplexBuffer symbolFiltered = basebandFiltered;
mQPSKDemodulator.receive(symbolFiltered);
ComplexBuffer filtered = filter(complexBuffer);
mQPSKDemodulator.receive(filtered);
}
if(mFilteredSymbolListener != null)
{
mFilteredSymbolListener.receive(symbolFiltered);
}
protected ComplexBuffer filter(ComplexBuffer complexBuffer)
{
return mBasebandFilter.filter(complexBuffer);
}
private double getSampleRate()
@@ -170,11 +106,11 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate((int)getSampleRate())
.passBandCutoff(6000)
.passBandCutoff(5100)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandAmplitude(0.0)
.stopBandStart(6250)
.stopBandStart(6500)
.stopBandRipple(0.01)
.build();
@@ -200,20 +136,6 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen
return filter;
}
private float[] getSymbolFilter()
{
float[] filter = mSymbolFilters.get(getSampleRate());
if(filter == null)
{
float samplesPerSymbol = (float)(getSampleRate() / 4800.0);
filter = FilterFactory.getRootRaisedCosine(samplesPerSymbol, 40, 0.2f);
mSymbolFilters.put(getSampleRate(), filter);
}
return filter;
}
public void dispose()
{
super.dispose();
@@ -221,15 +143,6 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen
mBasebandFilter.dispose();
mBasebandFilter = null;
mStreamConverter.dispose();
mStreamConverter = null;
mAGC.dispose();
mAGC = null;
mQPSKSlicer.dispose();
mQPSKSlicer = null;
mMessageFramer.dispose();
mMessageFramer = null;
}
@@ -252,7 +165,15 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen
@Override
public void receive(FrequencyChangeEvent frequencyChangeEvent)
{
//Ignored
switch(frequencyChangeEvent.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mCostasLoop.reset();
break;
}
}
};
}
@@ -271,20 +192,16 @@ public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListen
@Override
public void reset()
{
// TODO Auto-generated method stub
mCostasLoop.reset();
}
@Override
public void start(ScheduledExecutorService executor)
{
// TODO Auto-generated method stub
}
@Override
public void stop()
{
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,109 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* 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, either 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 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/>
*
******************************************************************************/
package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.dsp.psk.DQPSKDemodulatorInstrumented;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
import io.github.dsheirer.dsp.psk.SymbolDecisionData;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexBuffer;
public class P25_C4FMDecoder2Instrumented extends P25_C4FMDecoder2
{
private Listener<Double> mPLLPhaseErrorListener;
private Listener<Double> mPLLFrequencyListener;
private Listener<Double> mSamplesPerSymbolListener;
private Listener<Complex> mSymbolListener;
private Listener<ComplexBuffer> mFilteredSymbolListener;
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
public P25_C4FMDecoder2Instrumented(AliasList aliasList)
{
super(aliasList);
}
/**
* Overrides the filter method so that we can capture the filtered samples for instrumentation
*/
protected ComplexBuffer filter(ComplexBuffer complexBuffer)
{
ComplexBuffer filtered = super.filter(complexBuffer);
if(mFilteredSymbolListener != null)
{
mFilteredSymbolListener.receive(filtered);
}
return filtered;
}
/**
* Overrides this method so we can correctly configure for instrumented operations
*/
public void setSampleRate(double sampleRate)
{
super.setSampleRate(sampleRate);
InterpolatingSampleBufferInstrumented instrumentedBuffer = new InterpolatingSampleBufferInstrumented((float)(sampleRate / SYMBOL_RATE));
mInterpolatingSampleBuffer = instrumentedBuffer;
DQPSKDemodulatorInstrumented instrumented = new DQPSKDemodulatorInstrumented(mCostasLoop,
mSymbolPhaseErrorCalculator, mSymbolDecoder, instrumentedBuffer, mSampleRate);
mQPSKDemodulator = instrumented;
instrumented.setSymbolListener(mSymbolListener);
instrumented.setPLLErrorListener(mPLLPhaseErrorListener);
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
}
public void setSymbolListener(Listener<Complex> listener)
{
mSymbolListener = listener;
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSymbolListener(listener);
}
public void setPLLPhaseErrorListener(Listener<Double> listener)
{
mPLLPhaseErrorListener = listener;
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener);
}
public void setPLLFrequencyListener(Listener<Double> listener)
{
mPLLFrequencyListener = listener;
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener);
}
public void setFilteredBufferListener(Listener<ComplexBuffer> listener)
{
mFilteredSymbolListener = listener;
}
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
{
mSymbolDecisionDataListener = listener;
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener);
}
public void setSamplesPerSymbolListener(Listener<Double> listener)
{
mSamplesPerSymbolListener = listener;
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener);
}
}

View File

@@ -78,7 +78,7 @@ public class SyncBroadcast extends TSBKMessage
if( isMicroslotsLockedToMinuteRollover() )
{
sb.append( " MICROSLOT-MINUTE ROLLOVER:LOCKED" );
sb.append( " MICROSLOT-MINUTE ROLLOVER:SLOW" );
}
else
{