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

@@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit;
public class ThreadPoolManager
{
private int THREAD_POOL_SIZE = 10;
private int THREAD_POOL_SIZE = 2;
private int MAX_TASK_COUNT = 24;
private HashMap<ScheduledFuture<?>,ThreadType> mTasks =

View File

@@ -682,26 +682,33 @@ public class ProcessingChain implements Listener<Message>
@Override
public void run()
{
List<RealBuffer> sampleBuffers = new ArrayList<RealBuffer>();
mRealQueue.drainTo( sampleBuffers, 4 );
for( RealBuffer sampleBuffer: sampleBuffers )
try
{
if( mDecoder != null )
List<RealBuffer> sampleBuffers = new ArrayList<RealBuffer>();
mRealQueue.drainTo( sampleBuffers, 4 );
for( RealBuffer sampleBuffer: sampleBuffers )
{
float[] samples = sampleBuffer.getSamples();
for( float sample: samples )
if( mDecoder != null )
{
mDecoder.getRealReceiver().receive( sample );
float[] samples = sampleBuffer.getSamples();
for( float sample: samples )
{
mDecoder.getRealReceiver().receive( sample );
}
}
sampleBuffer.dispose();
}
sampleBuffer.dispose();
sampleBuffers.clear();
}
catch( Exception e )
{
mLog.error( "error during processing chain real processor run", e );
}
sampleBuffers.clear();
}
}
@@ -713,25 +720,32 @@ public class ProcessingChain implements Listener<Message>
@Override
public void run()
{
List<List<ComplexSample>> sampleSets =
new ArrayList<List<ComplexSample>>();
mComplexQueue.drainTo( sampleSets, 16 );
for( List<ComplexSample> samples: sampleSets )
try
{
for( ComplexSample sample: samples )
List<List<ComplexSample>> sampleSets =
new ArrayList<List<ComplexSample>>();
mComplexQueue.drainTo( sampleSets, 16 );
for( List<ComplexSample> samples: sampleSets )
{
if( sample != null && mDecoder != null )
for( ComplexSample sample: samples )
{
mDecoder.getComplexReceiver().receive( sample );
if( sample != null && mDecoder != null )
{
mDecoder.getComplexReceiver().receive( sample );
}
}
samples.clear();
}
samples.clear();
sampleSets.clear();
}
catch( Exception e )
{
mLog.error( "error during processing chain complex processor run", e );
}
sampleSets.clear();
}
}

View File

