Out of memory fixes and updates to the P25 decoder.

Memory Fixes
Decreased the decimation process repeat timing so that it runs smaller work chunks more often
Decreased scheduled thread pool to 2 hot threads
Wrapped all scheduled thread pool runnables in try/catch blocks so that an exception doesn't kill the sample buffer processing thread and cause the buffer to grow unbounded.

P25 updates
Moved TSBK crc checks into the message framer so that message corrections don't occur after the message is constructed.
Added more functionality to the channel state for handling registrations and call events.
This commit is contained in:
dennis.sheirer
2014-12-19 23:59:38 +00:00
parent 63eaef664b
commit ba053457bd
18 changed files with 758 additions and 214 deletions

View File

@@ -17,6 +17,10 @@
******************************************************************************/
package decode.p25;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import message.Message;
import alias.Alias;
import alias.AliasList;
@@ -35,11 +39,19 @@ import decode.p25.message.tsbk.osp.control.UnitDeregistrationAcknowledge;
import decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrant;
import decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrantUpdate;
import decode.p25.message.tsbk.osp.voice.GroupVoiceChannelGrantUpdateExplicit;
import decode.p25.message.tsbk.osp.voice.TelephoneInterconnectVoiceChannelGrant;
import decode.p25.message.tsbk.osp.voice.TelephoneInterconnectVoiceChannelGrantUpdate;
import decode.p25.message.tsbk.osp.voice.UnitToUnitAnswerRequest;
import decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrant;
import decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrantUpdate;
import decode.p25.reference.Opcode;
import decode.p25.reference.Vendor;
public class P25ChannelState extends ChannelState
{
private HashMap<String,Long> mRegistrations = new HashMap<String,Long>();
private String mLastDeRegistration;
private P25ActivitySummary mActivitySummary;
private String mNAC;
private String mSystem;
@@ -154,99 +166,261 @@ public class P25ChannelState extends ChannelState
switch( tsbk.getOpcode() )
{
case GROUP_VOICE_CHANNEL_GRANT:
GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant)tsbk;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( gvcg.getChannelIdentifier() + "-" + gvcg.getChannel() )
.details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) +
( gvcg.isEmergency() ? " EMERGENCY" : "") )
.frequency( gvcg.getDownlinkFrequency() )
.from( gvcg.getSourceAddress() )
.to( gvcg.getGroupAddress() )
.build() );
break;
case GROUP_VOICE_CHANNEL_GRANT_UPDATE:
GroupVoiceChannelGrantUpdate gvcgu =
(GroupVoiceChannelGrantUpdate)tsbk;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( gvcgu.getChannelID1() + "-" + gvcgu.getChannelNumber1() )
.details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) +
( gvcgu.isEmergency() ? " EMERGENCY" : "") )
.frequency( gvcgu.getDownlinkFrequency() )
.to( gvcgu.getGroupAddress1() )
.build() );
if( gvcgu.getChannelNumber2() != 0 )
{
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( gvcgu.getChannelID2() + "-" + gvcgu.getChannelNumber2() )
.details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) +
( gvcgu.isEmergency() ? " EMERGENCY" : "") )
.frequency( gvcgu.getDownlinkFrequency() )
.to( gvcgu.getGroupAddress2() )
.build() );
}
break;
case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT:
GroupVoiceChannelGrantUpdateExplicit gvcgue =
(GroupVoiceChannelGrantUpdateExplicit)tsbk;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( gvcgue.getTransmitChannelID() + "-" +
gvcgue.getTransmitChannelNumber() )
.details( ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) +
( gvcgue.isEmergency() ? " EMERGENCY" : "") )
.frequency( gvcgue.getDownlinkFrequency() )
.to( gvcgue.getGroupAddress() )
.build() );
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT:
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE:
case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT:
case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE:
processCall( tsbk );
break;
case TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
case UNIT_TO_UNIT_ANSWER_REQUEST:
UnitToUnitAnswerRequest utuar =
(UnitToUnitAnswerRequest)tsbk;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.PAGE )
.aliasList( mAliasList )
.details( ( utuar.isEmergency() ? "EMERGENCY" : "" ) )
.from( utuar.getSourceAddress() )
.to( utuar.getTargetAddress() )
.build() );
processPage( tsbk );
break;
case LOCATION_REGISTRATION_RESPONSE:
LocationRegistrationResponse lrr =
(LocationRegistrationResponse)tsbk;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.REGISTER )
.aliasList( mAliasList )
.details( "REG:" + lrr.getResponse().name() +
" SITE: " + lrr.getRFSSID() + "-" +
lrr.getSiteID() )
.from( lrr.getTargetAddress() )
.to( lrr.getGroupAddress() )
.build() );
break;
case UNIT_DEREGISTRATION_ACKNOWLEDGE:
UnitDeregistrationAcknowledge udr =
(UnitDeregistrationAcknowledge)tsbk;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.DEREGISTER )
.aliasList( mAliasList )
.from( udr.getSourceID() )
.build() );
processRegistration( tsbk );
default:
break;
}
}
}
}
/**
* Process a call event message
*/
private void processCall( TSBKMessage message )
{
switch( message.getOpcode() )
{
case GROUP_VOICE_CHANNEL_GRANT:
GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant)message;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( gvcg.getChannelIdentifier() + "-" + gvcg.getChannel() )
.details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) +
( gvcg.isEmergency() ? " EMERGENCY" : "") )
.frequency( gvcg.getDownlinkFrequency() )
.from( gvcg.getSourceAddress() )
.to( gvcg.getGroupAddress() )
.build() );
break;
case GROUP_VOICE_CHANNEL_GRANT_UPDATE:
GroupVoiceChannelGrantUpdate gvcgu =
(GroupVoiceChannelGrantUpdate)message;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( gvcgu.getChannelID1() + "-" + gvcgu.getChannelNumber1() )
.details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) +
( gvcgu.isEmergency() ? " EMERGENCY" : "") )
.frequency( gvcgu.getDownlinkFrequency1() )
.to( gvcgu.getGroupAddress1() )
.build() );
if( gvcgu.hasChannelNumber2() )
{
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( gvcgu.getChannelID2() + "-" + gvcgu.getChannelNumber2() )
.details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) +
( gvcgu.isEmergency() ? " EMERGENCY" : "") )
.frequency( gvcgu.getDownlinkFrequency2() )
.to( gvcgu.getGroupAddress2() )
.build() );
}
break;
case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT:
GroupVoiceChannelGrantUpdateExplicit gvcgue =
(GroupVoiceChannelGrantUpdateExplicit)message;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( gvcgue.getTransmitChannelID() + "-" +
gvcgue.getTransmitChannelNumber() )
.details( ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) +
( gvcgue.isEmergency() ? " EMERGENCY" : "") )
.frequency( gvcgue.getDownlinkFrequency() )
.to( gvcgue.getGroupAddress() )
.build() );
break;
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT:
TelephoneInterconnectVoiceChannelGrant tivcg =
(TelephoneInterconnectVoiceChannelGrant)message;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( tivcg.getChannelIdentifier() + "-" +
tivcg.getChannel() )
.details( ( tivcg.isEncrypted() ? "ENCRYPTED" : "" ) +
( tivcg.isEmergency() ? " EMERGENCY" : "") )
.frequency( tivcg.getDownlinkFrequency() )
.from( tivcg.getSourceAddress() )
.to( tivcg.getTargetAddress() )
.build() );
break;
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE:
TelephoneInterconnectVoiceChannelGrantUpdate tivcgu =
(TelephoneInterconnectVoiceChannelGrantUpdate)message;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( tivcgu.getChannelIdentifier() + "-" +
tivcgu.getChannel() )
.details( ( tivcgu.isEncrypted() ? "ENCRYPTED" : "" ) +
( tivcgu.isEmergency() ? " EMERGENCY" : "") )
.frequency( tivcgu.getDownlinkFrequency() )
.from( tivcgu.getSourceAddress() )
.to( tivcgu.getTargetAddress() )
.build() );
break;
case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT:
UnitToUnitVoiceChannelGrant uuvcg =
(UnitToUnitVoiceChannelGrant)message;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( uuvcg.getChannelIdentifier() + "-" +
uuvcg.getChannel() )
.details( ( uuvcg.isEncrypted() ? "ENCRYPTED" : "" ) +
( uuvcg.isEmergency() ? " EMERGENCY" : "") )
.frequency( uuvcg.getDownlinkFrequency() )
.from( uuvcg.getSourceAddress() )
.to( uuvcg.getTargetAddress() )
.build() );
break;
case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE:
UnitToUnitVoiceChannelGrantUpdate uuvcgu =
(UnitToUnitVoiceChannelGrantUpdate)message;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.CALL )
.aliasList( mAliasList )
.channel( uuvcgu.getChannelIdentifier() + "-" +
uuvcgu.getChannel() )
.details( ( uuvcgu.isEncrypted() ? "ENCRYPTED" : "" ) +
( uuvcgu.isEmergency() ? " EMERGENCY" : "") )
.frequency( uuvcgu.getDownlinkFrequency() )
.from( uuvcgu.getSourceAddress() )
.to( uuvcgu.getTargetAddress() )
.build() );
break;
default:
break;
}
}
/**
* Process a unit paging event message
*/
private void processPage( TSBKMessage message )
{
if( message.getOpcode() == Opcode.UNIT_TO_UNIT_ANSWER_REQUEST )
{
UnitToUnitAnswerRequest utuar = (UnitToUnitAnswerRequest)message;
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.PAGE )
.aliasList( mAliasList )
.details( ( utuar.isEmergency() ? "EMERGENCY" : "" ) )
.from( utuar.getSourceAddress() )
.to( utuar.getTargetAddress() )
.build() );
}
}
/**
* Process a unit registration or deregistration message
*/
private void processRegistration( TSBKMessage message )
{
switch( message.getOpcode() )
{
case LOCATION_REGISTRATION_RESPONSE:
LocationRegistrationResponse lrr =
(LocationRegistrationResponse)message;
String addressPlusGroup = lrr.getTargetAddress() +
lrr.getGroupAddress();
boolean updateEvent = false;
if( mRegistrations.containsKey( addressPlusGroup ) )
{
long timestamp = mRegistrations.get( addressPlusGroup );
updateEvent = ( System.currentTimeMillis() -
timestamp ) > 300000; //5 minutes
}
else
{
updateEvent = true;
}
mRegistrations.put( addressPlusGroup, System.currentTimeMillis() );
if( updateEvent )
{
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.REGISTER )
.aliasList( mAliasList )
.details( "RESPONSE:" + lrr.getResponse().name() +
" SITE: " + lrr.getRFSSID() + "-" +
lrr.getSiteID() )
.from( lrr.getTargetAddress() )
.to( lrr.getGroupAddress() )
.build() );
}
/* Reset the deregistration variable */
mLastDeRegistration = null;
break;
case UNIT_DEREGISTRATION_ACKNOWLEDGE:
UnitDeregistrationAcknowledge udr =
(UnitDeregistrationAcknowledge)message;
if( mLastDeRegistration != null &&
mLastDeRegistration.contentEquals( udr.getSourceID() ) )
{
break; //Suppress duplicate event
}
mLastDeRegistration = udr.getSourceID();
mCallEventModel.add(
new P25CallEvent.Builder( CallEventType.DEREGISTER )
.aliasList( mAliasList )
.from( udr.getSourceID() )
.build() );
List<String> keysToRemove = new ArrayList<String>();
/* Remove this radio from the registrations set */
for( String key: mRegistrations.keySet() )
{
if( key.startsWith( udr.getSourceID() ) )
{
keysToRemove.add( key );
}
}
for( String key: keysToRemove )
{
mRegistrations.remove( key );
}
break;
default:
break;
}
}
@Override
public void setSquelchState( SquelchState state )

