mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#90 [WIP] Resolves issue with MPT-1327 decoder which has apparently been broken for a long time.
This commit is contained in:
@@ -1,296 +1,302 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 dsp.fsk;
|
||||
|
||||
import buffer.BooleanAveragingBuffer;
|
||||
import dsp.symbol.SymbolEvent;
|
||||
import dsp.symbol.SymbolEvent.Shift;
|
||||
import instrument.Instrumentable;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.TapGroup;
|
||||
import instrument.tap.stream.SymbolEventTap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.real.RealBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.real.RealBuffer;
|
||||
import buffer.BooleanAveragingBuffer;
|
||||
import dsp.symbol.SymbolEvent;
|
||||
import dsp.symbol.SymbolEvent.Shift;
|
||||
|
||||
/**
|
||||
* Binary Frequency Shift Keying (FSK) decoder. Implements a BFSK correlation
|
||||
* decoder. Provides normal or inverted decoded output. Requires the symbol
|
||||
* rate to be an integer multiple of the sample rate.
|
||||
*
|
||||
* Implements a correlation decoder that correlates a modulated FSK signal upon
|
||||
* against a one-baud delayed version of itself, converting all float samples to
|
||||
* binary (0 or 1), and uses XOR to produce the correlation value. Low pass
|
||||
*
|
||||
* Implements a correlation decoder that correlates a modulated FSK signal upon
|
||||
* against a one-baud delayed version of itself, converting all float samples to
|
||||
* binary (0 or 1), and uses XOR to produce the correlation value. Low pass
|
||||
* filtering smoothes the correlated output.
|
||||
*
|
||||
*
|
||||
* Automatically aligns to symbol timing during symbol transitions (0 to 1, or
|
||||
* 1 to 0) by inspecting the samples at the symbol edges and advancing or
|
||||
* 1 to 0) by inspecting the samples at the symbol edges and advancing or
|
||||
* retarding symbol window to maintain continuous symbol alignment.
|
||||
*
|
||||
*
|
||||
* Use a DC-removal filter prior to this decoder to ensure samples don't have
|
||||
* a DC component.
|
||||
*
|
||||
*
|
||||
* Implements instrumentable interface, so that slice events can be received
|
||||
* externally to analyze decoder performance.
|
||||
*/
|
||||
public class FSK2Decoder implements Instrumentable, Listener<RealBuffer>
|
||||
{
|
||||
public enum Output{ NORMAL, INVERTED };
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FSK2Decoder.class);
|
||||
|
||||
/* Instrumentation taps */
|
||||
private static final String INSTRUMENT_DECISION =
|
||||
"Tap Point: FSK2 Symbol Decision";
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
private ArrayList<SymbolEventTap> mTaps = new ArrayList<SymbolEventTap>();
|
||||
|
||||
private Listener<Boolean> mListener;
|
||||
private BooleanAveragingBuffer mDelayBuffer;
|
||||
private BooleanAveragingBuffer mLowPassFilter;
|
||||
private Slicer mSlicer;
|
||||
private boolean mNormalOutput;
|
||||
private int mSamplesPerSymbol;
|
||||
private int mSymbolRate;
|
||||
|
||||
public FSK2Decoder( int sampleRate, int symbolRate, Output output )
|
||||
{
|
||||
/* Ensure we're using an integral symbolRate of the sampleRate */
|
||||
assert( sampleRate % symbolRate == 0 );
|
||||
|
||||
mSamplesPerSymbol = (int)( sampleRate / symbolRate );
|
||||
mNormalOutput = ( output == Output.NORMAL );
|
||||
mSymbolRate = symbolRate;
|
||||
|
||||
mDelayBuffer = new BooleanAveragingBuffer( mSamplesPerSymbol );
|
||||
mLowPassFilter = new BooleanAveragingBuffer( mSamplesPerSymbol );
|
||||
mSlicer = new Slicer( mSamplesPerSymbol );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of all references to prepare for garbage collection
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
public enum Output
|
||||
{
|
||||
NORMAL, INVERTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary sample input
|
||||
*/
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
for( float sample: buffer.getSamples() )
|
||||
{
|
||||
/* Instrumentation taps */
|
||||
private static final String INSTRUMENT_DECISION =
|
||||
"Tap Point: FSK2 Symbol Decision";
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
private ArrayList<SymbolEventTap> mTaps = new ArrayList<SymbolEventTap>();
|
||||
|
||||
private Listener<Boolean> mListener;
|
||||
private BooleanAveragingBuffer mDelayBuffer;
|
||||
private BooleanAveragingBuffer mLowPassFilter;
|
||||
private Slicer mSlicer;
|
||||
private boolean mNormalOutput;
|
||||
private int mSamplesPerSymbol;
|
||||
private int mSymbolRate;
|
||||
|
||||
public FSK2Decoder(int sampleRate, int symbolRate, Output output)
|
||||
{
|
||||
/* Ensure we're using an integral symbolRate of the sampleRate */
|
||||
assert (sampleRate % symbolRate == 0);
|
||||
|
||||
mSamplesPerSymbol = (int) (sampleRate / symbolRate);
|
||||
mNormalOutput = (output == Output.NORMAL);
|
||||
mSymbolRate = symbolRate;
|
||||
|
||||
mDelayBuffer = new BooleanAveragingBuffer(mSamplesPerSymbol);
|
||||
mLowPassFilter = new BooleanAveragingBuffer(mSamplesPerSymbol);
|
||||
mSlicer = new Slicer(mSamplesPerSymbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of all references to prepare for garbage collection
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary sample input
|
||||
*/
|
||||
@Override
|
||||
public void receive(RealBuffer buffer)
|
||||
{
|
||||
for(float sample : buffer.getSamples())
|
||||
{
|
||||
/* Square the sample. Greater than zero is a 1 (true) and less than
|
||||
* zero is a 0 (false) */
|
||||
boolean bitSample = ( sample >= 0.0f );
|
||||
boolean bitSample = (sample >= 0.0f);
|
||||
|
||||
/* Feed the delay buffer and fetch the one-baud delayed sample */
|
||||
boolean delayedBitSample = mDelayBuffer.get( bitSample );
|
||||
boolean delayedBitSample = mDelayBuffer.get(bitSample);
|
||||
|
||||
/* Correlation: xor current bit with delayed bit */
|
||||
boolean softBit = bitSample ^ delayedBitSample;
|
||||
boolean softBit = bitSample ^ delayedBitSample;
|
||||
|
||||
/* Low pass filter to smooth the correlated values */
|
||||
boolean filteredSoftBit = mLowPassFilter.getAverage( softBit );
|
||||
boolean filteredSoftBit = mLowPassFilter.getAverage(softBit);
|
||||
|
||||
/* Send the filtered correlated bit to the slicer */
|
||||
mSlicer.receive( filteredSoftBit );
|
||||
}
|
||||
}
|
||||
mSlicer.receive(filteredSoftBit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to receive the decoded FSK bits
|
||||
*/
|
||||
public void setListener( Listener<Boolean> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
/**
|
||||
* Registers a listener to receive the decoded FSK bits
|
||||
*/
|
||||
public void setListener(Listener<Boolean> listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener
|
||||
*/
|
||||
public void removeListener( Listener<Boolean> listener )
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
/**
|
||||
* Removes the listener
|
||||
*/
|
||||
public void removeListener(Listener<Boolean> listener)
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Symbol slicer with auto-aligning baud timing
|
||||
*/
|
||||
public class Slicer
|
||||
{
|
||||
private BitSet mBitSet = new BitSet();
|
||||
private int mSymbolLength;
|
||||
private int mDecisionThreshold;
|
||||
private int mSampleCounter;
|
||||
|
||||
public Slicer( int samplesPerSymbol )
|
||||
{
|
||||
mSymbolLength = samplesPerSymbol;
|
||||
|
||||
mDecisionThreshold = (int)( mSymbolLength / 2 );
|
||||
/**
|
||||
* Symbol slicer with auto-aligning baud timing
|
||||
*/
|
||||
public class Slicer
|
||||
{
|
||||
private BitSet mBitSet = new BitSet();
|
||||
private int mSymbolLength;
|
||||
private int mDecisionThreshold;
|
||||
private int mSampleCounter;
|
||||
|
||||
public Slicer(int samplesPerSymbol)
|
||||
{
|
||||
mSymbolLength = samplesPerSymbol;
|
||||
|
||||
mDecisionThreshold = (int) (mSymbolLength / 2);
|
||||
|
||||
/* Adjust for an odd number of samples per baud */
|
||||
if( mSymbolLength % 2 == 1 )
|
||||
{
|
||||
mDecisionThreshold++;
|
||||
}
|
||||
}
|
||||
|
||||
public void receive( boolean softBit )
|
||||
{
|
||||
if( mSampleCounter >= 0 )
|
||||
{
|
||||
if( softBit )
|
||||
{
|
||||
mBitSet.set( mSampleCounter );
|
||||
}
|
||||
else
|
||||
{
|
||||
mBitSet.clear( mSampleCounter );
|
||||
}
|
||||
}
|
||||
|
||||
mSampleCounter++;
|
||||
|
||||
if( mSampleCounter >= mSymbolLength )
|
||||
{
|
||||
boolean decision = mBitSet.cardinality() >= mDecisionThreshold;
|
||||
|
||||
send( decision );
|
||||
if(mSymbolLength % 2 == 1)
|
||||
{
|
||||
mDecisionThreshold++;
|
||||
}
|
||||
}
|
||||
|
||||
public void receive(boolean softBit)
|
||||
{
|
||||
if(mSampleCounter >= 0)
|
||||
{
|
||||
if(softBit)
|
||||
{
|
||||
mBitSet.set(mSampleCounter);
|
||||
}
|
||||
else
|
||||
{
|
||||
mBitSet.clear(mSampleCounter);
|
||||
}
|
||||
}
|
||||
|
||||
mSampleCounter++;
|
||||
|
||||
if(mSampleCounter >= mSymbolLength)
|
||||
{
|
||||
boolean decision = mBitSet.cardinality() >= mDecisionThreshold;
|
||||
|
||||
send(decision);
|
||||
|
||||
/* Shift timing left if the left bit in the bitset is opposite
|
||||
* the decision and the right bit is the same */
|
||||
if( ( mBitSet.get( 0 ) ^ decision ) &&
|
||||
( !( mBitSet.get( mSymbolLength - 1 ) ^ decision ) ) )
|
||||
{
|
||||
sendTapEvent( mBitSet, Shift.LEFT, decision );
|
||||
|
||||
reset();
|
||||
|
||||
mSampleCounter--;
|
||||
}
|
||||
if((mBitSet.get(0) ^ decision) &&
|
||||
(!(mBitSet.get(mSymbolLength - 1) ^ decision)))
|
||||
{
|
||||
sendTapEvent(mBitSet, Shift.LEFT, decision);
|
||||
|
||||
reset();
|
||||
|
||||
mSampleCounter--;
|
||||
}
|
||||
/* Shift timing right if the left bit is the same as the
|
||||
* decision and the right bit is opposite */
|
||||
else if( ( !( mBitSet.get( 0 ) ^ decision ) ) &&
|
||||
( mBitSet.get( mSymbolLength - 1 ) ^ decision ) )
|
||||
{
|
||||
sendTapEvent( mBitSet, Shift.RIGHT, decision );
|
||||
else if((!(mBitSet.get(0) ^ decision)) &&
|
||||
(mBitSet.get(mSymbolLength - 1) ^ decision))
|
||||
{
|
||||
sendTapEvent(mBitSet, Shift.RIGHT, decision);
|
||||
|
||||
/* Last bit from previous symbol to pre-fill next symbol */
|
||||
boolean previousSoftBit = mBitSet.get( mSymbolLength - 1 );
|
||||
|
||||
reset();
|
||||
|
||||
if( previousSoftBit )
|
||||
{
|
||||
mBitSet.set( 0 );
|
||||
}
|
||||
|
||||
mSampleCounter++;
|
||||
}
|
||||
boolean previousSoftBit = mBitSet.get(mSymbolLength - 1);
|
||||
|
||||
reset();
|
||||
|
||||
if(previousSoftBit)
|
||||
{
|
||||
mBitSet.set(0);
|
||||
}
|
||||
|
||||
mSampleCounter++;
|
||||
}
|
||||
/* No shift */
|
||||
else
|
||||
{
|
||||
sendTapEvent( mBitSet, Shift.NONE, decision );
|
||||
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the bit decision to the listener
|
||||
*/
|
||||
private void send( boolean decision )
|
||||
{
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( mNormalOutput ? decision : !decision );
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
mBitSet.clear();
|
||||
mSampleCounter = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
sendTapEvent(mBitSet, Shift.NONE, decision);
|
||||
|
||||
/**
|
||||
* Sends instrumentation tap event to all registered listeners
|
||||
*/
|
||||
private void sendTapEvent( BitSet bitset, Shift shift, boolean decision )
|
||||
{
|
||||
for( SymbolEventTap tap: mTaps )
|
||||
{
|
||||
SymbolEvent event =
|
||||
new SymbolEvent( bitset.get( 0, mSymbolLength ),
|
||||
mSymbolLength,
|
||||
decision,
|
||||
shift );
|
||||
|
||||
tap.receive( event );
|
||||
}
|
||||
}
|
||||
}
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instrumentation taps
|
||||
*/
|
||||
@Override
|
||||
/**
|
||||
* Sends the bit decision to the listener
|
||||
*/
|
||||
private void send(boolean decision)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(mNormalOutput ? decision : !decision);
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
mBitSet.clear();
|
||||
mSampleCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends instrumentation tap event to all registered listeners
|
||||
*/
|
||||
private void sendTapEvent(BitSet bitset, Shift shift, boolean decision)
|
||||
{
|
||||
for(SymbolEventTap tap : mTaps)
|
||||
{
|
||||
SymbolEvent event =
|
||||
new SymbolEvent(bitset.get(0, mSymbolLength),
|
||||
mSymbolLength,
|
||||
decision,
|
||||
shift);
|
||||
|
||||
tap.receive(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instrumentation taps
|
||||
*/
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<TapGroup>();
|
||||
|
||||
TapGroup tapGroup = new TapGroup( "FSK2 Decoder" );
|
||||
tapGroup.add( new SymbolEventTap( INSTRUMENT_DECISION, 0, .025f ) );
|
||||
|
||||
mAvailableTaps.add( tapGroup );
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
if(mAvailableTaps == null)
|
||||
{
|
||||
mAvailableTaps = new ArrayList<TapGroup>();
|
||||
|
||||
TapGroup tapGroup = new TapGroup("FSK2 Decoder");
|
||||
tapGroup.add(new SymbolEventTap(INSTRUMENT_DECISION, 0, .025f));
|
||||
|
||||
mAvailableTaps.add(tapGroup);
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void registerTap( Tap tap )
|
||||
/**
|
||||
* Add instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void registerTap(Tap tap)
|
||||
{
|
||||
if( tap instanceof SymbolEventTap && !mTaps.contains( tap ) )
|
||||
{
|
||||
mTaps.add( (SymbolEventTap)tap );
|
||||
}
|
||||
if(tap instanceof SymbolEventTap && !mTaps.contains(tap))
|
||||
{
|
||||
mTaps.add((SymbolEventTap) tap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void unregisterTap( Tap tap )
|
||||
/**
|
||||
* Remove instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void unregisterTap(Tap tap)
|
||||
{
|
||||
mTaps.remove( tap );
|
||||
mTaps.remove(tap);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user