mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
39 - WIP - incorporates the latest P25 C4FM decoder from master and
updates the LSM decoder to follow the same design pattern. Adds LSM decoder support to the demodulator viewer application. The current symbol timing gain for LSM is set too high -- is using the C4FM gain and causes too much fluctuation in the timing error.
This commit is contained in:
@@ -41,6 +41,21 @@ public abstract class Decoder extends Module implements IMessageProvider
|
||||
mMessageListener = null;
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the decoder type (ie protocol)
|
||||
*/
|
||||
|
||||
@@ -75,10 +75,10 @@ import io.github.dsheirer.module.decode.nbfm.NBFMDecoder;
|
||||
import io.github.dsheirer.module.decode.nbfm.NBFMDecoderEditor;
|
||||
import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1;
|
||||
import io.github.dsheirer.module.decode.p25.P25Decoder.Modulation;
|
||||
import io.github.dsheirer.module.decode.p25.P25DecoderC4FM;
|
||||
import io.github.dsheirer.module.decode.p25.P25DecoderEditor;
|
||||
import io.github.dsheirer.module.decode.p25.P25DecoderLSM;
|
||||
import io.github.dsheirer.module.decode.p25.P25DecoderState;
|
||||
import io.github.dsheirer.module.decode.p25.P25_C4FMDecoder;
|
||||
import io.github.dsheirer.module.decode.p25.P25_LSMDecoder;
|
||||
import io.github.dsheirer.module.decode.p25.audio.P25AudioModule;
|
||||
import io.github.dsheirer.module.decode.p25.message.filter.P25MessageFilterSet;
|
||||
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
|
||||
@@ -247,12 +247,12 @@ public class DecoderFactory
|
||||
switch(modulation)
|
||||
{
|
||||
case C4FM:
|
||||
modules.add(new P25_C4FMDecoder(aliasList));
|
||||
modules.add(new P25DecoderState(aliasList, channelType, P25Decoder.Modulation.C4FM,
|
||||
modules.add(new P25DecoderC4FM(aliasList));
|
||||
modules.add(new P25DecoderState(aliasList, channelType, Modulation.C4FM,
|
||||
p25Config.getIgnoreDataCalls()));
|
||||
break;
|
||||
case CQPSK:
|
||||
modules.add(new P25_LSMDecoder(aliasList));
|
||||
modules.add(new P25DecoderLSM(aliasList));
|
||||
modules.add(new P25DecoderState(aliasList, channelType, Modulation.CQPSK, p25Config.getIgnoreDataCalls()));
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -25,7 +25,7 @@ import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
|
||||
public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
||||
{
|
||||
private P25_LSMDecoder.Modulation mModulation = P25Decoder.Modulation.C4FM;
|
||||
private P25DecoderLSM.Modulation mModulation = P25Decoder.Modulation.C4FM;
|
||||
|
||||
private int mCallTimeout = 1;
|
||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||
@@ -48,12 +48,12 @@ public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "modulation")
|
||||
public P25_LSMDecoder.Modulation getModulation()
|
||||
public P25DecoderLSM.Modulation getModulation()
|
||||
{
|
||||
return mModulation;
|
||||
}
|
||||
|
||||
public void setModulation(P25_LSMDecoder.Modulation modulation)
|
||||
public void setModulation(P25DecoderLSM.Modulation modulation)
|
||||
{
|
||||
mModulation = modulation;
|
||||
}
|
||||
|
||||
@@ -18,26 +18,70 @@ package io.github.dsheirer.module.decode.p25;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferListener;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.ISourceEventProvider;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
public abstract class P25Decoder extends Decoder implements ISourceEventListener, ISourceEventProvider, Instrumentable
|
||||
public abstract class P25Decoder extends Decoder implements ISourceEventListener, ISourceEventProvider,
|
||||
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private double mSampleRate;
|
||||
private P25MessageProcessor mMessageProcessor;
|
||||
private AliasList mAliasList;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
private double mSymbolRate;
|
||||
|
||||
public P25Decoder(AliasList aliasList)
|
||||
public P25Decoder(double symbolRate, AliasList aliasList)
|
||||
{
|
||||
mSymbolRate = symbolRate;
|
||||
mAliasList = aliasList;
|
||||
mMessageProcessor = new P25MessageProcessor(mAliasList);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
}
|
||||
|
||||
protected double getSymbolRate()
|
||||
{
|
||||
return mSymbolRate;
|
||||
}
|
||||
|
||||
protected AliasList getAliasList()
|
||||
{
|
||||
return mAliasList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current sample rate for this decoder
|
||||
*/
|
||||
protected double getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets current sample rate for this decoder
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
if(sampleRate <= getSymbolRate() * 2)
|
||||
{
|
||||
throw new IllegalArgumentException("Sample rate [" + sampleRate + "] must be >9600 (2 * " +
|
||||
getSymbolRate() + " symbol rate)");
|
||||
}
|
||||
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Samples per symbol based on current sample rate and symbol rate.
|
||||
*/
|
||||
public float getSamplesPerSymbol()
|
||||
{
|
||||
return (float)(getSampleRate() / getSymbolRate());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
@@ -46,6 +90,83 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener
|
||||
mMessageProcessor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source event listener to receive source events from this decoder.
|
||||
*/
|
||||
@Override
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mSourceEventListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a registered source event listener from receiving source events from this decoder
|
||||
*/
|
||||
@Override
|
||||
public void removeSourceEventListener()
|
||||
{
|
||||
mSourceEventListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
return new Listener<SourceEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
process(sourceEvent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-class processing of received source events
|
||||
* @param sourceEvent
|
||||
*/
|
||||
protected abstract void process(SourceEvent sourceEvent);
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to an optional registered listener. This method should primarily be used to
|
||||
* issue frequency correction requests to the channel source.
|
||||
* @param sourceEvent to broadcast
|
||||
*/
|
||||
protected void broadcast(SourceEvent sourceEvent)
|
||||
{
|
||||
if(mSourceEventListener != null)
|
||||
{
|
||||
mSourceEventListener.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener interface to receive reusable complex buffers
|
||||
*/
|
||||
@Override
|
||||
public Listener<ReusableComplexBuffer> getReusableComplexBufferListener()
|
||||
{
|
||||
return P25Decoder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the decoder
|
||||
*/
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the decoder
|
||||
*/
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
|
||||
@@ -20,79 +20,88 @@ 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.psk.DQPSKDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.IComplexBufferListener;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class P25_C4FMDecoder extends P25Decoder implements IComplexBufferListener, Listener<ComplexBuffer>
|
||||
public class P25DecoderC4FM extends P25Decoder
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25_C4FMDecoder.class);
|
||||
protected static final double SYMBOL_RATE = 4800;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25DecoderC4FM.class);
|
||||
protected static final float SYMBOL_TIMING_GAIN = 0.1f;
|
||||
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||
protected DQPSKDemodulator mQPSKDemodulator;
|
||||
protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator;
|
||||
protected CostasLoop mCostasLoop;
|
||||
protected P25MessageFramer mMessageFramer;
|
||||
protected double mSampleRate;
|
||||
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
|
||||
private ComplexFIRFilter mBasebandFilter;
|
||||
|
||||
public P25_C4FMDecoder(AliasList aliasList)
|
||||
/**
|
||||
* P25 Phase 1 - standard C4FM modulation decoder. Uses Differential QPSK decoding with a Costas PLL and a
|
||||
* decision-directed phase and timing error detector.
|
||||
*
|
||||
* @param aliasList
|
||||
*/
|
||||
public P25DecoderC4FM(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
super(4800.0, aliasList);
|
||||
setSampleRate(48000.0);
|
||||
}
|
||||
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
if(sampleRate <= SYMBOL_RATE * 2)
|
||||
{
|
||||
throw new IllegalArgumentException("Sample rate must be at least twice the symbol rate [4800]");
|
||||
}
|
||||
super.setSampleRate(sampleRate);
|
||||
|
||||
mSampleRate = sampleRate;
|
||||
mBasebandFilter = new ComplexFIRFilter(getBasebandFilter(), 1.0f);
|
||||
|
||||
mCostasLoop = new CostasLoop(mSampleRate, SYMBOL_RATE);
|
||||
mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
|
||||
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SYMBOL_TIMING_GAIN);
|
||||
|
||||
mInterpolatingSampleBuffer = new InterpolatingSampleBufferInstrumented((float)(sampleRate / SYMBOL_RATE));
|
||||
mQPSKDemodulator = new DQPSKDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||
mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||
|
||||
//Message framer can trigger a symbol-inversion correction to the PLL when detected
|
||||
//Message framer can issue a symbol-inversion correction request to the PLL when detected
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
mQPSKDemodulator.setDibitListener(mMessageFramer);
|
||||
mQPSKDemodulator.setSymbolListener(mMessageFramer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method for processing incoming complex sample buffers
|
||||
* @param reusableComplexBuffer containing channelized complex samples
|
||||
*/
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
ComplexBuffer filtered = filter(complexBuffer);
|
||||
//User accounting of the incoming buffer is handled by the filter
|
||||
ReusableComplexBuffer filtered = filter(reusableComplexBuffer);
|
||||
|
||||
//User accounting of the filtered buffer is handled by the demodulator
|
||||
mQPSKDemodulator.receive(filtered);
|
||||
}
|
||||
|
||||
protected ComplexBuffer filter(ComplexBuffer complexBuffer)
|
||||
/**
|
||||
* Filters the complex buffer and returns a new reusable complex buffer with the filtered contents.
|
||||
* @param reusableComplexBuffer to filter
|
||||
* @return filtered complex buffer
|
||||
*/
|
||||
protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
return mBasebandFilter.filter(complexBuffer);
|
||||
}
|
||||
|
||||
private double getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
//User accounting of the incoming buffer is handled by the filter
|
||||
return mBasebandFilter.filter(reusableComplexBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a baseband filter for this decoder using the current sample rate
|
||||
*/
|
||||
private float[] getBasebandFilter()
|
||||
{
|
||||
//Attempt to reuse a cached (ie already-designed) filter if available
|
||||
float[] filter = mBasebandFilters.get(getSampleRate());
|
||||
|
||||
if(filter == null)
|
||||
@@ -110,19 +119,19 @@ public class P25_C4FMDecoder extends P25Decoder implements IComplexBufferListene
|
||||
try
|
||||
{
|
||||
filter = FilterFactory.getTaps(specification);
|
||||
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate());
|
||||
}
|
||||
|
||||
if(filter != null)
|
||||
{
|
||||
mBasebandFilters.put(getSampleRate(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("Couldn't design a C4FM symbol filter for sample rate: " + mSampleRate);
|
||||
throw new IllegalStateException("Couldn't design a C4FM baseband filter for sample rate: " + getSampleRate());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,60 +150,36 @@ public class P25_C4FMDecoder extends P25Decoder implements IComplexBufferListene
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
protected void process(SourceEvent sourceEvent)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
return new Listener<FrequencyChangeEvent>()
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
@Override
|
||||
public void receive(FrequencyChangeEvent frequencyChangeEvent)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
return P25_C4FMDecoder.this;
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
mCostasLoop.reset();
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
mCostasLoop.reset();
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* P25 modulation supported by this decoder
|
||||
*/
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.C4FM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this decoder to prepare for processing a new channel
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mCostasLoop.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*******************************************************************************
|
||||
* 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.DQPSKDecisionDirectedDemodulatorInstrumented;
|
||||
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.reusable.ReusableComplexBuffer;
|
||||
|
||||
public class P25DecoderC4FMInstrumented extends P25DecoderC4FM
|
||||
{
|
||||
private Listener<Double> mPLLPhaseErrorListener;
|
||||
private Listener<Double> mPLLFrequencyListener;
|
||||
private Listener<Double> mSamplesPerSymbolListener;
|
||||
private Listener<Complex> mComplexSymbolListener;
|
||||
private Listener<ReusableComplexBuffer> mFilteredSymbolListener;
|
||||
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
|
||||
|
||||
/**
|
||||
* Instrumented version of the P25 C4FM decoder that supports registering listeners to provide access to data as
|
||||
* it is being processed by the decoder.
|
||||
*
|
||||
* @param aliasList
|
||||
*/
|
||||
public P25DecoderC4FMInstrumented(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the filter method so that we can capture the filtered samples for instrumentation
|
||||
*/
|
||||
protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer);
|
||||
|
||||
//Increment user count before we hand to another listener or return from this method
|
||||
filtered.incrementUserCount();
|
||||
|
||||
if(mFilteredSymbolListener != null)
|
||||
{
|
||||
filtered.incrementUserCount();
|
||||
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(getSamplesPerSymbol(), SYMBOL_TIMING_GAIN);
|
||||
mInterpolatingSampleBuffer = instrumentedBuffer;
|
||||
|
||||
DQPSKDecisionDirectedDemodulatorInstrumented instrumented = new DQPSKDecisionDirectedDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, getSampleRate());
|
||||
mQPSKDemodulator = instrumented;
|
||||
|
||||
instrumented.setComplexSymbolListener(mComplexSymbolListener);
|
||||
instrumented.setPLLErrorListener(mPLLPhaseErrorListener);
|
||||
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
|
||||
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
|
||||
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
|
||||
instrumented.setSymbolListener(mMessageFramer);
|
||||
}
|
||||
|
||||
public void setComplexSymbolListener(Listener<Complex> listener)
|
||||
{
|
||||
mComplexSymbolListener = listener;
|
||||
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setComplexSymbolListener(listener);
|
||||
}
|
||||
|
||||
public void setPLLPhaseErrorListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLPhaseErrorListener = listener;
|
||||
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener);
|
||||
}
|
||||
|
||||
public void setPLLFrequencyListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLFrequencyListener = listener;
|
||||
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener);
|
||||
}
|
||||
|
||||
public void setFilteredBufferListener(Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mFilteredSymbolListener = listener;
|
||||
}
|
||||
|
||||
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
|
||||
{
|
||||
mSymbolDecisionDataListener = listener;
|
||||
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener);
|
||||
}
|
||||
|
||||
public void setSamplesPerSymbolListener(Listener<Double> listener)
|
||||
{
|
||||
mSamplesPerSymbolListener = listener;
|
||||
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,11 @@ import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
@@ -43,7 +47,7 @@ public class P25DecoderEditor extends ValidatingEditor<Channel>
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JComboBox<P25_LSMDecoder.Modulation> mComboModulation;
|
||||
private JComboBox<P25DecoderLSM.Modulation> mComboModulation;
|
||||
private JCheckBox mIgnoreDataCalls;
|
||||
private JLabel mTrafficChannelPoolSizeLabel;
|
||||
private JSlider mTrafficChannelPoolSize;
|
||||
@@ -57,9 +61,9 @@ public class P25DecoderEditor extends ValidatingEditor<Channel>
|
||||
{
|
||||
setLayout( new MigLayout( "insets 0 0 0 0,wrap 3", "[right][grow,fill][]", "" ) );
|
||||
|
||||
mComboModulation = new JComboBox<P25_LSMDecoder.Modulation>();
|
||||
mComboModulation.setModel( new DefaultComboBoxModel<P25_LSMDecoder.Modulation>(
|
||||
P25_LSMDecoder.Modulation.values() ) );
|
||||
mComboModulation = new JComboBox<P25DecoderLSM.Modulation>();
|
||||
mComboModulation.setModel( new DefaultComboBoxModel<P25DecoderLSM.Modulation>(
|
||||
P25DecoderLSM.Modulation.values() ) );
|
||||
mComboModulation.setEnabled( false );
|
||||
mComboModulation.addActionListener( new ActionListener()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/*******************************************************************************
|
||||
* 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.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter;
|
||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||
import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class P25DecoderLSM extends P25Decoder
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25DecoderLSM.class);
|
||||
protected static final float SYMBOL_TIMING_GAIN = 0.1f;
|
||||
|
||||
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
|
||||
private ComplexFIRFilter mBasebandFilter;
|
||||
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
|
||||
protected DQPSKGardnerDemodulator mQPSKDemodulator;
|
||||
protected P25MessageFramer mMessageFramer;
|
||||
protected CostasLoop mCostasLoop;
|
||||
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||
|
||||
/**
|
||||
* P25 Phase 1 - linear simulcast modulation (LSM) decoder. Uses Differential QPSK decoding with a Costas PLL and
|
||||
* a gardner timing error detector.
|
||||
*
|
||||
* @param aliasList
|
||||
*/
|
||||
public P25DecoderLSM(AliasList aliasList)
|
||||
{
|
||||
super(4800.0, aliasList);
|
||||
setSampleRate(48000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or changes the channel sample rate
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
super.setSampleRate(sampleRate);
|
||||
|
||||
mBasebandFilter = new ComplexFIRFilter(getBasebandFilter(), 1.0f);
|
||||
|
||||
mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
|
||||
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SYMBOL_TIMING_GAIN);
|
||||
|
||||
mQPSKDemodulator = new DQPSKGardnerDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||
|
||||
//Message framer can issue a symbol-inversion correction request to the PLL when detected
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
mQPSKDemodulator.setSymbolListener(mMessageFramer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method for receiving incoming channel samples
|
||||
*/
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
//The filter will decrement the user count when finished
|
||||
ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer);
|
||||
|
||||
//AGC will decrement the user count when finished
|
||||
ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered);
|
||||
|
||||
//Decoder will decrement the user count when finished
|
||||
mQPSKDemodulator.receive(gainApplied);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the complex buffer and returns a new reusable complex buffer with the filtered contents.
|
||||
* @param reusableComplexBuffer to filter
|
||||
* @return filtered complex buffer
|
||||
*/
|
||||
protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
//User accounting of the incoming buffer is handled by the filter
|
||||
return mBasebandFilter.filter(reusableComplexBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes source events - updates buffer and decoder when sample rate is established/changed and resets the
|
||||
* PLL after frequency changes/corrections
|
||||
*/
|
||||
@Override
|
||||
protected void process(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
mCostasLoop.reset();
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
mCostasLoop.reset();
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this decoder to prepare for processing a new channel
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mCostasLoop.reset();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBasebandFilter.dispose();
|
||||
mBasebandFilter = null;
|
||||
|
||||
mAGC.dispose();
|
||||
mAGC = null;
|
||||
|
||||
mQPSKDemodulator.dispose();
|
||||
mQPSKDemodulator = null;
|
||||
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.CQPSK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a baseband filter for this decoder using the current sample rate
|
||||
*/
|
||||
private float[] getBasebandFilter()
|
||||
{
|
||||
//Attempt to reuse a cached (ie already-designed) filter if available
|
||||
float[] filter = mBasebandFilters.get(getSampleRate());
|
||||
|
||||
if(filter == null)
|
||||
{
|
||||
filter = FilterFactory.getLowPass(48000, 7250, 8000, 60,
|
||||
WindowType.HANN, true);
|
||||
|
||||
mBasebandFilters.put(getSampleRate(), filter);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
@@ -16,23 +16,31 @@
|
||||
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.DQPSKGardnerDemodulatorInstrumented;
|
||||
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;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
|
||||
public class P25_C4FMDecoderInstrumented extends P25_C4FMDecoder
|
||||
public class P25DecoderLSMInstrumented extends P25DecoderLSM
|
||||
{
|
||||
private Listener<Double> mPLLPhaseErrorListener;
|
||||
private Listener<Double> mPLLFrequencyListener;
|
||||
private Listener<Double> mSamplesPerSymbolListener;
|
||||
private Listener<Complex> mSymbolListener;
|
||||
private Listener<ComplexBuffer> mFilteredSymbolListener;
|
||||
private Listener<Complex> mComplexSymbolListener;
|
||||
private Listener<ReusableComplexBuffer> mFilteredSymbolListener;
|
||||
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
|
||||
|
||||
public P25_C4FMDecoderInstrumented(AliasList aliasList)
|
||||
/**
|
||||
* P25 Phase 1 - linear simulcast modulation (LSM) decoder. Uses Differential QPSK decoding with a Costas PLL and
|
||||
* a gardner timing error detector.
|
||||
*
|
||||
* Instrumented decoder instance.
|
||||
*
|
||||
* @param aliasList
|
||||
*/
|
||||
public P25DecoderLSMInstrumented(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
}
|
||||
@@ -40,12 +48,16 @@ public class P25_C4FMDecoderInstrumented extends P25_C4FMDecoder
|
||||
/**
|
||||
* Overrides the filter method so that we can capture the filtered samples for instrumentation
|
||||
*/
|
||||
protected ComplexBuffer filter(ComplexBuffer complexBuffer)
|
||||
protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
ComplexBuffer filtered = super.filter(complexBuffer);
|
||||
ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer);
|
||||
|
||||
//Increment user count before we hand to another listener or return from this method
|
||||
filtered.incrementUserCount();
|
||||
|
||||
if(mFilteredSymbolListener != null)
|
||||
{
|
||||
filtered.incrementUserCount();
|
||||
mFilteredSymbolListener.receive(filtered);
|
||||
}
|
||||
|
||||
@@ -58,39 +70,40 @@ public class P25_C4FMDecoderInstrumented extends P25_C4FMDecoder
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
super.setSampleRate(sampleRate);
|
||||
InterpolatingSampleBufferInstrumented instrumentedBuffer = new InterpolatingSampleBufferInstrumented((float)(sampleRate / SYMBOL_RATE));
|
||||
|
||||
InterpolatingSampleBufferInstrumented instrumentedBuffer = new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SYMBOL_TIMING_GAIN);
|
||||
mInterpolatingSampleBuffer = instrumentedBuffer;
|
||||
|
||||
DQPSKDemodulatorInstrumented instrumented = new DQPSKDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, mSampleRate);
|
||||
DQPSKGardnerDemodulatorInstrumented instrumented = new DQPSKGardnerDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, getSampleRate());
|
||||
mQPSKDemodulator = instrumented;
|
||||
|
||||
instrumented.setSymbolListener(mSymbolListener);
|
||||
instrumented.setComplexSymbolListener(mComplexSymbolListener);
|
||||
instrumented.setPLLErrorListener(mPLLPhaseErrorListener);
|
||||
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
|
||||
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
|
||||
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
|
||||
instrumented.setDibitListener(mMessageFramer);
|
||||
instrumented.setSymbolListener(mMessageFramer);
|
||||
}
|
||||
|
||||
public void setSymbolListener(Listener<Complex> listener)
|
||||
public void setComplexSymbolListener(Listener<Complex> listener)
|
||||
{
|
||||
mSymbolListener = listener;
|
||||
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSymbolListener(listener);
|
||||
mComplexSymbolListener = listener;
|
||||
((DQPSKGardnerDemodulatorInstrumented)mQPSKDemodulator).setComplexSymbolListener(listener);
|
||||
}
|
||||
|
||||
public void setPLLPhaseErrorListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLPhaseErrorListener = listener;
|
||||
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener);
|
||||
((DQPSKGardnerDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener);
|
||||
}
|
||||
|
||||
public void setPLLFrequencyListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLFrequencyListener = listener;
|
||||
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener);
|
||||
((DQPSKGardnerDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener);
|
||||
}
|
||||
|
||||
public void setFilteredBufferListener(Listener<ComplexBuffer> listener)
|
||||
public void setFilteredBufferListener(Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mFilteredSymbolListener = listener;
|
||||
}
|
||||
@@ -98,12 +111,12 @@ public class P25_C4FMDecoderInstrumented extends P25_C4FMDecoder
|
||||
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
|
||||
{
|
||||
mSymbolDecisionDataListener = listener;
|
||||
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener);
|
||||
((DQPSKGardnerDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener);
|
||||
}
|
||||
|
||||
public void setSamplesPerSymbolListener(Listener<Double> listener)
|
||||
{
|
||||
mSamplesPerSymbolListener = listener;
|
||||
((DQPSKDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener);
|
||||
((DQPSKGardnerDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||
import io.github.dsheirer.dsp.psk.LSMDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.QPSKSymbolDecoder;
|
||||
import io.github.dsheirer.instrument.tap.TapGroup;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter;
|
||||
import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferListener;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class P25_LSMDecoder extends P25Decoder implements IReusableComplexBufferListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25_LSMDecoder.class);
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output";
|
||||
private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output";
|
||||
private static final String INSTRUMENT_LSM_DEMODULATOR_OUTPUT = "Tap Point: LSM Demodulator Output";
|
||||
private static final String INSTRUMENT_QPSK_SLICER_OUTPUT = "Tap Point: QPSK Slicer Output";
|
||||
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
|
||||
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
||||
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
||||
private ComplexFeedForwardGainControl mAGC =
|
||||
new ComplexFeedForwardGainControl(32);
|
||||
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
||||
private QPSKPolarSlicer mQPSKSlicer = new QPSKPolarSlicer();
|
||||
private P25MessageFramer mMessageFramer;
|
||||
private ReusableBufferListener mReusableBufferListener = new ReusableBufferListener();
|
||||
|
||||
public P25_LSMDecoder(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
|
||||
mBasebandFilter = new ComplexFIRFilter_CB_CB(FilterFactory.getLowPass(
|
||||
48000, 7250, 8000, 60, WindowType.HANN, true), 1.0f);
|
||||
|
||||
mBasebandFilter.setListener(mStreamConverter);
|
||||
|
||||
mStreamConverter.setListener(mAGC);
|
||||
|
||||
mAGC.setListener(mLSMDemodulator);
|
||||
|
||||
mLSMDemodulator.setSymbolListener(mQPSKSlicer);
|
||||
|
||||
mMessageFramer = new P25MessageFramer(aliasList, mLSMDemodulator);
|
||||
mQPSKSlicer.addListener(mMessageFramer);
|
||||
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBasebandFilter.dispose();
|
||||
mBasebandFilter = null;
|
||||
|
||||
mStreamConverter.dispose();
|
||||
mStreamConverter = null;
|
||||
|
||||
mAGC.dispose();
|
||||
mAGC = null;
|
||||
|
||||
mLSMDemodulator.dispose();
|
||||
mLSMDemodulator = null;
|
||||
|
||||
mQPSKSlicer.dispose();
|
||||
mQPSKSlicer = null;
|
||||
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableComplexBuffer> getReusableComplexBufferListener()
|
||||
{
|
||||
return mReusableBufferListener;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.CQPSK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of instrumentation taps for monitoring internal processing
|
||||
*/
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
if(mAvailableTaps == null)
|
||||
{
|
||||
mAvailableTaps = new ArrayList<>();
|
||||
|
||||
TapGroup group = new TapGroup("P25 LSM Decoder");
|
||||
|
||||
group.add(new ComplexTap(INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f));
|
||||
group.add(new ComplexTap(INSTRUMENT_AGC_OUTPUT, 0, 1.0f));
|
||||
group.add(new QPSKTap(INSTRUMENT_LSM_DEMODULATOR_OUTPUT, 0, 1.0f));
|
||||
group.add(new DibitTap(INSTRUMENT_QPSK_SLICER_OUTPUT, 0, 0.1f));
|
||||
|
||||
mAvailableTaps.add(group);
|
||||
|
||||
if(mLSMDemodulator != null)
|
||||
{
|
||||
mAvailableTaps.addAll(mLSMDemodulator.getTapGroups());
|
||||
}
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void registerTap(Tap tap)
|
||||
{
|
||||
if(mLSMDemodulator != null)
|
||||
{
|
||||
mLSMDemodulator.registerTap(tap);
|
||||
}
|
||||
|
||||
switch(tap.getName())
|
||||
{
|
||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
||||
ComplexSampleTap baseband = (ComplexSampleTap)tap;
|
||||
mStreamConverter.setListener(baseband);
|
||||
baseband.setListener(mAGC);
|
||||
break;
|
||||
case INSTRUMENT_AGC_OUTPUT:
|
||||
ComplexSampleTap agcSymbol = (ComplexSampleTap)tap;
|
||||
mAGC.setListener(agcSymbol);
|
||||
agcSymbol.setListener(mLSMDemodulator);
|
||||
break;
|
||||
case INSTRUMENT_LSM_DEMODULATOR_OUTPUT:
|
||||
QPSKTap qpsk = (QPSKTap)tap;
|
||||
mLSMDemodulator.setSymbolListener(qpsk);
|
||||
qpsk.setListener(mQPSKSlicer);
|
||||
break;
|
||||
case INSTRUMENT_QPSK_SLICER_OUTPUT:
|
||||
mQPSKSlicer.addListener((DibitTap)tap);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized tap: " +
|
||||
tap.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void unregisterTap(Tap tap)
|
||||
{
|
||||
if(mLSMDemodulator != null)
|
||||
{
|
||||
mLSMDemodulator.unregisterTap(tap);
|
||||
}
|
||||
|
||||
switch(tap.getName())
|
||||
{
|
||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
||||
mStreamConverter.setListener(mAGC);
|
||||
break;
|
||||
case INSTRUMENT_AGC_OUTPUT:
|
||||
mAGC.setListener(mLSMDemodulator);
|
||||
break;
|
||||
case INSTRUMENT_LSM_DEMODULATOR_OUTPUT:
|
||||
mLSMDemodulator.setSymbolListener(mQPSKSlicer);
|
||||
break;
|
||||
case INSTRUMENT_QPSK_SLICER_OUTPUT:
|
||||
mQPSKSlicer.removeListener((DibitTap)tap);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized tap: " +
|
||||
tap.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSourceEventListener()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class ReusableBufferListener implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
@Override
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
float[] samples = reusableComplexBuffer.getSamplesCopy();
|
||||
|
||||
//TODO: redesign the filter chain so that we can simply pass a float array ...
|
||||
|
||||
mBasebandFilter.receive(new ComplexBuffer(samples));
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user