View File

@@ -19,6 +19,7 @@ package decode.p25;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
@@ -31,6 +32,7 @@ import source.Source.SampleType;
import alias.AliasList;
import decode.Decoder;
import decode.DecoderType;
import dsp.agc.RealAutomaticGainControl;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
@@ -43,10 +45,17 @@ public class P25Decoder extends Decoder implements Instrumentable
{
private final static Logger mLog = LoggerFactory.getLogger( P25Decoder.class );
/* Instrumentation Taps */
private static final String INSTRUMENT_INPUT_TO_AGC = "Tap Point: Input to AGC";
private static final String INSTRUMENT_AGC_TO_SYMBOL_FILTER = "Tap Point: AGC to Symbol Filter";
private static final String INSTRUMENT_SYMBOL_FILTER_TO_SLICER = "Tap Point: Symbol Filter to Slicer";
private List<Tap> mAvailableTaps;
private ComplexFIRFilter mBasebandFilter = new ComplexFIRFilter(
FilterFactory.getLowPass( 48000, 5000, 31, WindowType.HAMMING ), 1.0 );
private FMDiscriminator mDemodulator = new FMDiscriminator( 1 );
private RealAutomaticGainControl mAGC = new RealAutomaticGainControl();
private C4FMSymbolFilter mSymbolFilter = new C4FMSymbolFilter();
private C4FMSlicer mSlicer = new C4FMSlicer();
private P25MessageFramer mNormalFramer;
@@ -80,7 +89,8 @@ public class P25Decoder extends Decoder implements Instrumentable
mDemodulator.setListener( getRealReceiver() );
}
addRealSampleListener( mSymbolFilter );
addRealSampleListener( mAGC );
mAGC.setListener( mSymbolFilter );
mSymbolFilter.setListener( mSlicer );
@@ -108,17 +118,65 @@ public class P25Decoder extends Decoder implements Instrumentable
@Override
public List<Tap> getTaps()
{
return new ArrayList<Tap>();
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
mAvailableTaps.add( new FloatTap( INSTRUMENT_INPUT_TO_AGC, 0, 1.0f ) );
mAvailableTaps.add( new FloatTap( INSTRUMENT_AGC_TO_SYMBOL_FILTER, 0, 1.0f ) );
mAvailableTaps.add( new FloatTap( INSTRUMENT_SYMBOL_FILTER_TO_SLICER, 0, 0.1f ) );
mAvailableTaps.addAll( mSymbolFilter.getTaps() );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
mSymbolFilter.addTap( tap );
switch( tap.getName() )
{
case INSTRUMENT_INPUT_TO_AGC:
FloatTap inputAGC = (FloatTap)tap;
removeRealListener( mAGC );
addRealSampleListener( inputAGC );
inputAGC.setListener( mAGC );
break;
case INSTRUMENT_AGC_TO_SYMBOL_FILTER:
FloatTap agcSymbol = (FloatTap)tap;
mAGC.setListener( agcSymbol );
agcSymbol.setListener( mSymbolFilter );
break;
case INSTRUMENT_SYMBOL_FILTER_TO_SLICER:
FloatTap symbolSlicer = (FloatTap)tap;
mSymbolFilter.setListener( symbolSlicer );
symbolSlicer.setListener( mSlicer );
break;
}
}
@Override
public void removeTap( Tap tap )
{
mSymbolFilter.removeTap( tap );
switch( tap.getName() )
{
case INSTRUMENT_INPUT_TO_AGC:
FloatTap inputAGC = (FloatTap)tap;
removeRealListener( inputAGC );
addRealSampleListener( mAGC );
break;
case INSTRUMENT_AGC_TO_SYMBOL_FILTER:
mAGC.setListener( mSymbolFilter );
break;
case INSTRUMENT_SYMBOL_FILTER_TO_SLICER:
mSymbolFilter.setListener( mSlicer );
break;
}
}
@Override

