[WIP] Implements reusable sample buffers across all components. This

produces a significant reduction in memory usage, especially for tuners
like the airspy and hackrf that produce 10/20 MSPS sample rates.  This
is observed to be around 66% reduction in peak memory usage and provides
a more stable memory utilization across time reducing stress on the
garbage collector.

Use of these reusable memory buffers will be pushed further down into
the processing chain modules to ensure we keep new object creation
to the lowest levels possible.
This commit is contained in:
Denny
2018-02-03 10:05:16 -05:00
parent fcd3c2fd46
commit f2f2c92afe
56 changed files with 1630 additions and 1255 deletions

View File

@@ -1,41 +1,8 @@
<component name="libraryTable">
<library name="imports">
<CLASSES>
<root url="jar://$PROJECT_DIR$/imports/logback-classic-1.1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/usb-api-1.0.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-io-2.4.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-lang3-3.2.1.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/tablefilter-swing-5.2.1.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-x86.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/JLargeArrays-1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-ppc.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-windows-x86.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-osx-x86.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/miglayout-4.0-swing.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-windows-x86_64.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/usb4java-1.2.0.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/jide-oss-3.5.15.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-x86_64.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/logback-core-1.1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/usb4java-javax-1.2.0.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/jsr80-1.0.1.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-arm.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-osx-x86_64.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/JTransforms-3.0.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.3.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/java-lame-3.98.4.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-codec-1.9.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-logging-1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/slf4j-api-1.7.21.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-aarch64.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/activation-1.1.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/axis.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-discovery-0.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/java-mail-1.4.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/javassist-3.20.0-GA.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/jaxrpc.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/mina-core-2.0.16.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/mina-http-2.0.16.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/radioreference-14.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/wsdl4j-1.5.1.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-math3-3.6.1.jar!/" />

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -20,7 +20,7 @@ package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
@@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<ComplexBuffer>, ISourceEventListener
public abstract class AbstractComplexPolyphaseChannelizer implements Listener<ReusableComplexBuffer>, ISourceEventListener
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractComplexPolyphaseChannelizer.class);

View File

@@ -18,7 +18,7 @@ package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
@@ -121,7 +121,7 @@ public class ChannelizerViewer extends JFrame
mTestTuner.getTunerController().getSampleRate());
mPrimarySpectrumPanel.setPreferredSize(new Dimension(1200, 200));
mPrimarySpectrumPanel.setDFTSize(mMainPanelDFTSize);
mTestTuner.getTunerController().addComplexBufferListener(mPrimarySpectrumPanel);
mTestTuner.getTunerController().addBufferListener(mPrimarySpectrumPanel);
}
return mPrimarySpectrumPanel;
@@ -140,7 +140,7 @@ public class ChannelizerViewer extends JFrame
long toneFrequency = 0;
SpinnerNumberModel model = new SpinnerNumberModel(toneFrequency, minimumFrequency, maximumFrequency,
100 );
100);
model.addChangeListener(new ChangeListener()
{
@@ -300,7 +300,7 @@ public class ChannelizerViewer extends JFrame
return tunerChannels;
}
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
public class PrimarySpectrumPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
{
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
@@ -324,9 +324,10 @@ public class ChannelizerViewer extends JFrame
}
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
mDFTProcessor.receive(complexBuffer);
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
reusableComplexBuffer.decrementUserCount();
}
@Override
@@ -336,7 +337,7 @@ public class ChannelizerViewer extends JFrame
}
}
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
public class ChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
{
private TunerChannelSource mSource;
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
@@ -373,17 +374,18 @@ public class ChannelizerViewer extends JFrame
if(mSource != null)
{
mSource.setListener(new Listener<ComplexBuffer>()
mSource.setListener(new Listener<ReusableComplexBuffer>()
{
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer complexBuffer)
{
if(mLoggingEnabled)
{
mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples()));
}
mDFTProcessor.receive(complexBuffer);
mDFTProcessor.receive(complexBuffer.incrementUserCount());
complexBuffer.decrementUserCount();
}
});
@@ -406,10 +408,10 @@ public class ChannelizerViewer extends JFrame
}
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
mLog.debug("Got a buffer for the channel dft processor");
mDFTProcessor.receive(complexBuffer);
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
reusableComplexBuffer.decrementUserCount();
}
@Override
@@ -419,7 +421,7 @@ public class ChannelizerViewer extends JFrame
}
}
public class DiscreteChannelPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
public class DiscreteChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor
{
private TunerChannelSource mSource;
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
@@ -454,17 +456,18 @@ public class ChannelizerViewer extends JFrame
if(mSource != null)
{
mSource.setListener(new Listener<ComplexBuffer>()
mSource.setListener(new Listener<ReusableComplexBuffer>()
{
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer complexBuffer)
{
if(mLoggingEnabled)
{
mLog.debug("Samples:" + Arrays.toString(complexBuffer.getSamples()));
}
mDFTProcessor.receive(complexBuffer);
mDFTProcessor.receive(complexBuffer.incrementUserCount());
complexBuffer.decrementUserCount();
}
});
@@ -487,10 +490,10 @@ public class ChannelizerViewer extends JFrame
}
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
mLog.debug("Got a buffer for the channel dft processor");
mDFTProcessor.receive(complexBuffer);
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
reusableComplexBuffer.decrementUserCount();
}
@Override
@@ -524,16 +527,13 @@ public class ChannelizerViewer extends JFrame
{
TestTuner tuner = new TestTuner();
//Turn on buffer reuse since we're simply profiling data flow/throughput
tuner.getTunerController().setReuseBuffers(true);
List<TunerChannel> tunerChannels = getTunerChannels(tuner);
List<TunerChannelSource> sources = new ArrayList<>();
int maxSourceCount = 30;
int sourceCount = 0;
for(TunerChannel tunerChannel: tunerChannels)
for(TunerChannel tunerChannel : tunerChannels)
{
if(sourceCount < maxSourceCount)
{
@@ -553,21 +553,24 @@ public class ChannelizerViewer extends JFrame
mLog.debug("Starting [" + sources.size() + "] tuner channel sources");
for(TunerChannelSource tunerChannelSource: sources)
for(TunerChannelSource tunerChannelSource : sources)
{
tunerChannelSource.setListener(new Listener<ComplexBuffer>()
tunerChannelSource.setListener(new Listener<ReusableComplexBuffer>()
{
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
//Do nothing
reusableComplexBuffer.decrementUserCount();
}
});
tunerChannelSource.start();
}
while(true);
while(true)
{
;
}
}
}
}

View File

@@ -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();
}
/**

View File

@@ -23,8 +23,8 @@ import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcesso
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.IComplexBufferProvider;
import io.github.dsheirer.sample.complex.reusable.IReusableComplexBufferProvider;
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
@@ -52,7 +52,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
private static final int POLYPHASE_FILTER_TAPS_PER_CHANNEL = 17;
private Broadcaster<SourceEvent> mSourceEventBroadcaster = new Broadcaster<>();
private IComplexBufferProvider mComplexBufferProvider;
private IReusableComplexBufferProvider mReusableBufferProvider;
private List<PolyphaseChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
private ChannelCalculator mChannelCalculator;
private ComplexPolyphaseChannelizerM2 mPolyphaseChannelizer;
@@ -72,18 +72,18 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* Note: add this channel manager as a source event listener to the complex buffer provider to ensure this manager
* adapts to changes in source frequency and sample rate.
*
* @param complexBufferProvider that supports register/deregister of this channel manager
* @param reusableBufferProvider that supports register/deregister of this channel manager
* @param frequency of the provided complex buffer samples
* @param sampleRate of the provided complex buffer samples
*/
public PolyphaseChannelManager(IComplexBufferProvider complexBufferProvider, long frequency, double sampleRate)
public PolyphaseChannelManager(IReusableComplexBufferProvider reusableBufferProvider, long frequency, double sampleRate)
{
if(complexBufferProvider == null)
if(reusableBufferProvider == null)
{
throw new IllegalArgumentException("Complex buffer provider argument cannot be null");
}
mComplexBufferProvider = complexBufferProvider;
mReusableBufferProvider = reusableBufferProvider;
int channelCount = (int)(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
@@ -229,7 +229,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 1)
{
mComplexBufferProvider.addComplexBufferListener(mBufferProcessor);
mReusableBufferProvider.addBufferListener(mBufferProcessor);
mBufferProcessor.start();
}
}
@@ -250,7 +250,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(mPolyphaseChannelizer.getRegisteredChannelCount() == 0)
{
mComplexBufferProvider.removeComplexBufferListener(mBufferProcessor);
mReusableBufferProvider.removeBufferListener(mBufferProcessor);
mBufferProcessor.stop();
}
}
@@ -538,7 +538,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer
* and/or the channel sources and output processors.
*/
public class BufferSourceEventMonitor implements Listener<ComplexBuffer>
public class BufferSourceEventMonitor implements Listener<ReusableComplexBuffer>
{
private Queue<SourceEvent> mQueuedSourceEvents = new ConcurrentLinkedQueue<>();
@@ -553,7 +553,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
}
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
try
{
@@ -578,13 +578,15 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(mPolyphaseChannelizer != null)
{
mPolyphaseChannelizer.receive(complexBuffer);
mPolyphaseChannelizer.receive(reusableComplexBuffer.incrementUserCount());
}
}
catch(Throwable throwable)
{
mLog.error("Error", throwable);
}
reusableComplexBuffer.decrementUserCount();
}
}
}

View File

@@ -17,8 +17,8 @@ package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
@@ -29,7 +29,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private TimestampedBufferAssembler mTimestampedBufferAssembler;
private ReusableBufferAssembler mReusableBufferAssembler;
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private double mChannelSampleRate;
private long mCenterFrequency;
@@ -51,25 +51,25 @@ public class PolyphaseChannelSource extends TunerChannelSource
super(producerSourceEventListener, tunerChannel);
mPolyphaseChannelOutputProcessor = outputProcessor;
mChannelSampleRate = channelSampleRate;
mTimestampedBufferAssembler = new TimestampedBufferAssembler(2500, mChannelSampleRate);
mReusableBufferAssembler = new ReusableBufferAssembler(2500, mChannelSampleRate);
}
/**
* Registers the listener to receive complex sample buffers from this channel source
*/
@Override
public void setListener(final Listener<ComplexBuffer> listener)
public void setListener(final Listener<ReusableComplexBuffer> listener)
{
mTimestampedBufferAssembler.setListener(listener);
mReusableBufferAssembler.setListener(listener);
}
/**
* Removes the listener from receiving complex sample buffers from this channel source
*/
@Override
public void removeListener(Listener<ComplexBuffer> listener)
public void removeListener(Listener<ReusableComplexBuffer> listener)
{
mTimestampedBufferAssembler.setListener(null);
mReusableBufferAssembler.setListener(null);
}
/**
@@ -108,7 +108,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
//Fully process the residual channel results buffer of the previous channel output processor
if(existingProcessor != null)
{
existingProcessor.processChannelResults(mTimestampedBufferAssembler);
existingProcessor.processChannelResults(mReusableBufferAssembler);
}
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
@@ -129,7 +129,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public void receiveChannelResults(PolyphaseChannelResultsBuffer channelResultsBuffer)
{
mTimestampedBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mReusableBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
}
@@ -200,7 +200,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
//Lock on the output processor so that it can't be changed in the middle of processing
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.processChannelResults(mTimestampedBufferAssembler);
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableBufferAssembler);
}
}

View File

@@ -59,7 +59,29 @@ public class ScheduledBufferProcessor<E> implements Listener<E>
*/
public ScheduledBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
{
mQueue = new OverflowableTransferQueue<>(maximumSize, resetThreshold);
this(new OverflowableTransferQueue<>(maximumSize, resetThreshold), distributionInterval, maxBuffersPerInterval);
}
/**
* Scheduled Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing task
* for periodically distributing internally queued elements to the registered listener. This processor provides
* a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
* processing workload.
*
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
*
* @param queue implmentation of an overflowable transfer queue
* @param distributionInterval in milliseconds specifying how often the internal scheduled thread pool buffer
* distributor should run
* @param maxBuffersPerInterval maximum number of buffers to pull from the internal queue during each distribution
* interval
*/
public ScheduledBufferProcessor(OverflowableTransferQueue<E> queue, long distributionInterval, int maxBuffersPerInterval)
{
mQueue = queue;
mDistributionInterval = distributionInterval;
mMaxBuffersPerInterval = maxBuffersPerInterval;
}
@@ -121,10 +143,18 @@ public class ScheduledBufferProcessor<E> implements Listener<E>
mScheduledFuture = null;
}
mQueue.clear();
clearQueue();
}
}
/**
* Clears any buffers from the queue
*/
protected void clearQueue()
{
mQueue.clear();
}
/**
* Indicates if this processor is currently running
*/

