mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class SyncBroadcast extends TSBKMessage
|
||||
|
||||
if( isMicroslotsLockedToMinuteRollover() )
|
||||
{
|
||||
sb.append( " MICROSLOT-MINUTE ROLLOVER:LOCKED" );
|
||||
sb.append( " MICROSLOT-MINUTE ROLLOVER:SLOW" );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user