@@ -124,7 +124,10 @@ public class DecodeComponentEditor extends AbstractChannelEditor
@Override
public void save()
{
mEditor.save();
if( mEditor != null )
{
mEditor.save();
}
//The component that calls save will invoke the change broadcast
mChannelNode.getChannel()

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()
{

View File

@@ -1,5 +1,13 @@
package dsp.fsk;
import instrument.Instrumentable;
import instrument.tap.Tap;
import instrument.tap.stream.AdditiveFloatTap;
import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -7,7 +15,8 @@ import sample.real.RealSampleListener;
import sample.real.RealSampleProvider;
public class C4FMSymbolFilter implements RealSampleListener,
RealSampleProvider
RealSampleProvider,
Instrumentable
{
private final static Logger mLog =
LoggerFactory.getLogger( C4FMSymbolFilter.class );
@@ -145,6 +154,11 @@ public class C4FMSymbolFilter implements RealSampleListener,
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 128/128
};
/* Instrumentation Taps */
private static final String INSTRUMENT_SYMBOL_SPREAD = "Tap Point: Symbol Spread (Goal=2.0)";
private FloatTap mSymbolSpreadTap;
private List<Tap> mAvailableTaps;
private static final int NUMBER_FILTER_TAPS = 8;
private static final int NUMBER_FILTER_STEPS = 128;
@@ -181,6 +195,8 @@ public class C4FMSymbolFilter implements RealSampleListener,
private RealSampleListener mListener;
private float mGain = 15.4f;
/**
* C4FM Symbol Filter
*
@@ -190,10 +206,15 @@ public class C4FMSymbolFilter implements RealSampleListener,
* is less than 2.0, then increase gain. If the value is over 2.0, then
* decrease gain.
*
* During testing, the incoming samples were within the range of -0.5 <> 0.5
* and applying a gain of 11.0 to those samples achieved the best symbol
* spread, centered about 2.0 with +/- 0.5% deviations on a strong signal.
* When preceded by the RealAutomaticGainFilter class, an optimal gain
* setting for this filter is 15.4 and that yields a symbol spread that
* is centered on 2.0, ranging 1.92 to 2.08.
*/
public C4FMSymbolFilter( float gain )
{
mGain = gain;
}
public C4FMSymbolFilter()
{
}
@@ -203,8 +224,7 @@ public class C4FMSymbolFilter implements RealSampleListener,
{
mSymbolClock += mSymbolTime;
// mHistory[ mHistoryLast++ ] = sample;
mHistory[ mHistoryLast++ ] = sample * 10.5f;
mHistory[ mHistoryLast++ ] = sample * mGain;
mHistoryLast %= NUMBER_FILTER_TAPS;
if( mSymbolClock > 1.0f )
@@ -298,6 +318,11 @@ public class C4FMSymbolFilter implements RealSampleListener,
( mCoarseFrequencyCorrection > 0 ? "-" : "+" ) +
mCoarseFrequencyCorrection + "]" );
}
if( mSymbolSpreadTap != null )
{
mSymbolSpreadTap.receive( mSymbolSpread );
}
/* dispatch the interpolated value to the listener */
if( mListener != null )
@@ -318,4 +343,40 @@ public class C4FMSymbolFilter implements RealSampleListener,
{
mListener = null;
}
@Override
public List<Tap> getTaps()
{
if( mAvailableTaps == null )
{
mAvailableTaps = new ArrayList<Tap>();
/*
* Since the target is 2.0, we subtract 2.0 from the value so that
* a good value is plotted right on the zero center line
*/
mAvailableTaps.add(
new AdditiveFloatTap( INSTRUMENT_SYMBOL_SPREAD, 0, 0.1f, -2.0f ) );
}
return mAvailableTaps;
}
@Override
public void addTap( Tap tap )
{
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
{
mSymbolSpreadTap = (FloatTap)tap;
}
}
@Override
public void removeTap( Tap tap )
{
if( tap.getName().contentEquals( INSTRUMENT_SYMBOL_SPREAD ) )
{
mSymbolSpreadTap = null;
}
}
}

View File

