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

@@ -4,6 +4,7 @@
<w>esnid</w>
<w>fleetsync</w>
<w>ident</w>
<w>polyphase</w>
<w>talkgroup</w>
<w>tgid</w>
</words>

View File

@@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="jmbe-interface-0.3.3">
<CLASSES>
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -4,6 +4,9 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/images" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/config" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$" type="java-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
@@ -11,11 +14,12 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../jmbe/jmbe/library/jmbe-0.3.2.jar!/" />
<root url="jar://$MODULE_DIR$/../jmbe/jmbe/library/jmbe-0.3.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="library" name="jmbe-interface-0.3.3" level="project" />
</component>
</module>

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

View File

@@ -1,12 +1,12 @@
/* ....Show License.... */
.chart {
-fx-background-image: url("../../../../images/Curve-fitted-background.png");
-fx-background-image: url("../../../Curve-fitted-background.png");
-fx-padding: 15 25 15 15;
}
.chart-plot-background {
-fx-background-color: red;
-fx-background-image: url("../../../../images/Curve-fitted-chart-background.png"),
url("../../../../images/Curve-fitted-graph-gridlines.png");
-fx-background-image: url("../../../Curve-fitted-chart-background.png"),
url("../../../Curve-fitted-graph-gridlines.png");
-fx-background-size: cover, auto;
-fx-background-repeat: no-repeat, repeat;
-fx-background-position: 0% 0%, 0% 100%;

View File

@@ -41,14 +41,14 @@ public class FilterViewer extends Application
private float[] getFilter()
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate( 10000 )
.sampleRate( 48000 )
.gridDensity( 16 )
.passBandCutoff( 1300 )
.passBandCutoff( 3000 )
.passBandAmplitude( 1.0 )
.passBandRipple( 0.01 )
.stopBandStart( 2675 )
.stopBandStart( 4000 )
.stopBandAmplitude( 0.0 )
.stopBandRipple( 0.001 )
.stopBandRipple( 0.027 )
.build();
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()

View File

@@ -0,0 +1,116 @@
/*******************************************************************************
* sdrtrunk
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 dsp.filter.polyphase;
public class FilterStage
{
private float[] mBuffer;
private int mBufferSize;
private int mBufferPointer;
private int[][] mIndexMap;
private float[] mCoefficients;
/**
* Single stage of a polyphase filter implemented with a circular sample buffer that uses an index map to ensure
* correct alignment of samples to filter taps.
*
* @param coefficients
*/
public FilterStage(float[] coefficients)
{
mCoefficients = coefficients;
mBufferSize = mCoefficients.length;
mBuffer = new float[mBufferSize];
//Note: buffer pointer starts at an illegal index but is decremented before first usage
mBufferPointer = mBufferSize;
generateIndexMap(mBufferSize);
}
/**
* Coefficients used by this filter stage.
*/
public float[] getCoefficients()
{
return mCoefficients;
}
/**
* Cleanup prior to disposal
*/
public void dispose()
{
mCoefficients = null;
mIndexMap = null;
mBuffer = null;
}
/**
* Loads the sample into the sample buffer
*/
private void load(float sample)
{
mBufferPointer--;
if (mBufferPointer < 0)
{
mBufferPointer += mBufferSize;
}
mBuffer[mBufferPointer] = sample;
}
/**
* Applies convolution against the currently loaded buffer samples.
*/
public float filter(float sample)
{
load(sample);
float accumulator = 0.0f;
for (int x = 0; x < mBufferSize; x++)
{
accumulator += mCoefficients[x] * mBuffer[mIndexMap[mBufferPointer][x]];
}
return accumulator;
}
/**
* Generates a circular buffer index map to support lookup of the translated index based on the current buffer
* pointer and the desired sample index.
*/
private void generateIndexMap(int size)
{
mIndexMap = new int[size][size];
for (int x = 0; x < size; x++)
{
for (int y = 0; y < size; y++)
{
int z = x + y;
mIndexMap[x][y] = z < size ? z : z - size;
}
}
}
}

