diff --git a/.classpath b/.classpath
index cecdafa7..495e4bc4 100644
--- a/.classpath
+++ b/.classpath
@@ -24,6 +24,6 @@
-
+
diff --git a/build/build.xml b/build/build.xml
index 5b068185..8eb04324 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -6,7 +6,7 @@
-
+
@@ -89,18 +89,27 @@
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
diff --git a/imports/jmbe-interface-0.2.0.jar b/imports/jmbe-interface-0.2.0.jar
new file mode 100644
index 00000000..85add8bd
Binary files /dev/null and b/imports/jmbe-interface-0.2.0.jar differ
diff --git a/scripts/run_sdrtrunk_linux b/scripts/run_sdrtrunk_linux
deleted file mode 100644
index 735cba6c..00000000
--- a/scripts/run_sdrtrunk_linux
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-java -cp "*:libs/*" gui.SDRTrunk&
\ No newline at end of file
diff --git a/scripts/run_sdrtrunk_linux.sh b/scripts/run_sdrtrunk_linux.sh
new file mode 100644
index 00000000..00355174
--- /dev/null
+++ b/scripts/run_sdrtrunk_linux.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk&
\ No newline at end of file
diff --git a/scripts/run_sdrtrunk_windows.bat b/scripts/run_sdrtrunk_windows.bat
index e911942d..1206e039 100644
--- a/scripts/run_sdrtrunk_windows.bat
+++ b/scripts/run_sdrtrunk_windows.bat
@@ -1 +1 @@
-java -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
\ No newline at end of file
+java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
\ No newline at end of file
diff --git a/src/alias/Alias.java b/src/alias/Alias.java
index 26404dc9..acccd6b5 100644
--- a/src/alias/Alias.java
+++ b/src/alias/Alias.java
@@ -24,7 +24,7 @@ import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import alias.action.AliasAction;
-import audio.AudioType;
+import audio.inverted.AudioType;
public class Alias
{
diff --git a/src/alias/AliasID.java b/src/alias/AliasID.java
index 090d091e..998896df 100644
--- a/src/alias/AliasID.java
+++ b/src/alias/AliasID.java
@@ -17,7 +17,7 @@
******************************************************************************/
package alias;
-import audio.AudioType;
+import audio.inverted.AudioType;
public abstract class AliasID
{
diff --git a/src/alias/talkgroup/TalkgroupID.java b/src/alias/talkgroup/TalkgroupID.java
index a6f79940..6fca233c 100644
--- a/src/alias/talkgroup/TalkgroupID.java
+++ b/src/alias/talkgroup/TalkgroupID.java
@@ -21,7 +21,7 @@ import javax.xml.bind.annotation.XmlAttribute;
import alias.AliasID;
import alias.AliasIDType;
-import audio.AudioType;
+import audio.inverted.AudioType;
public class TalkgroupID extends AliasID
diff --git a/src/alias/talkgroup/TalkgroupIDEditor.java b/src/alias/talkgroup/TalkgroupIDEditor.java
index 344d013c..6fb73d03 100644
--- a/src/alias/talkgroup/TalkgroupIDEditor.java
+++ b/src/alias/talkgroup/TalkgroupIDEditor.java
@@ -31,7 +31,7 @@ import javax.swing.JTextArea;
import javax.swing.JTextField;
import net.miginfocom.swing.MigLayout;
-import audio.AudioType;
+import audio.inverted.AudioType;
import controller.ConfigurableNode;
public class TalkgroupIDEditor extends JPanel implements ActionListener
diff --git a/src/audio/Audio.java b/src/audio/Audio.java
deleted file mode 100644
index 68b624f6..00000000
--- a/src/audio/Audio.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package audio;
-
-import javax.sound.sampled.AudioFormat;
-
-public class Audio
-{
- public static final AudioFormat MONO_48K_16_LE =
- new AudioFormat( 48000, //SampleRate
- 16, //Sample Size
- 1, //Channels
- true, //Signed
- false ); //Little Endian
-
- public static final AudioFormat STEREO_48K_16_LE =
- new AudioFormat( 48000, //SampleRate
- 16, //Sample Size
- 2, //Channels
- true, //Signed
- false ); //Little Endian
-}
diff --git a/src/decode/p25/audio/IMBEAudioFormat.java b/src/audio/AudioFormats.java
similarity index 96%
rename from src/decode/p25/audio/IMBEAudioFormat.java
rename to src/audio/AudioFormats.java
index 348640c7..e9ab4add 100644
--- a/src/decode/p25/audio/IMBEAudioFormat.java
+++ b/src/audio/AudioFormats.java
@@ -1,8 +1,8 @@
-package decode.p25.audio;
+package audio;
import javax.sound.sampled.AudioFormat;
-public class IMBEAudioFormat
+public class AudioFormats
{
public static final AudioFormat.Encoding IMBE_ENCODING =
new AudioFormat.Encoding( "IMBE" );
@@ -47,5 +47,4 @@ public class IMBEAudioFormat
PCM_FRAME_SIZE_BYTES,
PCM_48KHZ_RATE,
LITTLE_ENDIAN );
-
}
diff --git a/src/audio/AudioOutputImpl.java b/src/audio/AudioOutputImpl.java
index ae833592..78d26ad0 100644
--- a/src/audio/AudioOutputImpl.java
+++ b/src/audio/AudioOutputImpl.java
@@ -28,6 +28,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.real.RealSampleListener;
+import audio.inverted.AudioAdapter;
+import audio.inverted.AudioType;
+import audio.inverted.IAudioTypeListener;
import buffer.FloatSampleBufferAssembler;
public class AudioOutputImpl implements RealSampleListener,
diff --git a/src/audio/AudioPacket.java b/src/audio/AudioPacket.java
index faa232c1..17de2efa 100644
--- a/src/audio/AudioPacket.java
+++ b/src/audio/AudioPacket.java
@@ -1,24 +1,49 @@
package audio;
-import java.nio.ByteBuffer;
-
public class AudioPacket
{
- private ByteBuffer mAudioBuffer;
+ private String mSource;
+ private Type mType;
+ private byte[] mAudioData;
private int mPriority;
- public AudioPacket()
+ public AudioPacket( String source, Type type )
{
+ mSource = source;
+ mType = type;
}
- public void setAudioBuffer( ByteBuffer audioBuffer )
+ public AudioPacket( String source, byte[] audio, int priority )
{
- mAudioBuffer = audioBuffer;
+ this( source, Type.AUDIO );
+
+ mAudioData = audio;
+ mPriority = priority;
}
- public ByteBuffer getAudioBuffer()
+ public String getSource()
{
- return mAudioBuffer;
+ return mSource;
+ }
+
+ public Type getType()
+ {
+ return mType;
+ }
+
+ public byte[] getAudioData()
+ {
+ return mAudioData;
+ }
+
+ public int getPriority()
+ {
+ return mPriority;
}
+ public enum Type
+ {
+ AUDIO,
+ END;
+ }
}
diff --git a/src/audio/IAudioOutput.java b/src/audio/IAudioOutput.java
index f9add19c..790f45ee 100644
--- a/src/audio/IAudioOutput.java
+++ b/src/audio/IAudioOutput.java
@@ -1,6 +1,7 @@
package audio;
import audio.SquelchListener.SquelchState;
+import audio.inverted.AudioType;
public interface IAudioOutput
{
diff --git a/src/audio/IAudioPacketReceiver.java b/src/audio/IAudioPacketReceiver.java
deleted file mode 100644
index 1bab1254..00000000
--- a/src/audio/IAudioPacketReceiver.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package audio;
-
-public interface IAudioPacketReceiver
-{
-
-}
diff --git a/src/audio/MonoAudioOutput.java b/src/audio/MonoAudioOutput.java
index f771a709..2148509f 100644
--- a/src/audio/MonoAudioOutput.java
+++ b/src/audio/MonoAudioOutput.java
@@ -1,12 +1,183 @@
package audio;
-public class MonoAudioOutput
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.SourceDataLine;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import sample.Listener;
+import audio.AudioPacket.Type;
+
+/**
+ * Audio output with automatic flow control based on the availability of
+ * audio data packets.
+ */
+public class MonoAudioOutput implements Listener
{
- /**
- * Buffered
- */
+ private final static Logger mLog = LoggerFactory.getLogger( MonoAudioOutput.class );
+
+ /* Output line buffer size is set at one second of audio at 48 kHz and
+ * two bytes per sample */
+ private final static int BUFFER_SIZE = 48000;
+
+ /* The queue processor will run every 20 milliseconds checking for inbound
+ * audio and automatically starting or stopping audio playback. We set the
+ * data line available space threshold to start when we're 20 ms away from
+ * being full, and the stop threshold when 20 ms away from being empty */
+ private final static int SAMPLE_SIZE_40_MS = 48000 / 25;
+ private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS;
+ private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
+
+ private LinkedTransferQueue mBuffer = new LinkedTransferQueue<>();
+
+ private ScheduledExecutorService mExecutorService;
+
+ private SourceDataLine mOutput;
+
+ private boolean mCanProcessAudio;
+
+ private AtomicBoolean mMuted = new AtomicBoolean( true );
+ private AtomicBoolean mSquelched = new AtomicBoolean( false );
+
public MonoAudioOutput()
{
+ try
+ {
+ mOutput = AudioSystem.getSourceDataLine(
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS );
+
+ if( mOutput != null )
+ {
+ mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS, BUFFER_SIZE );
+
+ mCanProcessAudio = true;
+
+ mExecutorService = Executors.newSingleThreadScheduledExecutor();
+
+ /* Run the queue processor task every 40 milliseconds */
+ mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
+ 0, 40, TimeUnit.MILLISECONDS );
+ }
+ }
+ catch ( LineUnavailableException e )
+ {
+ mLog.error( "Couldn't obtain source data line for 48kHz PCM mono "
+ + "audio output" );
+ }
+ }
+
+ public void setMuted( boolean muted )
+ {
+ mMuted.set( muted );
+ }
+
+ public void setSquelched( boolean squelched )
+ {
+ mSquelched.set( squelched );
+ }
+
+
+ public void dispose()
+ {
+ mCanProcessAudio = false;
+
+ mBuffer.clear();
+ if( mExecutorService != null )
+ {
+ mExecutorService.shutdown();
+ }
+
+ if( mOutput != null )
+ {
+ mOutput.close();
+ }
+ }
+
+ @Override
+ public void receive( AudioPacket packet )
+ {
+ if( mCanProcessAudio )
+ {
+ mBuffer.add( packet );
+ }
+ }
+
+ public class QueueProcessor implements Runnable
+ {
+ private AtomicBoolean mProcessing = new AtomicBoolean();
+
+ public QueueProcessor()
+ {
+ }
+
+ @Override
+ public void run()
+ {
+ /* The mProcessing flag ensures that only one instance of the
+ * processor can run at any given time */
+ if( mProcessing.compareAndSet( false, true ) )
+ {
+ List packets = new ArrayList();
+
+ mBuffer.drainTo( packets );
+
+ if( packets.size() > 0 )
+ {
+ for( AudioPacket packet: packets )
+ {
+ if( mCanProcessAudio )
+ {
+ if( mMuted.get() || mSquelched.get() )
+ {
+ checkStop();
+ }
+ else if( packet.getType() == Type.AUDIO )
+ {
+ mOutput.write( packet.getAudioData(), 0,
+ packet.getAudioData().length );
+
+ checkStart();
+ }
+ }
+ }
+ }
+ else
+ {
+ checkStop();
+ }
+
+ mProcessing.set( false );
+ }
+ }
+
+ private void checkStart()
+ {
+ if( mCanProcessAudio && !mMuted.get() && !mSquelched.get() &&
+ !mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
+ {
+ mOutput.start();
+ }
+ }
+
+ private void checkStop()
+ {
+ if( mCanProcessAudio && mOutput.isRunning() &&
+ ( mOutput.available() >= STOP_THRESHOLD || mMuted.get() || mSquelched.get() ) )
+ {
+ mOutput.drain();
+ mOutput.stop();
+ }
+ }
}
}
diff --git a/src/audio/AudioAdapter.java b/src/audio/inverted/AudioAdapter.java
similarity index 99%
rename from src/audio/AudioAdapter.java
rename to src/audio/inverted/AudioAdapter.java
index 1cfa9377..2d07a4e8 100644
--- a/src/audio/AudioAdapter.java
+++ b/src/audio/inverted/AudioAdapter.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
******************************************************************************/
-package audio;
+package audio.inverted;
import util.Oscillator;
import dsp.filter.FilterFactory;
diff --git a/src/audio/AudioInverter.java b/src/audio/inverted/AudioInverter.java
similarity index 97%
rename from src/audio/AudioInverter.java
rename to src/audio/inverted/AudioInverter.java
index 70898f73..26408556 100644
--- a/src/audio/AudioInverter.java
+++ b/src/audio/inverted/AudioInverter.java
@@ -15,8 +15,9 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
******************************************************************************/
-package audio;
+package audio.inverted;
+import audio.InversionFrequency;
import sample.real.RealSampleListener;
import util.Oscillator;
import dsp.filter.Filters;
diff --git a/src/audio/AudioType.java b/src/audio/inverted/AudioType.java
similarity index 99%
rename from src/audio/AudioType.java
rename to src/audio/inverted/AudioType.java
index 643f5ce5..706ee683 100644
--- a/src/audio/AudioType.java
+++ b/src/audio/inverted/AudioType.java
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
******************************************************************************/
-package audio;
+package audio.inverted;
public enum AudioType
{
diff --git a/src/audio/IAudioTypeListener.java b/src/audio/inverted/IAudioTypeListener.java
similarity index 97%
rename from src/audio/IAudioTypeListener.java
rename to src/audio/inverted/IAudioTypeListener.java
index 92c794db..52321dcf 100644
--- a/src/audio/IAudioTypeListener.java
+++ b/src/audio/inverted/IAudioTypeListener.java
@@ -15,7 +15,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
******************************************************************************/
-package audio;
+package audio.inverted;
+
public interface IAudioTypeListener
{
diff --git a/src/controller/channel/ProcessingChain.java b/src/controller/channel/ProcessingChain.java
index 92a0a9c7..38a54674 100644
--- a/src/controller/channel/ProcessingChain.java
+++ b/src/controller/channel/ProcessingChain.java
@@ -52,8 +52,8 @@ import source.tuner.frequency.AutomaticFrequencyControl;
import source.tuner.frequency.FrequencyCorrectionControl;
import alias.AliasList;
import audio.IAudioOutput;
-import audio.IAudioTypeListener;
import audio.SquelchListener;
+import audio.inverted.IAudioTypeListener;
import controller.ResourceManager;
import controller.ThreadPoolManager.ThreadType;
import controller.activity.CallEvent;
diff --git a/src/controller/state/ChannelState.java b/src/controller/state/ChannelState.java
index 766693bc..3f395086 100644
--- a/src/controller/state/ChannelState.java
+++ b/src/controller/state/ChannelState.java
@@ -35,10 +35,10 @@ import properties.SystemProperties;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
-import audio.AudioType;
-import audio.IAudioTypeListener;
import audio.SquelchListener;
import audio.SquelchListener.SquelchState;
+import audio.inverted.AudioType;
+import audio.inverted.IAudioTypeListener;
import controller.activity.CallEvent;
import controller.activity.CallEvent.CallEventType;
import controller.activity.CallEventAliasCellRenderer;
@@ -294,7 +294,7 @@ public abstract class ChannelState implements Listener
{
/* Let all CALL state changes go through in order to update the fade
* timer, otherwise, don't update the state if it hasn't changed */
- if( mState != state || state == State.CALL )
+ if( mState != state || state == State.CALL || state == State.DATA || state == State.ENCRYPTED )
{
if( mState.canChangeTo( state ) )
{
@@ -314,6 +314,10 @@ public abstract class ChannelState implements Listener
mTimerService.stopResetTimer();
mTimerService.startFadeTimer();
break;
+ case ENCRYPTED:
+ setSquelchState( SquelchState.SQUELCH );
+ mTimerService.stopResetTimer();
+ mTimerService.startFadeTimer();
case NO_TUNER:
mTimerService.stopResetTimer();
if( getProcessingChain().getChannel()
@@ -569,12 +573,7 @@ public abstract class ChannelState implements Listener
@Override
public boolean canChangeTo( State state )
{
- return state == IDLE ||
- state == CALL ||
- state == CONTROL ||
- state == DATA ||
- state == FADE ||
- state == NO_TUNER;
+ return true;
}
},
CALL( "CALL" ) {
@@ -584,6 +583,7 @@ public abstract class ChannelState implements Listener
return state == CALL ||
state == CONTROL ||
state == DATA ||
+ state == ENCRYPTED ||
state == FADE;
}
},
@@ -594,6 +594,7 @@ public abstract class ChannelState implements Listener
return state == CALL ||
state == CONTROL ||
state == DATA ||
+ state == State.ENCRYPTED ||
state == FADE;
}
},
diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java
index 63cb42f7..57b3c89e 100644
--- a/src/decode/p25/P25Decoder.java
+++ b/src/decode/p25/P25Decoder.java
@@ -42,10 +42,12 @@ import dsp.filter.ComplexFIRFilter;
import dsp.filter.FilterFactory;
import dsp.filter.FloatFIRFilter;
import dsp.filter.Window.WindowType;
+import dsp.filter.equalizer.CMAEqualizer;
import dsp.gain.ComplexFeedForwardGainControl;
import dsp.gain.DirectGainControl;
import dsp.nbfm.FMDiscriminator;
import dsp.psk.CQPSKDemodulator;
+import dsp.psk.QPSKPolarSlicer;
import dsp.psk.QPSKStarSlicer;
public class P25Decoder extends Decoder implements Instrumentable
@@ -76,16 +78,16 @@ public class P25Decoder extends Decoder implements Instrumentable
/* Gain */
private ComplexFeedForwardGainControl mAGC;
private DirectGainControl mDGC;
+ private CMAEqualizer mEqualizer;
/* Filters */
private ComplexFIRFilter mBasebandFilter;
- private ComplexFIRFilter mRootRaisedCosineFilter;
private FloatFIRFilter mAudioFilter;
private C4FMSymbolFilter mSymbolFilter;
/* Slicers */
private C4FMSlicer mC4FMSlicer;
- private QPSKStarSlicer mCQPSKSlicer;
+ private QPSKPolarSlicer mCQPSKSlicer;
/* Message framers and processors */
private P25MessageFramer mMessageFramer;
@@ -134,20 +136,17 @@ public class P25Decoder extends Decoder implements Instrumentable
48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0 );
this.addComplexListener( mBasebandFilter );
+
+// mEqualizer = new CMAEqualizer( 1.0f, 0.005f );
+// mBasebandFilter.setListener( mEqualizer );
mAGC = new ComplexFeedForwardGainControl( 32 );
+// mEqualizer.setListener( mAGC );
mBasebandFilter.setListener( mAGC );
- /* Root raised cosine filter using 34 symbol periods and 10
- * samples per symbol with a roll-off (alpha) value of 0.2.
- * This should produce a filter with 341 coefficients. */
- mRootRaisedCosineFilter = new ComplexFIRFilter( FilterFactory
- .getRootRaisedCosine( 10, 34, 0.2 ), 1.0 );
- mAGC.setListener( mRootRaisedCosineFilter );
-
- mRootRaisedCosineFilter.setListener( mCQPSKDemodulator );
+ mAGC.setListener( mCQPSKDemodulator );
- mCQPSKSlicer = new QPSKStarSlicer();
+ mCQPSKSlicer = new QPSKPolarSlicer();
mCQPSKDemodulator.setListener( mCQPSKSlicer );
mCQPSKSlicer.addListener( mMessageFramer );
@@ -267,7 +266,7 @@ public class P25Decoder extends Decoder implements Instrumentable
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
ComplexTap baseband = (ComplexTap)tap;
mBasebandFilter.setListener( baseband );
- baseband.setListener( mAGC );
+ baseband.setListener( mEqualizer );
break;
case INSTRUMENT_AGC_OUTPUT:
ComplexTap agcSymbol = (ComplexTap)tap;
diff --git a/src/decode/p25/P25MessageFramer.java b/src/decode/p25/P25MessageFramer.java
index 7d02e706..033a3a84 100644
--- a/src/decode/p25/P25MessageFramer.java
+++ b/src/decode/p25/P25MessageFramer.java
@@ -43,8 +43,8 @@ public class P25MessageFramer implements Listener
LoggerFactory.getLogger( P25MessageFramer.class );
/* Determines the threshold for sync pattern soft matching */
- private static final int SYNC_MATCH_THRESHOLD = 4;
- private static final int SYNC_IN_CALL_THRESHOLD = 8;
+ private static final int SYNC_MATCH_THRESHOLD = 2;
+ private static final int SYNC_IN_CALL_THRESHOLD = 4;
private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD );
diff --git a/src/decode/p25/audio/IMBETargetDataLine.java b/src/decode/p25/audio/IMBETargetDataLine.java
index b3bfd088..041e233f 100644
--- a/src/decode/p25/audio/IMBETargetDataLine.java
+++ b/src/decode/p25/audio/IMBETargetDataLine.java
@@ -12,6 +12,8 @@ import javax.sound.sampled.TargetDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import audio.AudioFormats;
+
public class IMBETargetDataLine implements TargetDataLine
{
private final static Logger mLog =
@@ -35,7 +37,7 @@ public class IMBETargetDataLine implements TargetDataLine
public void open( AudioFormat format, int bufferSize )
throws LineUnavailableException
{
- if( format != IMBEAudioFormat.IMBE_AUDIO_FORMAT )
+ if( format != AudioFormats.IMBE_AUDIO_FORMAT )
{
throw new LineUnavailableException( "Unsupported format" );
}
@@ -44,7 +46,7 @@ public class IMBETargetDataLine implements TargetDataLine
@Override
public void open() throws LineUnavailableException
{
- open( IMBEAudioFormat.IMBE_AUDIO_FORMAT, BUFFER_SIZE );
+ open( AudioFormats.IMBE_AUDIO_FORMAT, BUFFER_SIZE );
}
@Override
@@ -161,7 +163,7 @@ public class IMBETargetDataLine implements TargetDataLine
@Override
public AudioFormat getFormat()
{
- return IMBEAudioFormat.IMBE_AUDIO_FORMAT;
+ return AudioFormats.IMBE_AUDIO_FORMAT;
}
/**
@@ -227,7 +229,7 @@ public class IMBETargetDataLine implements TargetDataLine
public javax.sound.sampled.Line.Info getLineInfo()
{
return new DataLine.Info( IMBETargetDataLine.class,
- IMBEAudioFormat.IMBE_AUDIO_FORMAT );
+ AudioFormats.IMBE_AUDIO_FORMAT );
}
/**
diff --git a/src/decode/p25/audio/P25AudioCodecTest.java b/src/decode/p25/audio/P25AudioCodecTest.java
index 56640fed..c35f330d 100644
--- a/src/decode/p25/audio/P25AudioCodecTest.java
+++ b/src/decode/p25/audio/P25AudioCodecTest.java
@@ -11,6 +11,8 @@ import javax.sound.sampled.spi.FormatConversionProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import audio.AudioFormats;
+
public class P25AudioCodecTest
{
private final static Logger mLog = LoggerFactory.getLogger( P25AudioCodecTest.class );
@@ -18,8 +20,8 @@ public class P25AudioCodecTest
public static void main( String[] args )
{
mLog.debug( "Starting ..." );
- AudioFormat from = IMBEAudioFormat.IMBE_AUDIO_FORMAT;
- AudioFormat to = IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS;
+ AudioFormat from = AudioFormats.IMBE_AUDIO_FORMAT;
+ AudioFormat to = AudioFormats.PCM_SIGNED_8KHZ_16BITS;
ServiceLoader loader = ServiceLoader.load( FormatConversionProvider.class );
diff --git a/src/decode/p25/audio/P25AudioOutput.java b/src/decode/p25/audio/P25AudioOutput.java
index 70657d86..51af5cb2 100644
--- a/src/decode/p25/audio/P25AudioOutput.java
+++ b/src/decode/p25/audio/P25AudioOutput.java
@@ -1,128 +1,85 @@
package decode.p25.audio;
-import java.io.IOException;
-import java.util.concurrent.LinkedTransferQueue;
-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.Line;
-import javax.sound.sampled.LineUnavailableException;
-import javax.sound.sampled.Mixer;
-import javax.sound.sampled.SourceDataLine;
-
+import jmbe.iface.AudioConversionLibrary;
+import jmbe.iface.AudioConverter;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
-import audio.AudioType;
+import audio.AudioFormats;
+import audio.AudioPacket;
import audio.IAudioOutput;
-import audio.IAudioTypeListener;
+import audio.MonoAudioOutput;
import audio.SquelchListener;
+import audio.inverted.AudioType;
+import audio.inverted.IAudioTypeListener;
import controller.ResourceManager;
-import controller.ThreadPoolManager;
-import controller.ThreadPoolManager.ThreadType;
import decode.p25.message.ldu.LDUMessage;
+import decode.p25.message.tdu.TDUMessage;
+import decode.p25.message.tdu.lc.TDULinkControlMessage;
/**
- * IMBE Audio Output converter. Processes 18-byte IMBE audio frames and
- * produces PCM 48k MONO 16-bit sample output.
+ * P25 Phase 1 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.
+ * Note: this class depends on the external JMBE library.
*/
-public class P25AudioOutput implements IAudioOutput, IAudioTypeListener,
- SquelchListener, Listener
+public class P25AudioOutput implements IAudioOutput, Listener,
+ SquelchListener, IAudioTypeListener
{
private final static Logger mLog = LoggerFactory.getLogger( P25AudioOutput.class );
+
+ private static final String IMBE_CODEC = "IMBE";
- /* 1 IMBE frame = 160 samples/320 bytes @ 8kHz or 1920 bytes at 48kHz */
- private static final int PROCESSED_8KHZ_AUDIO_FRAME_SIZE = 320;
- private static final int PROCESSED_48KHZ_AUDIO_FRAME_SIZE = 1920;
- private int mFrameSize = 1920;
-
- private IMBETargetDataLine mIMBETargetDataLine = new IMBETargetDataLine();
- private SourceDataLine mOutput;
- private AudioInputStream mIMBEToPCMConverter;
- private LinkedTransferQueue mIMBEFrameQueue =
- new LinkedTransferQueue();
- private LinkedTransferQueue mProcessedAudioQueue =
- new LinkedTransferQueue();
-
- private AtomicBoolean mConverting = new AtomicBoolean();
- private AtomicBoolean mDispatching = new AtomicBoolean();
- private AtomicBoolean mEnabled = new AtomicBoolean();
- private boolean mCanProcessAudio = false;
+ private boolean mCanConvertAudio = false;
private boolean mEncryptedAudio = false;
- private ThreadPoolManager mThreadPoolManager;
+ private AudioConverter mAudioConverter;
+
+ private MonoAudioOutput mAudioOutput = new MonoAudioOutput();
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(), 40, TimeUnit.MILLISECONDS );
- }
}
/**
* Primary inject point for p25 imbe audio frame messages. Each LDU audio
- * message contains 9 imbe voice frames.
+ * message contains 9 imbe voice frames. If the audio is not encrypted,
+ * we insert each audio frame asynchronously into the audio converter.
*/
public void receive( Message message )
{
- if( mCanProcessAudio )
+ if( mCanConvertAudio )
{
if( message instanceof LDUMessage )
{
LDUMessage ldu = (LDUMessage)message;
- /* Check valid ldu frames for encryption. Set state to encrypted
- * so that no encrypted frames are sent to the converter */
- if( ldu.isValid() )
+ /* If we detect a valid LDU message with the encryption flag
+ * set to true, we toggle the encrypted audio state until we
+ * receive the first TDU or TDULC message, then reset it. */
+ if( ldu.isValid() && ldu.isEncrypted() )
{
- mEncryptedAudio = ldu.isEncrypted();
+ mEncryptedAudio = true;
}
- if( !mEncryptedAudio )
+ if( !mEncryptedAudio && mAudioConverter != null )
{
for( byte[] frame: ((LDUMessage)message).getIMBEFrames() )
{
- if( !mIMBEFrameQueue.offer( frame ) )
- {
- mLog.debug( "IMBE frame queue full - throwing away "
- + "imbe audio frame" );
- }
+ byte[] audio = mAudioConverter.convert( frame );
+
+ mAudioOutput.receive( new AudioPacket( "P25 Audio", audio, 1 ) );
}
}
}
- }
- }
-
- /**
- * 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 );
+ else if( message instanceof TDUMessage || message instanceof TDULinkControlMessage )
+ {
+ mEncryptedAudio = false;
+ }
}
}
@@ -133,147 +90,60 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener,
*/
private void loadConverter()
{
- try
- {
- mOutput = AudioSystem.getSourceDataLine(
- IMBEAudioFormat.PCM_SIGNED_48KHZ_16BITS );
-
- if( mOutput != null )
- {
- mFrameSize = PROCESSED_48KHZ_AUDIO_FRAME_SIZE;
-
- /* Open the audio line with room for two buffers */
- mOutput.open( IMBEAudioFormat.PCM_SIGNED_48KHZ_16BITS,
- mFrameSize * 2 );
-
- mCanProcessAudio = true;
- }
- else
- {
- mLog.debug( "Couldn't obtain a 48kHz source data line "
- + "- attempting 8kHz rate" );
-
- mOutput = AudioSystem.getSourceDataLine(
- IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS );
-
- if( mOutput != null )
- {
- mFrameSize = PROCESSED_8KHZ_AUDIO_FRAME_SIZE;
-
- /* Open the audio line with room for two buffers */
- mOutput.open( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS,
- mFrameSize * 2 );
-
- mCanProcessAudio = true;
- }
- else
- {
- mLog.error( "Couldn't obtain a 48 kHz or 8 kHz "
- + "source data line - no decoded IMBE audio "
- + "will be available" );
-
- logSourceLines();
- }
- }
- }
- catch ( LineUnavailableException lue )
- {
- mLog.error( "AudioOutput - couldn't open audio output line "
- + "for playback", lue );
- }
- catch( IllegalArgumentException iae )
- {
- mLog.error( "Error fetching or opening audio output for "
- + "48kHz or 8kHz 16 bit audio", iae );
- }
- catch( Exception e )
- {
- mLog.error( "Error with audio output", e );
- }
-
- if( mCanProcessAudio )
- {
- try
- {
- if( mOutput.getFormat().getSampleRate() == IMBEAudioFormat.PCM_48KHZ_RATE )
- {
- mIMBEToPCMConverter = AudioSystem
- .getAudioInputStream( IMBEAudioFormat.PCM_SIGNED_48KHZ_16BITS,
- new AudioInputStream( mIMBETargetDataLine ) );
- }
- else
- {
- mIMBEToPCMConverter = AudioSystem
- .getAudioInputStream( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS,
- new AudioInputStream( mIMBETargetDataLine ) );
- }
-
- if( mIMBEToPCMConverter != null )
- {
- mLog.info( "IMBE audio converter library loaded successfully" );
- }
- else
- {
- mLog.info( "could not load IMBE audio converter library" );
- }
- }
- catch( IllegalArgumentException iae )
- {
- mLog.error( "could NOT find/load IMBE audio converter library", iae );
- }
- }
- }
-
- private void logSourceLines()
- {
- Mixer.Info[] infos = AudioSystem.getMixerInfo();
+ AudioConversionLibrary library = null;
- for( Mixer.Info info: AudioSystem.getMixerInfo() )
+ try
{
- Mixer mixer = AudioSystem.getMixer( info );
+ @SuppressWarnings( "rawtypes" )
+ Class temp = Class.forName( "jmbe.JMBEAudioLibrary" );
- Line.Info[] lineInfos = mixer.getSourceLineInfo();
+ library = (AudioConversionLibrary)temp.newInstance();
+
+ mAudioConverter = library.getAudioConverter( IMBE_CODEC,
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS );
- for( Line.Info lineInfo: lineInfos )
+ if( mAudioConverter != null )
{
- mLog.debug( "Source Line: " + lineInfo.toString() );
+ mCanConvertAudio = true;
+ mLog.info( "JMBE audio conversion library successfully loaded"
+ + " - P25 audio will be available" );
}
+ else
+ {
+ mLog.info( "JMBE audio conversion library NOT FOUND" );
+ }
+ }
+ catch ( ClassNotFoundException e1 )
+ {
+ mLog.error( "Couldn't find/load JMBE audio conversion library", e1 );
+ }
+ catch ( InstantiationException e1 )
+ {
+ mLog.error( "Couldn't instantiate JMBE audio conversion library class" );
+ }
+ catch ( IllegalAccessException e1 )
+ {
+ mLog.error( "Couldn't load JMBE audio conversion library due to "
+ + "security restrictions" );
}
}
- @Override
- public void setSquelch( SquelchState state )
- {
- /* Ignored */
- }
-
@Override
public void setAudioPlaybackEnabled( boolean enabled )
{
- mEnabled.set( enabled );
+ mAudioOutput.setMuted( !enabled );
}
@Override
public void dispose()
{
- mCanProcessAudio = false;
-
-
- mIMBEFrameQueue.clear();
- mProcessedAudioQueue.clear();
+ mAudioOutput.dispose();
+ }
- mConverting.set( false );
- mDispatching.set( false );
-
- mOutput.close();
- mOutput = null;
-
- mIMBETargetDataLine.close();
- mIMBETargetDataLine = null;
-
-
- // TODO Auto-generated method stub
-
+ @Override
+ public void setSquelch( SquelchState state )
+ {
+ /* not implemented */
}
@Override
@@ -281,122 +151,4 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener,
{
//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 one frame of converted audio from output */
- byte[] audio = new byte[ mFrameSize ];
-
- try
- {
- 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 < 4 )
- {
- writeAudioFrame();
- }
-
- mLog.debug( "Stopping ..." );
-
- mOutput.stop();
-
- mDispatching.set( false );
- }
- }
-
- private void writeAudioFrame()
- {
- byte[] audio = mProcessedAudioQueue.poll();
-
- if( audio == null )
- {
- mEmptyFrameCount++;
- mLog.debug( " ... inserting empty" );
- }
- else
- {
- mEmptyFrameCount = 0;
- }
-
- /* Send empty audio if we're not currently enabled to playback */
- if( audio == null || !mEnabled.get() )
- {
- audio = new byte[ mFrameSize ];
- }
-
- if( mOutput != null )
- {
- mOutput.write( audio, 0, audio.length );
- }
- }
- }
-}
-
+}
\ No newline at end of file
diff --git a/src/decode/p25/reference/Encryption.java b/src/decode/p25/reference/Encryption.java
index 773f9eb3..2606c597 100644
--- a/src/decode/p25/reference/Encryption.java
+++ b/src/decode/p25/reference/Encryption.java
@@ -13,6 +13,7 @@ public enum Encryption
TRIPLE_DES_2_KEY( 0x82 ),
TRIPLE_DES_3_KEY( 0x83 ),
AES_256( 0x84 ),
+ AES_CBC( 0x85 ),
DES_XL( 0x9F ), /* Motorola Proprietary */
DVI_XL( 0xA0 ), /* Motorola Proprietary */
DVP_XL( 0xA1 ), /* Motorola Proprietary */
diff --git a/src/decode/passport/PassportChannelState.java b/src/decode/passport/PassportChannelState.java
index 39131b3b..f64d34ea 100644
--- a/src/decode/passport/PassportChannelState.java
+++ b/src/decode/passport/PassportChannelState.java
@@ -27,8 +27,8 @@ import org.slf4j.LoggerFactory;
import message.Message;
import alias.Alias;
import alias.AliasList;
-import audio.AudioType;
import audio.SquelchListener;
+import audio.inverted.AudioType;
import controller.activity.CallEvent;
import controller.activity.CallEvent.CallEventType;
import controller.channel.ProcessingChain;
diff --git a/src/dsp/psk/CQPSKDemodulator.java b/src/dsp/psk/CQPSKDemodulator.java
index 91ba9870..19a9c2fc 100644
--- a/src/dsp/psk/CQPSKDemodulator.java
+++ b/src/dsp/psk/CQPSKDemodulator.java
@@ -48,6 +48,7 @@ import sample.complex.ComplexSample;
import source.tuner.frequency.FrequencyChangeListener;
import buffer.FloatAveragingBuffer;
import dsp.filter.interpolator.QPSKInterpolator;
+import dsp.symbol.Dibit;
/**
* Implements a CQPSK demodulator using a Gardner Detector to determine optimal
@@ -221,7 +222,7 @@ public class CQPSKDemodulator implements Instrumentable,
/* Sampling point */
private float mMu = 10.0f;
- private float mGainMu = 0.1f;
+ private float mGainMu = 0.05f;
/* Samples per symbol */
private float mOmega = 10.0f;
@@ -314,6 +315,9 @@ public class CQPSKDemodulator implements Instrumentable,
currentSymbol.quadrature() ) * middleSymbol.quadrature();
float gardnerError = normalize( errorInphase + errorQuadrature, 1.0f );
+
+// /*debug*/
+// gardnerError = 0.0f;
/* mOmega is samples per symbol and is constrained to floating
* between +/- .005 of the nominal 10.0 samples per symbol */
@@ -331,8 +335,9 @@ public class CQPSKDemodulator implements Instrumentable,
/* Update costas loop using phase error present in current
* symbol. The symbol is rotated from star orientation to polar
* orientation to simplify error calculation */
- mCostasLoop.receive(
- ComplexSample.multiply( currentSymbol, POINT_45_DEGREES ) );
+ mCostasLoop.receive( currentSymbol );
+// mCostasLoop.receive(
+// ComplexSample.multiply( currentSymbol, POINT_45_DEGREES ) );
/* Dispatch the differentiated symbol to the registered listener */
if( mListener != null )
@@ -442,7 +447,8 @@ public class CQPSKDemodulator implements Instrumentable,
*/
public ComplexSample getCurrentVector()
{
- return ComplexSample.fromAngle( mLoopPhase + THETA );
+// return ComplexSample.fromAngle( mLoopPhase + THETA );
+ return ComplexSample.fromAngle( mLoopPhase );
}
@Override
@@ -450,6 +456,9 @@ public class CQPSKDemodulator implements Instrumentable,
{
/* Calculate phase error */
float phaseError = getPhaseError( sample );
+
+ Dibit d = QPSKPolarSlicer.decide( sample );
+// mLog.debug( "Dibit: " + d.name() + " Error: " + phaseError );
/* Adjust for phase error */
adjust( phaseError );
diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java
index ac68ea20..57aacf78 100644
--- a/src/gui/SDRTrunk.java
+++ b/src/gui/SDRTrunk.java
@@ -105,9 +105,6 @@ public class SDRTrunk
*/
mResourceManager = new ResourceManager();
- /* Log any available audio converter plugins */
- logAvailableAudioPlugins();
-
mTitle = getTitle();
//Initialize the GUI
@@ -387,19 +384,6 @@ public class SDRTrunk
return homePath;
}
- private void logAvailableAudioPlugins()
- {
- ServiceLoader loader =
- ServiceLoader.load( FormatConversionProvider.class );
-
- Iterator it = loader.iterator();
-
- while( it.hasNext() )
- {
- mLog.info( "Available Audio Converter: " + it.next().getClass() );
- }
- }
-
private String getTitle()
{
StringBuilder sb = new StringBuilder();