From 6763a4bdd612dcaabb633901c3af8c607851cb50 Mon Sep 17 00:00:00 2001 From: denny Date: Fri, 11 Dec 2015 13:34:20 -0500 Subject: [PATCH] Adds de-emphasis filter to P25 decoded IMBE audio output. --- src/audio/output/StereoAudioOutput.java | 3 +- src/buffer/FloatCircularBuffer.java | 5 ++ src/dsp/filter/iir/DeemphasisFilter.java | 60 +++++++++++++++++++ src/module/decode/p25/P25DecoderPanel.java | 4 +- .../decode/p25/audio/P25AudioModule.java | 21 ++++--- src/source/wave/RealWaveSource.java | 8 ++- 6 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 src/dsp/filter/iir/DeemphasisFilter.java diff --git a/src/audio/output/StereoAudioOutput.java b/src/audio/output/StereoAudioOutput.java index 2a7b9dad..a8182359 100644 --- a/src/audio/output/StereoAudioOutput.java +++ b/src/audio/output/StereoAudioOutput.java @@ -95,7 +95,8 @@ public class StereoAudioOutput extends AudioOutput catch ( LineUnavailableException e ) { mLog.error( "Couldn't obtain source data line for 48kHz PCM stereo " - + "audio output" ); + + "audio output - mixer [" + mixer.getMixerInfo().getName() + + "] channel [" + mMixerChannel.name() + "]" ); } } diff --git a/src/buffer/FloatCircularBuffer.java b/src/buffer/FloatCircularBuffer.java index 0fa3ad79..ccb749e1 100644 --- a/src/buffer/FloatCircularBuffer.java +++ b/src/buffer/FloatCircularBuffer.java @@ -40,6 +40,11 @@ public class FloatCircularBuffer Arrays.fill( mBuffer, (float)0.0 ); } + public int getSize() + { + return mBuffer.length; + } + public float[] getBuffer() { return mBuffer; diff --git a/src/dsp/filter/iir/DeemphasisFilter.java b/src/dsp/filter/iir/DeemphasisFilter.java new file mode 100644 index 00000000..9ba94b70 --- /dev/null +++ b/src/dsp/filter/iir/DeemphasisFilter.java @@ -0,0 +1,60 @@ +package dsp.filter.iir; + +import sample.real.RealBuffer; + +public class DeemphasisFilter +{ + private static final float MAX_SAMPLE_VALUE = 0.95f; + private float mAlpha; + private float mGain; + private float mPrevious = 0.0f; + + public DeemphasisFilter( float sampleRate, float cutoff, float gain ) + { + mAlpha = (float)Math.exp( -2.0 * Math.PI * cutoff * ( 1.0 / sampleRate ) ); + mGain = gain; + } + + public float filter( float sample ) + { + mPrevious = sample + ( mAlpha * mPrevious ); + + return declip( mPrevious * mGain ); + } + + private float declip( float value ) + { + if( value > MAX_SAMPLE_VALUE ) + { + return MAX_SAMPLE_VALUE; + } + else if( value < -MAX_SAMPLE_VALUE ) + { + return -MAX_SAMPLE_VALUE; + } + + else return value; + } + + public float[] filter( float[] samples ) + { + for( int x = 0; x < samples.length; x++ ) + { + samples[ x ] = filter( samples[ x ] ); + } + + return samples; + } + + public RealBuffer filter( RealBuffer buffer ) + { + float[] samples = buffer.getSamples(); + + for( int x = 0; x < samples.length; x++ ) + { + samples[ x ] = filter( samples[ x ] ); + } + + return buffer; + } +} diff --git a/src/module/decode/p25/P25DecoderPanel.java b/src/module/decode/p25/P25DecoderPanel.java index 15520e0b..ea1dd648 100644 --- a/src/module/decode/p25/P25DecoderPanel.java +++ b/src/module/decode/p25/P25DecoderPanel.java @@ -149,7 +149,7 @@ public class P25DecoderPanel extends DecoderPanel { mFromAlias.setText( from.getName() ); - String icon = from.getIconName(); + final String icon = from.getIconName(); if( icon != null ) { @@ -180,7 +180,7 @@ public class P25DecoderPanel extends DecoderPanel { mToAlias.setText( to.getName() ); - String icon = to.getIconName(); + final String icon = to.getIconName(); if( icon != null ) { diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index 3d3f0d9b..cdf5ea0a 100644 --- a/src/module/decode/p25/audio/P25AudioModule.java +++ b/src/module/decode/p25/audio/P25AudioModule.java @@ -6,8 +6,6 @@ import message.IMessageListener; import message.Message; import module.Module; import module.decode.p25.message.ldu.LDUMessage; -import module.decode.p25.message.tdu.TDUMessage; -import module.decode.p25.message.tdu.lc.TDULinkControlMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +21,8 @@ import audio.squelch.ISquelchStateListener; import audio.squelch.SquelchState; import controller.channel.ChannelEvent; import controller.channel.IChannelEventListener; +import dsp.audio.P25AGC; +import dsp.filter.iir.DeemphasisFilter; public class P25AudioModule extends Module implements Listener, IAudioPacketProvider, IMessageListener, IChannelEventListener, @@ -43,6 +43,8 @@ public class P25AudioModule extends Module implements Listener, private AudioMetadata mAudioMetadata; private ChannelEventListener mChannelEventListener = new ChannelEventListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); +// private P25AGC mAGC = new P25AGC( 2.0, 10.0 ); + private DeemphasisFilter mDeemphasis = new DeemphasisFilter( 48000.0f, 300.0f, 0.6f ); public P25AudioModule( boolean record ) { @@ -89,9 +91,10 @@ public class P25AudioModule extends Module implements Listener, } /** - * Primary inject point for p25 imbe audio frame messages. Each LDU audio - * message contains 9 imbe voice frames. If the audio is not encrypted, - * we insert each audio frame asynchronously into the audio converter. + * Primary processing method for p25 imbe audio frame messages. Each LDU + * audio message contains 9 imbe voice frames. Each frame is converted to + * audio when the JMBE library is loaded, processed by auto-gain, and + * broadcast to the audio packet listener. */ public void receive( Message message ) { @@ -101,15 +104,15 @@ public class P25AudioModule extends Module implements Listener, { LDUMessage ldu = (LDUMessage)message; - /* 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. */ + /* Only process unencrypted audio frames */ if( !( ldu.isValid() && ldu.isEncrypted() ) ) { - for( byte[] frame: ((LDUMessage)message).getIMBEFrames() ) + for( byte[] frame: ldu.getIMBEFrames() ) { float[] audio = mAudioConverter.decode( frame ); + audio = mDeemphasis.filter( audio ); + mAudioPacketListener.receive( new AudioPacket( audio, mAudioMetadata.copyOf() ) ); } diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java index b801b73a..a7251674 100644 --- a/src/source/wave/RealWaveSource.java +++ b/src/source/wave/RealWaveSource.java @@ -36,7 +36,8 @@ import source.IControllableFileSource; import source.IFrameLocationListener; import source.RealSource; -public class RealWaveSource extends RealSource implements IControllableFileSource +public class RealWaveSource extends RealSource + implements IControllableFileSource, AutoCloseable { private final static Logger mLog = LoggerFactory.getLogger( RealWaveSource.class ); @@ -168,6 +169,11 @@ public class RealWaveSource extends RealSource implements IControllableFileSourc { if( samplesRead < buffer.length ) { + if( samplesRead == -1 ) + { + throw new IOException( "End of recording" ); + } + buffer = Arrays.copyOf( buffer, samplesRead ); }