View File

@@ -0,0 +1,208 @@
/*******************************************************************************
* sdrtrunk
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 dsp.filter.polyphase;
import dsp.filter.design.FilterDesignException;
import dsp.filter.fir.FIRFilter;
import dsp.filter.fir.FIRFilterSpecification;
import dsp.filter.fir.remez.RemezFIRFilterDesigner;
import dsp.mixer.Oscillator;
import gui.SDRTrunk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.RealBuffer;
import sample.real.RealToRealBufferAssembler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PolyphaseFIRDecimatingFilter_RB extends FIRFilter implements Listener<RealBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger( PolyphaseFIRDecimatingFilter_RB.class );
private List<FilterStage> mFilterStages = new ArrayList<>();
private int mFilterStagePointer;
private Listener<RealBuffer> mListener;
private RealToRealBufferAssembler mAssembler;
private int mDecimationRatio;
private float mAccumulator = 0.0f;
/**
* Polyphase filter stage for decimating real-valued sample buffers by the decimation ratio. Creates a number of
* polyphase filter arms/stages equal to the decimation ratio.
*
* Note: output real-valued buffer sizes are determined by the first input buffer that is presented to the filter
* where the output buffer size is equal to the floor of the input buffer size divided by the decimation ratio.
*
* @param coefficients of a low pass filter designed for the input sample rate with a cutoff frequency that conforms
* to the nyquist frequency for an output sample rate equal to the input sample rate divided by the decimation ratio.
*
* @param decimationRatio to decimate the input sample rate by.
*/
public PolyphaseFIRDecimatingFilter_RB(float[] coefficients, int decimationRatio)
{
mDecimationRatio = decimationRatio;
createFilterStages(coefficients, mDecimationRatio);
mFilterStagePointer = mFilterStages.size() - 1;
}
/**
* Creates polyphase filter stages (arms) from the set of coefficients.
*
* @param coefficients for the low-pass filter
* @param decimationRatio to determine the number of polyphase filter stages.
*/
private void createFilterStages(float[] coefficients, int decimationRatio)
{
int tapSize = (int)Math.ceil((double)coefficients.length / (double)decimationRatio);
float[][] coefficientSets = new float[decimationRatio][tapSize];
int stagePointer = 0;
int coefficientPointer = 0;
//Split the coefficients up into polyphase stage coefficient sets
for(int x = 0; x < coefficients.length; x++)
{
coefficientSets[stagePointer++][coefficientPointer] = coefficients[x];
if(stagePointer >= decimationRatio)
{
stagePointer = 0;
coefficientPointer++;
}
}
//Create the stages
for(int x = 0; x < decimationRatio; x++)
{
mFilterStages.add(new FilterStage(coefficientSets[x]));
}
}
public void dispose()
{
mListener = null;
}
@Override
public void receive(RealBuffer buffer)
{
if(mAssembler == null)
{
mAssembler = new RealToRealBufferAssembler(buffer.getSamples().length / mDecimationRatio);
mAssembler.setListener(mListener);
}
for(float sample: buffer.getSamples())
{
filter(sample);
}
int a = 0;
}
private void filter(float sample)
{
FilterStage stage = mFilterStages.get(mFilterStagePointer--);
mAccumulator += stage.filter(sample);
if(mFilterStagePointer < 0)
{
mAssembler.receive(mAccumulator);
mAccumulator = 0.0f;
mFilterStagePointer = mFilterStages.size() - 1;
}
}
public void setListener(Listener<RealBuffer> listener)
{
mListener = listener;
if(mAssembler != null)
{
mAssembler.setListener(mListener);
}
}
public void removeListener()
{
mListener = null;
if(mAssembler != null)
{
mAssembler.setListener(null);
}
}
public static void main(String[] args)
{
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();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
try
{
if(!designer.isValid())
{
throw new FilterDesignException("Couldn't design the filter");
}
float[] coefficients = designer.getImpulseResponse();
PolyphaseFIRDecimatingFilter_RB filter = new PolyphaseFIRDecimatingFilter_RB(coefficients, 6);
filter.setListener(new Listener<RealBuffer>()
{
@Override
public void receive(RealBuffer realBuffer)
{
mLog.info("Filtered: " + Arrays.toString(realBuffer.getSamples()));
}
});
Oscillator oscillator = new Oscillator(3400, 48000);
float[] samples = new float[500];
for(int x = 0; x < 500; x++)
{
samples[x] = oscillator.getFloat();
oscillator.rotate();
}
RealBuffer buffer = new RealBuffer(samples);
filter.receive(buffer);
}
catch(FilterDesignException e)
{
mLog.debug("Error designing filter", e);
}
}
}

