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