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: