mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
126 - Significant improvement in processor and memory utilization. Refactors all USB tuner sample processing with a new USBTransferProcessor class. LibUSB transfer timeout processing is now consolidated under the TunerManager and tuners register with this singleton to auto-start or auto-stop USB bulk transfer processing. Resolves long-standing issue impacting multi-tuner use where the second and subsequent tuners may have been routinely starved for LibUSB processing time under heavy-loading. All tuner buffer threaded processing is now handled as runnables in the scheduled thread pool, eliminating tuner-dedicated processing threads. Added tuner buffer size management to prevent memory blow-out when down-stream processing is either lagging or impacted by java garbage collection operations. Both tuners and individual DDC channels now have bounded sample buffer queues that automatically enter and exit overflow mode when predefined max/reset thresholds are met. When any queue goes into overflow mode, sample buffers are simply dropped in favor of preserving program/memory stability.
This commit is contained in:
127
src/sample/OverflowableTransferQueue.java
Normal file
127
src/sample/OverflowableTransferQueue.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package sample;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class OverflowableTransferQueue<E>
|
||||
{
|
||||
public enum State {NORMAL, OVERFLOW};
|
||||
private Listener<State> mStateListener;
|
||||
|
||||
private LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
|
||||
private AtomicInteger mCounter = new AtomicInteger();
|
||||
private int mMaximumSize;
|
||||
private int mResetThreshold;
|
||||
private boolean mOverflow = false;
|
||||
|
||||
/**
|
||||
* Concurrent transfer queue that couples a higher-throughput linked transfer queue with an atomic integer for
|
||||
* monitoring queue size. When the queue size exceeds maximum size (overflow), all inbound elements are ignored
|
||||
* until the queue size is reduced to or below the reset threshold.
|
||||
*
|
||||
* @param maximumSize of the queue. Overflow state will occur once queue size exceeds this value.
|
||||
* @param resetThreshold for resetting overflow state to normal, once queue size is at or below this value.
|
||||
*/
|
||||
public OverflowableTransferQueue(int maximumSize, int resetThreshold)
|
||||
{
|
||||
mMaximumSize = maximumSize;
|
||||
mResetThreshold = resetThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the element to the queue if able to do so without exceeding maximum queue size. Otherwise, ignores
|
||||
* the element.
|
||||
*/
|
||||
public void offer(E e)
|
||||
{
|
||||
if(!mOverflow)
|
||||
{
|
||||
if(mCounter.get() > mMaximumSize)
|
||||
{
|
||||
setOverflow(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mQueue.offer(e);
|
||||
mCounter.incrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves elements from the queue into the collection up to the maximum number of elements specified
|
||||
*/
|
||||
public int drainTo(Collection<? super E> collection, int maxElements)
|
||||
{
|
||||
int drainCount = mQueue.drainTo(collection, maxElements);
|
||||
|
||||
if(drainCount > 0)
|
||||
{
|
||||
mCounter.addAndGet(-drainCount);
|
||||
}
|
||||
|
||||
return drainCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener to receive state change events
|
||||
*/
|
||||
public void setStateListener(Listener<State> listener)
|
||||
{
|
||||
mStateListener = listener;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Current state of the queue, normal or overflow
|
||||
*/
|
||||
public State getState()
|
||||
{
|
||||
return mOverflow ? State.OVERFLOW : State.NORMAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the overflow state
|
||||
*/
|
||||
private void setOverflow(boolean overflow)
|
||||
{
|
||||
mOverflow = overflow;
|
||||
|
||||
if(mStateListener != null)
|
||||
{
|
||||
mStateListener.receive(getState());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all elements from the queue and resets the internal counter to 0
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
synchronized(mQueue)
|
||||
{
|
||||
mQueue.clear();
|
||||
mCounter.set(0);
|
||||
mOverflow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
src/source/tuner/LibUSBTransferProcessor.java
Normal file
125
src/source/tuner/LibUSBTransferProcessor.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package source.tuner;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.LibUsb;
|
||||
import source.tuner.usb.USBTransferProcessor;
|
||||
import util.ThreadPool;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Runnable for executing LibUsb processing of bulk transfer buffers
|
||||
*/
|
||||
public class LibUSBTransferProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LibUSBTransferProcessor.class);
|
||||
|
||||
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
|
||||
|
||||
private List<USBTransferProcessor> mRegisteredProcessors = new CopyOnWriteArrayList<>();
|
||||
private ByteBuffer mStatusBuffer = ByteBuffer.allocateDirect(4);
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private Processor mProcessor = new Processor();
|
||||
private ScheduledFuture mProcessorFuture;
|
||||
|
||||
/**
|
||||
* Registers the transfer processor so that LibUSB timeout processing will auto-start.
|
||||
* @param processor to register
|
||||
*/
|
||||
public void registerTransferProcessor(USBTransferProcessor processor)
|
||||
{
|
||||
if(!mRegisteredProcessors.contains(processor))
|
||||
{
|
||||
mRegisteredProcessors.add(processor);
|
||||
}
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the transfer processor so that LibUSB timeout processing will auto-stop once all processors have
|
||||
* been unregistered.
|
||||
*
|
||||
* @param processor to unregister
|
||||
*/
|
||||
public void unregisterTransferProcessor(USBTransferProcessor processor)
|
||||
{
|
||||
mRegisteredProcessors.remove(processor);
|
||||
|
||||
if(mRegisteredProcessors.isEmpty())
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts processing of LibUSB timeout events
|
||||
*/
|
||||
private void start()
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
//Set periodicity to an odd multiple to avoid contention with transfer buffer receivers
|
||||
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 0L, 9L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops processing of LibUSB timeout events
|
||||
*/
|
||||
private void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mProcessorFuture != null)
|
||||
{
|
||||
mProcessorFuture.cancel(true);
|
||||
mProcessorFuture = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable to invoke LibUSB timeout handler. All downstream completed transfer processing will occur on this
|
||||
* thread instance.
|
||||
*/
|
||||
class Processor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, mStatusBuffer.asIntBuffer());
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("Error processing timeout events for LibUSB - error code:" + result);
|
||||
}
|
||||
|
||||
mStatusBuffer.rewind();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package source.tuner;
|
||||
|
||||
@@ -25,6 +26,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Buffer;
|
||||
import sample.Listener;
|
||||
import sample.OverflowableTransferQueue;
|
||||
import sample.complex.Complex;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.ComplexSource;
|
||||
@@ -35,24 +37,27 @@ import source.tuner.frequency.IFrequencyChangeProcessor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class TunerChannelSource extends ComplexSource
|
||||
implements IFrequencyChangeProcessor, Listener<ComplexBuffer>
|
||||
public class TunerChannelSource extends ComplexSource implements IFrequencyChangeProcessor, Listener<ComplexBuffer>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger(TunerChannelSource.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
|
||||
|
||||
//Maximum number of filled buffers for the blocking queue
|
||||
private static final int BUFFER_MAX_CAPACITY = 300;
|
||||
|
||||
//Threshold for resetting buffer overflow condition
|
||||
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 200;
|
||||
|
||||
private static int CHANNEL_RATE = 48000;
|
||||
private static int CHANNEL_PASS_FREQUENCY = 12000;
|
||||
|
||||
private LinkedTransferQueue<ComplexBuffer> mBuffer =
|
||||
new LinkedTransferQueue<ComplexBuffer>();
|
||||
private OverflowableTransferQueue<ComplexBuffer> mBuffer;
|
||||
|
||||
private Tuner mTuner;
|
||||
private TunerChannel mTunerChannel;
|
||||
private Oscillator mMixer;
|
||||
@@ -69,6 +74,7 @@ public class TunerChannelSource extends ComplexSource
|
||||
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private boolean mExpended = false;
|
||||
private int mCount;
|
||||
|
||||
/**
|
||||
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
|
||||
@@ -87,26 +93,37 @@ public class TunerChannelSource extends ComplexSource
|
||||
* accept the decimation processing task
|
||||
* @throws SourceException if the tuner has an issue providing IQ samples
|
||||
*/
|
||||
public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel)
|
||||
throws RejectedExecutionException, SourceException
|
||||
public TunerChannelSource(Tuner tuner, TunerChannel tunerChannel) throws RejectedExecutionException, SourceException
|
||||
{
|
||||
mTuner = tuner;
|
||||
mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
|
||||
|
||||
mTunerChannel = tunerChannel;
|
||||
|
||||
mTuner.getTunerController().addListener((IFrequencyChangeProcessor) this);
|
||||
mTunerFrequency = mTuner.getTunerController().getFrequency();
|
||||
|
||||
mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
|
||||
mBuffer.setStateListener(new Listener<OverflowableTransferQueue.State>()
|
||||
{
|
||||
@Override
|
||||
public void receive(OverflowableTransferQueue.State state)
|
||||
{
|
||||
if(state == OverflowableTransferQueue.State.NORMAL)
|
||||
{
|
||||
mLog.debug("Channel [" + mTunerFrequency + "] buffer overflow - temporary pause until processing catches up");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Channel [" + mTunerFrequency + "] buffer overflow cleared - resuming normal processing");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Setup the frequency translator to the current source frequency */
|
||||
long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency();
|
||||
|
||||
mMixer = new Oscillator(frequencyOffset,
|
||||
mTuner.getTunerController().getSampleRate());
|
||||
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
|
||||
|
||||
/* Fire a sample rate change event to setup the decimation chain */
|
||||
frequencyChanged(new FrequencyChangeEvent(
|
||||
Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
|
||||
mTuner.getTunerController().getSampleRate()));
|
||||
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate()));
|
||||
}
|
||||
|
||||
|
||||
@@ -114,23 +131,22 @@ public class TunerChannelSource extends ComplexSource
|
||||
{
|
||||
if(mExpended)
|
||||
{
|
||||
throw new IllegalStateException("Attempt to re-start an expended "
|
||||
+ "tuner channel source. TunerChannelSource objects can only "
|
||||
+ "be used once. ");
|
||||
throw new IllegalStateException("Attempt to re-start an expended tuner channel source. TunerChannelSource" +
|
||||
" objects can only be used once. ");
|
||||
}
|
||||
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
/* Schedule the decimation task to run every 10 ms (100 iterations/second) */
|
||||
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor,
|
||||
0, 10, TimeUnit.MILLISECONDS);
|
||||
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
|
||||
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
|
||||
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
|
||||
|
||||
/* Finally, register to receive samples from the tuner */
|
||||
mTuner.addListener((Listener<ComplexBuffer>) this);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Attempt to start() and already running tuner channel source was ignored");
|
||||
mLog.warn("Attempt to start() an already running tuner channel source was ignored");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,14 +187,6 @@ public class TunerChannelSource extends ComplexSource
|
||||
{
|
||||
/* Tell the tuner to release/unregister our resources */
|
||||
mTuner.getTunerController().removeListener(this);
|
||||
// mTuner = null;
|
||||
// mTunerChannel = null;
|
||||
// mBuffer = null;
|
||||
// mFrequencyChangeProcessor = null;
|
||||
// mListener = null;
|
||||
// mMixer = null;
|
||||
// mDecimationFilter.dispose();
|
||||
// mDecimationFilter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +205,7 @@ public class TunerChannelSource extends ComplexSource
|
||||
{
|
||||
if(mRunning.get())
|
||||
{
|
||||
mBuffer.add(buffer);
|
||||
mBuffer.offer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,10 +256,8 @@ public class TunerChannelSource extends ComplexSource
|
||||
|
||||
if(mFrequencyChangeProcessor != null)
|
||||
{
|
||||
mFrequencyChangeProcessor.frequencyChanged(
|
||||
new FrequencyChangeEvent(
|
||||
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
|
||||
mChannelFrequencyCorrection));
|
||||
mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
|
||||
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
@@ -262,13 +268,8 @@ public class TunerChannelSource extends ComplexSource
|
||||
mMixer.setSampleRate(sampleRate);
|
||||
|
||||
/* Get new decimation filter */
|
||||
mDecimationFilter = FilterFactory
|
||||
.getDecimationFilter(sampleRate,
|
||||
CHANNEL_RATE,
|
||||
1, //Order
|
||||
CHANNEL_PASS_FREQUENCY,
|
||||
60, //dB attenuation
|
||||
WindowType.HAMMING);
|
||||
mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
|
||||
CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
|
||||
|
||||
/* re-add the original output listener */
|
||||
mDecimationFilter.setListener(mListener);
|
||||
@@ -328,48 +329,46 @@ public class TunerChannelSource extends ComplexSource
|
||||
{
|
||||
if(mProcessing)
|
||||
{
|
||||
{
|
||||
mBuffer.drainTo(mSampleBuffers, 8);
|
||||
mBuffer.drainTo(mSampleBuffers, 20);
|
||||
|
||||
for(Buffer buffer : mSampleBuffers)
|
||||
{
|
||||
for(Buffer buffer : mSampleBuffers)
|
||||
{
|
||||
/* Check to see if we've been shutdown */
|
||||
if(!mProcessing)
|
||||
{
|
||||
mBuffer.clear();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
if(!mProcessing)
|
||||
{
|
||||
mBuffer.clear();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
|
||||
/* We make a copy of the buffer so that we don't affect
|
||||
* anyone else that is using the same buffer, like other
|
||||
* channels or the spectral display */
|
||||
float[] translated = new float[samples.length];
|
||||
|
||||
float[] translated = new float[samples.length];
|
||||
|
||||
/* Perform frequency translation */
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
mMixer.rotate();
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
mMixer.rotate();
|
||||
|
||||
translated[x] = Complex.multiplyInphase(
|
||||
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
|
||||
translated[x] = Complex.multiplyInphase(
|
||||
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
|
||||
|
||||
translated[x + 1] = Complex.multiplyQuadrature(
|
||||
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
|
||||
}
|
||||
translated[x + 1] = Complex.multiplyQuadrature(
|
||||
samples[x], samples[x + 1], mMixer.inphase(), mMixer.quadrature());
|
||||
}
|
||||
|
||||
if(mProcessing)
|
||||
{
|
||||
final ComplexPrimeCICDecimate filter = mDecimationFilter;
|
||||
filter.receive(new ComplexBuffer(translated));
|
||||
}
|
||||
if(mProcessing)
|
||||
{
|
||||
final ComplexPrimeCICDecimate filter = mDecimationFilter;
|
||||
filter.receive(new ComplexBuffer(translated));
|
||||
}
|
||||
}
|
||||
|
||||
mSampleBuffers.clear();
|
||||
}
|
||||
|
||||
mSampleBuffers.clear();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,144 +1,155 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package source.tuner.airspy;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2015 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
|
||||
import dsp.filter.dc.DCRemovalFilter_RB;
|
||||
import dsp.filter.hilbert.HilbertTransform;
|
||||
import sample.adapter.ISampleAdapter;
|
||||
|
||||
public class AirspySampleAdapter implements ISampleAdapter
|
||||
{
|
||||
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
|
||||
|
||||
private boolean mSamplePacking = false;
|
||||
private static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
|
||||
|
||||
/**
|
||||
* Adapter to translate byte buffers received from the airspy tuner into
|
||||
* float buffers for processing.
|
||||
*/
|
||||
public AirspySampleAdapter()
|
||||
{
|
||||
}
|
||||
private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
|
||||
private HilbertTransform mHilbertTransform = new HilbertTransform();
|
||||
private boolean mSamplePacking = false;
|
||||
|
||||
/**
|
||||
* Sample packing places two 12-bit samples into 3 bytes when enabled or
|
||||
* places two 12-bit samples into 4 bytes when disabled.
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
public void setSamplePacking( boolean enabled )
|
||||
{
|
||||
mSamplePacking = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] convert( byte[] samples )
|
||||
{
|
||||
if( mSamplePacking )
|
||||
{
|
||||
return convertPacked( samples );
|
||||
}
|
||||
else
|
||||
{
|
||||
return convertUnpacked( samples );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the byte array containing unsigned 12-bit short values into
|
||||
* signed float values in the range -1 to 1;
|
||||
*
|
||||
* @param data - byte array of unsigned 16-bit values
|
||||
* @return converted float values
|
||||
*/
|
||||
private float[] convertUnpacked( byte[] data )
|
||||
{
|
||||
float[] samples = new float[ data.length / 2 ];
|
||||
/**
|
||||
* Adapter to translate byte buffers received from the airspy tuner into
|
||||
* float buffers for processing.
|
||||
*/
|
||||
public AirspySampleAdapter()
|
||||
{
|
||||
}
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
for( int x = 0; x < data.length; x += 2 )
|
||||
{
|
||||
samples[ pointer++ ] = scale( ( data[ x ] & 0xFF ) |
|
||||
( data[ x + 1 ] << 8 ) );
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts every 3 bytes containing a pair of 12-bit unsigned values into
|
||||
* a pair of float values in the range -1 to 1;
|
||||
*
|
||||
* @param data - byte array of unsigned 12-bit values
|
||||
* @return converted float values
|
||||
*/
|
||||
private float[] convertPacked( byte[] data1 )
|
||||
{
|
||||
byte[] data = new byte[ data1.length ];
|
||||
/**
|
||||
* Sample packing places two 12-bit samples into 3 bytes when enabled or
|
||||
* places two 12-bit samples into 4 bytes when disabled.
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
public void setSamplePacking(boolean enabled)
|
||||
{
|
||||
mSamplePacking = enabled;
|
||||
}
|
||||
|
||||
//Convert big-endian to little-endian
|
||||
for( int x = 0; x < data1.length; x += 4 )
|
||||
{
|
||||
data[ x ] = data1[ x + 3 ];
|
||||
data[ x + 1 ] = data1[ x + 2 ];
|
||||
data[ x + 2 ] = data1[ x + 1 ];
|
||||
data[ x + 3 ] = data1[ x ];
|
||||
}
|
||||
|
||||
int count = (int)( (float)data.length / 1.5f );
|
||||
@Override
|
||||
public float[] convert(byte[] samples)
|
||||
{
|
||||
float[] realSamples;
|
||||
|
||||
if(mSamplePacking)
|
||||
{
|
||||
realSamples = convertPacked(samples);
|
||||
}
|
||||
else
|
||||
{
|
||||
realSamples = convertUnpacked(samples);
|
||||
}
|
||||
|
||||
mDCFilter.filter(realSamples);
|
||||
|
||||
return mHilbertTransform.filter(realSamples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the byte array containing unsigned 12-bit short values into
|
||||
* signed float values in the range -1 to 1;
|
||||
*
|
||||
* @param data - byte array of unsigned 16-bit values
|
||||
* @return converted float values
|
||||
*/
|
||||
private float[] convertUnpacked(byte[] data)
|
||||
{
|
||||
float[] samples = new float[data.length / 2];
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
for(int x = 0; x < data.length; x += 2)
|
||||
{
|
||||
samples[pointer++] = scale((data[x] & 0xFF) |
|
||||
(data[x + 1] << 8));
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts every 3 bytes containing a pair of 12-bit unsigned values into
|
||||
* a pair of float values in the range -1 to 1;
|
||||
*
|
||||
* @param data1 - byte array of unsigned 12-bit values
|
||||
* @return converted float values
|
||||
*/
|
||||
private float[] convertPacked(byte[] data1)
|
||||
{
|
||||
byte[] data = new byte[data1.length];
|
||||
|
||||
//Convert big-endian to little-endian
|
||||
for(int x = 0; x < data1.length; x += 4)
|
||||
{
|
||||
data[x] = data1[x + 3];
|
||||
data[x + 1] = data1[x + 2];
|
||||
data[x + 2] = data1[x + 1];
|
||||
data[x + 3] = data1[x];
|
||||
}
|
||||
|
||||
int count = (int) ((float) data.length / 1.5f);
|
||||
|
||||
/* Ensure we have an even number of samples */
|
||||
if( count % 2 == 1 )
|
||||
{
|
||||
count--;
|
||||
}
|
||||
if(count % 2 == 1)
|
||||
{
|
||||
count--;
|
||||
}
|
||||
|
||||
int bytes = (int)((float)count * 1.5f);
|
||||
int bytes = (int) ((float) count * 1.5f);
|
||||
|
||||
float[] samples = new float[ count ];
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
int first;
|
||||
int second;
|
||||
|
||||
for( int x = 0; x < bytes; x += 3 )
|
||||
{
|
||||
first = ( ( data[ x ] << 4 ) & 0xFF0 ) |
|
||||
( ( data[ x + 1 ] >> 4 ) & 0xF );
|
||||
float[] samples = new float[count];
|
||||
|
||||
samples[ pointer++ ] = scale( first );
|
||||
int pointer = 0;
|
||||
|
||||
second = ( ( data[ x + 1 ] << 8 ) & 0xF00 ) |
|
||||
( data[ x + 2 ] & 0xFF );
|
||||
|
||||
samples[ pointer++ ] = scale( second );
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
int first;
|
||||
int second;
|
||||
|
||||
/**
|
||||
* Converts unsigned 12-bit values to signed 12-bit values and then scales
|
||||
* the signed value to a signed float value in range: -1.0 : +1.0
|
||||
*/
|
||||
public static float scale( int value )
|
||||
{
|
||||
return (float)(( value & 0xFFF ) - 2048 ) * SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
}
|
||||
for(int x = 0; x < bytes; x += 3)
|
||||
{
|
||||
first = ((data[x] << 4) & 0xFF0) |
|
||||
((data[x + 1] >> 4) & 0xF);
|
||||
|
||||
samples[pointer++] = scale(first);
|
||||
|
||||
second = ((data[x + 1] << 8) & 0xF00) |
|
||||
(data[x + 2] & 0xFF);
|
||||
|
||||
samples[pointer++] = scale(second);
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts unsigned 12-bit values to signed 12-bit values and then scales
|
||||
* the signed value to a signed float value in range: -1.0 : +1.0
|
||||
*/
|
||||
public static float scale(int value)
|
||||
{
|
||||
return (float) ((value & 0xFFF) - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import sample.complex.ComplexBuffer;
|
||||
import source.SourceException;
|
||||
import source.tuner.TunerController;
|
||||
import source.tuner.configuration.TunerConfiguration;
|
||||
import source.tuner.usb.USBTransferProcessor;
|
||||
import util.ThreadPool;
|
||||
|
||||
import javax.usb.UsbException;
|
||||
@@ -93,21 +94,11 @@ public class AirspyTunerController extends TunerController
|
||||
public static final AirspySampleRate DEFAULT_SAMPLE_RATE =
|
||||
new AirspySampleRate(0, 10000000, "10.00 MHz");
|
||||
|
||||
public static final long USB_TIMEOUT_MS = 2000l; //milliseconds
|
||||
public static final byte USB_ENDPOINT = (byte) 0x81;
|
||||
public static final byte USB_INTERFACE = (byte) 0x0;
|
||||
public static final int TRANSFER_BUFFER_POOL_SIZE = 16;
|
||||
|
||||
public static final byte USB_REQUEST_IN =
|
||||
(byte) (LibUsb.ENDPOINT_IN |
|
||||
LibUsb.REQUEST_TYPE_VENDOR |
|
||||
LibUsb.RECIPIENT_DEVICE);
|
||||
|
||||
public static final byte USB_REQUEST_OUT =
|
||||
(byte) (LibUsb.ENDPOINT_OUT |
|
||||
LibUsb.REQUEST_TYPE_VENDOR |
|
||||
LibUsb.RECIPIENT_DEVICE);
|
||||
|
||||
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
|
||||
private static final byte USB_INTERFACE = (byte) 0x0;
|
||||
private static final int USB_TRANSFER_BUFFER_SIZE = 262144;
|
||||
private static final byte USB_REQUEST_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
private static final byte USB_REQUEST_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
public static final DecimalFormat MHZ_FORMATTER = new DecimalFormat("#.00 MHz");
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AirspyTunerController.class);
|
||||
@@ -115,17 +106,11 @@ public class AirspyTunerController extends TunerController
|
||||
private Device mDevice;
|
||||
private DeviceHandle mDeviceHandle;
|
||||
|
||||
private LinkedTransferQueue<byte[]> mFilledBuffers = new LinkedTransferQueue<byte[]>();
|
||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
private int mBufferSize = 262144;
|
||||
private BufferProcessor mBufferProcessor = new BufferProcessor();
|
||||
private AirspySampleAdapter mSampleAdapter = new AirspySampleAdapter();
|
||||
private DCRemovalFilter_RB mDCFilter = new DCRemovalFilter_RB(0.01f);
|
||||
private HilbertTransform mHilbertTransform = new HilbertTransform();
|
||||
|
||||
private AirspyDeviceInformation mDeviceInfo;
|
||||
private List<AirspySampleRate> mSampleRates = new ArrayList<>();
|
||||
private int mSampleRate = 0;
|
||||
private USBTransferProcessor mUSBTransferProcessor;
|
||||
|
||||
public AirspyTunerController(Device device) throws SourceException
|
||||
{
|
||||
@@ -140,7 +125,7 @@ public class AirspyTunerController extends TunerController
|
||||
|
||||
int result = LibUsb.open(mDevice, mDeviceHandle);
|
||||
|
||||
if(result != 0)
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
if(result == LibUsb.ERROR_ACCESS)
|
||||
{
|
||||
@@ -201,6 +186,11 @@ public class AirspyTunerController extends TunerController
|
||||
{
|
||||
mLog.error("Setting sample rate is not supported by firmware", e);
|
||||
}
|
||||
|
||||
String deviceName = "Airspy " + getDeviceInfo().getSerialNumber();
|
||||
|
||||
mUSBTransferProcessor = new USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
|
||||
USB_TRANSFER_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1205,250 +1195,32 @@ public class AirspyTunerController extends TunerController
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a sample listener. If the buffer processing thread is
|
||||
* not currently running, starts it running in a new thread.
|
||||
* Adds the IQ buffer listener and automatically starts buffer transfer processing, if not already started.
|
||||
*/
|
||||
public void addListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
synchronized(mComplexBufferBroadcaster)
|
||||
if(mUSBTransferProcessor != null)
|
||||
{
|
||||
mComplexBufferBroadcaster.addListener(listener);
|
||||
|
||||
if(mBufferProcessor == null || !mBufferProcessor.isRunning())
|
||||
{
|
||||
mBufferProcessor = new BufferProcessor();
|
||||
|
||||
Thread thread = new Thread(mBufferProcessor);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("Airspy Buffer Processor");
|
||||
|
||||
thread.start();
|
||||
}
|
||||
mUSBTransferProcessor.addListener(listener);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Couldn't add IQ buffer listener to Airspy tuner - processor is null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the sample listener. If this is the last registered listener,
|
||||
* shuts down the buffer processing thread.
|
||||
* Removes the IQ buffer listener and stops buffer transfer processing if there are no more listeners.
|
||||
*/
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
synchronized(mComplexBufferBroadcaster)
|
||||
if(mUSBTransferProcessor != null)
|
||||
{
|
||||
mComplexBufferBroadcaster.removeListener(listener);
|
||||
|
||||
if(!mComplexBufferBroadcaster.hasListeners())
|
||||
{
|
||||
mBufferProcessor.stop();
|
||||
}
|
||||
mUSBTransferProcessor.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer processing thread. Fetches samples from the Airspy Tuner and
|
||||
* dispatches them to all registered listeners
|
||||
*/
|
||||
public class BufferProcessor implements Runnable, TransferCallback
|
||||
{
|
||||
private ScheduledFuture<?> mSampleDispatcherTask;
|
||||
private LinkedTransferQueue<Transfer> mAvailableTransfers;
|
||||
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private ByteBuffer mLibUsbHandlerStatus;
|
||||
private boolean mCancel = false;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
else
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
prepareTransfers();
|
||||
|
||||
mSampleDispatcherTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferDispatcher(),
|
||||
20, 20, TimeUnit.MILLISECONDS);
|
||||
|
||||
mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
|
||||
|
||||
List<Transfer> transfers = new ArrayList<>();
|
||||
|
||||
mCancel = false;
|
||||
|
||||
while(mRunning.get())
|
||||
{
|
||||
mAvailableTransfers.drainTo(transfers);
|
||||
|
||||
for(Transfer transfer : transfers)
|
||||
{
|
||||
int result = LibUsb.submitTransfer(transfer);
|
||||
|
||||
if(result == LibUsb.SUCCESS)
|
||||
{
|
||||
mTransfersInProgress.add(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("error submitting transfer [" + LibUsb.errorName(result) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
||||
null, USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer());
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("error handling events for libusb");
|
||||
}
|
||||
|
||||
transfers.clear();
|
||||
|
||||
mLibUsbHandlerStatus.rewind();
|
||||
}
|
||||
|
||||
if(mCancel)
|
||||
{
|
||||
for(Transfer transfer : mTransfersInProgress)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(null,
|
||||
USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer());
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("error handling events for libusb during cancel");
|
||||
}
|
||||
|
||||
mLibUsbHandlerStatus.rewind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the sample fetching thread
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mCancel = true;
|
||||
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mSampleDispatcherTask != null)
|
||||
{
|
||||
mSampleDispatcherTask.cancel(true);
|
||||
mFilledBuffers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this thread is running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTransfer(Transfer transfer)
|
||||
{
|
||||
mTransfersInProgress.remove(transfer);
|
||||
|
||||
switch(transfer.status())
|
||||
{
|
||||
case LibUsb.TRANSFER_COMPLETED:
|
||||
case LibUsb.TRANSFER_STALL:
|
||||
if(transfer.actualLength() > 0)
|
||||
{
|
||||
ByteBuffer buffer = transfer.buffer();
|
||||
|
||||
byte[] data = new byte[transfer.actualLength()];
|
||||
|
||||
buffer.get(data);
|
||||
|
||||
buffer.rewind();
|
||||
|
||||
if(isRunning())
|
||||
{
|
||||
mFilledBuffers.add(data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LibUsb.TRANSFER_CANCELLED:
|
||||
break;
|
||||
default:
|
||||
/* unexpected error */
|
||||
mLog.error("transfer error [" +
|
||||
getTransferStatus(transfer.status()) +
|
||||
"] transferred actual: " + transfer.actualLength());
|
||||
}
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares (allocates) a set of transfer buffers for use in
|
||||
* transferring data from the tuner via the bulk interface
|
||||
*/
|
||||
private void prepareTransfers() throws LibUsbException
|
||||
{
|
||||
if(mAvailableTransfers == null)
|
||||
{
|
||||
mAvailableTransfers = new LinkedTransferQueue<>();
|
||||
|
||||
for(int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++)
|
||||
{
|
||||
Transfer transfer = LibUsb.allocTransfer();
|
||||
|
||||
if(transfer == null)
|
||||
{
|
||||
throw new LibUsbException("couldn't allocate transfer",
|
||||
LibUsb.ERROR_NO_MEM);
|
||||
}
|
||||
|
||||
final ByteBuffer buffer =
|
||||
ByteBuffer.allocateDirect(mBufferSize);
|
||||
|
||||
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_ENDPOINT,
|
||||
buffer, BufferProcessor.this, "Buffer", USB_TIMEOUT_MS);
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
|
||||
* array and broadcasts the array to all registered listeners
|
||||
*/
|
||||
public class BufferDispatcher implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<byte[]> buffers = new ArrayList<>();
|
||||
|
||||
mFilledBuffers.drainTo(buffers);
|
||||
|
||||
for(byte[] buffer : buffers)
|
||||
{
|
||||
float[] realSamples = mSampleAdapter.convert(buffer);
|
||||
|
||||
realSamples = mDCFilter.filter(realSamples);
|
||||
|
||||
float[] quadratureSamples = mHilbertTransform.filter(realSamples);
|
||||
|
||||
mComplexBufferBroadcaster.broadcast(
|
||||
new ComplexBuffer(quadratureSamples));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error during Airspy buffer dispatching", e);
|
||||
}
|
||||
mLog.error("Couldn't remove IQ buffer listener from Airspy tuner - processor is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,27 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package source.tuner.fcd.proplusV2;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Device;
|
||||
import org.usb4java.DeviceDescriptor;
|
||||
|
||||
import source.SourceException;
|
||||
import source.tuner.MixerTunerType;
|
||||
import source.tuner.TunerClass;
|
||||
@@ -33,197 +30,200 @@ import source.tuner.configuration.TunerConfiguration;
|
||||
import source.tuner.fcd.FCDCommand;
|
||||
import source.tuner.fcd.FCDTunerController;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class FCD2TunerController extends FCDTunerController
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( FCD2TunerController.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FCD2TunerController.class);
|
||||
|
||||
public static final int MINIMUM_TUNABLE_FREQUENCY = 150000;
|
||||
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
|
||||
public static final int SAMPLE_RATE = 192000;
|
||||
|
||||
public FCD2TunerController( Device device, DeviceDescriptor descriptor )
|
||||
{
|
||||
super( device, descriptor,
|
||||
(int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(),
|
||||
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY );
|
||||
}
|
||||
public static final int MINIMUM_TUNABLE_FREQUENCY = 150000;
|
||||
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
|
||||
public static final int SAMPLE_RATE = 192000;
|
||||
|
||||
public void init() throws SourceException
|
||||
{
|
||||
super.init();
|
||||
public FCD2TunerController(Device device, DeviceDescriptor descriptor)
|
||||
{
|
||||
super(device, descriptor,
|
||||
(int)MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat().getSampleRate(),
|
||||
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
|
||||
}
|
||||
|
||||
mFrequencyController.setSampleRate( SAMPLE_RATE );
|
||||
public void init() throws SourceException
|
||||
{
|
||||
super.init();
|
||||
|
||||
try
|
||||
{
|
||||
setFCDMode( Mode.APPLICATION );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
throw new SourceException( "error setting Mode to APPLICATION", e );
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentSampleRate()
|
||||
{
|
||||
return SAMPLE_RATE;
|
||||
}
|
||||
mFrequencyController.setSampleRate(SAMPLE_RATE);
|
||||
|
||||
@Override
|
||||
try
|
||||
{
|
||||
setFCDMode(Mode.APPLICATION);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("error setting Mode to APPLICATION", e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentSampleRate()
|
||||
{
|
||||
return SAMPLE_RATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunerClass getTunerClass()
|
||||
{
|
||||
return TunerClass.FUNCUBE_DONGLE_PRO_PLUS;
|
||||
return TunerClass.FUNCUBE_DONGLE_PRO_PLUS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public TunerType getTunerType()
|
||||
{
|
||||
return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
|
||||
return TunerType.FUNCUBE_DONGLE_PRO_PLUS;
|
||||
}
|
||||
|
||||
public void setLNAGain( boolean enabled ) throws SourceException
|
||||
{
|
||||
try
|
||||
{
|
||||
send( FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0 );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
throw new SourceException( "error while setting LNA Gain", e );
|
||||
}
|
||||
}
|
||||
|
||||
public void setMixerGain( boolean enabled ) throws SourceException
|
||||
{
|
||||
try
|
||||
{
|
||||
send( FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0 );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
throw new SourceException( "error while setting Mixer Gain", e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply( TunerConfiguration config ) throws SourceException
|
||||
public void setLNAGain(boolean enabled) throws SourceException
|
||||
{
|
||||
if( config instanceof FCD2TunerConfiguration )
|
||||
{
|
||||
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
|
||||
|
||||
setFrequencyCorrection( fcd2.getFrequencyCorrection() );
|
||||
setFrequency( fcd2.getFrequency() );
|
||||
setLNAGain( fcd2.getGainLNA() );
|
||||
setMixerGain( fcd2.getGainMixer() );
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency( fcd2.getFrequency() );
|
||||
}
|
||||
catch( SourceException se )
|
||||
{
|
||||
//Do nothing, we couldn't set the frequency
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_LNA_GAIN, enabled ? 1 : 0);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("error while setting LNA Gain", e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getDCCorrection()
|
||||
{
|
||||
int dcCorrection = -999;
|
||||
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = send( FCDCommand.APP_GET_DC_CORRECTION );
|
||||
|
||||
buffer.order( ByteOrder.LITTLE_ENDIAN );
|
||||
|
||||
return buffer.getInt( 2 );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "error getting dc correction value", e );
|
||||
}
|
||||
|
||||
return dcCorrection;
|
||||
}
|
||||
|
||||
public void setDCCorrection( int value )
|
||||
{
|
||||
try
|
||||
{
|
||||
send( FCDCommand.APP_SET_DC_CORRECTION, value );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "error setting dc correction to [" + value + "]", e );
|
||||
}
|
||||
}
|
||||
|
||||
public int getIQCorrection()
|
||||
{
|
||||
int iqCorrection = -999;
|
||||
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = send( FCDCommand.APP_GET_IQ_CORRECTION );
|
||||
|
||||
buffer.order( ByteOrder.LITTLE_ENDIAN );
|
||||
|
||||
return buffer.getInt( 2 );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "error reading IQ correction value", e );
|
||||
}
|
||||
|
||||
return iqCorrection;
|
||||
}
|
||||
|
||||
public void setIQCorrection( int value )
|
||||
{
|
||||
try
|
||||
{
|
||||
send( FCDCommand.APP_SET_IQ_CORRECTION, value );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "error setting IQ correction to [" + value + "]", e );
|
||||
}
|
||||
}
|
||||
|
||||
public enum Block
|
||||
{
|
||||
CELLULAR_BAND_BLOCKED( "Blocked" ),
|
||||
NO_BAND_BLOCK( "Unblocked" ),
|
||||
UNKNOWN( "Unknown" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private Block( String label )
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public static Block getBlock( String block )
|
||||
{
|
||||
Block retVal = UNKNOWN;
|
||||
public void setMixerGain(boolean enabled) throws SourceException
|
||||
{
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new SourceException("error while setting Mixer Gain", e);
|
||||
}
|
||||
}
|
||||
|
||||
if( block.equalsIgnoreCase( "No blk" ) )
|
||||
{
|
||||
retVal = NO_BAND_BLOCK;
|
||||
}
|
||||
else if( block.equalsIgnoreCase( "Cell blk" ) )
|
||||
{
|
||||
retVal = CELLULAR_BAND_BLOCKED;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void apply(TunerConfiguration config) throws SourceException
|
||||
{
|
||||
if(config instanceof FCD2TunerConfiguration)
|
||||
{
|
||||
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
|
||||
|
||||
setFrequencyCorrection(fcd2.getFrequencyCorrection());
|
||||
setFrequency(fcd2.getFrequency());
|
||||
setLNAGain(fcd2.getGainLNA());
|
||||
setMixerGain(fcd2.getGainMixer());
|
||||
|
||||
try
|
||||
{
|
||||
setFrequency(fcd2.getFrequency());
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
//Do nothing, we couldn't set the frequency
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getDCCorrection()
|
||||
{
|
||||
int dcCorrection = -999;
|
||||
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_DC_CORRECTION);
|
||||
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
return buffer.getInt(2);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error getting dc correction value", e);
|
||||
}
|
||||
|
||||
return dcCorrection;
|
||||
}
|
||||
|
||||
public void setDCCorrection(int value)
|
||||
{
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_DC_CORRECTION, value);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error setting dc correction to [" + value + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getIQCorrection()
|
||||
{
|
||||
int iqCorrection = -999;
|
||||
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_IQ_CORRECTION);
|
||||
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
return buffer.getInt(2);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error reading IQ correction value", e);
|
||||
}
|
||||
|
||||
return iqCorrection;
|
||||
}
|
||||
|
||||
public void setIQCorrection(int value)
|
||||
{
|
||||
try
|
||||
{
|
||||
send(FCDCommand.APP_SET_IQ_CORRECTION, value);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error setting IQ correction to [" + value + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Block
|
||||
{
|
||||
CELLULAR_BAND_BLOCKED("Blocked"),
|
||||
NO_BAND_BLOCK("Unblocked"),
|
||||
UNKNOWN("Unknown");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private Block(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public static Block getBlock(String block)
|
||||
{
|
||||
Block retVal = UNKNOWN;
|
||||
|
||||
if(block.equalsIgnoreCase("No blk"))
|
||||
{
|
||||
retVal = NO_BAND_BLOCK;
|
||||
}
|
||||
else if(block.equalsIgnoreCase("Cell blk"))
|
||||
{
|
||||
retVal = CELLULAR_BAND_BLOCKED;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* Ported from libhackrf at:
|
||||
* https://github.com/mossmann/hackrf/tree/master/host/libhackrf
|
||||
@@ -61,8 +61,6 @@ import org.usb4java.DeviceDescriptor;
|
||||
import org.usb4java.DeviceHandle;
|
||||
import org.usb4java.LibUsb;
|
||||
import org.usb4java.LibUsbException;
|
||||
import org.usb4java.Transfer;
|
||||
import org.usb4java.TransferCallback;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.adapter.ByteSampleAdapter;
|
||||
@@ -70,38 +68,25 @@ import sample.complex.ComplexBuffer;
|
||||
import source.SourceException;
|
||||
import source.tuner.TunerController;
|
||||
import source.tuner.configuration.TunerConfiguration;
|
||||
import util.ThreadPool;
|
||||
import source.tuner.usb.USBTransferProcessor;
|
||||
|
||||
import javax.usb.UsbException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
||||
public class HackRFTunerController extends TunerController
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger(HackRFTunerController.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(HackRFTunerController.class);
|
||||
|
||||
public final static long USB_TIMEOUT_US = 1000000l; //uSeconds
|
||||
public static final byte USB_ENDPOINT = (byte) 0x81;
|
||||
public static final byte USB_INTERFACE = (byte) 0x0;
|
||||
public static final byte USB_ENDPOINT = (byte)0x81;
|
||||
public static final byte USB_INTERFACE = (byte)0x0;
|
||||
public static final int USB_TRANSFER_BUFFER_SIZE = 262144;
|
||||
|
||||
public static final byte REQUEST_TYPE_IN =
|
||||
(byte) (LibUsb.ENDPOINT_IN |
|
||||
LibUsb.REQUEST_TYPE_VENDOR |
|
||||
LibUsb.RECIPIENT_DEVICE);
|
||||
|
||||
public static final byte REQUEST_TYPE_OUT =
|
||||
(byte) (LibUsb.ENDPOINT_OUT |
|
||||
LibUsb.REQUEST_TYPE_VENDOR |
|
||||
LibUsb.RECIPIENT_DEVICE);
|
||||
public static final byte REQUEST_TYPE_IN = (byte)(LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
public static final byte REQUEST_TYPE_OUT = (byte)(LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR | LibUsb.RECIPIENT_DEVICE);
|
||||
|
||||
public static final long MIN_FREQUENCY = 10000000l;
|
||||
public static final long MAX_FREQUENCY = 6000000000l;
|
||||
@@ -109,18 +94,10 @@ public class HackRFTunerController extends TunerController
|
||||
public static final double USABLE_BANDWIDTH_PERCENT = 0.95;
|
||||
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
|
||||
|
||||
public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
|
||||
|
||||
private LinkedTransferQueue<byte[]> mFilledBuffers =
|
||||
new LinkedTransferQueue<byte[]>();
|
||||
|
||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
|
||||
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
|
||||
private BufferProcessor mBufferProcessor = new BufferProcessor();
|
||||
private USBTransferProcessor mUSBTransferProcessor;
|
||||
|
||||
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
|
||||
public int mBufferSize = 262144;
|
||||
private boolean mAmplifierEnabled = false;
|
||||
|
||||
private Device mDevice;
|
||||
@@ -167,6 +144,20 @@ public class HackRFTunerController extends TunerController
|
||||
throw new SourceException("HackRF Tuner Controller - couldn't "
|
||||
+ "claim USB interface or get endpoint or pipe", e);
|
||||
}
|
||||
|
||||
String name;
|
||||
|
||||
try
|
||||
{
|
||||
name = "HackRF " + getSerial().getSerialNumber();
|
||||
}
|
||||
catch(UsbException ue)
|
||||
{
|
||||
//Do nothing, we couldn't determine the serial number
|
||||
name = "HackRF - Unidentified Serial";
|
||||
}
|
||||
|
||||
mUSBTransferProcessor = new USBTransferProcessor(name, mDeviceHandle, mSampleAdapter, USB_TRANSFER_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,7 +206,7 @@ public class HackRFTunerController extends TunerController
|
||||
*/
|
||||
public BoardID getBoardID() throws UsbException
|
||||
{
|
||||
int id = readByte(Request.BOARD_ID_READ, (byte) 0, (byte) 0, false);
|
||||
int id = readByte(Request.BOARD_ID_READ, (byte)0, (byte)0, false);
|
||||
|
||||
return BoardID.lookup(id);
|
||||
}
|
||||
@@ -317,8 +308,8 @@ public class HackRFTunerController extends TunerController
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(8);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
int mhz = (int) (frequency / 1E6);
|
||||
int hz = (int) (frequency - (mhz * 1E6));
|
||||
int mhz = (int)(frequency / 1E6);
|
||||
int hz = (int)(frequency - (mhz * 1E6));
|
||||
|
||||
buffer.putInt(mhz);
|
||||
buffer.putInt(hz);
|
||||
@@ -350,7 +341,7 @@ public class HackRFTunerController extends TunerController
|
||||
if(config instanceof HackRFTunerConfiguration)
|
||||
{
|
||||
HackRFTunerConfiguration hackRFConfig =
|
||||
(HackRFTunerConfiguration) config;
|
||||
(HackRFTunerConfiguration)config;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -395,8 +386,8 @@ public class HackRFTunerController extends TunerController
|
||||
int transferred = LibUsb.controlTransfer(mDeviceHandle,
|
||||
REQUEST_TYPE_IN,
|
||||
request.getRequestNumber(),
|
||||
(short) value,
|
||||
(short) index,
|
||||
(short)value,
|
||||
(short)index,
|
||||
buffer,
|
||||
USB_TIMEOUT_US);
|
||||
|
||||
@@ -450,11 +441,11 @@ public class HackRFTunerController extends TunerController
|
||||
|
||||
if(signed)
|
||||
{
|
||||
return (int) (buffer.get());
|
||||
return (int)(buffer.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int) (buffer.get() & 0xFF);
|
||||
return (int)(buffer.get() & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,8 +459,8 @@ public class HackRFTunerController extends TunerController
|
||||
int transferred = LibUsb.controlTransfer(mDeviceHandle,
|
||||
REQUEST_TYPE_OUT,
|
||||
request.getRequestNumber(),
|
||||
(short) value,
|
||||
(short) index,
|
||||
(short)value,
|
||||
(short)index,
|
||||
buffer,
|
||||
USB_TIMEOUT_US);
|
||||
|
||||
@@ -567,7 +558,7 @@ public class HackRFTunerController extends TunerController
|
||||
|
||||
private Request(int number)
|
||||
{
|
||||
mRequestNumber = (byte) number;
|
||||
mRequestNumber = (byte)number;
|
||||
}
|
||||
|
||||
public byte getRequestNumber()
|
||||
@@ -685,7 +676,7 @@ public class HackRFTunerController extends TunerController
|
||||
|
||||
private BoardID(int number, String label)
|
||||
{
|
||||
mIDNumber = (byte) number;
|
||||
mIDNumber = (byte)number;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
@@ -732,7 +723,7 @@ public class HackRFTunerController extends TunerController
|
||||
|
||||
private Mode(int number, String label)
|
||||
{
|
||||
mNumber = (byte) number;
|
||||
mNumber = (byte)number;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
@@ -890,21 +881,7 @@ public class HackRFTunerController extends TunerController
|
||||
*/
|
||||
public void addListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
synchronized(mComplexBufferBroadcaster)
|
||||
{
|
||||
mComplexBufferBroadcaster.addListener(listener);
|
||||
|
||||
if(mBufferProcessor == null || !mBufferProcessor.isRunning())
|
||||
{
|
||||
mBufferProcessor = new BufferProcessor();
|
||||
|
||||
Thread thread = new Thread(mBufferProcessor);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("HackRF Sample Processor");
|
||||
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
mUSBTransferProcessor.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -913,241 +890,7 @@ public class HackRFTunerController extends TunerController
|
||||
*/
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
synchronized(mComplexBufferBroadcaster)
|
||||
{
|
||||
mComplexBufferBroadcaster.removeListener(listener);
|
||||
|
||||
if(!mComplexBufferBroadcaster.hasListeners())
|
||||
{
|
||||
mBufferProcessor.stop();
|
||||
}
|
||||
}
|
||||
mUSBTransferProcessor.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer processing thread. Fetches samples from the HackRF Tuner and
|
||||
* dispatches them to all registered listeners
|
||||
*/
|
||||
public class BufferProcessor implements Runnable, TransferCallback
|
||||
{
|
||||
private ScheduledFuture<?> mSampleDispatcherTask;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private LinkedTransferQueue<Transfer> mAvailableTransfers;
|
||||
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
|
||||
private ByteBuffer mLibUsbHandlerStatus;
|
||||
private boolean mCancel = false;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
prepareTransfers();
|
||||
|
||||
mSampleDispatcherTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferDispatcher(),
|
||||
20, 20, TimeUnit.MILLISECONDS);
|
||||
|
||||
mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
|
||||
|
||||
List<Transfer> transfers = new ArrayList<>();
|
||||
|
||||
mCancel = false;
|
||||
|
||||
while(mRunning.get())
|
||||
{
|
||||
mAvailableTransfers.drainTo(transfers);
|
||||
|
||||
for(Transfer transfer : transfers)
|
||||
{
|
||||
int result = LibUsb.submitTransfer(transfer);
|
||||
|
||||
if(result == LibUsb.SUCCESS)
|
||||
{
|
||||
mTransfersInProgress.add(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("error submitting transfer [" +
|
||||
LibUsb.errorName(result) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
||||
null, USB_TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer());
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("error handling events for libusb");
|
||||
}
|
||||
|
||||
transfers.clear();
|
||||
|
||||
mLibUsbHandlerStatus.rewind();
|
||||
}
|
||||
|
||||
if(mCancel)
|
||||
{
|
||||
for(Transfer transfer : mTransfersInProgress)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(null,
|
||||
USB_TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer());
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("error cancelling transfers in progress");
|
||||
}
|
||||
|
||||
mLibUsbHandlerStatus.rewind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the sample fetching thread
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mCancel = true;
|
||||
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mSampleDispatcherTask != null)
|
||||
{
|
||||
mSampleDispatcherTask.cancel(true);
|
||||
mFilledBuffers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this thread is running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTransfer(Transfer transfer)
|
||||
{
|
||||
mTransfersInProgress.remove(transfer);
|
||||
|
||||
switch(transfer.status())
|
||||
{
|
||||
case LibUsb.TRANSFER_COMPLETED:
|
||||
case LibUsb.TRANSFER_STALL:
|
||||
if(transfer.actualLength() > 0)
|
||||
{
|
||||
ByteBuffer buffer = transfer.buffer();
|
||||
|
||||
byte[] data = new byte[transfer.actualLength()];
|
||||
|
||||
buffer.get(data);
|
||||
|
||||
buffer.rewind();
|
||||
|
||||
if(isRunning())
|
||||
{
|
||||
mFilledBuffers.add(data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LibUsb.TRANSFER_CANCELLED:
|
||||
break;
|
||||
default:
|
||||
/* unexpected error */
|
||||
mLog.error("transfer error [" +
|
||||
getTransferStatus(transfer.status()) +
|
||||
"] transferred actual: " + transfer.actualLength());
|
||||
}
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares (allocates) a set of transfer buffers for use in
|
||||
* transferring data from the tuner via the bulk interface
|
||||
*/
|
||||
private void prepareTransfers() throws LibUsbException
|
||||
{
|
||||
if(mAvailableTransfers == null)
|
||||
{
|
||||
mAvailableTransfers = new LinkedTransferQueue<>();
|
||||
|
||||
for(int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++)
|
||||
{
|
||||
Transfer transfer = LibUsb.allocTransfer();
|
||||
|
||||
if(transfer == null)
|
||||
{
|
||||
throw new LibUsbException("couldn't allocate transfer",
|
||||
LibUsb.ERROR_NO_MEM);
|
||||
}
|
||||
|
||||
final ByteBuffer buffer =
|
||||
ByteBuffer.allocateDirect(mBufferSize);
|
||||
|
||||
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_ENDPOINT,
|
||||
buffer, BufferProcessor.this, "Buffer", USB_TIMEOUT_US);
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
|
||||
* array and broadcasts the array to all registered listeners
|
||||
*/
|
||||
public class BufferDispatcher implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
|
||||
|
||||
mFilledBuffers.drainTo(buffers);
|
||||
|
||||
for(byte[] buffer : buffers)
|
||||
{
|
||||
float[] samples = mSampleAdapter.convert(buffer);
|
||||
|
||||
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(samples));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error duruing HackRF buffer dispatching", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTransferStatus(int status)
|
||||
{
|
||||
switch(status)
|
||||
{
|
||||
case 0:
|
||||
return "TRANSFER COMPLETED (0)";
|
||||
case 1:
|
||||
return "TRANSFER ERROR (1)";
|
||||
case 2:
|
||||
return "TRANSFER TIMED OUT (2)";
|
||||
case 3:
|
||||
return "TRANSFER CANCELLED (3)";
|
||||
case 4:
|
||||
return "TRANSFER STALL (4)";
|
||||
case 5:
|
||||
return "TRANSFER NO DEVICE (5)";
|
||||
case 6:
|
||||
return "TRANSFER OVERFLOW (6)";
|
||||
default:
|
||||
return "UNKNOWN TRANSFER STATUS (" + status + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* Java version based on librtlsdr
|
||||
* Copyright (C) 2012-2013 by Steve Markgraf <steve@steve-m.de>
|
||||
@@ -21,7 +21,6 @@
|
||||
******************************************************************************/
|
||||
package source.tuner.rtl;
|
||||
|
||||
import buffer.FloatAveragingBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Device;
|
||||
@@ -29,54 +28,38 @@ import org.usb4java.DeviceDescriptor;
|
||||
import org.usb4java.DeviceHandle;
|
||||
import org.usb4java.LibUsb;
|
||||
import org.usb4java.LibUsbException;
|
||||
import org.usb4java.Transfer;
|
||||
import org.usb4java.TransferCallback;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.adapter.ByteSampleAdapter;
|
||||
import sample.adapter.ISampleAdapter;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.SourceException;
|
||||
import source.tuner.TunerController;
|
||||
import source.tuner.TunerManager;
|
||||
import source.tuner.TunerType;
|
||||
import util.ThreadPool;
|
||||
import source.tuner.usb.USBTransferProcessor;
|
||||
|
||||
import javax.usb.UsbDisconnectedException;
|
||||
import javax.usb.UsbException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public abstract class RTL2832TunerController extends TunerController
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RTL2832TunerController.class);
|
||||
|
||||
public final static int INT_NULL_VALUE = -1;
|
||||
public final static long LONG_NULL_VALUE = -1l;
|
||||
public final static double DOUBLE_NULL_VALUE = -1.0D;
|
||||
public final static int TWO_TO_22_POWER = 4194304;
|
||||
|
||||
public final static int USB_TRANSFER_BUFFER_SIZE = 131072;
|
||||
public final static byte USB_INTERFACE = (byte) 0x0;
|
||||
public final static byte USB_BULK_ENDPOINT = (byte) 0x81;
|
||||
public final static byte CONTROL_ENDPOINT_IN =
|
||||
(byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR);
|
||||
public final static byte CONTROL_ENDPOINT_OUT =
|
||||
(byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR);
|
||||
public final static byte CONTROL_ENDPOINT_IN = (byte) (LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR);
|
||||
public final static byte CONTROL_ENDPOINT_OUT = (byte) (LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR);
|
||||
|
||||
public final static long TIMEOUT_US = 1000000l; //uSeconds
|
||||
|
||||
public final static byte REQUEST_ZERO = (byte) 0;
|
||||
|
||||
public final static int TRANSFER_BUFFER_POOL_SIZE = 16;
|
||||
|
||||
public final static byte EEPROM_ADDRESS = (byte) 0xA0;
|
||||
|
||||
public final static byte[] sFIR_COEFFICIENTS =
|
||||
@@ -87,8 +70,7 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
(byte) 0x71, (byte) 0x74, (byte) 0x19, (byte) 0x41, (byte) 0xA5
|
||||
};
|
||||
|
||||
public static final SampleRate DEFAULT_SAMPLE_RATE =
|
||||
SampleRate.RATE_0_960MHZ;
|
||||
public static final SampleRate DEFAULT_SAMPLE_RATE = SampleRate.RATE_2_400MHZ;
|
||||
|
||||
protected Device mDevice;
|
||||
protected DeviceDescriptor mDeviceDescriptor;
|
||||
@@ -96,24 +78,13 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
|
||||
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
|
||||
|
||||
private BufferProcessor mBufferProcessor;
|
||||
|
||||
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
|
||||
|
||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
|
||||
private LinkedTransferQueue<byte[]> mFilledBuffers =
|
||||
new LinkedTransferQueue<byte[]>();
|
||||
|
||||
private SampleRateMonitor mSampleRateMonitor;
|
||||
private AtomicInteger mSampleCounter = new AtomicInteger();
|
||||
private static final DecimalFormat mDecimalFormatter =
|
||||
new DecimalFormat("###,###,###.0");
|
||||
private static final DecimalFormat mPercentFormatter =
|
||||
new DecimalFormat("###.00");
|
||||
|
||||
protected int mOscillatorFrequency = 28800000; //28.8 MHz
|
||||
public int mBufferSize = 131072;
|
||||
|
||||
private USBTransferProcessor mUSBTransferProcessor;
|
||||
|
||||
protected Descriptor mDescriptor;
|
||||
|
||||
@@ -190,6 +161,11 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
+ "descriptor byte array " +
|
||||
(eeprom == null ? "[null]" : Arrays.toString(eeprom)), e);
|
||||
}
|
||||
|
||||
String deviceName = getTunerType().getLabel() + " " + getUniqueID();
|
||||
|
||||
mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
|
||||
USB_TRANSFER_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,7 +241,7 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
switch(mode)
|
||||
{
|
||||
case QUADRATURE:
|
||||
/* Set intermediate frequency to 0 Hz */
|
||||
/* Set intermediate frequency to 0 Hz */
|
||||
setIFFrequency(0);
|
||||
|
||||
/* Enable I/Q ADC Input */
|
||||
@@ -476,14 +452,13 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
{
|
||||
try
|
||||
{
|
||||
if(mBufferProcessor.isRunning())
|
||||
if(mUSBTransferProcessor != null)
|
||||
{
|
||||
mBufferProcessor.stop();
|
||||
mUSBTransferProcessor.removeAllListeners();
|
||||
TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(mUSBTransferProcessor);
|
||||
}
|
||||
|
||||
LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE);
|
||||
|
||||
LibUsb.exit(null);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -1014,11 +989,6 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
mSampleRate = sampleRate;
|
||||
|
||||
mFrequencyController.setSampleRate(sampleRate.getRate());
|
||||
|
||||
if(mSampleRateMonitor != null)
|
||||
{
|
||||
mSampleRateMonitor.setSampleRate(mSampleRate.getRate());
|
||||
}
|
||||
}
|
||||
|
||||
public void setSampleRateFrequencyCorrection(int ppm) throws SourceException
|
||||
@@ -1318,331 +1288,6 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a sample listener. If the buffer processing thread is
|
||||
* not currently running, starts it running in a new thread.
|
||||
*/
|
||||
public void addListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
synchronized(mComplexBufferBroadcaster)
|
||||
{
|
||||
mComplexBufferBroadcaster.addListener(listener);
|
||||
|
||||
if(mBufferProcessor == null)
|
||||
{
|
||||
mBufferProcessor = new BufferProcessor();
|
||||
}
|
||||
|
||||
if(!mBufferProcessor.isRunning())
|
||||
{
|
||||
Thread thread = new Thread(mBufferProcessor);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("RTL2832 Sample Processor");
|
||||
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the sample listener. If this is the last registered listener,
|
||||
* shuts down the buffer processing thread.
|
||||
*/
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
synchronized(mComplexBufferBroadcaster)
|
||||
{
|
||||
mComplexBufferBroadcaster.removeListener(listener);
|
||||
|
||||
if(!mComplexBufferBroadcaster.hasListeners())
|
||||
{
|
||||
mBufferProcessor.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer processing thread. Fetches samples from the RTL2832 Tuner and
|
||||
* dispatches them to all registered listeners
|
||||
*/
|
||||
public class BufferProcessor implements Runnable, TransferCallback
|
||||
{
|
||||
private ScheduledFuture<?> mSampleDispatcherTask;
|
||||
private LinkedTransferQueue<Transfer> mAvailableTransfers;
|
||||
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private ByteBuffer mLibUsbHandlerStatus;
|
||||
private boolean mCancel = false;
|
||||
|
||||
public BufferProcessor()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
try
|
||||
{
|
||||
setSampleRate(mSampleRate);
|
||||
|
||||
resetUSBBuffer();
|
||||
}
|
||||
catch(SourceException e)
|
||||
{
|
||||
mLog.error("couldn't start buffer processor", e);
|
||||
|
||||
mRunning.set(false);
|
||||
}
|
||||
|
||||
prepareTransfers();
|
||||
|
||||
mSampleDispatcherTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferDispatcher(),
|
||||
20, 20, TimeUnit.MILLISECONDS);
|
||||
|
||||
mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
|
||||
|
||||
List<Transfer> transfers = new ArrayList<>();
|
||||
|
||||
mCancel = false;
|
||||
|
||||
while(mRunning.get())
|
||||
{
|
||||
mAvailableTransfers.drainTo(transfers);
|
||||
|
||||
for(Transfer transfer : transfers)
|
||||
{
|
||||
int result = LibUsb.submitTransfer(transfer);
|
||||
|
||||
if(result == LibUsb.SUCCESS)
|
||||
{
|
||||
mTransfersInProgress.add(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error submitting transfer [" +
|
||||
LibUsb.errorName(result) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
||||
null, TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer());
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("error handling events for libusb");
|
||||
}
|
||||
|
||||
transfers.clear();
|
||||
|
||||
mLibUsbHandlerStatus.rewind();
|
||||
}
|
||||
|
||||
if(mCancel)
|
||||
{
|
||||
for(Transfer transfer : mTransfersInProgress)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(null,
|
||||
TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer());
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("error handling events for libusb during cancel");
|
||||
}
|
||||
|
||||
mLibUsbHandlerStatus.rewind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the sample fetching thread
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mCancel = true;
|
||||
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
if(mSampleDispatcherTask != null)
|
||||
{
|
||||
mSampleDispatcherTask.cancel(true);
|
||||
mSampleDispatcherTask = null;
|
||||
mFilledBuffers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this thread is running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTransfer(Transfer transfer)
|
||||
{
|
||||
mTransfersInProgress.remove(transfer);
|
||||
|
||||
switch(transfer.status())
|
||||
{
|
||||
case LibUsb.TRANSFER_COMPLETED:
|
||||
case LibUsb.TRANSFER_STALL:
|
||||
if(transfer.actualLength() > 0)
|
||||
{
|
||||
ByteBuffer buffer = transfer.buffer();
|
||||
|
||||
byte[] data = new byte[transfer.actualLength()];
|
||||
|
||||
buffer.get(data);
|
||||
|
||||
buffer.rewind();
|
||||
|
||||
if(isRunning())
|
||||
{
|
||||
mFilledBuffers.add(data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LibUsb.TRANSFER_CANCELLED:
|
||||
break;
|
||||
default:
|
||||
/* unexpected error */
|
||||
mLog.error("transfer error [" +
|
||||
getTransferStatus(transfer.status()) +
|
||||
"] transferred actual: " + transfer.actualLength());
|
||||
}
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares (allocates) a set of transfer buffers for use in
|
||||
* transferring data from the tuner via the bulk interface
|
||||
*/
|
||||
private void prepareTransfers() throws LibUsbException
|
||||
{
|
||||
if(mAvailableTransfers == null)
|
||||
{
|
||||
mAvailableTransfers = new LinkedTransferQueue<>();
|
||||
|
||||
for(int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++)
|
||||
{
|
||||
Transfer transfer = LibUsb.allocTransfer();
|
||||
|
||||
if(transfer == null)
|
||||
{
|
||||
throw new LibUsbException("couldn't allocate transfer",
|
||||
LibUsb.ERROR_NO_MEM);
|
||||
}
|
||||
|
||||
final ByteBuffer buffer =
|
||||
ByteBuffer.allocateDirect(mBufferSize);
|
||||
|
||||
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_ENDPOINT,
|
||||
buffer, BufferProcessor.this, "Buffer", TIMEOUT_US);
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
|
||||
* array and broadcasts the array to all registered listeners
|
||||
*/
|
||||
public class BufferDispatcher implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
|
||||
|
||||
mFilledBuffers.drainTo(buffers);
|
||||
|
||||
for(byte[] buffer : buffers)
|
||||
{
|
||||
float[] samples = mSampleAdapter.convert(buffer);
|
||||
|
||||
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(samples));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error during rtl2832 buffer dispatcher run", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTransferStatus(int status)
|
||||
{
|
||||
switch(status)
|
||||
{
|
||||
case 0:
|
||||
return "TRANSFER COMPLETED (0)";
|
||||
case 1:
|
||||
return "TRANSFER ERROR (1)";
|
||||
case 2:
|
||||
return "TRANSFER TIMED OUT (2)";
|
||||
case 3:
|
||||
return "TRANSFER CANCELLED (3)";
|
||||
case 4:
|
||||
return "TRANSFER STALL (4)";
|
||||
case 5:
|
||||
return "TRANSFER NO DEVICE (5)";
|
||||
case 6:
|
||||
return "TRANSFER OVERFLOW (6)";
|
||||
default:
|
||||
return "UNKNOWN TRANSFER STATUS (" + status + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class USBEventHandlingThread implements Runnable
|
||||
{
|
||||
/**
|
||||
* If thread should abort.
|
||||
*/
|
||||
private volatile boolean mAbort;
|
||||
|
||||
/**
|
||||
* Aborts the event handling thread.
|
||||
*/
|
||||
public void abort()
|
||||
{
|
||||
mAbort = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
while(!mAbort)
|
||||
{
|
||||
int result = LibUsb.handleEventsTimeout(null, 1000);
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mAbort = true;
|
||||
|
||||
mLog.error("error handling usb events [" +
|
||||
LibUsb.errorName(result) + "]");
|
||||
|
||||
throw new LibUsbException("Unable to handle USB "
|
||||
+ "events", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum TunerTypeCheck
|
||||
{
|
||||
E4K(0xC8, 0x02, 0x40),
|
||||
@@ -1679,102 +1324,6 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Averages the sample rate over a 10-second period. The count is for the
|
||||
* number of bytes received from the tuner. There are two bytes for each
|
||||
* sample. So, we divide by 20 to get the average sample rate.
|
||||
*/
|
||||
public class SampleRateMonitor implements Runnable
|
||||
{
|
||||
private static final int BUFFER_SIZE = 5;
|
||||
private int mTargetSampleRate;
|
||||
private int mSampleRateMinimum;
|
||||
private int mSampleRateMaximum;
|
||||
private int mNewTargetSampleRate;
|
||||
private FloatAveragingBuffer mRateErrorBuffer =
|
||||
new FloatAveragingBuffer(BUFFER_SIZE);
|
||||
private AtomicBoolean mRateChanged = new AtomicBoolean();
|
||||
|
||||
public SampleRateMonitor(int sampleRate)
|
||||
{
|
||||
setTargetRate(sampleRate);
|
||||
}
|
||||
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mNewTargetSampleRate = sampleRate;
|
||||
mRateChanged.set(true);
|
||||
}
|
||||
|
||||
private void setTargetRate(int rate)
|
||||
{
|
||||
mTargetSampleRate = rate;
|
||||
mSampleRateMinimum = (int) ((float) mTargetSampleRate * 0.95f);
|
||||
mSampleRateMaximum = (int) ((float) mTargetSampleRate * 1.05f);
|
||||
|
||||
for(int x = 0; x < BUFFER_SIZE; x++)
|
||||
{
|
||||
mRateErrorBuffer.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(mRateChanged.compareAndSet(true, false))
|
||||
{
|
||||
setTargetRate(mNewTargetSampleRate);
|
||||
|
||||
/* Reset the sample counter */
|
||||
mSampleCounter.set(0);
|
||||
|
||||
mLog.info("monitor reset for new sample rate [" + mTargetSampleRate + "]");
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = mSampleCounter.getAndSet(0);
|
||||
|
||||
float current = (float) count / 20.0f;
|
||||
|
||||
/**
|
||||
* Only accept values +/- 5% of target rate
|
||||
*/
|
||||
float average;
|
||||
|
||||
if(mSampleRateMinimum < current && current < mSampleRateMaximum)
|
||||
{
|
||||
average = mRateErrorBuffer.get((float) mTargetSampleRate - current);
|
||||
}
|
||||
else
|
||||
{
|
||||
average = mTargetSampleRate;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
if(mDescriptor != null)
|
||||
{
|
||||
sb.append(mDescriptor.getSerial());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("DESCRIPTOR IS NULL");
|
||||
}
|
||||
sb.append("] sample rate current [");
|
||||
sb.append(mDecimalFormatter.format(current));
|
||||
sb.append(" Hz ");
|
||||
sb.append(mPercentFormatter.format(100.0f * (current / (float) mTargetSampleRate)));
|
||||
sb.append("% ] error [");
|
||||
sb.append(average);
|
||||
sb.append(" Hz ] target ");
|
||||
sb.append(mDecimalFormatter.format(mTargetSampleRate));
|
||||
|
||||
mLog.info(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RTL2832 EEPROM byte array descriptor parsing class
|
||||
*/
|
||||
@@ -1945,4 +1494,47 @@ public abstract class RTL2832TunerController extends TunerController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a sample listener. If the USB transfer processor is not currently running, it will auto-start
|
||||
*/
|
||||
public void addListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mUSBTransferProcessor.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the sample listener. If this is the last registered listener, the USB transfer processor will
|
||||
* halt buffer processing
|
||||
*/
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mUSBTransferProcessor.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* RTL-2832 USB transfer processor. Extends USB transfer processor and allows resetting the device USB
|
||||
* buffer prior to starting streaming.
|
||||
*/
|
||||
public class RTL2832USBTransferProcessor extends USBTransferProcessor
|
||||
{
|
||||
/**
|
||||
* Manages stream of USB transfer buffers and converts buffers to complex buffer samples for distribution to
|
||||
* any registered listeners.
|
||||
*
|
||||
* @param deviceName to use when logging information or errors
|
||||
* @param deviceHandle to the USB bulk transfer device
|
||||
* @param sampleAdapter specific to the tuner's byte buffer format for converting to floating point I/Q samples
|
||||
* @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144.
|
||||
*/
|
||||
public RTL2832USBTransferProcessor(String deviceName, DeviceHandle deviceHandle, ISampleAdapter sampleAdapter, int bufferSize)
|
||||
{
|
||||
super(deviceName, deviceHandle, sampleAdapter, bufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareDeviceStart()
|
||||
{
|
||||
resetUSBBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
390
src/source/tuner/usb/USBTransferProcessor.java
Normal file
390
src/source/tuner/usb/USBTransferProcessor.java
Normal file
@@ -0,0 +1,390 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package source.tuner.usb;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.DeviceHandle;
|
||||
import org.usb4java.LibUsb;
|
||||
import org.usb4java.LibUsbException;
|
||||
import org.usb4java.Transfer;
|
||||
import org.usb4java.TransferCallback;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import sample.OverflowableTransferQueue;
|
||||
import sample.adapter.ISampleAdapter;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.tuner.TunerManager;
|
||||
import util.ThreadPool;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class USBTransferProcessor implements TransferCallback
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(USBTransferProcessor.class);
|
||||
|
||||
private static final byte USB_BULK_TRANSFER_ENDPOINT = (byte) 0x81;
|
||||
private static final long USB_TIMEOUT_MS = 2000l; //milliseconds
|
||||
|
||||
//Number of native byte buffers to allocate for transferring data from the USB device
|
||||
private static final int TRANSFER_BUFFER_POOL_SIZE = 16;
|
||||
|
||||
//Maximum number of filled buffers for the blocking queue
|
||||
private static final int FILLED_BUFFER_MAX_CAPACITY = 300;
|
||||
|
||||
//Threshold for resetting buffer overflow condition
|
||||
private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 200;
|
||||
|
||||
private String mDeviceName;
|
||||
|
||||
//Handle to the USB bulk transfer device
|
||||
private DeviceHandle mDeviceHandle;
|
||||
|
||||
//Tuner format-specific byte to IQ float sample converter
|
||||
private ISampleAdapter mSampleAdapter;
|
||||
|
||||
//Byte array transfer buffers size in bytes
|
||||
private int mBufferSize;
|
||||
|
||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
private OverflowableTransferQueue<byte[]> mFilledBuffers;
|
||||
private LinkedTransferQueue<Transfer> mAvailableTransfers = new LinkedTransferQueue<>();
|
||||
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
|
||||
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
private ByteBuffer mLibUsbHandlerStatus = ByteBuffer.allocateDirect(4);
|
||||
|
||||
private BufferDispatcher mBufferDispatcher = new BufferDispatcher();
|
||||
private ScheduledFuture mBufferDispatcherFuture;
|
||||
|
||||
/**
|
||||
* Manages stream of USB transfer buffers and converts buffers to complex buffer samples for distribution to
|
||||
* any registered listeners.
|
||||
*
|
||||
* @param deviceName to use when logging information or errors
|
||||
* @param deviceHandle to the USB bulk transfer device
|
||||
* @param sampleAdapter specific to the tuner's byte buffer format for converting to floating point I/Q samples
|
||||
* @param bufferSize in bytes. Should be a multiple of two: 65536, 131072 or 262144.
|
||||
*/
|
||||
public USBTransferProcessor(String deviceName,
|
||||
DeviceHandle deviceHandle,
|
||||
ISampleAdapter sampleAdapter,
|
||||
int bufferSize)
|
||||
{
|
||||
mDeviceName = deviceName;
|
||||
mDeviceHandle = deviceHandle;
|
||||
mSampleAdapter = sampleAdapter;
|
||||
mBufferSize = bufferSize;
|
||||
|
||||
mFilledBuffers = new OverflowableTransferQueue<>(FILLED_BUFFER_MAX_CAPACITY, FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD);
|
||||
mFilledBuffers.setStateListener(new Listener<OverflowableTransferQueue.State>()
|
||||
{
|
||||
@Override
|
||||
public void receive(OverflowableTransferQueue.State state)
|
||||
{
|
||||
if(state == OverflowableTransferQueue.State.NORMAL)
|
||||
{
|
||||
mLog.debug(mDeviceName + " - buffer overflow - temporary pause until processing catches up");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug(mDeviceName + " - buffer overflow cleared - resuming normal processing");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start USB transfer buffer processing. Subsequent calls to this method after started will be ignored.
|
||||
*/
|
||||
private void start()
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
prepareDeviceStart();
|
||||
|
||||
prepareTransfers();
|
||||
|
||||
while(!mAvailableTransfers.isEmpty())
|
||||
{
|
||||
Transfer transfer = mAvailableTransfers.poll();
|
||||
|
||||
if(transfer != null)
|
||||
{
|
||||
int result = LibUsb.submitTransfer(transfer);
|
||||
|
||||
if(result == LibUsb.SUCCESS)
|
||||
{
|
||||
mTransfersInProgress.add(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: broadcast to each listener that this source has an error and is shutting down
|
||||
mLog.error(mDeviceName + "- error submitting transfer [" + LibUsb.errorName(result) + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Start transferred buffer dispatcher
|
||||
mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferDispatcher,
|
||||
20, 20, TimeUnit.MILLISECONDS);
|
||||
|
||||
//Register with LibUSB processor so that it auto-starts LibUSB processing
|
||||
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop USB transfer buffer processing. Subsequent calls to this method after stopped will be ignored.
|
||||
*/
|
||||
private void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
mBufferDispatcherFuture.cancel(true);
|
||||
|
||||
mFilledBuffers.clear();
|
||||
|
||||
//Cancel the lib usb process timer
|
||||
for(Transfer transfer : mTransfersInProgress)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
|
||||
|
||||
//Unregister from LibUSB processor so that it auto-stops LibUSB processing
|
||||
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this);
|
||||
|
||||
//Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed.
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS,
|
||||
mLibUsbHandlerStatus.asIntBuffer());
|
||||
|
||||
if(result != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error(mDeviceName + " - error while cancelling transfer buffers during shutdown/pause - error code:" + result);
|
||||
}
|
||||
|
||||
mLibUsbHandlerStatus.rewind();
|
||||
|
||||
executeDeviceStop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows sub-class implementations to execute any device-specific operations to prepare for starting USB transfers
|
||||
*/
|
||||
protected void prepareDeviceStart()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows sub-class implementations to execute any device-specific operations after stopping USB transfers
|
||||
*/
|
||||
protected void executeDeviceStop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there are complex buffer listeners registered with this processor
|
||||
*/
|
||||
public boolean hasListeners()
|
||||
{
|
||||
return mComplexBufferBroadcaster.hasListeners();
|
||||
}
|
||||
|
||||
public void addListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mComplexBufferBroadcaster.addListener(listener);
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mComplexBufferBroadcaster.removeListener(listener);
|
||||
|
||||
if(!hasListeners())
|
||||
{
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllListeners()
|
||||
{
|
||||
mComplexBufferBroadcaster.clear();
|
||||
stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares (allocates) a set of transfer buffers for use in transferring data from the USB device via the bulk
|
||||
* interface. Since we're using direct allocation (native), buffers are retained and reused across multiple
|
||||
* start/stop cycles.
|
||||
*/
|
||||
private void prepareTransfers() throws LibUsbException
|
||||
{
|
||||
while(mAvailableTransfers.size() < TRANSFER_BUFFER_POOL_SIZE)
|
||||
{
|
||||
Transfer transfer = LibUsb.allocTransfer();
|
||||
|
||||
if(transfer == null)
|
||||
{
|
||||
throw new LibUsbException("Couldn't allocate USB transfer buffer", LibUsb.ERROR_NO_MEM);
|
||||
}
|
||||
|
||||
final ByteBuffer buffer = ByteBuffer.allocateDirect(mBufferSize);
|
||||
|
||||
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer, this,
|
||||
"Buffer", USB_TIMEOUT_MS);
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a filled transfer buffer received back from the USB device
|
||||
*/
|
||||
@Override
|
||||
public void processTransfer(Transfer transfer)
|
||||
{
|
||||
mTransfersInProgress.remove(transfer);
|
||||
|
||||
switch(transfer.status())
|
||||
{
|
||||
case LibUsb.TRANSFER_COMPLETED:
|
||||
case LibUsb.TRANSFER_STALL:
|
||||
if(transfer.actualLength() > 0)
|
||||
{
|
||||
ByteBuffer buffer = transfer.buffer();
|
||||
|
||||
byte[] data = new byte[transfer.actualLength()];
|
||||
buffer.get(data);
|
||||
buffer.rewind();
|
||||
|
||||
if(mRunning.get())
|
||||
{
|
||||
mFilledBuffers.offer(data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LibUsb.TRANSFER_CANCELLED:
|
||||
break;
|
||||
default:
|
||||
//Unexpected transfer error
|
||||
mLog.error(mDeviceName + " - transfer error [" + getTransferStatus(transfer.status()) +
|
||||
"] transferred actual: " + transfer.actualLength());
|
||||
}
|
||||
|
||||
|
||||
if(mRunning.get())
|
||||
{
|
||||
int result = LibUsb.submitTransfer(transfer);
|
||||
|
||||
if(result == LibUsb.SUCCESS)
|
||||
{
|
||||
mTransfersInProgress.add(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
mAvailableTransfers.add(transfer);
|
||||
mLog.error(mDeviceName + " - error submitting transfer [" + LibUsb.errorName(result) + "]");
|
||||
|
||||
if(mTransfersInProgress.isEmpty())
|
||||
{
|
||||
mLog.warn(mDeviceName + " - all transfer buffer processing is stopped");
|
||||
//TODO: no transfers are in progress ... need to alert the user and the registered complex
|
||||
//TODO: buffer listeners so that they can respond accordingly
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//We're stopping - park the transfer buffers
|
||||
if(!mAvailableTransfers.contains(transfer))
|
||||
{
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the USB transfer status number into a descriptive label
|
||||
*/
|
||||
public static String getTransferStatus(int status)
|
||||
{
|
||||
switch(status)
|
||||
{
|
||||
case 0:
|
||||
return "TRANSFER COMPLETED (0)";
|
||||
case 1:
|
||||
return "TRANSFER ERROR (1)";
|
||||
case 2:
|
||||
return "TRANSFER TIMED OUT (2)";
|
||||
case 3:
|
||||
return "TRANSFER CANCELLED (3)";
|
||||
case 4:
|
||||
return "TRANSFER STALL (4)";
|
||||
case 5:
|
||||
return "TRANSFER NO DEVICE (5)";
|
||||
case 6:
|
||||
return "TRANSFER OVERFLOW (6)";
|
||||
default:
|
||||
return "UNKNOWN TRANSFER STATUS (" + status + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
|
||||
* array and broadcasts the array to all registered listeners
|
||||
*/
|
||||
public class BufferDispatcher implements Runnable
|
||||
{
|
||||
private List<byte[]> mBuffersToDispatch = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
mFilledBuffers.drainTo(mBuffersToDispatch, 15);
|
||||
|
||||
for(byte[] buffer : mBuffersToDispatch)
|
||||
{
|
||||
float[] complexSamples = mSampleAdapter.convert(buffer);
|
||||
|
||||
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e);
|
||||
}
|
||||
|
||||
mBuffersToDispatch.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user