mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
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:
57
.classpath
57
.classpath
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
28
src/audio/IAudioOutput.java
Normal file
28
src/audio/IAudioOutput.java
Normal 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 );
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
******************************************************************************/
|
||||
package audio;
|
||||
|
||||
public interface AudioTypeListener
|
||||
public interface IAudioTypeListener
|
||||
{
|
||||
public void setAudioType( AudioType type );
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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" );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
52
src/decode/p25/audio/IMBEAudioFormat.java
Normal file
52
src/decode/p25/audio/IMBEAudioFormat.java
Normal 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 );
|
||||
|
||||
}
|
||||
279
src/decode/p25/audio/IMBETargetDataLine.java
Normal file
279
src/decode/p25/audio/IMBETargetDataLine.java
Normal 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
|
||||
}
|
||||
}
|
||||
48
src/decode/p25/audio/P25AudioCodecTest.java
Normal file
48
src/decode/p25/audio/P25AudioCodecTest.java
Normal 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" );
|
||||
}
|
||||
}
|
||||
333
src/decode/p25/audio/P25AudioOutput.java
Normal file
333
src/decode/p25/audio/P25AudioOutput.java
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
52
src/decode/p25/message/ldu/lc/motorola/MotorolaOpcode_0.java
Normal file
52
src/decode/p25/message/ldu/lc/motorola/MotorolaOpcode_0.java
Normal 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 );
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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 ) )
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 ),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
128
src/edac/Golay23.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
117
src/edac/Hamming15.java
Normal 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 );
|
||||
}
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user