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.

This commit is contained in:
dennis.sheirer
2015-06-08 01:05:19 +00:00
parent 5dd4454c4c
commit 31482a1014
32 changed files with 366 additions and 432 deletions

View File

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

View File

@@ -0,0 +1,50 @@
package audio;
import javax.sound.sampled.AudioFormat;
public class AudioFormats
{
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_RATE = 8000;
public static final float PCM_48KHZ_RATE = 48000;
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_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES,
PCM_8KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_48KHZ_16BITS =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_48KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES,
PCM_48KHZ_RATE,
LITTLE_ENDIAN );
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
package audio;
import audio.SquelchListener.SquelchState;
import audio.inverted.AudioType;
public interface IAudioOutput
{

View File

@@ -1,6 +0,0 @@
package audio;
public interface IAudioPacketReceiver
{
}

View File

@@ -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<AudioPacket>
{
/**
* 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<AudioPacket> 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<AudioPacket> packets = new ArrayList<AudioPacket>();
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();
}
}
}
}

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package audio;
package audio.inverted;
import util.Oscillator;
import dsp.filter.FilterFactory;

View File

@@ -15,8 +15,9 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package audio;
package audio.inverted;
import audio.InversionFrequency;
import sample.real.RealSampleListener;
import util.Oscillator;
import dsp.filter.Filters;

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package audio;
package audio.inverted;
public enum AudioType
{

View File

@@ -15,7 +15,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package audio;
package audio.inverted;
public interface IAudioTypeListener
{