[WIP] Updates the AM decoder module.

Removes a number of unused classes in an effort to remove RealBuffer
usage across the codebase (has been replaced by ReusableBuffers).
This commit is contained in:
Denny
2018-06-30 05:37:13 -04:00
parent df1bc1031e
commit 6f1ce79461
24 changed files with 494 additions and 1986 deletions

View File

@@ -45,7 +45,6 @@ import io.github.dsheirer.module.decode.event.ICallEventProvider;
import io.github.dsheirer.module.decode.event.MessageActivityModel;
import io.github.dsheirer.module.log.EventLogger;
import io.github.dsheirer.record.wave.ComplexBufferWaveRecorder;
import io.github.dsheirer.record.wave.RealBufferWaveRecorder;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
@@ -709,7 +708,7 @@ public class ProcessingChain implements IChannelEventListener
for(Module module : mModules)
{
if(module instanceof RealBufferWaveRecorder || module instanceof ComplexBufferWaveRecorder)
if(module instanceof ComplexBufferWaveRecorder)
{
recordingModules.add(module);
}

View File

@@ -87,7 +87,6 @@ import io.github.dsheirer.module.decode.passport.PassportMessageFilter;
import io.github.dsheirer.module.decode.tait.Tait1200Decoder;
import io.github.dsheirer.module.decode.tait.Tait1200DecoderState;
import io.github.dsheirer.module.demodulate.am.AMDemodulatorModule;
import io.github.dsheirer.module.demodulate.audio.DemodulatedAudioFilterModule;
import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule;
import io.github.dsheirer.source.SourceType;
import org.slf4j.Logger;
@@ -100,7 +99,8 @@ public class DecoderFactory
{
private final static Logger mLog = LoggerFactory.getLogger(DecoderFactory.class);
private static final double CHANNEL_BANDWIDTH = 12500.0;
private static final double AM_CHANNEL_BANDWIDTH = 3000.0;
private static final double FM_CHANNEL_BANDWIDTH = 12500.0;
private static final double DEMODULATED_AUDIO_SAMPLE_RATE = 8000.0;
/**
@@ -148,16 +148,13 @@ public class DecoderFactory
switch(decodeConfig.getDecoderType())
{
case AM:
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new AMDemodulatorModule());
//TODO: update AM demodulator to produce demodulated 8 kHz audio and delete the following filter module
modules.add(new DemodulatedAudioFilterModule(4000, 6000));
}
modules.add(new AMDecoder(decodeConfig));
modules.add(new AlwaysUnsquelchedDecoderState(DecoderType.AM, channel.getName()));
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new AMDemodulatorModule(AM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case NBFM:
modules.add(new NBFMDecoder(decodeConfig));
@@ -165,7 +162,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case LTR_STANDARD:
@@ -175,7 +172,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case LTR_NET:
@@ -184,7 +181,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case MPT1327:
@@ -196,7 +193,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
if(channelType == ChannelType.STANDARD)
@@ -212,7 +209,7 @@ public class DecoderFactory
modules.add(new AudioModule(metadata));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new FMDemodulatorModule(CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
}
break;
case P25_PHASE1:

View File

@@ -15,68 +15,144 @@
******************************************************************************/
package io.github.dsheirer.module.demodulate.am;
import io.github.dsheirer.dsp.am.AMDemodulator_CB;
import io.github.dsheirer.dsp.am.AMDemodulator;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter_RB_RB;
import io.github.dsheirer.dsp.gain.AutomaticGainControl_RB;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.resample.RealResampler;
import io.github.dsheirer.dsp.gain.AutomaticGainControl;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.real.IFilteredRealBufferProvider;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AMDemodulatorModule extends Module implements IReusableComplexBufferListener, IFilteredRealBufferProvider
/**
* AM Demodulator Module for demodulating complex baseband channel sample buffers and producing demodulated audio
* at a specified output sample rate, normally 8 kHz.
*
* This module requires a sample rate SourceEvent prior to processing baseband sample buffers in order to configure
* internal filters and the resampler.
*/
public class AMDemodulatorModule extends Module implements ISourceEventListener, IReusableComplexBufferListener,
IReusableBufferProvider, Listener<ReusableComplexBuffer>
{
private static final int SAMPLE_RATE = 48000;
private ComplexFIRFilter_CB_CB mIQFilter;
private AMDemodulator_CB mDemodulator;
private RealFIRFilter_RB_RB mLowPassFilter;
private AutomaticGainControl_RB mAGC;
private ReusableBufferListener mReusableBufferListener = new ReusableBufferListener();
private final static Logger mLog = LoggerFactory.getLogger(AMDemodulatorModule.class);
private static float[] mLowPassFilterTaps;
private ComplexFIRFilter2 mIQFilter;
private AMDemodulator mDemodulator;
private RealFIRFilter2 mLowPassFilter;
private AutomaticGainControl mAGC = new AutomaticGainControl();
private double mChannelBandwidth;
private double mOutputSampleRate;
private RealResampler mResampler;
private Listener<ReusableBuffer> mResampledReusableBufferListener;
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
/**
* AM Demodulator Module
* Constructs an AM demodulator module
*
* @param channelBandwidth to use in filtering the baseband input buffers
* @param outputSampleRate specifies the resampled output sample rate for demodulated audio
*/
public AMDemodulatorModule()
public AMDemodulatorModule(double channelBandwidth, double outputSampleRate)
{
mIQFilter = new ComplexFIRFilter_CB_CB(FilterFactory.getLowPass(
SAMPLE_RATE, 5000, 73, WindowType.HAMMING), 1.0f);
mChannelBandwidth = channelBandwidth;
mOutputSampleRate = outputSampleRate;
mDemodulator = new AMDemodulator_CB(500.0f);
mIQFilter.setListener(mDemodulator);
mDemodulator = new AMDemodulator(500.0f);
mLowPassFilter = new RealFIRFilter_RB_RB(
FilterFactory.getLowPass(48000, 3000, 31, WindowType.COSINE), 1.0f);
mDemodulator.setListener(mLowPassFilter);
if(mLowPassFilterTaps == null)
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(mOutputSampleRate)
.gridDensity(16)
.oddLength(true)
.passBandCutoff(3000)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(3500)
.stopBandAmplitude(0.0)
.stopBandRipple(0.028)
.build();
mAGC = new AutomaticGainControl_RB();
mLowPassFilter.setListener(mAGC);
try
{
mLowPassFilterTaps = FilterFactory.getTaps(specification);
}
catch(FilterDesignException fde)
{
mLog.error("Couldn't design AM demodulator remez filter for sample rate [" + mOutputSampleRate +
"] pass frequency [3000] and stop frequency [3500] - using sinc filter");
}
if(mLowPassFilterTaps == null)
{
mLowPassFilterTaps = FilterFactory.getLowPass(mOutputSampleRate, 3000, 3500,
60, WindowType.HAMMING, true);
}
}
mLowPassFilter = new RealFIRFilter2(mLowPassFilterTaps);
}
@Override
public void receive(ReusableComplexBuffer basebandBuffer)
{
ReusableComplexBuffer filteredBuffer = mIQFilter.filter(basebandBuffer);
ReusableBuffer demodulated = mDemodulator.demodulate(filteredBuffer);
mResampler.resample(demodulated);
}
/**
* Processes resampled audio buffers produced by the resampler.
* @param resampledAudio buffer to process.
*/
private void processResampledAudio(ReusableBuffer resampledAudio)
{
if(mResampledReusableBufferListener != null)
{
ReusableBuffer filteredAudio = mLowPassFilter.filter(resampledAudio);
ReusableBuffer gainApplied = mAGC.process(filteredAudio);
mResampledReusableBufferListener.receive(gainApplied);
}
else
{
resampledAudio.decrementUserCount();
}
}
@Override
public Listener<ReusableComplexBuffer> getReusableComplexBufferListener()
{
return mReusableBufferListener;
return this;
}
@Override
public void dispose()
{
mIQFilter.dispose();
mIQFilter = null;
if(mIQFilter != null)
{
mIQFilter.dispose();
mIQFilter = null;
}
if(mLowPassFilter != null)
{
mLowPassFilter.dispose();
mLowPassFilter = null;
}
mDemodulator.dispose();
mDemodulator = null;
mLowPassFilter.dispose();
mLowPassFilter = null;
mAGC.dispose();
mAGC = null;
}
@@ -85,18 +161,6 @@ public class AMDemodulatorModule extends Module implements IReusableComplexBuffe
{
}
@Override
public void setFilteredRealBufferListener(Listener<RealBuffer> listener)
{
mAGC.setListener(listener);
}
@Override
public void removeFilteredRealBufferListener()
{
mAGC.removeListener();
}
@Override
public void start()
{
@@ -107,18 +171,97 @@ public class AMDemodulatorModule extends Module implements IReusableComplexBuffe
{
}
@Override
public void setBufferListener(Listener<ReusableBuffer> listener)
{
mResampledReusableBufferListener = listener;
}
public class ReusableBufferListener implements Listener<ReusableComplexBuffer>
@Override
public void removeBufferListener()
{
mResampledReusableBufferListener = null;
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
return mSourceEventProcessor;
}
/**
* Monitors sample rate change source event(s) to setup the initial I/Q filter and resampler
*/
public class SourceEventProcessor implements Listener<SourceEvent>
{
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
public void receive(SourceEvent sourceEvent)
{
float[] samples = reusableComplexBuffer.getSamplesCopy();
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
{
if(mIQFilter != null)
{
mIQFilter.dispose();
mIQFilter = null;
}
//TODO: redesign the filter chain so that we can simply pass a float array ...
double sampleRate = sourceEvent.getValue().doubleValue();
mIQFilter.receive(new ComplexBuffer(samples));
reusableComplexBuffer.decrementUserCount();
if((sampleRate < (2.0 * mChannelBandwidth)))
{
throw new IllegalStateException("AM Demodulator with channel bandwidth [" + mChannelBandwidth +
"] requires a channel sample rate of [" + (2.0 * mChannelBandwidth + "] - sample rate of [" +
sampleRate + "] is not supported"));
}
double cutoff = sampleRate / 4.0;
int passBandStop = (int)cutoff - 500;
int stopBandStart = (int)cutoff + 500;
float[] filterTaps = null;
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.oddLength(true)
.passBandCutoff(passBandStop)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(stopBandStart)
.stopBandAmplitude(0.0)
.stopBandRipple(0.028) //Approximately 60 dB attenuation
.build();
try
{
filterTaps = FilterFactory.getTaps(specification);
}
catch(FilterDesignException fde)
{
mLog.error("Couldn't design FM demodulator remez filter for sample rate [" + sampleRate +
"] pass frequency [" + passBandStop + "] and stop frequency [" + stopBandStart +
"] - using sinc filter");
}
if(filterTaps == null)
{
filterTaps = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, 60,
Window.WindowType.HAMMING, true);
}
mIQFilter = new ComplexFIRFilter2(filterTaps);
mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000);
mResampler.setListener(new Listener<ReusableBuffer>()
{
@Override
public void receive(ReusableBuffer resampledAudioBuffer)
{
processResampledAudio(resampledAudioBuffer);
}
});
}
}
}
}