View File

@@ -1,4 +1,22 @@
package dsp.filter;
/*******************************************************************************
* sdrtrunk
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 dsp.filter.polyphase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -25,244 +25,245 @@ import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener;
import dsp.gain.NonClippingGain;
public class P25AudioModule extends Module implements Listener<Message>,
IAudioPacketProvider, IMessageListener, IChannelEventListener,
IMetadataListener, ISquelchStateListener
public class P25AudioModule extends Module implements Listener<Message>,
IAudioPacketProvider, IMessageListener, IChannelEventListener,
IMetadataListener, ISquelchStateListener
{
private final static Logger mLog = LoggerFactory.getLogger( P25AudioModule.class );
private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class);
private static final String IMBE_CODEC = "IMBE";
private static final String IMBE_CODEC = "IMBE";
/* 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 static boolean mLibraryLoadStatusLogged = false;
private boolean mCanConvertAudio = false;
private AudioConverter mAudioConverter;
private Listener<AudioPacket> mAudioPacketListener;
private AudioMetadata mAudioMetadata;
private ChannelEventListener mChannelEventListener = new ChannelEventListener();
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private NonClippingGain mGain = new NonClippingGain( 5.0f, 0.95f );
/* 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 static boolean mLibraryLoadStatusLogged = false;
private boolean mCanConvertAudio = false;
private AudioConverter mAudioConverter;
private Listener<AudioPacket> mAudioPacketListener;
private AudioMetadata mAudioMetadata;
private ChannelEventListener mChannelEventListener = new ChannelEventListener();
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
public P25AudioModule( boolean record )
{
mSourceID = ++UNIQUE_ID;
mAudioMetadata = new AudioMetadata( mSourceID, record );
loadConverter();
}
@Override
public Listener<Message> getMessageListener()
{
return this;
}
public P25AudioModule(boolean record)
{
mSourceID = ++UNIQUE_ID;
mAudioMetadata = new AudioMetadata(mSourceID, record);
@Override
public Listener<SquelchState> getSquelchStateListener()
{
return mSquelchStateListener;
}
loadConverter();
}
@Override
public void dispose()
{
mAudioConverter = null;
}
@Override
public Listener<Message> getMessageListener()
{
return this;
}
@Override
public void reset()
{
mAudioMetadata.reset();
}
@Override
public Listener<SquelchState> getSquelchStateListener()
{
return mSquelchStateListener;
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void dispose()
{
mAudioConverter = null;
}
@Override
public void stop()
{
}
@Override
public void reset()
{
mAudioMetadata.reset();
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
/**
* 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)
{
if (mCanConvertAudio && mAudioPacketListener != null)
{
if (message instanceof LDUMessage)
{
LDUMessage ldu = (LDUMessage) message;
/**
* 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 )
{
if( mCanConvertAudio && mAudioPacketListener != null )
{
if( message instanceof LDUMessage )
{
LDUMessage ldu = (LDUMessage)message;
/* Only process unencrypted audio frames */
if( !( ldu.isValid() && ldu.isEncrypted() ) )
{
for( byte[] frame: ldu.getIMBEFrames() )
{
float[] audio = mAudioConverter.decode( frame );
if (!(ldu.isValid() && ldu.isEncrypted()))
{
for (byte[] frame : ldu.getIMBEFrames())
{
float[] audio = mAudioConverter.decode(frame);
audio = mGain.apply( audio );
audio = mGain.apply(audio);
mAudioPacketListener.receive(
new AudioPacket( audio, mAudioMetadata.copyOf() ) );
}
}
}
}
}
mAudioPacketListener.receive(
new AudioPacket(audio, mAudioMetadata.copyOf()));
}
}
}
}
}
/**
* Loads audio frame processing chain. Constructs an imbe targetdataline
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
*/
private void loadConverter()
{
AudioConversionLibrary library = null;
try
{
@SuppressWarnings( "rawtypes" )
Class temp = Class.forName( "jmbe.JMBEAudioLibrary" );
library = (AudioConversionLibrary)temp.newInstance();
if( ( library.getMajorVersion() == 0 &&
library.getMinorVersion() >= 3 &&
library.getBuildVersion() >= 1 ) ||
library.getMajorVersion() >= 1 )
{
mAudioConverter = library.getAudioConverter( IMBE_CODEC,
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
if( mAudioConverter != null )
{
mCanConvertAudio = true;
if( !mLibraryLoadStatusLogged )
{
mLog.info( "JMBE audio conversion library successfully loaded"
+ " - P25 audio will be available" );
mLibraryLoadStatusLogged = true;
}
}
else
{
if( !mLibraryLoadStatusLogged )
{
mLog.info( "JMBE audio conversion library NOT FOUND" );
mLibraryLoadStatusLogged = true;
}
}
}
else
{
mLog.warn( "JMBE library version 0.3.1 or higher is required - found: " + library.getVersion() );
}
}
catch ( ClassNotFoundException e1 )
{
if( !mLibraryLoadStatusLogged )
{
mLog.error( "Couldn't find/load JMBE audio conversion library" );
mLibraryLoadStatusLogged = true;
}
}
catch ( InstantiationException e1 )
{
if( !mLibraryLoadStatusLogged )
{
mLog.error( "Couldn't instantiate JMBE audio conversion library class" );
mLibraryLoadStatusLogged = true;
}
}
catch ( IllegalAccessException e1 )
{
if( !mLibraryLoadStatusLogged )
{
mLog.error( "Couldn't load JMBE audio conversion library due to "
+ "security restrictions" );
mLibraryLoadStatusLogged = true;
}
}
}
/**
* Loads audio frame processing chain. Constructs an imbe targetdataline
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
*/
private void loadConverter()
{
AudioConversionLibrary library = null;
@Override
public void setAudioPacketListener( Listener<AudioPacket> listener )
{
mAudioPacketListener = listener;
}
try
{
@SuppressWarnings("rawtypes")
Class temp = Class.forName("jmbe.JMBEAudioLibrary");
@Override
public void removeAudioPacketListener()
{
mAudioPacketListener = null;
}
@Override
public Listener<Metadata> getMetadataListener()
{
return mAudioMetadata;
}
library = (AudioConversionLibrary) temp.newInstance();
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mChannelEventListener;
}
if ((library.getMajorVersion() == 0 &&
library.getMinorVersion() >= 3 &&
library.getBuildVersion() >= 3) ||
library.getMajorVersion() >= 1)
{
// mAudioConverter = library.getAudioConverter( IMBE_CODEC,
// AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
/**
* 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. Internally, the P25 audio module
* doesn't have a squelch state. If there are IMBE audio frames, we have
* audio. We use this listener to signal to the recorder manager that the
* channel state is resetting and it should end the recording.
*/
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() ) );
if (mAudioConverter != null)
{
mCanConvertAudio = true;
mAudioMetadata.reset();
}
}
}
if (!mLibraryLoadStatusLogged)
{
mLog.info("JMBE audio conversion library successfully loaded"
+ " - P25 audio will be available");
mLibraryLoadStatusLogged = true;
}
}
else
{
if (!mLibraryLoadStatusLogged)
{
mLog.info("JMBE audio conversion library NOT FOUND");
mLibraryLoadStatusLogged = true;
}
}
}
else
{
mLog.warn("JMBE library version 0.3.1 or higher is required - found: " + library.getVersion());
}
}
catch (ClassNotFoundException e1)
{
if (!mLibraryLoadStatusLogged)
{
mLog.error("Couldn't find/load JMBE audio conversion library");
mLibraryLoadStatusLogged = true;
}
}
catch (InstantiationException e1)
{
if (!mLibraryLoadStatusLogged)
{
mLog.error("Couldn't instantiate JMBE audio conversion library class");
mLibraryLoadStatusLogged = true;
}
}
catch (IllegalAccessException e1)
{
if (!mLibraryLoadStatusLogged)
{
mLog.error("Couldn't load JMBE audio conversion library due to "
+ "security restrictions");
mLibraryLoadStatusLogged = true;
}
}
}
@Override
public void setAudioPacketListener(Listener<AudioPacket> listener)
{
mAudioPacketListener = listener;
}
@Override
public void removeAudioPacketListener()
{
mAudioPacketListener = null;
}
@Override
public Listener<Metadata> getMetadataListener()
{
return mAudioMetadata;
}
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mChannelEventListener;
}
/**
* 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. Internally, the P25 audio module
* doesn't have a squelch state. If there are IMBE audio frames, we have
* audio. We use this listener to signal to the recorder manager that the
* channel state is resetting and it should end the recording.
*/
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()));
mAudioMetadata.reset();
}
}
}
}

