diff --git a/.idea/libraries/imports.xml b/.idea/libraries/imports.xml index 3be82b35..e97bf8ec 100644 --- a/.idea/libraries/imports.xml +++ b/.idea/libraries/imports.xml @@ -1,41 +1,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.idea/misc.xml b/.idea/misc.xml index 05483570..d5d79e0c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java index 25d0de82..cbffb3f7 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/AbstractComplexPolyphaseChannelizer.java @@ -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, ISourceEventListener +public abstract class AbstractComplexPolyphaseChannelizer implements Listener, ISourceEventListener { private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class); diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java index 8b1f8d8d..283a83c2 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelizerViewer.java @@ -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, ISourceEventProcessor + public class PrimarySpectrumPanel extends JPanel implements Listener, 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, ISourceEventProcessor + public class ChannelPanel extends JPanel implements Listener, 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() + mSource.setListener(new Listener() { @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, ISourceEventProcessor + public class DiscreteChannelPanel extends JPanel implements Listener, 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() + mSource.setListener(new Listener() { @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 tunerChannels = getTunerChannels(tuner); List 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() + tunerChannelSource.setListener(new Listener() { @Override - public void receive(ComplexBuffer complexBuffer) + public void receive(ReusableComplexBuffer reusableComplexBuffer) { - //Do nothing + reusableComplexBuffer.decrementUserCount(); } }); tunerChannelSource.start(); } - while(true); + while(true) + { + ; + } } } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java index 662bbdbc..ddb01ae7 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ComplexPolyphaseChannelizerM2.java @@ -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(); } /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java index ccc13d1d..028cfb26 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java @@ -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 mSourceEventBroadcaster = new Broadcaster<>(); - private IComplexBufferProvider mComplexBufferProvider; + private IReusableComplexBufferProvider mReusableBufferProvider; private List 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 + public class BufferSourceEventMonitor implements Listener { private Queue 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(); } } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java index 3d2bcab4..0e62e6c7 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -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 listener) + public void setListener(final Listener listener) { - mTimestampedBufferAssembler.setListener(listener); + mReusableBufferAssembler.setListener(listener); } /** * Removes the listener from receiving complex sample buffers from this channel source */ @Override - public void removeListener(Listener listener) + public void removeListener(Listener 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); } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ScheduledBufferProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ScheduledBufferProcessor.java index 57c3b9fc..7c3ca3d7 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ScheduledBufferProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ScheduledBufferProcessor.java @@ -59,7 +59,29 @@ public class ScheduledBufferProcessor implements Listener */ 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 queue, long distributionInterval, int maxBuffersPerInterval) + { + mQueue = queue; mDistributionInterval = distributionInterval; mMaxBuffersPerInterval = maxBuffersPerInterval; } @@ -121,10 +143,18 @@ public class ScheduledBufferProcessor implements Listener mScheduledFuture = null; } - mQueue.clear(); + clearQueue(); } } + /** + * Clears any buffers from the queue + */ + protected void clearQueue() + { + mQueue.clear(); + } + /** * Indicates if this processor is currently running */ diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ScheduledReusableBufferProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ScheduledReusableBufferProcessor.java new file mode 100644 index 00000000..24eb7843 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ScheduledReusableBufferProcessor.java @@ -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 + * + ******************************************************************************/ +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 +{ + /** + * 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); + } +} diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java index 94de48c7..75d48dea 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/SynthesizerViewer.java @@ -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 + public class PrimarySpectrumPanel extends JPanel implements Listener { 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 + public class ChannelPanel extends JPanel implements Listener { 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()); } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java index c75205a4..5f9f2954 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/ChannelOutputProcessor.java @@ -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 channelResults, - TimestampedBufferAssembler timestampedBufferAssembler); + ReusableBufferAssembler reusableBufferAssembler); /** diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java index 916ae095..2dc2216a 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java index e6f52d3f..b17970f9 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java @@ -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 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) { diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java index 7b30e2ed..dcc637a5 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java @@ -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 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); } } } diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java index b0432b2e..5be64115 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java +++ b/src/main/java/io/github/dsheirer/dsp/mixer/AbstractOscillator.java @@ -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()); + } } diff --git a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java b/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java index b9e23c08..1f843dda 100644 --- a/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java +++ b/src/main/java/io/github/dsheirer/dsp/mixer/IOscillator.java @@ -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 diff --git a/src/main/java/io/github/dsheirer/module/Module.java b/src/main/java/io/github/dsheirer/module/Module.java index afeb566a..9137643b 100644 --- a/src/main/java/io/github/dsheirer/module/Module.java +++ b/src/main/java/io/github/dsheirer/module/Module.java @@ -32,12 +32,11 @@ public abstract class Module * ICallEventProvider provides call events * IChannelEventListener consumes channel events * IChannelEventProvider provides channel events (reset, selection, start, etc) - * IComplexBufferListener consumes complex samples and normally - * produces demodulated real sample buffers * IFrequencyCorrectionController provides tuned frequency error corrections * IMessageProvider produces messages * IRealBufferListener consumes demodulated real sample buffers * IRealBufferListener produces demodulated real sample buffers + * IReusableComplexBufferListener consumes complex samples and normally produces demodulated real sample buffers * ISquelchStateListener consumes squelch states * ISquelchStateProvider provides squelch states */ diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index 7dddae2a..060190ff 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -49,8 +49,8 @@ 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.complex.ComplexBuffer; -import io.github.dsheirer.sample.complex.IComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.ReusableBufferBroadcaster; import io.github.dsheirer.sample.real.IFilteredRealBufferListener; import io.github.dsheirer.sample.real.IFilteredRealBufferProvider; import io.github.dsheirer.sample.real.IUnFilteredRealBufferListener; @@ -98,7 +98,7 @@ public class ProcessingChain implements IChannelEventListener private Broadcaster mAudioPacketBroadcaster = new Broadcaster<>(); private Broadcaster mCallEventBroadcaster = new Broadcaster<>(); private Broadcaster mChannelEventBroadcaster = new Broadcaster<>(); - private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>(); + private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster(); private Broadcaster mDecoderStateEventBroadcaster = new Broadcaster<>(); private Broadcaster mHeartbeatBroadcaster = new Broadcaster<>(); private Broadcaster mSourceEventBroadcaster = new Broadcaster<>(); @@ -307,24 +307,14 @@ public class ProcessingChain implements IChannelEventListener mChannelEventBroadcaster.addListener(((IChannelEventListener) module).getChannelEventListener()); } - if(module instanceof IComplexBufferListener) - { - mComplexBufferBroadcaster.addListener(((IComplexBufferListener) module).getComplexBufferListener()); - } - if(module instanceof IDecoderStateEventListener) { mDecoderStateEventBroadcaster.addListener(((IDecoderStateEventListener) module).getDecoderStateListener()); } - if(module instanceof ISourceEventListener) + if(module instanceof IFilteredRealBufferListener) { - Listener listener = ((ISourceEventListener)module).getSourceEventListener(); - - if(listener != null) - { - mSourceEventBroadcaster.addListener(listener); - } + mFilteredRealBufferBroadcaster.addListener(((IFilteredRealBufferListener) module).getFilteredRealBufferListener()); } if(module instanceof IHeartbeatListener) @@ -337,9 +327,19 @@ public class ProcessingChain implements IChannelEventListener mMessageBroadcaster.addListener(((IMessageListener) module).getMessageListener()); } - if(module instanceof IFilteredRealBufferListener) + if(module instanceof IReusableComplexBufferListener) { - mFilteredRealBufferBroadcaster.addListener(((IFilteredRealBufferListener) module).getFilteredRealBufferListener()); + mComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener()); + } + + if(module instanceof ISourceEventListener) + { + Listener listener = ((ISourceEventListener)module).getSourceEventListener(); + + if(listener != null) + { + mSourceEventBroadcaster.addListener(listener); + } } if(module instanceof ISquelchStateListener) @@ -379,19 +379,14 @@ public class ProcessingChain implements IChannelEventListener mChannelEventBroadcaster.removeListener(((IChannelEventListener) module).getChannelEventListener()); } - if(module instanceof IComplexBufferListener) - { - mComplexBufferBroadcaster.removeListener(((IComplexBufferListener) module).getComplexBufferListener()); - } - if(module instanceof IDecoderStateEventListener) { mDecoderStateEventBroadcaster.removeListener(((IDecoderStateEventListener) module).getDecoderStateListener()); } - if(module instanceof ISourceEventListener) + if(module instanceof IFilteredRealBufferListener) { - mSourceEventBroadcaster.removeListener(((ISourceEventListener) module).getSourceEventListener()); + mFilteredRealBufferBroadcaster.removeListener(((IFilteredRealBufferListener) module).getFilteredRealBufferListener()); } if(module instanceof IHeartbeatListener) @@ -399,16 +394,21 @@ public class ProcessingChain implements IChannelEventListener mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener()); } + if(module instanceof IReusableComplexBufferListener) + { + mComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener()); + } + + if(module instanceof ISourceEventListener) + { + mSourceEventBroadcaster.removeListener(((ISourceEventListener) module).getSourceEventListener()); + } + if(module instanceof IMessageListener) { mMessageBroadcaster.removeListener(((IMessageListener) module).getMessageListener()); } - if(module instanceof IFilteredRealBufferListener) - { - mFilteredRealBufferBroadcaster.removeListener(((IFilteredRealBufferListener) module).getFilteredRealBufferListener()); - } - if(module instanceof ISquelchStateListener) { mSquelchStateBroadcaster.removeListener(((ISquelchStateListener) module).getSquelchStateListener()); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25_LSMDecoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25_LSMDecoder.java index 9ec1ec00..518eb07f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25_LSMDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25_LSMDecoder.java @@ -1,19 +1,17 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,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 + * 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 + * ******************************************************************************/ package io.github.dsheirer.module.decode.p25; @@ -33,7 +31,8 @@ import io.github.dsheirer.instrument.tap.stream.QPSKTap; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.complex.ComplexBuffer; import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter; -import io.github.dsheirer.sample.complex.IComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.source.SourceEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,192 +40,207 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; -public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener +public class P25_LSMDecoder extends P25Decoder implements IReusableComplexBufferListener { - private final static Logger mLog = LoggerFactory.getLogger( P25_LSMDecoder.class ); - + private final static Logger mLog = LoggerFactory.getLogger(P25_LSMDecoder.class); + /* Instrumentation Taps */ - private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output"; - private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output"; - private static final String INSTRUMENT_LSM_DEMODULATOR_OUTPUT = "Tap Point: LSM Demodulator Output"; - private static final String INSTRUMENT_QPSK_SLICER_OUTPUT = "Tap Point: QPSK Slicer Output"; + private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output"; + private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output"; + private static final String INSTRUMENT_LSM_DEMODULATOR_OUTPUT = "Tap Point: LSM Demodulator Output"; + private static final String INSTRUMENT_QPSK_SLICER_OUTPUT = "Tap Point: QPSK Slicer Output"; - private List mAvailableTaps; - - private ComplexFIRFilter_CB_CB mBasebandFilter; - private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter(); - private ComplexFeedForwardGainControl mAGC = - new ComplexFeedForwardGainControl( 32 ); - private LSMDemodulator mLSMDemodulator = new LSMDemodulator(); - private QPSKPolarSlicer mQPSKSlicer = new QPSKPolarSlicer(); - private P25MessageFramer mMessageFramer; - - public P25_LSMDecoder( AliasList aliasList ) - { - super( aliasList ); - - mBasebandFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass( - 48000, 7250, 8000, 60, WindowType.HANN, true ), 1.0f ); - - mBasebandFilter.setListener( mStreamConverter ); - - mStreamConverter.setListener( mAGC ); + private List mAvailableTaps; - mAGC.setListener( mLSMDemodulator ); - - mLSMDemodulator.setSymbolListener( mQPSKSlicer ); - - mMessageFramer = new P25MessageFramer( aliasList, mLSMDemodulator ); - mQPSKSlicer.addListener( mMessageFramer ); - - mMessageFramer.setListener( getMessageProcessor() ); - } - - public void dispose() - { - super.dispose(); - - mBasebandFilter.dispose(); - mBasebandFilter = null; - - mStreamConverter.dispose(); - mStreamConverter = null; - - mAGC.dispose(); - mAGC = null; + private ComplexFIRFilter_CB_CB mBasebandFilter; + private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter(); + private ComplexFeedForwardGainControl mAGC = + new ComplexFeedForwardGainControl(32); + private LSMDemodulator mLSMDemodulator = new LSMDemodulator(); + private QPSKPolarSlicer mQPSKSlicer = new QPSKPolarSlicer(); + private P25MessageFramer mMessageFramer; + private ReusableBufferListener mReusableBufferListener = new ReusableBufferListener(); - mLSMDemodulator.dispose(); - mLSMDemodulator = null; - - mQPSKSlicer.dispose(); - mQPSKSlicer = null; - - mMessageFramer.dispose(); - mMessageFramer = null; - } + public P25_LSMDecoder(AliasList aliasList) + { + super(aliasList); + + mBasebandFilter = new ComplexFIRFilter_CB_CB(FilterFactory.getLowPass( + 48000, 7250, 8000, 60, WindowType.HANN, true), 1.0f); + + mBasebandFilter.setListener(mStreamConverter); + + mStreamConverter.setListener(mAGC); + + mAGC.setListener(mLSMDemodulator); + + mLSMDemodulator.setSymbolListener(mQPSKSlicer); + + mMessageFramer = new P25MessageFramer(aliasList, mLSMDemodulator); + mQPSKSlicer.addListener(mMessageFramer); + + mMessageFramer.setListener(getMessageProcessor()); + } + + public void dispose() + { + super.dispose(); + + mBasebandFilter.dispose(); + mBasebandFilter = null; + + mStreamConverter.dispose(); + mStreamConverter = null; + + mAGC.dispose(); + mAGC = null; + + mLSMDemodulator.dispose(); + mLSMDemodulator = null; + + mQPSKSlicer.dispose(); + mQPSKSlicer = null; + + mMessageFramer.dispose(); + mMessageFramer = null; + } @Override - public Listener getComplexBufferListener() - { - return mBasebandFilter; - } + public Listener getReusableComplexBufferListener() + { + return mReusableBufferListener; + } - public Modulation getModulation() - { - return Modulation.CQPSK; - } - - /** - * Provides a list of instrumentation taps for monitoring internal processing - */ - @Override + public Modulation getModulation() + { + return Modulation.CQPSK; + } + + /** + * Provides a list of instrumentation taps for monitoring internal processing + */ + @Override public List getTapGroups() { - if( mAvailableTaps == null ) - { - mAvailableTaps = new ArrayList<>(); + if(mAvailableTaps == null) + { + mAvailableTaps = new ArrayList<>(); - TapGroup group = new TapGroup( "P25 LSM Decoder" ); - - group.add( new ComplexTap( INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f ) ); - group.add( new ComplexTap( INSTRUMENT_AGC_OUTPUT, 0, 1.0f ) ); - group.add( new QPSKTap( INSTRUMENT_LSM_DEMODULATOR_OUTPUT, 0, 1.0f ) ); - group.add( new DibitTap( INSTRUMENT_QPSK_SLICER_OUTPUT, 0, 0.1f ) ); + TapGroup group = new TapGroup("P25 LSM Decoder"); - mAvailableTaps.add( group ); - - if( mLSMDemodulator != null ) - { - mAvailableTaps.addAll( mLSMDemodulator.getTapGroups() ); - } - } - - return mAvailableTaps; + group.add(new ComplexTap(INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f)); + group.add(new ComplexTap(INSTRUMENT_AGC_OUTPUT, 0, 1.0f)); + group.add(new QPSKTap(INSTRUMENT_LSM_DEMODULATOR_OUTPUT, 0, 1.0f)); + group.add(new DibitTap(INSTRUMENT_QPSK_SLICER_OUTPUT, 0, 0.1f)); + + mAvailableTaps.add(group); + + if(mLSMDemodulator != null) + { + mAvailableTaps.addAll(mLSMDemodulator.getTapGroups()); + } + } + + return mAvailableTaps; } - /** - * Adds the instrumentation tap - */ - @Override - public void registerTap( Tap tap ) + /** + * Adds the instrumentation tap + */ + @Override + public void registerTap(Tap tap) { - if( mLSMDemodulator != null ) - { - mLSMDemodulator.registerTap( tap ); - } - - switch( tap.getName() ) - { - case INSTRUMENT_BASEBAND_FILTER_OUTPUT: - ComplexSampleTap baseband = (ComplexSampleTap)tap; - mStreamConverter.setListener( baseband ); - baseband.setListener( mAGC ); - break; - case INSTRUMENT_AGC_OUTPUT: - ComplexSampleTap agcSymbol = (ComplexSampleTap)tap; - mAGC.setListener( agcSymbol ); - agcSymbol.setListener( mLSMDemodulator ); - break; - case INSTRUMENT_LSM_DEMODULATOR_OUTPUT: - QPSKTap qpsk = (QPSKTap)tap; - mLSMDemodulator.setSymbolListener( qpsk ); - qpsk.setListener( mQPSKSlicer ); - break; - case INSTRUMENT_QPSK_SLICER_OUTPUT: - mQPSKSlicer.addListener( (DibitTap)tap ); - break; - default: - throw new IllegalArgumentException( "Unrecognized tap: " + - tap.getName() ); - } + if(mLSMDemodulator != null) + { + mLSMDemodulator.registerTap(tap); + } + + switch(tap.getName()) + { + case INSTRUMENT_BASEBAND_FILTER_OUTPUT: + ComplexSampleTap baseband = (ComplexSampleTap)tap; + mStreamConverter.setListener(baseband); + baseband.setListener(mAGC); + break; + case INSTRUMENT_AGC_OUTPUT: + ComplexSampleTap agcSymbol = (ComplexSampleTap)tap; + mAGC.setListener(agcSymbol); + agcSymbol.setListener(mLSMDemodulator); + break; + case INSTRUMENT_LSM_DEMODULATOR_OUTPUT: + QPSKTap qpsk = (QPSKTap)tap; + mLSMDemodulator.setSymbolListener(qpsk); + qpsk.setListener(mQPSKSlicer); + break; + case INSTRUMENT_QPSK_SLICER_OUTPUT: + mQPSKSlicer.addListener((DibitTap)tap); + break; + default: + throw new IllegalArgumentException("Unrecognized tap: " + + tap.getName()); + } } - /** - * Removes the instrumentation tap - */ - @Override - public void unregisterTap( Tap tap ) + /** + * Removes the instrumentation tap + */ + @Override + public void unregisterTap(Tap tap) { - if( mLSMDemodulator != null ) - { - mLSMDemodulator.unregisterTap( tap ); - } - - switch( tap.getName() ) - { - case INSTRUMENT_BASEBAND_FILTER_OUTPUT: - mStreamConverter.setListener( mAGC ); - break; - case INSTRUMENT_AGC_OUTPUT: - mAGC.setListener( mLSMDemodulator ); - break; - case INSTRUMENT_LSM_DEMODULATOR_OUTPUT: - mLSMDemodulator.setSymbolListener( mQPSKSlicer ); - break; - case INSTRUMENT_QPSK_SLICER_OUTPUT: - mQPSKSlicer.removeListener( (DibitTap)tap ); - break; - default: - throw new IllegalArgumentException( "Unrecognized tap: " + - tap.getName() ); - } + if(mLSMDemodulator != null) + { + mLSMDemodulator.unregisterTap(tap); + } + + switch(tap.getName()) + { + case INSTRUMENT_BASEBAND_FILTER_OUTPUT: + mStreamConverter.setListener(mAGC); + break; + case INSTRUMENT_AGC_OUTPUT: + mAGC.setListener(mLSMDemodulator); + break; + case INSTRUMENT_LSM_DEMODULATOR_OUTPUT: + mLSMDemodulator.setSymbolListener(mQPSKSlicer); + break; + case INSTRUMENT_QPSK_SLICER_OUTPUT: + mQPSKSlicer.removeListener((DibitTap)tap); + break; + default: + throw new IllegalArgumentException("Unrecognized tap: " + + tap.getName()); + } } - @Override - public Listener getSourceEventListener() - { - return null; - } + @Override + public Listener getSourceEventListener() + { + return null; + } - @Override - public void setSourceEventListener(Listener listener) - { + @Override + public void setSourceEventListener(Listener listener) + { - } + } - @Override - public void removeSourceEventListener() - { + @Override + public void removeSourceEventListener() + { - } + } + + public class ReusableBufferListener implements Listener + { + @Override + public void receive(ReusableComplexBuffer reusableComplexBuffer) + { + float[] samples = reusableComplexBuffer.getCopyOfSamples(); + + //TODO: redesign the filter chain so that we can simply pass a float array ... + + mBasebandFilter.receive(new ComplexBuffer(samples)); + reusableComplexBuffer.decrementUserCount(); + } + } } diff --git a/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java b/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java index 0cbfdf7a..8677b89a 100644 --- a/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java +++ b/src/main/java/io/github/dsheirer/module/demodulate/am/AMDemodulatorModule.java @@ -1,19 +1,17 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,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 + * 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 + * ******************************************************************************/ package io.github.dsheirer.module.demodulate.am; @@ -26,12 +24,12 @@ import io.github.dsheirer.dsp.gain.AutomaticGainControl_RB; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.complex.ComplexBuffer; -import io.github.dsheirer.sample.complex.IComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.sample.real.IFilteredRealBufferProvider; import io.github.dsheirer.sample.real.RealBuffer; -public class AMDemodulatorModule extends Module - implements IComplexBufferListener, IFilteredRealBufferProvider +public class AMDemodulatorModule extends Module implements IReusableComplexBufferListener, IFilteredRealBufferProvider { private static final int SAMPLE_RATE = 48000; @@ -39,6 +37,7 @@ public class AMDemodulatorModule extends Module private AMDemodulator_CB mDemodulator; private RealFIRFilter_RB_RB mLowPassFilter; private AutomaticGainControl_RB mAGC; + private ReusableBufferListener mReusableBufferListener = new ReusableBufferListener(); /** * AM Demodulator Module @@ -60,9 +59,9 @@ public class AMDemodulatorModule extends Module } @Override - public Listener getComplexBufferListener() + public Listener getReusableComplexBufferListener() { - return mIQFilter; + return mReusableBufferListener; } @Override @@ -107,4 +106,19 @@ public class AMDemodulatorModule extends Module public void stop() { } + + + public class ReusableBufferListener implements Listener + { + @Override + public void receive(ReusableComplexBuffer reusableComplexBuffer) + { + float[] samples = reusableComplexBuffer.getCopyOfSamples(); + + //TODO: redesign the filter chain so that we can simply pass a float array ... + + mIQFilter.receive(new ComplexBuffer(samples)); + reusableComplexBuffer.decrementUserCount(); + } + } } diff --git a/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java b/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java index 7a06160a..72380d34 100644 --- a/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java +++ b/src/main/java/io/github/dsheirer/module/demodulate/fm/FMDemodulatorModule.java @@ -24,13 +24,14 @@ import io.github.dsheirer.dsp.fm.FMDemodulator_CB; import io.github.dsheirer.module.Module; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.complex.ComplexBuffer; -import io.github.dsheirer.sample.complex.IComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.sample.real.IUnFilteredRealBufferProvider; import io.github.dsheirer.sample.real.RealBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class FMDemodulatorModule extends Module implements IComplexBufferListener, IUnFilteredRealBufferProvider +public class FMDemodulatorModule extends Module implements IReusableComplexBufferListener, IUnFilteredRealBufferProvider { private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatorModule.class); @@ -38,6 +39,7 @@ public class FMDemodulatorModule extends Module implements IComplexBufferListene private ComplexFIRFilter_CB_CB mIQFilter; private FMDemodulator_CB mDemodulator; + private ReusableBufferListener mReusableBufferListener = new ReusableBufferListener(); /** * FM Demodulator with I/Q filter. Demodulated output is unfiltered and @@ -68,9 +70,9 @@ public class FMDemodulatorModule extends Module implements IComplexBufferListene } @Override - public Listener getComplexBufferListener() + public Listener getReusableComplexBufferListener() { - return mIQFilter; + return mReusableBufferListener; } @Override @@ -110,4 +112,18 @@ public class FMDemodulatorModule extends Module implements IComplexBufferListene public void stop() { } + + public class ReusableBufferListener implements Listener + { + @Override + public void receive(ReusableComplexBuffer reusableComplexBuffer) + { + float[] samples = reusableComplexBuffer.getCopyOfSamples(); + + //TODO: redesign the filter chain so that we can simply pass a float array ... + + mIQFilter.receive(new ComplexBuffer(samples)); + reusableComplexBuffer.decrementUserCount(); + } + } } diff --git a/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java b/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java index a2e203c6..12b519a3 100644 --- a/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java +++ b/src/main/java/io/github/dsheirer/record/wave/ComplexBufferWaveRecorder.java @@ -1,28 +1,26 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,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 + * 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 + * ******************************************************************************/ package io.github.dsheirer.record.wave; import io.github.dsheirer.module.Module; -import io.github.dsheirer.sample.Buffer; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.complex.ComplexBuffer; -import io.github.dsheirer.sample.complex.IComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.IReusableBufferDisposedListener; +import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferListener; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; import org.slf4j.Logger; @@ -40,32 +38,26 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * WAVE audio recorder module for recording complex (I&Q) samples to a wave file */ -public class ComplexBufferWaveRecorder extends Module - implements IComplexBufferListener, Listener +public class ComplexBufferWaveRecorder extends Module implements IReusableComplexBufferListener, Listener { - private final static Logger mLog = LoggerFactory.getLogger( ComplexBufferWaveRecorder.class ); + private final static Logger mLog = LoggerFactory.getLogger(ComplexBufferWaveRecorder.class); private WaveWriter mWriter; private String mFilePrefix; private Path mFile; - private AudioFormat mAudioFormat; - - private BufferProcessor mBufferProcessor; - private ScheduledFuture mProcessorHandle; - private LinkedBlockingQueue mBuffers = new LinkedBlockingQueue<>( 500 ); - - private AtomicBoolean mRunning = new AtomicBoolean(); - - public ComplexBufferWaveRecorder( int sampleRate, String filePrefix ) - { - mAudioFormat = new AudioFormat( sampleRate, //SampleRate - 16, //Sample Size - 2, //Channels - true, //Signed - false ); //Little Endian + private AudioFormat mAudioFormat; - mFilePrefix = filePrefix; - } + private BufferProcessor mBufferProcessor; + private ScheduledFuture mProcessorHandle; + private LinkedBlockingQueue mBuffers = new LinkedBlockingQueue<>(500); + + private AtomicBoolean mRunning = new AtomicBoolean(); + + public ComplexBufferWaveRecorder(int sampleRate, String filePrefix) + { + mAudioFormat = new AudioFormat(sampleRate, 16, 2, true, false); + mFilePrefix = filePrefix; + } public Path getFile() { @@ -81,137 +73,148 @@ public class ComplexBufferWaveRecorder extends Module mBufferProcessor = new BufferProcessor(); } - try - { - StringBuilder sb = new StringBuilder(); - sb.append( mFilePrefix ); - sb.append( "_" ); - sb.append( TimeStamp.getTimeStamp( "_" ) ); - sb.append( ".wav" ); - - mFile = Paths.get( sb.toString() ); - - mWriter = new WaveWriter( mAudioFormat, mFile ); - - /* Schedule the processor to run every 500 milliseconds */ - mProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate( - mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS ); - } - catch( IOException io ) - { - mLog.error( "Error starting complex baseband recorder", io ); - } - } - } - - public void stop() - { - if( mRunning.compareAndSet( true, false ) ) - { - receive( new PoisonPill() ); - } - } - - @Override - public void receive( ComplexBuffer buffer ) - { - if( mRunning.get() ) - { - boolean success = mBuffers.offer( buffer ); - - if( !success ) - { - mLog.error( "recorder buffer overflow - purging [" + - mFile.toFile().getAbsolutePath() + "]" ); - - mBuffers.clear(); - } - } - } - - @Override - public Listener getComplexBufferListener() - { - return this; - } - - @Override - public void dispose() - { - stop(); - } - - @Override - public void reset() - { - } - - public class BufferProcessor implements Runnable - { - public void run() - { - try + try { - Buffer buffer = mBuffers.poll(); - - while( buffer != null ) - { - if( buffer instanceof PoisonPill ) - { - buffer = null; - - mBuffers.clear(); - - if( mWriter != null ) - { - try - { - mWriter.close(); - mWriter = null; - } - catch( IOException io ) - { - mLog.error( "Error stopping complex wave recorder [" + - getFile() + "]", io ); - } - } - - mFile = null; + StringBuilder sb = new StringBuilder(); + sb.append(mFilePrefix); + sb.append("_"); + sb.append(TimeStamp.getTimeStamp("_")); + sb.append(".wav"); - if( mProcessorHandle != null ) - { - mProcessorHandle.cancel( true ); - } - - mProcessorHandle = null; - } - else - { - mWriter.writeData( ConversionUtils.convertToSigned16BitSamples( buffer ) ); - buffer = mBuffers.poll(); - } - } + mFile = Paths.get(sb.toString()); + + mWriter = new WaveWriter(mAudioFormat, mFile); + + /* Schedule the processor to run every 500 milliseconds */ + mProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate( + mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS); } - catch ( IOException ioe ) - { - /* Stop this module if/when we get an IO exception */ - mBuffers.clear(); - stop(); - - mLog.error( "IOException while trying to write to the wave " - + "writer", ioe ); - } - } + catch(IOException io) + { + mLog.error("Error starting complex baseband recorder", io); + } + } } - /** - * This is used as a sentinel value to signal the buffer processor to end - */ - public class PoisonPill extends ComplexBuffer - { - public PoisonPill() - { - super( new float[ 1 ] ); - } - } + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + receive(new PoisonPill()); + } + } + + @Override + public void receive(ReusableComplexBuffer buffer) + { + if(mRunning.get()) + { + boolean success = mBuffers.offer(buffer); + + if(!success) + { + mLog.error("recorder buffer overflow - purging [" + + mFile.toFile().getAbsolutePath() + "]"); + + mBuffers.clear(); + } + } + } + + @Override + public Listener getReusableComplexBufferListener() + { + return this; + } + + @Override + public void dispose() + { + stop(); + } + + @Override + public void reset() + { + } + + public class BufferProcessor implements Runnable + { + public void run() + { + try + { + ReusableComplexBuffer reusableComplexBuffer = mBuffers.poll(); + + while(reusableComplexBuffer != null) + { + if(reusableComplexBuffer instanceof PoisonPill) + { + //Clear the buffer queue + reusableComplexBuffer = mBuffers.poll(); + + while(reusableComplexBuffer != null) + { + reusableComplexBuffer.decrementUserCount(); + reusableComplexBuffer = mBuffers.poll(); + } + + if(mWriter != null) + { + try + { + mWriter.close(); + mWriter = null; + } + catch(IOException io) + { + mLog.error("Error stopping complex wave recorder [" + getFile() + "]", io); + } + } + + mFile = null; + + if(mProcessorHandle != null) + { + mProcessorHandle.cancel(true); + } + + mProcessorHandle = null; + } + else + { + mWriter.writeData(ConversionUtils.convertToSigned16BitSamples(reusableComplexBuffer)); + reusableComplexBuffer.decrementUserCount(); + reusableComplexBuffer = mBuffers.poll(); + } + } + } + catch(IOException ioe) + { + /* Stop this module if/when we get an IO exception */ + mBuffers.clear(); + stop(); + + mLog.error("IOException while trying to write to the wave writer", ioe); + } + } + } + + /** + * This is used as a sentinel value to signal the buffer processor to end + */ + public class PoisonPill extends ReusableComplexBuffer + { + public PoisonPill() + { + super(new IReusableBufferDisposedListener() + { + @Override + public void disposed(ReusableComplexBuffer reusableComplexBuffer) + { + //no-op + } + }, new float[2], System.currentTimeMillis()); + } + } } diff --git a/src/main/java/io/github/dsheirer/sample/Broadcaster.java b/src/main/java/io/github/dsheirer/sample/Broadcaster.java index 52df7104..c705bf1d 100644 --- a/src/main/java/io/github/dsheirer/sample/Broadcaster.java +++ b/src/main/java/io/github/dsheirer/sample/Broadcaster.java @@ -25,7 +25,7 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public class Broadcaster implements Listener { - private List> mListeners = new CopyOnWriteArrayList<>(); + protected List> mListeners = new CopyOnWriteArrayList<>(); /** * Implements the Listener interface to receive an element and broadcast that element to all registered diff --git a/src/main/java/io/github/dsheirer/sample/Buffer.java b/src/main/java/io/github/dsheirer/sample/Buffer.java index bb1f6aa4..bb059cb9 100644 --- a/src/main/java/io/github/dsheirer/sample/Buffer.java +++ b/src/main/java/io/github/dsheirer/sample/Buffer.java @@ -17,7 +17,7 @@ package io.github.dsheirer.sample; public class Buffer { - private float[] mSamples; + protected float[] mSamples; /** * Buffer containing float sample data diff --git a/src/main/java/io/github/dsheirer/sample/OverflowableTransferQueue.java b/src/main/java/io/github/dsheirer/sample/OverflowableTransferQueue.java index 6eb49d6d..c2b5a6d9 100644 --- a/src/main/java/io/github/dsheirer/sample/OverflowableTransferQueue.java +++ b/src/main/java/io/github/dsheirer/sample/OverflowableTransferQueue.java @@ -36,9 +36,9 @@ public class OverflowableTransferQueue private IOverflowListener mOverflowListener; private Source mSourceOverflowListener; - private LinkedTransferQueue mQueue = new LinkedTransferQueue(); - private AtomicInteger mCounter = new AtomicInteger(); - private AtomicBoolean mOverflow = new AtomicBoolean(); + protected LinkedTransferQueue mQueue = new LinkedTransferQueue(); + protected AtomicInteger mCounter = new AtomicInteger(); + protected AtomicBoolean mOverflow = new AtomicBoolean(); private int mMaximumSize; private int mResetThreshold; @@ -73,6 +73,21 @@ public class OverflowableTransferQueue setOverflow(true); } } + else + { + overflow(e); + } + } + + /** + * Invoked when the buffer is in an overflow state. The element argument is thrown away. Override this method + * in subclasses to perform any necessary cleanup action(s). + * + * @param e element that is being thrown away due to an overflow condition + */ + protected void overflow(E e) + { + //No-op. Override in subclass to perform any cleanup actions during overflow } /** diff --git a/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java new file mode 100644 index 00000000..705a5448 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/adapter/ComplexShortAdapter.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.sample.adapter; + +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * Converts 16-bit little endian byte data into an array of complex floats + */ +public class ComplexShortAdapter implements IComplexSampleAdapter +{ + private ShortToFloatMap mMap = new ShortToFloatMap(); + private FloatBuffer mFloatBuffer; + private ByteOrder mByteOrder = ByteOrder.LITTLE_ENDIAN; + + @Override + public void convertAndLoad(byte[] samples, ReusableComplexBuffer reusableComplexBuffer) + { + int sampleCount = samples.length / 2; + + if(mFloatBuffer == null || mFloatBuffer.capacity() != sampleCount) + { + mFloatBuffer = FloatBuffer.allocate(sampleCount); + } + + mFloatBuffer.rewind(); + + /* Wrap byte array in a byte buffer so we can process them as shorts */ + ByteBuffer buffer = ByteBuffer.wrap(samples); + + /* Set endian to correct byte ordering */ + buffer.order(mByteOrder); + + while(buffer.hasRemaining()) + { + mFloatBuffer.put(mMap.get(buffer.getShort())); + } + + reusableComplexBuffer.reloadFrom(mFloatBuffer, System.currentTimeMillis()); + } +} diff --git a/src/main/java/io/github/dsheirer/sample/adapter/IComplexSampleAdapter.java b/src/main/java/io/github/dsheirer/sample/adapter/IComplexSampleAdapter.java new file mode 100644 index 00000000..471ee032 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/adapter/IComplexSampleAdapter.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.sample.adapter; + +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; + +public interface IComplexSampleAdapter +{ + /** + * Converts the sample byte array into complex float samples, loading the converted float samples into the buffer. + * @param samples + * @param reusableComplexBuffer + */ + void convertAndLoad(byte[] samples, ReusableComplexBuffer reusableComplexBuffer); +} diff --git a/src/main/java/io/github/dsheirer/sample/complex/ComplexBuffer.java b/src/main/java/io/github/dsheirer/sample/complex/ComplexBuffer.java index 884a6f20..edc3d4ce 100644 --- a/src/main/java/io/github/dsheirer/sample/complex/ComplexBuffer.java +++ b/src/main/java/io/github/dsheirer/sample/complex/ComplexBuffer.java @@ -19,9 +19,6 @@ import io.github.dsheirer.sample.Buffer; public class ComplexBuffer extends Buffer { - //Default timestamp - we use a static variable here so that there is only ever 1 instance in memory - private static final long DEFAULT_TIMESTAMP = 0; - /** * Wrapper around float array containing interleaved I/Q samples */ @@ -29,24 +26,4 @@ public class ComplexBuffer extends Buffer { super(samples); } - - - /** - * Reference timestamp for the first sample in this buffer. This functionality is intended to be implemented by a - * subclass implementation. The default value is 0. - * - * @return reference timestamp or a value of 0 if hasTimestamp() indicates false. - */ - public long getTimestamp() - { - return DEFAULT_TIMESTAMP; - } - - /** - * Indicates if this buffer has a timestamp that references the first sample in the buffer. - */ - public boolean hasTimestamp() - { - return false; - } } diff --git a/src/main/java/io/github/dsheirer/sample/complex/reusable/OverflowableReusableBufferTransferQueue.java b/src/main/java/io/github/dsheirer/sample/complex/reusable/OverflowableReusableBufferTransferQueue.java new file mode 100644 index 00000000..05486981 --- /dev/null +++ b/src/main/java/io/github/dsheirer/sample/complex/reusable/OverflowableReusableBufferTransferQueue.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.sample.complex.reusable; + +import io.github.dsheirer.sample.OverflowableTransferQueue; + +public class OverflowableReusableBufferTransferQueue extends OverflowableTransferQueue +{ + /** + * Concurrent transfer queue that couples a higher-throughput linked transfer queue with an atomic integer for + * monitoring queue size. When the queue size exceeds maximum size (overflow), all inbound elements are ignored + * until the queue size is reduced to or below the reset threshold. + * + * This implementation includes special handling for reusable complex buffers. + * + * @param maximumSize of the queue. Overflow state will occur once queue size exceeds this value. + * @param resetThreshold for resetting overflow state to normal, once queue size is at or below this value. + */ + public OverflowableReusableBufferTransferQueue(int maximumSize, int resetThreshold) + { + super(maximumSize, resetThreshold); + } + + /** + * Overrides the overflow method to decrement the user count on any buffers that are being discarded when the queue + * is in an overflow state. + * + * @param reusableComplexBuffer that will be discarded + */ + @Override + protected void overflow(ReusableComplexBuffer reusableComplexBuffer) + { + reusableComplexBuffer.decrementUserCount(); + } + + /** + * Overrides the buffer clear method to decrement the user count on each buffer that is being cleared from the queue. + */ + @Override + public void clear() + { + synchronized(mQueue) + { + ReusableComplexBuffer buffer = mQueue.poll(); + + while(buffer != null) + { + buffer.decrementUserCount(); + buffer = mQueue.poll(); + } + + mCounter.set(0); + mOverflow.set(false); + } + } +} diff --git a/src/main/java/io/github/dsheirer/source/ComplexSource.java b/src/main/java/io/github/dsheirer/source/ComplexSource.java index e23e3305..a06be2dc 100644 --- a/src/main/java/io/github/dsheirer/source/ComplexSource.java +++ b/src/main/java/io/github/dsheirer/source/ComplexSource.java @@ -1,10 +1,25 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ package io.github.dsheirer.source; import io.github.dsheirer.sample.Provider; import io.github.dsheirer.sample.SampleType; -import io.github.dsheirer.sample.complex.ComplexBuffer; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; -public abstract class ComplexSource extends Source implements Provider +public abstract class ComplexSource extends Source implements Provider { @Override public SampleType getSampleType() diff --git a/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java b/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java index 735afe52..fc117def 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java +++ b/src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java @@ -1,25 +1,24 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 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 + * 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 + * ******************************************************************************/ package io.github.dsheirer.source.mixer; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.ISampleAdapter; -import io.github.dsheirer.sample.complex.ComplexBuffer; +import io.github.dsheirer.sample.adapter.IComplexSampleAdapter; +import io.github.dsheirer.sample.complex.reusable.ReusableBufferQueue; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,87 +31,72 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ComplexMixer { - private final static Logger mLog = - LoggerFactory.getLogger( ComplexMixer.class ); + private final static Logger mLog = LoggerFactory.getLogger(ComplexMixer.class); - private long mFrequency = 0; - private int mBufferSize = 16384; - - private BufferReader mBufferReader = new BufferReader(); - private TargetDataLine mTargetDataLine; - private AudioFormat mAudioFormat; - private String mName; - private int mBytesPerFrame = 0; - private ISampleAdapter mSampleAdapter; - private Listener mListener; - - /** - * Complex Mixer - constructs a reader on the mixer/sound card target - * data line using the specified audio format (sample size, sample rate ) - * and broadcasts complex (I/Q) sample buffers to all registered listeners. - * Reads sample buffers sized to 10% of the sample rate specified in audio - * format. - * - * @param targetDataLine - mixer or sound card to be used - * - * @param format - audio format - * - * @param name - token name to use for this mixer - * - * @param sampleAdapter - adapter to convert byte array data read from the - * mixer into ComplexBuffer. The adapter can optionally invert the channel - * data if the left/right stereo channels are inverted. - */ - public ComplexMixer( TargetDataLine targetDataLine, - AudioFormat format, - String name, - ISampleAdapter sampleAdapter, - Listener listener ) + private long mFrequency = 0; + + private BufferReader mBufferReader = new BufferReader(); + private TargetDataLine mTargetDataLine; + private AudioFormat mAudioFormat; + private String mName; + private int mBytesPerFrame = 0; + private IComplexSampleAdapter mSampleAdapter; + private Listener mListener; + private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue(); + + /** + * Complex Mixer - constructs a reader on the mixer/sound card target data line using the specified audio + * format (sample size, sample rate ) and broadcasts complex (I/Q) sample buffers to all registered listeners. + * Reads sample buffers sized to 10% of the sample rate specified in audio format. + * + * @param targetDataLine - mixer or sound card to be used + * @param format - audio format + * @param name - token name to use for this mixer + * @param complexSampleAdapter - adapter to convert byte array data read from the mixer into ComplexBuffer. + * @param listener to receive generated complex buffers + */ + public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name, + IComplexSampleAdapter complexSampleAdapter, Listener listener) { - mTargetDataLine = targetDataLine; - mName = name; + mTargetDataLine = targetDataLine; + mName = name; mAudioFormat = format; - mSampleAdapter = sampleAdapter; + mSampleAdapter = complexSampleAdapter; mListener = listener; } - - public TargetDataLine getTargetDataLine() - { - return mTargetDataLine; - } - + public void start() { - if( !mBufferReader.isRunning() ) - { - Thread thread = new Thread( mBufferReader ); - thread.setDaemon( true ); - thread.setName( mName + " Complex Sample Reader" ); - thread.start(); - } + if(!mBufferReader.isRunning()) + { + Thread thread = new Thread(mBufferReader); + thread.setDaemon(true); + thread.setName(mName + " Complex Sample Reader"); + thread.start(); + } } public void stop() { - mBufferReader.stop(); + mBufferReader.stop(); + } + + @Override + public String toString() + { + return mName; } - @Override - public String toString() - { - return mName; - } - public int getSampleRate() { - if( mTargetDataLine != null ) - { - return (int)mTargetDataLine.getFormat().getSampleRate(); - } - else - { - return 0; - } + if(mTargetDataLine != null) + { + return (int)mTargetDataLine.getFormat().getSampleRate(); + } + else + { + return 0; + } } /** @@ -120,7 +104,7 @@ public class ComplexMixer */ public long getFrequency() throws SourceException { - return mFrequency; + return mFrequency; } /** @@ -128,125 +112,119 @@ public class ComplexMixer * be useful if you are streaming an external audio source in through the * sound card and you want to specify a frequency for that source */ - public void setFrequency( long frequency ) + public void setFrequency(long frequency) { - mFrequency = frequency; + mFrequency = frequency; } - /** - * Reader thread. Performs blocking read against the mixer target data - * line, converts the samples to an array of floats using the specified - * adapter. Dispatches float arrays to all registered listeners. - */ - public class BufferReader implements Runnable - { - private AtomicBoolean mRunning = new AtomicBoolean(); - - @Override + /** + * Reader thread. Performs blocking read against the mixer target data + * line, converts the samples to an array of floats using the specified + * adapter. Dispatches float arrays to all registered listeners. + */ + public class BufferReader implements Runnable + { + private AtomicBoolean mRunning = new AtomicBoolean(); + + @Override public void run() { - if( mRunning.compareAndSet( false, true ) ) - { - if( mTargetDataLine == null ) - { - mRunning.set( false ); + if(mRunning.compareAndSet(false, true)) + { + if(mTargetDataLine == null) + { + mRunning.set(false); + mLog.error("ComplexMixerSource - mixer target data line is null"); + } + else + { + mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8; - mLog.error( "ComplexMixerSource - mixer target data line" - + " is null" ); - } - else - { - mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8; - - if( mBytesPerFrame == AudioSystem.NOT_SPECIFIED ) - { - mBytesPerFrame = 2; - } - - /* Set buffer size to 1/10 second of samples */ - mBufferSize = (int)( mAudioFormat.getSampleRate() - * 0.05 ) * mBytesPerFrame; - - /* We'll reuse the same buffer for each read */ - byte[] buffer = new byte[ mBufferSize ]; - - try + if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED) { - mTargetDataLine.open( mAudioFormat ); - - mTargetDataLine.start(); + mBytesPerFrame = 2; } - catch ( LineUnavailableException e ) + + //Set buffer size to 1/10 second of samples + int sampleCount = (int)(mAudioFormat.getSampleRate() * 0.10) * 2; + int bufferSize = sampleCount * mBytesPerFrame; + + //We'll reuse the same buffer for each read + byte[] buffer = new byte[bufferSize]; + + try { - mLog.error( "ComplexMixerSource - mixer target data line" - + "not available to read data from", e ); - - mRunning.set( false ); + mTargetDataLine.open(mAudioFormat); + mTargetDataLine.start(); + } + catch(LineUnavailableException e) + { + mLog.error("ComplexMixerSource - mixer target data line not available to read data from", e); + mRunning.set(false); } - - while( mRunning.get() ) - { - try - { - /* Blocking read - waits until the buffer fills */ - mTargetDataLine.read( buffer, 0, buffer.length ); - /* Convert samples to float array */ - float[] samples = - mSampleAdapter.convert( buffer ); - - /* Dispatch samples to registered listeners */ - if( mListener != null ) - { - mListener.receive( new ComplexBuffer( samples ) ); - } - } - catch ( Exception e ) + while(mRunning.get()) + { + try { - mLog.error( "error while reading" - + "from the mixer target data line", e ); - - mRunning.set( false ); + //Blocking read - waits until the buffer fills + mTargetDataLine.read(buffer, 0, buffer.length); + + /* Dispatch samples to registered listeners */ + if(mListener != null) + { + ReusableComplexBuffer reusableComplexBuffer = mReusableBufferQueue.getBuffer(sampleCount); + + //Convert samples and load into reusable buffer + mSampleAdapter.convertAndLoad(buffer, reusableComplexBuffer); + + mListener.receive(reusableComplexBuffer); + } } - } - } - - /* Close the data line if it is still open */ - if( mTargetDataLine != null && mTargetDataLine.isOpen() ) - { - mTargetDataLine.close(); - } - } + catch(Exception e) + { + mLog.error("error while reading from the mixer target data line", e); + mRunning.set(false); + } + } + } + + /* Close the data line if it is still open */ + if(mTargetDataLine != null && mTargetDataLine.isOpen()) + { + mTargetDataLine.close(); + } + } } - /** - * Stops the reader thread - */ - public void stop() - { - if( mRunning.compareAndSet( true, false ) ) - { - mTargetDataLine.stop(); - mTargetDataLine.close(); - } - } + /** + * Stops the reader thread + */ + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + mTargetDataLine.stop(); + mTargetDataLine.close(); + } + } - /** - * Indicates if the reader thread is running - */ - public boolean isRunning() - { - return mRunning.get(); - } - } + /** + * Indicates if the reader thread is running + */ + public boolean isRunning() + { + return mRunning.get(); + } + } public void dispose() { - if( mBufferReader != null ) - { - mBufferReader.stop(); - } - - mListener = null; + if(mBufferReader != null) + { + mBufferReader.stop(); + } + + mListener = null; } } diff --git a/src/main/java/io/github/dsheirer/source/recording/Recording.java b/src/main/java/io/github/dsheirer/source/recording/Recording.java index 1fb2495f..77be419b 100644 --- a/src/main/java/io/github/dsheirer/source/recording/Recording.java +++ b/src/main/java/io/github/dsheirer/source/recording/Recording.java @@ -19,7 +19,6 @@ import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.source.tuner.ITunerChannelProvider; import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; @@ -27,16 +26,14 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -public class Recording implements Comparable, ISourceEventProcessor, ITunerChannelProvider +public class Recording implements Comparable, ISourceEventProcessor { private RecordingConfiguration mConfiguration; private List mTunerChannels = new ArrayList(); - @SuppressWarnings( "unused" ) private long mCenterFrequency; - @SuppressWarnings( "unused" ) private SettingsManager mSettingsManager; public Recording( SettingsManager settingsManager, @@ -75,19 +72,12 @@ public class Recording implements Comparable, ISourceEventProcessor, return mTunerChannels.size() > 0; } - @Override public TunerChannelSource getChannel( TunerChannel channel ) { // TODO Auto-generated method stub return null; } - @Override - public void releaseChannel( TunerChannelSource source ) - { - // TODO Auto-generated method stub - } - public RecordingConfiguration getRecordingConfiguration() { return mConfiguration; diff --git a/src/main/java/io/github/dsheirer/source/tuner/ITunerChannelProvider.java b/src/main/java/io/github/dsheirer/source/tuner/ITunerChannelProvider.java deleted file mode 100644 index 550931c9..00000000 --- a/src/main/java/io/github/dsheirer/source/tuner/ITunerChannelProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************* - * SDR Trunk - * 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 - ******************************************************************************/ -package io.github.dsheirer.source.tuner; - -import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.source.tuner.channel.TunerChannel; -import io.github.dsheirer.source.tuner.channel.TunerChannelSource; - -import java.util.concurrent.RejectedExecutionException; - -public interface ITunerChannelProvider -{ - /** - * Returns a tuner frequency channel source, tuned to the correct frequency - * - * @return - source for 48k sample rate - */ - public abstract TunerChannelSource getChannel(TunerChannel channel ) - throws RejectedExecutionException, SourceException; - - /** - * Releases the tuned channel resources - */ - public abstract void releaseChannel( TunerChannelSource source ); - - -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java index 5a7b4abf..60394ee6 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java @@ -17,10 +17,10 @@ ******************************************************************************/ package io.github.dsheirer.source.tuner; -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.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.source.ISourceEventListener; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -33,10 +33,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener, - IComplexBufferProvider, Listener + IReusableComplexBufferProvider, Listener { private final static Logger mLog = LoggerFactory.getLogger(TunerController.class); - protected Broadcaster mSampleBroadcaster = new Broadcaster<>(); + protected ReusableBufferBroadcaster mSampleBroadcaster = new ReusableBufferBroadcaster(); protected FrequencyController mFrequencyController; private int mMiddleUnusableHalfBandwidth; private double mUsableBandwidthPercentage; @@ -89,13 +89,13 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, case REQUEST_START_SAMPLE_STREAM: if(sourceEvent.getSource() instanceof Listener) { - addComplexBufferListener((Listener)sourceEvent.getSource()); + addBufferListener((Listener)sourceEvent.getSource()); } break; case REQUEST_STOP_SAMPLE_STREAM: if(sourceEvent.getSource() instanceof Listener) { - removeComplexBufferListener((Listener)sourceEvent.getSource()); + removeBufferListener((Listener)sourceEvent.getSource()); } break; default: @@ -230,7 +230,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, * Adds the listener to receive complex buffer samples */ @Override - public void addComplexBufferListener(Listener listener) + public void addBufferListener(Listener listener) { mSampleBroadcaster.addListener(listener); } @@ -239,7 +239,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, * Removes the listener from receiving complex buffer samples */ @Override - public void removeComplexBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mSampleBroadcaster.removeListener(listener); } @@ -248,7 +248,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, * Indicates if there are any complex buffer listeners registered on this controller */ @Override - public boolean hasComplexBufferListeners() + public boolean hasBufferListeners() { return mSampleBroadcaster.hasListeners(); } @@ -256,16 +256,16 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor, /** * Broadcasts the buffer to any registered listeners */ - protected void broadcast(ComplexBuffer complexBuffer) + protected void broadcast(ReusableComplexBuffer reusableComplexBuffer) { - mSampleBroadcaster.broadcast(complexBuffer); + mSampleBroadcaster.broadcast(reusableComplexBuffer.incrementUserCount()); } /** * Implements the Listener interface to receive and distribute complex buffers from subclass implementations */ @Override - public void receive(ComplexBuffer complexBuffer) + public void receive(ReusableComplexBuffer complexBuffer) { broadcast(complexBuffer); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerManager.java b/src/main/java/io/github/dsheirer/source/tuner/TunerManager.java index 74ee26b0..91aae7ba 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerManager.java @@ -31,6 +31,7 @@ import io.github.dsheirer.source.tuner.rtl.RTL2832Tuner; import io.github.dsheirer.source.tuner.rtl.RTL2832TunerController; import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerController; import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerController; +import io.github.dsheirer.source.tuner.usb.USBMasterProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.usb4java.Device; @@ -53,11 +54,11 @@ public class TunerManager * buffers can register with this processor and the processor will auto-start and auto-stop while USB transfer * processors are registered. */ - public static final LibUSBTransferProcessor LIBUSB_TRANSFER_PROCESSOR; + public static final USBMasterProcessor LIBUSB_TRANSFER_PROCESSOR; static { - LIBUSB_TRANSFER_PROCESSOR = new LibUSBTransferProcessor(); + LIBUSB_TRANSFER_PROCESSOR = new USBMasterProcessor(); } public TunerManager(MixerManager mixerManager, TunerModel tunerModel) diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleAdapter.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java similarity index 54% rename from src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleAdapter.java rename to src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java index fa0fa459..799d4e84 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleAdapter.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspySampleConverter.java @@ -20,25 +20,46 @@ package io.github.dsheirer.source.tuner.airspy; import io.github.dsheirer.dsp.filter.dc.DCRemovalFilter_RB; import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform; -import io.github.dsheirer.sample.adapter.ISampleAdapter; +import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; -public class AirspySampleAdapter implements ISampleAdapter +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +public class AirspySampleConverter extends NativeBufferConverter { private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f; private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f); private HilbertTransform mHilbertTransform = new HilbertTransform(); private boolean mSamplePacking = false; - + private FloatBuffer mFloatBuffer; + private float[] mConvertedSamples; /** * Adapter to translate byte buffers received from the airspy tuner into * float buffers for processing. */ - public AirspySampleAdapter() + public AirspySampleConverter() { } + @Override + protected FloatBuffer convertSamples(ByteBuffer buffer, int length) + { + float[] samples = convert(buffer); + + if(mFloatBuffer == null || mFloatBuffer.capacity() != samples.length) + { + mFloatBuffer = FloatBuffer.allocate(samples.length); + } + + mFloatBuffer.rewind(); + mFloatBuffer.put(samples); + + return mFloatBuffer; + } + /** * Sample packing places two 12-bit samples into 3 bytes when enabled or * places two 12-bit samples into 4 bytes when disabled. @@ -50,98 +71,85 @@ public class AirspySampleAdapter implements ISampleAdapter mSamplePacking = enabled; } - @Override - public float[] convert(byte[] samples) + private float[] convert(ByteBuffer samples) { - float[] realSamples; - if(mSamplePacking) { - realSamples = convertPacked(samples); + convertPacked(samples.order(ByteOrder.LITTLE_ENDIAN)); } else { - realSamples = convertUnpacked(samples); + convertUnpacked(samples); } - mDCFilter.filter(realSamples); + mDCFilter.filter(mConvertedSamples); - return mHilbertTransform.filter(realSamples); + return mHilbertTransform.filter(mConvertedSamples); } /** * Converts the byte array containing unsigned 12-bit short values into * signed float values in the range -1 to 1; * - * @param data - byte array of unsigned 16-bit values - * @return converted float values + * @param buffer - native byte buffer containing unsigned 16-bit values */ - private float[] convertUnpacked(byte[] data) + private void convertUnpacked(ByteBuffer buffer) { - float[] samples = new float[data.length / 2]; + buffer.rewind(); + + if(mConvertedSamples == null || mConvertedSamples.length != buffer.capacity() / 2) + { + mConvertedSamples = new float[buffer.capacity() / 2]; + } int pointer = 0; - for(int x = 0; x < data.length; x += 2) - { - samples[pointer++] = scale((data[x] & 0xFF) | - (data[x + 1] << 8)); - } - return samples; + while(buffer.remaining() >= 2) + { + byte lsb = buffer.get(); + byte msb = buffer.get(); + mConvertedSamples[pointer++] = scale((lsb & 0xFF) | (msb << 8)); + } } /** * Converts every 3 bytes containing a pair of 12-bit unsigned values into * a pair of float values in the range -1 to 1; * - * @param data1 - byte array of unsigned 12-bit values - * @return converted float values + * @param buffer - native byte buffer containing packet 12-bit unsigned samples */ - private float[] convertPacked(byte[] data1) + private void convertPacked(ByteBuffer buffer) { - byte[] data = new byte[data1.length]; + buffer.rewind(); - //Convert big-endian to little-endian - for(int x = 0; x < data1.length; x += 4) + int sampleCount = buffer.capacity() / 3 * 2; + + if(mConvertedSamples == null || mConvertedSamples.length != sampleCount) { - data[x] = data1[x + 3]; - data[x + 1] = data1[x + 2]; - data[x + 2] = data1[x + 1]; - data[x + 3] = data1[x]; + mConvertedSamples = new float[sampleCount]; } - int count = (int) ((float) data.length / 1.5f); - - /* Ensure we have an even number of samples */ - if(count % 2 == 1) - { - count--; - } - - int bytes = (int) ((float) count * 1.5f); - - float[] samples = new float[count]; - int pointer = 0; + byte b1; + byte b2; + byte b3; int first; int second; - for(int x = 0; x < bytes; x += 3) + while(buffer.remaining() >= 3) { - first = ((data[x] << 4) & 0xFF0) | - ((data[x + 1] >> 4) & 0xF); + b1 = buffer.get(); + b2 = buffer.get(); + b3 = buffer.get(); - samples[pointer++] = scale(first); + first = ((b1 << 4) & 0xFF0) | ((b2 >> 4) & 0xF); + mConvertedSamples[pointer++] = scale(first); - second = ((data[x + 1] << 8) & 0xF00) | - (data[x + 2] & 0xFF); - - samples[pointer++] = scale(second); + second = ((b2 << 8) & 0xF00) | (b3 & 0xFF); + mConvertedSamples[pointer++] = scale(second); } - - return samples; } /** diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java index 8ea4ad65..a709bad9 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerController.java @@ -22,11 +22,12 @@ import java.util.List; /** * SDR Trunk - * Copyright (C) 2015-2017 Dennis Sheirer + * Copyright (C) 2015-2018 Dennis Sheirer * + * ----------------------------------------------------------------------------- * Ported from libairspy at: * https://github.com/airspy/host/tree/master/libairspy - * ----------------------------------------------------------------------------- + * * Copyright (c) 2013, Michael Ossmann * Copyright (c) 2012, Jared Boone * Copyright (c) 2014, Youssef Touil @@ -94,7 +95,7 @@ public class AirspyTunerController extends USBTunerController private Device mDevice; private DeviceHandle mDeviceHandle; - private AirspySampleAdapter mSampleAdapter = new AirspySampleAdapter(); + private AirspySampleConverter mSampleAdapter = new AirspySampleConverter(); private AirspyDeviceInformation mDeviceInfo; private List mSampleRates = new ArrayList<>(); private int mSampleRate = 0; @@ -122,8 +123,7 @@ public class AirspyTunerController extends USBTunerController + "airspy rules file in \\etc\\udev\\rules.d ??"); } - throw new SourceException("Couldn't open airspy device - " + - LibUsb.strError(result)); + throw new SourceException("Couldn't open airspy device - " + LibUsb.strError(result)); } try diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java index ddd74ab4..efddbc58 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/CICTunerChannelSource.java @@ -25,7 +25,6 @@ import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.OverflowableTransferQueue; import io.github.dsheirer.sample.complex.Complex; import io.github.dsheirer.sample.complex.ComplexBuffer; -import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.tuner.Tuner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +32,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; -public class CICTunerChannelSource extends TunerChannelSource +public class CICTunerChannelSource { private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class); @@ -69,14 +68,12 @@ public class CICTunerChannelSource extends TunerChannelSource */ public CICTunerChannelSource(Tuner tuner, TunerChannel tunerChannel) { - super(tuner.getTunerController().getSourceEventListener(), tunerChannel); - mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD); - mBuffer.setSourceOverflowListener(this); +// mBuffer.setSourceOverflowListener(this); //Setup the frequency translator to the current source frequency mTunerFrequency = tuner.getTunerController().getFrequency(); - long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency(); + long frequencyOffset = mTunerFrequency - tunerChannel.getFrequency(); mFrequencyCorrectionMixer = new Oscillator(frequencyOffset, tuner.getTunerController().getSampleRate()); //Setup the decimation filter chain @@ -87,7 +84,6 @@ public class CICTunerChannelSource extends TunerChannelSource * Sets the center frequency for the incoming sample buffers * @param frequency in hertz */ - @Override protected void setFrequency(long frequency) { mTunerFrequency = frequency; @@ -102,7 +98,6 @@ public class CICTunerChannelSource extends TunerChannelSource * Current frequency correction being applied to the channel sample stream * @return correction in hertz */ - @Override public long getFrequencyCorrection() { return mChannelFrequencyCorrection; @@ -118,7 +113,7 @@ public class CICTunerChannelSource extends TunerChannelSource updateMixerFrequencyOffset(); - broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection)); +// broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection)); } /** @@ -130,7 +125,6 @@ public class CICTunerChannelSource extends TunerChannelSource mBuffer.offer(buffer); } - @Override public void setListener(Listener listener) { /* Save a pointer to the listener so that if we have to change the @@ -140,7 +134,6 @@ public class CICTunerChannelSource extends TunerChannelSource mDecimationFilter.setListener(listener); } - @Override public void removeListener(Listener listener) { mDecimationFilter.removeListener(); @@ -165,7 +158,7 @@ public class CICTunerChannelSource extends TunerChannelSource mTunerSampleRate = sampleRate; - broadcastConsumerSourceEvent(SourceEvent.channelSampleRateChange(getSampleRate())); +// broadcastConsumerSourceEvent(SourceEvent.channelSampleRateChange(getSampleRate())); } } @@ -175,8 +168,8 @@ public class CICTunerChannelSource extends TunerChannelSource */ private void updateMixerFrequencyOffset() { - long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection; - mFrequencyCorrectionMixer.setFrequency(offset); +// long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection; +// mFrequencyCorrectionMixer.setFrequency(offset); } public double getSampleRate() @@ -184,7 +177,6 @@ public class CICTunerChannelSource extends TunerChannelSource return CHANNEL_RATE; } - @Override protected void processSamples() { mBuffer.drainTo(mSampleBuffers, 20); diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java index 7137b611..396d223c 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/TunerChannelSource.java @@ -19,7 +19,7 @@ package io.github.dsheirer.source.tuner.channel; 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.ComplexSource; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -89,7 +89,7 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc * Sets the listener to receive the complex buffer sample output from this channel * @param complexBufferListener to receive complex buffers */ - public abstract void setListener(Listener complexBufferListener); + public abstract void setListener(Listener complexBufferListener); /** * Commands sub-class to process queued samples and distribute them to the consumer. This method will be invoked diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java index db6cf1bd..9e77661e 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java @@ -19,8 +19,8 @@ package io.github.dsheirer.source.tuner.fcd; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.ShortAdapter; -import io.github.dsheirer.sample.complex.ComplexBuffer; +import io.github.dsheirer.sample.adapter.ComplexShortAdapter; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.mixer.ComplexMixer; import io.github.dsheirer.source.tuner.MixerTunerDataLine; @@ -87,19 +87,19 @@ public abstract class FCDTunerController extends TunerController mLog.error("Error setting sample rate to [" + audioFormat.getSampleRate() + "]", se); } - mComplexMixer = new ComplexMixer( mixerTDL.getTargetDataLine(), audioFormat, tunerName, new ShortAdapter(), - mSampleBroadcaster); + mComplexMixer = new ComplexMixer( mixerTDL.getTargetDataLine(), audioFormat, tunerName, + new ComplexShortAdapter(), mSampleBroadcaster); } /** * Overrides the super class functionality to auto-start the complex mixer and provide samples to listeners */ @Override - public void addComplexBufferListener(Listener listener) + public void addBufferListener(Listener listener) { - boolean hasExistingListeners = hasComplexBufferListeners(); + boolean hasExistingListeners = hasBufferListeners(); - super.addComplexBufferListener(listener); + super.addBufferListener(listener); if(!hasExistingListeners) { @@ -112,11 +112,11 @@ public abstract class FCDTunerController extends TunerController * more registered listeners */ @Override - public void removeComplexBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { - super.removeComplexBufferListener(listener); + super.removeBufferListener(listener); - if(!hasComplexBufferListeners()) + if(!hasBufferListeners()) { mComplexMixer.stop(); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java index 01773f25..e9082ec7 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerController.java @@ -53,11 +53,12 @@ ******************************************************************************/ package io.github.dsheirer.source.tuner.hackrf; -import io.github.dsheirer.sample.adapter.ByteSampleAdapter; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; import io.github.dsheirer.source.tuner.usb.USBTunerController; +import io.github.dsheirer.source.tuner.usb.converter.ByteSampleConverter; +import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; import org.apache.commons.io.EndianUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,7 +91,7 @@ public class HackRFTunerController extends USBTunerController public static final double USABLE_BANDWIDTH_PERCENT = 0.95; public static final int DC_SPIKE_AVOID_BUFFER = 5000; - private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter(); + private NativeBufferConverter mNativeBufferConverter = new ByteSampleConverter(); private USBTransferProcessor mUSBTransferProcessor; private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ; @@ -153,7 +154,7 @@ public class HackRFTunerController extends USBTunerController name = "HackRF - Unidentified Serial"; } - mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mSampleAdapter, USB_TRANSFER_BUFFER_SIZE); + mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mNativeBufferConverter, USB_TRANSFER_BUFFER_SIZE); } @Override diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java index c71218a2..a6c8c138 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/RTL2832TunerController.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014-2017 Dennis Sheirer + * Copyright (C) 2014-2018 Dennis Sheirer * * Java version based on librtlsdr * Copyright (C) 2012-2013 by Steve Markgraf @@ -22,14 +22,14 @@ package io.github.dsheirer.source.tuner.rtl; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.adapter.ByteSampleAdapter; -import io.github.dsheirer.sample.adapter.ISampleAdapter; -import io.github.dsheirer.sample.complex.ComplexBuffer; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.tuner.TunerManager; import io.github.dsheirer.source.tuner.TunerType; import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; import io.github.dsheirer.source.tuner.usb.USBTunerController; +import io.github.dsheirer.source.tuner.usb.converter.ByteSampleConverter; +import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.usb4java.Device; @@ -77,7 +77,7 @@ public abstract class RTL2832TunerController extends USBTunerController private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE; - protected ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter(); + protected NativeBufferConverter mNativeBufferConverter = new ByteSampleConverter(); protected int mOscillatorFrequency = 28800000; //28.8 MHz protected USBTransferProcessor mUSBTransferProcessor; protected Descriptor mDescriptor; @@ -91,11 +91,7 @@ public abstract class RTL2832TunerController extends USBTunerController long maxTunableFrequency, int centerUnusableBandwidth, double usableBandwidthPercentage) throws SourceException { - super(minTunableFrequency, - maxTunableFrequency, - centerUnusableBandwidth, - usableBandwidthPercentage); - + super(minTunableFrequency, maxTunableFrequency, centerUnusableBandwidth, usableBandwidthPercentage); mDevice = device; mDeviceDescriptor = deviceDescriptor; } @@ -158,7 +154,7 @@ public abstract class RTL2832TunerController extends USBTunerController String deviceName = getTunerType().getLabel() + " " + getUniqueID(); - mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter, + mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferConverter, USB_TRANSFER_BUFFER_SIZE); } @@ -1498,11 +1494,11 @@ public abstract class RTL2832TunerController extends USBTunerController * Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started. */ @Override - public void addComplexBufferListener(Listener listener) + public void addBufferListener(Listener listener) { - boolean hasExistingListeners = hasComplexBufferListeners(); + boolean hasExistingListeners = hasBufferListeners(); - super.addComplexBufferListener(listener); + super.addBufferListener(listener); if(!hasExistingListeners) { @@ -1514,11 +1510,11 @@ public abstract class RTL2832TunerController extends USBTunerController * Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners. */ @Override - public void removeComplexBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { - super.removeComplexBufferListener(listener); + super.removeBufferListener(listener); - if(!hasComplexBufferListeners()) + if(!hasBufferListeners()) { mUSBTransferProcessor.removeListener(); } @@ -1536,12 +1532,12 @@ public abstract class RTL2832TunerController extends USBTunerController * * @param deviceName to use when logging information or errors * @param deviceHandle to the USB bulk transfer device - * @param sampleAdapter specific to the tuner's byte buffer format for converting to floating point I/Q samples + * @param nativeBufferConverter specific to the tuner's byte buffer format for converting to floating point I/Q samples * @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144. */ - public RTL2832USBTransferProcessor(String deviceName, DeviceHandle deviceHandle, ISampleAdapter sampleAdapter, int bufferSize) + public RTL2832USBTransferProcessor(String deviceName, DeviceHandle deviceHandle, NativeBufferConverter nativeBufferConverter, int bufferSize) { - super(deviceName, deviceHandle, sampleAdapter, bufferSize); + super(deviceName, deviceHandle, nativeBufferConverter, bufferSize); } @Override diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java index 79a6e00a..dcc0c24b 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerController.java @@ -252,7 +252,7 @@ public class E4KTunerController extends RTL2832TunerController String deviceName = getTunerType().getLabel() + " " + getUniqueID(); - mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter, + mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mNativeBufferConverter, USB_TRANSFER_BUFFER_SIZE); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java index b4a09a40..123fde8f 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/SampleGenerator.java @@ -17,10 +17,10 @@ package io.github.dsheirer.source.tuner.test; import io.github.dsheirer.dsp.mixer.IOscillator; import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator; -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.TimestampedComplexBuffer; +import io.github.dsheirer.sample.complex.reusable.ReusableBufferBroadcaster; +import io.github.dsheirer.sample.complex.reusable.ReusableBufferQueue; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,7 +32,7 @@ public class SampleGenerator { private final static Logger mLog = LoggerFactory.getLogger(SampleGenerator.class); - private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>(); + private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster(); private IOscillator mOscillator; private int mSweepUpdateInterval; private long mInterval; @@ -79,21 +79,6 @@ public class SampleGenerator this(sampleRate, frequency, interval, 0); } - /** - * Instructs the sample generator to reuse the same sample buffer each time instead of generating new samples for - * each and every buffer. This is useful when you simply want to generate data to test or profile data flows and - * are not concerned about the content of the buffers. - * - * The generator will create a new buffer of data each time that it detects that the sample size changes or when - * the tone frequency changes. - * - * @param reuseBuffers set to true to turn on buffer reuse - */ - public void setReuseBuffers(boolean reuseBuffers) - { - mReuseBuffers = reuseBuffers; - } - /** * Updates the number of complex samples generated per interval */ @@ -139,7 +124,7 @@ public class SampleGenerator * * @param listener to receive complex sample buffers */ - public void addListener(Listener listener) + public void addListener(Listener listener) { mComplexBufferBroadcaster.addListener(listener); @@ -152,7 +137,7 @@ public class SampleGenerator /** * Removes the listener and stops the sample generator if there are no more listeners. */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mComplexBufferBroadcaster.removeListener(listener); @@ -200,31 +185,18 @@ public class SampleGenerator public class Generator implements Runnable { private int mTriggerInterval = 0; - private long mOscillatorFrequency = 1; - private ComplexBuffer mReusableBuffer; + private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("** Sample Generator **"); @Override public void run() { if(mComplexBufferBroadcaster.hasListeners()) { - if(mReuseBuffers) - { - if(mReusableBuffer == null || - mReusableBuffer.getSamples().length != mSamplesPerInterval || - mOscillatorFrequency != mOscillator.getFrequency()) - { - mOscillatorFrequency = (long)mOscillator.getFrequency(); - mReusableBuffer = new TimestampedComplexBuffer(mOscillator.generateComplex(mSamplesPerInterval)); - } + ReusableComplexBuffer reusableComplexBuffer = mReusableBufferQueue.getBuffer(mSamplesPerInterval); - mComplexBufferBroadcaster.receive(mReusableBuffer); - } - else - { - float[] samples = mOscillator.generateComplex(mSamplesPerInterval); - mComplexBufferBroadcaster.receive(new TimestampedComplexBuffer(samples)); - } + mOscillator.generateComplex(reusableComplexBuffer); + + mComplexBufferBroadcaster.broadcast(reusableComplexBuffer.incrementUserCount()); if(mSweepUpdateInterval != 0) { diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/TestTuner.java b/src/main/java/io/github/dsheirer/source/tuner/test/TestTuner.java index 4a81a60b..5449280e 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/TestTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/TestTuner.java @@ -41,9 +41,6 @@ public class TestTuner extends Tuner /** * Returns the tuner controller cast as a test tuner controller. - * - * Note: this is a temporary method until the tuner controller interface can be updated with the start(listener) - * and stop() methods. */ public TestTunerController getTunerController() { diff --git a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java index c9dac7ce..576ca349 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/test/TestTunerController.java @@ -19,7 +19,7 @@ package io.github.dsheirer.source.tuner.test; 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.SourceException; import io.github.dsheirer.source.tuner.TunerController; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; @@ -34,7 +34,7 @@ public class TestTunerController extends TunerController public static final long MAXIMUM_FREQUENCY = 1000000000l; public static final int SAMPLE_RATE = 800000; public static final int DC_NOISE_BANDWIDTH = 0; - public static final double USABLE_BANDWIDTH = 1.00; + public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00; public static final int SPECTRAL_FRAME_RATE = 20; public static final long SAMPLE_GENERATION_INTERVAL = 1000 / SPECTRAL_FRAME_RATE; @@ -47,7 +47,7 @@ public class TestTunerController extends TunerController */ public TestTunerController() { - super(MINIMUM_FREQUENCY, MAXIMUM_FREQUENCY, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH); + super(MINIMUM_FREQUENCY, MAXIMUM_FREQUENCY, DC_NOISE_BANDWIDTH, USABLE_BANDWIDTH_PERCENTAGE); int sweepRate = 0; //Hz per interval long initialToneFrequency = SAMPLE_RATE / 2 + 100; @@ -65,25 +65,14 @@ public class TestTunerController extends TunerController } } - /** - * Sets the sample generator to reuse buffers instead of generating new buffers each time. Enable this setting - * when you simply want to test/profile buffer data flows for consumer processes. - * - * @param reuseBuffers set to true to turn on buffer reuse - */ - public void setReuseBuffers(boolean reuseBuffers) - { - mSampleGenerator.setReuseBuffers(reuseBuffers); - } - @Override - public void addComplexBufferListener(Listener listener) + public void addBufferListener(Listener listener) { mSampleGenerator.addListener(listener); } @Override - public void removeComplexBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { mSampleGenerator.removeListener(listener); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/LibUSBTransferProcessor.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java similarity index 68% rename from src/main/java/io/github/dsheirer/source/tuner/LibUSBTransferProcessor.java rename to src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java index 09c10372..d13e4a9b 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/LibUSBTransferProcessor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBMasterProcessor.java @@ -1,24 +1,20 @@ /******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 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. + * 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 + * You should have received a copy of the GNU General Public License along with this program. + * If not, see * ******************************************************************************/ -package io.github.dsheirer.source.tuner; +package io.github.dsheirer.source.tuner.usb; -import io.github.dsheirer.source.tuner.usb.USBTransferProcessor; import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,11 +28,12 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** - * Runnable for executing LibUsb processing of bulk transfer buffers + * Scheduled runnable for the primary LibUsb processing thread. Each USB device's transfer processor + * registers with this processor to support auto-start/stop of this event processing thread. */ -public class LibUSBTransferProcessor +public class USBMasterProcessor { - private final static Logger mLog = LoggerFactory.getLogger(LibUSBTransferProcessor.class); + private final static Logger mLog = LoggerFactory.getLogger(USBMasterProcessor.class); private static final long USB_TIMEOUT_MS = 2000l; //milliseconds @@ -112,14 +109,21 @@ public class LibUSBTransferProcessor @Override public void run() { - int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, mStatusBuffer.asIntBuffer()); - - if(result != LibUsb.SUCCESS) + try { - mLog.error("Error processing timeout events for LibUSB - error code:" + result); - } + int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, mStatusBuffer.asIntBuffer()); - mStatusBuffer.rewind(); + if(result != LibUsb.SUCCESS) + { + mLog.error("Error processing timeout events for LibUSB - error code:" + result); + } + + mStatusBuffer.rewind(); + } + catch(Throwable throwable) + { + mLog.error("Error while processing LibUSB timeout events", throwable); + } } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java index b1bf58a0..129f228e 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTransferProcessor.java @@ -1,29 +1,24 @@ /******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 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. + * 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 + * You should have received a copy of the GNU General Public License along with this program. + * If not, see * ******************************************************************************/ package io.github.dsheirer.source.tuner.usb; import io.github.dsheirer.sample.Listener; -import io.github.dsheirer.sample.OverflowableTransferQueue; -import io.github.dsheirer.sample.adapter.ISampleAdapter; -import io.github.dsheirer.sample.complex.ComplexBuffer; -import io.github.dsheirer.sample.real.IOverflowListener; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import io.github.dsheirer.source.tuner.TunerManager; +import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter; import io.github.dsheirer.util.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,8 +29,6 @@ import org.usb4java.Transfer; import org.usb4java.TransferCallback; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -49,72 +42,48 @@ public class USBTransferProcessor implements TransferCallback private static final long USB_TIMEOUT_MS = 2000l; //milliseconds //Number of native byte buffers to allocate for transferring data from the USB device - private static final int TRANSFER_BUFFER_POOL_SIZE = 16; + private static final int TRANSFER_BUFFER_POOL_SIZE = 20; + private static final int MAX_TRANSFERS_IN_PROGRESS_SIZE = 10; + private int mTransfersToSubmit = 0; - //Maximum number of filled buffers for the blocking queue - private static final int FILLED_BUFFER_MAX_CAPACITY = 500; + private LinkedTransferQueue mAvailableTransfers = new LinkedTransferQueue<>(); + private LinkedTransferQueue mInProgressTransfers = new LinkedTransferQueue<>(); + private LinkedTransferQueue mCompletedTransfers = new LinkedTransferQueue<>(); - //Threshold for resetting buffer overflow condition - private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 100; - - private String mDeviceName; - - //Handle to the USB bulk transfer device - private DeviceHandle mDeviceHandle; - - //Tuner format-specific byte to IQ float sample converter - private ISampleAdapter mSampleAdapter; + //Tuner format-specific byte buffer to IQ float sample converter + private NativeBufferConverter mNativeBufferConverter; //Byte array transfer buffers size in bytes private int mBufferSize; - private OverflowableTransferQueue mFilledBuffers; - private LinkedTransferQueue mAvailableTransfers = new LinkedTransferQueue<>(); - private LinkedTransferQueue mTransfersInProgress = new LinkedTransferQueue<>(); - private Listener mComplexBufferListener; + private Listener mComplexBufferListener; + //Handle to the USB bulk transfer device + private DeviceHandle mUsbBulkTransferDeviceHandle; private AtomicBoolean mRunning = new AtomicBoolean(); - private ByteBuffer mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4); - - private BufferDispatcher mBufferDispatcher = new BufferDispatcher(); + private CompletedTransferProcessor mCompletedTransferProcessor = new CompletedTransferProcessor(); private ScheduledFuture mBufferDispatcherFuture; + private ScheduledFuture mRestartFuture; + private String mDeviceName; + /** * Manages stream of USB transfer buffers and converts buffers to complex buffer samples for distribution to * any registered listeners. * * @param deviceName to use when logging information or errors - * @param deviceHandle to the USB bulk transfer device - * @param sampleAdapter specific to the tuner's byte buffer format for converting to floating point I/Q samples + * @param usbBulkTransferDeviceHandle to the USB bulk transfer device + * @param nativeBufferConverter specific to the tuner's byte buffer format for converting to floating point I/Q samples * @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144. */ - public USBTransferProcessor(String deviceName, - DeviceHandle deviceHandle, - ISampleAdapter sampleAdapter, - int bufferSize) + public USBTransferProcessor(String deviceName, DeviceHandle usbBulkTransferDeviceHandle, + NativeBufferConverter nativeBufferConverter, int bufferSize) { mDeviceName = deviceName; - mDeviceHandle = deviceHandle; - mSampleAdapter = sampleAdapter; + mUsbBulkTransferDeviceHandle = usbBulkTransferDeviceHandle; + mNativeBufferConverter = nativeBufferConverter; mBufferSize = bufferSize; - - mFilledBuffers = new OverflowableTransferQueue<>(FILLED_BUFFER_MAX_CAPACITY, FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD); - mFilledBuffers.setOverflowListener(new IOverflowListener() - { - @Override - public void sourceOverflow(boolean overflow) - { - if(overflow) - { - mLog.debug(mDeviceName + " - buffer overflow - temporary pause until processing catches up"); - } - else - { - mLog.debug(mDeviceName + " - buffer overflow cleared - resuming normal processing"); - } - } - }); } /** @@ -125,10 +94,104 @@ public class USBTransferProcessor implements TransferCallback if(mRunning.compareAndSet(false, true)) { prepareDeviceStart(); - prepareTransfers(); + mTransfersToSubmit = MAX_TRANSFERS_IN_PROGRESS_SIZE; + submitTransfers(); - while(!mAvailableTransfers.isEmpty()) + //Start transferred buffer dispatcher + mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mCompletedTransferProcessor, + 0, 6, TimeUnit.MILLISECONDS); + + //Register with LibUSB processor so that it auto-starts LibUSB processing + TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this); + } + } + + /** + * Stop USB transfer buffer processing. Subsequent calls to this method after stopped will be ignored. + */ + private void stop() + { + if(mRunning.compareAndSet(true, false)) + { + mBufferDispatcherFuture.cancel(true); + + //Cancel all buffers that are currently in progress + for(Transfer transfer : mInProgressTransfers) + { + LibUsb.cancelTransfer(transfer); + } + + //Clear all completed buffers + Transfer completedTransfer = mCompletedTransfers.poll(); + + while(completedTransfer != null) + { + completedTransfer.buffer().rewind(); + mAvailableTransfers.add(completedTransfer); + completedTransfer = mCompletedTransfers.poll(); + } + + //Unregister from LibUSB processor so that it auto-stops LibUSB processing + TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(this); + + //Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed. + int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, + mLibUsbHandlerStatus.asIntBuffer()); + + if(result != LibUsb.SUCCESS) + { + mLog.error(mDeviceName + " - error while cancelling transfer buffers during shutdown/pause - error code:" + result); + } + + mLibUsbHandlerStatus.rewind(); + + executeDeviceStop(); + } + } + + /** + * Restarts the device after there is an error. Initially stops the device and then schedules a start() to + * occur in 10 milliseconds. + */ + private void restart() + { + stop(); + + //Attempt to clear any halt condition + LibUsb.clearHalt(mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT); + + mLog.warn("USB tuner [" + mDeviceName + "] stopped due to a buffer transfer error. Restarting in 10 milliseconds"); + + if(mRestartFuture == null) + { + Runnable runnable = new Runnable() + { + @Override + public void run() + { + start(); + mRestartFuture = null; + } + }; + + mRestartFuture = ThreadPool.SCHEDULED.schedule(runnable, 10, TimeUnit.MILLISECONDS); + } + } + + /** + * (Re)Submits transfer buffers to the USB device. Since we're offloading byte buffer to complex sample conversion + * to another thread, there may be times when sufficient transfer buffers are not available to keep a level number + * of transfer buffers in progress. Therefore, we track the transfer buffers to submit deficit number and attempt + * to play catch-up each time this method is invoked. + */ + private void submitTransfers() + { + if(mRunning.get()) + { + int submitted = 0; + + for(int x = 0; x < mTransfersToSubmit; x++) { Transfer transfer = mAvailableTransfers.poll(); @@ -138,11 +201,14 @@ public class USBTransferProcessor implements TransferCallback if(result == LibUsb.SUCCESS) { - mTransfersInProgress.add(transfer); + mInProgressTransfers.add(transfer); + submitted++; } else if(result == LibUsb.ERROR_PIPE) { - int resetResult = LibUsb.clearHalt(mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT); + mLog.warn("USB pipe error - attempting to clear halt on USB device [" + mDeviceName + "]"); + + int resetResult = LibUsb.clearHalt(mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT); if(resetResult == LibUsb.SUCCESS) { @@ -150,7 +216,8 @@ public class USBTransferProcessor implements TransferCallback if(resubmitResult == LibUsb.SUCCESS) { - mTransfersInProgress.add(transfer); + mInProgressTransfers.add(transfer); + submitted++; } else { @@ -170,48 +237,7 @@ public class USBTransferProcessor implements TransferCallback } } - //Start transferred buffer dispatcher - mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferDispatcher, - 0, 11, TimeUnit.MILLISECONDS); - - //Register with LibUSB processor so that it auto-starts LibUSB processing - TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this); - } - } - - /** - * Stop USB transfer buffer processing. Subsequent calls to this method after stopped will be ignored. - */ - private void stop() - { - if(mRunning.compareAndSet(true, false)) - { - mBufferDispatcherFuture.cancel(true); - - mFilledBuffers.clear(); - - //Cancel the lib usb process timer - for(Transfer transfer : mTransfersInProgress) - { - LibUsb.cancelTransfer(transfer); - } - - - //Unregister from LibUSB processor so that it auto-stops LibUSB processing - TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(this); - - //Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed. - int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, - mLibUsbHandlerStatus.asIntBuffer()); - - if(result != LibUsb.SUCCESS) - { - mLog.error(mDeviceName + " - error while cancelling transfer buffers during shutdown/pause - error code:" + result); - } - - mLibUsbHandlerStatus.rewind(); - - executeDeviceStop(); + mTransfersToSubmit -= submitted; } } @@ -234,7 +260,7 @@ public class USBTransferProcessor implements TransferCallback /** * Sets the listener and auto-starts the buffer processor */ - public void setListener(Listener listener) + public void setListener(Listener listener) { mComplexBufferListener = listener; start(); @@ -254,7 +280,7 @@ public class USBTransferProcessor implements TransferCallback /** * Prepares (allocates) a set of transfer buffers for use in transferring data from the USB device via the bulk - * interface. Since we're using direct allocation (native), buffers are retained and reused across multiple + * interface. Since we're using direct memory allocation (native), buffers are retained and reused across multiple * start/stop cycles. */ private void prepareTransfers() throws LibUsbException @@ -270,7 +296,7 @@ public class USBTransferProcessor implements TransferCallback final ByteBuffer buffer = ByteBuffer.allocateDirect(mBufferSize); - LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer, this, + LibUsb.fillBulkTransfer(transfer, mUsbBulkTransferDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer, this, "Buffer", USB_TIMEOUT_MS); mAvailableTransfers.add(transfer); @@ -278,12 +304,15 @@ public class USBTransferProcessor implements TransferCallback } /** - * Process a filled transfer buffer received back from the USB device + * Process a filled transfer buffer received back from the USB device. Note: this method is invoked on the USB + * bus processing thread, so we try to keep processing to a minimum and place transfers in the completed + * transfer queue so that the scheduled processor thread handles any conversion and additional downstream + * processing workload. */ @Override public void processTransfer(Transfer transfer) { - mTransfersInProgress.remove(transfer); + mInProgressTransfers.remove(transfer); switch(transfer.status()) { @@ -292,78 +321,32 @@ public class USBTransferProcessor implements TransferCallback case LibUsb.TRANSFER_TIMED_OUT: if(transfer.actualLength() > 0) { - ByteBuffer buffer = transfer.buffer(); - - byte[] data = new byte[transfer.actualLength()]; - buffer.get(data); - buffer.rewind(); - - if(mRunning.get()) - { - mFilledBuffers.offer(data); - } - } - break; - case LibUsb.TRANSFER_CANCELLED: - break; - default: - //Unexpected transfer error - mLog.error(mDeviceName + " - transfer error [" + getTransferStatus(transfer.status()) + - "] transferred actual: " + transfer.actualLength()); - } - - - if(mRunning.get()) - { - int result = LibUsb.submitTransfer(transfer); - - if(result == LibUsb.SUCCESS) - { - mTransfersInProgress.add(transfer); - } - else if(result == LibUsb.ERROR_PIPE) - { - int resetResult = LibUsb.clearHalt(mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT); - - if(resetResult == LibUsb.SUCCESS) - { - int resubmitResult = LibUsb.submitTransfer(transfer); - - if(resubmitResult == LibUsb.SUCCESS) - { - mTransfersInProgress.add(transfer); - } - else - { - mLog.error(mDeviceName + " - error resubmitting transfer after endpoint clear halt"); - } + mCompletedTransfers.add(transfer); } else { - mLog.error(mDeviceName + " - unable to clear device endpoint halt"); + transfer.buffer().rewind(); + mAvailableTransfers.add(transfer); } - } - else - { + break; + case LibUsb.TRANSFER_CANCELLED: + transfer.buffer().rewind(); mAvailableTransfers.add(transfer); - mLog.error(mDeviceName + " - error submitting transfer [" + LibUsb.errorName(result) + "]"); + break; + default: + //Unexpected transfer error - need to reset the bulk transfer interface + mLog.error(mDeviceName + " - transfer error [" + getTransferStatus(transfer.status()) + + "] transferred actual: " + transfer.actualLength()); + transfer.buffer().rewind(); + mAvailableTransfers.add(transfer); + restart(); + return; + } - if(mTransfersInProgress.isEmpty()) - { - mLog.warn(mDeviceName + " - all transfer buffer processing is stopped"); - //TODO: no transfers are in progress ... need to alert the user and the registered complex - //TODO: buffer listeners so that they can respond accordingly - } - } - } - else - { - //We're stopping - park the transfer buffers - if(!mAvailableTransfers.contains(transfer)) - { - mAvailableTransfers.add(transfer); - } - } + mTransfersToSubmit++; + + //Dispatch a new transfer + submitTransfers(); } /** @@ -393,39 +376,39 @@ public class USBTransferProcessor implements TransferCallback } /** - * Fetches byte[] chunks from the raw sample buffer. Converts each byte - * array and broadcasts the array to all registered listeners + * Processes completed USB buffer transfers, converts the transferred bytes into complex samples and dispatches the + * sample buffer to the listener. */ - public class BufferDispatcher implements Runnable + public class CompletedTransferProcessor implements Runnable { - private List mBuffersToDispatch = new ArrayList<>(); - @Override public void run() { - mFilledBuffers.drainTo(mBuffersToDispatch, 50); - - while(!mBuffersToDispatch.isEmpty()) + try { - try + Transfer transfer = mCompletedTransfers.poll(); + + while(transfer != null) { - for(byte[] buffer : mBuffersToDispatch) + if(mRunning.get() && mComplexBufferListener != null) { - float[] complexSamples = mSampleAdapter.convert(buffer); + ByteBuffer nativeBuffer = transfer.buffer(); - if(mComplexBufferListener != null) - { - mComplexBufferListener.receive(new ComplexBuffer(complexSamples)); - } + ReusableComplexBuffer sampleBuffer = mNativeBufferConverter.convert(nativeBuffer, transfer.actualLength()); + + mComplexBufferListener.receive(sampleBuffer); } - } - catch(Exception e) - { - mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e); - } - mBuffersToDispatch.clear(); - mFilledBuffers.drainTo(mBuffersToDispatch, 50); + transfer.buffer().rewind(); + mAvailableTransfers.add(transfer); + + //Fetch the next transfer to process + transfer = mCompletedTransfers.poll(); + } + } + catch(Throwable throwable) + { + mLog.error("Error while processing USB transfer buffers", throwable); } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java index 19d60729..db114e7d 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/USBTunerController.java @@ -19,7 +19,7 @@ package io.github.dsheirer.source.tuner.usb; 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.tuner.TunerController; public abstract class USBTunerController extends TunerController @@ -44,11 +44,11 @@ public abstract class USBTunerController extends TunerController * Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started. */ @Override - public void addComplexBufferListener(Listener listener) + public void addBufferListener(Listener listener) { - boolean hasExistingListeners = hasComplexBufferListeners(); + boolean hasExistingListeners = hasBufferListeners(); - super.addComplexBufferListener(listener); + super.addBufferListener(listener); if(!hasExistingListeners) { @@ -60,11 +60,11 @@ public abstract class USBTunerController extends TunerController * Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners. */ @Override - public void removeComplexBufferListener(Listener listener) + public void removeBufferListener(Listener listener) { - super.removeComplexBufferListener(listener); + super.removeBufferListener(listener); - if(!hasComplexBufferListeners()) + if(!hasBufferListeners()) { getUSBTransferProcessor().removeListener(); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/ByteSampleConverter.java b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/ByteSampleConverter.java new file mode 100644 index 00000000..8a20164f --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/ByteSampleConverter.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.source.tuner.usb.converter; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public class ByteSampleConverter extends NativeBufferConverter +{ + private final static float[] LOOKUP_VALUES; + + //Creates a static lookup table that converts the 8-bit valued range from 0 - 255 into scaled float values + //of -1.0 to 0 to 1.0 + static + { + LOOKUP_VALUES = new float[256]; + + for(int x = 0; x < 256; x++) + { + LOOKUP_VALUES[x] = (float)(x - 127) / 128.0f; + } + } + + private FloatBuffer mFloatBuffer; + + /** + * Converts native byte buffers containing 8-bit complex samples into complex float samples loaded into a tracked, + * reusable complex sample buffer. Internally tracks the reusable buffer until all downstream consumers have finished + * processing the buffer contents and then reuses the buffer (and memory) for subsequent samples. + */ + public ByteSampleConverter() + { + } + + /** + * Converts the 8-bit complex samples contained in the native buffer into floats that are loaded into a float + * buffer and subsequently transferred to a reusable complex buffer by the parent class. + * + * @param nativeBuffer containing 8-bit complex samples + * @param length of bytes to read from the native buffer + * @return float buffer loaded with converted samples + */ + @Override + protected FloatBuffer convertSamples(ByteBuffer nativeBuffer, int length) + { + nativeBuffer.rewind(); + + if(mFloatBuffer == null || mFloatBuffer.capacity() != nativeBuffer.capacity()) + { + mFloatBuffer = FloatBuffer.allocate(nativeBuffer.capacity()); + } + + mFloatBuffer.rewind(); + + int count = 0; + + while(nativeBuffer.hasRemaining() && count < length) + { + byte sample = nativeBuffer.get(); + count++; + + mFloatBuffer.put(LOOKUP_VALUES[(sample & 0xFF)]); + } + + return mFloatBuffer; + } +} diff --git a/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java new file mode 100644 index 00000000..9d9dfcbf --- /dev/null +++ b/src/main/java/io/github/dsheirer/source/tuner/usb/converter/NativeBufferConverter.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package io.github.dsheirer.source.tuner.usb.converter; + +import io.github.dsheirer.sample.complex.reusable.ReusableBufferQueue; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public abstract class NativeBufferConverter +{ + private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue(); + + /** + * Converts native byte buffers into complex float samples and produces reusable complex sample buffers. Tracks + * each reusable buffer until the downstream consumer(s) are finished with the buffer and then reuses the buffer. + */ + public NativeBufferConverter() + { + } + + /** + * Converts the native byte buffer to complex float samples and returns the samples in a reusable buffer. Internally + * tracks each reusable buffer until all consumers indicate they are finished processing the buffer and then reuses + * the buffer. + * + * @param byteBuffer containing native memory byte samples + * @return native buffer samples converted to complex floats loaded into a reusable buffer + */ + public ReusableComplexBuffer convert(ByteBuffer byteBuffer, int length) + { + FloatBuffer floatBuffer = convertSamples(byteBuffer, length); + + ReusableComplexBuffer reusableComplexBuffer = mReusableBufferQueue.getBuffer(floatBuffer.capacity()); + + //The reusable buffer will rewind the float buffer to the beginning + reusableComplexBuffer.reloadFrom(floatBuffer, System.currentTimeMillis()); + + return reusableComplexBuffer; + } + + /** + * Converts the native byte buffer bytes into complex float samples. + * + * @param buffer containing native byte buffer samples + * @return a buffer with complex float samples. This float buffer will not be modified, therefore sub-class + * implementations can reuse the float buffer. + */ + protected abstract FloatBuffer convertSamples(ByteBuffer buffer, int length); +} diff --git a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java index e219d85b..4ddb7704 100644 --- a/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java +++ b/src/main/java/io/github/dsheirer/source/wave/ComplexWaveSource.java @@ -1,26 +1,24 @@ /******************************************************************************* - * sdrtrunk - * Copyright (C) 2014-2017 Dennis Sheirer + * 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 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. + * 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 + * You should have received a copy of the GNU General Public License along with this program. + * If not, see * ******************************************************************************/ package io.github.dsheirer.source.wave; import io.github.dsheirer.sample.ConversionUtils; import io.github.dsheirer.sample.Listener; -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.source.ComplexSource; import io.github.dsheirer.source.IControllableFileSource; import io.github.dsheirer.source.IFrameLocationListener; @@ -44,9 +42,10 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil private int mBytesPerFrame; private int mFrameCounter = 0; private long mFrequency = 0; - private Listener mListener; + private Listener mListener; private AudioInputStream mInputStream; private File mFile; + private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue(); public ComplexWaveSource(File file) throws IOException { @@ -220,10 +219,10 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil buffer = Arrays.copyOf(buffer, samplesRead); } - float[] samples = ConversionUtils - .convertFromSigned16BitSamples(buffer); + float[] samples = ConversionUtils.convertFromSigned16BitSamples(buffer); - mListener.receive(new ComplexBuffer(samples)); + ReusableComplexBuffer reusableBuffer = mReusableBufferQueue.getBuffer(samples.length); + mListener.receive(reusableBuffer.incrementUserCount()); } } } @@ -233,7 +232,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil * the wave file */ @Override - public void setListener(Listener listener) + public void setListener(Listener listener) { mListener = listener; } @@ -241,7 +240,7 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil /** * Unregisters the listener from receiving sample buffers */ - public void removeListener(Listener listener) + public void removeListener(Listener listener) { mListener = null; } diff --git a/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java b/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java index 08a4e355..c646aba7 100644 --- a/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/ChannelSpectrumPanel.java @@ -26,7 +26,8 @@ import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType; import io.github.dsheirer.module.ProcessingChain; 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.RealBuffer; import io.github.dsheirer.settings.ColorSetting.ColorSettingName; import io.github.dsheirer.settings.ColorSettingMenuItem; @@ -68,6 +69,7 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener private JLayeredPane mLayeredPane; private SpectrumPanel mSpectrumPanel; private ChannelOverlayPanel mOverlayPanel; + private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue(); private Channel mCurrentChannel; @@ -274,7 +276,9 @@ public class ChannelSpectrumPanel extends JPanel implements ChannelEventListener //Hack: we're placing real samples in a complex buffer that the DFT //processor is expecting. - mDFTProcessor.receive(new ComplexBuffer(decimated.getSamples())); + ReusableComplexBuffer reusableComplexBuffer = mReusableBufferQueue.getBuffer(decimated.getSamples().length); + reusableComplexBuffer.reloadFrom(decimated.getSamples(), System.currentTimeMillis()); + mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount()); } /** diff --git a/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java b/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java index fa0cb3f3..172a278f 100644 --- a/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java +++ b/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java @@ -20,7 +20,7 @@ import io.github.dsheirer.dsp.filter.Window.WindowType; import io.github.dsheirer.properties.SystemProperties; 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.sample.real.IOverflowListener; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; @@ -41,7 +41,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * Processes both complex samples or float samples and dispatches a float array of DFT results, using configurable fft * size and output dispatch timelines. */ -public class DFTProcessor implements Listener, ISourceEventProcessor, IDFTWidthChangeProcessor +public class DFTProcessor implements Listener, ISourceEventProcessor, IDFTWidthChangeProcessor { private static final Logger mLog = LoggerFactory.getLogger(DFTProcessor.class); private static final int BUFFER_QUEUE_MAX_SIZE = 12; @@ -192,13 +192,18 @@ public class DFTProcessor implements Listener, ISourceEventProces * Places the sample into a transfer queue for future processing. */ @Override - public void receive(ComplexBuffer sampleBuffer) + public void receive(ReusableComplexBuffer sampleBuffer) { mOverflowableBufferStream.offer(sampleBuffer); } private void calculate() { + //We always send the previous calculated samples - this should improve the screen rendering since the frame + //rate will always occur on an even rhythm. Any delays caused by processing will be absorbed and not impact + //the screen rendering. + dispatch(mPreviousSamples); + try { if(mFrameFlushCount > 0) @@ -220,14 +225,12 @@ public class DFTProcessor implements Listener, ISourceEventProces mFFT.complexForward(samples); } - dispatch(samples); - mPreviousSamples = samples; } catch(IOException ioe) { //No new data, dispatch the previous samples again - dispatch(mPreviousSamples); + //no-op } } diff --git a/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java b/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java index 0faec35c..1525d8e4 100644 --- a/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java +++ b/src/main/java/io/github/dsheirer/spectrum/OverflowableBufferStream.java @@ -16,8 +16,8 @@ package io.github.dsheirer.spectrum; -import io.github.dsheirer.sample.Buffer; import io.github.dsheirer.sample.OverflowableTransferQueue; +import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,20 +25,20 @@ import java.io.IOException; import java.nio.FloatBuffer; import java.util.LinkedList; -public class OverflowableBufferStream extends OverflowableTransferQueue +public class OverflowableBufferStream extends OverflowableTransferQueue { private static final Logger mLog = LoggerFactory.getLogger(OverflowableBufferStream.class); private int mFlushCount = 0; - private Buffer mCurrentBuffer; + private ReusableComplexBuffer mCurrentBuffer; private int mCurrentBufferPointer = 0; private FloatBuffer mFloatBuffer; - private LinkedList mBufferList = new LinkedList<>(); + private LinkedList mBufferList = new LinkedList<>(); private int mBufferFetchLimit; /** - * Stream for receiving Buffers from a producer thread and providing access to a consumer thread to read the - * sample data. Extends the overflowable transfer queue to define maximum and reset thresholds to ensure the + * Stream for receiving reusable buffers from a producer thread and providing access to a consumer thread to read + * the sample data. Extends the overflowable transfer queue to define maximum and reset thresholds to ensure the * queue doesn't grow too large. * * This stream is designed for consistent sample size requests each time the get() samples method is invoked, such @@ -55,7 +55,7 @@ public class OverflowableBufferStream extends OverflowableTransferQueue { super(maxSize, resetThreshold); mFloatBuffer = FloatBuffer.allocate(arraySize); - mBufferFetchLimit = maxSize = resetThreshold; + mBufferFetchLimit = maxSize - resetThreshold; } /** @@ -181,7 +181,13 @@ public class OverflowableBufferStream extends OverflowableTransferQueue */ private void getNextBuffer() throws IOException { - mCurrentBuffer = null; + if(mCurrentBuffer != null) + { + //Decrement the user count to let the originator know we're done with their buffer + mCurrentBuffer.decrementUserCount(); + mCurrentBuffer = null; + } + mCurrentBufferPointer = 0; if(mBufferList.isEmpty()) @@ -196,7 +202,28 @@ public class OverflowableBufferStream extends OverflowableTransferQueue if(mCurrentBuffer == null) { - throw new IOException("Buffer queue is (currently) empty"); + throw new IOException("Reusable complex buffer queue is (currently) empty"); + } + } + + /** + * Clears all elements from the queue and resets the internal counter to 0 + */ + @Override + public void clear() + { + synchronized(mQueue) + { + ReusableComplexBuffer buffer = mQueue.poll(); + + while(buffer != null) + { + buffer.decrementUserCount(); + buffer = mQueue.poll(); + } + + mCounter.set(0); + mOverflow.set(false); } } } diff --git a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java index a5032815..1b07c2f2 100644 --- a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java @@ -26,7 +26,7 @@ import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.properties.SystemProperties; 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.ColorSetting.ColorSettingName; import io.github.dsheirer.settings.ColorSettingMenuItem; import io.github.dsheirer.settings.SettingsManager; @@ -75,7 +75,8 @@ import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Hashtable; -public class SpectralDisplayPanel extends JPanel implements Listener, ISourceEventProcessor, IDFTWidthChangeProcessor +public class SpectralDisplayPanel extends JPanel implements Listener, ISourceEventProcessor, + IDFTWidthChangeProcessor { private static final long serialVersionUID = 1L; @@ -412,9 +413,10 @@ public class SpectralDisplayPanel extends JPanel implements Listener