Merge pull request #142 from DSheirer/126-tuner-buffer-processor-out-of-memory

126 - Tuner processing refactor and significant improvement in processor and memory utilization.  R…
This commit is contained in:
Denny Sheirer
2017-01-28 09:09:47 -05:00
committed by GitHub
11 changed files with 1748 additions and 2007 deletions

View File

@@ -0,0 +1,127 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 sample;
import java.util.Collection;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class OverflowableTransferQueue<E>
{
public enum State {NORMAL, OVERFLOW};
private Listener<State> mStateListener;
private LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
private AtomicInteger mCounter = new AtomicInteger();
private int mMaximumSize;
private int mResetThreshold;
private boolean mOverflow = false;
/**
* 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.
*
* @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 OverflowableTransferQueue(int maximumSize, int resetThreshold)
{
mMaximumSize = maximumSize;
mResetThreshold = resetThreshold;
}
/**
* Adds the element to the queue if able to do so without exceeding maximum queue size. Otherwise, ignores
* the element.
*/
public void offer(E e)
{
if(!mOverflow)
{
if(mCounter.get() > mMaximumSize)
{
setOverflow(true);
}
else
{
mQueue.offer(e);
mCounter.incrementAndGet();
}
}
}
/**
* Retrieves elements from the queue into the collection up to the maximum number of elements specified
*/
public int drainTo(Collection<? super E> collection, int maxElements)
{
int drainCount = mQueue.drainTo(collection, maxElements);
if(drainCount > 0)
{
mCounter.addAndGet(-drainCount);
}
return drainCount;
}
/**
* Sets a listener to receive state change events
*/
public void setStateListener(Listener<State> listener)
{
mStateListener = listener;
}
/**
* Current state of the queue, normal or overflow
*/
public State getState()
{
return mOverflow ? State.OVERFLOW : State.NORMAL;
}
/**
* Toggles the overflow state
*/
private void setOverflow(boolean overflow)
{
mOverflow = overflow;
if(mStateListener != null)
{
mStateListener.receive(getState());
}
}
/**
* Clears all elements from the queue and resets the internal counter to 0
*/
public void clear()
{
synchronized(mQueue)
{
mQueue.clear();
mCounter.set(0);
mOverflow = false;
}
}
}

View File

@@ -0,0 +1,125 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 source.tuner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.LibUsb;
import source.tuner.usb.USBTransferProcessor;
import util.ThreadPool;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Runnable for executing LibUsb processing of bulk transfer buffers
*/
public class LibUSBTransferProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(LibUSBTransferProcessor.class);
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
private List<USBTransferProcessor> mRegisteredProcessors = new CopyOnWriteArrayList<>();
private ByteBuffer mStatusBuffer = ByteBuffer.allocateDirect(4);
private AtomicBoolean mRunning = new AtomicBoolean();
private Processor mProcessor = new Processor();
private ScheduledFuture mProcessorFuture;
/**
* Registers the transfer processor so that LibUSB timeout processing will auto-start.
* @param processor to register
*/
public void registerTransferProcessor(USBTransferProcessor processor)
{
if(!mRegisteredProcessors.contains(processor))
{
mRegisteredProcessors.add(processor);
}
start();
}
/**
* Unregisters the transfer processor so that LibUSB timeout processing will auto-stop once all processors have
* been unregistered.
*
* @param processor to unregister
*/
public void unregisterTransferProcessor(USBTransferProcessor processor)
{
mRegisteredProcessors.remove(processor);
if(mRegisteredProcessors.isEmpty())
{
stop();
}
}
/**
* Starts processing of LibUSB timeout events
*/
private void start()
{
if(mRunning.compareAndSet(false, true))
{
//Set periodicity to an odd multiple to avoid contention with transfer buffer receivers
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 0L, 9L, TimeUnit.MILLISECONDS);
}
}
/**
* Stops processing of LibUSB timeout events
*/
private void stop()
{
if(mRunning.compareAndSet(true, false))
{
if(mProcessorFuture != null)
{
mProcessorFuture.cancel(true);
mProcessorFuture = null;
}
}
}
/**
* Runnable to invoke LibUSB timeout handler. All downstream completed transfer processing will occur on this
* thread instance.
*/
class Processor implements Runnable
{
@Override
public void run()
{
int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, mStatusBuffer.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error("Error processing timeout events for LibUSB - error code:" + result);
}
mStatusBuffer.rewind();
}
}
}

View File

