mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 01:27:34 -05:00
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:
@@ -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
|
||||
}
|
||||
50
src/audio/AudioFormats.java
Normal file
50
src/audio/AudioFormats.java
Normal 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 );
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package audio;
|
||||
|
||||
import audio.SquelchListener.SquelchState;
|
||||
import audio.inverted.AudioType;
|
||||
|
||||
public interface IAudioOutput
|
||||
{
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package audio;
|
||||
|
||||
public interface IAudioPacketReceiver
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
{
|
||||
Reference in New Issue
Block a user