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