View File

@@ -0,0 +1,45 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.complex.reusable.OverflowableReusableBufferTransferQueue;
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
public class ScheduledReusableBufferProcessor extends ScheduledBufferProcessor<ReusableComplexBuffer>
{
/**
* Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing
* task for periodically distributing internally queued elements to the registered listener. This processor
* provides a convenient way to create a thread-safe buffer for receiving elements from one thread/runnable and then
* distributing those elements to a registered listener where distribution occurs on a separate scheduled thread
* pool runnable thread. This allows the calling input thread to quickly return without incurring any subsequent
* processing workload.
*
* The internal queue is an overflowable queue implementation that allows a listener to be registered to receive
* notifications of overflow and reset state. Queue sizing parameters are specified in the constructor.
*
* @param maximumSize of the internal queue (overflow happens when this is exceeded)
* @param resetThreshold of the internal queue (overflow reset happens once queue size falls below this threshold
* @param distributionInterval in milliseconds specifying how often the internal scheduled thread pool buffer
* distributor should run
* @param maxBuffersPerInterval maximum number of buffers to pull from the internal queue during each distribution
*/
public ScheduledReusableBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
{
super(new OverflowableReusableBufferTransferQueue(maximumSize, resetThreshold),
distributionInterval, maxBuffersPerInterval);
}
}

View File

@@ -23,7 +23,8 @@ import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.reusable.ReusableBufferQueue;
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceEvent;
@@ -140,7 +141,7 @@ public class SynthesizerViewer extends JFrame
return mChannel2Panel;
}
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>
public class PrimarySpectrumPanel extends JPanel implements Listener<ReusableComplexBuffer>
{
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
@@ -174,13 +175,14 @@ public class SynthesizerViewer extends JFrame
}
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
mDFTProcessor.receive(complexBuffer);
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
reusableComplexBuffer.decrementUserCount();
}
}
public class ChannelPanel extends JPanel implements Listener<ComplexBuffer>
public class ChannelPanel extends JPanel implements Listener<ReusableComplexBuffer>
{
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
@@ -208,9 +210,10 @@ public class SynthesizerViewer extends JFrame
}
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
mDFTProcessor.receive(complexBuffer);
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
reusableComplexBuffer.decrementUserCount();
}
}
@@ -274,6 +277,7 @@ public class SynthesizerViewer extends JFrame
private TwoChannelSynthesizerM2 mSynthesizer;
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
private int mSamplesPerCycle = CHANNEL_SAMPLE_RATE / DATA_GENERATOR_FRAME_RATE;
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue();
public DataGenerationManager()
{
@@ -292,15 +296,19 @@ public class SynthesizerViewer extends JFrame
@Override
public void run()
{
float[] channel1 = getChannel1ControlPanel().getOscillator().generateComplex(mSamplesPerCycle);
float[] channel2 = getChannel2ControlPanel().getOscillator().generateComplex(mSamplesPerCycle);
ReusableComplexBuffer channel1Buffer = mReusableBufferQueue.getBuffer(mSamplesPerCycle);
getChannel1ControlPanel().getOscillator().generateComplex(channel1Buffer);
float[] synthesized = mSynthesizer.process(channel1, channel2);
ReusableComplexBuffer channel2Buffer = mReusableBufferQueue.getBuffer(mSamplesPerCycle);
getChannel2ControlPanel().getOscillator().generateComplex(channel2Buffer);
float[] synthesized = mSynthesizer.process(channel1Buffer.getSamples(), channel2Buffer.getSamples());
mFS4DownConverter.mixComplex(synthesized);
ReusableComplexBuffer synthesizedBuffer = mReusableBufferQueue.getBuffer(synthesized.length);
getChannel1Panel().receive(new ComplexBuffer(channel1));
getChannel2Panel().receive(new ComplexBuffer(channel2));
getSpectrumPanel().receive(new ComplexBuffer(synthesized));
getChannel1Panel().receive(channel1Buffer.incrementUserCount());
getChannel2Panel().receive(channel2Buffer.incrementUserCount());
getSpectrumPanel().receive(synthesizedBuffer.incrementUserCount());
}
}

View File

@@ -22,7 +22,7 @@ import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
import io.github.dsheirer.sample.real.IOverflowListener;
import io.github.dsheirer.source.Source;
import org.slf4j.Logger;
@@ -89,10 +89,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
/**
* Processes all enqueued polyphase channelizer results until the internal queue is empty
* @param timestampedBufferAssembler to receive the processed channel results
* @param reusableBufferAssembler to receive the processed channel results
*/
@Override
public void processChannelResults(TimestampedBufferAssembler timestampedBufferAssembler)
public void processChannelResults(ReusableBufferAssembler reusableBufferAssembler)
{
try
{
@@ -100,7 +100,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
if(toProcess > 0)
{
process(mChannelResultsToProcess, timestampedBufferAssembler);
process(mChannelResultsToProcess, reusableBufferAssembler);
}
}
catch(Throwable throwable)
@@ -114,10 +114,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
/**
* Sub-class implementation to process one polyphase channelizer result array.
* @param channelResults to process
* @param timestampedBufferAssembler to receive the processed channelizer results
* @param reusableBufferAssembler to receive the processed channelizer results
*/
public abstract void process(List<PolyphaseChannelResultsBuffer> channelResults,
TimestampedBufferAssembler timestampedBufferAssembler);
ReusableBufferAssembler reusableBufferAssembler);
/**

View File

@@ -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

View File

@@ -19,7 +19,7 @@
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer;
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -73,11 +73,11 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
*
* @param channelResults to process containing a list of channel array of I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param timestampedBufferAssembler to receive the extracted, frequency-translated channel results
* @param reusableBufferAssembler to receive the extracted, frequency-translated channel results
*/
@Override
public void process(List<PolyphaseChannelResultsBuffer> channelResults,
TimestampedBufferAssembler timestampedBufferAssembler)
ReusableBufferAssembler reusableBufferAssembler)
{
for(PolyphaseChannelResultsBuffer channelResultsBuffer: channelResults)
{
@@ -90,7 +90,7 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
getFrequencyCorrectionMixer().mixComplex(samples);
}
timestampedBufferAssembler.receive(samples);
reusableBufferAssembler.receive(samples);
}
catch(IllegalArgumentException iae)
{

View File

@@ -18,7 +18,7 @@ package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelResultsBuffer;
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
import io.github.dsheirer.sample.complex.TimestampedBufferAssembler;
import io.github.dsheirer.sample.complex.reusable.ReusableBufferAssembler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -92,11 +92,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
* extracted frequency-corrected channel I/Q sample set to the complex sample listener.
*
* @param channelResultsBuffers to process containing an array of channel I/Q sample pairs (I0,Q0,I1,Q1...In,Qn)
* @param timestampedBufferAssembler to receive the extracted, frequency-translated channel results
* @param reusableBufferAssembler to receive the extracted, frequency-translated channel results
*/
@Override
public void process(List<PolyphaseChannelResultsBuffer> channelResultsBuffers,
TimestampedBufferAssembler timestampedBufferAssembler)
ReusableBufferAssembler reusableBufferAssembler)
{
for(PolyphaseChannelResultsBuffer buffer: channelResultsBuffers)
{
@@ -112,7 +112,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
getFrequencyCorrectionMixer().mixComplex(synthesized);
timestampedBufferAssembler.receive(synthesized);
reusableBufferAssembler.receive(synthesized);
}
}
}

