diff --git a/.classpath b/.classpath
index 4039eba0..050a1c6c 100644
--- a/.classpath
+++ b/.classpath
@@ -1,28 +1,29 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/audio/AudioOutput.java b/src/audio/AudioOutputImpl.java
similarity index 93%
rename from src/audio/AudioOutput.java
rename to src/audio/AudioOutputImpl.java
index da0dacfd..ae833592 100644
--- a/src/audio/AudioOutput.java
+++ b/src/audio/AudioOutputImpl.java
@@ -30,12 +30,11 @@ import org.slf4j.LoggerFactory;
import sample.real.RealSampleListener;
import buffer.FloatSampleBufferAssembler;
-public class AudioOutput implements RealSampleListener,
- SquelchListener,
- AudioTypeListener
+public class AudioOutputImpl implements RealSampleListener,
+ SquelchListener, IAudioTypeListener, IAudioOutput
{
private final static Logger mLog =
- LoggerFactory.getLogger( AudioOutput.class );
+ LoggerFactory.getLogger( AudioOutputImpl.class );
private static final int sAUDIO_BLOCK_SIZE = 24000;
private static final AudioFormat sAUDIO_FORMAT =
@@ -54,7 +53,7 @@ public class AudioOutput implements RealSampleListener,
private boolean mEnabled = false;
private SquelchState mSquelchState = SquelchState.SQUELCH;
- public AudioOutput( String channelName )
+ public AudioOutputImpl( String channelName )
{
try
{
@@ -75,11 +74,13 @@ public class AudioOutput implements RealSampleListener,
mSquelchState = state;
}
+ @Override
public void setAudioPlaybackEnabled( boolean enabled )
{
mEnabled = enabled;
}
+ @Override
public void dispose()
{
mOutput.stop();
diff --git a/src/audio/IAudioOutput.java b/src/audio/IAudioOutput.java
new file mode 100644
index 00000000..f9add19c
--- /dev/null
+++ b/src/audio/IAudioOutput.java
@@ -0,0 +1,28 @@
+package audio;
+
+import audio.SquelchListener.SquelchState;
+
+public interface IAudioOutput
+{
+ /**
+ * Squelch / Unsquelch state for this channel
+ */
+ public abstract void setSquelch( SquelchState state );
+
+ /**
+ * Audio playback defines if this channel is currently playing on the
+ * system speakers.
+ */
+ public abstract void setAudioPlaybackEnabled( boolean enabled );
+
+ /**
+ * Dispose of any system resources for this audio output
+ */
+ public abstract void dispose();
+
+ /**
+ * Sets the audio to normal or inverted output
+ */
+ public abstract void setAudioType( AudioType type );
+
+}
\ No newline at end of file
diff --git a/src/audio/AudioTypeListener.java b/src/audio/IAudioTypeListener.java
similarity index 96%
rename from src/audio/AudioTypeListener.java
rename to src/audio/IAudioTypeListener.java
index 48ddbeae..92c794db 100644
--- a/src/audio/AudioTypeListener.java
+++ b/src/audio/IAudioTypeListener.java
@@ -17,7 +17,7 @@
******************************************************************************/
package audio;
-public interface AudioTypeListener
+public interface IAudioTypeListener
{
public void setAudioType( AudioType type );
}
diff --git a/src/bits/BinaryMessage.java b/src/bits/BinaryMessage.java
index 585cf6c7..3a2e0744 100644
--- a/src/bits/BinaryMessage.java
+++ b/src/bits/BinaryMessage.java
@@ -17,7 +17,6 @@
******************************************************************************/
package bits;
-import java.util.Arrays;
import java.util.BitSet;
import org.slf4j.Logger;
@@ -110,6 +109,21 @@ public class BinaryMessage extends BitSet
this.mPointer = size - 1;
}
+ /**
+ * Returns a mew binary message containing the bits from (inclusive) to
+ * end (exclusive).
+ *
+ * @param start
+ * @param end
+ * @return
+ */
+ public BinaryMessage getSubMessage( int start, int end )
+ {
+ BitSet subset = this.get( start, end );
+
+ return new BinaryMessage( subset, end - start );
+ }
+
public CRC getCRC()
{
return mCRC;
@@ -386,8 +400,6 @@ public class BinaryMessage extends BitSet
}
}
- mLog.debug( "Value is:" + value );
-
return (byte)( value & 0xFF );
}
@@ -613,6 +625,10 @@ public class BinaryMessage extends BitSet
{
set( offset + x );
}
+ else
+ {
+ clear( offset + x );
+ }
}
}
diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java
index 3cc95f81..1ae34152 100644
--- a/src/controller/channel/Channel.java
+++ b/src/controller/channel/Channel.java
@@ -215,7 +215,9 @@ public class Channel extends Configuration
{
mSelected = selected;
- if( mProcessingChain != null && mProcessingChain.isProcessing() )
+ if( mProcessingChain != null &&
+ mProcessingChain.isProcessing() &&
+ mProcessingChain.getAudioOutput() != null )
{
mProcessingChain.getAudioOutput().setAudioPlaybackEnabled( selected );
}
diff --git a/src/controller/channel/ProcessingChain.java b/src/controller/channel/ProcessingChain.java
index ee577053..b7d1ef58 100644
--- a/src/controller/channel/ProcessingChain.java
+++ b/src/controller/channel/ProcessingChain.java
@@ -51,8 +51,9 @@ import source.tuner.FrequencyChangeListener;
import source.tuner.Tuner;
import source.tuner.TunerChannelSource;
import alias.AliasList;
-import audio.AudioOutput;
-import audio.AudioTypeListener;
+import audio.AudioOutputImpl;
+import audio.IAudioOutput;
+import audio.IAudioTypeListener;
import audio.SquelchListener;
import controller.ResourceManager;
import controller.ThreadPoolManager.ThreadType;
@@ -80,7 +81,6 @@ public class ProcessingChain implements Listener
protected Decoder mDecoder;
protected List mRecorders = new ArrayList();
protected ArrayList mEventLoggers = new ArrayList();
- protected AudioOutput mAudioOutput;
protected AutomaticFrequencyControl mAFC;
protected ChannelState mChannelState;
protected ResourceManager mResourceManager;
@@ -109,6 +109,16 @@ public class ProcessingChain implements Listener
mChannel.getAliasListName() );
}
}
+
+ public IAudioOutput getAudioOutput()
+ {
+ if( mDecoder != null )
+ {
+ return mDecoder.getAudioOutput();
+ }
+
+ return null;
+ }
/**
* Current frequency correction setting
@@ -178,12 +188,6 @@ public class ProcessingChain implements Listener
mEventLoggers.clear();
mRecorders.clear();
-
- if( mAudioOutput != null )
- {
- mAudioOutput.dispose();
- }
-
mResourceManager = null;
}
@@ -234,11 +238,6 @@ public class ProcessingChain implements Listener
updateEventLogging();
- if( mAudioOutput != null )
- {
- mAudioOutput.setAudioPlaybackEnabled( false );
- }
-
mRunning.set( false );
}
else
@@ -374,14 +373,6 @@ public class ProcessingChain implements Listener
/* Register to receive decoded messages and auxiliary messages */
mDecoder.addMessageListener( ProcessingChain.this );
- /* Setup the audio output processing thread */
- String threadName = "SDRTrunk Audio - " +
- mChannel.getSite().getName() +
- "-" + mChannel.getName();
-
- mAudioOutput = new AudioOutput( threadName );
- addRealListener( mAudioOutput );
-
/* Automatic frequency control */
if( mAFC != null )
{
@@ -415,14 +406,12 @@ public class ProcessingChain implements Listener
.setListener( (FrequencyChangeListener)tcs );
}
}
+
+ /* Register audio output to be controlled by the channel state */
+ mChannelState.addListener( (SquelchListener)mDecoder.getAudioOutput() );
+ mChannelState.setListener( (IAudioTypeListener)mDecoder.getAudioOutput() );
}
- }
-
- if( mAudioOutput != null )
- {
- /* Register audio output to be controlled by the channel state */
- mChannelState.addListener( (SquelchListener)mAudioOutput );
- mChannelState.setListener( (AudioTypeListener)mAudioOutput );
+
}
}
}
@@ -548,11 +537,6 @@ public class ProcessingChain implements Listener
return mChannel;
}
- public AudioOutput getAudioOutput()
- {
- return mAudioOutput;
- }
-
public Source getSource()
{
return mSource;
diff --git a/src/controller/state/ChannelState.java b/src/controller/state/ChannelState.java
index 54327f51..12b7b90a 100644
--- a/src/controller/state/ChannelState.java
+++ b/src/controller/state/ChannelState.java
@@ -36,7 +36,7 @@ import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import audio.AudioType;
-import audio.AudioTypeListener;
+import audio.IAudioTypeListener;
import audio.SquelchListener;
import audio.SquelchListener.SquelchState;
import controller.activity.CallEvent;
@@ -103,7 +103,7 @@ public abstract class ChannelState implements Listener
new ArrayList();
protected Broadcaster mChangeBroadcaster =
new Broadcaster();
- protected AudioTypeListener mAudioTypeListener;
+ protected IAudioTypeListener mAudioTypeListener;
protected AudioType mAudioType = AudioType.NORMAL;
protected CallEventModel mCallEventModel = new CallEventModel();
@@ -397,7 +397,7 @@ public abstract class ChannelState implements Listener
setState( State.FADE );
}
- public void setListener( AudioTypeListener listener )
+ public void setListener( IAudioTypeListener listener )
{
mAudioTypeListener = listener;
}
diff --git a/src/decode/Decoder.java b/src/decode/Decoder.java
index 89737600..62455e18 100644
--- a/src/decode/Decoder.java
+++ b/src/decode/Decoder.java
@@ -27,6 +27,7 @@ import sample.complex.ComplexSample;
import sample.real.RealSampleBroadcaster;
import sample.real.RealSampleListener;
import source.Source.SampleType;
+import audio.IAudioOutput;
import eventlog.MessageEventLogger;
public abstract class Decoder implements Listener
@@ -65,6 +66,8 @@ public abstract class Decoder implements Listener
return mRealBroadcaster;
}
+ public abstract IAudioOutput getAudioOutput();
+
/**
* Returns a complex listener interface for connecting this decoder to a
* float stream provider
diff --git a/src/decode/DecoderFactory.java b/src/decode/DecoderFactory.java
index cb9b75b4..644cbe44 100644
--- a/src/decode/DecoderFactory.java
+++ b/src/decode/DecoderFactory.java
@@ -102,8 +102,8 @@ public class DecoderFactory
case P25_PHASE1:
DecodeConfigP25Phase1 p25Config = (DecodeConfigP25Phase1)config;
- retVal = new P25Decoder( sampleType, p25Config.getModulation(),
- aliasList );
+ retVal = new P25Decoder( chain.getResourceManager(),
+ sampleType, p25Config.getModulation(), aliasList );
break;
default:
throw new IllegalArgumentException(
diff --git a/src/decode/am/AMDecoder.java b/src/decode/am/AMDecoder.java
index f329d9ca..1ac93f89 100644
--- a/src/decode/am/AMDecoder.java
+++ b/src/decode/am/AMDecoder.java
@@ -1,5 +1,7 @@
package decode.am;
+import audio.AudioOutputImpl;
+import audio.IAudioOutput;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import decode.Decoder;
@@ -31,6 +33,8 @@ public class AMDecoder extends Decoder
private FloatFIRFilter mLowPassFilter;
+ private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "AM Decoder Audio Output" );
+
public AMDecoder( SampleType sampleType )
{
super( sampleType );
@@ -74,9 +78,18 @@ public class AMDecoder extends Decoder
mLowPassFilter.setListener( mAudioAGC );
mAudioAGC.setListener( this.getRealReceiver() );
+
+ addRealSampleListener( mAudioOutput );
}
}
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
@Override
public DecoderType getType()
{
@@ -89,4 +102,5 @@ public class AMDecoder extends Decoder
throw new IllegalArgumentException( "cannot add real sample "
+ "listener to AM demodulator - not implemented" );
}
+
}
diff --git a/src/decode/fleetsync2/Fleetsync2Decoder.java b/src/decode/fleetsync2/Fleetsync2Decoder.java
index f586958e..98369eb5 100644
--- a/src/decode/fleetsync2/Fleetsync2Decoder.java
+++ b/src/decode/fleetsync2/Fleetsync2Decoder.java
@@ -29,6 +29,7 @@ import sample.Listener;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
+import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
@@ -188,4 +189,11 @@ public class Fleetsync2Decoder extends Decoder implements Instrumentable
{
//Not implemented
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ //Not implemented
+ return null;
+ }
}
diff --git a/src/decode/lj1200/LJ1200Decoder.java b/src/decode/lj1200/LJ1200Decoder.java
index dbecfe4a..cf4367b8 100644
--- a/src/decode/lj1200/LJ1200Decoder.java
+++ b/src/decode/lj1200/LJ1200Decoder.java
@@ -29,6 +29,7 @@ import sample.Broadcaster;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
+import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
@@ -196,4 +197,11 @@ public class LJ1200Decoder extends Decoder implements Instrumentable
{
//Not implemented
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ //Not implemented
+ return null;
+ }
}
diff --git a/src/decode/ltrnet/LTRNetDecoder.java b/src/decode/ltrnet/LTRNetDecoder.java
index 850ac4db..be8da2c5 100644
--- a/src/decode/ltrnet/LTRNetDecoder.java
+++ b/src/decode/ltrnet/LTRNetDecoder.java
@@ -27,6 +27,8 @@ import message.MessageDirection;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
+import audio.AudioOutputImpl;
+import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
@@ -49,6 +51,7 @@ public class LTRNetDecoder extends Decoder implements Instrumentable
private LTRFSKDecoder mLTRFSKDecoder;
private MessageFramer mLTRMessageFramer;
private LTRNetMessageProcessor mLTRMessageProcessor;
+ private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "LTRNet Decoder Audio Output" );
private List mAvailableTaps;
@@ -83,6 +86,8 @@ public class LTRNetDecoder extends Decoder implements Instrumentable
mDCRemovalFilter.setListener( this.getRealReceiver() );
}
+
+ addRealSampleListener( mAudioOutput );
mLTRFSKDecoder = new LTRFSKDecoder();
@@ -159,4 +164,10 @@ public class LTRNetDecoder extends Decoder implements Instrumentable
}
// TODO Auto-generated method stub
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ return mAudioOutput;
+ }
}
diff --git a/src/decode/ltrstandard/LTRStandardDecoder.java b/src/decode/ltrstandard/LTRStandardDecoder.java
index 9594f547..71b50786 100644
--- a/src/decode/ltrstandard/LTRStandardDecoder.java
+++ b/src/decode/ltrstandard/LTRStandardDecoder.java
@@ -21,6 +21,8 @@ import message.MessageDirection;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
+import audio.AudioOutputImpl;
+import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
@@ -43,6 +45,7 @@ public class LTRStandardDecoder extends Decoder
private LTRFSKDecoder mLTRFSKDecoder;
private MessageFramer mLTRMessageFramer;
private LTRStandardMessageProcessor mLTRMessageProcessor;
+ private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "LTR Standard Decoder Audio Output" );
public LTRStandardDecoder( SampleType sampleType,
AliasList aliasList,
@@ -75,6 +78,8 @@ public class LTRStandardDecoder extends Decoder
mDCRemovalFilter.setListener( this.getRealReceiver() );
}
+
+ addRealSampleListener( mAudioOutput );
mLTRFSKDecoder = new LTRFSKDecoder();
@@ -125,4 +130,10 @@ public class LTRStandardDecoder extends Decoder
mDemodulator.addListener( listener );
}
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ return mAudioOutput;
+ }
}
diff --git a/src/decode/mdc1200/MDCDecoder.java b/src/decode/mdc1200/MDCDecoder.java
index f6a48916..79b4d09c 100644
--- a/src/decode/mdc1200/MDCDecoder.java
+++ b/src/decode/mdc1200/MDCDecoder.java
@@ -28,6 +28,7 @@ import java.util.List;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
+import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
@@ -206,4 +207,11 @@ public class MDCDecoder extends Decoder implements Instrumentable
{
//Not implemented
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ // Not implemented
+ return null;
+ }
}
diff --git a/src/decode/mpt1327/MPT1327Decoder.java b/src/decode/mpt1327/MPT1327Decoder.java
index ba487a56..c3ebc6ad 100644
--- a/src/decode/mpt1327/MPT1327Decoder.java
+++ b/src/decode/mpt1327/MPT1327Decoder.java
@@ -29,6 +29,8 @@ import sample.Broadcaster;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
+import audio.AudioOutputImpl;
+import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
@@ -82,6 +84,7 @@ public class MPT1327Decoder extends Decoder implements Instrumentable
private MessageFramer mControlMessageFramer;
private MessageFramer mTrafficMessageFramer;
private MPT1327MessageProcessor mMessageProcessor;
+ private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "MPT1327 Decoder Audio Output" );
public MPT1327Decoder( SampleType sampleType,
AliasList aliasList,
@@ -191,6 +194,8 @@ public class MPT1327Decoder extends Decoder implements Instrumentable
super.dispose();
mSymbolBroadcaster.dispose();
+
+ mAudioOutput.dispose();
mControlMessageFramer.dispose();
@@ -212,7 +217,6 @@ public class MPT1327Decoder extends Decoder implements Instrumentable
mMessageProcessor.dispose();
-
mTrafficMessageFramer.dispose();
}
@@ -349,4 +353,10 @@ public class MPT1327Decoder extends Decoder implements Instrumentable
return getLabel();
}
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ return mAudioOutput;
+ }
}
diff --git a/src/decode/nbfm/NBFMDecoder.java b/src/decode/nbfm/NBFMDecoder.java
index 364612b2..aa003a3a 100644
--- a/src/decode/nbfm/NBFMDecoder.java
+++ b/src/decode/nbfm/NBFMDecoder.java
@@ -17,6 +17,8 @@
******************************************************************************/
package decode.nbfm;
+import audio.AudioOutputImpl;
+import audio.IAudioOutput;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import decode.Decoder;
@@ -34,6 +36,7 @@ public class NBFMDecoder extends Decoder
private FilteringNBFMDemodulator mDemodulator;
private DCRemovalFilter2 mDCRemovalFilter;
+ private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "NBFM Decoder Audio Output" );
public NBFMDecoder( SampleType sampleType )
{
@@ -68,6 +71,8 @@ public class NBFMDecoder extends Decoder
*/
mDCRemovalFilter.setListener( this.getRealReceiver() );
}
+
+ addRealSampleListener( mAudioOutput );
}
@Override
@@ -84,4 +89,10 @@ public class NBFMDecoder extends Decoder
mDemodulator.addListener( listener );
}
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ return mAudioOutput;
+ }
}
diff --git a/src/decode/p25/P25ChannelState.java b/src/decode/p25/P25ChannelState.java
index 3e5887f0..7c7a86c8 100644
--- a/src/decode/p25/P25ChannelState.java
+++ b/src/decode/p25/P25ChannelState.java
@@ -41,6 +41,7 @@ import controller.activity.CallEvent.CallEventType;
import controller.channel.ProcessingChain;
import controller.state.AuxChannelState;
import controller.state.ChannelState;
+import decode.p25.audio.P25AudioOutput;
import decode.p25.message.P25Message;
import decode.p25.message.hdu.HDUMessage;
import decode.p25.message.ldu.LDU1Message;
@@ -148,6 +149,8 @@ public class P25ChannelState extends ChannelState
private String mCurrentChannel = "CURRENT";
private long mCurrentChannelFrequency = 0;
+ private P25AudioOutput mAudioOutput;
+
public P25ChannelState( ProcessingChain chain, AliasList aliasList )
{
super( chain, aliasList );
@@ -163,6 +166,11 @@ public class P25ChannelState extends ChannelState
}
}
+ public void setAudioOutput( P25AudioOutput audioOutput )
+ {
+ mAudioOutput = audioOutput;
+ }
+
public void addListener( SquelchListener listener )
{
super.addListener( listener );
@@ -801,6 +809,12 @@ public class P25ChannelState extends ChannelState
private void processLDU( LDUMessage ldu )
{
+ /* Send audio frames to the audio output */
+ if( mAudioOutput != null )
+ {
+ mAudioOutput.receive( ldu );
+ }
+
if( ldu instanceof LDU1Message )
{
switch( ((LDU1Message)ldu).getOpcode() )
diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java
index 94be8982..52b55ed7 100644
--- a/src/decode/p25/P25Decoder.java
+++ b/src/decode/p25/P25Decoder.java
@@ -35,8 +35,10 @@ import source.Source.SampleType;
import source.tuner.DirectFrequencyController;
import source.tuner.FrequencyChangeListener;
import alias.AliasList;
+import controller.ResourceManager;
import decode.Decoder;
import decode.DecoderType;
+import decode.p25.audio.P25AudioOutput;
import dsp.filter.ComplexFIRFilter;
import dsp.filter.FilterFactory;
import dsp.filter.FloatFIRFilter;
@@ -88,11 +90,15 @@ public class P25Decoder extends Decoder
private P25MessageFramer mNormalFramer;
private P25MessageFramer mInvertedFramer;
private P25MessageProcessor mMessageProcessor;
+
+ /* Audio */
+ private P25AudioOutput mAudioOutput;
private AliasList mAliasList;
private Modulation mModulation;
- public P25Decoder( SampleType sampleType,
+ public P25Decoder( ResourceManager resourceManager,
+ SampleType sampleType,
Modulation modulation,
AliasList aliasList )
{
@@ -168,6 +174,14 @@ public class P25Decoder extends Decoder
mC4FMSlicer.addListener( mNormalFramer );
mC4FMSlicer.addListener( mInvertedFramer );
}
+
+ mAudioOutput = new P25AudioOutput( resourceManager );
+ mMessageProcessor.addMessageListener( mAudioOutput );
+ }
+
+ public P25AudioOutput getAudioOutput()
+ {
+ return mAudioOutput;
}
@Override
diff --git a/src/decode/p25/P25MessageProcessor.java b/src/decode/p25/P25MessageProcessor.java
index 91c6427d..9128a747 100644
--- a/src/decode/p25/P25MessageProcessor.java
+++ b/src/decode/p25/P25MessageProcessor.java
@@ -12,6 +12,7 @@ import sample.Listener;
import alias.AliasList;
import decode.p25.message.IBandIdentifier;
import decode.p25.message.IdentifierReceiver;
+import decode.p25.message.ldu.LDUMessage;
public class P25MessageProcessor implements Listener
{
@@ -36,6 +37,14 @@ public class P25MessageProcessor implements Listener
@Override
public void receive( Message message )
{
+ /* Debug - prints out the imbe message frames */
+ if( message instanceof LDUMessage )
+ {
+ ((LDUMessage)message).getIMBEFrames();
+ }
+
+
+
if( message.isValid() )
{
/* Insert band identifier update messages into channel-type messages */
diff --git a/src/decode/p25/audio/IMBEAudioFormat.java b/src/decode/p25/audio/IMBEAudioFormat.java
new file mode 100644
index 00000000..ac0eee1e
--- /dev/null
+++ b/src/decode/p25/audio/IMBEAudioFormat.java
@@ -0,0 +1,52 @@
+package decode.p25.audio;
+
+import javax.sound.sampled.AudioFormat;
+
+public class IMBEAudioFormat
+{
+ public static final AudioFormat.Encoding IMBE_ENCODING =
+ new AudioFormat.Encoding( "IMBE" );
+
+ public static final boolean LITTLE_ENDIAN = false;
+ public static final boolean BIG_ENDIAN = true;
+
+ public static final float IMBE_FRAME_RATE = 50;
+ public static final float IMBE_SAMPLE_RATE = 50;
+ public static final float PCM_8KHZ_SAMPLE_RATE = 8000;
+ public static final float PCM_48KHZ_SAMPLE_RATE = 48000;
+ public static final float PCM_FRAME_RATE = 8000;
+
+ public static final int IMBE_FRAME_SIZE_BYTES = 18;
+ public static final int IMBE_SAMPLE_SIZE_BITS = 144;
+ public static final int ONE_CHANNEL = 1;
+ public static final int PCM_SAMPLE_SIZE_BITS = 16;
+ public static final int PCM_FRAME_SIZE_BYTES = 2;
+
+ public static AudioFormat IMBE_AUDIO_FORMAT =
+ new AudioFormat( IMBE_ENCODING,
+ IMBE_SAMPLE_RATE,
+ IMBE_SAMPLE_SIZE_BITS,
+ ONE_CHANNEL,
+ IMBE_FRAME_SIZE_BYTES,
+ IMBE_FRAME_RATE,
+ LITTLE_ENDIAN );
+
+ public static AudioFormat PCM_SIGNED_8KHZ_16BITS =
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_8KHZ_SAMPLE_RATE,
+ PCM_SAMPLE_SIZE_BITS,
+ ONE_CHANNEL,
+ PCM_FRAME_SIZE_BYTES,
+ PCM_FRAME_RATE,
+ LITTLE_ENDIAN );
+
+ public static AudioFormat PCM_SIGNED_48KHZ_16BITS =
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_8KHZ_SAMPLE_RATE,
+ PCM_SAMPLE_SIZE_BITS,
+ ONE_CHANNEL,
+ PCM_FRAME_SIZE_BYTES,
+ PCM_FRAME_RATE,
+ LITTLE_ENDIAN );
+
+}
diff --git a/src/decode/p25/audio/IMBETargetDataLine.java b/src/decode/p25/audio/IMBETargetDataLine.java
new file mode 100644
index 00000000..5a695010
--- /dev/null
+++ b/src/decode/p25/audio/IMBETargetDataLine.java
@@ -0,0 +1,279 @@
+package decode.p25.audio;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Control;
+import javax.sound.sampled.Control.Type;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.LineListener;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.TargetDataLine;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class IMBETargetDataLine implements TargetDataLine
+{
+ private final static Logger mLog =
+ LoggerFactory.getLogger( IMBETargetDataLine.class );
+
+ public static final int BUFFER_SIZE = 18;
+
+ public byte[] mFrame;
+
+ /**
+ * IMBE target dataline provides a java audio system compatible interface to
+ * ingest and buffer raw IMBE audio frames and expose a standard audio
+ * system interface for follow-on audio processors to consume the output audio
+ * stream provided by this interface.
+ */
+ public IMBETargetDataLine()
+ {
+ mLog.debug( "Constructing instance of TDL" );
+ }
+
+ @Override
+ public void open( AudioFormat format, int bufferSize )
+ throws LineUnavailableException
+ {
+ if( format == IMBEAudioFormat.IMBE_AUDIO_FORMAT )
+ {
+ mLog.debug( "We have now set the state - open:" );
+ }
+ else
+ {
+ throw new LineUnavailableException( "Unsupported format" );
+ }
+ }
+
+ @Override
+ public void open() throws LineUnavailableException
+ {
+ mLog.debug( "open() invoked" );
+
+ open( IMBEAudioFormat.IMBE_AUDIO_FORMAT, BUFFER_SIZE );
+ }
+
+ @Override
+ public void close()
+ {
+ mLog.debug( "Closing the IMBE target data line" );
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return true;
+ }
+
+ @Override
+ public void open( AudioFormat format ) throws LineUnavailableException
+ {
+ open( format, BUFFER_SIZE );
+ }
+
+ @Override
+ public int read( byte[] buffer, int offset, int length )
+ {
+ if( mFrame != null )
+ {
+ System.arraycopy( mFrame, 0, buffer, 0, mFrame.length );
+
+ return mFrame.length;
+ }
+
+ return 0;
+ }
+
+ private int getBytesAvailable()
+ {
+ return 1920;
+// return mFrame.length;
+ }
+
+ /**
+ * Primary inject point for submitting IMBE frame data into the audio
+ * system via this data line
+ */
+ public void receive( byte[] data )
+ {
+ mFrame = data;
+ }
+
+ /**
+ * Not implemented
+ */
+ @Override
+ public void drain()
+ {
+ }
+
+ /**
+ * Flushes (deletes) any buffered audio frames. If a frame is currently
+ * being processed it will continue and a stop event will be broadcast
+ * after the current frame has been dispatched.
+ */
+ @Override
+ public void flush()
+ {
+ mFrame = null;
+ }
+
+ /**
+ * If the line is open, sets the running state to true and attempts to
+ * retrieve a frame from the buffer. If successful, a line start event is
+ * broadcast. If no frames are currently buffered, then a line start event
+ * will be broadcast once the first frame is received.
+ *
+ * If the line is closed, or if the running state is already set to true,
+ * then the start() is ignored.
+ */
+ @Override
+ public void start()
+ {
+ }
+
+ /**
+ * Stops this data line, sets running and active states to false and purges
+ * any currently buffered data frames. Note: the line will remain open
+ * until close() is invoked and incoming frame data will continue to buffer.
+ */
+ @Override
+ public void stop()
+ {
+ }
+
+ /**
+ * Indicates if this line us currently opened, but does not reflect if the
+ * line is currently streaming data. Use the isActive() method to determine
+ * streaming status.
+ */
+ @Override
+ public boolean isRunning()
+ {
+ return true;
+ }
+
+ /**
+ * Indicates if this line is currently streaming data
+ */
+ @Override
+ public boolean isActive()
+ {
+ return true;
+ }
+
+ /**
+ * IMBE AudioFormat produced by this target data line.
+ */
+ @Override
+ public AudioFormat getFormat()
+ {
+ return IMBEAudioFormat.IMBE_AUDIO_FORMAT;
+ }
+
+ /**
+ * Size of the internal buffer in bytes. This method will not return the
+ * true size of the buffer if this data line was constructed with a buffer
+ * size other than the default (250 frames) buffer size.
+ */
+ @Override
+ public int getBufferSize()
+ {
+ return 18;
+ }
+
+ /**
+ * Number of bytes of imbe frame data available. Value will be the number
+ * of buffered frames times 18 bytes.
+ */
+ @Override
+ public int available()
+ {
+ return 18;
+ }
+
+ /**
+ * Current number of frames provided by this data line since it was opened.
+ * Counter rolls over once it exceeds the max integer value.
+ */
+ @Override
+ public int getFramePosition()
+ {
+ return 0;
+ }
+
+ /**
+ * Current number of frames provided by this data line since it was opened
+ */
+ @Override
+ public long getLongFramePosition()
+ {
+ return 0;
+ }
+
+ /**
+ * Number of microseconds worth of data that has been sourced by this
+ * target data line. Returns the frame counter times 20,000 microseconds.
+ */
+ @Override
+ public long getMicrosecondPosition()
+ {
+ return 0;
+ }
+
+ /**
+ * Not implemented
+ */
+ @Override
+ public float getLevel()
+ {
+ return AudioSystem.NOT_SPECIFIED;
+ }
+
+ @Override
+ public javax.sound.sampled.Line.Info getLineInfo()
+ {
+ return new DataLine.Info( IMBETargetDataLine.class,
+ IMBEAudioFormat.IMBE_AUDIO_FORMAT );
+ }
+
+ /**
+ * No controls are implemented for this data line
+ */
+ @Override
+ public Control[] getControls()
+ {
+ return null;
+ }
+
+ /**
+ * No controls are implemented for this data line
+ */
+ @Override
+ public boolean isControlSupported( Type control )
+ {
+ return false;
+ }
+
+ /**
+ * No controls are implemented for this data line
+ */
+ @Override
+ public Control getControl( Type control )
+ {
+ return null;
+ }
+
+ @Override
+ public void addLineListener( LineListener listener )
+ {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void removeLineListener( LineListener listener )
+ {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/src/decode/p25/audio/P25AudioCodecTest.java b/src/decode/p25/audio/P25AudioCodecTest.java
new file mode 100644
index 00000000..56640fed
--- /dev/null
+++ b/src/decode/p25/audio/P25AudioCodecTest.java
@@ -0,0 +1,48 @@
+package decode.p25.audio;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.TargetDataLine;
+import javax.sound.sampled.spi.FormatConversionProvider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class P25AudioCodecTest
+{
+ private final static Logger mLog = LoggerFactory.getLogger( P25AudioCodecTest.class );
+
+ public static void main( String[] args )
+ {
+ mLog.debug( "Starting ..." );
+ AudioFormat from = IMBEAudioFormat.IMBE_AUDIO_FORMAT;
+ AudioFormat to = IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS;
+
+ ServiceLoader loader = ServiceLoader.load( FormatConversionProvider.class );
+
+ Iterator it = loader.iterator();
+
+ while( it.hasNext() )
+ {
+ mLog.debug( "Provider:" + it.next().getClass() );
+ }
+
+
+
+// try
+// {
+// TargetDataLine tdl = AudioSystem.getTargetDataLine( from );
+//
+// mLog.debug( "We got a tdl ..." );
+// }
+// catch ( Exception e )
+// {
+// e.printStackTrace();
+// }
+
+ mLog.debug( "Finished" );
+ }
+}
diff --git a/src/decode/p25/audio/P25AudioOutput.java b/src/decode/p25/audio/P25AudioOutput.java
new file mode 100644
index 00000000..6ff7a950
--- /dev/null
+++ b/src/decode/p25/audio/P25AudioOutput.java
@@ -0,0 +1,333 @@
+package decode.p25.audio;
+
+import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.SourceDataLine;
+
+import message.Message;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import sample.Listener;
+import audio.AudioType;
+import audio.IAudioOutput;
+import audio.IAudioTypeListener;
+import audio.SquelchListener;
+import audio.SquelchListener.SquelchState;
+import controller.ResourceManager;
+import controller.ThreadPoolManager;
+import controller.ThreadPoolManager.ThreadType;
+import decode.p25.message.ldu.LDUMessage;
+
+/**
+ * IMBE Audio Output converter. Processes 18-byte IMBE audio frames and
+ * produces PCM 48k MONO 16-bit sample output.
+ *
+ * Note: this class depends on an external imbe format conversion provider that
+ * is provided by an external library like JMBE, and that library must be on the
+ * classpath and discoverable at runtime.
+ */
+public class P25AudioOutput implements IAudioOutput, IAudioTypeListener,
+ SquelchListener, Listener
+{
+ private final static Logger mLog = LoggerFactory.getLogger( P25AudioOutput.class );
+
+ /* 1 IMBE frame = 160 samples/320 bytes @ 8kHz or 1920 bytes at 48kHz */
+ private static final int PROCESSED_AUDIO_FRAME_SIZE = 320;
+ private IMBETargetDataLine mIMBETargetDataLine = new IMBETargetDataLine();
+ private SourceDataLine mOutput;
+ private AudioInputStream mIMBEToPCMConverter;
+// private AudioInputStream mUpsampler;
+ private ArrayBlockingQueue mIMBEFrameQueue =
+ new ArrayBlockingQueue( 100 );
+ private ArrayBlockingQueue mProcessedAudioQueue =
+ new ArrayBlockingQueue( 100 );
+
+ private AtomicBoolean mConverting = new AtomicBoolean();
+ private AtomicBoolean mDispatching = new AtomicBoolean();
+ private AtomicBoolean mEnabled = new AtomicBoolean();
+ private boolean mCanProcessAudio = false;
+
+ private ThreadPoolManager mThreadPoolManager;
+
+ public P25AudioOutput( ResourceManager resourceManager )
+ {
+ loadConverter();
+
+ if( mCanProcessAudio )
+ {
+ mThreadPoolManager = resourceManager.getThreadPoolManager();
+
+ /* Schedule the imbe frame processor to run slightly faster than 6
+ * times a second. Each LDU message contains 9 frames and the
+ * frame rate is 50, so we need to run at least 6 times a second */
+ mThreadPoolManager.scheduleFixedRate( ThreadType.SOURCE_SAMPLE_PROCESSING,
+ new IMBEFrameConverter(), 150, TimeUnit.MILLISECONDS );
+ }
+ }
+
+ /**
+ * Primary inject point for p25 imbe audio frame messages. Each LDU audio
+ * message contains 9 imbe voice frames.
+ */
+ public void receive( Message message )
+ {
+ if( mCanProcessAudio )
+ {
+ if( message instanceof LDUMessage )
+ {
+ for( byte[] frame: ((LDUMessage)message).getIMBEFrames() )
+ {
+ if( !mIMBEFrameQueue.offer( frame ) )
+ {
+ mLog.debug( "IMBE frame queue full - throwing away imbe audio frame" );
+ }
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Receives processed audio buffers from the imbe frame converter and queues
+ * them for dispatch. If the dispatcher is not currently running, this
+ * method will start it.
+ */
+ private void receive( byte[] audio )
+ {
+ mProcessedAudioQueue.offer( audio );
+
+ if( !mDispatching.get() )
+ {
+ mThreadPoolManager.scheduleOnce( new AudioDispatcher(), 0, TimeUnit.MILLISECONDS );
+ }
+ }
+
+ /**
+ * Loads audio frame processing chain. Constructs an imbe targetdataline
+ * to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
+ * stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
+ */
+ private void loadConverter()
+ {
+ try
+ {
+ mIMBEToPCMConverter = AudioSystem
+ .getAudioInputStream( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS,
+ new AudioInputStream( mIMBETargetDataLine ) );
+
+ if( mIMBEToPCMConverter != null )
+ {
+ mLog.debug( "IMBE audio converter library loaded successfully" );
+// mUpsampler = AudioSystem
+// .getAudioInputStream( IMBEAudioFormat.PCM_SIGNED_48KHZ_16BITS,
+// mIMBEToPCMConverter );
+//
+// if( mUpsampler != null )
+// {
+// mLog.debug( "Resampler loaded successfully" );
+
+ try
+ {
+ mOutput = AudioSystem.getSourceDataLine( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS );
+
+ if( mOutput != null )
+ {
+ mLog.debug( "Audio output line loaded successfully" );
+
+ /* Open the audio line with room for two buffers */
+ mOutput.open( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS,
+ PROCESSED_AUDIO_FRAME_SIZE * 2 );
+ mCanProcessAudio = true;
+ }
+ else
+ {
+ mLog.debug( "Couldn't create output line" );
+ }
+ }
+ catch ( LineUnavailableException e )
+ {
+ mLog.error( "AudioOutput - couldn't open audio speakers "
+ + "for playback", e );
+ }
+
+// }
+// else
+// {
+// mIMBEToPCMConverter = null;
+//
+// mLog.debug( "couldn't load Resampler" );
+// }
+ }
+ else
+ {
+ mLog.debug( "could not load IMBE audio converter library" );
+ }
+ }
+ catch( IllegalArgumentException iae )
+ {
+ mLog.equals( "couldn't find/load IMBE to PCM audio converter" );
+ }
+ }
+
+ @Override
+ public void setSquelch( SquelchState state )
+ {
+ /* Ignored */
+ }
+
+ @Override
+ public void setAudioPlaybackEnabled( boolean enabled )
+ {
+ mEnabled.set( enabled );
+ }
+
+ @Override
+ public void dispose()
+ {
+ mCanProcessAudio = false;
+
+
+ mIMBEFrameQueue.clear();
+ mProcessedAudioQueue.clear();
+
+ mConverting.set( false );
+ mDispatching.set( false );
+
+ mOutput.close();
+ mOutput = null;
+
+ mIMBETargetDataLine.close();
+ mIMBETargetDataLine = null;
+
+
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAudioType( AudioType type )
+ {
+ //Not implemented
+ }
+
+ private class IMBEFrameConverter implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ if( mConverting.compareAndSet( false, true ) )
+ {
+ boolean processing = true;
+
+ while( processing )
+ {
+ byte[] frame = mIMBEFrameQueue.poll();
+
+ if( frame != null )
+ {
+ if( frame.length == 18 )
+ {
+ /* Insert an 18-byte imbe frame into the stream */
+ mIMBETargetDataLine.receive( frame );
+
+ /* Read 1920 bytes of converted upsamped audio from output */
+ byte[] audio = new byte[ PROCESSED_AUDIO_FRAME_SIZE ];
+
+ try
+ {
+// mLog.debug( "reading converted audio bytes - available: " + mUpsampler.available() );
+// int read = mUpsampler.read( audio );
+ int read = mIMBEToPCMConverter.read( audio );
+
+ if( read > 0 )
+ {
+ receive( audio );
+ }
+ else
+ {
+ mLog.debug( "Couldn't read audio data from "
+ + "conversion stream after inserting imbe "
+ + "frame" );
+ }
+ }
+ catch ( IOException e )
+ {
+ mLog.error( "Error reading processed audio data "
+ + "from imbe conversion stream", e );
+ }
+ }
+ else
+ {
+ mLog.equals( "We got an imbe frame that's not 18 bytes - length:" + ( frame == null ? "null" : frame.length ) );
+ }
+ }
+ else
+ {
+ processing = false;
+ }
+ }
+
+ mConverting.set( false );
+ }
+ }
+ }
+
+ private class AudioDispatcher implements Runnable
+ {
+ private int mEmptyFrameCount = 0;
+
+ @Override
+ public void run()
+ {
+ if( mDispatching.compareAndSet( false, true ) )
+ {
+ mOutput.drain();
+
+ /* Write 2 frames of audio and then start the line */
+ writeAudioFrame();
+ writeAudioFrame();
+
+ mOutput.start();
+
+ while( mEmptyFrameCount < 3 )
+ {
+ writeAudioFrame();
+ }
+
+ mOutput.stop();
+
+ mDispatching.set( false );
+ }
+ }
+
+ private void writeAudioFrame()
+ {
+ byte[] audio = mProcessedAudioQueue.poll();
+
+ if( audio == null )
+ {
+ mEmptyFrameCount++;
+ }
+
+ /* Send empty audio if we're not currently enabled to playback */
+ if( audio == null || !mEnabled.get() )
+ {
+ audio = new byte[ PROCESSED_AUDIO_FRAME_SIZE ];
+ }
+
+ if( mOutput != null )
+ {
+ mOutput.write( audio, 0, audio.length );
+ }
+ }
+ }
+}
+
diff --git a/src/decode/p25/message/ldu/LDUMessage.java b/src/decode/p25/message/ldu/LDUMessage.java
index c55d48c4..883731ea 100644
--- a/src/decode/p25/message/ldu/LDUMessage.java
+++ b/src/decode/p25/message/ldu/LDUMessage.java
@@ -1,15 +1,21 @@
package decode.p25.message.ldu;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import alias.AliasList;
import bits.BinaryMessage;
-import decode.p25.P25Interleave;
import decode.p25.message.P25Message;
import decode.p25.reference.DataUnitID;
import edac.CRC;
-import edac.Galois24;
public class LDUMessage extends P25Message
{
+ private final static Logger mLog = LoggerFactory.getLogger( LDUMessage.class );
+
public static final int IMBE_FRAME_1 = 64;
public static final int IMBE_FRAME_2 = 208;
public static final int IMBE_FRAME_3 = 392;
@@ -19,133 +25,7 @@ public class LDUMessage extends P25Message
public static final int IMBE_FRAME_7 = 1128;
public static final int IMBE_FRAME_8 = 1312;
public static final int IMBE_FRAME_9 = 1488;
-
- public static final int[] PN_SEED_1 = { 64,65,66,67,68,69,70,71,72,73,74,75 };
- public static final int[] PN_SEED_2 = { 208,209,210,211,212,213,214,215,216,
- 217,218,219 };
- public static final int[] PN_SEED_3 = { 392,393,394,395,396,397,398,399,400,
- 401,402,403 };
- public static final int[] PN_SEED_4 = { 576,577,578,579,580,581,582,583,584,
- 585,586,587 };
- public static final int[] PN_SEED_5 = { 760,761,762,763,764,765,766,767,768,
- 769,770,771 };
- public static final int[] PN_SEED_6 = { 944,945,946,947,948,949,950,951,952,
- 953,954,955 };
- public static final int[] PN_SEED_7 = { 1128,1129,1130,1131,1132,1133,1134,
- 1135,1136,1137,1138,1139 };
- public static final int[] PN_SEED_8 = { 1312,1313,1314,1315,1316,1317,1318,
- 1319,1320,1321,1322,1323 };
- public static final int[] PN_SEED_9 = { 1488,1489,1490,1491,1492,1493,1494,
- 1495,1496,1497,1498,1499 };
-
- public static final int[] FRAME1_1= { 64,65,66,67,68,69,70,71 };
- public static final int[] FRAME1_2= { 72,73,74,75,87,88,89,90 };
- public static final int[] FRAME1_3= { 91,92,93,94,95,96,97,98 };
- public static final int[] FRAME1_4= { 110,111,112,113,114,115,116,117 };
- public static final int[] FRAME1_5= { 118,119,120,121,133,134,135,136 };
- public static final int[] FRAME1_6= { 137,138,139,140,141,142,143,144 };
- public static final int[] FRAME1_7= { 156,157,158,159,160,161,162,163 };
- public static final int[] FRAME1_8= { 164,165,166,171,172,173,174,175 };
- public static final int[] FRAME1_9= { 176,177,178,179,180,181,186,187 };
- public static final int[] FRAME1_10= { 188,189,190,191,192,193,194,195 };
- public static final int[] FRAME1_11= { 196,201,202,203,204,205,206,207 };
-
- public static final int[] FRAME2_1= { 208,209,210,211,212,213,214,215 };
- public static final int[] FRAME2_2= { 216,217,218,219,231,232,233,234 };
- public static final int[] FRAME2_3= { 235,236,237,238,239,240,241,242 };
- public static final int[] FRAME2_4= { 254,255,256,257,258,259,260,261 };
- public static final int[] FRAME2_5= { 262,263,264,265,277,278,279,280 };
- public static final int[] FRAME2_6= { 281,282,283,284,285,286,287,288 };
- public static final int[] FRAME2_7= { 300,301,302,303,304,305,306,307 };
- public static final int[] FRAME2_8= { 308,309,310,315,316,317,318,319 };
- public static final int[] FRAME2_9= { 320,321,322,323,324,325,330,331 };
- public static final int[] FRAME2_10= { 332,333,334,335,336,337,338,339 };
- public static final int[] FRAME2_11= { 340,345,346,347,348,349,350,351 };
- public static final int[] FRAME3_1= { 392,393,394,395,396,397,398,399 };
- public static final int[] FRAME3_2= { 400,401,402,403,415,416,417,418 };
- public static final int[] FRAME3_3= { 419,420,421,422,423,424,425,426 };
- public static final int[] FRAME3_4= { 438,439,440,441,442,443,444,445 };
- public static final int[] FRAME3_5= { 446,447,448,449,461,462,463,464 };
- public static final int[] FRAME3_6= { 465,466,467,468,469,470,471,472 };
- public static final int[] FRAME3_7= { 484,485,486,487,488,489,490,491 };
- public static final int[] FRAME3_8= { 492,493,494,499,500,501,502,503 };
- public static final int[] FRAME3_9= { 504,505,506,507,508,509,514,515 };
- public static final int[] FRAME3_10= { 516,517,518,519,520,521,522,523 };
- public static final int[] FRAME3_11= { 524,529,530,531,532,533,534,535 };
-
- public static final int[] FRAME4_1= { 576,577,578,579,580,581,582,583 };
- public static final int[] FRAME4_2= { 584,585,586,587,599,600,601,602 };
- public static final int[] FRAME4_3= { 603,604,605,606,607,608,609,610 };
- public static final int[] FRAME4_4= { 622,623,624,625,626,627,628,629 };
- public static final int[] FRAME4_5= { 630,631,632,633,645,646,647,648 };
- public static final int[] FRAME4_6= { 649,650,651,652,653,654,655,656 };
- public static final int[] FRAME4_7= { 668,669,670,671,672,673,674,675 };
- public static final int[] FRAME4_8= { 676,677,678,683,684,685,686,687 };
- public static final int[] FRAME4_9= { 688,689,690,691,692,693,698,699 };
- public static final int[] FRAME4_10= { 700,701,702,703,704,705,706,707 };
- public static final int[] FRAME4_11= { 708,713,714,715,716,717,718,719 };
-
- public static final int[] FRAME5_1= { 760,761,762,763,764,765,766,767 };
- public static final int[] FRAME5_2= { 768,769,770,771,783,784,785,786 };
- public static final int[] FRAME5_3= { 787,788,789,790,791,792,793,794 };
- public static final int[] FRAME5_4= { 806,807,808,809,810,811,812,813 };
- public static final int[] FRAME5_5= { 814,815,816,817,829,830,831,832 };
- public static final int[] FRAME5_6= { 833,834,835,836,837,838,839,840 };
- public static final int[] FRAME5_7= { 852,853,854,855,856,857,858,859 };
- public static final int[] FRAME5_8= { 860,861,862,867,868,869,870,871 };
- public static final int[] FRAME5_9= { 872,873,874,875,876,877,882,883 };
- public static final int[] FRAME5_10= { 884,885,886,887,888,889,890,891 };
- public static final int[] FRAME5_11= { 892,897,898,899,900,901,902,903 };
-
- public static final int[] FRAME6_1= { 944,945,946,947,948,949,950,951 };
- public static final int[] FRAME6_2= { 952,953,954,955,967,968,969,970 };
- public static final int[] FRAME6_3= { 971,972,973,974,975,976,977,978 };
- public static final int[] FRAME6_4= { 990,991,992,993,994,995,996,997 };
- public static final int[] FRAME6_5= { 998,999,1000,1001,1013,1014,1015,1016 };
- public static final int[] FRAME6_6= { 1017,1018,1019,1020,1021,1022,1023,1024 };
- public static final int[] FRAME6_7= { 1036,1037,1038,1039,1040,1041,1042,1043 };
- public static final int[] FRAME6_8= { 1044,1045,1046,1051,1052,1053,1054,1055 };
- public static final int[] FRAME6_9= { 1056,1057,1058,1059,1060,1061,1066,1067 };
- public static final int[] FRAME6_10= { 1068,1069,1070,1071,1072,1073,1074,1075 };
- public static final int[] FRAME6_11= { 1076,1081,1082,1083,1084,1085,1086,1087 };
-
- public static final int[] FRAME7_1= { 1128,1129,1130,1131,1132,1133,1134,1135 };
- public static final int[] FRAME7_2= { 1136,1137,1138,1139,1151,1152,1153,1154 };
- public static final int[] FRAME7_3= { 1155,1156,1157,1158,1159,1160,1161,1162 };
- public static final int[] FRAME7_4= { 1174,1175,1176,1177,1178,1179,1180,1181 };
- public static final int[] FRAME7_5= { 1182,1183,1184,1185,1197,1198,1199,1200 };
- public static final int[] FRAME7_6= { 1201,1202,1203,1204,1205,1206,1207,1208 };
- public static final int[] FRAME7_7= { 1220,1221,1222,1223,1224,1225,1226,1227 };
- public static final int[] FRAME7_8= { 1228,1229,1230,1235,1236,1237,1238,1239 };
- public static final int[] FRAME7_9= { 1240,1241,1242,1243,1244,1245,1250,1251 };
- public static final int[] FRAME7_10= { 1252,1253,1254,1255,1256,1257,1258,1259 };
- public static final int[] FRAME7_11= { 1260,1265,1266,1267,1268,1269,1270,1271 };
-
- public static final int[] FRAME8_1= { 1312,1313,1314,1315,1316,1317,1318,1319 };
- public static final int[] FRAME8_2= { 1320,1321,1322,1323,1335,1336,1337,1338 };
- public static final int[] FRAME8_3= { 1339,1340,1341,1342,1343,1344,1345,1346 };
- public static final int[] FRAME8_4= { 1358,1359,1360,1361,1362,1363,1364,1365 };
- public static final int[] FRAME8_5= { 1366,1367,1368,1369,1381,1382,1383,1384 };
- public static final int[] FRAME8_6= { 1385,1386,1387,1388,1389,1390,1391,1392 };
- public static final int[] FRAME8_7= { 1404,1405,1406,1407,1408,1409,1410,1411 };
- public static final int[] FRAME8_8= { 1412,1413,1414,1419,1420,1421,1422,1423 };
- public static final int[] FRAME8_9= { 1424,1425,1426,1427,1428,1429,1434,1435 };
- public static final int[] FRAME8_10= { 1436,1437,1438,1439,1440,1441,1442,1443 };
- public static final int[] FRAME8_11= { 1444,1449,1450,1451,1452,1453,1454,1455 };
-
- public static final int[] FRAME9_1= { 1488,1489,1490,1491,1492,1493,1494,1495 };
- public static final int[] FRAME9_2= { 1496,1497,1498,1499,1511,1512,1513,1514 };
- public static final int[] FRAME9_3= { 1515,1516,1517,1518,1519,1520,1521,1522 };
- public static final int[] FRAME9_4= { 1534,1535,1536,1537,1538,1539,1540,1541 };
- public static final int[] FRAME9_5= { 1542,1543,1544,1545,1557,1558,1559,1560 };
- public static final int[] FRAME9_6= { 1561,1562,1563,1564,1565,1566,1567,1568 };
- public static final int[] FRAME9_7= { 1580,1581,1582,1583,1584,1585,1586,1587 };
- public static final int[] FRAME9_8= { 1588,1589,1590,1595,1596,1597,1598,1599 };
- public static final int[] FRAME9_9= { 1600,1601,1602,1603,1604,1605,1610,1611 };
- public static final int[] FRAME9_10= { 1612,1613,1614,1615,1616,1617,1618,1619 };
- public static final int[] FRAME9_11= { 1620,1625,1626,1627,1628,1629,1630,1631 };
-
public static final int[] LOW_SPEED_DATA = { 1456,1457,1458,1459,1460,1461,
1462,1463,1472,1473,1474,1475,1476,1477,1478,1479 };
@@ -154,17 +34,6 @@ public class LDUMessage extends P25Message
{
super( message, duid, aliasList );
- deinterleave();
-
- /* Perform error detection & correction on coset zero of each frame */
- errorCorrectCosetZero();
-
- /* Remove randomizer from coset words c1 - c6 */
- derandomize();
-
- /* Perform edac against remaining info vectors */
- errorCorrectInformationVectors();
-
/* NID CRC is checked in the message framer, thus a constructed message
* means it passed the CRC */
@@ -199,89 +68,6 @@ public class LDUMessage extends P25Message
return mMessage.getHex( LOW_SPEED_DATA, 4 );
}
- /**
- * Deinterleaves 144 message bits of each of the 9 imbe voice frames
- */
- private void deinterleave()
- {
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_1, IMBE_FRAME_1 + 144 );
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_2, IMBE_FRAME_2 + 144 );
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_3, IMBE_FRAME_3 + 144 );
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_4, IMBE_FRAME_4 + 144 );
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_5, IMBE_FRAME_5 + 144 );
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_6, IMBE_FRAME_6 + 144 );
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_7, IMBE_FRAME_7 + 144 );
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_8, IMBE_FRAME_8 + 144 );
- P25Interleave.deinterleaveVoice( mMessage, IMBE_FRAME_9, IMBE_FRAME_9 + 144 );
- }
-
-
- /**
- * Performs error detection and correction on coset work zero of each of the
- * 9 imbe voice frames.
- */
- private void errorCorrectCosetZero()
- {
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_1 );
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_2 );
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_3 );
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_4 );
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_5 );
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_6 );
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_7 );
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_8 );
- Galois24.checkAndCorrect( mMessage, IMBE_FRAME_9 );
- }
-
- /**
- * Generates a pseudo-random noise randomizer mask and applies (xor) the
- * mask against coset words c1 through c6 of each of the 9 imbe voice frames
- */
- private void derandomize()
- {
- derandomize( PN_SEED_1 );
- derandomize( PN_SEED_2 );
- derandomize( PN_SEED_3 );
- derandomize( PN_SEED_4 );
- derandomize( PN_SEED_5 );
- derandomize( PN_SEED_6 );
- derandomize( PN_SEED_7 );
- derandomize( PN_SEED_8 );
- derandomize( PN_SEED_9 );
- }
-
- /**
- * Perform error detection and correction against information vectors 1 - 6
- */
- private void errorCorrectInformationVectors()
- {
- errorCorrectInformationVectors( IMBE_FRAME_1 );
- errorCorrectInformationVectors( IMBE_FRAME_2 );
- errorCorrectInformationVectors( IMBE_FRAME_3 );
- errorCorrectInformationVectors( IMBE_FRAME_4 );
- errorCorrectInformationVectors( IMBE_FRAME_5 );
- errorCorrectInformationVectors( IMBE_FRAME_6 );
- errorCorrectInformationVectors( IMBE_FRAME_7 );
- errorCorrectInformationVectors( IMBE_FRAME_8 );
- errorCorrectInformationVectors( IMBE_FRAME_9 );
- }
-
- /**
- * Performs Golay(23,12,7) error detection and correction against information
- * vectors 1 - 3 and Hamming(15,11,3) against vectors 4 - 6.
- *
- * @param frameStart - bit position of the imbe frame start
- */
- private void errorCorrectInformationVectors( int frameStart )
- {
- Galois24.checkAndCorrect( mMessage, frameStart + 23 );
- Galois24.checkAndCorrect( mMessage, frameStart + 46 );
- Galois24.checkAndCorrect( mMessage, frameStart + 69 );
-
- //TODO: hamming edac for iv 4,5,6 ...
- }
-
-
/* Temporary override */
public boolean isValid()
{
@@ -289,150 +75,24 @@ public class LDUMessage extends P25Message
}
/**
- * Removes randomizer by generating a pseudo-random noise sequence from the
- * first 12 bits of coset word c0 and applies (xor) that sequence against
- * message coset words c1 through c6.
- *
- * @param seedBits - first 12 bit indexes of coset word c0
+ * Returns a 162 byte array containing 9 IMBE voice frames of 18-bytes
+ * (144-bits) each. Each frame is intact as transmitted and requires
+ * deinterleaving, error correction, derandomizing, etc.
*/
- public void derandomize( int[] seedBits )
+ public List getIMBEFrames()
{
- /* Set the offset to the first seed bit plus 23 to point to coset c1 */
- int offset = seedBits[ 0 ] + 23;
+ List frames = new ArrayList();
- /* Get seed value from first 12 bits of coset c0 */
- int seed = mMessage.getInt( seedBits );
-
- /* Left shift 4 places (multiply by 16) */
- seed <<= 4;
-
- for( int x = 0; x < 114; x++ )
- {
- seed = ( ( 173 * seed ) + 13849 ) & 65536;
+ frames.add( mMessage.get( IMBE_FRAME_1, IMBE_FRAME_1 + 144 ).toByteArray() );
+ frames.add( mMessage.get( IMBE_FRAME_2, IMBE_FRAME_2 + 144 ).toByteArray() );
+ frames.add( mMessage.get( IMBE_FRAME_3, IMBE_FRAME_3 + 144 ).toByteArray() );
+ frames.add( mMessage.get( IMBE_FRAME_4, IMBE_FRAME_4 + 144 ).toByteArray() );
+ frames.add( mMessage.get( IMBE_FRAME_5, IMBE_FRAME_5 + 144 ).toByteArray() );
+ frames.add( mMessage.get( IMBE_FRAME_6, IMBE_FRAME_6 + 144 ).toByteArray() );
+ frames.add( mMessage.get( IMBE_FRAME_7, IMBE_FRAME_7 + 144 ).toByteArray() );
+ frames.add( mMessage.get( IMBE_FRAME_8, IMBE_FRAME_8 + 144 ).toByteArray() );
+ frames.add( mMessage.get( IMBE_FRAME_9, IMBE_FRAME_9 + 144 ).toByteArray() );
- /* If seed bit 15 is set, toggle the corresponding message bit */
- if( ( seed & 32768 ) == 32768 )
- {
- mMessage.flip( x + offset );
- }
- }
- }
-
- /**
- * Extracts 9 IMBE voice frames of 11-bytes (88-bit) each into a 99 byte array
- */
- public byte[] getIMBEFrameData()
- {
- byte[] data = new byte[ 99 ];
-
- data[ 0 ] = mMessage.getByte( FRAME1_1 );
- data[ 1 ] = mMessage.getByte( FRAME1_2 );
- data[ 2 ] = mMessage.getByte( FRAME1_3 );
- data[ 3 ] = mMessage.getByte( FRAME1_4 );
- data[ 4 ] = mMessage.getByte( FRAME1_5 );
- data[ 5 ] = mMessage.getByte( FRAME1_6 );
- data[ 6 ] = mMessage.getByte( FRAME1_7 );
- data[ 7 ] = mMessage.getByte( FRAME1_8 );
- data[ 8 ] = mMessage.getByte( FRAME1_9 );
- data[ 9 ] = mMessage.getByte( FRAME1_10 );
- data[ 10 ] = mMessage.getByte( FRAME1_11 );
-
- data[ 11 ] = mMessage.getByte( FRAME2_1 );
- data[ 12 ] = mMessage.getByte( FRAME2_2 );
- data[ 13 ] = mMessage.getByte( FRAME2_3 );
- data[ 14 ] = mMessage.getByte( FRAME2_4 );
- data[ 15 ] = mMessage.getByte( FRAME2_5 );
- data[ 16 ] = mMessage.getByte( FRAME2_6 );
- data[ 17 ] = mMessage.getByte( FRAME2_7 );
- data[ 18 ] = mMessage.getByte( FRAME2_8 );
- data[ 19 ] = mMessage.getByte( FRAME2_9 );
- data[ 20 ] = mMessage.getByte( FRAME2_10 );
- data[ 21 ] = mMessage.getByte( FRAME2_11 );
-
- data[ 22 ] = mMessage.getByte( FRAME3_1 );
- data[ 23 ] = mMessage.getByte( FRAME3_2 );
- data[ 24 ] = mMessage.getByte( FRAME3_3 );
- data[ 25 ] = mMessage.getByte( FRAME3_4 );
- data[ 26 ] = mMessage.getByte( FRAME3_5 );
- data[ 27 ] = mMessage.getByte( FRAME3_6 );
- data[ 28 ] = mMessage.getByte( FRAME3_7 );
- data[ 29 ] = mMessage.getByte( FRAME3_8 );
- data[ 30 ] = mMessage.getByte( FRAME3_9 );
- data[ 31 ] = mMessage.getByte( FRAME3_10 );
- data[ 32 ] = mMessage.getByte( FRAME3_11 );
-
- data[ 33 ] = mMessage.getByte( FRAME4_1 );
- data[ 34 ] = mMessage.getByte( FRAME4_2 );
- data[ 35 ] = mMessage.getByte( FRAME4_3 );
- data[ 36 ] = mMessage.getByte( FRAME4_4 );
- data[ 37 ] = mMessage.getByte( FRAME4_5 );
- data[ 38 ] = mMessage.getByte( FRAME4_6 );
- data[ 39 ] = mMessage.getByte( FRAME4_7 );
- data[ 40 ] = mMessage.getByte( FRAME4_8 );
- data[ 41 ] = mMessage.getByte( FRAME4_9 );
- data[ 42 ] = mMessage.getByte( FRAME4_10 );
- data[ 43 ] = mMessage.getByte( FRAME4_11 );
-
- data[ 44 ] = mMessage.getByte( FRAME5_1 );
- data[ 45 ] = mMessage.getByte( FRAME5_2 );
- data[ 46 ] = mMessage.getByte( FRAME5_3 );
- data[ 47 ] = mMessage.getByte( FRAME5_4 );
- data[ 48 ] = mMessage.getByte( FRAME5_5 );
- data[ 49 ] = mMessage.getByte( FRAME5_6 );
- data[ 50 ] = mMessage.getByte( FRAME5_7 );
- data[ 51 ] = mMessage.getByte( FRAME5_8 );
- data[ 52 ] = mMessage.getByte( FRAME5_9 );
- data[ 53 ] = mMessage.getByte( FRAME5_10 );
- data[ 54 ] = mMessage.getByte( FRAME5_11 );
-
- data[ 55 ] = mMessage.getByte( FRAME6_1 );
- data[ 56 ] = mMessage.getByte( FRAME6_2 );
- data[ 57 ] = mMessage.getByte( FRAME6_3 );
- data[ 58 ] = mMessage.getByte( FRAME6_4 );
- data[ 59 ] = mMessage.getByte( FRAME6_5 );
- data[ 60 ] = mMessage.getByte( FRAME6_6 );
- data[ 61 ] = mMessage.getByte( FRAME6_7 );
- data[ 62 ] = mMessage.getByte( FRAME6_8 );
- data[ 63 ] = mMessage.getByte( FRAME6_9 );
- data[ 64 ] = mMessage.getByte( FRAME6_10 );
- data[ 65 ] = mMessage.getByte( FRAME6_11 );
-
- data[ 66 ] = mMessage.getByte( FRAME7_1 );
- data[ 67 ] = mMessage.getByte( FRAME7_2 );
- data[ 68 ] = mMessage.getByte( FRAME7_3 );
- data[ 69 ] = mMessage.getByte( FRAME7_4 );
- data[ 70 ] = mMessage.getByte( FRAME7_5 );
- data[ 71 ] = mMessage.getByte( FRAME7_6 );
- data[ 72 ] = mMessage.getByte( FRAME7_7 );
- data[ 73 ] = mMessage.getByte( FRAME7_8 );
- data[ 74 ] = mMessage.getByte( FRAME7_9 );
- data[ 75 ] = mMessage.getByte( FRAME7_10 );
- data[ 76 ] = mMessage.getByte( FRAME7_11 );
-
- data[ 77 ] = mMessage.getByte( FRAME8_1 );
- data[ 78 ] = mMessage.getByte( FRAME8_2 );
- data[ 79 ] = mMessage.getByte( FRAME8_3 );
- data[ 80 ] = mMessage.getByte( FRAME8_4 );
- data[ 81 ] = mMessage.getByte( FRAME8_5 );
- data[ 82 ] = mMessage.getByte( FRAME8_6 );
- data[ 83 ] = mMessage.getByte( FRAME8_7 );
- data[ 84 ] = mMessage.getByte( FRAME8_8 );
- data[ 85 ] = mMessage.getByte( FRAME8_9 );
- data[ 86 ] = mMessage.getByte( FRAME8_10 );
- data[ 87 ] = mMessage.getByte( FRAME8_11 );
-
- data[ 88 ] = mMessage.getByte( FRAME9_1 );
- data[ 89 ] = mMessage.getByte( FRAME9_2 );
- data[ 90 ] = mMessage.getByte( FRAME9_3 );
- data[ 91 ] = mMessage.getByte( FRAME9_4 );
- data[ 92 ] = mMessage.getByte( FRAME9_5 );
- data[ 93 ] = mMessage.getByte( FRAME9_6 );
- data[ 94 ] = mMessage.getByte( FRAME9_7 );
- data[ 95 ] = mMessage.getByte( FRAME9_8 );
- data[ 96 ] = mMessage.getByte( FRAME9_9 );
- data[ 97 ] = mMessage.getByte( FRAME9_10 );
- data[ 98 ] = mMessage.getByte( FRAME9_11 );
-
- return data;
+ return frames;
}
}
diff --git a/src/decode/p25/message/ldu/lc/motorola/MotorolaOpcode_0.java b/src/decode/p25/message/ldu/lc/motorola/MotorolaOpcode_0.java
new file mode 100644
index 00000000..f15cc939
--- /dev/null
+++ b/src/decode/p25/message/ldu/lc/motorola/MotorolaOpcode_0.java
@@ -0,0 +1,52 @@
+package decode.p25.message.ldu.lc.motorola;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import decode.p25.message.ldu.LDU1Message;
+import decode.p25.reference.LinkControlOpcode;
+
+public class MotorolaOpcode_0 extends LDU1Message
+{
+ private final static Logger mLog =
+ LoggerFactory.getLogger( MotorolaOpcode_0.class );
+ public static final int[] TARGET_ADDRESS = { 536,537,538,539,540,541,546,
+ 547,548,549,550,551,556,557,558,559,560,561,566,567,568,569,570,571 };
+ public static final int[] SOURCE_ADDRESS = { 720,721,722,723,724,725,730,
+ 731,732,733,734,735,740,741,742,743,744,745,750,751,752,753,754,755 };
+
+ public MotorolaOpcode_0( LDU1Message message )
+ {
+ super( message );
+ }
+
+ @Override
+ public String getEventType()
+ {
+ return LinkControlOpcode.STATUS_QUERY.getDescription();
+ }
+
+ @Override
+ public String getMessage()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( getMessageStub() );
+
+ sb.append( " FROM:" + getSourceAddress() );
+
+ sb.append( " TO:" + getTargetAddress() );
+
+ return sb.toString();
+ }
+
+ public String getTargetAddress()
+ {
+ return mMessage.getHex( TARGET_ADDRESS, 6 );
+ }
+
+ public String getSourceAddress()
+ {
+ return mMessage.getHex( SOURCE_ADDRESS, 6 );
+ }
+}
diff --git a/src/decode/p25/message/pdu/confirmed/PDUConfirmedMessage.java b/src/decode/p25/message/pdu/confirmed/PDUConfirmedMessage.java
index b171e5ee..3ee3ad8c 100644
--- a/src/decode/p25/message/pdu/confirmed/PDUConfirmedMessage.java
+++ b/src/decode/p25/message/pdu/confirmed/PDUConfirmedMessage.java
@@ -40,8 +40,6 @@ public class PDUConfirmedMessage extends PDUMessage
{
super( message.getSourceMessage(), DataUnitID.PDUC, message.getAliasList() );
mCRC = message.getCRCResults();
-
- mLog.debug( toString() );
}
private void checkCRC()
diff --git a/src/decode/p25/message/tdu/lc/TDULinkControlMessage.java b/src/decode/p25/message/tdu/lc/TDULinkControlMessage.java
index a77d109e..4012224d 100644
--- a/src/decode/p25/message/tdu/lc/TDULinkControlMessage.java
+++ b/src/decode/p25/message/tdu/lc/TDULinkControlMessage.java
@@ -12,7 +12,7 @@ import decode.p25.reference.LinkControlOpcode;
import decode.p25.reference.Vendor;
import decode.p25.reference.VendorLinkControlOpcode;
import edac.CRC;
-import edac.Galois24;
+import edac.Golay24;
import edac.ReedSolomon_24_12_13;
public class TDULinkControlMessage extends P25Message
@@ -183,7 +183,7 @@ public class TDULinkControlMessage extends P25Message
while( x < mMessage.size() )
{
- mMessage = Galois24.checkAndCorrect( mMessage, x );
+ mMessage = Golay24.checkAndCorrect( mMessage, x );
if( !( mMessage.getCRC() == CRC.PASSED ) )
{
diff --git a/src/decode/p25/message/tsbk/TSBKMessageFactory.java b/src/decode/p25/message/tsbk/TSBKMessageFactory.java
index 12f363eb..73a50611 100644
--- a/src/decode/p25/message/tsbk/TSBKMessageFactory.java
+++ b/src/decode/p25/message/tsbk/TSBKMessageFactory.java
@@ -3,12 +3,14 @@ package decode.p25.message.tsbk;
import alias.AliasList;
import bits.BinaryMessage;
import decode.p25.message.tsbk.motorola.ChannelCWIDUpdate;
+import decode.p25.message.tsbk.motorola.CongestionHoldOffTimer;
import decode.p25.message.tsbk.motorola.MotorolaOpcode;
import decode.p25.message.tsbk.motorola.MotorolaTSBKMessage;
import decode.p25.message.tsbk.motorola.PatchGroupAdd;
import decode.p25.message.tsbk.motorola.PatchGroupDelete;
import decode.p25.message.tsbk.motorola.PatchGroupVoiceChannelGrant;
import decode.p25.message.tsbk.motorola.PatchGroupVoiceChannelGrantUpdate;
+import decode.p25.message.tsbk.motorola.PlannedControlChannnelShutdown;
import decode.p25.message.tsbk.motorola.SystemLoading;
import decode.p25.message.tsbk.osp.control.AcknowledgeResponse;
import decode.p25.message.tsbk.osp.control.AdjacentStatusBroadcast;
@@ -203,6 +205,12 @@ public class TSBKMessageFactory
switch( motorolaOpcode )
{
+ case CCH_PLANNED_SHUTDOWN:
+ return new PlannedControlChannnelShutdown( message, duid, aliasList );
+ case CWID_UPDATE:
+ return new ChannelCWIDUpdate( message, duid, aliasList );
+ case CONGESTION_HOLD_OFF_TIMER:
+ return new CongestionHoldOffTimer( message, duid, aliasList );
case PATCH_GROUP_ADD:
return new PatchGroupAdd( message, duid, aliasList );
case PATCH_GROUP_DELETE:
@@ -211,8 +219,6 @@ public class TSBKMessageFactory
return new PatchGroupVoiceChannelGrant( message, duid, aliasList );
case PATCH_GROUP_CHANNEL_GRANT_UPDATE:
return new PatchGroupVoiceChannelGrantUpdate( message, duid, aliasList );
- case CWID_UPDATE:
- return new ChannelCWIDUpdate( message, duid, aliasList );
case SYSTEM_LOAD:
return new SystemLoading( message, duid, aliasList );
default:
diff --git a/src/decode/p25/message/tsbk/motorola/CongestionHoldOffTimer.java b/src/decode/p25/message/tsbk/motorola/CongestionHoldOffTimer.java
new file mode 100644
index 00000000..72ff29c1
--- /dev/null
+++ b/src/decode/p25/message/tsbk/motorola/CongestionHoldOffTimer.java
@@ -0,0 +1,32 @@
+package decode.p25.message.tsbk.motorola;
+
+import alias.AliasList;
+import bits.BinaryMessage;
+import decode.p25.reference.DataUnitID;
+
+public class CongestionHoldOffTimer extends MotorolaTSBKMessage
+{
+ public CongestionHoldOffTimer( BinaryMessage message,
+ DataUnitID duid,
+ AliasList aliasList )
+ {
+ super( message, duid, aliasList );
+ }
+
+ @Override
+ public String getEventType()
+ {
+ return MotorolaOpcode.CONGESTION_HOLD_OFF_TIMER.getDescription();
+ }
+
+ public String getMessage()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( getMessageStub() );
+
+
+
+ return sb.toString();
+ }
+}
diff --git a/src/decode/p25/message/tsbk/motorola/MotorolaOpcode.java b/src/decode/p25/message/tsbk/motorola/MotorolaOpcode.java
index e7d09b90..48cedf59 100644
--- a/src/decode/p25/message/tsbk/motorola/MotorolaOpcode.java
+++ b/src/decode/p25/message/tsbk/motorola/MotorolaOpcode.java
@@ -7,7 +7,7 @@ public enum MotorolaOpcode
PATCH_GROUP_CHANNEL_GRANT( "PATCH VOICE CHANNEL GRANT", "PATCH VOICE CHANNEL GRANT", 0x02 ),
PATCH_GROUP_CHANNEL_GRANT_UPDATE( "PATCH VOICE CHANNEL UPDATE", "PATCH VOICE CHANNEL GRANT UPDATE", 0x03 ),
OP04( "MOT OP04:UNKNOWN", "Opcode 0x04 Unknown", 0x04 ),
- OP05( "MOT OP05:UNKNOWN ", "Opcode 0x05 Unknown", 0x05 ),
+ CONGESTION_HOLD_OFF_TIMER( "CONGESTION HOLD-OFF TIMER", "CONGESTION HOLD-OFF TIMER", 0x05 ),
OP06( "MOT OP06:UNKNOWN ", "Opcode 0x06 Unknown", 0x06 ),
OP07( "MOT OP07:UNKNOWN ", "Opcode 0x07 Unknown", 0x07 ),
OP08( "MOT OP08:UNKNOWN ", "Opcode 0x08 Unknown", 0x08 ),
@@ -16,7 +16,7 @@ public enum MotorolaOpcode
CWID_UPDATE( "FCC CWID UPDATE", "FCC CWID CALLSIGN", 0x0B ),
OP0C( "MOT OP0C:UNKNOWN ", "Opcode 0x0C Unknown", 0x0C ),
OP0D( "MOT OP0D:UNKNOWN ", "Opcode 0x0D Unknown", 0x0D ),
- OP0E( "MOT OP0E:UNKNOWN ", "Opcode 0x0E Unknown", 0x0E ),
+ CCH_PLANNED_SHUTDOWN( "PLANNED CONTROL CHANNEL SHUTDOWN ", "PLANNED CONTROL CHANNEL SHUTDOWN", 0x0E ),
OP0F( "MOT OP0F:UNKNOWN ", "Opcode 0x0F Unknown", 0x0F ),
OP10( "MOT OP10:UNKNOWN ", "Opcode 0x10 Unknown", 0x10 ),
OP11( "MOT OP11:UNKNOWN ", "Opcode 0x11 Unknown", 0x11 ),
diff --git a/src/decode/p25/message/tsbk/motorola/PlannedControlChannnelShutdown.java b/src/decode/p25/message/tsbk/motorola/PlannedControlChannnelShutdown.java
new file mode 100644
index 00000000..ee2236c6
--- /dev/null
+++ b/src/decode/p25/message/tsbk/motorola/PlannedControlChannnelShutdown.java
@@ -0,0 +1,30 @@
+package decode.p25.message.tsbk.motorola;
+
+import alias.AliasList;
+import bits.BinaryMessage;
+import decode.p25.reference.DataUnitID;
+
+public class PlannedControlChannnelShutdown extends MotorolaTSBKMessage
+{
+ public PlannedControlChannnelShutdown( BinaryMessage message,
+ DataUnitID duid,
+ AliasList aliasList )
+ {
+ super( message, duid, aliasList );
+ }
+
+ @Override
+ public String getEventType()
+ {
+ return MotorolaOpcode.CCH_PLANNED_SHUTDOWN.getDescription();
+ }
+
+ public String getMessage()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( getMessageStub() );
+
+ return sb.toString();
+ }
+}
diff --git a/src/decode/passport/PassportDecoder.java b/src/decode/passport/PassportDecoder.java
index fefe7ee2..609d1644 100644
--- a/src/decode/passport/PassportDecoder.java
+++ b/src/decode/passport/PassportDecoder.java
@@ -26,6 +26,8 @@ import java.util.List;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
+import audio.AudioOutputImpl;
+import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
@@ -49,6 +51,7 @@ public class PassportDecoder extends Decoder implements Instrumentable
private LTRFSKDecoder mPassportFSKDecoder;
private MessageFramer mPassportMessageFramer;
private PassportMessageProcessor mPassportMessageProcessor;
+ private AudioOutputImpl mAudioOutput = new AudioOutputImpl( "Passport Decoder Audio Output" );
private List mAvailableTaps;
@@ -107,6 +110,8 @@ public class PassportDecoder extends Decoder implements Instrumentable
mPassportMessageProcessor = new PassportMessageProcessor( aliasList );
mPassportMessageFramer.addMessageListener( mPassportMessageProcessor );
mPassportMessageProcessor.addMessageListener( this );
+
+ addRealSampleListener( mAudioOutput );
}
@Override
@@ -148,4 +153,10 @@ public class PassportDecoder extends Decoder implements Instrumentable
mNBFMDemodulator.addListener( listener );
}
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ return mAudioOutput;
+ }
}
diff --git a/src/decode/tait/Tait1200Decoder.java b/src/decode/tait/Tait1200Decoder.java
index 22dd87b1..455ba341 100644
--- a/src/decode/tait/Tait1200Decoder.java
+++ b/src/decode/tait/Tait1200Decoder.java
@@ -29,6 +29,7 @@ import sample.Broadcaster;
import sample.real.RealSampleListener;
import source.Source.SampleType;
import alias.AliasList;
+import audio.IAudioOutput;
import bits.MessageFramer;
import bits.SyncPattern;
import decode.Decoder;
@@ -203,4 +204,11 @@ public class Tait1200Decoder extends Decoder implements Instrumentable
{
//Not implemented
}
+
+ @Override
+ public IAudioOutput getAudioOutput()
+ {
+ // Not implemented
+ return null;
+ }
}
diff --git a/src/edac/CRCP25.java b/src/edac/CRCP25.java
index f094afff..ee0eae5e 100644
--- a/src/edac/CRCP25.java
+++ b/src/edac/CRCP25.java
@@ -393,7 +393,7 @@ public class CRCP25
while( x < tdulc.size() && passes )
{
- tdulc = Galois24.checkAndCorrect( tdulc, x );
+ tdulc = Golay24.checkAndCorrect( tdulc, x );
passes = tdulc.getCRC() == CRC.PASSED;
diff --git a/src/edac/Golay23.java b/src/edac/Golay23.java
new file mode 100644
index 00000000..b1c47b81
--- /dev/null
+++ b/src/edac/Golay23.java
@@ -0,0 +1,128 @@
+package edac;
+
+import bits.BinaryMessage;
+
+public class Golay23
+{
+ public static final int MAX_CORRECTABLE_ERRORS = 3;
+
+ public static final int[] CHECKSUMS = new int[]
+ {
+ 0x63A, 0x31D, 0x7B4, 0x3DA, 0x1ED, 0x6CC, 0x366, 0x1B3,
+ 0x6E3, 0x54B, 0x49F, 0x475, 0x400, 0x200, 0x100, 0x080,
+ 0x040, 0x020, 0x010, 0x008, 0x004, 0x002, 0x001
+ };
+
+ /**
+ * Implements Golay(23,11,7) error detection and correction. Returns the
+ * number of detected errors. If the error count is less than or equal to
+ * the max correctable errors (3), then the error bits are corrected.
+ * Otherwise the message is left intact and an error count greater than 3
+ * is returned.
+ *
+ * @param frame - message frame bitset
+ * @param startIndex - first bit index of the golay protected bit sequence
+ *
+ * @return - number of detected errors
+ */
+ public static int checkAndCorrect( BinaryMessage frame, int startIndex )
+ {
+ int syndrome = getSyndrome( frame, startIndex );
+
+ /* No errors */
+ if( syndrome == 0 )
+ {
+ return 0;
+ }
+
+ BinaryMessage copy = frame.getSubMessage( startIndex, startIndex + 23 );
+
+ int index = -1;
+ int syndromeWeight = MAX_CORRECTABLE_ERRORS;
+ int errors = 0;
+
+ while( index < 23 )
+ {
+ if( index != -1 )
+ {
+ /* restore the previous flipped bit */
+ if( index > 0 )
+ {
+ copy.flip( index - 1 );
+ }
+
+ copy.flip( index );
+
+ syndromeWeight = MAX_CORRECTABLE_ERRORS - 1;
+ }
+
+ syndrome = getSyndrome( copy, 0 );
+
+ if( syndrome > 0 )
+ {
+ for( int i = 0; i < 23; i++ )
+ {
+ errors = Integer.bitCount( syndrome );
+
+ if( errors <= syndromeWeight )
+ {
+ copy.xor( 12, 11, syndrome );
+
+ copy.rotateRight( i, 0, 22 );
+
+ if( index >= 0 )
+ {
+ errors ++;
+ }
+
+ int corrected = copy.getInt( 0, 22 );
+ int original = frame.getInt( startIndex, startIndex + 22 );
+
+ int errorCount = Integer.bitCount( original ^ corrected );
+
+ if( errorCount <= 3 )
+ {
+ frame.load( startIndex, 23, corrected );
+ }
+
+ return errorCount;
+ }
+ else
+ {
+ copy.rotateLeft( 0, 22 );
+ syndrome = getSyndrome( copy, 0 );
+ }
+ }
+
+ index++;
+ }
+ }
+
+ /* Return an error count greater than 3 to indicate failed correction attempt */
+ return 4;
+ }
+
+ private static int getSyndrome( BinaryMessage frame, int startIndex )
+ {
+ int calculated = calculateChecksum( frame, startIndex );
+
+ int checksum = frame.getInt( startIndex + 12, startIndex + 22 );
+
+ return ( checksum ^ calculated );
+ }
+
+ private static int calculateChecksum( BinaryMessage frame, int startIndex )
+ {
+ int calculated = 0; //Starting value
+
+ /* Iterate the set bits and XOR running checksum with lookup value */
+ for (int i = frame.nextSetBit( startIndex );
+ i >= startIndex && i < startIndex + 12;
+ i = frame.nextSetBit( i+1 ) )
+ {
+ calculated ^= CHECKSUMS[ i - startIndex ];
+ }
+
+ return calculated;
+ }
+}
diff --git a/src/edac/Galois24.java b/src/edac/Golay24.java
similarity index 89%
rename from src/edac/Galois24.java
rename to src/edac/Golay24.java
index 04d60b02..6ec20ba7 100644
--- a/src/edac/Galois24.java
+++ b/src/edac/Golay24.java
@@ -29,9 +29,9 @@ import bits.BinaryMessage;
/**
* Galois 24/12/7 decoder
*/
-public class Galois24
+public class Golay24
{
- private final static Logger mLog = LoggerFactory.getLogger( Galois24.class );
+ private final static Logger mLog = LoggerFactory.getLogger( Golay24.class );
/**
* Galois 24/12 checksums generated by:
@@ -60,6 +60,13 @@ public class Galois24
return calculated;
}
+ /**
+ * Performs error detection and returns a corrected copy of the 24-bit
+ * message that starts at the start index.
+ * @param message - source message containing startIndex + 24 bits length
+ * @param startIndex - start of the 24-bit galois 24 protected bit set
+ * @return - corrected 24-bit galois value
+ */
public static BinaryMessage checkAndCorrect( BinaryMessage message, int startIndex )
{
boolean parityError = message.cardinality() % 2 != 0;
@@ -149,7 +156,7 @@ public class Galois24
return message;
}
-
+
private static int getSyndrome( BinaryMessage message, int startIndex )
{
int calculated = calculateChecksum( message, startIndex );
@@ -158,19 +165,4 @@ public class Galois24
return ( checksum ^ calculated );
}
-
- public static void main( String[] args )
- {
- String msg = "01001111011100100011011";
-
- BinaryMessage message = BinaryMessage.load( msg );
-
- mLog.debug( "MSG: " + message.toString() );
-
- int x = 0;
-
- message = checkAndCorrect( message, x );
-
- mLog.debug( "MSG: " + message.toString() + " Pass:" + ( message.getCRC()== CRC.PASSED ) );
- }
}
diff --git a/src/edac/Hamming15.java b/src/edac/Hamming15.java
new file mode 100644
index 00000000..00183d40
--- /dev/null
+++ b/src/edac/Hamming15.java
@@ -0,0 +1,117 @@
+package edac;
+
+import bits.BinaryMessage;
+
+public class Hamming15
+{
+ private static int[] CHECKSUMS = new int[] { 0xF,0xE,0xD,0xC,0xB,0XA,0x9,
+ 0x7,0x6,0x5,0x3 };
+
+ /**
+ * Performs error detection and correction of any single-bit errors.
+ *
+ * @param frame - binary frame containing a hamming(15,11,4) protected field
+ *
+ * @param startIndex - offset to the first bit of the field
+ *
+ * @return - 0 = no errors
+ * 1 = a single-bit error was corrected
+ * 2 = two or more errors detected - no corrections made
+ */
+ public static int checkAndCorrect( BinaryMessage frame, int startIndex )
+ {
+ int syndrome = getSyndrome( frame, startIndex );
+
+ switch( syndrome )
+ {
+ case 0:
+ return 0;
+ case 1:
+ frame.flip( startIndex + 14 ); //Parity 1
+ return 1;
+ case 2:
+ frame.flip( startIndex + 13 ); //Parity 2
+ return 1;
+ case 3:
+ frame.flip( startIndex + 10 ); //Data 1
+ return 1;
+ case 4:
+ frame.flip( startIndex + 12 ); //Parity 4
+ return 1;
+ case 5:
+ frame.flip( startIndex + 9 ); //Data 2
+ return 1;
+ case 6:
+ frame.flip( startIndex + 8 ); //Data 3
+ return 1;
+ case 7:
+ frame.flip( startIndex + 7 ); //Data 4
+ return 1;
+ case 8:
+ frame.flip( startIndex + 11 ); //Parity 8
+ return 1;
+ case 9:
+ frame.flip( startIndex + 6 ); //Data 5
+ return 1;
+ case 10:
+ frame.flip( startIndex + 5 ); //Data 6
+ return 1;
+ case 11:
+ frame.flip( startIndex + 4 ); //Data 7
+ return 1;
+ case 12:
+ frame.flip( startIndex + 3 ); //Data 8
+ return 1;
+ case 13:
+ frame.flip( startIndex + 2 ); //Data 9
+ return 1;
+ case 14:
+ frame.flip( startIndex + 1 ); //Data 10
+ return 1;
+ case 15:
+ frame.flip( startIndex ); //Data 11
+ return 1;
+ }
+
+ /* We'll never get to here */
+ return 2;
+ }
+
+ /**
+ * Calculates the checksum (Parity 8,4,2,1) for data (11 <> 1 ) bits.
+ * @param frame - frame containing hamming(15) protected word
+ * @param startIndex - start bit index of the hamming protected word
+ * @return parity value, 0 - 15
+ */
+ private static int calculateChecksum( BinaryMessage frame, int startIndex )
+ {
+ int calculated = 0; //Starting value
+
+ /* Iterate the set bits and XOR running checksum with lookup value */
+ for (int i = frame.nextSetBit( startIndex );
+ i >= startIndex && i < startIndex + 11;
+ i = frame.nextSetBit( i+1 ) )
+ {
+ calculated ^= CHECKSUMS[ i - startIndex ];
+ }
+
+ return calculated;
+ }
+
+ /**
+ * Calculates the syndrome - xor of the calculated checksum and the actual
+ * checksum.
+ *
+ * @param frame - binary frame containing a hamming(15,11,4) protected word
+ * @param startIndex - of bit 0 of the hamming protected word
+ * @return - 0 (no errors) or 1 (single bit error corrected)
+ */
+ private static int getSyndrome( BinaryMessage frame, int startIndex )
+ {
+ int calculated = calculateChecksum( frame, startIndex );
+
+ int checksum = frame.getInt( startIndex + 11, startIndex + 14 );
+
+ return ( checksum ^ calculated );
+ }
+}
diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java
index 2b51b5a1..b9b5e3ce 100644
--- a/src/gui/SDRTrunk.java
+++ b/src/gui/SDRTrunk.java
@@ -29,8 +29,11 @@ import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.ServiceLoader;
import javax.imageio.ImageIO;
+import javax.sound.sampled.spi.FormatConversionProvider;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
@@ -97,6 +100,9 @@ public class SDRTrunk
*/
mResourceManager = new ResourceManager();
+ /* Log any available audio converter plugins */
+ logAvailableAudioPlugins();
+
//Initialize the GUI
initGUI();
@@ -373,4 +379,17 @@ public class SDRTrunk
return homePath;
}
+
+ private void logAvailableAudioPlugins()
+ {
+ ServiceLoader loader =
+ ServiceLoader.load( FormatConversionProvider.class );
+
+ Iterator it = loader.iterator();
+
+ while( it.hasNext() )
+ {
+ mLog.info( "Available Audio Converter: " + it.next().getClass() );
+ }
+ }
}
diff --git a/src/instrument/gui/AudioSourceFrame.java b/src/instrument/gui/AudioSourceFrame.java
index f4761f9c..ac9d9987 100644
--- a/src/instrument/gui/AudioSourceFrame.java
+++ b/src/instrument/gui/AudioSourceFrame.java
@@ -42,6 +42,7 @@ import org.slf4j.LoggerFactory;
import source.wave.WaveSource;
import source.wave.WaveSource.PositionListener;
+import controller.ResourceManager;
import decode.Decoder;
import decode.DecoderType;
import decode.fleetsync2.Fleetsync2Decoder;
@@ -195,7 +196,9 @@ public class AudioSourceFrame extends JInternalFrame implements PositionListener
decoder = new PassportDecoder( mSource.getSampleType(), null );
break;
case P25_PHASE1:
- decoder = new P25Decoder( mSource.getSampleType(),
+ ResourceManager rm = new ResourceManager();
+
+ decoder = new P25Decoder( rm, mSource.getSampleType(),
(Modulation)mComboModulations.getSelectedItem(), null );
break;
case TAIT_1200: