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