Initial commit of redesigned internal processing and audio management.

Note: Several of the decoders are currently non-functioning in this
commit.  If you want the latest functioning codebase, please checkout
the commit prior to this one.  All decoders will eventually be updated
with this new architecture.

Currently broken:
- Logging and recording
- Aux decoders (Fleetsync/MDC/LoJack/Tait)
- LTR/Passport decoders

Changes in this commit:
- Module architecture for decoders and components with central
provisioning by the DecoderFactory
- Transition from Complex sample stream processing to float buffer
processing for improved memory management
- Redesigned filter structures - to support buffers and streams
- Resolved several memory leak issues
- Universal sharing of the thread pool manager
- Added centralized audio management with call/user prioritization and
new gui with mute/volume controls
- Embedded audio metadata for eventual streaming support
- Redesigned ProcessingChain to support independent re-use
- Added new TrafficChannelManager to handle traffic channel allocations
- Traffic Channel pool size and Call timeout available in P25 and
MPT1327 configs
- Redesigned channel and decoder gui components
This commit is contained in:
denny
2015-09-01 07:10:26 -04:00
parent 538a299246
commit f4d8ea281d
570 changed files with 21578 additions and 17012 deletions

View File

@@ -0,0 +1,692 @@
package module.decode.p25;
import java.util.ArrayList;
import message.Message;
import module.decode.p25.message.P25Message;
import module.decode.p25.message.hdu.HDUMessage;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.ldu.LDU2Message;
import module.decode.p25.message.ldu.lc.LDULCMessageFactory;
import module.decode.p25.message.pdu.PDUMessage;
import module.decode.p25.message.pdu.PDUMessageFactory;
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessageFactory;
import module.decode.p25.message.tdu.TDUMessage;
import module.decode.p25.message.tdu.lc.TDULCMessageFactory;
import module.decode.p25.message.tdu.lc.TDULinkControlMessage;
import module.decode.p25.message.tsbk.TSBKMessage;
import module.decode.p25.message.tsbk.TSBKMessageFactory;
import module.decode.p25.reference.DataUnitID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.AliasList;
import bits.BinaryMessage;
import bits.BitSetFullException;
import bits.ISyncDetectListener;
import bits.MultiSyncPatternMatcher;
import bits.SoftSyncDetector;
import bits.SyncDetector;
import dsp.psk.CQPSKDemodulator;
import dsp.psk.CQPSKDemodulator2;
import dsp.symbol.Dibit;
import dsp.symbol.FrameSync;
import edac.BCH_63_16_11;
import edac.CRC;
import edac.CRCP25;
public class P25MessageFramer implements Listener<Dibit>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageFramer.class );
/* Determines the threshold for sync pattern soft matching */
private static final int SYNC_MATCH_THRESHOLD = 2;
private static final int SYNC_IN_CALL_THRESHOLD = 4;
private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD );
/* Costas Loop phase lock error correction values. A phase lock error of
* 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An
* error of 180 degrees requires a correction of 1/2 of the symbol rate */
public static final double SYMBOL_FREQUENCY = 2.0d * Math.PI * 4800.0d / 48000.0d;
public static final double PHASE_CORRECTION_90_DEGREES = SYMBOL_FREQUENCY / 4.0d;
public static final double PHASE_CORRECTION_180_DEGREES = SYMBOL_FREQUENCY / 2.0d;
private MultiSyncPatternMatcher mMatcher = new MultiSyncPatternMatcher( 48 );
private ArrayList<P25MessageAssembler> mAssemblers =
new ArrayList<P25MessageAssembler>();
public static final int TSBK_BEGIN = 64;
public static final int TSBK_CRC_START = 144;
public static final int TSBK_END = 260;
public static final int TSBK_DECODED_END = 160;
public static final int PDU0_BEGIN = 64;
public static final int PDU0_CRC_BEGIN = 144;
public static final int PDU0_END = 260;
public static final int PDU1_BEGIN = 160;
public static final int PDU1_END = 356;
public static final int PDU2_BEGIN = 256;
public static final int PDU2_END = 452;
public static final int PDU3_BEGIN = 352;
public static final int PDU3_END = 548;
public static final int PDU3_DECODED_END = 448;
private Listener<Message> mListener;
private AliasList mAliasList;
private Trellis_1_2_Rate mHalfRate = new Trellis_1_2_Rate();
private Trellis_3_4_Rate mThreeQuarterRate = new Trellis_3_4_Rate();
private BCH_63_16_11 mNIDDecoder = new BCH_63_16_11();
/**
* Constructs a P25 message framer to receive a stream of symbols and
* detect the sync pattern then capture the following stream of symbols up
* to the message length, and then broadcast that bit buffer to the registered
* listener.
*/
public P25MessageFramer( AliasList aliasList )
{
mAliasList = aliasList;
mPrimarySyncDetector.setListener( new ISyncDetectListener()
{
@Override
public void syncDetected()
{
for( P25MessageAssembler assembler: mAssemblers )
{
if( !assembler.isActive() )
{
assembler.setActive( true );
break;
}
}
}
} );
mMatcher.add( mPrimarySyncDetector );
/**
* We use two message assemblers to catch any sync detections, so that
* if we have a false trigger then we still have a chance to catch the
* valid message. If we have two false triggers before the arrival of
* the actual message sync, then the third or subsequent real or false
* sync pattern will produce a debug message indicating no assemblers
* are available..
*/
mAssemblers.add( new P25MessageAssembler() );
mAssemblers.add( new P25MessageAssembler() );
}
public P25MessageFramer( AliasList aliasList, CQPSKDemodulator demodulator )
{
this( aliasList );
if( demodulator != null )
{
/* For CQPSK, we include 3 additional sync detectors to watch for and
* correction +/-90 and 180 degree costas loop phase lock errors */
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CCW,
demodulator, PHASE_CORRECTION_90_DEGREES ) );
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_90_CW,
demodulator, -PHASE_CORRECTION_90_DEGREES ) );
mMatcher.add( new CostasPhaseErrorDetector( FrameSync.P25_PHASE1_ERROR_180,
demodulator, PHASE_CORRECTION_180_DEGREES ) );
}
}
private void dispatch( Message message )
{
if( mListener != null )
{
mListener.receive( message );
}
}
@Override
public void receive( Dibit symbol )
{
for( P25MessageAssembler assembler: mAssemblers )
{
if( assembler.isActive() )
{
assembler.receive( symbol );
if( assembler.complete() )
{
assembler.reset();
}
}
}
mMatcher.receive( symbol.getBit1(), symbol.getBit2() );
}
public void setListener( Listener<Message> listener )
{
mListener = listener;
}
public void removeListener( Listener<Message> listener )
{
mListener = null;
}
private class P25MessageAssembler
{
/* Starting position of the status symbol counter is 24 symbols to
* account for the 48-bit sync pattern which is not included in message */
private int mStatusSymbolPointer = 24;
private BinaryMessage mMessage;
private int mMessageLength;
private boolean mComplete = false;
private boolean mActive = false;
private DataUnitID mDUID = DataUnitID.NID;
public P25MessageAssembler()
{
mMessageLength = mDUID.getMessageLength();
mMessage = new BinaryMessage( mMessageLength );
reset();
}
public void receive( Dibit dibit )
{
if( mActive )
{
if( mStatusSymbolPointer == 35 )
{
mStatusSymbolPointer = 0;
}
else
{
mStatusSymbolPointer++;
try
{
mMessage.add( dibit.getBit1() );
mMessage.add( dibit.getBit2() );
}
catch( BitSetFullException e )
{
mComplete = true;
}
/* Check the message for complete */
if( mMessage.isFull() )
{
checkComplete();
}
}
}
}
public void reset()
{
mDUID = DataUnitID.NID;
mMessage.setSize( mDUID.getMessageLength() );
mMessage.clear();
mStatusSymbolPointer = 24;
mComplete = false;
mActive = false;
}
public void setActive( boolean active )
{
mActive = active;
}
private void setDUID( DataUnitID id )
{
mDUID = id;
mMessageLength = id.getMessageLength();
mMessage.setSize( mMessageLength );
}
private void checkComplete()
{
switch( mDUID )
{
case NID:
mMessage = mNIDDecoder.correctNID( mMessage );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
int value = mMessage.getInt( P25Message.DUID );
DataUnitID duid = DataUnitID.fromValue( value );
if( duid != DataUnitID.UNKN )
{
setDUID( duid );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
break;
case HDU:
mComplete = true;
dispatch( new HDUMessage( mMessage.copy(), mDUID, mAliasList ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case LDU1:
mComplete = true;
LDU1Message ldu1 = new LDU1Message( mMessage.copy(),
mDUID, mAliasList );
/* Convert the LDU1 message into a link control LDU1 message */
dispatch( LDULCMessageFactory.getMessage( ldu1 ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case LDU2:
mComplete = true;
dispatch( new LDU2Message( mMessage.copy(), mDUID, mAliasList ) );
/* We're in a call now, lower the sync match threshold */
mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD );
break;
case PDU0:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU0_BEGIN, PDU0_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU0_BEGIN, PDU0_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
PDU0_BEGIN, PDU0_CRC_BEGIN );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
boolean confirmed = mMessage.get(
PDUMessage.CONFIRMATION_REQUIRED_INDICATOR );
if( confirmed )
{
setDUID( DataUnitID.PDUC );
}
else
{
setDUID( DataUnitID.PDU1 );
}
mMessage.setPointer( PDU1_BEGIN );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case PDU1:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU1_BEGIN, PDU1_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU1_BEGIN, PDU1_END ) )
{
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 1 )
{
mMessage.setSize( PDU2_BEGIN );
PDUMessage pduMessage1 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU1, mAliasList );
dispatch( pduMessage1 );
mComplete = true;
}
else
{
setDUID( DataUnitID.PDU2 );
mMessage.setPointer( PDU2_BEGIN );
}
}
else
{
mComplete = true;
}
break;
case PDU2:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU2_BEGIN, PDU2_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU2_BEGIN, PDU2_END ) )
{
if( mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW ) == 2 )
{
mMessage.setSize( PDU3_BEGIN );
PDUMessage pduMessage2 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU2, mAliasList );
dispatch( pduMessage2 );
mComplete = true;
}
else
{
setDUID( DataUnitID.PDU3 );
mMessage.setPointer( PDU3_BEGIN );
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case PDU3:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, PDU3_BEGIN, PDU3_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, PDU3_BEGIN, PDU3_END ) )
{
mMessage.setSize( PDU3_DECODED_END );
PDUMessage pduMessage3 = PDUMessageFactory.getMessage(
mMessage.copy(), DataUnitID.PDU3, mAliasList );
dispatch( pduMessage3 );
}
mComplete = true;
break;
case PDUC:
/* De-interleave the latest block*/
P25Interleave.deinterleaveData( mMessage,
mMessage.size() - 196, mMessage.size() );
/* Decode 3/4 rate convolutional encoding from latest block */
if( mThreeQuarterRate.decode( mMessage,
mMessage.size() - 196, mMessage.size() ) )
{
/* Resize the message and adjust the message pointer
* to account for removing 48 + 4 parity bits */
mMessage.setSize( mMessage.size() - 52 );
mMessage.adjustPointer( -52 );
int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW );
int current = ( mMessage.size() - 160 ) / 144;
if( current < blocks )
{
mMessage.setSize( mMessage.size() + 196 );
mMessageLength = mMessage.size();
}
else
{
PDUConfirmedMessage pducm =
new PDUConfirmedMessage( mMessage.copy(),
mAliasList );
/* Translate into correct subclass */
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
mComplete = true;
}
break;
}
else
{
PDUConfirmedMessage pducm =
new PDUConfirmedMessage( mMessage.copy(),
mAliasList );
/* Translate into correct subclass */
dispatch( PDUConfirmedMessageFactory.getMessage( pducm ) );
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TDU:
dispatch( new TDUMessage( mMessage.copy(), mDUID, mAliasList ) );
mComplete = true;
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TDULC:
TDULinkControlMessage tdulc = new TDULinkControlMessage(
mMessage.copy(), mDUID, mAliasList );
/* Convert to an appropriate link control message */
tdulc = TDULCMessageFactory.getMessage( tdulc );
dispatch( tdulc );
mComplete = true;
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TSBK1:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer1 = mMessage.copy();
tsbkBuffer1.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage1 = TSBKMessageFactory.getMessage(
tsbkBuffer1, DataUnitID.TSBK1, mAliasList );
if( tsbkMessage1.isLastBlock() )
{
mComplete = true;
}
else
{
setDUID( DataUnitID.TSBK2 );
mMessage.setPointer( TSBK_BEGIN );
}
dispatch( tsbkMessage1 );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
/* Set sync match threshold to normal */
mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD );
break;
case TSBK2:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer2 = mMessage.copy();
tsbkBuffer2.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage2 = TSBKMessageFactory.getMessage(
tsbkBuffer2, DataUnitID.TSBK2, mAliasList );
if( tsbkMessage2.isLastBlock() )
{
mComplete = true;
}
else
{
setDUID( DataUnitID.TSBK3 );
mMessage.setPointer( TSBK_BEGIN );
}
dispatch( tsbkMessage2 );
}
else
{
mComplete = true;
}
}
else
{
mComplete = true;
}
break;
case TSBK3:
/* Remove interleaving */
P25Interleave.deinterleaveData( mMessage, TSBK_BEGIN, TSBK_END );
/* Remove trellis encoding - abort processing if we have an
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
mMessage = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( mMessage.getCRC() != CRC.FAILED_CRC )
{
BinaryMessage tsbkBuffer3 = mMessage.copy();
tsbkBuffer3.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage(
tsbkBuffer3, DataUnitID.TSBK3, mAliasList );
dispatch( tsbkMessage3 );
}
}
mComplete = true;
break;
case UNKN:
mComplete = true;
dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) );
break;
default:
mComplete = true;
break;
}
}
public void dispose()
{
mMessage = null;
mHalfRate.dispose();
}
/**
* Flag to indicate when this assembler has received all of the bits it
* is looking for (ie message length), and should then be removed from
* receiving any more bits
*/
public boolean complete()
{
return mComplete;
}
public boolean isActive()
{
return mActive;
}
}
/**
* Sync pattern detector to listen for costas loop phase lock errors and
* apply a phase correction to the costas loop so that we don't miss any
* messages.
*
* When the costas loop locks with a +/- 90 degree or 180 degree phase
* error, the slicer will incorrectly apply the symbol pattern rotated left
* or right by the phase error. However, we can detect these rotated sync
* patterns and apply immediate phase correction so that message processing
* can continue.
*/
public class CostasPhaseErrorDetector extends SyncDetector
{
private CQPSKDemodulator mDemodulator;
private double mCorrection;
public CostasPhaseErrorDetector( FrameSync frameSync,
CQPSKDemodulator demodulator, double correction )
{
super( frameSync.getSync() );
mDemodulator = demodulator;
mCorrection = correction;
setListener( new ISyncDetectListener()
{
@Override
public void syncDetected()
{
mDemodulator.correctPhaseError( mCorrection );
/* Since we detected a sync pattern, start a message assembler */
for( P25MessageAssembler assembler: mAssemblers )
{
if( !assembler.isActive() )
{
assembler.setActive( true );
break;
}
}
}
} );
}
}
}