View File

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

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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<AudioPacket> mAudioPacketBroadcaster = new Broadcaster<>();
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster<>();
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private ReusableBufferBroadcaster mComplexBufferBroadcaster = new ReusableBufferBroadcaster();
private Broadcaster<DecoderStateEvent> mDecoderStateEventBroadcaster = new Broadcaster<>();
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
private Broadcaster<SourceEvent> 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<SourceEvent> 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<SourceEvent> 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());

View File

@@ -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 <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.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<TapGroup> 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<TapGroup> 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<ComplexBuffer> getComplexBufferListener()
{
return mBasebandFilter;
}
public Listener<ReusableComplexBuffer> 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<TapGroup> 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<SourceEvent> getSourceEventListener()
{
return null;
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
return null;
}
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
}
}
@Override
public void removeSourceEventListener()
{
@Override
public void removeSourceEventListener()
{
}
}
public class ReusableBufferListener implements Listener<ReusableComplexBuffer>
{
@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();
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.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<ComplexBuffer> getComplexBufferListener()
public Listener<ReusableComplexBuffer> getReusableComplexBufferListener()
{
return mIQFilter;
return mReusableBufferListener;
}
@Override
@@ -107,4 +106,19 @@ public class AMDemodulatorModule extends Module
public void stop()
{
}
public class ReusableBufferListener implements Listener<ReusableComplexBuffer>
{
@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();
}
}
}

View File

@@ -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<ComplexBuffer> getComplexBufferListener()
public Listener<ReusableComplexBuffer> 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<ReusableComplexBuffer>
{
@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();
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.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<ComplexBuffer>
public class ComplexBufferWaveRecorder extends Module implements IReusableComplexBufferListener, Listener<ReusableComplexBuffer>
{
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<ComplexBuffer> 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<ReusableComplexBuffer> 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<ComplexBuffer> 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<ReusableComplexBuffer> 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());
}
}
}

View File

@@ -25,7 +25,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public class Broadcaster<T> implements Listener<T>
{
private List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
protected List<Listener<T>> mListeners = new CopyOnWriteArrayList<>();
/**
* Implements the Listener<T> interface to receive an element and broadcast that element to all registered

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.sample;
public class Buffer
{
private float[] mSamples;
protected float[] mSamples;
/**
* Buffer containing float sample data

View File

@@ -36,9 +36,9 @@ public class OverflowableTransferQueue<E>
private IOverflowListener mOverflowListener;
private Source mSourceOverflowListener;
private LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
private AtomicInteger mCounter = new AtomicInteger();
private AtomicBoolean mOverflow = new AtomicBoolean();
protected LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
protected AtomicInteger mCounter = new AtomicInteger();
protected AtomicBoolean mOverflow = new AtomicBoolean();
private int mMaximumSize;
private int mResetThreshold;
@@ -73,6 +73,21 @@ public class OverflowableTransferQueue<E>
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
}
/**

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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());
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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);
}

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.sample.complex.reusable;
import io.github.dsheirer.sample.OverflowableTransferQueue;
public class OverflowableReusableBufferTransferQueue extends OverflowableTransferQueue<ReusableComplexBuffer>
{
/**
* 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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<ComplexBuffer>
public abstract class ComplexSource extends Source implements Provider<ReusableComplexBuffer>
{
@Override
public SampleType getSampleType()

View File

@@ -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 <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.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<ComplexBuffer> 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<ComplexBuffer> 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<ReusableComplexBuffer> 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<ReusableComplexBuffer> 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;
}
}

View File

@@ -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<Recording>, ISourceEventProcessor, ITunerChannelProvider
public class Recording implements Comparable<Recording>, ISourceEventProcessor
{
private RecordingConfiguration mConfiguration;
private List<TunerChannelSource> mTunerChannels = new ArrayList<TunerChannelSource>();
@SuppressWarnings( "unused" )
private long mCenterFrequency;
@SuppressWarnings( "unused" )
private SettingsManager mSettingsManager;
public Recording( SettingsManager settingsManager,
@@ -75,19 +72,12 @@ public class Recording implements Comparable<Recording>, 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;

View File

@@ -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 <http://www.gnu.org/licenses/>
******************************************************************************/
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 );
}