View File

@@ -1,11 +1,13 @@
package decode.p25.message.tsbk;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
import decode.p25.message.tsbk.osp.control.IdentifierUpdate;
import decode.p25.message.tsbk.osp.control.IdentifierUpdateReceiver;
import decode.p25.reference.DataUnitID;
public abstract class GroupMultiChannelGrant extends ChannelGrant
implements IdentifierUpdateReceiver
{
public static final int[] CHANNEL_ID_1 = { 80,81,82,83 };
public static final int[] CHANNEL_NUMBER_1 = { 84,85,86,87,
@@ -17,6 +19,9 @@ public abstract class GroupMultiChannelGrant extends ChannelGrant
112,113,114,115,116,117,118,119 };
public static final int[] GROUP_ADDRESS_2 = { 120,121,122,123,124,125,126,
127,128,129,130,131,132,133,134,135 };
private IdentifierUpdate mIdentifierUpdate1;
private IdentifierUpdate mIdentifierUpdate2;
public GroupMultiChannelGrant( BitSetBuffer message,
DataUnitID duid,
@@ -37,11 +42,14 @@ public abstract class GroupMultiChannelGrant extends ChannelGrant
sb.append( " GRP1:" );
sb.append( getGroupAddress1() );
sb.append( " CHAN2:" );
sb.append( getChannelID2() + "/" + getChannelNumber2() );
sb.append( " GRP2:" );
sb.append( getGroupAddress2() );
if( hasChannelNumber2() )
{
sb.append( " CHAN2:" );
sb.append( getChannelID2() + "/" + getChannelNumber2() );
sb.append( " GRP2:" );
sb.append( getGroupAddress2() );
}
return sb.toString();
}
@@ -76,6 +84,11 @@ public abstract class GroupMultiChannelGrant extends ChannelGrant
return mMessage.getHex( GROUP_ADDRESS_2, 4 );
}
public boolean hasChannelNumber2()
{
return mMessage.getInt( GROUP_ADDRESS_2 ) > 0;
}
@Override
public String getFromID()
{
@@ -87,4 +100,59 @@ public abstract class GroupMultiChannelGrant extends ChannelGrant
{
return getGroupAddress2();
}
@Override
public void setIdentifierMessage( int identifier, IdentifierUpdate message )
{
if( identifier == getChannelID1() )
{
mIdentifierUpdate1 = message;
}
if( hasChannelNumber2() && identifier == getChannelID2() )
{
mIdentifierUpdate2 = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers;
if( hasChannelNumber2() )
{
identifiers = new int[ 2 ];
identifiers[ 0 ] = getChannelID1();
identifiers[ 1 ] = getChannelID2();
}
else
{
identifiers = new int[ 1 ];
identifiers[ 0 ] = getChannelID1();
}
return identifiers;
}
public long getDownlinkFrequency1()
{
return calculateDownlink( mIdentifierUpdate1, getChannel() );
}
public long getUplinkFrequency1()
{
return calculateUplink( mIdentifierUpdate1, getChannel() );
}
public long getDownlinkFrequency2()
{
return calculateDownlink( mIdentifierUpdate2, getChannel() );
}
public long getUplinkFrequency2()
{
return calculateUplink( mIdentifierUpdate2, getChannel() );
}
}

View File

@@ -36,15 +36,10 @@ public class TSBKMessage extends P25Message
public TSBKMessage( BitSetBuffer message, DataUnitID duid, AliasList aliasList )
{
super( message, duid, aliasList );
checkCRC();
}
protected void checkCRC()
{
mCRC = new CRC[ 1 ];
mCRC[ 0 ] = CRCP25.correctCCITT80( mMessage, MESSAGE_START, CRC_START );
/* Since CRC is checked before construction, mark it as passed */
mCRC = new CRC[ 1 ];
mCRC[ 0 ] = CRC.PASSED;
}
public boolean isLastBlock()

View File

@@ -18,6 +18,6 @@ public class GroupVoiceChannelGrantUpdate extends GroupMultiChannelGrant
@Override
public String getEventType()
{
return Opcode.GROUP_VOICE_CHANNEL_GRANT.getDescription();
return Opcode.GROUP_VOICE_CHANNEL_GRANT_UPDATE.getDescription();
}
}

View File

@@ -3,7 +3,6 @@ package decode.p25.message.tsbk.osp.voice;
import java.util.ArrayList;
import java.util.List;
import alias.Alias;
import alias.AliasList;
import bits.BitSetBuffer;
import decode.p25.message.tsbk.TSBKMessage;
@@ -34,6 +33,12 @@ public class TelephoneInterconnectAnswerRequest extends TSBKMessage
super( message, duid, aliasList );
}
@Override
public String getEventType()
{
return Opcode.TELEPHONE_INTERCONNECT_ANSWER_REQUEST.getDescription();
}
public String getMessage()
{
StringBuilder sb = new StringBuilder();
@@ -72,12 +77,6 @@ public class TelephoneInterconnectAnswerRequest extends TSBKMessage
return mMessage.getHex( TARGET_ADDRESS, 6 );
}
@Override
public String getEventType()
{
return Opcode.TELEPHONE_INTERCONNECT_ANSWER_REQUEST.getDescription();
}
@Override
public String getToID()
{