mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
[WIP] Implements reusable sample buffers across all components. This
produces a significant reduction in memory usage, especially for tuners like the airspy and hackrf that produce 10/20 MSPS sample rates. This is observed to be around 66% reduction in peak memory usage and provides a more stable memory utilization across time reducing stress on the garbage collector. Use of these reusable memory buffers will be pushed further down into the processing chain modules to ensure we keep new object creation to the lowest levels possible.
This commit is contained in:
@@ -20,7 +20,7 @@ package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<ComplexBuffer>, ISourceEventListener
|
||||
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<ReusableComplexBuffer>, ISourceEventListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
@@ -121,7 +121,7 @@ public class ChannelizerViewer extends JFrame
|
||||
mTestTuner.getTunerController().getSampleRate());
|
||||
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1200, 200));
|
||||
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
|
||||
mTestTuner.getTunerController().addComplexBufferListener(mPrimarySpectrumPanel);
|
||||
mTestTuner.getTunerController().addBufferListener(mPrimarySpectrumPanel);
|
||||
}
|
||||
|
||||
return mPrimarySpectrumPanel;
|
||||
@@ -140,7 +140,7 @@ public class ChannelizerViewer extends JFrame
|
||||
long toneFrequency = 0;
|
||||
|
||||
SpinnerNumberModel model = new SpinnerNumberModel(toneFrequency, minimumFrequency, maximumFrequency,
|
||||
100 );
|
||||
100);
|
||||
|
||||
model.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@@ -300,7 +300,7 @@ public class ChannelizerViewer extends JFrame
|
||||
return tunerChannels;
|
||||
}
|
||||
|
||||
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||
public class PrimarySpectrumPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
@@ -324,9 +324,10 @@ public class ChannelizerViewer extends JFrame
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -336,7 +337,7 @@ public class ChannelizerViewer extends JFrame
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||
public class ChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private TunerChannelSource mSource;
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
@@ -373,17 +374,18 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
if(mSource != null)
|
||||
{
|
||||
mSource.setListener(new Listener<ComplexBuffer>()
|
||||
mSource.setListener(new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer complexBuffer)
|
||||
{
|
||||
if(mLoggingEnabled)
|
||||
{
|
||||
mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples()));
|
||||
}
|
||||
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
mDFTProcessor.receive(complexBuffer.incrementUserCount());
|
||||
complexBuffer.decrementUserCount();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -406,10 +408,10 @@ public class ChannelizerViewer extends JFrame
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
mLog.debug("Got a buffer for the channel dft processor");
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -419,7 +421,7 @@ public class ChannelizerViewer extends JFrame
|
||||
}
|
||||
}
|
||||
|
||||
public class DiscreteChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||
public class DiscreteChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private TunerChannelSource mSource;
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
@@ -454,17 +456,18 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
if(mSource != null)
|
||||
{
|
||||
mSource.setListener(new Listener<ComplexBuffer>()
|
||||
mSource.setListener(new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer complexBuffer)
|
||||
{
|
||||
if(mLoggingEnabled)
|
||||
{
|
||||
mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples()));
|
||||
}
|
||||
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
mDFTProcessor.receive(complexBuffer.incrementUserCount());
|
||||
complexBuffer.decrementUserCount();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -487,10 +490,10 @@ public class ChannelizerViewer extends JFrame
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
mLog.debug("Got a buffer for the channel dft processor");
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -524,16 +527,13 @@ public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
TestTuner tuner = new TestTuner();
|
||||
|
||||
//Turn on buffer reuse since we're simply profiling data flow/throughput
|
||||
tuner.getTunerController().setReuseBuffers(true);
|
||||
|
||||
List<TunerChannel> tunerChannels = getTunerChannels(tuner);
|
||||
|
||||
List<TunerChannelSource> sources = new ArrayList<>();
|
||||
|
||||
int maxSourceCount = 30;
|
||||
int sourceCount = 0;
|
||||
for(TunerChannel tunerChannel: tunerChannels)
|
||||
for(TunerChannel tunerChannel : tunerChannels)
|
||||
{
|
||||
if(sourceCount < maxSourceCount)
|
||||
{
|
||||
@@ -553,21 +553,24 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
mLog.debug("Starting [" + sources.size() + "] tuner channel sources");
|
||||
|
||||
for(TunerChannelSource tunerChannelSource: sources)
|
||||
for(TunerChannelSource tunerChannelSource : sources)
|
||||
{
|
||||
tunerChannelSource.setListener(new Listener<ComplexBuffer>()
|
||||
tunerChannelSource.setListener(new Listener<ReusableComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
//Do nothing
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
});
|
||||
|
||||
tunerChannelSource.start();
|
||||
}
|
||||
|
||||
while(true);
|
||||
while(true)
|
||||
{
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package io.github.dsheirer.dsp.filter.channelizer;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -84,7 +84,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
public ComplexPolyphaseChannelizerM2(double sampleRate, int tapsPerFilter) throws FilterDesignException
|
||||
{
|
||||
super(sampleRate, (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH));
|
||||
super(sampleRate, getChannelCount(sampleRate));
|
||||
|
||||
float[] filterTaps = FilterFactory.getSincM2Channelizer(getChannelSampleRate(), getChannelCount(),
|
||||
tapsPerFilter, WindowType.BLACKMAN_HARRIS_7, true);
|
||||
@@ -95,6 +95,25 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
init(filterTaps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the multiple of two number of channels that can be channelized from the specified sample rate so that
|
||||
* each channel has a minimum bandwidth of the default channel bandwidth (12.5 kHz).
|
||||
* @param sampleRate to channelize
|
||||
* @return number of multiple of two channels that can be channelized.
|
||||
*/
|
||||
private static int getChannelCount(double sampleRate)
|
||||
{
|
||||
int channels = (int)(sampleRate / DEFAULT_MINIMUM_CHANNEL_BANDWIDTH);
|
||||
|
||||
if(channels % 2 != 0)
|
||||
{
|
||||
channels--;
|
||||
}
|
||||
|
||||
mLog.debug("Sample Rate [" + sampleRate + "] will produce [" + channels + "] channels at [" + (sampleRate / (double)channels) + "] each");
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this channelizer to use the new sample rate. This method creates a new filter suitable for the
|
||||
* sample rate and reinitializes all internal data structures to prepare for processing the new sample rate.
|
||||
@@ -123,15 +142,12 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
* Receives the complex sample buffer and processes the results through the channelizer.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
//Use the buffer's reference timestamp to update our timestamp manager (for timestamping output buffers)
|
||||
if(complexBuffer.hasTimestamp())
|
||||
{
|
||||
mTimestampManager.setReferenceTimestamp(complexBuffer.getTimestamp());
|
||||
}
|
||||
mTimestampManager.setReferenceTimestamp(reusableComplexBuffer.getTimestamp());
|
||||
|
||||
float[] samples = complexBuffer.getSamples();
|
||||
float[] samples = reusableComplexBuffer.getSamples();
|
||||
|
||||
int samplesPointer = 0;
|
||||
|
||||
@@ -153,6 +169,9 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
process();
|
||||
}
|
||||
}
|
||||
|
||||
//Decrement the user count to let the originator know we're done with their buffer
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,8 +23,8 @@ import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcesso
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.IComplexBufferProvider;
|
||||
import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferProvider;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
@@ -52,7 +52,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 17;
|
||||
|
||||
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
|
||||
private IComplexBufferProvider mComplexBufferProvider;
|
||||
private IReusableComplexBufferProvider mReusableBufferProvider;
|
||||
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private ChannelCalculator mChannelCalculator;
|
||||
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
|
||||
@@ -72,18 +72,18 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
* Note: add this channel manager as a source event listener to the complex buffer provider to ensure this manager
|
||||
* adapts to changes in source frequency and sample rate.
|
||||
*
|
||||
* @param complexBufferProvider that supports register/deregister of this channel manager
|
||||
* @param reusableBufferProvider that supports register/deregister of this channel manager
|
||||
* @param frequency of the provided complex buffer samples
|
||||
* @param sampleRate of the provided complex buffer samples
|
||||
*/
|
||||
public PolyphaseChannelManager(IComplexBufferProvider complexBufferProvider, long frequency, double sampleRate)
|
||||
public PolyphaseChannelManager(IReusableComplexBufferProvider reusableBufferProvider, long frequency, double sampleRate)
|
||||
{
|
||||
if(complexBufferProvider == null)
|
||||
if(reusableBufferProvider == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Complex buffer provider argument cannot be null");
|
||||
}
|
||||
|
||||
mComplexBufferProvider = complexBufferProvider;
|
||||
mReusableBufferProvider = reusableBufferProvider;
|
||||
|
||||
int channelCount = (int)(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
|
||||
|
||||
@@ -229,7 +229,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
|
||||
{
|
||||
mComplexBufferProvider.addComplexBufferListener(mBufferProcessor);
|
||||
mReusableBufferProvider.addBufferListener(mBufferProcessor);
|
||||
mBufferProcessor.start();
|
||||
}
|
||||
}
|
||||
@@ -250,7 +250,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
|
||||
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
|
||||
{
|
||||
mComplexBufferProvider.removeComplexBufferListener(mBufferProcessor);
|
||||
mReusableBufferProvider.removeBufferListener(mBufferProcessor);
|
||||
mBufferProcessor.stop();
|
||||
}
|
||||
}
|
||||
@@ -538,7 +538,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
* that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer
|
||||
* and/or the channel sources and output processors.
|
||||
*/
|
||||
public class BufferSourceEventMonitor implements Listener<ComplexBuffer>
|
||||
public class BufferSourceEventMonitor implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private Queue<SourceEvent> mQueuedSourceEvents = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@@ -553,7 +553,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -578,13 +578,15 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
|
||||
if(mPolyphaseChannelizer != null)
|
||||
{
|
||||
mPolyphaseChannelizer.receive(complexBuffer);
|
||||
mPolyphaseChannelizer.receive(reusableComplexBuffer.incrementUserCount());
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error", throwable);
|
||||
}
|
||||
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
@@ -29,7 +29,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||
|
||||
private TimestampedBufferAssembler mTimestampedBufferAssembler;
|
||||
private ReusableBufferAssembler mReusableBufferAssembler;
|
||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||
private double mChannelSampleRate;
|
||||
private long mCenterFrequency;
|
||||
@@ -51,25 +51,25 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
super(producerSourceEventListener, tunerChannel);
|
||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||
mChannelSampleRate = channelSampleRate;
|
||||
mTimestampedBufferAssembler = new TimestampedBufferAssembler(2500, mChannelSampleRate);
|
||||
mReusableBufferAssembler = new ReusableBufferAssembler(2500, mChannelSampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive complex sample buffers from this channel source
|
||||
*/
|
||||
@Override
|
||||
public void setListener(final Listener<ComplexBuffer> listener)
|
||||
public void setListener(final Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mTimestampedBufferAssembler.setListener(listener);
|
||||
mReusableBufferAssembler.setListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving complex sample buffers from this channel source
|
||||
*/
|
||||
@Override
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
public void removeListener(Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mTimestampedBufferAssembler.setListener(null);
|
||||
mReusableBufferAssembler.setListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +108,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
//Fully process the residual channel results buffer of the previous channel output processor
|
||||
if(existingProcessor != null)
|
||||
{
|
||||
existingProcessor.processChannelResults(mTimestampedBufferAssembler);
|
||||
existingProcessor.processChannelResults(mReusableBufferAssembler);
|
||||
}
|
||||
|
||||
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
|
||||
@@ -129,7 +129,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
*/
|
||||
public void receiveChannelResults(PolyphaseChannelResultsBuffer channelResultsBuffer)
|
||||
{
|
||||
mTimestampedBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
|
||||
mReusableBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
|
||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
|
||||
//Lock on the output processor so that it can't be changed in the middle of processing
|
||||
synchronized(mPolyphaseChannelOutputProcessor)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.processChannelResults(mTimestampedBufferAssembler);
|
||||
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableBufferAssembler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,29 @@ public class ScheduledBufferProcessor<E> implements Listener<E>
|
||||
*/
|
||||
public ScheduledBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
|
||||
{
|
||||
mQueue = new OverflowableTransferQueue<>(maximumSize, resetThreshold);
|
||||
this(new OverflowableTransferQueue<>(maximumSize, resetThreshold), distributionInterval, maxBuffersPerInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
|
||||
* for periodically distributing internally queued elements to the registered listener. This processor provides
|
||||
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
|
||||
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
|
||||
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
|
||||
* processing workload.
|
||||
*
|
||||
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
|
||||
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
|
||||
*
|
||||
* @param queue implmentation of an overflowable transfer queue
|
||||
* @param distributionInterval in milliseconds specifying how often the internal scheduled thread pool buffer
|
||||
* distributor should run
|
||||
* @param maxBuffersPerInterval maximum number of buffers to pull from the internal queue during each distribution
|
||||
* interval
|
||||
*/
|
||||
public ScheduledBufferProcessor(OverflowableTransferQueue<E> queue, long distributionInterval, int maxBuffersPerInterval)
|
||||
{
|
||||
mQueue = queue;
|
||||
mDistributionInterval = distributionInterval;
|
||||
mMaxBuffersPerInterval = maxBuffersPerInterval;
|
||||
}
|
||||
@@ -121,10 +143,18 @@ public class ScheduledBufferProcessor<E> implements Listener<E>
|
||||
mScheduledFuture = null;
|
||||
}
|
||||
|
||||
mQueue.clear();
|
||||
clearQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any buffers from the queue
|
||||
*/
|
||||
protected void clearQueue()
|
||||
{
|
||||
mQueue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this processor is currently running
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.reusable.OverflowableReusableBufferTransferQueue;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
|
||||
public class ScheduledReusableBufferProcessor extends ScheduledBufferProcessor<ReusableComplexBuffer>
|
||||
{
|
||||
/**
|
||||
* Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing
|
||||
* task for periodically distributing internally queued elements to the registered listener. This processor
|
||||
* provides a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
|
||||
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
|
||||
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
|
||||
* processing workload.
|
||||
*
|
||||
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
|
||||
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
|
||||
*
|
||||
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
|
||||
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
|
||||
* @param distributionInterval in milliseconds specifying how often the internal scheduled thread pool buffer
|
||||
* distributor should run
|
||||
* @param maxBuffersPerInterval maximum number of buffers to pull from the internal queue during each distribution
|
||||
*/
|
||||
public ScheduledReusableBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
|
||||
{
|
||||
super(new OverflowableReusableBufferTransferQueue(maximumSize, resetThreshold),
|
||||
distributionInterval, maxBuffersPerInterval);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,8 @@ import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SampleType;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
||||
import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
@@ -140,7 +141,7 @@ public class SynthesizerViewer extends JFrame
|
||||
return mChannel2Panel;
|
||||
}
|
||||
|
||||
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>
|
||||
public class PrimarySpectrumPanel extends JPanel implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
@@ -174,13 +175,14 @@ public class SynthesizerViewer extends JFrame
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>
|
||||
public class ChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>
|
||||
{
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
@@ -208,9 +210,10 @@ public class SynthesizerViewer extends JFrame
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
public void receive(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
mDFTProcessor.receive(complexBuffer);
|
||||
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
|
||||
reusableComplexBuffer.decrementUserCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +277,7 @@ public class SynthesizerViewer extends JFrame
|
||||
private TwoChannelSynthesizerM2 mSynthesizer;
|
||||
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
|
||||
private int mSamplesPerCycle = CHANNEL_SAMPLE_RATE / DATA_GENERATOR_FRAME_RATE;
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue();
|
||||
|
||||
public DataGenerationManager()
|
||||
{
|
||||
@@ -292,15 +296,19 @@ public class SynthesizerViewer extends JFrame
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
float[] channel1 = getChannel1ControlPanel().getOscillator().generateComplex(mSamplesPerCycle);
|
||||
float[] channel2 = getChannel2ControlPanel().getOscillator().generateComplex(mSamplesPerCycle);
|
||||
ReusableComplexBuffer channel1Buffer = mReusableBufferQueue.getBuffer(mSamplesPerCycle);
|
||||
getChannel1ControlPanel().getOscillator().generateComplex(channel1Buffer);
|
||||
|
||||
float[] synthesized = mSynthesizer.process(channel1, channel2);
|
||||
ReusableComplexBuffer channel2Buffer = mReusableBufferQueue.getBuffer(mSamplesPerCycle);
|
||||
getChannel2ControlPanel().getOscillator().generateComplex(channel2Buffer);
|
||||
|
||||
float[] synthesized = mSynthesizer.process(channel1Buffer.getSamples(), channel2Buffer.getSamples());
|
||||
mFS4DownConverter.mixComplex(synthesized);
|
||||
ReusableComplexBuffer synthesizedBuffer = mReusableBufferQueue.getBuffer(synthesized.length);
|
||||
|
||||
getChannel1Panel().receive(new ComplexBuffer(channel1));
|
||||
getChannel2Panel().receive(new ComplexBuffer(channel2));
|
||||
getSpectrumPanel().receive(new ComplexBuffer(synthesized));
|
||||
getChannel1Panel().receive(channel1Buffer.incrementUserCount());
|
||||
getChannel2Panel().receive(channel2Buffer.incrementUserCount());
|
||||
getSpectrumPanel().receive(synthesizedBuffer.incrementUserCount());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.sample.OverflowableTransferQueue;
|
||||
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
|
||||
import io.github.dsheirer.sample.real.IOverflowListener;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import org.slf4j.Logger;
|
||||
@@ -89,10 +89,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
|
||||
/**
|
||||
* Processes all enqueued polyphase channelizer results until the internal queue is empty
|
||||
* @param timestampedBufferAssembler to receive the processed channel results
|
||||
* @param reusableBufferAssembler to receive the processed channel results
|
||||
*/
|
||||
@Override
|
||||
public void processChannelResults(TimestampedBufferAssembler timestampedBufferAssembler)
|
||||
public void processChannelResults(ReusableBufferAssembler reusableBufferAssembler)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -100,7 +100,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
|
||||
if(toProcess > 0)
|
||||
{
|
||||
process(mChannelResultsToProcess, timestampedBufferAssembler);
|
||||
process(mChannelResultsToProcess, reusableBufferAssembler);
|
||||
}
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
@@ -114,10 +114,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
/**
|
||||
* Sub-class implementation to process one polyphase channelizer result array.
|
||||
* @param channelResults to process
|
||||
* @param timestampedBufferAssembler to receive the processed channelizer results
|
||||
* @param reusableBufferAssembler to receive the processed channelizer results
|
||||
*/
|
||||
public abstract void process(List<PolyphaseChannelResultsBuffer> channelResults,
|
||||
TimestampedBufferAssembler timestampedBufferAssembler);
|
||||
ReusableBufferAssembler reusableBufferAssembler);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
|
||||
import io.github.dsheirer.source.Source;
|
||||
|
||||
import java.util.List;
|
||||
@@ -35,9 +35,9 @@ public interface IPolyphaseChannelOutputProcessor
|
||||
/**
|
||||
* Process the channel output channel results queue and deliver the output to the listener
|
||||
*
|
||||
* @param timestampedBufferAssembler to receive the processed channel results
|
||||
* @param reusableBufferAssembler to receive the processed channel results
|
||||
*/
|
||||
void processChannelResults(TimestampedBufferAssembler timestampedBufferAssembler);
|
||||
void processChannelResults(ReusableBufferAssembler reusableBufferAssembler);
|
||||
|
||||
/**
|
||||
* Sets the desired frequency offset from center. The samples will be mixed with an oscillator set to this offset
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer;
|
||||
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -73,11 +73,11 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
|
||||
*
|
||||
* @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
* @param timestampedBufferAssembler to receive the extracted, frequency-translated channel results
|
||||
* @param reusableBufferAssembler to receive the extracted, frequency-translated channel results
|
||||
*/
|
||||
@Override
|
||||
public void process(List<PolyphaseChannelResultsBuffer> channelResults,
|
||||
TimestampedBufferAssembler timestampedBufferAssembler)
|
||||
ReusableBufferAssembler reusableBufferAssembler)
|
||||
{
|
||||
for(PolyphaseChannelResultsBuffer channelResultsBuffer: channelResults)
|
||||
{
|
||||
@@ -90,7 +90,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
getFrequencyCorrectionMixer().mixComplex(samples);
|
||||
}
|
||||
|
||||
timestampedBufferAssembler.receive(samples);
|
||||
reusableBufferAssembler.receive(samples);
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer;
|
||||
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
|
||||
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
|
||||
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -92,11 +92,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
|
||||
*
|
||||
* @param channelResultsBuffers to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
|
||||
* @param timestampedBufferAssembler to receive the extracted, frequency-translated channel results
|
||||
* @param reusableBufferAssembler to receive the extracted, frequency-translated channel results
|
||||
*/
|
||||
@Override
|
||||
public void process(List<PolyphaseChannelResultsBuffer> channelResultsBuffers,
|
||||
TimestampedBufferAssembler timestampedBufferAssembler)
|
||||
ReusableBufferAssembler reusableBufferAssembler)
|
||||
{
|
||||
for(PolyphaseChannelResultsBuffer buffer: channelResultsBuffers)
|
||||
{
|
||||
@@ -112,7 +112,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
|
||||
getFrequencyCorrectionMixer().mixComplex(synthesized);
|
||||
|
||||
timestampedBufferAssembler.receive(synthesized);
|
||||
reusableBufferAssembler.receive(synthesized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,17 @@
|
||||
package io.github.dsheirer.dsp.mixer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public abstract class AbstractOscillator implements IOscillator
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractOscillator.class);
|
||||
|
||||
private FloatBuffer mSampleBuffer;
|
||||
private boolean mEnabled;
|
||||
private double mFrequency;
|
||||
private double mSampleRate;
|
||||
@@ -144,4 +152,30 @@ public abstract class AbstractOscillator implements IOscillator
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates enough complex samples to fill the reusable complex buffer
|
||||
* @param reusableComplexBuffer to fill with complex samples
|
||||
*/
|
||||
@Override
|
||||
public void generateComplex(ReusableComplexBuffer reusableComplexBuffer)
|
||||
{
|
||||
int sampleCount = reusableComplexBuffer.getSampleLength();
|
||||
|
||||
if(mSampleBuffer == null || mSampleBuffer.capacity() != sampleCount * 2)
|
||||
{
|
||||
mSampleBuffer = FloatBuffer.allocate(sampleCount * 2);
|
||||
}
|
||||
|
||||
mSampleBuffer.rewind();
|
||||
|
||||
for(int x = 0; x < sampleCount; x++)
|
||||
{
|
||||
mSampleBuffer.put(inphase());
|
||||
mSampleBuffer.put(quadrature());
|
||||
rotate();
|
||||
}
|
||||
|
||||
reusableComplexBuffer.reloadFrom(mSampleBuffer, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.dsp.mixer;
|
||||
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
|
||||
/**
|
||||
* Oscillator interface.
|
||||
*/
|
||||
@@ -86,6 +88,15 @@ public interface IOscillator
|
||||
*/
|
||||
float[] generateComplex(int sampleCount);
|
||||
|
||||
|
||||
/**
|
||||
* Generates complex samples and fills the reusable complex buffer using the current system time in millis for
|
||||
* the update timestamp.
|
||||
*
|
||||
* @param reusableComplexBuffer to fill with complex samples
|
||||
*/
|
||||
void generateComplex(ReusableComplexBuffer reusableComplexBuffer);
|
||||
|
||||
/**
|
||||
* Mixes (heterodynes) the complex sample array using the current settings of this oscillator.
|
||||
* @param complexSamples to mix to a new frequency
|
||||
|
||||
Reference in New Issue
Block a user