mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -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:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user