View File

@@ -37,7 +37,7 @@ public class RecorderManager implements Listener<AudioPacket>
{
private static final Logger mLog = LoggerFactory.getLogger( RecorderManager.class );
public static final int AUDIO_SAMPLE_RATE = 48000;
public static final int AUDIO_SAMPLE_RATE = 8000;
private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>();

View File

@@ -1,19 +1,19 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* SDR Trunk
* Copyright (C) 2015 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 sample.real;
@@ -24,51 +24,49 @@ import sample.Listener;
*/
public class RealToRealBufferAssembler implements RealSampleListener
{
private int mBufferSize;
private float[] mBuffer;
private int mBufferPointer;
private Listener<RealBuffer> mListener;
public RealToRealBufferAssembler( int bufferSize )
{
mBufferSize = bufferSize;
mBuffer = new float[ mBufferSize ];
}
public void dispose()
{
mListener = null;
mBuffer = null;
}
public void reset()
{
mBuffer = new float[ mBufferSize ];
mBufferPointer = 0;
}
@Override
public void receive( float sample )
{
if( mBufferPointer >= mBufferSize )
{
if( mListener != null )
{
mListener.receive( new RealBuffer( mBuffer ) );
}
private int mBufferSize;
private float[] mBuffer;
private int mBufferPointer;
mBuffer = new float[ mBufferSize ];
mBufferPointer = 0;
}
private Listener<RealBuffer> mListener;
mBuffer[ mBufferPointer ] = sample;
public RealToRealBufferAssembler(int bufferSize)
{
mBufferSize = bufferSize;
mBuffer = new float[mBufferSize];
}
mBufferPointer++;
}
public void dispose()
{
mListener = null;
mBuffer = null;
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void reset()
{
mBuffer = new float[mBufferSize];
mBufferPointer = 0;
}
@Override
public void receive(float sample)
{
mBuffer[mBufferPointer++] = sample;
if (mBufferPointer >= mBufferSize)
{
if (mListener != null)
{
mListener.receive(new RealBuffer(mBuffer));
}
mBuffer = new float[mBufferSize];
mBufferPointer = 0;
}
}
public void setListener(Listener<RealBuffer> listener)
{
mListener = listener;
}
}

View File

@@ -1599,7 +1599,7 @@ public abstract class RTL2832TunerController extends TunerController
}
catch( Exception e )
{
mLog.error( "error duing rtl2832 buffer dispatcher run", e );
mLog.error( "error during rtl2832 buffer dispatcher run", e );
}
}
}