Changes internal audio sample rate from 48 kHz down to 8 kHz. Updates the P25 audio module to output 8 kHz audio. Updates the audio recorder to record at 8 kHz rate.

Note: required updated JMBE 0.3.3 library which supports 8 kHz audio output sample rate.
This commit is contained in:
Denny
2016-08-19 21:09:39 -04:00
parent cc65d44237
commit 3af4b953f4
18 changed files with 857 additions and 447 deletions

View File

@@ -35,7 +35,7 @@ public class AudioFormats
IMBE_FRAME_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_8KHZ_16BITS =
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_MONO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
@@ -43,7 +43,16 @@ public class AudioFormats
PCM_FRAME_SIZE_BYTES_MONO,
PCM_8KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_STEREO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
TWO_CHANNELS,
PCM_FRAME_SIZE_BYTES_STEREO,
PCM_8KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_48KHZ_RATE,
@@ -66,11 +75,11 @@ public class AudioFormats
* Source Data Line Info for a 48 kHz, 16-bits signed PCM one channel
*/
public static final Line.Info MONO_SOURCE_DATALINE_INFO =
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_48KHZ_16BITS_MONO );
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_MONO);
/**
* Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
*/
public static final Line.Info STEREO_SOURCE_DATALINE_INFO =
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_48KHZ_16BITS_STEREO );
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_STEREO);
}

View File