View File

@@ -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<ComplexBuffer>
IReusableComplexBufferProvider, Listener<ReusableComplexBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(TunerController.class);
protected Broadcaster<ComplexBuffer> 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<ComplexBuffer>)sourceEvent.getSource());
addBufferListener((Listener<ReusableComplexBuffer>)sourceEvent.getSource());
}
break;
case REQUEST_STOP_SAMPLE_STREAM:
if(sourceEvent.getSource() instanceof Listener)
{
removeComplexBufferListener((Listener<ComplexBuffer>)sourceEvent.getSource());
removeBufferListener((Listener<ReusableComplexBuffer>)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<ComplexBuffer> listener)
public void addBufferListener(Listener<ReusableComplexBuffer> 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<ComplexBuffer> listener)
public void removeBufferListener(Listener<ReusableComplexBuffer> 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<T> interface to receive and distribute complex buffers from subclass implementations
*/
@Override
public void receive(ComplexBuffer complexBuffer)
public void receive(ReusableComplexBuffer complexBuffer)
{
broadcast(complexBuffer);
}

View File

@@ -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)

View File

@@ -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;
}
/**

View File

@@ -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 <mike@ossmann.com>
* Copyright (c) 2012, Jared Boone <jared@sharebrained.com>
* Copyright (c) 2014, Youssef Touil <youssef@airspy.com>
@@ -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<AirspySampleRate> 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

View File

@@ -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<ComplexBuffer> 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<ComplexBuffer> 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);

View File

@@ -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<ComplexBuffer> complexBufferListener);
public abstract void setListener(Listener<ReusableComplexBuffer> complexBufferListener);
/**
* Commands sub-class to process queued samples and distribute them to the consumer. This method will be invoked

View File

@@ -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<ComplexBuffer> listener)
public void addBufferListener(Listener<ReusableComplexBuffer> 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<ComplexBuffer> listener)
public void removeBufferListener(Listener<ReusableComplexBuffer> listener)
{
super.removeComplexBufferListener(listener);
super.removeBufferListener(listener);
if(!hasComplexBufferListeners())
if(!hasBufferListeners())
{
mComplexMixer.stop();
}

View File

@@ -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

View File

@@ -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 <steve@steve-m.de>
@@ -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<ComplexBuffer> listener)
public void addBufferListener(Listener<ReusableComplexBuffer> 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<ComplexBuffer> listener)
public void removeBufferListener(Listener<ReusableComplexBuffer> 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

View File

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

View File

@@ -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<ComplexBuffer> 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<ComplexBuffer> listener)
public void addListener(Listener<ReusableComplexBuffer> 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<ComplexBuffer> listener)
public void removeListener(Listener<ReusableComplexBuffer> 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)
{

View File

@@ -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()
{

View File

@@ -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<ComplexBuffer> listener)
public void addBufferListener(Listener<ReusableComplexBuffer> listener)
{
mSampleGenerator.addListener(listener);
}
@Override
public void removeComplexBufferListener(Listener<ComplexBuffer> listener)
public void removeBufferListener(Listener<ReusableComplexBuffer> listener)
{
mSampleGenerator.removeListener(listener);
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.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);
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.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<Transfer> mAvailableTransfers = new LinkedTransferQueue<>();
private LinkedTransferQueue<Transfer> mInProgressTransfers = new LinkedTransferQueue<>();
private LinkedTransferQueue<Transfer> 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<byte[]> mFilledBuffers;
private LinkedTransferQueue<Transfer> mAvailableTransfers = new LinkedTransferQueue<>();
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
private Listener<ComplexBuffer> mComplexBufferListener;
private Listener<ReusableComplexBuffer> 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<ComplexBuffer> listener)
public void setListener(Listener<ReusableComplexBuffer> 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<byte[]> 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);
}
}
}

View File

@@ -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<ComplexBuffer> listener)
public void addBufferListener(Listener<ReusableComplexBuffer> 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<ComplexBuffer> listener)
public void removeBufferListener(Listener<ReusableComplexBuffer> listener)
{
super.removeComplexBufferListener(listener);
super.removeBufferListener(listener);
if(!hasComplexBufferListeners())
if(!hasBufferListeners())
{
getUSBTransferProcessor().removeListener();
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.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<ComplexBuffer> mListener;
private Listener<ReusableComplexBuffer> 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<ComplexBuffer> listener)
public void setListener(Listener<ReusableComplexBuffer> 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<ComplexBuffer> listener)
public void removeListener(Listener<ReusableComplexBuffer> listener)
{
mListener = null;
}

View File

@@ -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());
}
/**

View File

@@ -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<ComplexBuffer>, ISourceEventProcessor, IDFTWidthChangeProcessor
public class DFTProcessor implements Listener<ReusableComplexBuffer>, 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<ComplexBuffer>, 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<ComplexBuffer>, ISourceEventProces
mFFT.complexForward(samples);
}
dispatch(samples);
mPreviousSamples = samples;
}
catch(IOException ioe)
{
//No new data, dispatch the previous samples again
dispatch(mPreviousSamples);
//no-op
}
}

View File

@@ -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<Buffer>
public class OverflowableBufferStream extends OverflowableTransferQueue<ReusableComplexBuffer>
{
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<Buffer> mBufferList = new LinkedList<>();
private LinkedList<ReusableComplexBuffer> 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<Buffer>
{
super(maxSize, resetThreshold);
mFloatBuffer = FloatBuffer.allocate(arraySize);
mBufferFetchLimit = maxSize = resetThreshold;
mBufferFetchLimit = maxSize - resetThreshold;
}
/**
@@ -181,7 +181,13 @@ public class OverflowableBufferStream extends OverflowableTransferQueue<Buffer>
*/
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<Buffer>
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);
}
}
}