@@ -1,19 +1,20 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
* sdrtrunk
* Copyright (C) 2014-2017 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 source.tuner;
@@ -25,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Buffer;
import sample.Listener;
import sample.OverflowableTransferQueue;
import sample.complex.Complex;
import sample.complex.ComplexBuffer;
import source.ComplexSource;
@@ -35,24 +37,27 @@ import source.tuner.frequency.IFrequencyChangeProcessor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class TunerChannelSource extends ComplexSource
implements IFrequencyChangeProcessor, Listener<ComplexBuffer>
public class TunerChannelSource extends ComplexSource implements IFrequencyChangeProcessor, Listener<ComplexBuffer>
{
private final static Logger mLog =
LoggerFactory.getLogger(TunerChannelSource.class);
private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
//Maximum number of filled buffers for the blocking queue
private static final int BUFFER_MAX_CAPACITY = 300;
//Threshold for resetting buffer overflow condition
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 200;
private static int CHANNEL_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000;
private LinkedTransferQueue<ComplexBuffer> mBuffer =
new LinkedTransferQueue<ComplexBuffer>();
private OverflowableTransferQueue<ComplexBuffer> mBuffer;
private Tuner mTuner;
private TunerChannel mTunerChannel;
private Oscillator mMixer;
@@ -69,6 +74,7 @@ public class TunerChannelSource extends ComplexSource
private AtomicBoolean mRunning = new AtomicBoolean();
private boolean mExpended = false;
private int mCount;
/**
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
@@ -87,26 +93,37 @@ public class TunerChannelSource extends ComplexSource
* accept the decimation processing task
* @throws SourceException if the tuner has an issue providing IQ samples
*/
public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel)
throws RejectedExecutionException, SourceException
public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel) throws RejectedExecutionException, SourceException
{
mTuner = tuner;
mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
mTunerChannel = tunerChannel;
mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
mTunerFrequency = mTuner.getTunerController().getFrequency();
mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
mBuffer.setStateListener(new Listener<OverflowableTransferQueue.State>()
{
@Override
public void receive(OverflowableTransferQueue.State state)
{
if(state == OverflowableTransferQueue.State.NORMAL)
{
mLog.debug("Channel [" + mTunerFrequency + "] buffer overflow - temporary pause until processing catches up");
}
else
{
mLog.debug("Channel [" + mTunerFrequency + "] buffer overflow cleared - resuming normal processing");
}
}
});
/* Setup the frequency translator to the current source frequency */
long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency();
mMixer = new Oscillator(frequencyOffset,
mTuner.getTunerController().getSampleRate());
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
/* Fire a sample rate change event to setup the decimation chain */
frequencyChanged(new FrequencyChangeEvent(
Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
mTuner.getTunerController().getSampleRate()));
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate()));
}
@@ -114,23 +131,22 @@ public class TunerChannelSource extends ComplexSource
{
if(mExpended)
{
throw new IllegalStateException("Attempt to re-start an expended "
+ "tuner channel source. TunerChannelSource objects can only "
+ "be used once. ");
throw new IllegalStateException("Attempt to re-start an expended tuner channel source. TunerChannelSource" +
" objects can only be used once. ");
}
if(mRunning.compareAndSet(false, true))
{
/* Schedule the decimation task to run every 10 ms (100 iterations/second) */
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor,
0, 10, TimeUnit.MILLISECONDS);
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
/* Finally, register to receive samples from the tuner */
mTuner.addListener((Listener<ComplexBuffer>) this);
}
else
{
mLog.warn("Attempt to start() and already running tuner channel source was ignored");
mLog.warn("Attempt to start() an already running tuner channel source was ignored");
}
}
@@ -171,14 +187,6 @@ public class TunerChannelSource extends ComplexSource
{
/* Tell the tuner to release/unregister our resources */
mTuner.getTunerController().removeListener(this);
// mTuner = null;
// mTunerChannel = null;
// mBuffer = null;
// mFrequencyChangeProcessor = null;
// mListener = null;
// mMixer = null;
// mDecimationFilter.dispose();
// mDecimationFilter = null;
}
}
@@ -197,7 +205,7 @@ public class TunerChannelSource extends ComplexSource
{
if(mRunning.get())
{
mBuffer.add(buffer);
mBuffer.offer(buffer);
}
}
@@ -248,10 +256,8 @@ public class TunerChannelSource extends ComplexSource
if(mFrequencyChangeProcessor != null)
{
mFrequencyChangeProcessor.frequencyChanged(
new FrequencyChangeEvent(
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
mChannelFrequencyCorrection));
mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
}
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
@@ -262,13 +268,8 @@ public class TunerChannelSource extends ComplexSource
mMixer.setSampleRate(sampleRate);
/* Get new decimation filter */
mDecimationFilter = FilterFactory
.getDecimationFilter(sampleRate,
CHANNEL_RATE,
1, //Order
CHANNEL_PASS_FREQUENCY,
60, //dB attenuation
WindowType.HAMMING);
mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
/* re-add the original output listener */
mDecimationFilter.setListener(mListener);
@@ -328,48 +329,46 @@ public class TunerChannelSource extends ComplexSource
{
if(mProcessing)
{
{
mBuffer.drainTo(mSampleBuffers, 8);
mBuffer.drainTo(mSampleBuffers, 20);
for(Buffer buffer : mSampleBuffers)
{
for(Buffer buffer : mSampleBuffers)
{
/* Check to see if we've been shutdown */
if(!mProcessing)
{
mBuffer.clear();
return;
}
else
{
float[] samples = buffer.getSamples();
if(!mProcessing)
{
mBuffer.clear();
return;
}
else
{
float[] samples = buffer.getSamples();
/* We make a copy of the buffer so that we don't affect
* anyone else that is using the same buffer, like other
* channels or the spectral display */
float[] translated = new float[samples.length];
float[] translated = new float[samples.length];
/* Perform frequency translation */
for(int x = 0; x < samples.length; x += 2)
{
mMixer.rotate();
for(int x = 0; x < samples.length; x += 2)
{
mMixer.rotate();
translated[x] = Complex.multiplyInphase(
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
translated[x] = Complex.multiplyInphase(
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
translated[x + 1] = Complex.multiplyQuadrature(
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
}
translated[x + 1] = Complex.multiplyQuadrature(
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
}
if(mProcessing)
{
final ComplexPrimeCICDecimate filter = mDecimationFilter;
filter.receive(new ComplexBuffer(translated));
}
if(mProcessing)
{
final ComplexPrimeCICDecimate filter = mDecimationFilter;
filter.receive(new ComplexBuffer(translated));
}
}
mSampleBuffers.clear();
}
mSampleBuffers.clear();
}
}
catch(Exception e)

View File

@@ -42,12 +42,24 @@ import java.util.Collection;
public class TunerManager
{
private final static Logger mLog =
LoggerFactory.getLogger(TunerManager.class);
private final static Logger mLog = LoggerFactory.getLogger(TunerManager.class);
private MixerManager mMixerManager;
private TunerModel mTunerModel;
/**
* Application-wide LibUSB timeout processor for transfer buffers. All classes that need to use USB transfer
* 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;
static
{
LIBUSB_TRANSFER_PROCESSOR = new LibUSBTransferProcessor();
}
public TunerManager(MixerManager mixerManager, TunerModel tunerModel)
{
mMixerManager = mixerManager;

View File

@@ -1,144 +1,155 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 source.tuner.airspy;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 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/>
******************************************************************************/
import dsp.filter.dc.DCRemovalFilter_RB;
import dsp.filter.hilbert.HilbertTransform;
import sample.adapter.ISampleAdapter;
public class AirspySampleAdapter implements ISampleAdapter
{
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
private boolean mSamplePacking = false;
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
/**
* Adapter to translate byte buffers received from the airspy tuner into
* float buffers for processing.
*/
public AirspySampleAdapter()
{
}
private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
private HilbertTransform mHilbertTransform = new HilbertTransform();
private boolean mSamplePacking = false;
/**
* Sample packing places two 12-bit samples into 3 bytes when enabled or
* places two 12-bit samples into 4 bytes when disabled.
*
* @param enabled
*/
public void setSamplePacking( boolean enabled )
{
mSamplePacking = enabled;
}
@Override
public float[] convert( byte[] samples )
{
if( mSamplePacking )
{
return convertPacked( samples );
}
else
{
return convertUnpacked( samples );
}
}
/**
* 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
*/
private float[] convertUnpacked( byte[] data )
{
float[] samples = new float[ data.length / 2 ];
/**
* Adapter to translate byte buffers received from the airspy tuner into
* float buffers for processing.
*/
public AirspySampleAdapter()
{
}
int pointer = 0;
for( int x = 0; x < data.length; x += 2 )
{
samples[ pointer++ ] = scale( ( data[ x ] & 0xFF ) |
( data[ x + 1 ] << 8 ) );
}
return samples;
}
/**
* 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 data - byte array of unsigned 12-bit values
* @return converted float values
*/
private float[] convertPacked( byte[] data1 )
{
byte[] data = new byte[ data1.length ];
/**
* Sample packing places two 12-bit samples into 3 bytes when enabled or
* places two 12-bit samples into 4 bytes when disabled.
*
* @param enabled
*/
public void setSamplePacking(boolean enabled)
{
mSamplePacking = enabled;
}
//Convert big-endian to little-endian
for( int x = 0; x < data1.length; x += 4 )
{
data[ x ] = data1[ x + 3 ];
data[ x + 1 ] = data1[ x + 2 ];
data[ x + 2 ] = data1[ x + 1 ];
data[ x + 3 ] = data1[ x ];
}
int count = (int)( (float)data.length / 1.5f );
@Override
public float[] convert(byte[] samples)
{
float[] realSamples;
if(mSamplePacking)
{
realSamples = convertPacked(samples);
}
else
{
realSamples = convertUnpacked(samples);
}
mDCFilter.filter(realSamples);
return mHilbertTransform.filter(realSamples);
}
/**
* 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
*/
private float[] convertUnpacked(byte[] data)
{
float[] samples = new float[data.length / 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;
}
/**
* 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
*/
private float[] convertPacked(byte[] data1)
{
byte[] data = new byte[data1.length];
//Convert big-endian to little-endian
for(int x = 0; x < data1.length; x += 4)
{
data[x] = data1[x + 3];
data[x + 1] = data1[x + 2];
data[x + 2] = data1[x + 1];
data[x + 3] = data1[x];
}
int count = (int) ((float) data.length / 1.5f);
/* Ensure we have an even number of samples */
if( count % 2 == 1 )
{
count--;
}
if(count % 2 == 1)
{
count--;
}
int bytes = (int)((float)count * 1.5f);
int bytes = (int) ((float) count * 1.5f);
float[] samples = new float[ count ];
int pointer = 0;
int first;
int second;
for( int x = 0; x < bytes; x += 3 )
{
first = ( ( data[ x ] << 4 ) & 0xFF0 ) |
( ( data[ x + 1 ] >> 4 ) & 0xF );
float[] samples = new float[count];
samples[ pointer++ ] = scale( first );
int pointer = 0;
second = ( ( data[ x + 1 ] << 8 ) & 0xF00 ) |
( data[ x + 2 ] & 0xFF );
samples[ pointer++ ] = scale( second );
}
return samples;
}
int first;
int second;
/**
* Converts unsigned 12-bit values to signed 12-bit values and then scales
* the signed value to a signed float value in range: -1.0 : +1.0
*/
public static float scale( int value )
{
return (float)(( value & 0xFFF ) - 2048 ) * SCALE_SIGNED_12_BIT_TO_FLOAT;
}
for(int x = 0; x < bytes; x += 3)
{
first = ((data[x] << 4) & 0xFF0) |
((data[x + 1] >> 4) & 0xF);
samples[pointer++] = scale(first);
second = ((data[x + 1] << 8) & 0xF00) |
(data[x + 2] & 0xFF);
samples[pointer++] = scale(second);
}
return samples;
}
/**
* Converts unsigned 12-bit values to signed 12-bit values and then scales
* the signed value to a signed float value in range: -1.0 : +1.0
*/
public static float scale(int value)
{
return (float) ((value & 0xFFF) - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT;
}
}

View File

@@ -17,6 +17,7 @@ import sample.complex.ComplexBuffer;
import source.SourceException;
import source.tuner.TunerController;
import source.tuner.configuration.TunerConfiguration;
import source.tuner.usb.USBTransferProcessor;
import util.ThreadPool;
import javax.usb.UsbException;
@@ -93,21 +94,11 @@ public class AirspyTunerController extends TunerController
public static final AirspySampleRate DEFAULT_SAMPLE_RATE =
new AirspySampleRate(0, 10000000, "10.00 MHz");
public static final long USB_TIMEOUT_MS = 2000l; //milliseconds
public static final byte USB_ENDPOINT = (byte) 0x81;
public static final byte USB_INTERFACE = (byte) 0x0;
public static final int TRANSFER_BUFFER_POOL_SIZE = 16;
public static final byte USB_REQUEST_IN =
(byte) (LibUsb.ENDPOINT_IN |
LibUsb.REQUEST_TYPE_VENDOR |
LibUsb.RECIPIENT_DEVICE);
public static final byte USB_REQUEST_OUT =
(byte) (LibUsb.ENDPOINT_OUT |
LibUsb.REQUEST_TYPE_VENDOR |
LibUsb.RECIPIENT_DEVICE);
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
private static final byte USB_INTERFACE = (byte) 0x0;
private static final int USB_TRANSFER_BUFFER_SIZE = 262144;
private static final byte USB_REQUEST_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
private static final byte USB_REQUEST_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz");
private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class);
@@ -115,17 +106,11 @@ public class AirspyTunerController extends TunerController
private Device mDevice;
private DeviceHandle mDeviceHandle;
private LinkedTransferQueue<byte[]> mFilledBuffers = new LinkedTransferQueue<byte[]>();
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private int mBufferSize = 262144;
private BufferProcessor mBufferProcessor = new BufferProcessor();
private AirspySampleAdapter mSampleAdapter = new AirspySampleAdapter();
private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
private HilbertTransform mHilbertTransform = new HilbertTransform();
private AirspyDeviceInformation mDeviceInfo;
private List<AirspySampleRate> mSampleRates = new ArrayList<>();
private int mSampleRate = 0;
private USBTransferProcessor mUSBTransferProcessor;
public AirspyTunerController(Device device) throws SourceException
{
@@ -140,7 +125,7 @@ public class AirspyTunerController extends TunerController
int result = LibUsb.open(mDevice, mDeviceHandle);
if(result != 0)
if(result != LibUsb.SUCCESS)
{
if(result == LibUsb.ERROR_ACCESS)
{
@@ -201,6 +186,11 @@ public class AirspyTunerController extends TunerController
{
mLog.error("Setting sample rate is not supported by firmware", e);
}
String deviceName = "Airspy " + getDeviceInfo().getSerialNumber();
mUSBTransferProcessor = new USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
USB_TRANSFER_BUFFER_SIZE);
}
/**
@@ -1205,250 +1195,32 @@ public class AirspyTunerController extends TunerController
}
/**
* Adds a sample listener. If the buffer processing thread is
* not currently running, starts it running in a new thread.
* Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started.
*/
public void addListener(Listener<ComplexBuffer> listener)
{
synchronized(mComplexBufferBroadcaster)
if(mUSBTransferProcessor != null)
{
mComplexBufferBroadcaster.addListener(listener);
if(mBufferProcessor == null || !mBufferProcessor.isRunning())
{
mBufferProcessor = new BufferProcessor();
Thread thread = new Thread(mBufferProcessor);
thread.setDaemon(true);
thread.setName("Airspy Buffer Processor");
thread.start();
}
mUSBTransferProcessor.addListener(listener);
}
else
{
mLog.error("Couldn't add IQ buffer listener to Airspy tuner - processor is null");
}
}
/**
* Removes the sample listener. If this is the last registered listener,
* shuts down the buffer processing thread.
* Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners.
*/
public void removeListener(Listener<ComplexBuffer> listener)
{
synchronized(mComplexBufferBroadcaster)
if(mUSBTransferProcessor != null)
{
mComplexBufferBroadcaster.removeListener(listener);
if(!mComplexBufferBroadcaster.hasListeners())
{
mBufferProcessor.stop();
}
mUSBTransferProcessor.removeListener(listener);
}
}
/**
* Buffer processing thread. Fetches samples from the Airspy Tuner and
* dispatches them to all registered listeners
*/
public class BufferProcessor implements Runnable, TransferCallback
{
private ScheduledFuture<?> mSampleDispatcherTask;
private LinkedTransferQueue<Transfer> mAvailableTransfers;
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private ByteBuffer mLibUsbHandlerStatus;
private boolean mCancel = false;
@Override
public void run()
else
{
if(mRunning.compareAndSet(false, true))
{
prepareTransfers();
mSampleDispatcherTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferDispatcher(),
20, 20, TimeUnit.MILLISECONDS);
mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
List<Transfer> transfers = new ArrayList<>();
mCancel = false;
while(mRunning.get())
{
mAvailableTransfers.drainTo(transfers);
for(Transfer transfer : transfers)
{
int result = LibUsb.submitTransfer(transfer);
if(result == LibUsb.SUCCESS)
{
mTransfersInProgress.add(transfer);
}
else
{
mLog.error("error submitting transfer [" + LibUsb.errorName(result) + "]");
}
}
int result = LibUsb.handleEventsTimeoutCompleted(
null, USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error("error handling events for libusb");
}
transfers.clear();
mLibUsbHandlerStatus.rewind();
}
if(mCancel)
{
for(Transfer transfer : mTransfersInProgress)
{
LibUsb.cancelTransfer(transfer);
}
int result = LibUsb.handleEventsTimeoutCompleted(null,
USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error("error handling events for libusb during cancel");
}
mLibUsbHandlerStatus.rewind();
}
}
}
/**
* Stops the sample fetching thread
*/
public void stop()
{
mCancel = true;
if(mRunning.compareAndSet(true, false))
{
if(mSampleDispatcherTask != null)
{
mSampleDispatcherTask.cancel(true);
mFilledBuffers.clear();
}
}
}
/**
* Indicates if this thread is running
*/
public boolean isRunning()
{
return mRunning.get();
}
@Override
public void processTransfer(Transfer transfer)
{
mTransfersInProgress.remove(transfer);
switch(transfer.status())
{
case LibUsb.TRANSFER_COMPLETED:
case LibUsb.TRANSFER_STALL:
if(transfer.actualLength() > 0)
{
ByteBuffer buffer = transfer.buffer();
byte[] data = new byte[transfer.actualLength()];
buffer.get(data);
buffer.rewind();
if(isRunning())
{
mFilledBuffers.add(data);
}
}
break;
case LibUsb.TRANSFER_CANCELLED:
break;
default:
/* unexpected error */
mLog.error("transfer error [" +
getTransferStatus(transfer.status()) +
"] transferred actual: " + transfer.actualLength());
}
mAvailableTransfers.add(transfer);
}
/**
* Prepares (allocates) a set of transfer buffers for use in
* transferring data from the tuner via the bulk interface
*/
private void prepareTransfers() throws LibUsbException
{
if(mAvailableTransfers == null)
{
mAvailableTransfers = new LinkedTransferQueue<>();
for(int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++)
{
Transfer transfer = LibUsb.allocTransfer();
if(transfer == null)
{
throw new LibUsbException("couldn't allocate transfer",
LibUsb.ERROR_NO_MEM);
}
final ByteBuffer buffer =
ByteBuffer.allocateDirect(mBufferSize);
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_ENDPOINT,
buffer, BufferProcessor.this, "Buffer", USB_TIMEOUT_MS);
mAvailableTransfers.add(transfer);
}
}
}
}
/**
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
* array and broadcasts the array to all registered listeners
*/
public class BufferDispatcher implements Runnable
{
@Override
public void run()
{
try
{
ArrayList<byte[]> buffers = new ArrayList<>();
mFilledBuffers.drainTo(buffers);
for(byte[] buffer : buffers)
{
float[] realSamples = mSampleAdapter.convert(buffer);
realSamples = mDCFilter.filter(realSamples);
float[] quadratureSamples = mHilbertTransform.filter(realSamples);
mComplexBufferBroadcaster.broadcast(
new ComplexBuffer(quadratureSamples));
}
}
catch(Exception e)
{
mLog.error("error during Airspy buffer dispatching", e);
}
mLog.error("Couldn't remove IQ buffer listener from Airspy tuner - processor is null");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +1,27 @@
/*******************************************************************************
* 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/>
* sdrtrunk
* Copyright (C) 2014-2017 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 source.tuner.fcd.proplusV2;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import source.SourceException;
import source.tuner.MixerTunerType;
import source.tuner.TunerClass;
@@ -33,197 +30,200 @@ import source.tuner.configuration.TunerConfiguration;
import source.tuner.fcd.FCDCommand;
import source.tuner.fcd.FCDTunerController;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class FCD2TunerController extends FCDTunerController
{
private final static Logger mLog = LoggerFactory.getLogger( FCD2TunerController.class );
private final static Logger mLog = LoggerFactory.getLogger(FCD2TunerController.class);
public static final int MINIMUM_TUNABLE_FREQUENCY = 150000;
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
public static final int SAMPLE_RATE = 192000;
public FCD2TunerController( Device device, DeviceDescriptor descriptor )
{
super( device, descriptor,
(int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(),
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY );
}
public static final int MINIMUM_TUNABLE_FREQUENCY = 150000;
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
public static final int SAMPLE_RATE = 192000;
public void init() throws SourceException
{
super.init();
public FCD2TunerController(Device device, DeviceDescriptor descriptor)
{
super(device, descriptor,
(int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(),
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
}
mFrequencyController.setSampleRate( SAMPLE_RATE );
public void init() throws SourceException
{
super.init();
try
{
setFCDMode( Mode.APPLICATION );
}
catch( Exception e )
{
throw new SourceException( "error setting Mode to APPLICATION", e );
}
}
public int getCurrentSampleRate()
{
return SAMPLE_RATE;
}
mFrequencyController.setSampleRate(SAMPLE_RATE);
@Override
try
{
setFCDMode(Mode.APPLICATION);
}
catch(Exception e)
{
throw new SourceException("error setting Mode to APPLICATION", e);
}
}
public int getCurrentSampleRate()
{
return SAMPLE_RATE;
}
@Override
public TunerClass getTunerClass()
{
return TunerClass.FUNCUBE_DONGLE_PRO_PLUS;
return TunerClass.FUNCUBE_DONGLE_PRO_PLUS;
}
@Override
@Override
public TunerType getTunerType()
{
return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
}
public void setLNAGain( boolean enabled ) throws SourceException
{
try
{
send( FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0 );
}
catch ( Exception e )
{
throw new SourceException( "error while setting LNA Gain", e );
}
}
public void setMixerGain( boolean enabled ) throws SourceException
{
try
{
send( FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0 );
}
catch ( Exception e )
{
throw new SourceException( "error while setting Mixer Gain", e );
}
}
@Override
public void apply( TunerConfiguration config ) throws SourceException
public void setLNAGain(boolean enabled) throws SourceException
{
if( config instanceof FCD2TunerConfiguration )
{
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
setFrequencyCorrection( fcd2.getFrequencyCorrection() );
setFrequency( fcd2.getFrequency() );
setLNAGain( fcd2.getGainLNA() );
setMixerGain( fcd2.getGainMixer() );
try
{
setFrequency( fcd2.getFrequency() );
}
catch( SourceException se )
{
//Do nothing, we couldn't set the frequency
}
}
try
{
send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0);
}
catch(Exception e)
{
throw new SourceException("error while setting LNA Gain", e);
}
}
public int getDCCorrection()
{
int dcCorrection = -999;
try
{
ByteBuffer buffer = send( FCDCommand.APP_GET_DC_CORRECTION );
buffer.order( ByteOrder.LITTLE_ENDIAN );
return buffer.getInt( 2 );
}
catch ( Exception e )
{
mLog.error( "error getting dc correction value", e );
}
return dcCorrection;
}
public void setDCCorrection( int value )
{
try
{
send( FCDCommand.APP_SET_DC_CORRECTION, value );
}
catch ( Exception e )
{
mLog.error( "error setting dc correction to [" + value + "]", e );
}
}
public int getIQCorrection()
{
int iqCorrection = -999;
try
{
ByteBuffer buffer = send( FCDCommand.APP_GET_IQ_CORRECTION );
buffer.order( ByteOrder.LITTLE_ENDIAN );
return buffer.getInt( 2 );
}
catch ( Exception e )
{
mLog.error( "error reading IQ correction value", e );
}
return iqCorrection;
}
public void setIQCorrection( int value )
{
try
{
send( FCDCommand.APP_SET_IQ_CORRECTION, value );
}
catch ( Exception e )
{
mLog.error( "error setting IQ correction to [" + value + "]", e );
}
}
public enum Block
{
CELLULAR_BAND_BLOCKED( "Blocked" ),
NO_BAND_BLOCK( "Unblocked" ),
UNKNOWN( "Unknown" );
private String mLabel;
private Block( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public static Block getBlock( String block )
{
Block retVal = UNKNOWN;
public void setMixerGain(boolean enabled) throws SourceException
{
try
{
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
}
catch(Exception e)
{
throw new SourceException("error while setting Mixer Gain", e);
}
}
if( block.equalsIgnoreCase( "No blk" ) )
{
retVal = NO_BAND_BLOCK;
}
else if( block.equalsIgnoreCase( "Cell blk" ) )
{
retVal = CELLULAR_BAND_BLOCKED;
}
return retVal;
}
}
@Override
public void apply(TunerConfiguration config) throws SourceException
{
if(config instanceof FCD2TunerConfiguration)
{
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
setFrequencyCorrection(fcd2.getFrequencyCorrection());
setFrequency(fcd2.getFrequency());
setLNAGain(fcd2.getGainLNA());
setMixerGain(fcd2.getGainMixer());
try
{
setFrequency(fcd2.getFrequency());
}
catch(SourceException se)
{
//Do nothing, we couldn't set the frequency
}
}
}
public int getDCCorrection()
{
int dcCorrection = -999;
try
{
ByteBuffer buffer = send(FCDCommand.APP_GET_DC_CORRECTION);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer.getInt(2);
}
catch(Exception e)
{
mLog.error("error getting dc correction value", e);
}
return dcCorrection;
}
public void setDCCorrection(int value)
{
try
{
send(FCDCommand.APP_SET_DC_CORRECTION, value);
}
catch(Exception e)
{
mLog.error("error setting dc correction to [" + value + "]", e);
}
}
public int getIQCorrection()
{
int iqCorrection = -999;
try
{
ByteBuffer buffer = send(FCDCommand.APP_GET_IQ_CORRECTION);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer.getInt(2);
}
catch(Exception e)
{
mLog.error("error reading IQ correction value", e);
}
return iqCorrection;
}
public void setIQCorrection(int value)
{
try
{
send(FCDCommand.APP_SET_IQ_CORRECTION, value);
}
catch(Exception e)
{
mLog.error("error setting IQ correction to [" + value + "]", e);
}
}
public enum Block
{
CELLULAR_BAND_BLOCKED("Blocked"),
NO_BAND_BLOCK("Unblocked"),
UNKNOWN("Unknown");
private String mLabel;
private Block(String label)
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public static Block getBlock(String block)
{
Block retVal = UNKNOWN;
if(block.equalsIgnoreCase("No blk"))
{
retVal = NO_BAND_BLOCK;
}
else if(block.equalsIgnoreCase("Cell blk"))
{
retVal = CELLULAR_BAND_BLOCKED;
}
return retVal;
}
}
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014-2017 Dennis Sheirer
*
* Ported from libhackrf at:
* https://github.com/mossmann/hackrf/tree/master/host/libhackrf
@@ -61,8 +61,6 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
import org.usb4java.Transfer;
import org.usb4java.TransferCallback;
import sample.Broadcaster;
import sample.Listener;
import sample.adapter.ByteSampleAdapter;
@@ -70,38 +68,25 @@ import sample.complex.ComplexBuffer;
import source.SourceException;
import source.tuner.TunerController;
import source.tuner.configuration.TunerConfiguration;
import util.ThreadPool;
import source.tuner.usb.USBTransferProcessor;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class HackRFTunerController extends TunerController
{
private final static Logger mLog =
LoggerFactory.getLogger(HackRFTunerController.class);
private final static Logger mLog = LoggerFactory.getLogger(HackRFTunerController.class);
public final static long USB_TIMEOUT_US = 1000000l; //uSeconds
public static final byte USB_ENDPOINT = (byte) 0x81;
public static final byte USB_INTERFACE = (byte) 0x0;
public static final byte USB_ENDPOINT = (byte)0x81;
public static final byte USB_INTERFACE = (byte)0x0;
public static final int USB_TRANSFER_BUFFER_SIZE = 262144;
public static final byte REQUEST_TYPE_IN =
(byte) (LibUsb.ENDPOINT_IN |
LibUsb.REQUEST_TYPE_VENDOR |
LibUsb.RECIPIENT_DEVICE);
public static final byte REQUEST_TYPE_OUT =
(byte) (LibUsb.ENDPOINT_OUT |
LibUsb.REQUEST_TYPE_VENDOR |
LibUsb.RECIPIENT_DEVICE);
public static final byte REQUEST_TYPE_IN = (byte)(LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final byte REQUEST_TYPE_OUT = (byte)(LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final long MIN_FREQUENCY = 10000000l;
public static final long MAX_FREQUENCY = 6000000000l;
@@ -109,18 +94,10 @@ public class HackRFTunerController extends TunerController
public static final double USABLE_BANDWIDTH_PERCENT = 0.95;
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
private LinkedTransferQueue<byte[]> mFilledBuffers =
new LinkedTransferQueue<byte[]>();
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
private BufferProcessor mBufferProcessor = new BufferProcessor();
private USBTransferProcessor mUSBTransferProcessor;
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
public int mBufferSize = 262144;
private boolean mAmplifierEnabled = false;
private Device mDevice;
@@ -167,6 +144,20 @@ public class HackRFTunerController extends TunerController
throw new SourceException("HackRF Tuner Controller - couldn't "
+ "claim USB interface or get endpoint or pipe", e);
}
String name;
try
{
name = "HackRF " + getSerial().getSerialNumber();
}
catch(UsbException ue)
{
//Do nothing, we couldn't determine the serial number
name = "HackRF - Unidentified Serial";
}
mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mSampleAdapter, USB_TRANSFER_BUFFER_SIZE);
}
/**
@@ -215,7 +206,7 @@ public class HackRFTunerController extends TunerController
*/
public BoardID getBoardID() throws UsbException
{
int id = readByte(Request.BOARD_ID_READ, (byte) 0, (byte) 0, false);
int id = readByte(Request.BOARD_ID_READ, (byte)0, (byte)0, false);
return BoardID.lookup(id);
}
@@ -317,8 +308,8 @@ public class HackRFTunerController extends TunerController
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.order(ByteOrder.LITTLE_ENDIAN);
int mhz = (int) (frequency / 1E6);
int hz = (int) (frequency - (mhz * 1E6));
int mhz = (int)(frequency / 1E6);
int hz = (int)(frequency - (mhz * 1E6));
buffer.putInt(mhz);
buffer.putInt(hz);
@@ -350,7 +341,7 @@ public class HackRFTunerController extends TunerController
if(config instanceof HackRFTunerConfiguration)
{
HackRFTunerConfiguration hackRFConfig =
(HackRFTunerConfiguration) config;
(HackRFTunerConfiguration)config;
try
{
@@ -395,8 +386,8 @@ public class HackRFTunerController extends TunerController
int transferred = LibUsb.controlTransfer(mDeviceHandle,
REQUEST_TYPE_IN,
request.getRequestNumber(),
(short) value,
(short) index,
(short)value,
(short)index,
buffer,
USB_TIMEOUT_US);
@@ -450,11 +441,11 @@ public class HackRFTunerController extends TunerController
if(signed)
{
return (int) (buffer.get());
return (int)(buffer.get());
}
else
{
return (int) (buffer.get() & 0xFF);
return (int)(buffer.get() & 0xFF);
}
}
@@ -468,8 +459,8 @@ public class HackRFTunerController extends TunerController
int transferred = LibUsb.controlTransfer(mDeviceHandle,
REQUEST_TYPE_OUT,
request.getRequestNumber(),
(short) value,
(short) index,
(short)value,
(short)index,
buffer,
USB_TIMEOUT_US);
@@ -567,7 +558,7 @@ public class HackRFTunerController extends TunerController
private Request(int number)
{
mRequestNumber = (byte) number;
mRequestNumber = (byte)number;
}
public byte getRequestNumber()
@@ -685,7 +676,7 @@ public class HackRFTunerController extends TunerController
private BoardID(int number, String label)
{
mIDNumber = (byte) number;
mIDNumber = (byte)number;
mLabel = label;
}
@@ -732,7 +723,7 @@ public class HackRFTunerController extends TunerController
private Mode(int number, String label)
{
mNumber = (byte) number;
mNumber = (byte)number;
mLabel = label;
}
@@ -890,21 +881,7 @@ public class HackRFTunerController extends TunerController
*/
public void addListener(Listener<ComplexBuffer> listener)
{
synchronized(mComplexBufferBroadcaster)
{
mComplexBufferBroadcaster.addListener(listener);
if(mBufferProcessor == null || !mBufferProcessor.isRunning())
{
mBufferProcessor = new BufferProcessor();
Thread thread = new Thread(mBufferProcessor);
thread.setDaemon(true);
thread.setName("HackRF Sample Processor");
thread.start();
}
}
mUSBTransferProcessor.addListener(listener);
}
/**
@@ -913,241 +890,7 @@ public class HackRFTunerController extends TunerController
*/
public void removeListener(Listener<ComplexBuffer> listener)
{
synchronized(mComplexBufferBroadcaster)
{
mComplexBufferBroadcaster.removeListener(listener);
if(!mComplexBufferBroadcaster.hasListeners())
{
mBufferProcessor.stop();
}
}
mUSBTransferProcessor.removeListener(listener);
}
/**
* Buffer processing thread. Fetches samples from the HackRF Tuner and
* dispatches them to all registered listeners
*/
public class BufferProcessor implements Runnable, TransferCallback
{
private ScheduledFuture<?> mSampleDispatcherTask;
private AtomicBoolean mRunning = new AtomicBoolean();
private LinkedTransferQueue<Transfer> mAvailableTransfers;
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
private ByteBuffer mLibUsbHandlerStatus;
private boolean mCancel = false;
@Override
public void run()
{
if(mRunning.compareAndSet(false, true))
{
prepareTransfers();
mSampleDispatcherTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferDispatcher(),
20, 20, TimeUnit.MILLISECONDS);
mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
List<Transfer> transfers = new ArrayList<>();
mCancel = false;
while(mRunning.get())
{
mAvailableTransfers.drainTo(transfers);
for(Transfer transfer : transfers)
{
int result = LibUsb.submitTransfer(transfer);
if(result == LibUsb.SUCCESS)
{
mTransfersInProgress.add(transfer);
}
else
{
mLog.error("error submitting transfer [" +
LibUsb.errorName(result) + "]");
}
}
int result = LibUsb.handleEventsTimeoutCompleted(
null, USB_TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error("error handling events for libusb");
}
transfers.clear();
mLibUsbHandlerStatus.rewind();
}
if(mCancel)
{
for(Transfer transfer : mTransfersInProgress)
{
LibUsb.cancelTransfer(transfer);
}
int result = LibUsb.handleEventsTimeoutCompleted(null,
USB_TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error("error cancelling transfers in progress");
}
mLibUsbHandlerStatus.rewind();
}
}
}
/**
* Stops the sample fetching thread
*/
public void stop()
{
mCancel = true;
if(mRunning.compareAndSet(true, false))
{
if(mSampleDispatcherTask != null)
{
mSampleDispatcherTask.cancel(true);
mFilledBuffers.clear();
}
}
}
/**
* Indicates if this thread is running
*/
public boolean isRunning()
{
return mRunning.get();
}
@Override
public void processTransfer(Transfer transfer)
{
mTransfersInProgress.remove(transfer);
switch(transfer.status())
{
case LibUsb.TRANSFER_COMPLETED:
case LibUsb.TRANSFER_STALL:
if(transfer.actualLength() > 0)
{
ByteBuffer buffer = transfer.buffer();
byte[] data = new byte[transfer.actualLength()];
buffer.get(data);
buffer.rewind();
if(isRunning())
{
mFilledBuffers.add(data);
}
}
break;
case LibUsb.TRANSFER_CANCELLED:
break;
default:
/* unexpected error */
mLog.error("transfer error [" +
getTransferStatus(transfer.status()) +
"] transferred actual: " + transfer.actualLength());
}
mAvailableTransfers.add(transfer);
}
/**
* Prepares (allocates) a set of transfer buffers for use in
* transferring data from the tuner via the bulk interface
*/
private void prepareTransfers() throws LibUsbException
{
if(mAvailableTransfers == null)
{
mAvailableTransfers = new LinkedTransferQueue<>();
for(int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++)
{
Transfer transfer = LibUsb.allocTransfer();
if(transfer == null)
{
throw new LibUsbException("couldn't allocate transfer",
LibUsb.ERROR_NO_MEM);
}
final ByteBuffer buffer =
ByteBuffer.allocateDirect(mBufferSize);
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_ENDPOINT,
buffer, BufferProcessor.this, "Buffer", USB_TIMEOUT_US);
mAvailableTransfers.add(transfer);
}
}
}
}
/**
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
* array and broadcasts the array to all registered listeners
*/
public class BufferDispatcher implements Runnable
{
@Override
public void run()
{
try
{
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
mFilledBuffers.drainTo(buffers);
for(byte[] buffer : buffers)
{
float[] samples = mSampleAdapter.convert(buffer);
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(samples));
}
}
catch(Exception e)
{
mLog.error("error duruing HackRF buffer dispatching", e);
}
}
}
public static String getTransferStatus(int status)
{
switch(status)
{
case 0:
return "TRANSFER COMPLETED (0)";
case 1:
return "TRANSFER ERROR (1)";
case 2:
return "TRANSFER TIMED OUT (2)";
case 3:
return "TRANSFER CANCELLED (3)";
case 4:
return "TRANSFER STALL (4)";
case 5:
return "TRANSFER NO DEVICE (5)";
case 6:
return "TRANSFER OVERFLOW (6)";
default:
return "UNKNOWN TRANSFER STATUS (" + status + ")";
}
}
}

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014-2017 Dennis Sheirer
*
* Java version based on librtlsdr
* Copyright (C) 2012-2013 by Steve Markgraf <steve@steve-m.de>
@@ -21,7 +21,6 @@
******************************************************************************/
package source.tuner.rtl;
import buffer.FloatAveragingBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
@@ -29,54 +28,38 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
import org.usb4java.Transfer;
import org.usb4java.TransferCallback;
import sample.Broadcaster;
import sample.Listener;
import sample.adapter.ByteSampleAdapter;
import sample.adapter.ISampleAdapter;
import sample.complex.ComplexBuffer;
import source.SourceException;
import source.tuner.TunerController;
import source.tuner.TunerManager;
import source.tuner.TunerType;
import util.ThreadPool;
import source.tuner.usb.USBTransferProcessor;
import javax.usb.UsbDisconnectedException;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class RTL2832TunerController extends TunerController
{
private final static Logger mLog = LoggerFactory.getLogger(RTL2832TunerController.class);
public final static int INT_NULL_VALUE = -1;
public final static long LONG_NULL_VALUE = -1l;
public final static double DOUBLE_NULL_VALUE = -1.0D;
public final static int TWO_TO_22_POWER = 4194304;
public final static int USB_TRANSFER_BUFFER_SIZE = 131072;
public final static byte USB_INTERFACE = (byte) 0x0;
public final static byte USB_BULK_ENDPOINT = (byte) 0x81;
public final static byte CONTROL_ENDPOINT_IN =
(byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR);
public final static byte CONTROL_ENDPOINT_OUT =
(byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR);
public final static byte CONTROL_ENDPOINT_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR);
public final static byte CONTROL_ENDPOINT_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR);
public final static long TIMEOUT_US = 1000000l; //uSeconds
public final static byte REQUEST_ZERO = (byte) 0;
public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
public final static byte EEPROM_ADDRESS = (byte) 0xA0;
public final static byte[] sFIR_COEFFICIENTS =
@@ -87,8 +70,7 @@ public abstract class RTL2832TunerController extends TunerController
(byte) 0x71, (byte) 0x74, (byte) 0x19, (byte) 0x41, (byte) 0xA5
};
public static final SampleRate DEFAULT_SAMPLE_RATE =
SampleRate.RATE_0_960MHZ;
public static final SampleRate DEFAULT_SAMPLE_RATE = SampleRate.RATE_2_400MHZ;
protected Device mDevice;
protected DeviceDescriptor mDeviceDescriptor;
@@ -96,24 +78,13 @@ public abstract class RTL2832TunerController extends TunerController
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
private BufferProcessor mBufferProcessor;
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private LinkedTransferQueue<byte[]> mFilledBuffers =
new LinkedTransferQueue<byte[]>();
private SampleRateMonitor mSampleRateMonitor;
private AtomicInteger mSampleCounter = new AtomicInteger();
private static final DecimalFormat mDecimalFormatter =
new DecimalFormat("###,###,###.0");
private static final DecimalFormat mPercentFormatter =
new DecimalFormat("###.00");
protected int mOscillatorFrequency = 28800000; //28.8 MHz
public int mBufferSize = 131072;
private USBTransferProcessor mUSBTransferProcessor;
protected Descriptor mDescriptor;
@@ -190,6 +161,11 @@ public abstract class RTL2832TunerController extends TunerController
+ "descriptor byte array " +
(eeprom == null ? "[null]" : Arrays.toString(eeprom)), e);
}
String deviceName = getTunerType().getLabel() + " " + getUniqueID();
mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
USB_TRANSFER_BUFFER_SIZE);
}
/**
@@ -265,7 +241,7 @@ public abstract class RTL2832TunerController extends TunerController
switch(mode)
{
case QUADRATURE:
/* Set intermediate frequency to 0 Hz */
/* Set intermediate frequency to 0 Hz */
setIFFrequency(0);
/* Enable I/Q ADC Input */
@@ -476,14 +452,13 @@ public abstract class RTL2832TunerController extends TunerController
{
try
{
if(mBufferProcessor.isRunning())
if(mUSBTransferProcessor != null)
{
mBufferProcessor.stop();
mUSBTransferProcessor.removeAllListeners();
TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(mUSBTransferProcessor);
}
LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE);
LibUsb.exit(null);
}
catch(Exception e)
{
@@ -1014,11 +989,6 @@ public abstract class RTL2832TunerController extends TunerController
mSampleRate = sampleRate;
mFrequencyController.setSampleRate(sampleRate.getRate());
if(mSampleRateMonitor != null)
{
mSampleRateMonitor.setSampleRate(mSampleRate.getRate());
}
}
public void setSampleRateFrequencyCorrection(int ppm) throws SourceException
@@ -1318,331 +1288,6 @@ public abstract class RTL2832TunerController extends TunerController
}
}
/**
* Adds a sample listener. If the buffer processing thread is
* not currently running, starts it running in a new thread.
*/
public void addListener(Listener<ComplexBuffer> listener)
{
synchronized(mComplexBufferBroadcaster)
{
mComplexBufferBroadcaster.addListener(listener);
if(mBufferProcessor == null)
{
mBufferProcessor = new BufferProcessor();
}
if(!mBufferProcessor.isRunning())
{
Thread thread = new Thread(mBufferProcessor);
thread.setDaemon(true);
thread.setName("RTL2832 Sample Processor");
thread.start();
}
}
}
/**
* Removes the sample listener. If this is the last registered listener,
* shuts down the buffer processing thread.
*/
public void removeListener(Listener<ComplexBuffer> listener)
{
synchronized(mComplexBufferBroadcaster)
{
mComplexBufferBroadcaster.removeListener(listener);
if(!mComplexBufferBroadcaster.hasListeners())
{
mBufferProcessor.stop();
}
}
}
/**
* Buffer processing thread. Fetches samples from the RTL2832 Tuner and
* dispatches them to all registered listeners
*/
public class BufferProcessor implements Runnable, TransferCallback
{
private ScheduledFuture<?> mSampleDispatcherTask;
private LinkedTransferQueue<Transfer> mAvailableTransfers;
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private ByteBuffer mLibUsbHandlerStatus;
private boolean mCancel = false;
public BufferProcessor()
{
}
@Override
public void run()
{
if(mRunning.compareAndSet(false, true))
{
try
{
setSampleRate(mSampleRate);
resetUSBBuffer();
}
catch(SourceException e)
{
mLog.error("couldn't start buffer processor", e);
mRunning.set(false);
}
prepareTransfers();
mSampleDispatcherTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferDispatcher(),
20, 20, TimeUnit.MILLISECONDS);
mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
List<Transfer> transfers = new ArrayList<>();
mCancel = false;
while(mRunning.get())
{
mAvailableTransfers.drainTo(transfers);
for(Transfer transfer : transfers)
{
int result = LibUsb.submitTransfer(transfer);
if(result == LibUsb.SUCCESS)
{
mTransfersInProgress.add(transfer);
}
else
{
mLog.error("Error submitting transfer [" +
LibUsb.errorName(result) + "]");
}
}
int result = LibUsb.handleEventsTimeoutCompleted(
null, TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error("error handling events for libusb");
}
transfers.clear();
mLibUsbHandlerStatus.rewind();
}
if(mCancel)
{
for(Transfer transfer : mTransfersInProgress)
{
LibUsb.cancelTransfer(transfer);
}
int result = LibUsb.handleEventsTimeoutCompleted(null,
TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer());
if(result != LibUsb.SUCCESS)
{
mLog.error("error handling events for libusb during cancel");
}
mLibUsbHandlerStatus.rewind();
}
}
}
/**
* Stops the sample fetching thread
*/
public void stop()
{
mCancel = true;
if(mRunning.compareAndSet(true, false))
{
if(mSampleDispatcherTask != null)
{
mSampleDispatcherTask.cancel(true);
mSampleDispatcherTask = null;
mFilledBuffers.clear();
}
}
}
/**
* Indicates if this thread is running
*/
public boolean isRunning()
{
return mRunning.get();
}
@Override
public void processTransfer(Transfer transfer)
{
mTransfersInProgress.remove(transfer);
switch(transfer.status())
{
case LibUsb.TRANSFER_COMPLETED:
case LibUsb.TRANSFER_STALL:
if(transfer.actualLength() > 0)
{
ByteBuffer buffer = transfer.buffer();
byte[] data = new byte[transfer.actualLength()];
buffer.get(data);
buffer.rewind();
if(isRunning())
{
mFilledBuffers.add(data);
}
}
break;
case LibUsb.TRANSFER_CANCELLED:
break;
default:
/* unexpected error */
mLog.error("transfer error [" +
getTransferStatus(transfer.status()) +
"] transferred actual: " + transfer.actualLength());
}
mAvailableTransfers.add(transfer);
}
/**
* Prepares (allocates) a set of transfer buffers for use in
* transferring data from the tuner via the bulk interface
*/
private void prepareTransfers() throws LibUsbException
{
if(mAvailableTransfers == null)
{
mAvailableTransfers = new LinkedTransferQueue<>();
for(int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++)
{
Transfer transfer = LibUsb.allocTransfer();
if(transfer == null)
{
throw new LibUsbException("couldn't allocate transfer",
LibUsb.ERROR_NO_MEM);
}
final ByteBuffer buffer =
ByteBuffer.allocateDirect(mBufferSize);
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_ENDPOINT,
buffer, BufferProcessor.this, "Buffer", TIMEOUT_US);
mAvailableTransfers.add(transfer);
}
}
}
}
/**
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
* array and broadcasts the array to all registered listeners
*/
public class BufferDispatcher implements Runnable
{
@Override
public void run()
{
try
{
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
mFilledBuffers.drainTo(buffers);
for(byte[] buffer : buffers)
{
float[] samples = mSampleAdapter.convert(buffer);
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(samples));
}
}
catch(Exception e)
{
mLog.error("error during rtl2832 buffer dispatcher run", e);
}
}
}
public static String getTransferStatus(int status)
{
switch(status)
{
case 0:
return "TRANSFER COMPLETED (0)";
case 1:
return "TRANSFER ERROR (1)";
case 2:
return "TRANSFER TIMED OUT (2)";
case 3:
return "TRANSFER CANCELLED (3)";
case 4:
return "TRANSFER STALL (4)";
case 5:
return "TRANSFER NO DEVICE (5)";
case 6:
return "TRANSFER OVERFLOW (6)";
default:
return "UNKNOWN TRANSFER STATUS (" + status + ")";
}
}
public class USBEventHandlingThread implements Runnable
{
/**
* If thread should abort.
*/
private volatile boolean mAbort;
/**
* Aborts the event handling thread.
*/
public void abort()
{
mAbort = true;
}
@Override
public void run()
{
while(!mAbort)
{
int result = LibUsb.handleEventsTimeout(null, 1000);
if(result != LibUsb.SUCCESS)
{
mAbort = true;
mLog.error("error handling usb events [" +
LibUsb.errorName(result) + "]");
throw new LibUsbException("Unable to handle USB "
+ "events", result);
}
}
}
}
public enum TunerTypeCheck
{
E4K(0xC8, 0x02, 0x40),
@@ -1679,102 +1324,6 @@ public abstract class RTL2832TunerController extends TunerController
}
}
/**
* Averages the sample rate over a 10-second period. The count is for the
* number of bytes received from the tuner. There are two bytes for each
* sample. So, we divide by 20 to get the average sample rate.
*/
public class SampleRateMonitor implements Runnable
{
private static final int BUFFER_SIZE = 5;
private int mTargetSampleRate;
private int mSampleRateMinimum;
private int mSampleRateMaximum;
private int mNewTargetSampleRate;
private FloatAveragingBuffer mRateErrorBuffer =
new FloatAveragingBuffer(BUFFER_SIZE);
private AtomicBoolean mRateChanged = new AtomicBoolean();
public SampleRateMonitor(int sampleRate)
{
setTargetRate(sampleRate);
}
public void setSampleRate(int sampleRate)
{
mNewTargetSampleRate = sampleRate;
mRateChanged.set(true);
}
private void setTargetRate(int rate)
{
mTargetSampleRate = rate;
mSampleRateMinimum = (int) ((float) mTargetSampleRate * 0.95f);
mSampleRateMaximum = (int) ((float) mTargetSampleRate * 1.05f);
for(int x = 0; x < BUFFER_SIZE; x++)
{
mRateErrorBuffer.get(0);
}
}
@Override
public void run()
{
if(mRateChanged.compareAndSet(true, false))
{
setTargetRate(mNewTargetSampleRate);
/* Reset the sample counter */
mSampleCounter.set(0);
mLog.info("monitor reset for new sample rate [" + mTargetSampleRate + "]");
}
else
{
int count = mSampleCounter.getAndSet(0);
float current = (float) count / 20.0f;
/**
* Only accept values +/- 5% of target rate
*/
float average;
if(mSampleRateMinimum < current && current < mSampleRateMaximum)
{
average = mRateErrorBuffer.get((float) mTargetSampleRate - current);
}
else
{
average = mTargetSampleRate;
}
StringBuilder sb = new StringBuilder();
sb.append("[");
if(mDescriptor != null)
{
sb.append(mDescriptor.getSerial());
}
else
{
sb.append("DESCRIPTOR IS NULL");
}
sb.append("] sample rate current [");
sb.append(mDecimalFormatter.format(current));
sb.append(" Hz ");
sb.append(mPercentFormatter.format(100.0f * (current / (float) mTargetSampleRate)));
sb.append("% ] error [");
sb.append(average);
sb.append(" Hz ] target ");
sb.append(mDecimalFormatter.format(mTargetSampleRate));
mLog.info(sb.toString());
}
}
}
/**
* RTL2832 EEPROM byte array descriptor parsing class
*/
@@ -1945,4 +1494,47 @@ public abstract class RTL2832TunerController extends TunerController
}
}
/**
* Adds a sample listener. If the USB transfer processor is not currently running, it will auto-start
*/
public void addListener(Listener<ComplexBuffer> listener)
{
mUSBTransferProcessor.addListener(listener);
}
/**
* Removes the sample listener. If this is the last registered listener, the USB transfer processor will
* halt buffer processing
*/
public void removeListener(Listener<ComplexBuffer> listener)
{
mUSBTransferProcessor.removeListener(listener);
}
/**
* RTL-2832 USB transfer processor. Extends USB transfer processor and allows resetting the device USB
* buffer prior to starting streaming.
*/
public class RTL2832USBTransferProcessor extends USBTransferProcessor
{
/**
* 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 bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144.
*/
public RTL2832USBTransferProcessor(String deviceName, DeviceHandle deviceHandle, ISampleAdapter sampleAdapter, int bufferSize)
{
super(deviceName, deviceHandle, sampleAdapter, bufferSize);
}
@Override
protected void prepareDeviceStart()
{
resetUSBBuffer();
}
}
}

