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,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -14,6 +14,7 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package source.tuner; package source.tuner;
@@ -25,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sample.Buffer; import sample.Buffer;
import sample.Listener; import sample.Listener;
import sample.OverflowableTransferQueue;
import sample.complex.Complex; import sample.complex.Complex;
import sample.complex.ComplexBuffer; import sample.complex.ComplexBuffer;
import source.ComplexSource; import source.ComplexSource;
@@ -35,24 +37,27 @@ import source.tuner.frequency.IFrequencyChangeProcessor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class TunerChannelSource extends ComplexSource public class TunerChannelSource extends ComplexSource implements IFrequencyChangeProcessor, Listener<ComplexBuffer>
implements IFrequencyChangeProcessor, Listener<ComplexBuffer>
{ {
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
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_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000; private static int CHANNEL_PASS_FREQUENCY = 12000;
private LinkedTransferQueue<ComplexBuffer> mBuffer = private OverflowableTransferQueue<ComplexBuffer> mBuffer;
new LinkedTransferQueue<ComplexBuffer>();
private Tuner mTuner; private Tuner mTuner;
private TunerChannel mTunerChannel; private TunerChannel mTunerChannel;
private Oscillator mMixer; private Oscillator mMixer;
@@ -69,6 +74,7 @@ public class TunerChannelSource extends ComplexSource
private AtomicBoolean mRunning = new AtomicBoolean(); private AtomicBoolean mRunning = new AtomicBoolean();
private boolean mExpended = false; private boolean mExpended = false;
private int mCount;
/** /**
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a * 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 * accept the decimation processing task
* @throws SourceException if the tuner has an issue providing IQ samples * @throws SourceException if the tuner has an issue providing IQ samples
*/ */
public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel) public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel) throws RejectedExecutionException, SourceException
throws RejectedExecutionException, SourceException
{ {
mTuner = tuner; mTuner = tuner;
mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
mTunerChannel = tunerChannel; mTunerChannel = tunerChannel;
mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
mTunerFrequency = mTuner.getTunerController().getFrequency(); 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 */ /* Setup the frequency translator to the current source frequency */
long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency(); long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency();
mMixer = new Oscillator(frequencyOffset, mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
mTuner.getTunerController().getSampleRate());
/* Fire a sample rate change event to setup the decimation chain */ /* Fire a sample rate change event to setup the decimation chain */
frequencyChanged(new FrequencyChangeEvent( frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate()));
Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
mTuner.getTunerController().getSampleRate()));
} }
@@ -114,23 +131,22 @@ public class TunerChannelSource extends ComplexSource
{ {
if(mExpended) if(mExpended)
{ {
throw new IllegalStateException("Attempt to re-start an expended " throw new IllegalStateException("Attempt to re-start an expended tuner channel source. TunerChannelSource" +
+ "tuner channel source. TunerChannelSource objects can only " " objects can only be used once. ");
+ "be used once. ");
} }
if(mRunning.compareAndSet(false, true)) if(mRunning.compareAndSet(false, true))
{ {
/* Schedule the decimation task to run every 10 ms (100 iterations/second) */ //Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, //to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
0, 10, TimeUnit.MILLISECONDS); mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
/* Finally, register to receive samples from the tuner */ /* Finally, register to receive samples from the tuner */
mTuner.addListener((Listener<ComplexBuffer>) this); mTuner.addListener((Listener<ComplexBuffer>) this);
} }
else 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 */ /* Tell the tuner to release/unregister our resources */
mTuner.getTunerController().removeListener(this); 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()) if(mRunning.get())
{ {
mBuffer.add(buffer); mBuffer.offer(buffer);
} }
} }
@@ -248,10 +256,8 @@ public class TunerChannelSource extends ComplexSource
if(mFrequencyChangeProcessor != null) if(mFrequencyChangeProcessor != null)
{ {
mFrequencyChangeProcessor.frequencyChanged( mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
new FrequencyChangeEvent( Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
mChannelFrequencyCorrection));
} }
break; break;
case NOTIFICATION_SAMPLE_RATE_CHANGE: case NOTIFICATION_SAMPLE_RATE_CHANGE:
@@ -262,13 +268,8 @@ public class TunerChannelSource extends ComplexSource
mMixer.setSampleRate(sampleRate); mMixer.setSampleRate(sampleRate);
/* Get new decimation filter */ /* Get new decimation filter */
mDecimationFilter = FilterFactory mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
.getDecimationFilter(sampleRate, CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
CHANNEL_RATE,
1, //Order
CHANNEL_PASS_FREQUENCY,
60, //dB attenuation
WindowType.HAMMING);
/* re-add the original output listener */ /* re-add the original output listener */
mDecimationFilter.setListener(mListener); mDecimationFilter.setListener(mListener);
@@ -328,8 +329,7 @@ public class TunerChannelSource extends ComplexSource
{ {
if(mProcessing) if(mProcessing)
{ {
{ mBuffer.drainTo(mSampleBuffers, 20);
mBuffer.drainTo(mSampleBuffers, 8);
for(Buffer buffer : mSampleBuffers) for(Buffer buffer : mSampleBuffers)
{ {
@@ -371,7 +371,6 @@ public class TunerChannelSource extends ComplexSource
mSampleBuffers.clear(); mSampleBuffers.clear();
} }
} }
}
catch(Exception e) catch(Exception e)
{ {
/* Only log the stack trace if we're still processing */ /* Only log the stack trace if we're still processing */

View File

@@ -42,12 +42,24 @@ import java.util.Collection;
public class TunerManager public class TunerManager
{ {
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger(TunerManager.class);
LoggerFactory.getLogger(TunerManager.class);
private MixerManager mMixerManager; private MixerManager mMixerManager;
private TunerModel mTunerModel; 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) public TunerManager(MixerManager mixerManager, TunerModel tunerModel)
{ {
mMixerManager = mixerManager; mMixerManager = mixerManager;

View File

@@ -1,8 +1,6 @@
package source.tuner.airspy;
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -16,16 +14,23 @@ package source.tuner.airspy;
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package source.tuner.airspy;
import dsp.filter.dc.DCRemovalFilter_RB;
import dsp.filter.hilbert.HilbertTransform;
import sample.adapter.ISampleAdapter; import sample.adapter.ISampleAdapter;
public class AirspySampleAdapter implements ISampleAdapter public class AirspySampleAdapter implements ISampleAdapter
{ {
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f; 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 boolean mSamplePacking = false;
/** /**
* Adapter to translate byte buffers received from the airspy tuner into * Adapter to translate byte buffers received from the airspy tuner into
* float buffers for processing. * float buffers for processing.
@@ -40,22 +45,28 @@ public class AirspySampleAdapter implements ISampleAdapter
* *
* @param enabled * @param enabled
*/ */
public void setSamplePacking( boolean enabled ) public void setSamplePacking(boolean enabled)
{ {
mSamplePacking = enabled; mSamplePacking = enabled;
} }
@Override @Override
public float[] convert( byte[] samples ) public float[] convert(byte[] samples)
{ {
if( mSamplePacking ) float[] realSamples;
if(mSamplePacking)
{ {
return convertPacked( samples ); realSamples = convertPacked(samples);
} }
else else
{ {
return convertUnpacked( samples ); realSamples = convertUnpacked(samples);
} }
mDCFilter.filter(realSamples);
return mHilbertTransform.filter(realSamples);
} }
/** /**
@@ -65,16 +76,16 @@ public class AirspySampleAdapter implements ISampleAdapter
* @param data - byte array of unsigned 16-bit values * @param data - byte array of unsigned 16-bit values
* @return converted float values * @return converted float values
*/ */
private float[] convertUnpacked( byte[] data ) private float[] convertUnpacked(byte[] data)
{ {
float[] samples = new float[ data.length / 2 ]; float[] samples = new float[data.length / 2];
int pointer = 0; int pointer = 0;
for( int x = 0; x < data.length; x += 2 ) for(int x = 0; x < data.length; x += 2)
{ {
samples[ pointer++ ] = scale( ( data[ x ] & 0xFF ) | samples[pointer++] = scale((data[x] & 0xFF) |
( data[ x + 1 ] << 8 ) ); (data[x + 1] << 8));
} }
return samples; return samples;
@@ -84,50 +95,50 @@ public class AirspySampleAdapter implements ISampleAdapter
* Converts every 3 bytes containing a pair of 12-bit unsigned values into * Converts every 3 bytes containing a pair of 12-bit unsigned values into
* a pair of float values in the range -1 to 1; * a pair of float values in the range -1 to 1;
* *
* @param data - byte array of unsigned 12-bit values * @param data1 - byte array of unsigned 12-bit values
* @return converted float values * @return converted float values
*/ */
private float[] convertPacked( byte[] data1 ) private float[] convertPacked(byte[] data1)
{ {
byte[] data = new byte[ data1.length ]; byte[] data = new byte[data1.length];
//Convert big-endian to little-endian //Convert big-endian to little-endian
for( int x = 0; x < data1.length; x += 4 ) for(int x = 0; x < data1.length; x += 4)
{ {
data[ x ] = data1[ x + 3 ]; data[x] = data1[x + 3];
data[ x + 1 ] = data1[ x + 2 ]; data[x + 1] = data1[x + 2];
data[ x + 2 ] = data1[ x + 1 ]; data[x + 2] = data1[x + 1];
data[ x + 3 ] = data1[ x ]; data[x + 3] = data1[x];
} }
int count = (int)( (float)data.length / 1.5f ); int count = (int) ((float) data.length / 1.5f);
/* Ensure we have an even number of samples */ /* Ensure we have an even number of samples */
if( count % 2 == 1 ) if(count % 2 == 1)
{ {
count--; count--;
} }
int bytes = (int)((float)count * 1.5f); int bytes = (int) ((float) count * 1.5f);
float[] samples = new float[ count ]; float[] samples = new float[count];
int pointer = 0; int pointer = 0;
int first; int first;
int second; int second;
for( int x = 0; x < bytes; x += 3 ) for(int x = 0; x < bytes; x += 3)
{ {
first = ( ( data[ x ] << 4 ) & 0xFF0 ) | first = ((data[x] << 4) & 0xFF0) |
( ( data[ x + 1 ] >> 4 ) & 0xF ); ((data[x + 1] >> 4) & 0xF);
samples[ pointer++ ] = scale( first ); samples[pointer++] = scale(first);
second = ( ( data[ x + 1 ] << 8 ) & 0xF00 ) | second = ((data[x + 1] << 8) & 0xF00) |
( data[ x + 2 ] & 0xFF ); (data[x + 2] & 0xFF);
samples[ pointer++ ] = scale( second ); samples[pointer++] = scale(second);
} }
return samples; return samples;
@@ -137,8 +148,8 @@ public class AirspySampleAdapter implements ISampleAdapter
* Converts unsigned 12-bit values to signed 12-bit values and then scales * 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 * the signed value to a signed float value in range: -1.0 : +1.0
*/ */
public static float scale( int value ) public static float scale(int value)
{ {
return (float)(( value & 0xFFF ) - 2048 ) * SCALE_SIGNED_12_BIT_TO_FLOAT; 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.SourceException;
import source.tuner.TunerController; import source.tuner.TunerController;
import source.tuner.configuration.TunerConfiguration; import source.tuner.configuration.TunerConfiguration;
import source.tuner.usb.USBTransferProcessor;
import util.ThreadPool; import util.ThreadPool;
import javax.usb.UsbException; import javax.usb.UsbException;
@@ -93,21 +94,11 @@ public class AirspyTunerController extends TunerController
public static final AirspySampleRate DEFAULT_SAMPLE_RATE = public static final AirspySampleRate DEFAULT_SAMPLE_RATE =
new AirspySampleRate(0, 10000000, "10.00 MHz"); new AirspySampleRate(0, 10000000, "10.00 MHz");
public static final long USB_TIMEOUT_MS = 2000l; //milliseconds private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
public static final byte USB_ENDPOINT = (byte) 0x81; private static final byte USB_INTERFACE = (byte) 0x0;
public static final byte USB_INTERFACE = (byte) 0x0; private static final int USB_TRANSFER_BUFFER_SIZE = 262144;
public static final int TRANSFER_BUFFER_POOL_SIZE = 16; 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 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);
public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz"); public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz");
private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class); private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class);
@@ -115,17 +106,11 @@ public class AirspyTunerController extends TunerController
private Device mDevice; private Device mDevice;
private DeviceHandle mDeviceHandle; 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 AirspySampleAdapter mSampleAdapter = new AirspySampleAdapter();
private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
private HilbertTransform mHilbertTransform = new HilbertTransform();
private AirspyDeviceInformation mDeviceInfo; private AirspyDeviceInformation mDeviceInfo;
private List<AirspySampleRate> mSampleRates = new ArrayList<>(); private List<AirspySampleRate> mSampleRates = new ArrayList<>();
private int mSampleRate = 0; private int mSampleRate = 0;
private USBTransferProcessor mUSBTransferProcessor;
public AirspyTunerController(Device device) throws SourceException public AirspyTunerController(Device device) throws SourceException
{ {
@@ -140,7 +125,7 @@ public class AirspyTunerController extends TunerController
int result = LibUsb.open(mDevice, mDeviceHandle); int result = LibUsb.open(mDevice, mDeviceHandle);
if(result != 0) if(result != LibUsb.SUCCESS)
{ {
if(result == LibUsb.ERROR_ACCESS) 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); 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 * Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started.
* not currently running, starts it running in a new thread.
*/ */
public void addListener(Listener<ComplexBuffer> listener) public void addListener(Listener<ComplexBuffer> listener)
{ {
synchronized(mComplexBufferBroadcaster) if(mUSBTransferProcessor != null)
{ {
mComplexBufferBroadcaster.addListener(listener); mUSBTransferProcessor.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();
}
}
}
/**
* 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 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()
{
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 else
{ {
mLog.error("error submitting transfer [" + LibUsb.errorName(result) + "]"); mLog.error("Couldn't add IQ buffer listener to Airspy tuner - processor is null");
}
}
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 * Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners.
* array and broadcasts the array to all registered listeners
*/ */
public class BufferDispatcher implements Runnable public void removeListener(Listener<ComplexBuffer> listener)
{ {
@Override if(mUSBTransferProcessor != null)
public void run()
{ {
try mUSBTransferProcessor.removeListener(listener);
{
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));
} }
} else
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");
}
} }
} }
} }

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -14,16 +14,10 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package source.tuner.fcd; package source.tuner.fcd;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import javax.usb.UsbClaimException;
import javax.usb.UsbException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.usb4java.Device; import org.usb4java.Device;
@@ -31,7 +25,6 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle; import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb; import org.usb4java.LibUsb;
import org.usb4java.LibUsbException; import org.usb4java.LibUsbException;
import source.SourceException; import source.SourceException;
import source.tuner.TunerClass; import source.tuner.TunerClass;
import source.tuner.TunerController; import source.tuner.TunerController;
@@ -39,10 +32,15 @@ import source.tuner.TunerType;
import source.tuner.configuration.TunerConfiguration; import source.tuner.configuration.TunerConfiguration;
import source.tuner.fcd.proV1.FCD1TunerController.Block; import source.tuner.fcd.proV1.FCD1TunerController.Block;
import javax.usb.UsbClaimException;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
public abstract class FCDTunerController extends TunerController public abstract class FCDTunerController extends TunerController
{ {
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger(FCDTunerController.class);
LoggerFactory.getLogger( FCDTunerController.class );
public static final double USABLE_BANDWIDTH_PERCENT = 1.0; public static final double USABLE_BANDWIDTH_PERCENT = 1.0;
public static final int DC_SPIKE_AVOID_BUFFER = 5000; public static final int DC_SPIKE_AVOID_BUFFER = 5000;
@@ -66,26 +64,23 @@ public abstract class FCDTunerController extends TunerController
* @param minTunableFrequency * @param minTunableFrequency
* @param maxTunableFrequency * @param maxTunableFrequency
*/ */
public FCDTunerController( Device device, public FCDTunerController(Device device,
DeviceDescriptor descriptor, DeviceDescriptor descriptor,
int sampleRate, int sampleRate,
int minTunableFrequency, int minTunableFrequency,
int maxTunableFrequency ) int maxTunableFrequency)
{ {
super( minTunableFrequency, super(minTunableFrequency, maxTunableFrequency, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
maxTunableFrequency,
DC_SPIKE_AVOID_BUFFER,
USABLE_BANDWIDTH_PERCENT );
mDevice = device; mDevice = device;
mDeviceDescriptor = descriptor; mDeviceDescriptor = descriptor;
try try
{ {
mFrequencyController.setSampleRate( sampleRate ); mFrequencyController.setSampleRate(sampleRate);
} }
catch( SourceException se ) catch(SourceException se)
{ {
mLog.error("Error setting sample rate to [" + sampleRate + "]", se ); mLog.error("Error setting sample rate to [" + sampleRate + "]", se);
} }
} }
@@ -102,14 +97,13 @@ public abstract class FCDTunerController extends TunerController
{ {
mDeviceHandle = new DeviceHandle(); mDeviceHandle = new DeviceHandle();
int result = LibUsb.open( mDevice, mDeviceHandle ); int result = LibUsb.open(mDevice, mDeviceHandle);
if( result != LibUsb.SUCCESS ) if(result != LibUsb.SUCCESS)
{ {
mDeviceHandle = null; mDeviceHandle = null;
throw new SourceException( "libusb couldn't open funcube usb " throw new SourceException("libusb couldn't open funcube usb device [" + LibUsb.errorName(result) + "]");
+ "device [" + LibUsb.errorName( result ) + "]" );
} }
claimInterface(); claimInterface();
@@ -120,15 +114,15 @@ public abstract class FCDTunerController extends TunerController
*/ */
public void dispose() public void dispose()
{ {
if( mDeviceHandle != null ) if(mDeviceHandle != null)
{ {
try try
{ {
LibUsb.close( mDeviceHandle ); LibUsb.close(mDeviceHandle);
} }
catch( Exception e ) catch(Exception e)
{ {
mLog.error( "error while closing device handle", e ); mLog.error("error while closing device handle", e);
} }
mDeviceHandle = null; mDeviceHandle = null;
@@ -156,24 +150,24 @@ public abstract class FCDTunerController extends TunerController
/** /**
* Applies the settings in the tuner configuration * Applies the settings in the tuner configuration
*/ */
public abstract void apply( TunerConfiguration config ) throws SourceException; public abstract void apply(TunerConfiguration config) throws SourceException;
/** /**
* USB address (bus/port) * USB address (bus/port)
*/ */
public String getUSBAddress() public String getUSBAddress()
{ {
if( mDevice != null ) if(mDevice != null)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( "Bus:" ); sb.append("Bus:");
int bus = LibUsb.getBusNumber( mDevice ); int bus = LibUsb.getBusNumber(mDevice);
sb.append( bus ); sb.append(bus);
sb.append( " Port:" ); sb.append(" Port:");
int port = LibUsb.getPortNumber( mDevice ); int port = LibUsb.getPortNumber(mDevice);
sb.append( port ); sb.append(port);
return sb.toString(); return sb.toString();
} }
@@ -186,17 +180,13 @@ public abstract class FCDTunerController extends TunerController
*/ */
public String getUSBID() public String getUSBID()
{ {
if( mDeviceDescriptor != null ) if(mDeviceDescriptor != null)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( String.format( "%04X", sb.append(String.format("%04X", (int)(mDeviceDescriptor.idVendor() & 0xFFFF)));
(int)( mDeviceDescriptor.idVendor() & 0xFFFF ) ) ); sb.append(":");
sb.append(String.format("%04X", (int)(mDeviceDescriptor.idProduct() & 0xFFFF)));
sb.append( ":" );
sb.append( String.format( "%04X",
(int)( mDeviceDescriptor.idProduct() & 0xFFFF ) ) );
return sb.toString(); return sb.toString();
} }
@@ -209,11 +199,11 @@ public abstract class FCDTunerController extends TunerController
*/ */
public String getUSBSpeed() public String getUSBSpeed()
{ {
if( mDevice != null ) if(mDevice != null)
{ {
int speed = LibUsb.getDeviceSpeed( mDevice ); int speed = LibUsb.getDeviceSpeed(mDevice);
switch( speed ) switch(speed)
{ {
case 0: case 0:
return "1.1 LOW"; return "1.1 LOW";
@@ -233,25 +223,25 @@ public abstract class FCDTunerController extends TunerController
/** /**
* Set fcd interface mode * Set fcd interface mode
*/ */
public void setFCDMode( Mode mode ) throws UsbException, UsbClaimException public void setFCDMode(Mode mode) throws UsbException, UsbClaimException
{ {
ByteBuffer response = null; ByteBuffer response = null;
switch( mode ) switch(mode)
{ {
case APPLICATION: case APPLICATION:
response = send( FCDCommand.BL_QUERY ); response = send(FCDCommand.BL_QUERY);
break; break;
case BOOTLOADER: case BOOTLOADER:
response = send( FCDCommand.APP_RESET ); response = send(FCDCommand.APP_RESET);
break; break;
default: default:
break; break;
} }
if( response != null ) if(response != null)
{ {
mConfiguration.set( response ); mConfiguration.set(response);
} }
else else
{ {
@@ -262,16 +252,16 @@ public abstract class FCDTunerController extends TunerController
/** /**
* Sets the actual (uncorrected) device frequency * Sets the actual (uncorrected) device frequency
*/ */
public void setTunedFrequency( long frequency ) throws SourceException public void setTunedFrequency(long frequency) throws SourceException
{ {
try try
{ {
send( FCDCommand.APP_SET_FREQUENCY_HZ, frequency ); send(FCDCommand.APP_SET_FREQUENCY_HZ, frequency);
} }
catch( Exception e ) catch(Exception e)
{ {
throw new SourceException( "Couldn't set FCD Local " + throw new SourceException("Couldn't set FCD Local " +
"Oscillator Frequency [" + frequency + "]", e ); "Oscillator Frequency [" + frequency + "]", e);
} }
} }
@@ -282,16 +272,16 @@ public abstract class FCDTunerController extends TunerController
{ {
try try
{ {
ByteBuffer buffer = send( FCDCommand.APP_GET_FREQUENCY_HZ ); ByteBuffer buffer = send(FCDCommand.APP_GET_FREQUENCY_HZ);
buffer.order( ByteOrder.LITTLE_ENDIAN ); buffer.order(ByteOrder.LITTLE_ENDIAN);
return (int)( buffer.getInt( 2 ) & 0xFFFFFFFF ); return (int)(buffer.getInt(2) & 0xFFFFFFFF);
} }
catch( Exception e ) catch(Exception e)
{ {
throw new SourceException( "FCDTunerController - " throw new SourceException("FCDTunerController - "
+ "couldn't get LO frequency", e ); + "couldn't get LO frequency", e);
} }
} }
@@ -309,169 +299,149 @@ public abstract class FCDTunerController extends TunerController
*/ */
private void claimInterface() throws SourceException private void claimInterface() throws SourceException
{ {
if( mDeviceHandle != null ) if(mDeviceHandle != null)
{ {
int result = LibUsb.kernelDriverActive( mDeviceHandle, FCD_INTERFACE ); int result = LibUsb.kernelDriverActive(mDeviceHandle, FCD_INTERFACE);
if( result == 1 ) if(result == 1)
{ {
result = LibUsb.detachKernelDriver( mDeviceHandle, result = LibUsb.detachKernelDriver(mDeviceHandle, FCD_INTERFACE);
FCD_INTERFACE );
if( result != LibUsb.SUCCESS ) if(result != LibUsb.SUCCESS)
{ {
mLog.error( "failed attempt to detach kernel driver [" + mLog.error("failed attempt to detach kernel driver [" + LibUsb.errorName(result) + "]");
LibUsb.errorName( result ) + "]" );
} }
} }
result = LibUsb.claimInterface( mDeviceHandle, FCD_INTERFACE ); result = LibUsb.claimInterface(mDeviceHandle, FCD_INTERFACE);
if( result != LibUsb.SUCCESS ) if(result != LibUsb.SUCCESS)
{ {
throw new SourceException( "couldn't claim usb interface [" + throw new SourceException("couldn't claim usb interface [" + LibUsb.errorName(result) + "]");
LibUsb.errorName( result ) + "]" );
} }
} }
else else
{ {
throw new SourceException( "couldn't claim usb hid interface - no " throw new SourceException("couldn't claim usb hid interface - no device handle");
+ "device handle" );
} }
} }
/** /**
* Performs an interrupt write to the OUT endpoint. * Performs an interrupt write to the OUT endpoint.
* @param buffer - direct allocated buffer. Must be 64 bytes in length.
* *
* @param buffer - direct allocated buffer. Must be 64 bytes in length.
* @throws LibUsbException on error * @throws LibUsbException on error
*/ */
private void write( ByteBuffer buffer ) throws LibUsbException private void write(ByteBuffer buffer) throws LibUsbException
{ {
if( mDeviceHandle != null ) if(mDeviceHandle != null)
{ {
IntBuffer transferred = IntBuffer.allocate( 1 ); IntBuffer transferred = IntBuffer.allocate(1);
int result = LibUsb.interruptTransfer( mDeviceHandle, int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_OUT, buffer, transferred, 500l);
FCD_ENDPOINT_OUT,
buffer,
transferred,
500l );
if( result != LibUsb.SUCCESS ) if(result != LibUsb.SUCCESS)
{ {
throw new LibUsbException( "error writing byte buffer", throw new LibUsbException("error writing byte buffer", result);
result );
} }
// else if( transferred.get() != buffer.capacity() )
// {
// throw new LibUsbException( "transferred bytes [" +
// transferred.get( 0 ) + "] is not what was expected [" +
// buffer.capacity() + "]", result );
// }
} }
else else
{ {
throw new LibUsbException( "device handle is null", throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE);
LibUsb.ERROR_NO_DEVICE );
} }
} }
/** /**
* Performs an interrupt write to the OUT endpoint for the FCD command. * Performs an interrupt write to the OUT endpoint for the FCD command.
*
* @param command - no-argument command to write * @param command - no-argument command to write
* @throws LibUsbException - on error * @throws LibUsbException - on error
*/ */
private void write( FCDCommand command ) throws LibUsbException private void write(FCDCommand command) throws LibUsbException
{ {
ByteBuffer buffer = ByteBuffer.allocateDirect( 64 ); ByteBuffer buffer = ByteBuffer.allocateDirect(64);
buffer.put( 0, command.getCommand() ); buffer.put(0, command.getCommand());
buffer.put( 1, (byte)0x00 ); buffer.put(1, (byte)0x00);
write( buffer ); write(buffer);
} }
/** /**
* Convenience logger for debugging read/write operations * Convenience logger for debugging read/write operations
*/ */
@SuppressWarnings( "unused" ) @SuppressWarnings("unused")
private void log( String label, ByteBuffer buffer ) private void log(String label, ByteBuffer buffer)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( label ); sb.append(label);
sb.append( " " ); sb.append(" ");
sb.append( buffer.get( 0 ) ); sb.append(buffer.get(0));
sb.append( " | " ); sb.append(" | ");
for( int x = 0; x < 64; x++ ) for(int x = 0; x < 64; x++)
{ {
sb.append( String.format( "%02X", (int)( buffer.get( x ) & 0xFF ) ) ); sb.append(String.format("%02X", (int)(buffer.get(x) & 0xFF)));
sb.append( " " ); sb.append(" ");
} }
mLog.debug( sb.toString() ); mLog.debug(sb.toString());
} }
/** /**
* Performs an interrupt write to the OUT endpoint for the FCD command. * Performs an interrupt write to the OUT endpoint for the FCD command.
*
* @param command - command to write * @param command - command to write
* @param argument - value to write with the command * @param argument - value to write with the command
* @throws LibUsbException - on error * @throws LibUsbException - on error
*/ */
private void write( FCDCommand command, long argument ) throws LibUsbException private void write(FCDCommand command, long argument) throws LibUsbException
{ {
ByteBuffer buffer = ByteBuffer.allocateDirect( 64 ); ByteBuffer buffer = ByteBuffer.allocateDirect(64);
/* The FCD expects little-endian formatted values */ /* The FCD expects little-endian formatted values */
buffer.order( ByteOrder.LITTLE_ENDIAN ); buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.put( 0, command.getCommand() ); buffer.put(0, command.getCommand());
buffer.putLong( 1, argument ); buffer.putLong(1, argument);
write( buffer ); write(buffer);
} }
/** /**
* Performs an interrupt read against the endpoint * Performs an interrupt read against the endpoint
*
* @return buffer read from FCD * @return buffer read from FCD
* @throws LibUsbException on error * @throws LibUsbException on error
*/ */
private ByteBuffer read() throws LibUsbException private ByteBuffer read() throws LibUsbException
{ {
if( mDeviceHandle != null ) if(mDeviceHandle != null)
{ {
ByteBuffer buffer = ByteBuffer.allocateDirect( 64 ); ByteBuffer buffer = ByteBuffer.allocateDirect(64);
IntBuffer transferred = IntBuffer.allocate( 1 ); IntBuffer transferred = IntBuffer.allocate(1);
int result = LibUsb.interruptTransfer( mDeviceHandle, int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_IN, buffer, transferred, 500l);
FCD_ENDPOINT_IN,
buffer,
transferred,
500l );
if( result != LibUsb.SUCCESS ) if(result != LibUsb.SUCCESS)
{ {
throw new LibUsbException( "error reading byte buffer", throw new LibUsbException("error reading byte buffer", result);
result );
} }
else if( transferred.get( 0 ) != buffer.capacity() ) else if(transferred.get(0) != buffer.capacity())
{ {
throw new LibUsbException( "received bytes [" + throw new LibUsbException("received bytes [" + transferred.get(0) +
transferred.get( 0 ) + "] didn't match expected " "] didn't match expected length [" + buffer.capacity() + "]", result);
+ "length [" + buffer.capacity() + "]", result );
} }
return buffer; return buffer;
} }
throw new LibUsbException( "device handle is null", throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE);
LibUsb.ERROR_NO_DEVICE );
} }
/** /**
@@ -482,9 +452,9 @@ public abstract class FCDTunerController extends TunerController
* @param argument - command argument to send * @param argument - command argument to send
* @throws LibUsbException - on error * @throws LibUsbException - on error
*/ */
protected void send( FCDCommand command, long argument ) throws LibUsbException protected void send(FCDCommand command, long argument) throws LibUsbException
{ {
write( command, argument ); write(command, argument);
read(); read();
} }
@@ -495,9 +465,9 @@ public abstract class FCDTunerController extends TunerController
* @param command - command to send * @param command - command to send
* @throws LibUsbException - on error * @throws LibUsbException - on error
*/ */
protected ByteBuffer send( FCDCommand command ) throws LibUsbException protected ByteBuffer send(FCDCommand command) throws LibUsbException
{ {
write( command ); write(command);
return read(); return read();
} }
@@ -524,19 +494,19 @@ public abstract class FCDTunerController extends TunerController
/** /**
* Extracts the configuration string from the buffer * Extracts the configuration string from the buffer
*/ */
public void set( ByteBuffer buffer ) public void set(ByteBuffer buffer)
{ {
if( buffer.capacity() == 64 ) if(buffer.capacity() == 64)
{ {
byte[] data = new byte[ 64 ]; byte[] data = new byte[64];
for( int x = 0; x < 64; x++ ) for(int x = 0; x < 64; x++)
{ {
data[ x ] = buffer.get( x ); data[x] = buffer.get(x);
} }
mConfig = new String( data ); mConfig = new String(data);
mMode = Mode.getMode( mConfig ); mMode = Mode.getMode(mConfig);
} }
else else
{ {
@@ -554,10 +524,10 @@ public abstract class FCDTunerController extends TunerController
{ {
FCDModel retVal = FCDModel.FUNCUBE_UNKNOWN; FCDModel retVal = FCDModel.FUNCUBE_UNKNOWN;
switch( mMode ) switch(mMode)
{ {
case APPLICATION: case APPLICATION:
retVal = FCDModel.getFCD( mConfig.substring( 15, 22 ) ); retVal = FCDModel.getFCD(mConfig.substring(15, 22));
break; break;
case BOOTLOADER: case BOOTLOADER:
case UNKNOWN: case UNKNOWN:
@@ -572,10 +542,10 @@ public abstract class FCDTunerController extends TunerController
{ {
Block retVal = Block.UNKNOWN; Block retVal = Block.UNKNOWN;
switch( mMode ) switch(mMode)
{ {
case APPLICATION: case APPLICATION:
retVal = Block.getBlock( mConfig.substring( 23, 33 ).trim() ); retVal = Block.getBlock(mConfig.substring(23, 33).trim());
break; break;
case BOOTLOADER: case BOOTLOADER:
case UNKNOWN: case UNKNOWN:
@@ -590,10 +560,10 @@ public abstract class FCDTunerController extends TunerController
{ {
String retVal = null; String retVal = null;
switch( mMode ) switch(mMode)
{ {
case APPLICATION: case APPLICATION:
retVal = mConfig.substring( 9, 14 ); retVal = mConfig.substring(9, 14);
break; break;
case BOOTLOADER: case BOOTLOADER:
case UNKNOWN: case UNKNOWN:
@@ -617,25 +587,25 @@ public abstract class FCDTunerController extends TunerController
ERROR, ERROR,
UNKNOWN; UNKNOWN;
public static Mode getMode( String config ) public static Mode getMode(String config)
{ {
Mode retVal = UNKNOWN; Mode retVal = UNKNOWN;
if( config == null ) if(config == null)
{ {
retVal = ERROR; retVal = ERROR;
} }
else else
{ {
if( config.length() >= 8 ) if(config.length() >= 8)
{ {
String mode = config.substring( 2, 8 ).trim(); String mode = config.substring(2, 8).trim();
if( mode.equalsIgnoreCase( "FCDAPP" ) ) if(mode.equalsIgnoreCase("FCDAPP"))
{ {
retVal = APPLICATION; retVal = APPLICATION;
} }
else if( mode.equalsIgnoreCase( "FCDBL" ) ) else if(mode.equalsIgnoreCase("FCDBL"))
{ {
retVal = BOOTLOADER; retVal = BOOTLOADER;
} }

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -14,17 +14,14 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package source.tuner.fcd.proplusV2; package source.tuner.fcd.proplusV2;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.usb4java.Device; import org.usb4java.Device;
import org.usb4java.DeviceDescriptor; import org.usb4java.DeviceDescriptor;
import source.SourceException; import source.SourceException;
import source.tuner.MixerTunerType; import source.tuner.MixerTunerType;
import source.tuner.TunerClass; import source.tuner.TunerClass;
@@ -33,34 +30,37 @@ import source.tuner.configuration.TunerConfiguration;
import source.tuner.fcd.FCDCommand; import source.tuner.fcd.FCDCommand;
import source.tuner.fcd.FCDTunerController; import source.tuner.fcd.FCDTunerController;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class FCD2TunerController extends FCDTunerController 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 MINIMUM_TUNABLE_FREQUENCY = 150000;
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000; public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
public static final int SAMPLE_RATE = 192000; public static final int SAMPLE_RATE = 192000;
public FCD2TunerController( Device device, DeviceDescriptor descriptor ) public FCD2TunerController(Device device, DeviceDescriptor descriptor)
{ {
super( device, descriptor, super(device, descriptor,
(int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(), (int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(),
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY ); MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
} }
public void init() throws SourceException public void init() throws SourceException
{ {
super.init(); super.init();
mFrequencyController.setSampleRate( SAMPLE_RATE ); mFrequencyController.setSampleRate(SAMPLE_RATE);
try try
{ {
setFCDMode( Mode.APPLICATION ); setFCDMode(Mode.APPLICATION);
} }
catch( Exception e ) catch(Exception e)
{ {
throw new SourceException( "error setting Mode to APPLICATION", e ); throw new SourceException("error setting Mode to APPLICATION", e);
} }
} }
@@ -81,47 +81,47 @@ public class FCD2TunerController extends FCDTunerController
return TunerType.FUNCUBE_DONGLE_PRO_PLUS; return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
} }
public void setLNAGain( boolean enabled ) throws SourceException public void setLNAGain(boolean enabled) throws SourceException
{ {
try try
{ {
send( FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0 ); send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0);
} }
catch ( Exception e ) catch(Exception e)
{ {
throw new SourceException( "error while setting LNA Gain", e ); throw new SourceException("error while setting LNA Gain", e);
} }
} }
public void setMixerGain( boolean enabled ) throws SourceException public void setMixerGain(boolean enabled) throws SourceException
{ {
try try
{ {
send( FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0 ); send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
} }
catch ( Exception e ) catch(Exception e)
{ {
throw new SourceException( "error while setting Mixer Gain", e ); throw new SourceException("error while setting Mixer Gain", e);
} }
} }
@Override @Override
public void apply( TunerConfiguration config ) throws SourceException public void apply(TunerConfiguration config) throws SourceException
{ {
if( config instanceof FCD2TunerConfiguration ) if(config instanceof FCD2TunerConfiguration)
{ {
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config; FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
setFrequencyCorrection( fcd2.getFrequencyCorrection() ); setFrequencyCorrection(fcd2.getFrequencyCorrection());
setFrequency( fcd2.getFrequency() ); setFrequency(fcd2.getFrequency());
setLNAGain( fcd2.getGainLNA() ); setLNAGain(fcd2.getGainLNA());
setMixerGain( fcd2.getGainMixer() ); setMixerGain(fcd2.getGainMixer());
try try
{ {
setFrequency( fcd2.getFrequency() ); setFrequency(fcd2.getFrequency());
} }
catch( SourceException se ) catch(SourceException se)
{ {
//Do nothing, we couldn't set the frequency //Do nothing, we couldn't set the frequency
} }
@@ -134,29 +134,29 @@ public class FCD2TunerController extends FCDTunerController
try try
{ {
ByteBuffer buffer = send( FCDCommand.APP_GET_DC_CORRECTION ); ByteBuffer buffer = send(FCDCommand.APP_GET_DC_CORRECTION);
buffer.order( ByteOrder.LITTLE_ENDIAN ); buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer.getInt( 2 ); return buffer.getInt(2);
} }
catch ( Exception e ) catch(Exception e)
{ {
mLog.error( "error getting dc correction value", e ); mLog.error("error getting dc correction value", e);
} }
return dcCorrection; return dcCorrection;
} }
public void setDCCorrection( int value ) public void setDCCorrection(int value)
{ {
try try
{ {
send( FCDCommand.APP_SET_DC_CORRECTION, value ); send(FCDCommand.APP_SET_DC_CORRECTION, value);
} }
catch ( Exception e ) catch(Exception e)
{ {
mLog.error( "error setting dc correction to [" + value + "]", e ); mLog.error("error setting dc correction to [" + value + "]", e);
} }
} }
@@ -166,41 +166,41 @@ public class FCD2TunerController extends FCDTunerController
try try
{ {
ByteBuffer buffer = send( FCDCommand.APP_GET_IQ_CORRECTION ); ByteBuffer buffer = send(FCDCommand.APP_GET_IQ_CORRECTION);
buffer.order( ByteOrder.LITTLE_ENDIAN ); buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer.getInt( 2 ); return buffer.getInt(2);
} }
catch ( Exception e ) catch(Exception e)
{ {
mLog.error( "error reading IQ correction value", e ); mLog.error("error reading IQ correction value", e);
} }
return iqCorrection; return iqCorrection;
} }
public void setIQCorrection( int value ) public void setIQCorrection(int value)
{ {
try try
{ {
send( FCDCommand.APP_SET_IQ_CORRECTION, value ); send(FCDCommand.APP_SET_IQ_CORRECTION, value);
} }
catch ( Exception e ) catch(Exception e)
{ {
mLog.error( "error setting IQ correction to [" + value + "]", e ); mLog.error("error setting IQ correction to [" + value + "]", e);
} }
} }
public enum Block public enum Block
{ {
CELLULAR_BAND_BLOCKED( "Blocked" ), CELLULAR_BAND_BLOCKED("Blocked"),
NO_BAND_BLOCK( "Unblocked" ), NO_BAND_BLOCK("Unblocked"),
UNKNOWN( "Unknown" ); UNKNOWN("Unknown");
private String mLabel; private String mLabel;
private Block( String label ) private Block(String label)
{ {
mLabel = label; mLabel = label;
} }
@@ -210,15 +210,15 @@ public class FCD2TunerController extends FCDTunerController
return mLabel; return mLabel;
} }
public static Block getBlock( String block ) public static Block getBlock(String block)
{ {
Block retVal = UNKNOWN; Block retVal = UNKNOWN;
if( block.equalsIgnoreCase( "No blk" ) ) if(block.equalsIgnoreCase("No blk"))
{ {
retVal = NO_BAND_BLOCK; retVal = NO_BAND_BLOCK;
} }
else if( block.equalsIgnoreCase( "Cell blk" ) ) else if(block.equalsIgnoreCase("Cell blk"))
{ {
retVal = CELLULAR_BAND_BLOCKED; retVal = CELLULAR_BAND_BLOCKED;
} }

View File

@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * SDR Trunk
* Copyright (C) 2014 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* Ported from libhackrf at: * Ported from libhackrf at:
* https://github.com/mossmann/hackrf/tree/master/host/libhackrf * https://github.com/mossmann/hackrf/tree/master/host/libhackrf
@@ -61,8 +61,6 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle; import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb; import org.usb4java.LibUsb;
import org.usb4java.LibUsbException; import org.usb4java.LibUsbException;
import org.usb4java.Transfer;
import org.usb4java.TransferCallback;
import sample.Broadcaster; import sample.Broadcaster;
import sample.Listener; import sample.Listener;
import sample.adapter.ByteSampleAdapter; import sample.adapter.ByteSampleAdapter;
@@ -70,38 +68,25 @@ import sample.complex.ComplexBuffer;
import source.SourceException; import source.SourceException;
import source.tuner.TunerController; import source.tuner.TunerController;
import source.tuner.configuration.TunerConfiguration; import source.tuner.configuration.TunerConfiguration;
import util.ThreadPool; import source.tuner.usb.USBTransferProcessor;
import javax.usb.UsbException; import javax.usb.UsbException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue; 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 public class HackRFTunerController extends TunerController
{ {
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger(HackRFTunerController.class);
LoggerFactory.getLogger(HackRFTunerController.class);
public final static long USB_TIMEOUT_US = 1000000l; //uSeconds public final static long USB_TIMEOUT_US = 1000000l; //uSeconds
public static final byte USB_ENDPOINT = (byte) 0x81; public static final byte USB_ENDPOINT = (byte)0x81;
public static final byte USB_INTERFACE = (byte) 0x0; public static final byte USB_INTERFACE = (byte)0x0;
public static final int USB_TRANSFER_BUFFER_SIZE = 262144;
public static final byte REQUEST_TYPE_IN = public static final byte REQUEST_TYPE_IN = (byte)(LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
(byte) (LibUsb.ENDPOINT_IN | public static final byte REQUEST_TYPE_OUT = (byte)(LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
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 MIN_FREQUENCY = 10000000l;
public static final long MAX_FREQUENCY = 6000000000l; 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 double USABLE_BANDWIDTH_PERCENT = 0.95;
public static final int DC_SPIKE_AVOID_BUFFER = 5000; 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 ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
private BufferProcessor mBufferProcessor = new BufferProcessor(); private USBTransferProcessor mUSBTransferProcessor;
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ; private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
public int mBufferSize = 262144;
private boolean mAmplifierEnabled = false; private boolean mAmplifierEnabled = false;
private Device mDevice; private Device mDevice;
@@ -167,6 +144,20 @@ public class HackRFTunerController extends TunerController
throw new SourceException("HackRF Tuner Controller - couldn't " throw new SourceException("HackRF Tuner Controller - couldn't "
+ "claim USB interface or get endpoint or pipe", e); + "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 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); return BoardID.lookup(id);
} }
@@ -317,8 +308,8 @@ public class HackRFTunerController extends TunerController
ByteBuffer buffer = ByteBuffer.allocateDirect(8); ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.order(ByteOrder.LITTLE_ENDIAN);
int mhz = (int) (frequency / 1E6); int mhz = (int)(frequency / 1E6);
int hz = (int) (frequency - (mhz * 1E6)); int hz = (int)(frequency - (mhz * 1E6));
buffer.putInt(mhz); buffer.putInt(mhz);
buffer.putInt(hz); buffer.putInt(hz);
@@ -350,7 +341,7 @@ public class HackRFTunerController extends TunerController
if(config instanceof HackRFTunerConfiguration) if(config instanceof HackRFTunerConfiguration)
{ {
HackRFTunerConfiguration hackRFConfig = HackRFTunerConfiguration hackRFConfig =
(HackRFTunerConfiguration) config; (HackRFTunerConfiguration)config;
try try
{ {
@@ -395,8 +386,8 @@ public class HackRFTunerController extends TunerController
int transferred = LibUsb.controlTransfer(mDeviceHandle, int transferred = LibUsb.controlTransfer(mDeviceHandle,
REQUEST_TYPE_IN, REQUEST_TYPE_IN,
request.getRequestNumber(), request.getRequestNumber(),
(short) value, (short)value,
(short) index, (short)index,
buffer, buffer,
USB_TIMEOUT_US); USB_TIMEOUT_US);
@@ -450,11 +441,11 @@ public class HackRFTunerController extends TunerController
if(signed) if(signed)
{ {
return (int) (buffer.get()); return (int)(buffer.get());
} }
else 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, int transferred = LibUsb.controlTransfer(mDeviceHandle,
REQUEST_TYPE_OUT, REQUEST_TYPE_OUT,
request.getRequestNumber(), request.getRequestNumber(),
(short) value, (short)value,
(short) index, (short)index,
buffer, buffer,
USB_TIMEOUT_US); USB_TIMEOUT_US);
@@ -567,7 +558,7 @@ public class HackRFTunerController extends TunerController
private Request(int number) private Request(int number)
{ {
mRequestNumber = (byte) number; mRequestNumber = (byte)number;
} }
public byte getRequestNumber() public byte getRequestNumber()
@@ -685,7 +676,7 @@ public class HackRFTunerController extends TunerController
private BoardID(int number, String label) private BoardID(int number, String label)
{ {
mIDNumber = (byte) number; mIDNumber = (byte)number;
mLabel = label; mLabel = label;
} }
@@ -732,7 +723,7 @@ public class HackRFTunerController extends TunerController
private Mode(int number, String label) private Mode(int number, String label)
{ {
mNumber = (byte) number; mNumber = (byte)number;
mLabel = label; mLabel = label;
} }
@@ -890,21 +881,7 @@ public class HackRFTunerController extends TunerController
*/ */
public void addListener(Listener<ComplexBuffer> listener) public void addListener(Listener<ComplexBuffer> listener)
{ {
synchronized(mComplexBufferBroadcaster) mUSBTransferProcessor.addListener(listener);
{
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();
}
}
} }
/** /**
@@ -913,241 +890,7 @@ public class HackRFTunerController extends TunerController
*/ */
public void removeListener(Listener<ComplexBuffer> listener) public void removeListener(Listener<ComplexBuffer> listener)
{ {
synchronized(mComplexBufferBroadcaster) mUSBTransferProcessor.removeListener(listener);
{
mComplexBufferBroadcaster.removeListener(listener);
if(!mComplexBufferBroadcaster.hasListeners())
{
mBufferProcessor.stop();
}
}
} }
/**
* 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 * SDR Trunk
* Copyright (C) 2014 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* Java version based on librtlsdr * Java version based on librtlsdr
* Copyright (C) 2012-2013 by Steve Markgraf <steve@steve-m.de> * Copyright (C) 2012-2013 by Steve Markgraf <steve@steve-m.de>
@@ -21,7 +21,6 @@
******************************************************************************/ ******************************************************************************/
package source.tuner.rtl; package source.tuner.rtl;
import buffer.FloatAveragingBuffer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.usb4java.Device; import org.usb4java.Device;
@@ -29,54 +28,38 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle; import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb; import org.usb4java.LibUsb;
import org.usb4java.LibUsbException; import org.usb4java.LibUsbException;
import org.usb4java.Transfer;
import org.usb4java.TransferCallback;
import sample.Broadcaster; import sample.Broadcaster;
import sample.Listener; import sample.Listener;
import sample.adapter.ByteSampleAdapter; import sample.adapter.ByteSampleAdapter;
import sample.adapter.ISampleAdapter;
import sample.complex.ComplexBuffer; import sample.complex.ComplexBuffer;
import source.SourceException; import source.SourceException;
import source.tuner.TunerController; import source.tuner.TunerController;
import source.tuner.TunerManager;
import source.tuner.TunerType; import source.tuner.TunerType;
import util.ThreadPool; import source.tuner.usb.USBTransferProcessor;
import javax.usb.UsbDisconnectedException; import javax.usb.UsbDisconnectedException;
import javax.usb.UsbException; import javax.usb.UsbException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; 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 public abstract class RTL2832TunerController extends TunerController
{ {
private final static Logger mLog = LoggerFactory.getLogger(RTL2832TunerController.class); 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 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_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_IN = public final static byte CONTROL_ENDPOINT_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR);
(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 long TIMEOUT_US = 1000000l; //uSeconds
public final static byte REQUEST_ZERO = (byte) 0; 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 EEPROM_ADDRESS = (byte) 0xA0;
public final static byte[] sFIR_COEFFICIENTS = 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 (byte) 0x71, (byte) 0x74, (byte) 0x19, (byte) 0x41, (byte) 0xA5
}; };
public static final SampleRate DEFAULT_SAMPLE_RATE = public static final SampleRate DEFAULT_SAMPLE_RATE = SampleRate.RATE_2_400MHZ;
SampleRate.RATE_0_960MHZ;
protected Device mDevice; protected Device mDevice;
protected DeviceDescriptor mDeviceDescriptor; protected DeviceDescriptor mDeviceDescriptor;
@@ -96,24 +78,13 @@ public abstract class RTL2832TunerController extends TunerController
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE; private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
private BufferProcessor mBufferProcessor;
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter(); private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>(); 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 protected int mOscillatorFrequency = 28800000; //28.8 MHz
public int mBufferSize = 131072;
private USBTransferProcessor mUSBTransferProcessor;
protected Descriptor mDescriptor; protected Descriptor mDescriptor;
@@ -190,6 +161,11 @@ public abstract class RTL2832TunerController extends TunerController
+ "descriptor byte array " + + "descriptor byte array " +
(eeprom == null ? "[null]" : Arrays.toString(eeprom)), e); (eeprom == null ? "[null]" : Arrays.toString(eeprom)), e);
} }
String deviceName = getTunerType().getLabel() + " " + getUniqueID();
mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
USB_TRANSFER_BUFFER_SIZE);
} }
/** /**
@@ -476,14 +452,13 @@ public abstract class RTL2832TunerController extends TunerController
{ {
try try
{ {
if(mBufferProcessor.isRunning()) if(mUSBTransferProcessor != null)
{ {
mBufferProcessor.stop(); mUSBTransferProcessor.removeAllListeners();
TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(mUSBTransferProcessor);
} }
LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE); LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE);
LibUsb.exit(null);
} }
catch(Exception e) catch(Exception e)
{ {
@@ -1014,11 +989,6 @@ public abstract class RTL2832TunerController extends TunerController
mSampleRate = sampleRate; mSampleRate = sampleRate;
mFrequencyController.setSampleRate(sampleRate.getRate()); mFrequencyController.setSampleRate(sampleRate.getRate());
if(mSampleRateMonitor != null)
{
mSampleRateMonitor.setSampleRate(mSampleRate.getRate());
}
} }
public void setSampleRateFrequencyCorrection(int ppm) throws SourceException 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 public enum TunerTypeCheck
{ {
E4K(0xC8, 0x02, 0x40), 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 * 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();
}
}
}