@@ -3,6 +3,10 @@ package audio;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
import dsp.filter.design.FilterDesignException;
import dsp.filter.fir.FIRFilterSpecification;
import dsp.filter.fir.remez.RemezFIRFilterDesigner;
import dsp.filter.polyphase.PolyphaseFIRDecimatingFilter_RB;
import module.Module;
import org.slf4j.Logger;
@@ -22,171 +26,213 @@ import dsp.filter.Filters;
import dsp.filter.fir.real.RealFIRFilter_R_R;
/**
* Provides packaging of demodulated audio sample buffers into audio packets for
* broadcast to registered audio packet listeners. Includes audio packet
* Provides packaging of demodulated audio sample buffers into audio packets for
* broadcast to registered audio packet listeners. Includes audio packet
* metadata in constructed audio packets.
*
*
* Incorporates audio squelch state listener to control if audio packets are
* broadcast or ignored.
*/
public class AudioModule extends Module implements IAudioPacketProvider,
IChannelEventListener,
IMetadataListener,
IFilteredRealBufferListener,
ISquelchStateListener,
Listener<RealBuffer>
public class AudioModule extends Module implements IAudioPacketProvider,
IChannelEventListener,
IMetadataListener,
IFilteredRealBufferListener,
ISquelchStateListener,
Listener<RealBuffer>
{
protected static final Logger mLog = LoggerFactory.getLogger( AudioModule.class );
/* Provides a unique identifier for this audio module instance to use as a
* source identifier for all audio packets */
private static int UNIQUE_ID = 0;
private int mSourceID;
private AudioMetadata mAudioMetadata;
private ChannelEventListener mChannelEventListener = new ChannelEventListener();
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
private Listener<AudioPacket> mAudioPacketListener;
protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
//TODO: remove this -- is being replaced by the DemodulatedAudioFilterModule
private RealFIRFilter_R_R mAudioFilter = new RealFIRFilter_R_R(
Filters.FIR_BANDPASS_AUDIO_48KHZ.getCoefficients(), 2.0f );
public AudioModule( boolean record )
{
mSourceID = ++UNIQUE_ID;
mAudioMetadata = new AudioMetadata( mSourceID, record );
}
@Override
public void dispose()
{
mSquelchStateListener = null;
mAudioPacketListener = null;
}
private static float[] mDecimationCoefficients;
@Override
public void reset()
{
mAudioMetadata.reset();
}
@Override
public void start( ScheduledExecutorService executor )
{
/* No start operations provided */
}
static
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(48000)
.gridDensity(16)
.passBandCutoff(3000)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(4000)
.stopBandAmplitude(0.0)
.stopBandRipple(0.027)
.build();
@Override
public void stop()
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
try
{
if (!designer.isValid())
{
throw new FilterDesignException("Couldn't design the audio decimation filter");
}
mDecimationCoefficients = designer.getImpulseResponse();
}
catch (FilterDesignException e)
{
mLog.debug("Error designing filter", e);
}
}
/* Provides a unique identifier for this audio module instance to use as a
* source identifier for all audio packets */
private static int UNIQUE_ID = 0;
private int mSourceID;
private AudioMetadata mAudioMetadata;
private ChannelEventListener mChannelEventListener = new ChannelEventListener();
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
private Listener<AudioPacket> mAudioPacketListener;
private PolyphaseFIRDecimatingFilter_RB mAudioDecimationFilter;
//TODO: remove this -- is being replaced by the DemodulatedAudioFilterModule
private RealFIRFilter_R_R mAudioFilter = new RealFIRFilter_R_R(
Filters.FIR_BANDPASS_AUDIO_48KHZ.getCoefficients(), 2.0f);
public AudioModule(boolean record)
{
mSourceID = ++UNIQUE_ID;
mAudioMetadata = new AudioMetadata(mSourceID, record);
mAudioDecimationFilter = new PolyphaseFIRDecimatingFilter_RB(mDecimationCoefficients, 6);
mAudioDecimationFilter.setListener(new Listener<RealBuffer>()
{
@Override
public void receive(RealBuffer realBuffer)
{
if (mAudioPacketListener != null)
{
AudioPacket packet = new AudioPacket(realBuffer.getSamples(), mAudioMetadata.copyOf());
mAudioPacketListener.receive(packet);
}
}
});
}
@Override
public void dispose()
{
mSquelchStateListener = null;
mAudioPacketListener = null;
}
@Override
public void reset()
{
mAudioMetadata.reset();
}
@Override
public void start(ScheduledExecutorService executor)
{
/* No start operations provided */
}
@Override
public void stop()
{
/* Issue an end audio packet in case a recorder is still rolling */
if( mAudioPacketListener != null )
{
mAudioPacketListener.receive( new AudioPacket( AudioPacket.Type.END,
mAudioMetadata.copyOf() ) );
}
}
if (mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END,
mAudioMetadata.copyOf()));
}
}
/**
* Processes demodulated audio samples into audio packets with current audio
* metadata and sends to the registered listener
*/
@Override
public void receive( RealBuffer buffer )
{
if( mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH )
{
/* We make a copy of the samples, so that we don't affect any other
* processes that might be concurrently processing the same buffer */
float[] audio = Arrays.copyOf( buffer.getSamples(), buffer.getSamples().length );
mAudioFilter.filter( audio );
AudioPacket packet = new AudioPacket( audio, mAudioMetadata.copyOf() );
/**
* Processes demodulated audio samples into audio packets with current audio
* metadata and sends to the registered listener
*/
@Override
public void receive(RealBuffer buffer)
{
if (mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH)
{
mAudioDecimationFilter.receive(buffer);
if( mAudioPacketListener != null )
{
mAudioPacketListener.receive( packet );
}
}
}
// /* We make a copy of the samples, so that we don't affect any other
// * processes that might be concurrently processing the same buffer */
// float[] audio = Arrays.copyOf( buffer.getSamples(), buffer.getSamples().length );
//
// mAudioFilter.filter( audio );
}
}
@Override
public Listener<RealBuffer> getFilteredRealBufferListener()
{
return this;
}
@Override
public Listener<RealBuffer> getFilteredRealBufferListener()
{
return this;
}
@Override
public Listener<Metadata> getMetadataListener()
{
return mAudioMetadata;
}
@Override
public Listener<Metadata> getMetadataListener()
{
return mAudioMetadata;
}
@Override
public void setAudioPacketListener( Listener<AudioPacket> listener )
{
mAudioPacketListener = listener;
}
@Override
public void setAudioPacketListener(Listener<AudioPacket> listener)
{
mAudioPacketListener = listener;
}
@Override
public void removeAudioPacketListener()
{
mAudioPacketListener = null;
}
@Override
public void removeAudioPacketListener()
{
mAudioPacketListener = null;
}
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mChannelEventListener;
}
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mChannelEventListener;
}
@Override
public Listener<SquelchState> getSquelchStateListener()
{
return mSquelchStateListener;
}
@Override
public Listener<SquelchState> getSquelchStateListener()
{
return mSquelchStateListener;
}
/**
* Wrapper for channel event listener. Responds to channel state reset
* events to remove/cleanup current audio metadata
*/
public class ChannelEventListener implements Listener<ChannelEvent>
{
@Override
public void receive( ChannelEvent event )
{
switch( event.getEvent() )
{
case NOTIFICATION_STATE_RESET:
mAudioMetadata.reset();
break;
case NOTIFICATION_SELECTION_CHANGE:
mAudioMetadata.setSelected( event.getChannel().isSelected() );
break;
default:
break;
}
}
}
/**
* Wrapper for channel event listener. Responds to channel state reset
* events to remove/cleanup current audio metadata
*/
public class ChannelEventListener implements Listener<ChannelEvent>
{
@Override
public void receive(ChannelEvent event)
{
switch (event.getEvent())
{
case NOTIFICATION_STATE_RESET:
mAudioMetadata.reset();
break;
case NOTIFICATION_SELECTION_CHANGE:
mAudioMetadata.setSelected(event.getChannel().isSelected());
break;
default:
break;
}
}
}
/**
* Wrapper for squelch state listener
*/
public class SquelchStateListener implements Listener<SquelchState>
{
@Override
public void receive( SquelchState state )
{
if( state == SquelchState.SQUELCH && mAudioPacketListener != null )
{
mAudioPacketListener.receive( new AudioPacket( AudioPacket.Type.END,
mAudioMetadata.copyOf() ) );
}
mSquelchState = state;
}
}
/**
* Wrapper for squelch state listener
*/
public class SquelchStateListener implements Listener<SquelchState>
{
@Override
public void receive(SquelchState state)
{
if (state == SquelchState.SQUELCH && mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END,
mAudioMetadata.copyOf()));
}
mSquelchState = state;
}
}
}

View File

@@ -5,7 +5,7 @@ import audio.metadata.AudioMetadata;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
* Copyright (C) 2014,2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -33,14 +33,14 @@ import controller.ThreadPoolManager;
*/
public class MonoAudioOutput extends AudioOutput
{
private final static int BUFFER_SIZE = 48000;
private final static int BUFFER_SIZE = 8000;
public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
{
super( threadPoolManager,
mixer,
MixerChannel.MONO,
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO,
AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
AudioFormats.MONO_SOURCE_DATALINE_INFO,
BUFFER_SIZE );
}

View File

@@ -33,7 +33,7 @@ import controller.ThreadPoolManager;
*/
public class StereoAudioOutput extends AudioOutput
{
private final static int BUFFER_SIZE = 96000;
private final static int BUFFER_SIZE = 16000;
public StereoAudioOutput( ThreadPoolManager threadPoolManager,
Mixer mixer, MixerChannel channel )
@@ -41,7 +41,7 @@ public class StereoAudioOutput extends AudioOutput
super( threadPoolManager,
mixer,
channel,
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO,
AudioFormats.PCM_SIGNED_8KHZ_16BITS_STEREO,
AudioFormats.STEREO_SOURCE_DATALINE_INFO,
BUFFER_SIZE );
}