P25 audio decoder integration.

Auto-discovers the jmbe ( https://github.com/DSheirer/jmbe ) library at startup and provides decode P25 Phase 1 audio
This commit is contained in:
dennis.sheirer
2015-03-29 13:25:36 +00:00
parent 52f2e3bdca
commit 023553c3bf
41 changed files with 1391 additions and 470 deletions

View File

@@ -1,28 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="lib" path="imports/commons-lang3-3.2.1.jar"/>
<classpathentry kind="lib" path="imports/jide-oss-3.5.15.jar"/>
<classpathentry kind="lib" path="imports/jsr80-1.0.1.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-arm.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-x86_64.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-x86.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-osx-x86_64.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-osx-x86.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-windows-x86_64.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-windows-x86.jar"/>
<classpathentry kind="lib" path="imports/miglayout-4.0-swing.jar"/>
<classpathentry kind="lib" path="imports/usb-api-1.0.2.jar"/>
<classpathentry kind="lib" path="imports/usb4java-1.2.0.jar"/>
<classpathentry kind="lib" path="imports/usb4java-javax-1.2.0.jar"/>
<classpathentry kind="lib" path="config"/>
<classpathentry kind="lib" path="imports/JTransforms-3.0.jar" sourcepath="/home/denny/.m2/repository/com/github/wendykierp/JTransforms/3.0/JTransforms-3.0-sources.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-ppc.jar"/>
<classpathentry kind="lib" path="imports/JLargeArrays-1.2.jar"/>
<classpathentry kind="lib" path="imports/slf4j-api-1.7.7.jar"/>
<classpathentry kind="lib" path="imports/logback-classic-1.1.2.jar"/>
<classpathentry kind="lib" path="imports/logback-core-1.1.2.jar"/>
<classpathentry kind="lib" path="imports/commons-io-2.4.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="lib" path="imports/commons-lang3-3.2.1.jar"/>
<classpathentry kind="lib" path="imports/jide-oss-3.5.15.jar"/>
<classpathentry kind="lib" path="imports/jsr80-1.0.1.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-arm.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-x86_64.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-x86.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-osx-x86_64.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-osx-x86.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-windows-x86_64.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-windows-x86.jar"/>
<classpathentry kind="lib" path="imports/miglayout-4.0-swing.jar"/>
<classpathentry kind="lib" path="imports/usb-api-1.0.2.jar"/>
<classpathentry kind="lib" path="imports/usb4java-1.2.0.jar"/>
<classpathentry kind="lib" path="imports/usb4java-javax-1.2.0.jar"/>
<classpathentry kind="lib" path="config"/>
<classpathentry kind="lib" path="imports/JTransforms-3.0.jar" sourcepath="/home/denny/.m2/repository/com/github/wendykierp/JTransforms/3.0/JTransforms-3.0-sources.jar"/>
<classpathentry kind="lib" path="imports/libusb4java-1.2.0-linux-ppc.jar"/>
<classpathentry kind="lib" path="imports/JLargeArrays-1.2.jar"/>
<classpathentry kind="lib" path="imports/slf4j-api-1.7.7.jar"/>
<classpathentry kind="lib" path="imports/logback-classic-1.1.2.jar"/>
<classpathentry kind="lib" path="imports/logback-core-1.1.2.jar"/>
<classpathentry kind="lib" path="imports/commons-io-2.4.jar"/>
<classpathentry kind="lib" path="/jmbe/library/jmbe-0.1.0.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -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();

View File

@@ -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 );
}

View File

@@ -17,7 +17,7 @@
******************************************************************************/
package audio;
public interface AudioTypeListener
public interface IAudioTypeListener
{
public void setAudioType( AudioType type );
}

View File

@@ -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 );
}
}
}

View File

@@ -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 );
}

View File

@@ -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<Message>
protected Decoder mDecoder;
protected List<Recorder> mRecorders = new ArrayList<Recorder>();
protected ArrayList<EventLogger> mEventLoggers = new ArrayList<EventLogger>();
protected AudioOutput mAudioOutput;
protected AutomaticFrequencyControl mAFC;
protected ChannelState mChannelState;
protected ResourceManager mResourceManager;
@@ -109,6 +109,16 @@ public class ProcessingChain implements Listener<Message>
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<Message>
mEventLoggers.clear();
mRecorders.clear();
if( mAudioOutput != null )
{
mAudioOutput.dispose();
}
mResourceManager = null;
}
@@ -234,11 +238,6 @@ public class ProcessingChain implements Listener<Message>
updateEventLogging();
if( mAudioOutput != null )
{
mAudioOutput.setAudioPlaybackEnabled( false );
}
mRunning.set( false );
}
else
@@ -374,14 +373,6 @@ public class ProcessingChain implements Listener<Message>
/* 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<Message>
.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<Message>
return mChannel;
}
public AudioOutput getAudioOutput()
{
return mAudioOutput;
}
public Source getSource()
{
return mSource;

View File

@@ -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<Message>
new ArrayList<AuxChannelState>();
protected Broadcaster<ChangedAttribute> mChangeBroadcaster =
new Broadcaster<ChangedAttribute>();
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<Message>
setState( State.FADE );
}
public void setListener( AudioTypeListener listener )
public void setListener( IAudioTypeListener listener )
{
mAudioTypeListener = listener;
}

View File

@@ -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<Message>
@@ -65,6 +66,8 @@ public abstract class Decoder implements Listener<Message>
return mRealBroadcaster;
}
public abstract IAudioOutput getAudioOutput();
/**
* Returns a complex listener interface for connecting this decoder to a
* float stream provider

View File

@@ -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(

View File

@@ -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" );
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<Tap> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

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

View File

@@ -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

View File

@@ -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<Message>
{
@@ -36,6 +37,14 @@ public class P25MessageProcessor implements Listener<Message>
@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 */

View File

@@ -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 );
}

View File

@@ -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
}
}

View File

@@ -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<FormatConversionProvider> loader = ServiceLoader.load( FormatConversionProvider.class );
Iterator<FormatConversionProvider> 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" );
}
}

View File

@@ -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<Message>
{
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<byte[]> mIMBEFrameQueue =
new ArrayBlockingQueue<byte[]>( 100 );
private ArrayBlockingQueue<byte[]> mProcessedAudioQueue =
new ArrayBlockingQueue<byte[]>( 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 );
}
}
}
}

View File

@@ -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<byte[]> getIMBEFrames()
{
/* Set the offset to the first seed bit plus 23 to point to coset c1 */
int offset = seedBits[ 0 ] + 23;
List<byte[]> frames = new ArrayList<byte[]>();
/* 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;
}
}

View File

@@ -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 );
}
}

View File

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

View File

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

View File

@@ -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:

View File

@@ -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();
}
}

View File

@@ -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 ),

View File

@@ -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();
}
}

View File

@@ -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<Tap> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

128
src/edac/Golay23.java Normal file
View File

@@ -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;
}
}

View File

@@ -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 ) );
}
}

117
src/edac/Hamming15.java Normal file
View File

@@ -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 );
}
}

View File

@@ -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<FormatConversionProvider> loader =
ServiceLoader.load( FormatConversionProvider.class );
Iterator<FormatConversionProvider> it = loader.iterator();
while( it.hasNext() )
{
mLog.info( "Available Audio Converter: " + it.next().getClass() );
}
}
}

View File

@@ -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: