mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Merge pull request #79 from DSheirer/53_audio_sample_rate_to_8kHz
53 audio sample rate to 8k hz
This commit is contained in:
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -4,6 +4,7 @@
|
||||
<w>esnid</w>
|
||||
<w>fleetsync</w>
|
||||
<w>ident</w>
|
||||
<w>polyphase</w>
|
||||
<w>talkgroup</w>
|
||||
<w>tgid</w>
|
||||
</words>
|
||||
|
||||
9
.idea/libraries/jmbe_interface_0_3_3.xml
generated
Normal file
9
.idea/libraries/jmbe_interface_0_3_3.xml
generated
Normal 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>
|
||||
Binary file not shown.
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,209 @@ 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
|
||||
{
|
||||
//Band-pass filter to both decimate (48kHz to 8kHz) and block 0 - 300 Hz (LTR signalling).
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
|
||||
.sampleRate( 48000 )
|
||||
.stopFrequency1( 150 )
|
||||
.passFrequencyBegin( 300 )
|
||||
.passFrequencyEnd( 3500 )
|
||||
.stopFrequency2( 4000 )
|
||||
.stopRipple( 0.0004 )
|
||||
.passRipple( 0.008)
|
||||
.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;
|
||||
|
||||
public AudioModule(boolean record)
|
||||
{
|
||||
mSourceID = ++UNIQUE_ID;
|
||||
mAudioMetadata = new AudioMetadata(mSourceID, record);
|
||||
|
||||
mAudioDecimationFilter = new PolyphaseFIRDecimatingFilter_RB(mDecimationCoefficients, 6, 2.0f);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -40,37 +40,35 @@ public class FilterViewer extends Application
|
||||
*/
|
||||
private float[] getFilter()
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate( 10000 )
|
||||
.gridDensity( 16 )
|
||||
.passBandCutoff( 1300 )
|
||||
// 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();
|
||||
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
||||
.sampleRate( 8000 )
|
||||
.stopBandCutoff( 250 )
|
||||
.stopBandAmplitude( 0.0 )
|
||||
.stopBandRipple( 0.01 )
|
||||
.passBandStart( 300 )
|
||||
.passBandAmplitude( 1.0 )
|
||||
.passBandRipple( 0.01 )
|
||||
.stopBandStart( 2675 )
|
||||
.stopBandAmplitude( 0.0 )
|
||||
.stopBandRipple( 0.001 )
|
||||
.build();
|
||||
|
||||
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
||||
// .sampleRate( 8000 )
|
||||
// .gridDensity( 16 )
|
||||
// .stopBandCutoff( 2000 )
|
||||
// .stopBandAmplitude( 0.0 )
|
||||
// .stopBandRipple( 0.003 )
|
||||
// .passBandStart( 3000 )
|
||||
// .passBandAmplitude( 1.0 )
|
||||
// .passBandRipple( 0.001 )
|
||||
// .build();
|
||||
|
||||
// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
|
||||
// .sampleRate( 8000 )
|
||||
// .order( 164 )
|
||||
// .stopFrequency1( 1100 )
|
||||
// .passFrequencyBegin( 1200 )
|
||||
// .passFrequencyEnd( 2400 )
|
||||
// .stopFrequency2( 2500 )
|
||||
// .stopRipple( 0.01 )
|
||||
// .passRipple( 0.01 )
|
||||
// .sampleRate( 48000 )
|
||||
// .stopFrequency1( 150 )
|
||||
// .passFrequencyBegin( 200 )
|
||||
// .passFrequencyEnd( 3950 )
|
||||
// .stopFrequency2( 4000 )
|
||||
// .stopRipple( 0.0005 )
|
||||
// .passRipple( 0.003)
|
||||
// .build();
|
||||
|
||||
float[] taps = null;
|
||||
|
||||
116
src/dsp/filter/polyphase/FilterStage.java
Normal file
116
src/dsp/filter/polyphase/FilterStage.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
212
src/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java
Normal file
212
src/dsp/filter/polyphase/PolyphaseFIRDecimatingFilter_RB.java
Normal file
@@ -0,0 +1,212 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
private float mGain = 1.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.
|
||||
* @param gain to apply to the filtered output. Use a value of 1.0 to apply no gain.
|
||||
*/
|
||||
public PolyphaseFIRDecimatingFilter_RB(float[] coefficients, int decimationRatio, float gain)
|
||||
{
|
||||
mDecimationRatio = decimationRatio;
|
||||
mGain = gain;
|
||||
|
||||
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 * mGain);
|
||||
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, 1.0f);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,244 +25,249 @@ 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)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("JMBE audio conversion library [");
|
||||
sb.append(library.getVersion());
|
||||
sb.append("] successfully loaded - P25 audio will be available");
|
||||
|
||||
mLog.info(sb.toString());
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user