@@ -33,6 +33,7 @@ public class P25MessageFramer implements Listener<Dibit>
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;
@@ -378,23 +379,33 @@ public class P25MessageFramer implements Listener<Dibit>
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
BitSetBuffer tsbkBuffer1 = mMessage.copy();
tsbkBuffer1.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage1 = TSBKMessageFactory.getMessage(
tsbkBuffer1, DataUnitID.TSBK1, mAliasList );
CRC tsbkCRC = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( tsbkMessage1.isLastBlock() )
if( tsbkCRC != CRC.FAILED_CRC )
{
mComplete = true;
BitSetBuffer 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
{
setDUID( DataUnitID.TSBK2 );
mMessage.setPointer( TSBK_BEGIN );
mComplete = true;
}
dispatch( tsbkMessage1 );
}
else
{
@@ -409,23 +420,33 @@ public class P25MessageFramer implements Listener<Dibit>
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
BitSetBuffer tsbkBuffer2 = mMessage.copy();
tsbkBuffer2.setSize( TSBK_DECODED_END );
CRC tsbkCRC = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
TSBKMessage tsbkMessage2 = TSBKMessageFactory.getMessage(
tsbkBuffer2, DataUnitID.TSBK2, mAliasList );
if( tsbkMessage2.isLastBlock() )
if( tsbkCRC != CRC.FAILED_CRC )
{
mComplete = true;
BitSetBuffer 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
{
setDUID( DataUnitID.TSBK3 );
mMessage.setPointer( TSBK_BEGIN );
mComplete = true;
}
dispatch( tsbkMessage2 );
}
else
{
@@ -440,13 +461,19 @@ public class P25MessageFramer implements Listener<Dibit>
* unsuccessful decode due to excessive errors */
if( mHalfRate.decode( mMessage, TSBK_BEGIN, TSBK_END ) )
{
BitSetBuffer tsbkBuffer3 = mMessage.copy();
tsbkBuffer3.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage(
tsbkBuffer3, DataUnitID.TSBK3, mAliasList );
CRC tsbkCRC = CRCP25.correctCCITT80( mMessage,
TSBK_BEGIN, TSBK_CRC_START );
if( tsbkCRC != CRC.FAILED_CRC )
{
BitSetBuffer tsbkBuffer3 = mMessage.copy();
tsbkBuffer3.setSize( TSBK_DECODED_END );
TSBKMessage tsbkMessage3 = TSBKMessageFactory.getMessage(
tsbkBuffer3, DataUnitID.TSBK3, mAliasList );
dispatch( tsbkMessage3 );
dispatch( tsbkMessage3 );
}
}
mComplete = true;

View File

@@ -18,12 +18,14 @@
package gui;
import java.awt.AWTException;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -34,6 +36,7 @@ import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import net.miginfocom.swing.MigLayout;
@@ -188,6 +191,30 @@ public class SDRTrunk
JMenu fileMenu = new JMenu( "File" );
menuBar.add( fileMenu );
JMenuItem logFilesMenu = new JMenuItem( "Logs & Recordings" );
logFilesMenu.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
Desktop.getDesktop().open( getHomePath().toFile() );
}
catch( Exception e )
{
mLog.error( "Couldn't open file explorer" );
JOptionPane.showMessageDialog( mMainGui,
"Can't launch file explorer - files are located at: " +
getHomePath().toString(),
"Can't launch file explorer",
JOptionPane.ERROR_MESSAGE );
}
}
} );
fileMenu.add( logFilesMenu );
JMenuItem settingsMenu = new JMenuItem( "Icon Manager" );
settingsMenu.addActionListener( new ActionListener()

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* 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 instrument.tap.stream;
public class AdditiveFloatTap extends FloatTap
{
private float mValue;
public AdditiveFloatTap( String name,
int delay,
float sampleRateRatio,
float addValue )
{
super( name, delay, sampleRateRatio );
mValue = addValue;
}
@Override
public void receive( float sample )
{
super.receive( sample + mValue );
}
}

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* 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 instrument.tap.stream;
public class GainFloatTap extends FloatTap
{
private float mGain;
public GainFloatTap( String name,
int delay,
float sampleRateRatio,
float gain )
{
super( name, delay, sampleRateRatio );
mGain = gain;
}
@Override
public void receive( float sample )
{
super.receive( sample * mGain );
}
}

View File

@@ -24,6 +24,9 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexSample;
@@ -34,6 +37,7 @@ import source.tuner.FrequencyChangeEvent.Attribute;
import util.Oscillator;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
import decode.p25.P25Decoder;
import dsp.filter.ComplexFilter;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
@@ -42,6 +46,9 @@ public class TunerChannelSource extends ComplexSource
implements FrequencyChangeListener,
Listener<ComplexBuffer>
{
private final static Logger mLog =
LoggerFactory.getLogger( TunerChannelSource.class );
private static int sCHANNEL_RATE = 48000;
private static int sCHANNEL_PASS_FREQUENCY = 12500;
@@ -87,9 +94,9 @@ public class TunerChannelSource extends ComplexSource
frequencyChanged( new FrequencyChangeEvent(
Attribute.SAMPLE_RATE, mTuner.getSampleRate() ) );
/* Schedule the decimation task to run 20 times a second */
/* Schedule the decimation task to run 50 times a second */
mTaskHandle = mThreadPoolManager.schedule( ThreadType.DECIMATION,
new DecimationProcessor(), 50, TimeUnit.MILLISECONDS );
new DecimationProcessor(), 20, TimeUnit.MILLISECONDS );
/* Finally, register to receive samples from the tuner */
mTuner.addListener( (Listener<ComplexBuffer>)this );
@@ -243,41 +250,51 @@ public class TunerChannelSource extends ComplexSource
@Override
public void run()
{
List<ComplexBuffer> sampleBuffers =
new ArrayList<ComplexBuffer>();
if( mBuffer != null )
/* General exception handler so that any errors won't kill the
* decimation thread and cause the input buffers to fill up and
* run the program out of memory */
try
{
/* Limit to 10, so that after a garbage collect we don't
* induce wild swings as it tries to take tons of samples
* and process them, causing delays and buffer refill */
mBuffer.drainTo( sampleBuffers, 10 );
for( ComplexBuffer sampleArray: sampleBuffers )
List<ComplexBuffer> sampleBuffers =
new ArrayList<ComplexBuffer>();
if( mBuffer != null )
{
float[] samples = sampleArray.getSamples();
for( int x = 0; x < samples.length; x += 2 )
/* Limit to 4, so that after a garbage collect we don't
* induce wild swings as it tries to take tons of samples
* and process them, causing delays and buffer refill */
mBuffer.drainTo( sampleBuffers, 4 );
for( ComplexBuffer sampleArray: sampleBuffers )
{
Float left = samples[ x ];
Float right = samples[ x + 1 ];
float[] samples = sampleArray.getSamples();
if( left != null && right != null )
for( int x = 0; x < samples.length; x += 2 )
{
/* perform frequency translation */
ComplexSample translated = ComplexSample.multiply(
mSineWaveGenerator.nextComplex(), left, right );
/* decimate the sample rate */
if( mDecimationFilters[ 0 ] != null )
{
mDecimationFilters[ 0 ].receive( translated );
}
Float left = samples[ x ];
Float right = samples[ x + 1 ];
if( left != null && right != null )
{
/* perform frequency translation */
ComplexSample translated = ComplexSample.multiply(
mSineWaveGenerator.nextComplex(), left, right );
/* decimate the sample rate */
if( mDecimationFilters[ 0 ] != null )
{
mDecimationFilters[ 0 ].receive( translated );
}
}
}
}
sampleBuffers.clear();
}
sampleBuffers.clear();
}
catch( Exception e )
{
mLog.error( "encountered an error during decimation process", e );
}
}
}