View File

@@ -0,0 +1,390 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 source.tuner.usb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
import org.usb4java.Transfer;
import org.usb4java.TransferCallback;
import sample.Broadcaster;
import sample.Listener;
import sample.OverflowableTransferQueue;
import sample.adapter.ISampleAdapter;
import sample.complex.ComplexBuffer;
import source.tuner.TunerManager;
import util.ThreadPool;
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;
import java.util.concurrent.atomic.AtomicBoolean;
public class USBTransferProcessor implements TransferCallback
{
private final static Logger mLog = LoggerFactory.getLogger(USBTransferProcessor.class);
private static final byte USB_BULK_TRANSFER_ENDPOINT = (byte) 0x81;
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;
//Maximum number of filled buffers for the blocking queue
private static final int FILLED_BUFFER_MAX_CAPACITY = 300;
//Threshold for resetting buffer overflow condition
private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 200;
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;
//Byte array transfer buffers size in bytes
private int mBufferSize;
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private OverflowableTransferQueue<byte[]> mFilledBuffers;
private LinkedTransferQueue<Transfer> mAvailableTransfers = new LinkedTransferQueue<>();
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private ByteBuffer mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
private BufferDispatcher mBufferDispatcher = new BufferDispatcher();
private ScheduledFuture mBufferDispatcherFuture;
/**
* 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 bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144.
*/
public USBTransferProcessor(String deviceName,
DeviceHandle deviceHandle,
ISampleAdapter sampleAdapter,
int bufferSize)
{
mDeviceName = deviceName;
mDeviceHandle = deviceHandle;
mSampleAdapter = sampleAdapter;
mBufferSize = bufferSize;
mFilledBuffers = new OverflowableTransferQueue<>(FILLED_BUFFER_MAX_CAPACITY, FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD);
mFilledBuffers.setStateListener(new Listener<OverflowableTransferQueue.State>()
{
@Override
public void receive(OverflowableTransferQueue.State state)
{
if(state == OverflowableTransferQueue.State.NORMAL)
{
mLog.debug(mDeviceName + " - buffer overflow - temporary pause until processing catches up");
}
else
{
mLog.debug(mDeviceName + " - buffer overflow cleared - resuming normal processing");
}
}
});
}
/**
* Start USB transfer buffer processing. Subsequent calls to this method after started will be ignored.
*/
private void start()
{
if(mRunning.compareAndSet(false, true))
{
prepareDeviceStart();
prepareTransfers();
while(!mAvailableTransfers.isEmpty())
{
Transfer transfer = mAvailableTransfers.poll();
if(transfer != null)
{
int result = LibUsb.submitTransfer(transfer);
if(result == LibUsb.SUCCESS)
{
mTransfersInProgress.add(transfer);
}
else
{
//TODO: broadcast to each listener that this source has an error and is shutting down
mLog.error(mDeviceName + "- error submitting transfer [" + LibUsb.errorName(result) + "]");
}
}
}
//Start transferred buffer dispatcher
mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferDispatcher,
20, 20, 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.registerTransferProcessor(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();
}
}
/**
* Allows sub-class implementations to execute any device-specific operations to prepare for starting USB transfers
*/
protected void prepareDeviceStart()
{
}
/**
* Allows sub-class implementations to execute any device-specific operations after stopping USB transfers
*/
protected void executeDeviceStop()
{
}
/**
* Indicates if there are complex buffer listeners registered with this processor
*/
public boolean hasListeners()
{
return mComplexBufferBroadcaster.hasListeners();
}
public void addListener(Listener<ComplexBuffer> listener)
{
mComplexBufferBroadcaster.addListener(listener);
start();
}
public void removeListener(Listener<ComplexBuffer> listener)
{
mComplexBufferBroadcaster.removeListener(listener);
if(!hasListeners())
{
stop();
}
}
public void removeAllListeners()
{
mComplexBufferBroadcaster.clear();
stop();
}
/**
* 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
* start/stop cycles.
*/
private void prepareTransfers() throws LibUsbException
{
while(mAvailableTransfers.size() < TRANSFER_BUFFER_POOL_SIZE)
{
Transfer transfer = LibUsb.allocTransfer();
if(transfer == null)
{
throw new LibUsbException("Couldn't allocate USB transfer buffer", LibUsb.ERROR_NO_MEM);
}
final ByteBuffer buffer = ByteBuffer.allocateDirect(mBufferSize);
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer, this,
"Buffer", USB_TIMEOUT_MS);
mAvailableTransfers.add(transfer);
}
}
/**
* Process a filled transfer buffer received back from the USB device
*/
@Override
public void processTransfer(Transfer transfer)
{
mTransfersInProgress.remove(transfer);
switch(transfer.status())
{
case LibUsb.TRANSFER_COMPLETED:
case LibUsb.TRANSFER_STALL:
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
{
mAvailableTransfers.add(transfer);
mLog.error(mDeviceName + " - error submitting transfer [" + LibUsb.errorName(result) + "]");
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);
}
}
}
/**
* Converts the USB transfer status number into a descriptive label
*/
public static String getTransferStatus(int status)
{
switch(status)
{
case 0:
return "TRANSFER COMPLETED (0)";
case 1:
return "TRANSFER ERROR (1)";
case 2:
return "TRANSFER TIMED OUT (2)";
case 3:
return "TRANSFER CANCELLED (3)";
case 4:
return "TRANSFER STALL (4)";
case 5:
return "TRANSFER NO DEVICE (5)";
case 6:
return "TRANSFER OVERFLOW (6)";
default:
return "UNKNOWN TRANSFER STATUS (" + status + ")";
}
}
/**
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
* array and broadcasts the array to all registered listeners
*/
public class BufferDispatcher implements Runnable
{
private List<byte[]> mBuffersToDispatch = new ArrayList<>();
@Override
public void run()
{
try
{
mFilledBuffers.drainTo(mBuffersToDispatch, 15);
for(byte[] buffer : mBuffersToDispatch)
{
float[] complexSamples = mSampleAdapter.convert(buffer);
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples));
}
}
catch(Exception e)
{
mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e);
}
mBuffersToDispatch.clear();
}
}
}