From 31482a10140d6c5ba90bd6c1b4f7a82a9443d434 Mon Sep 17 00:00:00 2001 From: "dennis.sheirer" Date: Mon, 8 Jun 2015 01:05:19 +0000 Subject: [PATCH] P25 Decoder - improved LSM decoding and IMBE audio decoding. Includes redesigned audio output class to handle automatic start/stop of audio playback based on availability of audio data. --- .classpath | 2 +- build/build.xml | 23 +- imports/jmbe-interface-0.2.0.jar | Bin 0 -> 15493 bytes scripts/run_sdrtrunk_linux | 2 - scripts/run_sdrtrunk_linux.sh | 2 + scripts/run_sdrtrunk_windows.bat | 2 +- src/alias/Alias.java | 2 +- src/alias/AliasID.java | 2 +- src/alias/talkgroup/TalkgroupID.java | 2 +- src/alias/talkgroup/TalkgroupIDEditor.java | 2 +- src/audio/Audio.java | 20 - .../AudioFormats.java} | 5 +- src/audio/AudioOutputImpl.java | 3 + src/audio/AudioPacket.java | 41 +- src/audio/IAudioOutput.java | 1 + src/audio/IAudioPacketReceiver.java | 6 - src/audio/MonoAudioOutput.java | 179 +++++++- src/audio/{ => inverted}/AudioAdapter.java | 2 +- src/audio/{ => inverted}/AudioInverter.java | 3 +- src/audio/{ => inverted}/AudioType.java | 2 +- .../{ => inverted}/IAudioTypeListener.java | 3 +- src/controller/channel/ProcessingChain.java | 2 +- src/controller/state/ChannelState.java | 19 +- src/decode/p25/P25Decoder.java | 23 +- src/decode/p25/P25MessageFramer.java | 4 +- src/decode/p25/audio/IMBETargetDataLine.java | 10 +- src/decode/p25/audio/P25AudioCodecTest.java | 6 +- src/decode/p25/audio/P25AudioOutput.java | 394 ++++-------------- src/decode/p25/reference/Encryption.java | 1 + src/decode/passport/PassportChannelState.java | 2 +- src/dsp/psk/CQPSKDemodulator.java | 17 +- src/gui/SDRTrunk.java | 16 - 32 files changed, 366 insertions(+), 432 deletions(-) create mode 100644 imports/jmbe-interface-0.2.0.jar delete mode 100644 scripts/run_sdrtrunk_linux create mode 100644 scripts/run_sdrtrunk_linux.sh delete mode 100644 src/audio/Audio.java rename src/{decode/p25/audio/IMBEAudioFormat.java => audio/AudioFormats.java} (96%) delete mode 100644 src/audio/IAudioPacketReceiver.java rename src/audio/{ => inverted}/AudioAdapter.java (99%) rename src/audio/{ => inverted}/AudioInverter.java (97%) rename src/audio/{ => inverted}/AudioType.java (99%) rename src/audio/{ => inverted}/IAudioTypeListener.java (97%) 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 0000000000000000000000000000000000000000..85add8bd53e3841db57aecc621adeeabf9c7e455 GIT binary patch literal 15493 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%0DjRqOkE| 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 { - /** - * 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();