mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
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:
@@ -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 =
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
40
src/instrument/tap/stream/AdditiveFloatTap.java
Normal file
40
src/instrument/tap/stream/AdditiveFloatTap.java
Normal 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 );
|
||||
}
|
||||
}
|
||||
40
src/instrument/tap/stream/GainFloatTap.java
Normal file
40
src/instrument/tap/stream/GainFloatTap.java
Normal 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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user