View File

@@ -1115,15 +1115,22 @@ public class HackRFTunerController extends TunerController
@Override
public void run()
{
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
mFilledBuffers.drainTo( buffers );
for( byte[] buffer: buffers )
try
{
float[] samples = mSampleAdapter.convert( buffer );
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
broadcast( new ComplexBuffer( samples ) );
mFilledBuffers.drainTo( buffers );
for( byte[] buffer: buffers )
{
float[] samples = mSampleAdapter.convert( buffer );
broadcast( new ComplexBuffer( samples ) );
}
}
catch( Exception e )
{
mLog.error( "error duruing HackRF buffer dispatching", e );
}
}
}

View File

@@ -1611,15 +1611,22 @@ public abstract class RTL2832TunerController extends TunerController
@Override
public void run()
{
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
mFilledBuffers.drainTo( buffers );
for( byte[] buffer: buffers )
try
{
float[] samples = mSampleAdapter.convert( buffer );
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
broadcast( new ComplexBuffer( samples ) );
mFilledBuffers.drainTo( buffers );
for( byte[] buffer: buffers )
{
float[] samples = mSampleAdapter.convert( buffer );
broadcast( new ComplexBuffer( samples ) );
}
}
catch( Exception e )
{
mLog.error( "error duing rtl2832 buffer dispatcher run", e );
}
}
}

View File

@@ -400,14 +400,21 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
@Override
public void run()
{
/* Only run if we're not currently running */
if( mRunning.compareAndSet( false, true ) )
try
{
checkFFTSize();
/* Only run if we're not currently running */
if( mRunning.compareAndSet( false, true ) )
{
checkFFTSize();
calculate();
mRunning.set( false );
calculate();
mRunning.set( false );
}
}
catch( Exception e )
{
mLog.error( "error during dft processor calculation task", e );
}
}
}