diff --git a/src/sample/OverflowableTransferQueue.java b/src/sample/OverflowableTransferQueue.java
new file mode 100644
index 00000000..e872fb5e
--- /dev/null
+++ b/src/sample/OverflowableTransferQueue.java
@@ -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
+ *
+ ******************************************************************************/
+package sample;
+
+import java.util.Collection;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class OverflowableTransferQueue
+{
+ public enum State {NORMAL, OVERFLOW};
+ private Listener mStateListener;
+
+ private LinkedTransferQueue mQueue = new LinkedTransferQueue();
+ 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 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;
+ }
+ }
+}
diff --git a/src/source/tuner/LibUSBTransferProcessor.java b/src/source/tuner/LibUSBTransferProcessor.java
new file mode 100644
index 00000000..d1536570
--- /dev/null
+++ b/src/source/tuner/LibUSBTransferProcessor.java
@@ -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
+ *
+ ******************************************************************************/
+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 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();
+ }
+ }
+}
diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java
index 4a00107e..c50fd267 100644
--- a/src/source/tuner/TunerChannelSource.java
+++ b/src/source/tuner/TunerChannelSource.java
@@ -1,19 +1,20 @@
/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014-2016 Dennis Sheirer
+ * sdrtrunk
+ * Copyright (C) 2014-2017 Dennis Sheirer
*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
******************************************************************************/
package source.tuner;
@@ -25,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Buffer;
import sample.Listener;
+import sample.OverflowableTransferQueue;
import sample.complex.Complex;
import sample.complex.ComplexBuffer;
import source.ComplexSource;
@@ -35,24 +37,27 @@ import source.tuner.frequency.IFrequencyChangeProcessor;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-public class TunerChannelSource extends ComplexSource
- implements IFrequencyChangeProcessor, Listener
+public class TunerChannelSource extends ComplexSource implements IFrequencyChangeProcessor, Listener
{
- private final static Logger mLog =
- LoggerFactory.getLogger(TunerChannelSource.class);
+ private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
+
+ //Maximum number of filled buffers for the blocking queue
+ private static final int BUFFER_MAX_CAPACITY = 300;
+
+ //Threshold for resetting buffer overflow condition
+ private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 200;
private static int CHANNEL_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000;
- private LinkedTransferQueue mBuffer =
- new LinkedTransferQueue();
+ private OverflowableTransferQueue mBuffer;
+
private Tuner mTuner;
private TunerChannel mTunerChannel;
private Oscillator mMixer;
@@ -69,6 +74,7 @@ public class TunerChannelSource extends ComplexSource
private AtomicBoolean mRunning = new AtomicBoolean();
private boolean mExpended = false;
+ private int mCount;
/**
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
@@ -87,26 +93,37 @@ public class TunerChannelSource extends ComplexSource
* accept the decimation processing task
* @throws SourceException if the tuner has an issue providing IQ samples
*/
- public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel)
- throws RejectedExecutionException, SourceException
+ public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel) throws RejectedExecutionException, SourceException
{
mTuner = tuner;
- mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
-
mTunerChannel = tunerChannel;
-
+ mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
mTunerFrequency = mTuner.getTunerController().getFrequency();
+ mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
+ mBuffer.setStateListener(new Listener()
+ {
+ @Override
+ public void receive(OverflowableTransferQueue.State state)
+ {
+ if(state == OverflowableTransferQueue.State.NORMAL)
+ {
+ mLog.debug("Channel [" + mTunerFrequency + "] buffer overflow - temporary pause until processing catches up");
+ }
+ else
+ {
+ mLog.debug("Channel [" + mTunerFrequency + "] buffer overflow cleared - resuming normal processing");
+ }
+ }
+ });
+
/* Setup the frequency translator to the current source frequency */
long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency();
- mMixer = new Oscillator(frequencyOffset,
- mTuner.getTunerController().getSampleRate());
+ mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
/* Fire a sample rate change event to setup the decimation chain */
- frequencyChanged(new FrequencyChangeEvent(
- Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
- mTuner.getTunerController().getSampleRate()));
+ frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate()));
}
@@ -114,23 +131,22 @@ public class TunerChannelSource extends ComplexSource
{
if(mExpended)
{
- throw new IllegalStateException("Attempt to re-start an expended "
- + "tuner channel source. TunerChannelSource objects can only "
- + "be used once. ");
+ throw new IllegalStateException("Attempt to re-start an expended tuner channel source. TunerChannelSource" +
+ " objects can only be used once. ");
}
if(mRunning.compareAndSet(false, true))
{
- /* Schedule the decimation task to run every 10 ms (100 iterations/second) */
- mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor,
- 0, 10, TimeUnit.MILLISECONDS);
+ //Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
+ //to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
+ mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
/* Finally, register to receive samples from the tuner */
mTuner.addListener((Listener) this);
}
else
{
- mLog.warn("Attempt to start() and already running tuner channel source was ignored");
+ mLog.warn("Attempt to start() an already running tuner channel source was ignored");
}
}
@@ -171,14 +187,6 @@ public class TunerChannelSource extends ComplexSource
{
/* Tell the tuner to release/unregister our resources */
mTuner.getTunerController().removeListener(this);
-// mTuner = null;
-// mTunerChannel = null;
-// mBuffer = null;
-// mFrequencyChangeProcessor = null;
-// mListener = null;
-// mMixer = null;
-// mDecimationFilter.dispose();
-// mDecimationFilter = null;
}
}
@@ -197,7 +205,7 @@ public class TunerChannelSource extends ComplexSource
{
if(mRunning.get())
{
- mBuffer.add(buffer);
+ mBuffer.offer(buffer);
}
}
@@ -248,10 +256,8 @@ public class TunerChannelSource extends ComplexSource
if(mFrequencyChangeProcessor != null)
{
- mFrequencyChangeProcessor.frequencyChanged(
- new FrequencyChangeEvent(
- Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
- mChannelFrequencyCorrection));
+ mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
+ Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
}
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
@@ -262,13 +268,8 @@ public class TunerChannelSource extends ComplexSource
mMixer.setSampleRate(sampleRate);
/* Get new decimation filter */
- mDecimationFilter = FilterFactory
- .getDecimationFilter(sampleRate,
- CHANNEL_RATE,
- 1, //Order
- CHANNEL_PASS_FREQUENCY,
- 60, //dB attenuation
- WindowType.HAMMING);
+ mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
+ CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
/* re-add the original output listener */
mDecimationFilter.setListener(mListener);
@@ -328,48 +329,46 @@ public class TunerChannelSource extends ComplexSource
{
if(mProcessing)
{
- {
- mBuffer.drainTo(mSampleBuffers, 8);
+ mBuffer.drainTo(mSampleBuffers, 20);
- for(Buffer buffer : mSampleBuffers)
- {
+ for(Buffer buffer : mSampleBuffers)
+ {
/* Check to see if we've been shutdown */
- if(!mProcessing)
- {
- mBuffer.clear();
- return;
- }
- else
- {
- float[] samples = buffer.getSamples();
+ if(!mProcessing)
+ {
+ mBuffer.clear();
+ return;
+ }
+ else
+ {
+ float[] samples = buffer.getSamples();
/* We make a copy of the buffer so that we don't affect
* anyone else that is using the same buffer, like other
* channels or the spectral display */
- float[] translated = new float[samples.length];
-
+ float[] translated = new float[samples.length];
+
/* Perform frequency translation */
- for(int x = 0; x < samples.length; x += 2)
- {
- mMixer.rotate();
+ for(int x = 0; x < samples.length; x += 2)
+ {
+ mMixer.rotate();
- translated[x] = Complex.multiplyInphase(
- samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
+ translated[x] = Complex.multiplyInphase(
+ samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
- translated[x + 1] = Complex.multiplyQuadrature(
- samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
- }
+ translated[x + 1] = Complex.multiplyQuadrature(
+ samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
+ }
- if(mProcessing)
- {
- final ComplexPrimeCICDecimate filter = mDecimationFilter;
- filter.receive(new ComplexBuffer(translated));
- }
+ if(mProcessing)
+ {
+ final ComplexPrimeCICDecimate filter = mDecimationFilter;
+ filter.receive(new ComplexBuffer(translated));
}
}
-
- mSampleBuffers.clear();
}
+
+ mSampleBuffers.clear();
}
}
catch(Exception e)
diff --git a/src/source/tuner/TunerManager.java b/src/source/tuner/TunerManager.java
index 0c82b839..b5181c26 100644
--- a/src/source/tuner/TunerManager.java
+++ b/src/source/tuner/TunerManager.java
@@ -42,12 +42,24 @@ import java.util.Collection;
public class TunerManager
{
- private final static Logger mLog =
- LoggerFactory.getLogger(TunerManager.class);
+ private final static Logger mLog = LoggerFactory.getLogger(TunerManager.class);
private MixerManager mMixerManager;
private TunerModel mTunerModel;
+
+ /**
+ * Application-wide LibUSB timeout processor for transfer buffers. All classes that need to use USB transfer
+ * buffers can register with this processor and the processor will auto-start and auto-stop while USB transfer
+ * processors are registered.
+ */
+ public static final LibUSBTransferProcessor LIBUSB_TRANSFER_PROCESSOR;
+
+ static
+ {
+ LIBUSB_TRANSFER_PROCESSOR = new LibUSBTransferProcessor();
+ }
+
public TunerManager(MixerManager mixerManager, TunerModel tunerModel)
{
mMixerManager = mixerManager;
diff --git a/src/source/tuner/airspy/AirspySampleAdapter.java b/src/source/tuner/airspy/AirspySampleAdapter.java
index abf42a44..deab9119 100644
--- a/src/source/tuner/airspy/AirspySampleAdapter.java
+++ b/src/source/tuner/airspy/AirspySampleAdapter.java
@@ -1,144 +1,155 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2017 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
package source.tuner.airspy;
-/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2015 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
- ******************************************************************************/
-
+import dsp.filter.dc.DCRemovalFilter_RB;
+import dsp.filter.hilbert.HilbertTransform;
import sample.adapter.ISampleAdapter;
public class AirspySampleAdapter implements ISampleAdapter
{
- private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
-
- private boolean mSamplePacking = false;
+ private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
- /**
- * Adapter to translate byte buffers received from the airspy tuner into
- * float buffers for processing.
- */
- public AirspySampleAdapter()
- {
- }
+ private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
+ private HilbertTransform mHilbertTransform = new HilbertTransform();
+ private boolean mSamplePacking = false;
- /**
- * Sample packing places two 12-bit samples into 3 bytes when enabled or
- * places two 12-bit samples into 4 bytes when disabled.
- *
- * @param enabled
- */
- public void setSamplePacking( boolean enabled )
- {
- mSamplePacking = enabled;
- }
-
- @Override
- public float[] convert( byte[] samples )
- {
- if( mSamplePacking )
- {
- return convertPacked( samples );
- }
- else
- {
- return convertUnpacked( samples );
- }
- }
- /**
- * Converts the byte array containing unsigned 12-bit short values into
- * signed float values in the range -1 to 1;
- *
- * @param data - byte array of unsigned 16-bit values
- * @return converted float values
- */
- private float[] convertUnpacked( byte[] data )
- {
- float[] samples = new float[ data.length / 2 ];
+ /**
+ * Adapter to translate byte buffers received from the airspy tuner into
+ * float buffers for processing.
+ */
+ public AirspySampleAdapter()
+ {
+ }
- int pointer = 0;
-
- for( int x = 0; x < data.length; x += 2 )
- {
- samples[ pointer++ ] = scale( ( data[ x ] & 0xFF ) |
- ( data[ x + 1 ] << 8 ) );
- }
-
- return samples;
- }
-
- /**
- * Converts every 3 bytes containing a pair of 12-bit unsigned values into
- * a pair of float values in the range -1 to 1;
- *
- * @param data - byte array of unsigned 12-bit values
- * @return converted float values
- */
- private float[] convertPacked( byte[] data1 )
- {
- byte[] data = new byte[ data1.length ];
+ /**
+ * Sample packing places two 12-bit samples into 3 bytes when enabled or
+ * places two 12-bit samples into 4 bytes when disabled.
+ *
+ * @param enabled
+ */
+ public void setSamplePacking(boolean enabled)
+ {
+ mSamplePacking = enabled;
+ }
- //Convert big-endian to little-endian
- for( int x = 0; x < data1.length; x += 4 )
- {
- data[ x ] = data1[ x + 3 ];
- data[ x + 1 ] = data1[ x + 2 ];
- data[ x + 2 ] = data1[ x + 1 ];
- data[ x + 3 ] = data1[ x ];
- }
-
- int count = (int)( (float)data.length / 1.5f );
+ @Override
+ public float[] convert(byte[] samples)
+ {
+ float[] realSamples;
+
+ if(mSamplePacking)
+ {
+ realSamples = convertPacked(samples);
+ }
+ else
+ {
+ realSamples = convertUnpacked(samples);
+ }
+
+ mDCFilter.filter(realSamples);
+
+ return mHilbertTransform.filter(realSamples);
+ }
+
+ /**
+ * Converts the byte array containing unsigned 12-bit short values into
+ * signed float values in the range -1 to 1;
+ *
+ * @param data - byte array of unsigned 16-bit values
+ * @return converted float values
+ */
+ private float[] convertUnpacked(byte[] data)
+ {
+ float[] samples = new float[data.length / 2];
+
+ int pointer = 0;
+
+ for(int x = 0; x < data.length; x += 2)
+ {
+ samples[pointer++] = scale((data[x] & 0xFF) |
+ (data[x + 1] << 8));
+ }
+
+ return samples;
+ }
+
+ /**
+ * Converts every 3 bytes containing a pair of 12-bit unsigned values into
+ * a pair of float values in the range -1 to 1;
+ *
+ * @param data1 - byte array of unsigned 12-bit values
+ * @return converted float values
+ */
+ private float[] convertPacked(byte[] data1)
+ {
+ byte[] data = new byte[data1.length];
+
+ //Convert big-endian to little-endian
+ for(int x = 0; x < data1.length; x += 4)
+ {
+ data[x] = data1[x + 3];
+ data[x + 1] = data1[x + 2];
+ data[x + 2] = data1[x + 1];
+ data[x + 3] = data1[x];
+ }
+
+ int count = (int) ((float) data.length / 1.5f);
/* Ensure we have an even number of samples */
- if( count % 2 == 1 )
- {
- count--;
- }
+ if(count % 2 == 1)
+ {
+ count--;
+ }
- int bytes = (int)((float)count * 1.5f);
+ int bytes = (int) ((float) count * 1.5f);
- float[] samples = new float[ count ];
-
- int pointer = 0;
-
- int first;
- int second;
-
- for( int x = 0; x < bytes; x += 3 )
- {
- first = ( ( data[ x ] << 4 ) & 0xFF0 ) |
- ( ( data[ x + 1 ] >> 4 ) & 0xF );
+ float[] samples = new float[count];
- samples[ pointer++ ] = scale( first );
+ int pointer = 0;
- second = ( ( data[ x + 1 ] << 8 ) & 0xF00 ) |
- ( data[ x + 2 ] & 0xFF );
-
- samples[ pointer++ ] = scale( second );
- }
-
- return samples;
- }
+ int first;
+ int second;
- /**
- * Converts unsigned 12-bit values to signed 12-bit values and then scales
- * the signed value to a signed float value in range: -1.0 : +1.0
- */
- public static float scale( int value )
- {
- return (float)(( value & 0xFFF ) - 2048 ) * SCALE_SIGNED_12_BIT_TO_FLOAT;
- }
+ for(int x = 0; x < bytes; x += 3)
+ {
+ first = ((data[x] << 4) & 0xFF0) |
+ ((data[x + 1] >> 4) & 0xF);
+
+ samples[pointer++] = scale(first);
+
+ second = ((data[x + 1] << 8) & 0xF00) |
+ (data[x + 2] & 0xFF);
+
+ samples[pointer++] = scale(second);
+ }
+
+ return samples;
+ }
+
+ /**
+ * Converts unsigned 12-bit values to signed 12-bit values and then scales
+ * the signed value to a signed float value in range: -1.0 : +1.0
+ */
+ public static float scale(int value)
+ {
+ return (float) ((value & 0xFFF) - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT;
+ }
}
diff --git a/src/source/tuner/airspy/AirspyTunerController.java b/src/source/tuner/airspy/AirspyTunerController.java
index aaab5fd4..f8362bf6 100644
--- a/src/source/tuner/airspy/AirspyTunerController.java
+++ b/src/source/tuner/airspy/AirspyTunerController.java
@@ -17,6 +17,7 @@ import sample.complex.ComplexBuffer;
import source.SourceException;
import source.tuner.TunerController;
import source.tuner.configuration.TunerConfiguration;
+import source.tuner.usb.USBTransferProcessor;
import util.ThreadPool;
import javax.usb.UsbException;
@@ -93,21 +94,11 @@ public class AirspyTunerController extends TunerController
public static final AirspySampleRate DEFAULT_SAMPLE_RATE =
new AirspySampleRate(0, 10000000, "10.00 MHz");
- public static final long USB_TIMEOUT_MS = 2000l; //milliseconds
- public static final byte USB_ENDPOINT = (byte) 0x81;
- public static final byte USB_INTERFACE = (byte) 0x0;
- public static final int TRANSFER_BUFFER_POOL_SIZE = 16;
-
- public static final byte USB_REQUEST_IN =
- (byte) (LibUsb.ENDPOINT_IN |
- LibUsb.REQUEST_TYPE_VENDOR |
- LibUsb.RECIPIENT_DEVICE);
-
- public static final byte USB_REQUEST_OUT =
- (byte) (LibUsb.ENDPOINT_OUT |
- LibUsb.REQUEST_TYPE_VENDOR |
- LibUsb.RECIPIENT_DEVICE);
-
+ private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
+ private static final byte USB_INTERFACE = (byte) 0x0;
+ private static final int USB_TRANSFER_BUFFER_SIZE = 262144;
+ private static final byte USB_REQUEST_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
+ private static final byte USB_REQUEST_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz");
private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class);
@@ -115,17 +106,11 @@ public class AirspyTunerController extends TunerController
private Device mDevice;
private DeviceHandle mDeviceHandle;
- private LinkedTransferQueue mFilledBuffers = new LinkedTransferQueue();
- private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>();
- private int mBufferSize = 262144;
- private BufferProcessor mBufferProcessor = new BufferProcessor();
private AirspySampleAdapter mSampleAdapter = new AirspySampleAdapter();
- private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
- private HilbertTransform mHilbertTransform = new HilbertTransform();
-
private AirspyDeviceInformation mDeviceInfo;
private List mSampleRates = new ArrayList<>();
private int mSampleRate = 0;
+ private USBTransferProcessor mUSBTransferProcessor;
public AirspyTunerController(Device device) throws SourceException
{
@@ -140,7 +125,7 @@ public class AirspyTunerController extends TunerController
int result = LibUsb.open(mDevice, mDeviceHandle);
- if(result != 0)
+ if(result != LibUsb.SUCCESS)
{
if(result == LibUsb.ERROR_ACCESS)
{
@@ -201,6 +186,11 @@ public class AirspyTunerController extends TunerController
{
mLog.error("Setting sample rate is not supported by firmware", e);
}
+
+ String deviceName = "Airspy " + getDeviceInfo().getSerialNumber();
+
+ mUSBTransferProcessor = new USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
+ USB_TRANSFER_BUFFER_SIZE);
}
/**
@@ -1205,250 +1195,32 @@ public class AirspyTunerController extends TunerController
}
/**
- * Adds a sample listener. If the buffer processing thread is
- * not currently running, starts it running in a new thread.
+ * Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started.
*/
public void addListener(Listener listener)
{
- synchronized(mComplexBufferBroadcaster)
+ if(mUSBTransferProcessor != null)
{
- mComplexBufferBroadcaster.addListener(listener);
-
- if(mBufferProcessor == null || !mBufferProcessor.isRunning())
- {
- mBufferProcessor = new BufferProcessor();
-
- Thread thread = new Thread(mBufferProcessor);
- thread.setDaemon(true);
- thread.setName("Airspy Buffer Processor");
-
- thread.start();
- }
+ mUSBTransferProcessor.addListener(listener);
+ }
+ else
+ {
+ mLog.error("Couldn't add IQ buffer listener to Airspy tuner - processor is null");
}
}
/**
- * Removes the sample listener. If this is the last registered listener,
- * shuts down the buffer processing thread.
+ * Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners.
*/
public void removeListener(Listener listener)
{
- synchronized(mComplexBufferBroadcaster)
+ if(mUSBTransferProcessor != null)
{
- mComplexBufferBroadcaster.removeListener(listener);
-
- if(!mComplexBufferBroadcaster.hasListeners())
- {
- mBufferProcessor.stop();
- }
+ mUSBTransferProcessor.removeListener(listener);
}
- }
-
- /**
- * Buffer processing thread. Fetches samples from the Airspy Tuner and
- * dispatches them to all registered listeners
- */
- public class BufferProcessor implements Runnable, TransferCallback
- {
- private ScheduledFuture> mSampleDispatcherTask;
- private LinkedTransferQueue mAvailableTransfers;
- private LinkedTransferQueue mTransfersInProgress = new LinkedTransferQueue<>();
- private AtomicBoolean mRunning = new AtomicBoolean();
- private ByteBuffer mLibUsbHandlerStatus;
- private boolean mCancel = false;
-
- @Override
- public void run()
+ else
{
- if(mRunning.compareAndSet(false, true))
- {
- prepareTransfers();
-
- mSampleDispatcherTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferDispatcher(),
- 20, 20, TimeUnit.MILLISECONDS);
-
- mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
-
- List transfers = new ArrayList<>();
-
- mCancel = false;
-
- while(mRunning.get())
- {
- mAvailableTransfers.drainTo(transfers);
-
- for(Transfer transfer : transfers)
- {
- int result = LibUsb.submitTransfer(transfer);
-
- if(result == LibUsb.SUCCESS)
- {
- mTransfersInProgress.add(transfer);
- }
- else
- {
- mLog.error("error submitting transfer [" + LibUsb.errorName(result) + "]");
- }
- }
-
- int result = LibUsb.handleEventsTimeoutCompleted(
- null, USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer());
-
- if(result != LibUsb.SUCCESS)
- {
- mLog.error("error handling events for libusb");
- }
-
- transfers.clear();
-
- mLibUsbHandlerStatus.rewind();
- }
-
- if(mCancel)
- {
- for(Transfer transfer : mTransfersInProgress)
- {
- LibUsb.cancelTransfer(transfer);
- }
-
- int result = LibUsb.handleEventsTimeoutCompleted(null,
- USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer());
-
- if(result != LibUsb.SUCCESS)
- {
- mLog.error("error handling events for libusb during cancel");
- }
-
- mLibUsbHandlerStatus.rewind();
- }
- }
- }
-
- /**
- * Stops the sample fetching thread
- */
- public void stop()
- {
- mCancel = true;
-
- if(mRunning.compareAndSet(true, false))
- {
- if(mSampleDispatcherTask != null)
- {
- mSampleDispatcherTask.cancel(true);
- mFilledBuffers.clear();
- }
- }
- }
-
- /**
- * Indicates if this thread is running
- */
- public boolean isRunning()
- {
- return mRunning.get();
- }
-
- @Override
- public void processTransfer(Transfer transfer)
- {
- mTransfersInProgress.remove(transfer);
-
- switch(transfer.status())
- {
- case LibUsb.TRANSFER_COMPLETED:
- case LibUsb.TRANSFER_STALL:
- if(transfer.actualLength() > 0)
- {
- ByteBuffer buffer = transfer.buffer();
-
- byte[] data = new byte[transfer.actualLength()];
-
- buffer.get(data);
-
- buffer.rewind();
-
- if(isRunning())
- {
- mFilledBuffers.add(data);
- }
- }
- break;
- case LibUsb.TRANSFER_CANCELLED:
- break;
- default:
- /* unexpected error */
- mLog.error("transfer error [" +
- getTransferStatus(transfer.status()) +
- "] transferred actual: " + transfer.actualLength());
- }
-
- mAvailableTransfers.add(transfer);
- }
-
- /**
- * Prepares (allocates) a set of transfer buffers for use in
- * transferring data from the tuner via the bulk interface
- */
- private void prepareTransfers() throws LibUsbException
- {
- if(mAvailableTransfers == null)
- {
- mAvailableTransfers = new LinkedTransferQueue<>();
-
- for(int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++)
- {
- Transfer transfer = LibUsb.allocTransfer();
-
- if(transfer == null)
- {
- throw new LibUsbException("couldn't allocate transfer",
- LibUsb.ERROR_NO_MEM);
- }
-
- final ByteBuffer buffer =
- ByteBuffer.allocateDirect(mBufferSize);
-
- LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_ENDPOINT,
- buffer, BufferProcessor.this, "Buffer", USB_TIMEOUT_MS);
-
- mAvailableTransfers.add(transfer);
- }
- }
- }
- }
-
- /**
- * Fetches byte[] chunks from the raw sample buffer. Converts each byte
- * array and broadcasts the array to all registered listeners
- */
- public class BufferDispatcher implements Runnable
- {
- @Override
- public void run()
- {
- try
- {
- ArrayList buffers = new ArrayList<>();
-
- mFilledBuffers.drainTo(buffers);
-
- for(byte[] buffer : buffers)
- {
- float[] realSamples = mSampleAdapter.convert(buffer);
-
- realSamples = mDCFilter.filter(realSamples);
-
- float[] quadratureSamples = mHilbertTransform.filter(realSamples);
-
- mComplexBufferBroadcaster.broadcast(
- new ComplexBuffer(quadratureSamples));
- }
- }
- catch(Exception e)
- {
- mLog.error("error during Airspy buffer dispatching", e);
- }
+ mLog.error("Couldn't remove IQ buffer listener from Airspy tuner - processor is null");
}
}
}
diff --git a/src/source/tuner/fcd/FCDTunerController.java b/src/source/tuner/fcd/FCDTunerController.java
index f782044e..f8dae0e8 100644
--- a/src/source/tuner/fcd/FCDTunerController.java
+++ b/src/source/tuner/fcd/FCDTunerController.java
@@ -1,29 +1,23 @@
/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
+ * 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
+ *
******************************************************************************/
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.LoggerFactory;
import org.usb4java.Device;
@@ -31,7 +25,6 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
-
import source.SourceException;
import source.tuner.TunerClass;
import source.tuner.TunerController;
@@ -39,610 +32,587 @@ import source.tuner.TunerType;
import source.tuner.configuration.TunerConfiguration;
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
{
- private final static Logger mLog =
- LoggerFactory.getLogger( FCDTunerController.class );
+ private final static Logger mLog = LoggerFactory.getLogger(FCDTunerController.class);
- public static final double USABLE_BANDWIDTH_PERCENT = 1.0;
- public static final int DC_SPIKE_AVOID_BUFFER = 5000;
-
- public final static byte FCD_INTERFACE = (byte)0x2;
- public final static byte FCD_ENDPOINT_IN = (byte)0x82;
- public final static byte FCD_ENDPOINT_OUT = (byte)0x2;
-
- private Device mDevice;
- private DeviceDescriptor mDeviceDescriptor;
- private DeviceHandle mDeviceHandle;
-
- private FCDConfiguration mConfiguration = new FCDConfiguration();
-
- /**
- * Generic FCD tuner controller - contains functionality common across both
- * funcube dongle tuners
- *
- * @param device
- * @param descriptor
- * @param minTunableFrequency
- * @param maxTunableFrequency
- */
- public FCDTunerController( Device device,
- DeviceDescriptor descriptor,
- int sampleRate,
- int minTunableFrequency,
- int maxTunableFrequency )
- {
- super( minTunableFrequency,
- maxTunableFrequency,
- DC_SPIKE_AVOID_BUFFER,
- USABLE_BANDWIDTH_PERCENT );
- mDevice = device;
- mDeviceDescriptor = descriptor;
+ public static final double USABLE_BANDWIDTH_PERCENT = 1.0;
+ public static final int DC_SPIKE_AVOID_BUFFER = 5000;
- try
- {
- mFrequencyController.setSampleRate( sampleRate );
- }
- catch( SourceException se )
- {
- mLog.error("Error setting sample rate to [" + sampleRate + "]", se );
- }
- }
+ public final static byte FCD_INTERFACE = (byte)0x2;
+ public final static byte FCD_ENDPOINT_IN = (byte)0x82;
+ public final static byte FCD_ENDPOINT_OUT = (byte)0x2;
- /**
- * Initializes the controller by opening the USB device and claiming the
- * HID interface.
- *
- * Invoke this method after constructing this class to setup the
- * controller.
- *
- * @throws SourceException if cannot open and claim the USB device
- */
- public void init() throws SourceException
- {
- mDeviceHandle = new DeviceHandle();
-
- int result = LibUsb.open( mDevice, mDeviceHandle );
+ private Device mDevice;
+ private DeviceDescriptor mDeviceDescriptor;
+ private DeviceHandle mDeviceHandle;
- if( result != LibUsb.SUCCESS )
- {
- mDeviceHandle = null;
-
- throw new SourceException( "libusb couldn't open funcube usb "
- + "device [" + LibUsb.errorName( result ) + "]" );
- }
-
- claimInterface();
- }
+ private FCDConfiguration mConfiguration = new FCDConfiguration();
- /**
- * Disposes of resources. Closes the USB device and interface.
- */
- public void dispose()
- {
- if( mDeviceHandle != null )
- {
- try
- {
- LibUsb.close( mDeviceHandle );
- }
- catch( Exception e )
- {
- mLog.error( "error while closing device handle", e );
- }
-
- mDeviceHandle = null;
- }
-
- mDeviceDescriptor = null;
- mDevice = null;
- }
+ /**
+ * Generic FCD tuner controller - contains functionality common across both
+ * funcube dongle tuners
+ *
+ * @param device
+ * @param descriptor
+ * @param minTunableFrequency
+ * @param maxTunableFrequency
+ */
+ public FCDTunerController(Device device,
+ DeviceDescriptor descriptor,
+ int sampleRate,
+ int minTunableFrequency,
+ int maxTunableFrequency)
+ {
+ super(minTunableFrequency, maxTunableFrequency, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
+ mDevice = device;
+ mDeviceDescriptor = descriptor;
- /**
- * Sample rate of the tuner
- */
- public abstract int getCurrentSampleRate() throws SourceException;
-
- /**
- * Tuner class
- */
- public abstract TunerClass getTunerClass();
+ try
+ {
+ mFrequencyController.setSampleRate(sampleRate);
+ }
+ catch(SourceException se)
+ {
+ mLog.error("Error setting sample rate to [" + sampleRate + "]", se);
+ }
+ }
- /**
- * Tuner type
- */
- public abstract TunerType getTunerType();
+ /**
+ * Initializes the controller by opening the USB device and claiming the
+ * HID interface.
+ *
+ * Invoke this method after constructing this class to setup the
+ * controller.
+ *
+ * @throws SourceException if cannot open and claim the USB device
+ */
+ public void init() throws SourceException
+ {
+ mDeviceHandle = new DeviceHandle();
- /**
- * Applies the settings in the tuner configuration
- */
- public abstract void apply( TunerConfiguration config ) throws SourceException;
+ int result = LibUsb.open(mDevice, mDeviceHandle);
- /**
- * USB address (bus/port)
- */
- public String getUSBAddress()
- {
- if( mDevice != null )
- {
- StringBuilder sb = new StringBuilder();
-
- sb.append( "Bus:" );
- int bus = LibUsb.getBusNumber( mDevice );
- sb.append( bus );
-
- sb.append( " Port:" );
- int port = LibUsb.getPortNumber( mDevice );
- sb.append( port );
-
- return sb.toString();
- }
+ if(result != LibUsb.SUCCESS)
+ {
+ mDeviceHandle = null;
- return "UNKNOWN";
- }
+ throw new SourceException("libusb couldn't open funcube usb device [" + LibUsb.errorName(result) + "]");
+ }
- /**
- * USB Vendor and Product ID
- */
- public String getUSBID()
- {
- if( mDeviceDescriptor != null )
- {
- StringBuilder sb = new StringBuilder();
-
- sb.append( String.format( "%04X",
- (int)( mDeviceDescriptor.idVendor() & 0xFFFF ) ) );
+ claimInterface();
+ }
- sb.append( ":" );
-
- sb.append( String.format( "%04X",
- (int)( mDeviceDescriptor.idProduct() & 0xFFFF ) ) );
+ /**
+ * Disposes of resources. Closes the USB device and interface.
+ */
+ public void dispose()
+ {
+ if(mDeviceHandle != null)
+ {
+ try
+ {
+ LibUsb.close(mDeviceHandle);
+ }
+ catch(Exception e)
+ {
+ mLog.error("error while closing device handle", e);
+ }
- return sb.toString();
- }
+ mDeviceHandle = null;
+ }
- return "UNKNOWN";
- }
+ mDeviceDescriptor = null;
+ mDevice = null;
+ }
- /**
- * USB Port Speed. Should be 2.0 for both types of funcube dongles
- */
- public String getUSBSpeed()
- {
- if( mDevice != null )
- {
- int speed = LibUsb.getDeviceSpeed( mDevice );
+ /**
+ * Sample rate of the tuner
+ */
+ public abstract int getCurrentSampleRate() throws SourceException;
- switch( speed )
- {
- case 0:
- return "1.1 LOW";
- case 1:
- return "1.1 FULL";
- case 2:
- return "2.0 HIGH";
- case 3:
- return "3.0 SUPER";
- default:
- }
- }
-
- return "UNKNOWN";
- }
-
- /**
- * Set fcd interface mode
- */
- public void setFCDMode( Mode mode ) throws UsbException, UsbClaimException
- {
- ByteBuffer response = null;
-
- switch( mode )
- {
- case APPLICATION:
- response = send( FCDCommand.BL_QUERY );
- break;
- case BOOTLOADER:
- response = send( FCDCommand.APP_RESET );
- break;
- default:
- break;
- }
-
- if( response != null )
- {
- mConfiguration.set( response );
- }
- else
- {
- mConfiguration.setModeUnknown();
- }
- }
+ /**
+ * Tuner class
+ */
+ public abstract TunerClass getTunerClass();
- /**
- * Sets the actual (uncorrected) device frequency
- */
- public void setTunedFrequency( long frequency ) throws SourceException
- {
- try
- {
- send( FCDCommand.APP_SET_FREQUENCY_HZ, frequency );
- }
- catch( Exception e )
- {
- throw new SourceException( "Couldn't set FCD Local " +
- "Oscillator Frequency [" + frequency + "]", e );
- }
- }
+ /**
+ * Tuner type
+ */
+ public abstract TunerType getTunerType();
- /**
- * Gets the actual (uncorrected) device frequency
- */
- public long getTunedFrequency() throws SourceException
- {
- try
- {
- ByteBuffer buffer = send( FCDCommand.APP_GET_FREQUENCY_HZ );
-
- buffer.order( ByteOrder.LITTLE_ENDIAN );
-
- return (int)( buffer.getInt( 2 ) & 0xFFFFFFFF );
- }
- catch( Exception e )
- {
- throw new SourceException( "FCDTunerController - "
- + "couldn't get LO frequency", e );
- }
- }
-
- /**
- * Returns the FCD device configuration
- */
- public FCDConfiguration getConfiguration()
- {
- return mConfiguration;
- }
+ /**
+ * Applies the settings in the tuner configuration
+ */
+ public abstract void apply(TunerConfiguration config) throws SourceException;
- /**
- * Claims the USB interface. Attempts to detach the active kernel driver if
- * one is currently attached.
- */
- private void claimInterface() throws SourceException
- {
- if( mDeviceHandle != null )
- {
- int result = LibUsb.kernelDriverActive( mDeviceHandle, FCD_INTERFACE );
-
- if( result == 1 )
- {
- result = LibUsb.detachKernelDriver( mDeviceHandle,
- FCD_INTERFACE );
+ /**
+ * USB address (bus/port)
+ */
+ public String getUSBAddress()
+ {
+ if(mDevice != null)
+ {
+ StringBuilder sb = new StringBuilder();
- if( result != LibUsb.SUCCESS )
- {
- mLog.error( "failed attempt to detach kernel driver [" +
- LibUsb.errorName( result ) + "]" );
- }
- }
-
- result = LibUsb.claimInterface( mDeviceHandle, FCD_INTERFACE );
-
- if( result != LibUsb.SUCCESS )
- {
- throw new SourceException( "couldn't claim usb interface [" +
- LibUsb.errorName( result ) + "]" );
- }
- }
- else
- {
- throw new SourceException( "couldn't claim usb hid interface - no "
- + "device handle" );
- }
- }
+ sb.append("Bus:");
+ int bus = LibUsb.getBusNumber(mDevice);
+ sb.append(bus);
- /**
- * Performs an interrupt write to the OUT endpoint.
- * @param buffer - direct allocated buffer. Must be 64 bytes in length.
- *
- * @throws LibUsbException on error
- */
- private void write( ByteBuffer buffer ) throws LibUsbException
- {
- if( mDeviceHandle != null )
- {
- IntBuffer transferred = IntBuffer.allocate( 1 );
-
- int result = LibUsb.interruptTransfer( mDeviceHandle,
- FCD_ENDPOINT_OUT,
- buffer,
- transferred,
- 500l );
-
- if( result != LibUsb.SUCCESS )
- {
- throw new LibUsbException( "error writing byte buffer",
- result );
- }
-// else if( transferred.get() != buffer.capacity() )
-// {
-// throw new LibUsbException( "transferred bytes [" +
-// transferred.get( 0 ) + "] is not what was expected [" +
-// buffer.capacity() + "]", result );
-// }
- }
- else
- {
- throw new LibUsbException( "device handle is null",
- LibUsb.ERROR_NO_DEVICE );
- }
- }
-
- /**
- * Performs an interrupt write to the OUT endpoint for the FCD command.
- * @param command - no-argument command to write
- * @throws LibUsbException - on error
- */
- private void write( FCDCommand command ) throws LibUsbException
- {
- ByteBuffer buffer = ByteBuffer.allocateDirect( 64 );
+ sb.append(" Port:");
+ int port = LibUsb.getPortNumber(mDevice);
+ sb.append(port);
- buffer.put( 0, command.getCommand() );
- buffer.put( 1, (byte)0x00 );
+ return sb.toString();
+ }
- write( buffer );
- }
+ return "UNKNOWN";
+ }
- /**
- * Convenience logger for debugging read/write operations
- */
- @SuppressWarnings( "unused" )
- private void log( String label, ByteBuffer buffer )
- {
- StringBuilder sb = new StringBuilder();
+ /**
+ * USB Vendor and Product ID
+ */
+ public String getUSBID()
+ {
+ if(mDeviceDescriptor != null)
+ {
+ StringBuilder sb = new StringBuilder();
- sb.append( label );
-
- sb.append( " " );
+ sb.append(String.format("%04X", (int)(mDeviceDescriptor.idVendor() & 0xFFFF)));
+ sb.append(":");
+ sb.append(String.format("%04X", (int)(mDeviceDescriptor.idProduct() & 0xFFFF)));
- sb.append( buffer.get( 0 ) );
- sb.append( " | " );
-
- for( int x = 0; x < 64; x++ )
- {
- sb.append( String.format( "%02X", (int)( buffer.get( x ) & 0xFF ) ) );
- sb.append( " " );
- }
-
- mLog.debug( sb.toString() );
- }
-
- /**
- * Performs an interrupt write to the OUT endpoint for the FCD command.
- * @param command - command to write
- * @param argument - value to write with the command
- * @throws LibUsbException - on error
- */
- private void write( FCDCommand command, long argument ) throws LibUsbException
- {
- ByteBuffer buffer = ByteBuffer.allocateDirect( 64 );
+ return sb.toString();
+ }
+
+ return "UNKNOWN";
+ }
+
+ /**
+ * USB Port Speed. Should be 2.0 for both types of funcube dongles
+ */
+ public String getUSBSpeed()
+ {
+ if(mDevice != null)
+ {
+ int speed = LibUsb.getDeviceSpeed(mDevice);
+
+ switch(speed)
+ {
+ case 0:
+ return "1.1 LOW";
+ case 1:
+ return "1.1 FULL";
+ case 2:
+ return "2.0 HIGH";
+ case 3:
+ return "3.0 SUPER";
+ default:
+ }
+ }
+
+ return "UNKNOWN";
+ }
+
+ /**
+ * Set fcd interface mode
+ */
+ public void setFCDMode(Mode mode) throws UsbException, UsbClaimException
+ {
+ ByteBuffer response = null;
+
+ switch(mode)
+ {
+ case APPLICATION:
+ response = send(FCDCommand.BL_QUERY);
+ break;
+ case BOOTLOADER:
+ response = send(FCDCommand.APP_RESET);
+ break;
+ default:
+ break;
+ }
+
+ if(response != null)
+ {
+ mConfiguration.set(response);
+ }
+ else
+ {
+ mConfiguration.setModeUnknown();
+ }
+ }
+
+ /**
+ * Sets the actual (uncorrected) device frequency
+ */
+ public void setTunedFrequency(long frequency) throws SourceException
+ {
+ try
+ {
+ send(FCDCommand.APP_SET_FREQUENCY_HZ, frequency);
+ }
+ catch(Exception e)
+ {
+ throw new SourceException("Couldn't set FCD Local " +
+ "Oscillator Frequency [" + frequency + "]", e);
+ }
+ }
+
+ /**
+ * Gets the actual (uncorrected) device frequency
+ */
+ public long getTunedFrequency() throws SourceException
+ {
+ try
+ {
+ ByteBuffer buffer = send(FCDCommand.APP_GET_FREQUENCY_HZ);
+
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ return (int)(buffer.getInt(2) & 0xFFFFFFFF);
+ }
+ catch(Exception e)
+ {
+ throw new SourceException("FCDTunerController - "
+ + "couldn't get LO frequency", e);
+ }
+ }
+
+ /**
+ * Returns the FCD device configuration
+ */
+ public FCDConfiguration getConfiguration()
+ {
+ return mConfiguration;
+ }
+
+ /**
+ * Claims the USB interface. Attempts to detach the active kernel driver if
+ * one is currently attached.
+ */
+ private void claimInterface() throws SourceException
+ {
+ if(mDeviceHandle != null)
+ {
+ int result = LibUsb.kernelDriverActive(mDeviceHandle, FCD_INTERFACE);
+
+ if(result == 1)
+ {
+ result = LibUsb.detachKernelDriver(mDeviceHandle, FCD_INTERFACE);
+
+ if(result != LibUsb.SUCCESS)
+ {
+ mLog.error("failed attempt to detach kernel driver [" + LibUsb.errorName(result) + "]");
+ }
+ }
+
+ result = LibUsb.claimInterface(mDeviceHandle, FCD_INTERFACE);
+
+ if(result != LibUsb.SUCCESS)
+ {
+ throw new SourceException("couldn't claim usb interface [" + LibUsb.errorName(result) + "]");
+ }
+ }
+ else
+ {
+ throw new SourceException("couldn't claim usb hid interface - no device handle");
+ }
+ }
+
+ /**
+ * Performs an interrupt write to the OUT endpoint.
+ *
+ * @param buffer - direct allocated buffer. Must be 64 bytes in length.
+ * @throws LibUsbException on error
+ */
+ private void write(ByteBuffer buffer) throws LibUsbException
+ {
+ if(mDeviceHandle != null)
+ {
+ IntBuffer transferred = IntBuffer.allocate(1);
+
+ int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_OUT, buffer, transferred, 500l);
+
+ if(result != LibUsb.SUCCESS)
+ {
+ throw new LibUsbException("error writing byte buffer", result);
+ }
+ }
+ else
+ {
+ throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE);
+ }
+ }
+
+ /**
+ * Performs an interrupt write to the OUT endpoint for the FCD command.
+ *
+ * @param command - no-argument command to write
+ * @throws LibUsbException - on error
+ */
+ private void write(FCDCommand command) throws LibUsbException
+ {
+ ByteBuffer buffer = ByteBuffer.allocateDirect(64);
+
+ buffer.put(0, command.getCommand());
+ buffer.put(1, (byte)0x00);
+
+ write(buffer);
+ }
+
+ /**
+ * Convenience logger for debugging read/write operations
+ */
+ @SuppressWarnings("unused")
+ private void log(String label, ByteBuffer buffer)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(label);
+
+ sb.append(" ");
+
+ sb.append(buffer.get(0));
+ sb.append(" | ");
+
+ for(int x = 0; x < 64; x++)
+ {
+ sb.append(String.format("%02X", (int)(buffer.get(x) & 0xFF)));
+ sb.append(" ");
+ }
+
+ mLog.debug(sb.toString());
+ }
+
+ /**
+ * Performs an interrupt write to the OUT endpoint for the FCD command.
+ *
+ * @param command - command to write
+ * @param argument - value to write with the command
+ * @throws LibUsbException - on error
+ */
+ private void write(FCDCommand command, long argument) throws LibUsbException
+ {
+ ByteBuffer buffer = ByteBuffer.allocateDirect(64);
/* The FCD expects little-endian formatted values */
- buffer.order( ByteOrder.LITTLE_ENDIAN );
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.put( 0, command.getCommand() );
- buffer.putLong( 1, argument );
+ buffer.put(0, command.getCommand());
+ buffer.putLong(1, argument);
- write( buffer );
- }
-
- /**
- * Performs an interrupt read against the endpoint
- * @return buffer read from FCD
- * @throws LibUsbException on error
- */
- private ByteBuffer read() throws LibUsbException
- {
- if( mDeviceHandle != null )
- {
- ByteBuffer buffer = ByteBuffer.allocateDirect( 64 );
+ write(buffer);
+ }
- IntBuffer transferred = IntBuffer.allocate( 1 );
-
+ /**
+ * Performs an interrupt read against the endpoint
+ *
+ * @return buffer read from FCD
+ * @throws LibUsbException on error
+ */
+ private ByteBuffer read() throws LibUsbException
+ {
+ if(mDeviceHandle != null)
+ {
+ ByteBuffer buffer = ByteBuffer.allocateDirect(64);
- int result = LibUsb.interruptTransfer( mDeviceHandle,
- FCD_ENDPOINT_IN,
- buffer,
- transferred,
- 500l );
-
- if( result != LibUsb.SUCCESS )
- {
- throw new LibUsbException( "error reading byte buffer",
- result );
- }
- else if( transferred.get( 0 ) != buffer.capacity() )
- {
- throw new LibUsbException( "received bytes [" +
- transferred.get( 0 ) + "] didn't match expected "
- + "length [" + buffer.capacity() + "]", result );
- }
+ IntBuffer transferred = IntBuffer.allocate(1);
- return buffer;
- }
-
- throw new LibUsbException( "device handle is null",
- LibUsb.ERROR_NO_DEVICE );
- }
-
- /**
- * Sends the FCD command and argument. Performs a read to complete the
- * command.
- *
- * @param command - command to send
- * @param argument - command argument to send
- * @throws LibUsbException - on error
- */
- protected void send( FCDCommand command, long argument ) throws LibUsbException
- {
- write( command, argument );
- read();
- }
-
- /**
- * Sends the no-argument FCD command. Performs a read to complete the
- * command.
- *
- * @param command - command to send
- * @throws LibUsbException - on error
- */
- protected ByteBuffer send( FCDCommand command ) throws LibUsbException
- {
- write( command );
- return read();
- }
- /**
- * FCD configuration string parsing class
- */
- public class FCDConfiguration
- {
- private String mConfig;
- private Mode mMode;
+ int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_IN, buffer, transferred, 500l);
- public FCDConfiguration()
- {
- mConfig = null;
- mMode = Mode.UNKNOWN;
- }
-
- private void setModeUnknown()
- {
- mConfig = null;
- mMode = Mode.UNKNOWN;
- }
-
- /**
- * Extracts the configuration string from the buffer
- */
- public void set( ByteBuffer buffer )
- {
- if( buffer.capacity() == 64 )
- {
- byte[] data = new byte[ 64 ];
-
- for( int x = 0; x < 64; x++ )
- {
- data[ x ] = buffer.get( x );
- }
+ if(result != LibUsb.SUCCESS)
+ {
+ throw new LibUsbException("error reading byte buffer", result);
+ }
+ else if(transferred.get(0) != buffer.capacity())
+ {
+ throw new LibUsbException("received bytes [" + transferred.get(0) +
+ "] didn't match expected length [" + buffer.capacity() + "]", result);
+ }
+
+ return buffer;
+ }
+
+ throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE);
+ }
+
+ /**
+ * Sends the FCD command and argument. Performs a read to complete the
+ * command.
+ *
+ * @param command - command to send
+ * @param argument - command argument to send
+ * @throws LibUsbException - on error
+ */
+ protected void send(FCDCommand command, long argument) throws LibUsbException
+ {
+ write(command, argument);
+ read();
+ }
+
+ /**
+ * Sends the no-argument FCD command. Performs a read to complete the
+ * command.
+ *
+ * @param command - command to send
+ * @throws LibUsbException - on error
+ */
+ protected ByteBuffer send(FCDCommand command) throws LibUsbException
+ {
+ write(command);
+ return read();
+ }
+
+ /**
+ * FCD configuration string parsing class
+ */
+ public class FCDConfiguration
+ {
+ private String mConfig;
+ private Mode mMode;
+
+ public FCDConfiguration()
+ {
+ mConfig = null;
+ mMode = Mode.UNKNOWN;
+ }
+
+ private void setModeUnknown()
+ {
+ mConfig = null;
+ mMode = Mode.UNKNOWN;
+ }
+
+ /**
+ * Extracts the configuration string from the buffer
+ */
+ public void set(ByteBuffer buffer)
+ {
+ if(buffer.capacity() == 64)
+ {
+ byte[] data = new byte[64];
+
+ for(int x = 0; x < 64; x++)
+ {
+ data[x] = buffer.get(x);
+ }
+
+ mConfig = new String(data);
+ mMode = Mode.getMode(mConfig);
+ }
+ else
+ {
+ mConfig = null;
+ mMode = Mode.ERROR;
+ }
+ }
+
+ public Mode getMode()
+ {
+ return mMode;
+ }
- mConfig = new String( data );
- mMode = Mode.getMode( mConfig );
- }
- else
- {
- mConfig = null;
- mMode = Mode.ERROR;
- }
- }
-
- public Mode getMode()
- {
- return mMode;
- }
-
public FCDModel getModel()
- {
- FCDModel retVal = FCDModel.FUNCUBE_UNKNOWN;
-
- switch( mMode )
- {
- case APPLICATION:
- retVal = FCDModel.getFCD( mConfig.substring( 15, 22 ) );
- break;
- case BOOTLOADER:
- case UNKNOWN:
- case ERROR:
- break;
- }
-
- return retVal;
- }
-
- public Block getBandBlocking()
- {
- Block retVal = Block.UNKNOWN;
-
- switch( mMode )
- {
- case APPLICATION:
- retVal = Block.getBlock( mConfig.substring( 23, 33 ).trim() );
- break;
- case BOOTLOADER:
- case UNKNOWN:
- case ERROR:
- break;
- }
-
- return retVal;
- }
+ {
+ FCDModel retVal = FCDModel.FUNCUBE_UNKNOWN;
- public String getFirmware()
- {
- String retVal = null;
-
- switch( mMode )
- {
- case APPLICATION:
- retVal = mConfig.substring( 9, 14 );
- break;
- case BOOTLOADER:
- case UNKNOWN:
- case ERROR:
- break;
- }
+ switch(mMode)
+ {
+ case APPLICATION:
+ retVal = FCDModel.getFCD(mConfig.substring(15, 22));
+ break;
+ case BOOTLOADER:
+ case UNKNOWN:
+ case ERROR:
+ break;
+ }
- return retVal;
- }
+ return retVal;
+ }
- public String toString()
- {
- return getModel().getLabel();
- }
- }
-
- public enum Mode
- {
- APPLICATION,
- BOOTLOADER,
- ERROR,
- UNKNOWN;
+ public Block getBandBlocking()
+ {
+ Block retVal = Block.UNKNOWN;
- public static Mode getMode( String config )
- {
- Mode retVal = UNKNOWN;
+ switch(mMode)
+ {
+ case APPLICATION:
+ retVal = Block.getBlock(mConfig.substring(23, 33).trim());
+ break;
+ case BOOTLOADER:
+ case UNKNOWN:
+ case ERROR:
+ break;
+ }
- if( config == null )
- {
- retVal = ERROR;
- }
- else
- {
- if( config.length() >= 8 )
- {
- String mode = config.substring( 2, 8 ).trim();
+ return retVal;
+ }
- if( mode.equalsIgnoreCase( "FCDAPP" ) )
- {
- retVal = APPLICATION;
- }
- else if( mode.equalsIgnoreCase( "FCDBL" ) )
- {
- retVal = BOOTLOADER;
- }
- }
- }
-
- return retVal;
- }
- }
+ public String getFirmware()
+ {
+ String retVal = null;
+
+ switch(mMode)
+ {
+ case APPLICATION:
+ retVal = mConfig.substring(9, 14);
+ break;
+ case BOOTLOADER:
+ case UNKNOWN:
+ case ERROR:
+ break;
+ }
+
+ return retVal;
+ }
+
+ public String toString()
+ {
+ return getModel().getLabel();
+ }
+ }
+
+ public enum Mode
+ {
+ APPLICATION,
+ BOOTLOADER,
+ ERROR,
+ UNKNOWN;
+
+ public static Mode getMode(String config)
+ {
+ Mode retVal = UNKNOWN;
+
+ if(config == null)
+ {
+ retVal = ERROR;
+ }
+ else
+ {
+ if(config.length() >= 8)
+ {
+ String mode = config.substring(2, 8).trim();
+
+ if(mode.equalsIgnoreCase("FCDAPP"))
+ {
+ retVal = APPLICATION;
+ }
+ else if(mode.equalsIgnoreCase("FCDBL"))
+ {
+ retVal = BOOTLOADER;
+ }
+ }
+ }
+
+ return retVal;
+ }
+ }
}
diff --git a/src/source/tuner/fcd/proplusV2/FCD2TunerController.java b/src/source/tuner/fcd/proplusV2/FCD2TunerController.java
index ac781262..e1fb788c 100644
--- a/src/source/tuner/fcd/proplusV2/FCD2TunerController.java
+++ b/src/source/tuner/fcd/proplusV2/FCD2TunerController.java
@@ -1,30 +1,27 @@
/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014-2016 Dennis Sheirer
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
+ * 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
+ *
******************************************************************************/
package source.tuner.fcd.proplusV2;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
-
import source.SourceException;
import source.tuner.MixerTunerType;
import source.tuner.TunerClass;
@@ -33,197 +30,200 @@ import source.tuner.configuration.TunerConfiguration;
import source.tuner.fcd.FCDCommand;
import source.tuner.fcd.FCDTunerController;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
public class FCD2TunerController extends FCDTunerController
{
- private final static Logger mLog = LoggerFactory.getLogger( FCD2TunerController.class );
+ private final static Logger mLog = LoggerFactory.getLogger(FCD2TunerController.class);
- public static final int MINIMUM_TUNABLE_FREQUENCY = 150000;
- public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
- public static final int SAMPLE_RATE = 192000;
-
- public FCD2TunerController( Device device, DeviceDescriptor descriptor )
- {
- super( device, descriptor,
- (int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(),
- MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY );
- }
+ public static final int MINIMUM_TUNABLE_FREQUENCY = 150000;
+ public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
+ public static final int SAMPLE_RATE = 192000;
- public void init() throws SourceException
- {
- super.init();
+ public FCD2TunerController(Device device, DeviceDescriptor descriptor)
+ {
+ super(device, descriptor,
+ (int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(),
+ MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
+ }
- mFrequencyController.setSampleRate( SAMPLE_RATE );
+ public void init() throws SourceException
+ {
+ super.init();
- try
- {
- setFCDMode( Mode.APPLICATION );
- }
- catch( Exception e )
- {
- throw new SourceException( "error setting Mode to APPLICATION", e );
- }
- }
-
- public int getCurrentSampleRate()
- {
- return SAMPLE_RATE;
- }
+ mFrequencyController.setSampleRate(SAMPLE_RATE);
- @Override
+ try
+ {
+ setFCDMode(Mode.APPLICATION);
+ }
+ catch(Exception e)
+ {
+ throw new SourceException("error setting Mode to APPLICATION", e);
+ }
+ }
+
+ public int getCurrentSampleRate()
+ {
+ return SAMPLE_RATE;
+ }
+
+ @Override
public TunerClass getTunerClass()
{
- return TunerClass.FUNCUBE_DONGLE_PRO_PLUS;
+ return TunerClass.FUNCUBE_DONGLE_PRO_PLUS;
}
- @Override
+ @Override
public TunerType getTunerType()
{
- return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
+ return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
}
-
- public void setLNAGain( boolean enabled ) throws SourceException
- {
- try
- {
- send( FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0 );
- }
- catch ( Exception e )
- {
- throw new SourceException( "error while setting LNA Gain", e );
- }
- }
- public void setMixerGain( boolean enabled ) throws SourceException
- {
- try
- {
- send( FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0 );
- }
- catch ( Exception e )
- {
- throw new SourceException( "error while setting Mixer Gain", e );
- }
- }
-
- @Override
- public void apply( TunerConfiguration config ) throws SourceException
+ public void setLNAGain(boolean enabled) throws SourceException
{
- if( config instanceof FCD2TunerConfiguration )
- {
- FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
-
- setFrequencyCorrection( fcd2.getFrequencyCorrection() );
- setFrequency( fcd2.getFrequency() );
- setLNAGain( fcd2.getGainLNA() );
- setMixerGain( fcd2.getGainMixer() );
-
- try
- {
- setFrequency( fcd2.getFrequency() );
- }
- catch( SourceException se )
- {
- //Do nothing, we couldn't set the frequency
- }
- }
+ try
+ {
+ send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0);
+ }
+ catch(Exception e)
+ {
+ throw new SourceException("error while setting LNA Gain", e);
+ }
}
-
- public int getDCCorrection()
- {
- int dcCorrection = -999;
-
- try
- {
- ByteBuffer buffer = send( FCDCommand.APP_GET_DC_CORRECTION );
-
- buffer.order( ByteOrder.LITTLE_ENDIAN );
-
- return buffer.getInt( 2 );
- }
- catch ( Exception e )
- {
- mLog.error( "error getting dc correction value", e );
- }
-
- return dcCorrection;
- }
-
- public void setDCCorrection( int value )
- {
- try
- {
- send( FCDCommand.APP_SET_DC_CORRECTION, value );
- }
- catch ( Exception e )
- {
- mLog.error( "error setting dc correction to [" + value + "]", e );
- }
- }
-
- public int getIQCorrection()
- {
- int iqCorrection = -999;
-
- try
- {
- ByteBuffer buffer = send( FCDCommand.APP_GET_IQ_CORRECTION );
-
- buffer.order( ByteOrder.LITTLE_ENDIAN );
-
- return buffer.getInt( 2 );
- }
- catch ( Exception e )
- {
- mLog.error( "error reading IQ correction value", e );
- }
-
- return iqCorrection;
- }
-
- public void setIQCorrection( int value )
- {
- try
- {
- send( FCDCommand.APP_SET_IQ_CORRECTION, value );
- }
- catch ( Exception e )
- {
- mLog.error( "error setting IQ correction to [" + value + "]", e );
- }
- }
-
- public enum Block
- {
- CELLULAR_BAND_BLOCKED( "Blocked" ),
- NO_BAND_BLOCK( "Unblocked" ),
- UNKNOWN( "Unknown" );
-
- private String mLabel;
-
- private Block( String label )
- {
- mLabel = label;
- }
-
- public String getLabel()
- {
- return mLabel;
- }
- public static Block getBlock( String block )
- {
- Block retVal = UNKNOWN;
+ public void setMixerGain(boolean enabled) throws SourceException
+ {
+ try
+ {
+ send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
+ }
+ catch(Exception e)
+ {
+ throw new SourceException("error while setting Mixer Gain", e);
+ }
+ }
- if( block.equalsIgnoreCase( "No blk" ) )
- {
- retVal = NO_BAND_BLOCK;
- }
- else if( block.equalsIgnoreCase( "Cell blk" ) )
- {
- retVal = CELLULAR_BAND_BLOCKED;
- }
-
- return retVal;
- }
- }
+ @Override
+ public void apply(TunerConfiguration config) throws SourceException
+ {
+ if(config instanceof FCD2TunerConfiguration)
+ {
+ FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
+
+ setFrequencyCorrection(fcd2.getFrequencyCorrection());
+ setFrequency(fcd2.getFrequency());
+ setLNAGain(fcd2.getGainLNA());
+ setMixerGain(fcd2.getGainMixer());
+
+ try
+ {
+ setFrequency(fcd2.getFrequency());
+ }
+ catch(SourceException se)
+ {
+ //Do nothing, we couldn't set the frequency
+ }
+ }
+ }
+
+ public int getDCCorrection()
+ {
+ int dcCorrection = -999;
+
+ try
+ {
+ ByteBuffer buffer = send(FCDCommand.APP_GET_DC_CORRECTION);
+
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ return buffer.getInt(2);
+ }
+ catch(Exception e)
+ {
+ mLog.error("error getting dc correction value", e);
+ }
+
+ return dcCorrection;
+ }
+
+ public void setDCCorrection(int value)
+ {
+ try
+ {
+ send(FCDCommand.APP_SET_DC_CORRECTION, value);
+ }
+ catch(Exception e)
+ {
+ mLog.error("error setting dc correction to [" + value + "]", e);
+ }
+ }
+
+ public int getIQCorrection()
+ {
+ int iqCorrection = -999;
+
+ try
+ {
+ ByteBuffer buffer = send(FCDCommand.APP_GET_IQ_CORRECTION);
+
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ return buffer.getInt(2);
+ }
+ catch(Exception e)
+ {
+ mLog.error("error reading IQ correction value", e);
+ }
+
+ return iqCorrection;
+ }
+
+ public void setIQCorrection(int value)
+ {
+ try
+ {
+ send(FCDCommand.APP_SET_IQ_CORRECTION, value);
+ }
+ catch(Exception e)
+ {
+ mLog.error("error setting IQ correction to [" + value + "]", e);
+ }
+ }
+
+ public enum Block
+ {
+ CELLULAR_BAND_BLOCKED("Blocked"),
+ NO_BAND_BLOCK("Unblocked"),
+ UNKNOWN("Unknown");
+
+ private String mLabel;
+
+ private Block(String label)
+ {
+ mLabel = label;
+ }
+
+ public String getLabel()
+ {
+ return mLabel;
+ }
+
+ public static Block getBlock(String block)
+ {
+ Block retVal = UNKNOWN;
+
+ if(block.equalsIgnoreCase("No blk"))
+ {
+ retVal = NO_BAND_BLOCK;
+ }
+ else if(block.equalsIgnoreCase("Cell blk"))
+ {
+ retVal = CELLULAR_BAND_BLOCKED;
+ }
+
+ return retVal;
+ }
+ }
}
diff --git a/src/source/tuner/hackrf/HackRFTunerController.java b/src/source/tuner/hackrf/HackRFTunerController.java
index fb570199..1741bfaf 100644
--- a/src/source/tuner/hackrf/HackRFTunerController.java
+++ b/src/source/tuner/hackrf/HackRFTunerController.java
@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
+ * Copyright (C) 2014-2017 Dennis Sheirer
*
* Ported from libhackrf at:
* https://github.com/mossmann/hackrf/tree/master/host/libhackrf
@@ -61,8 +61,6 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
-import org.usb4java.Transfer;
-import org.usb4java.TransferCallback;
import sample.Broadcaster;
import sample.Listener;
import sample.adapter.ByteSampleAdapter;
@@ -70,38 +68,25 @@ import sample.complex.ComplexBuffer;
import source.SourceException;
import source.tuner.TunerController;
import source.tuner.configuration.TunerConfiguration;
-import util.ThreadPool;
+import source.tuner.usb.USBTransferProcessor;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
public class HackRFTunerController extends TunerController
{
- private final static Logger mLog =
- LoggerFactory.getLogger(HackRFTunerController.class);
+ private final static Logger mLog = LoggerFactory.getLogger(HackRFTunerController.class);
public final static long USB_TIMEOUT_US = 1000000l; //uSeconds
- public static final byte USB_ENDPOINT = (byte) 0x81;
- public static final byte USB_INTERFACE = (byte) 0x0;
+ public static final byte USB_ENDPOINT = (byte)0x81;
+ public static final byte USB_INTERFACE = (byte)0x0;
+ public static final int USB_TRANSFER_BUFFER_SIZE = 262144;
- public static final byte REQUEST_TYPE_IN =
- (byte) (LibUsb.ENDPOINT_IN |
- LibUsb.REQUEST_TYPE_VENDOR |
- LibUsb.RECIPIENT_DEVICE);
-
- public static final byte REQUEST_TYPE_OUT =
- (byte) (LibUsb.ENDPOINT_OUT |
- LibUsb.REQUEST_TYPE_VENDOR |
- LibUsb.RECIPIENT_DEVICE);
+ public static final byte REQUEST_TYPE_IN = (byte)(LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
+ public static final byte REQUEST_TYPE_OUT = (byte)(LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
public static final long MIN_FREQUENCY = 10000000l;
public static final long MAX_FREQUENCY = 6000000000l;
@@ -109,18 +94,10 @@ public class HackRFTunerController extends TunerController
public static final double USABLE_BANDWIDTH_PERCENT = 0.95;
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
- public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
-
- private LinkedTransferQueue mFilledBuffers =
- new LinkedTransferQueue();
-
- private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>();
-
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
- private BufferProcessor mBufferProcessor = new BufferProcessor();
+ private USBTransferProcessor mUSBTransferProcessor;
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
- public int mBufferSize = 262144;
private boolean mAmplifierEnabled = false;
private Device mDevice;
@@ -167,6 +144,20 @@ public class HackRFTunerController extends TunerController
throw new SourceException("HackRF Tuner Controller - couldn't "
+ "claim USB interface or get endpoint or pipe", e);
}
+
+ String name;
+
+ try
+ {
+ name = "HackRF " + getSerial().getSerialNumber();
+ }
+ catch(UsbException ue)
+ {
+ //Do nothing, we couldn't determine the serial number
+ name = "HackRF - Unidentified Serial";
+ }
+
+ mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mSampleAdapter, USB_TRANSFER_BUFFER_SIZE);
}
/**
@@ -215,7 +206,7 @@ public class HackRFTunerController extends TunerController
*/
public BoardID getBoardID() throws UsbException
{
- int id = readByte(Request.BOARD_ID_READ, (byte) 0, (byte) 0, false);
+ int id = readByte(Request.BOARD_ID_READ, (byte)0, (byte)0, false);
return BoardID.lookup(id);
}
@@ -317,8 +308,8 @@ public class HackRFTunerController extends TunerController
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
buffer.order(ByteOrder.LITTLE_ENDIAN);
- int mhz = (int) (frequency / 1E6);
- int hz = (int) (frequency - (mhz * 1E6));
+ int mhz = (int)(frequency / 1E6);
+ int hz = (int)(frequency - (mhz * 1E6));
buffer.putInt(mhz);
buffer.putInt(hz);
@@ -350,7 +341,7 @@ public class HackRFTunerController extends TunerController
if(config instanceof HackRFTunerConfiguration)
{
HackRFTunerConfiguration hackRFConfig =
- (HackRFTunerConfiguration) config;
+ (HackRFTunerConfiguration)config;
try
{
@@ -395,8 +386,8 @@ public class HackRFTunerController extends TunerController
int transferred = LibUsb.controlTransfer(mDeviceHandle,
REQUEST_TYPE_IN,
request.getRequestNumber(),
- (short) value,
- (short) index,
+ (short)value,
+ (short)index,
buffer,
USB_TIMEOUT_US);
@@ -450,11 +441,11 @@ public class HackRFTunerController extends TunerController
if(signed)
{
- return (int) (buffer.get());
+ return (int)(buffer.get());
}
else
{
- return (int) (buffer.get() & 0xFF);
+ return (int)(buffer.get() & 0xFF);
}
}
@@ -468,8 +459,8 @@ public class HackRFTunerController extends TunerController
int transferred = LibUsb.controlTransfer(mDeviceHandle,
REQUEST_TYPE_OUT,
request.getRequestNumber(),
- (short) value,
- (short) index,
+ (short)value,
+ (short)index,
buffer,
USB_TIMEOUT_US);
@@ -567,7 +558,7 @@ public class HackRFTunerController extends TunerController
private Request(int number)
{
- mRequestNumber = (byte) number;
+ mRequestNumber = (byte)number;
}
public byte getRequestNumber()
@@ -685,7 +676,7 @@ public class HackRFTunerController extends TunerController
private BoardID(int number, String label)
{
- mIDNumber = (byte) number;
+ mIDNumber = (byte)number;
mLabel = label;
}
@@ -732,7 +723,7 @@ public class HackRFTunerController extends TunerController
private Mode(int number, String label)
{
- mNumber = (byte) number;
+ mNumber = (byte)number;
mLabel = label;
}
@@ -890,21 +881,7 @@ public class HackRFTunerController extends TunerController
*/
public void addListener(Listener listener)
{
- synchronized(mComplexBufferBroadcaster)
- {
- mComplexBufferBroadcaster.addListener(listener);
-
- if(mBufferProcessor == null || !mBufferProcessor.isRunning())
- {
- mBufferProcessor = new BufferProcessor();
-
- Thread thread = new Thread(mBufferProcessor);
- thread.setDaemon(true);
- thread.setName("HackRF Sample Processor");
-
- thread.start();
- }
- }
+ mUSBTransferProcessor.addListener(listener);
}
/**
@@ -913,241 +890,7 @@ public class HackRFTunerController extends TunerController
*/
public void removeListener(Listener listener)
{
- synchronized(mComplexBufferBroadcaster)
- {
- mComplexBufferBroadcaster.removeListener(listener);
-
- if(!mComplexBufferBroadcaster.hasListeners())
- {
- mBufferProcessor.stop();
- }
- }
+ mUSBTransferProcessor.removeListener(listener);
}
- /**
- * Buffer processing thread. Fetches samples from the HackRF Tuner and
- * dispatches them to all registered listeners
- */
- public class BufferProcessor implements Runnable, TransferCallback
- {
- private ScheduledFuture> mSampleDispatcherTask;
- private AtomicBoolean mRunning = new AtomicBoolean();
- private LinkedTransferQueue mAvailableTransfers;
- private LinkedTransferQueue 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 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 buffers = new ArrayList();
-
- 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 + ")";
- }
- }
}
diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java
index d31a6499..e70431ff 100644
--- a/src/source/tuner/rtl/RTL2832TunerController.java
+++ b/src/source/tuner/rtl/RTL2832TunerController.java
@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
+ * Copyright (C) 2014-2017 Dennis Sheirer
*
* Java version based on librtlsdr
* Copyright (C) 2012-2013 by Steve Markgraf
@@ -21,7 +21,6 @@
******************************************************************************/
package source.tuner.rtl;
-import buffer.FloatAveragingBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Device;
@@ -29,54 +28,38 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
-import org.usb4java.Transfer;
-import org.usb4java.TransferCallback;
import sample.Broadcaster;
import sample.Listener;
import sample.adapter.ByteSampleAdapter;
+import sample.adapter.ISampleAdapter;
import sample.complex.ComplexBuffer;
import source.SourceException;
import source.tuner.TunerController;
+import source.tuner.TunerManager;
import source.tuner.TunerType;
-import util.ThreadPool;
+import source.tuner.usb.USBTransferProcessor;
import javax.usb.UsbDisconnectedException;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
-import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.LinkedTransferQueue;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
public abstract class RTL2832TunerController extends TunerController
{
private final static Logger mLog = LoggerFactory.getLogger(RTL2832TunerController.class);
- public final static int INT_NULL_VALUE = -1;
- public final static long LONG_NULL_VALUE = -1l;
- public final static double DOUBLE_NULL_VALUE = -1.0D;
public final static int TWO_TO_22_POWER = 4194304;
+ public final static int USB_TRANSFER_BUFFER_SIZE = 131072;
public final static byte USB_INTERFACE = (byte) 0x0;
- public final static byte USB_BULK_ENDPOINT = (byte) 0x81;
- public final static byte CONTROL_ENDPOINT_IN =
- (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR);
- public final static byte CONTROL_ENDPOINT_OUT =
- (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR);
+ public final static byte CONTROL_ENDPOINT_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR);
+ public final static byte CONTROL_ENDPOINT_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR);
public final static long TIMEOUT_US = 1000000l; //uSeconds
-
public final static byte REQUEST_ZERO = (byte) 0;
-
- public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
-
public final static byte EEPROM_ADDRESS = (byte) 0xA0;
public final static byte[] sFIR_COEFFICIENTS =
@@ -87,8 +70,7 @@ public abstract class RTL2832TunerController extends TunerController
(byte) 0x71, (byte) 0x74, (byte) 0x19, (byte) 0x41, (byte) 0xA5
};
- public static final SampleRate DEFAULT_SAMPLE_RATE =
- SampleRate.RATE_0_960MHZ;
+ public static final SampleRate DEFAULT_SAMPLE_RATE = SampleRate.RATE_2_400MHZ;
protected Device mDevice;
protected DeviceDescriptor mDeviceDescriptor;
@@ -96,24 +78,13 @@ public abstract class RTL2832TunerController extends TunerController
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
- private BufferProcessor mBufferProcessor;
-
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>();
- private LinkedTransferQueue mFilledBuffers =
- new LinkedTransferQueue();
-
- private SampleRateMonitor mSampleRateMonitor;
- private AtomicInteger mSampleCounter = new AtomicInteger();
- private static final DecimalFormat mDecimalFormatter =
- new DecimalFormat("###,###,###.0");
- private static final DecimalFormat mPercentFormatter =
- new DecimalFormat("###.00");
-
protected int mOscillatorFrequency = 28800000; //28.8 MHz
- public int mBufferSize = 131072;
+
+ private USBTransferProcessor mUSBTransferProcessor;
protected Descriptor mDescriptor;
@@ -190,6 +161,11 @@ public abstract class RTL2832TunerController extends TunerController
+ "descriptor byte array " +
(eeprom == null ? "[null]" : Arrays.toString(eeprom)), e);
}
+
+ String deviceName = getTunerType().getLabel() + " " + getUniqueID();
+
+ mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
+ USB_TRANSFER_BUFFER_SIZE);
}
/**
@@ -265,7 +241,7 @@ public abstract class RTL2832TunerController extends TunerController
switch(mode)
{
case QUADRATURE:
- /* Set intermediate frequency to 0 Hz */
+ /* Set intermediate frequency to 0 Hz */
setIFFrequency(0);
/* Enable I/Q ADC Input */
@@ -476,14 +452,13 @@ public abstract class RTL2832TunerController extends TunerController
{
try
{
- if(mBufferProcessor.isRunning())
+ if(mUSBTransferProcessor != null)
{
- mBufferProcessor.stop();
+ mUSBTransferProcessor.removeAllListeners();
+ TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(mUSBTransferProcessor);
}
LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE);
-
- LibUsb.exit(null);
}
catch(Exception e)
{
@@ -1014,11 +989,6 @@ public abstract class RTL2832TunerController extends TunerController
mSampleRate = sampleRate;
mFrequencyController.setSampleRate(sampleRate.getRate());
-
- if(mSampleRateMonitor != null)
- {
- mSampleRateMonitor.setSampleRate(mSampleRate.getRate());
- }
}
public void setSampleRateFrequencyCorrection(int ppm) throws SourceException
@@ -1318,331 +1288,6 @@ public abstract class RTL2832TunerController extends TunerController
}
}
- /**
- * Adds a sample listener. If the buffer processing thread is
- * not currently running, starts it running in a new thread.
- */
- public void addListener(Listener 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 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 mAvailableTransfers;
- private LinkedTransferQueue 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 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 buffers = new ArrayList();
-
- mFilledBuffers.drainTo(buffers);
-
- for(byte[] buffer : buffers)
- {
- float[] samples = mSampleAdapter.convert(buffer);
-
- mComplexBufferBroadcaster.broadcast(new ComplexBuffer(samples));
- }
- }
- catch(Exception e)
- {
- mLog.error("error during rtl2832 buffer dispatcher run", e);
- }
- }
- }
-
- public static String getTransferStatus(int status)
- {
- switch(status)
- {
- case 0:
- return "TRANSFER COMPLETED (0)";
- case 1:
- return "TRANSFER ERROR (1)";
- case 2:
- return "TRANSFER TIMED OUT (2)";
- case 3:
- return "TRANSFER CANCELLED (3)";
- case 4:
- return "TRANSFER STALL (4)";
- case 5:
- return "TRANSFER NO DEVICE (5)";
- case 6:
- return "TRANSFER OVERFLOW (6)";
- default:
- return "UNKNOWN TRANSFER STATUS (" + status + ")";
- }
- }
-
-
- public class USBEventHandlingThread implements Runnable
- {
- /**
- * If thread should abort.
- */
- private volatile boolean mAbort;
-
- /**
- * Aborts the event handling thread.
- */
- public void abort()
- {
- mAbort = true;
- }
-
- @Override
- public void run()
- {
- while(!mAbort)
- {
- int result = LibUsb.handleEventsTimeout(null, 1000);
-
- if(result != LibUsb.SUCCESS)
- {
- mAbort = true;
-
- mLog.error("error handling usb events [" +
- LibUsb.errorName(result) + "]");
-
- throw new LibUsbException("Unable to handle USB "
- + "events", result);
- }
- }
- }
- }
-
public enum TunerTypeCheck
{
E4K(0xC8, 0x02, 0x40),
@@ -1679,102 +1324,6 @@ public abstract class RTL2832TunerController extends TunerController
}
}
-
- /**
- * Averages the sample rate over a 10-second period. The count is for the
- * number of bytes received from the tuner. There are two bytes for each
- * sample. So, we divide by 20 to get the average sample rate.
- */
- public class SampleRateMonitor implements Runnable
- {
- private static final int BUFFER_SIZE = 5;
- private int mTargetSampleRate;
- private int mSampleRateMinimum;
- private int mSampleRateMaximum;
- private int mNewTargetSampleRate;
- private FloatAveragingBuffer mRateErrorBuffer =
- new FloatAveragingBuffer(BUFFER_SIZE);
- private AtomicBoolean mRateChanged = new AtomicBoolean();
-
- public SampleRateMonitor(int sampleRate)
- {
- setTargetRate(sampleRate);
- }
-
- public void setSampleRate(int sampleRate)
- {
- mNewTargetSampleRate = sampleRate;
- mRateChanged.set(true);
- }
-
- private void setTargetRate(int rate)
- {
- mTargetSampleRate = rate;
- mSampleRateMinimum = (int) ((float) mTargetSampleRate * 0.95f);
- mSampleRateMaximum = (int) ((float) mTargetSampleRate * 1.05f);
-
- for(int x = 0; x < BUFFER_SIZE; x++)
- {
- mRateErrorBuffer.get(0);
- }
- }
-
- @Override
- public void run()
- {
- if(mRateChanged.compareAndSet(true, false))
- {
- setTargetRate(mNewTargetSampleRate);
-
- /* Reset the sample counter */
- mSampleCounter.set(0);
-
- mLog.info("monitor reset for new sample rate [" + mTargetSampleRate + "]");
- }
- else
- {
- int count = mSampleCounter.getAndSet(0);
-
- float current = (float) count / 20.0f;
-
- /**
- * Only accept values +/- 5% of target rate
- */
- float average;
-
- if(mSampleRateMinimum < current && current < mSampleRateMaximum)
- {
- average = mRateErrorBuffer.get((float) mTargetSampleRate - current);
- }
- else
- {
- average = mTargetSampleRate;
- }
-
- StringBuilder sb = new StringBuilder();
- sb.append("[");
- if(mDescriptor != null)
- {
- sb.append(mDescriptor.getSerial());
- }
- else
- {
- sb.append("DESCRIPTOR IS NULL");
- }
- sb.append("] sample rate current [");
- sb.append(mDecimalFormatter.format(current));
- sb.append(" Hz ");
- sb.append(mPercentFormatter.format(100.0f * (current / (float) mTargetSampleRate)));
- sb.append("% ] error [");
- sb.append(average);
- sb.append(" Hz ] target ");
- sb.append(mDecimalFormatter.format(mTargetSampleRate));
-
- mLog.info(sb.toString());
- }
- }
- }
-
/**
* RTL2832 EEPROM byte array descriptor parsing class
*/
@@ -1945,4 +1494,47 @@ public abstract class RTL2832TunerController extends TunerController
}
}
+ /**
+ * Adds a sample listener. If the USB transfer processor is not currently running, it will auto-start
+ */
+ public void addListener(Listener 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 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();
+ }
+ }
}
diff --git a/src/source/tuner/usb/USBTransferProcessor.java b/src/source/tuner/usb/USBTransferProcessor.java
new file mode 100644
index 00000000..92636056
--- /dev/null
+++ b/src/source/tuner/usb/USBTransferProcessor.java
@@ -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
+ *
+ ******************************************************************************/
+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 mComplexBufferBroadcaster = new Broadcaster<>();
+ private OverflowableTransferQueue mFilledBuffers;
+ private LinkedTransferQueue mAvailableTransfers = new LinkedTransferQueue<>();
+ private LinkedTransferQueue 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()
+ {
+ @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 listener)
+ {
+ mComplexBufferBroadcaster.addListener(listener);
+
+ start();
+ }
+
+ public void removeListener(Listener 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 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();
+ }
+ }
+}