mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
Merge pull request #142 from DSheirer/126-tuner-buffer-processor-out-of-memory
126 - Tuner processing refactor and significant improvement in processor and memory utilization. R…
This commit is contained in:
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