View File

@@ -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<ComplexBuffer>, ISourceEventProcessor, IDFTWidthChangeProcessor
public class SpectralDisplayPanel extends JPanel implements Listener<ReusableComplexBuffer>, ISourceEventProcessor,
IDFTWidthChangeProcessor
{
private static final long serialVersionUID = 1L;
@@ -412,9 +413,10 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ComplexBuff
* Complex sample buffer receive method
*/
@Override
public void receive(ComplexBuffer sampleBuffer)
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
mDFTProcessor.receive(sampleBuffer);
mDFTProcessor.receive(reusableComplexBuffer.incrementUserCount());
reusableComplexBuffer.decrementUserCount();
}
/**
@@ -435,7 +437,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ComplexBuff
mTuner.getTunerController().addListener(this);
//Register the dft processor to receive samples from the tuner
mTuner.getTunerController().addComplexBufferListener(mDFTProcessor);
mTuner.getTunerController().addBufferListener(mDFTProcessor);
mSpectrumPanel.setSampleSize(mTuner.getSampleSize());
@@ -458,7 +460,7 @@ public class SpectralDisplayPanel extends JPanel implements Listener<ComplexBuff
mTuner.getTunerController().removeListener(this);
//Deregister the dft processor from receiving samples
mTuner.getTunerController().removeComplexBufferListener(mDFTProcessor);
mTuner.getTunerController().removeBufferListener(mDFTProcessor);
mTuner = null;
}
}