View File

@@ -1,120 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 io.github.dsheirer.module.demodulate.audio;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.dc.DCRemovalFilter_RealBuffer;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter_RB_RB;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IFilteredRealBufferProvider;
import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener;
import io.github.dsheirer.sample.real.RealBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Deprecated //Remove once polyphase channelizer enhancement is complete
public class DemodulatedAudioFilterModule extends Module implements IUnFilteredRealBufferListener, IFilteredRealBufferProvider
{
private final static Logger mLog = LoggerFactory.getLogger(DemodulatedAudioFilterModule.class);
private DCRemovalFilter_RealBuffer mDCFilter;
private RealFIRFilter_RB_RB mBandPassFilter;
/**
* Filters demodulated audio and removes any DC component from the buffer
*
* @param pass - audio pass band pass frequency
* @param stop - audio pass band stop frequency
*/
public DemodulatedAudioFilterModule(int pass, int stop)
{
assert (stop > pass);
mDCFilter = new DCRemovalFilter_RealBuffer();
//TODO: change this to a band pass filter
mBandPassFilter = new RealFIRFilter_RB_RB(FilterFactory.getLowPass(48000, pass, stop, 60,
WindowType.HANN, true), 1.0f);
mDCFilter.setListener(mBandPassFilter);
}
/**
* Constructs a demodulated audio filter using the filter taps and gain provided
*/
public DemodulatedAudioFilterModule(float[] taps, float gain)
{
mBandPassFilter = new RealFIRFilter_RB_RB(taps, gain);
}
@Override
public void dispose()
{
mBandPassFilter.dispose();
mBandPassFilter = null;
if(mDCFilter != null)
{
mDCFilter.dispose();
mDCFilter = null;
}
}
@Override
public void reset()
{
}
@Override
public void setFilteredRealBufferListener(Listener<RealBuffer> listener)
{
mBandPassFilter.setListener(listener);
}
@Override
public void removeFilteredRealBufferListener()
{
mBandPassFilter.removeListener();
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public Listener<RealBuffer> getUnFilteredRealBufferListener()
{
if(mDCFilter != null)
{
return mDCFilter;
}
else
{
return mBandPassFilter;
}
}
}