mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
16
build.gradle
16
build.gradle
@@ -127,13 +127,16 @@ dependencies {
|
||||
implementation 'io.github.dsheirer:jairspy:1.0.12' //Airspy tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jairspyhf:1.8.1' //Airspy HF+ tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jhackrf:2026.01.3' //HackRF tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jhydrasdr:1.1.4' //HydraSDR tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jrtl:1.4.0.1' //RTL-SDR tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jsdrplay:3.15.3' //SDRplay RSP tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
||||
implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs
|
||||
implementation 'io.github.dsheirer:usb4java:1.3.5' //Fork with Loader support for OSX Multi-Version
|
||||
implementation 'javax.usb:usb-api:1.0.2'
|
||||
implementation('io.github.dsheirer:usb4java:1.3.5') { //Fork with Loader support for OSX Multi-Version
|
||||
//Excludes the javax.usb API and the duplicate original usb4java classes. The native libraries are listed below.
|
||||
exclude group: 'org.usb4java', module: 'usb4java-javax'
|
||||
}
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||
implementation 'org.apache.commons:commons-compress:1.28.0'
|
||||
implementation 'org.apache.commons:commons-lang3:3.20.0'
|
||||
@@ -144,8 +147,13 @@ dependencies {
|
||||
implementation 'org.controlsfx:controlsfx:11.2.3'
|
||||
implementation 'org.rauschig:jarchivelib:1.2.0'
|
||||
implementation 'org.slf4j:slf4j-api:2.0.17'
|
||||
implementation 'org.usb4java:libusb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||
implementation 'org.usb4java:libusb4java:1.3.0:darwin-x86-64' //usb4java native library
|
||||
implementation 'org.usb4java:libusb4java:1.3.0:linux-aarch64' //usb4java native library
|
||||
implementation 'org.usb4java:libusb4java:1.3.0:linux-arm' //usb4java native library
|
||||
implementation 'org.usb4java:libusb4java:1.3.0:linux-x86' //usb4java native library
|
||||
implementation 'org.usb4java:libusb4java:1.3.0:linux-x86-64' //usb4java native library
|
||||
implementation 'org.usb4java:libusb4java:1.3.0:win32-x86' //usb4java native library
|
||||
implementation 'org.usb4java:libusb4java:1.3.0:win32-x86-64' //usb4java native library
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,14 @@ dependencyResolutionManagement {
|
||||
content { includeModule 'io.github.dsheirer', 'jrtl' }
|
||||
}
|
||||
|
||||
//jHackRF is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jHackRF/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jhackrf' }
|
||||
}
|
||||
|
||||
//jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jHydraSDR/releases/download'
|
||||
|
||||
@@ -347,6 +347,7 @@ public class TunerFactory
|
||||
return new FCD2TunerConfiguration(uniqueID);
|
||||
case HACKRF_JAWBREAKER:
|
||||
case HACKRF_ONE:
|
||||
case HACKRF_PRO:
|
||||
case HACKRF_RAD1O:
|
||||
return new HackRFTunerConfiguration(uniqueID);
|
||||
case HYDRASDR_R828D:
|
||||
|
||||
@@ -35,6 +35,7 @@ public enum TunerType
|
||||
FUNCUBE_DONGLE_PRO("Funcube Dongle Pro"),
|
||||
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro Plus"),
|
||||
HACKRF_ONE("ONE"),
|
||||
HACKRF_PRO("Pro"),
|
||||
HACKRF_JAWBREAKER("Jawbreaker"),
|
||||
HACKRF_RAD1O("RAD1O"),
|
||||
HYDRASDR_R828D("R828D"),
|
||||
@@ -75,7 +76,7 @@ public enum TunerType
|
||||
* Supported USB tuner types
|
||||
*/
|
||||
public static final EnumSet<TunerType> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY_R820T, BLADERF, ELONICS_E4000, FITIPOWER_FC0012,
|
||||
HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HYDRASDR_R828D, RAFAELMICRO_R820T);
|
||||
HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HACKRF_PRO, HYDRASDR_R828D, RAFAELMICRO_R820T);
|
||||
|
||||
/**
|
||||
* Supported sound card tuner types
|
||||
|
||||
@@ -40,8 +40,6 @@ import org.usb4java.LibUsb;
|
||||
import org.usb4java.LibUsbException;
|
||||
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
import javax.usb.UsbClaimException;
|
||||
import javax.usb.UsbException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.IntBuffer;
|
||||
@@ -428,7 +426,7 @@ public abstract class FCDTunerController extends TunerController
|
||||
/**
|
||||
* Set fcd interface mode
|
||||
*/
|
||||
public void setFCDMode(Mode mode) throws UsbException, UsbClaimException
|
||||
public void setFCDMode(Mode mode)
|
||||
{
|
||||
ByteBuffer response = null;
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
import javax.usb.UsbClaimException;
|
||||
import javax.usb.UsbException;
|
||||
|
||||
/**
|
||||
* Funcube Dongle Pro tuner controller. Combines USB HID control with Audio Mixer data streaming interface.
|
||||
@@ -131,7 +129,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
/**
|
||||
* Gets the current LNA Gain value from the controller and stores it
|
||||
*/
|
||||
public LNAGain getLNAGainSetting() throws UsbClaimException, UsbException
|
||||
public LNAGain getLNAGainSetting()
|
||||
{
|
||||
if(mLNAGain == null)
|
||||
{
|
||||
@@ -148,10 +146,8 @@ public class FCD1TunerController extends FCDTunerController
|
||||
* Sets lna gain for the controller
|
||||
*
|
||||
* @param gain
|
||||
* @throws UsbException
|
||||
* @throws UsbClaimException
|
||||
*/
|
||||
public void setLNAGain(LNAGain gain) throws UsbException, UsbClaimException
|
||||
public void setLNAGain(LNAGain gain)
|
||||
{
|
||||
send(FCDCommand.APP_SET_LNA_GAIN, gain.getSetting());
|
||||
}
|
||||
@@ -169,10 +165,8 @@ public class FCD1TunerController extends FCDTunerController
|
||||
*
|
||||
* @param value - new phase correction
|
||||
* @throws IllegalArgumentException for value outside limits (0.0 <> 1.0)
|
||||
* @throws UsbClaimException - if cannot claim the FCD HID controller
|
||||
* @throws UsbException - if there was a usb error
|
||||
*/
|
||||
public void setPhaseCorrection(double value) throws UsbClaimException, UsbException
|
||||
public void setPhaseCorrection(double value)
|
||||
{
|
||||
if(value < -1.0 || value > 1.0)
|
||||
{
|
||||
@@ -194,11 +188,8 @@ public class FCD1TunerController extends FCDTunerController
|
||||
/**
|
||||
* @param value - gain correction value
|
||||
* @throws IllegalArgumentException for value outside limits (0.0 <> 1.0)
|
||||
* @throws UsbClaimException - if cannot claim the FCD HID controller
|
||||
* @throws UsbException - if there was a usb error
|
||||
*/
|
||||
public void setGainCorrection(double value)
|
||||
throws UsbClaimException, UsbException
|
||||
{
|
||||
if(value < -1.0 || value > 1.0)
|
||||
{
|
||||
@@ -220,7 +211,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
* Gain correction values range -1.0 to 0.0 to 1.0 and applied to the dongle as values 0 to 65534
|
||||
* Phase correction values range -1.0 to 0.0 to 1.0 and applied to the dongle as signed values -32768 to 32767
|
||||
*/
|
||||
private void setPhaseAndGainCorrection() throws UsbClaimException, UsbException
|
||||
private void setPhaseAndGainCorrection()
|
||||
{
|
||||
//UNSIGNED short gain value - masked into a long to avoid sign-extension
|
||||
long gain = (long)((1.0 + mGainCorrection) * Short.MAX_VALUE);
|
||||
@@ -239,10 +230,8 @@ public class FCD1TunerController extends FCDTunerController
|
||||
* Note: testing shows that when the dongle is unplugged, any stored phase and gain correction values are reset.
|
||||
* Reading the dongle after a fresh plugin shows the values reset.
|
||||
*
|
||||
* @throws UsbClaimException
|
||||
* @throws UsbException
|
||||
*/
|
||||
private void getPhaseAndGainCorrection() throws UsbClaimException, UsbException
|
||||
private void getPhaseAndGainCorrection()
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_IQ_CORRECTION);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
@@ -276,10 +265,8 @@ public class FCD1TunerController extends FCDTunerController
|
||||
*
|
||||
* @param value - new DC correction value for the inphase component
|
||||
* @throws IllegalArgumentException for values outside limits (-1.0 <> 1.0)
|
||||
* @throws UsbClaimException - if cannot claim the FCD HID controller
|
||||
* @throws UsbException - if there was a usb error
|
||||
*/
|
||||
public void setDCCorrectionInPhase(double value) throws UsbClaimException, UsbException
|
||||
public void setDCCorrectionInPhase(double value)
|
||||
{
|
||||
if(value < -1.0 || value > 1.0)
|
||||
{
|
||||
@@ -304,10 +291,8 @@ public class FCD1TunerController extends FCDTunerController
|
||||
*
|
||||
* @param value - new DC correction value for the quadrature component
|
||||
* @throws IllegalArgumentException for values outside limits (-1.0 <> 1.0)
|
||||
* @throws UsbClaimException - if cannot claim the FCD HID controller
|
||||
* @throws UsbException - if there was a usb error
|
||||
*/
|
||||
public void setDCCorrectionQuadrature(double value) throws UsbClaimException, UsbException
|
||||
public void setDCCorrectionQuadrature(double value)
|
||||
{
|
||||
if(value < -1.0 || value > 1.0)
|
||||
{
|
||||
@@ -332,7 +317,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
* Both Inphase and Quadrature correction values range -1.0 to 0.0 to 1.0 and are applied to the dongle as signed
|
||||
* short values -32768 to 0 to 32767
|
||||
*/
|
||||
private void setDCIQCorrection() throws UsbClaimException, UsbException
|
||||
private void setDCIQCorrection()
|
||||
{
|
||||
//I & Q DC offset correction values are signed short values
|
||||
short inphase = (short)(mDCCorrectionInPhase * Short.MAX_VALUE);
|
||||
@@ -348,7 +333,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
send(FCDCommand.APP_SET_DC_CORRECTION, correction);
|
||||
}
|
||||
|
||||
private void getDCIQCorrection() throws UsbClaimException, UsbException
|
||||
private void getDCIQCorrection()
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_DC_CORRECTION);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
@@ -370,7 +355,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
mDCCorrectionInPhase = ((short)inphase / (double)Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void getLNAEnhanceSetting() throws UsbClaimException, UsbException
|
||||
private void getLNAEnhanceSetting()
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_LNA_ENHANCE);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
@@ -383,12 +368,12 @@ public class FCD1TunerController extends FCDTunerController
|
||||
return mLNAEnhance;
|
||||
}
|
||||
|
||||
public void setLNAEnhance(LNAEnhance enhance) throws UsbClaimException, UsbException
|
||||
public void setLNAEnhance(LNAEnhance enhance)
|
||||
{
|
||||
send(FCDCommand.APP_SET_LNA_ENHANCE, enhance.getSetting());
|
||||
}
|
||||
|
||||
private void getMixerGainSetting() throws UsbClaimException, UsbException
|
||||
private void getMixerGainSetting()
|
||||
{
|
||||
ByteBuffer buffer = send(FCDCommand.APP_GET_MIXER_GAIN);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
@@ -401,7 +386,7 @@ public class FCD1TunerController extends FCDTunerController
|
||||
return mMixerGain;
|
||||
}
|
||||
|
||||
public void setMixerGain(MixerGain gain) throws UsbClaimException, UsbException
|
||||
public void setMixerGain(MixerGain gain)
|
||||
{
|
||||
send(FCDCommand.APP_SET_MIXER_GAIN, gain.getSetting());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -22,17 +22,12 @@ import io.github.dsheirer.preference.source.ChannelizerType;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.BoardID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* HackRF Tuner
|
||||
*/
|
||||
public class HackRFTuner extends Tuner
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(HackRFTuner.class);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param controller for the HackRF
|
||||
@@ -48,14 +43,11 @@ public class HackRFTuner extends Tuner
|
||||
@Override
|
||||
public String getPreferredName()
|
||||
{
|
||||
try
|
||||
String serialNumber = getController().getSerialNumber();
|
||||
|
||||
if(serialNumber != null)
|
||||
{
|
||||
HackRFTunerController.Serial serial = getController().getSerial();
|
||||
return "HackRF " + getController().getTunerType() + " " + serial.getSerialNumber();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//No - op
|
||||
return "HackRF " + getController().getTunerType() + " " + serialNumber;
|
||||
}
|
||||
|
||||
return "HackRF " + getController().getTunerType();
|
||||
@@ -81,16 +73,14 @@ public class HackRFTuner extends Tuner
|
||||
@Override
|
||||
public String getUniqueID()
|
||||
{
|
||||
try
|
||||
String serialNumber = getController().getSerialNumber();
|
||||
|
||||
if(serialNumber != null)
|
||||
{
|
||||
return getTunerClass().toString() + " " + getController().getSerial().getSerialNumber();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error gettting serial number", e);
|
||||
return getTunerClass().toString() + " " + serialNumber;
|
||||
}
|
||||
|
||||
return BoardID.HACKRF_ONE.getLabel();
|
||||
return "HackRF One";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 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
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner.hackrf;
|
||||
|
||||
import io.github.dsheirer.hackrf.HackRf;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerController.HackRFLNAGain;
|
||||
@@ -37,7 +38,6 @@ import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.usb.UsbException;
|
||||
|
||||
/**
|
||||
* HackRF Tuner Editor
|
||||
@@ -174,7 +174,7 @@ public class HackRFTunerEditor extends TunerEditor<HackRFTuner,HackRFTunerConfig
|
||||
getTuner().getController().setVGAGain(vgaGain);
|
||||
save();
|
||||
}
|
||||
catch(UsbException e)
|
||||
catch(SourceException e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(HackRFTunerEditor.this, "HackRF Tuner Controller"
|
||||
+ " - couldn't apply the VGA gain setting - " + e.getLocalizedMessage());
|
||||
@@ -211,7 +211,7 @@ public class HackRFTunerEditor extends TunerEditor<HackRFTuner,HackRFTunerConfig
|
||||
getTuner().getController().setLNAGain(lnaGain);
|
||||
save();
|
||||
}
|
||||
catch(UsbException e)
|
||||
catch(SourceException e)
|
||||
{
|
||||
JOptionPane.showMessageDialog(HackRFTunerEditor.this, "HackRF Tuner Controller"
|
||||
+ " - couldn't apply the LNA gain setting - " + e.getLocalizedMessage());
|
||||
@@ -240,7 +240,7 @@ public class HackRFTunerEditor extends TunerEditor<HackRFTuner,HackRFTunerConfig
|
||||
getTuner().getController().setAmplifierEnabled(mAmplifier.isSelected());
|
||||
save();
|
||||
}
|
||||
catch(UsbException e)
|
||||
catch(SourceException e)
|
||||
{
|
||||
mLog.error("couldn't enable/disable amplifier", e);
|
||||
|
||||
@@ -274,7 +274,7 @@ public class HackRFTunerEditor extends TunerEditor<HackRFTuner,HackRFTunerConfig
|
||||
adjustForSampleRate(sampleRate.getRate());
|
||||
save();
|
||||
}
|
||||
catch(SourceException | UsbException e2)
|
||||
catch(SourceException e2)
|
||||
{
|
||||
JOptionPane.showMessageDialog(HackRFTunerEditor.this, "HackRF Tuner Controller"
|
||||
+ " - couldn't apply the sample rate setting [" + sampleRate.getLabel() +
|
||||
@@ -329,85 +329,35 @@ public class HackRFTunerEditor extends TunerEditor<HackRFTuner,HackRFTunerConfig
|
||||
|
||||
private String getTunerInfo()
|
||||
{
|
||||
HackRFTunerController.BoardID board = HackRFTunerController.BoardID.INVALID;
|
||||
|
||||
try
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
board = getTuner().getController().getBoardID();
|
||||
}
|
||||
}
|
||||
catch(UsbException e)
|
||||
{
|
||||
mLog.error("couldn't read HackRF board identifier", e);
|
||||
}
|
||||
HackRFTunerController controller = hasTuner() ? getTuner().getController() : null;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("<html><h3>HackRF Tuner</h3>");
|
||||
sb.append("<b>Board: </b>");
|
||||
sb.append(board.getLabel());
|
||||
sb.append("<br>");
|
||||
|
||||
HackRFTunerController.Serial serial = null;
|
||||
appendInfo(sb, "Board", controller != null ? controller.getBoardName() : null);
|
||||
appendInfo(sb, "Serial", controller != null ? controller.getSerialNumber() : null);
|
||||
appendInfo(sb, "Part", controller != null ? controller.getPartId() : null);
|
||||
appendInfo(sb, "Firmware", controller != null ? controller.getFirmwareVersion() : null);
|
||||
|
||||
try
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
serial = getTuner().getController().getSerial();
|
||||
}
|
||||
appendInfo(sb, "Library", "libhackrf " + HackRf.libraryVersion() + " (" + HackRf.libraryRelease() + ")");
|
||||
}
|
||||
catch(Exception e)
|
||||
catch(RuntimeException | LinkageError e)
|
||||
{
|
||||
mLog.error("couldn't read HackRF serial number", e);
|
||||
}
|
||||
|
||||
if(serial != null)
|
||||
{
|
||||
sb.append("<b>Serial: </b>");
|
||||
sb.append(serial.getSerialNumber());
|
||||
sb.append("<br>");
|
||||
|
||||
sb.append("<b>Part: </b>");
|
||||
sb.append(serial.getPartID());
|
||||
sb.append("<br>");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("<b>Serial: Unknown</b><br>");
|
||||
sb.append("<b>Part: Unknown</b><br>");
|
||||
}
|
||||
|
||||
String firmware = null;
|
||||
|
||||
try
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
firmware = getTuner().getController().getFirmwareVersion();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("couldn't read HackRF firmware version", e);
|
||||
}
|
||||
|
||||
if(firmware != null)
|
||||
{
|
||||
sb.append("<b>Firmware: </b>");
|
||||
sb.append(firmware);
|
||||
sb.append("<br>");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("<b>Firmware: Unknown</b><br>");
|
||||
mLog.error("couldn't read libhackrf version", e);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a labeled tuner info value, or Unknown when the value is not available.
|
||||
*/
|
||||
private static void appendInfo(StringBuilder sb, String label, String value)
|
||||
{
|
||||
sb.append("<b>").append(label).append(": </b>").append(value != null ? value : "Unknown").append("<br>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
|
||||
@@ -1,854 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner.usb;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferFactory;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
import io.github.dsheirer.source.tuner.TunerType;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.usb4java.Context;
|
||||
import org.usb4java.Device;
|
||||
import org.usb4java.DeviceDescriptor;
|
||||
import org.usb4java.DeviceHandle;
|
||||
import org.usb4java.DeviceList;
|
||||
import org.usb4java.LibUsb;
|
||||
import org.usb4java.Transfer;
|
||||
import org.usb4java.TransferCallback;
|
||||
|
||||
/**
|
||||
* Tuner controller implementation for USB tuners. Manages general USB operations and incorporates threaded USB
|
||||
* Transfer processing.
|
||||
*/
|
||||
public abstract class USBTunerController extends TunerController
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(USBTunerController.class);
|
||||
private static final int USB_INTERFACE = 0x0; //Common value for all currently supported devices
|
||||
private static final int USB_CONFIGURATION = 0x1; //Common value for all currently supported devices
|
||||
private static final int USB_BULK_TRANSFER_BUFFER_POOL_SIZE = 8;
|
||||
protected static final byte USB_BULK_TRANSFER_ENDPOINT = (byte) 0x81;
|
||||
private static final long USB_BULK_TRANSFER_TIMEOUT_MS = 2000l;
|
||||
|
||||
protected int mBus;
|
||||
protected String mPortAddress;
|
||||
private Context mDeviceContext = new Context();
|
||||
private Device mDevice;
|
||||
private DeviceHandle mDeviceHandle;
|
||||
private DeviceDescriptor mDeviceDescriptor;
|
||||
private TransferManager mTransferManager = new TransferManager();
|
||||
private UsbEventProcessor mEventProcessor = new UsbEventProcessor();
|
||||
private AtomicBoolean mStreaming = new AtomicBoolean();
|
||||
private boolean mRunning = false;
|
||||
|
||||
//Troubleshooting libusb bug: https://github.com/DSheirer/sdrtrunk/issues/1253
|
||||
private int mAnomalousTransfersDetected = 0;
|
||||
|
||||
/**
|
||||
* USB tuner controller class. Provides auto-start and auto-stop function when complex buffer listeners are added
|
||||
* or removed from this tuner controller.
|
||||
*
|
||||
* @param bus number USB
|
||||
* @param portAddress address USB
|
||||
* @param tunerErrorListener to receive errors from this tuner controller
|
||||
*/
|
||||
public USBTunerController(int bus, String portAddress, ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
super(tunerErrorListener);
|
||||
mBus = bus;
|
||||
mPortAddress = portAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param bus usb
|
||||
* @param portAddress usb
|
||||
* @param minimum tunable frequency in Hertz
|
||||
* @param maximum tunable frequency in Hertz
|
||||
* @param halfBandwidth that is unusable for DC spike avoidance
|
||||
* @param usablePercent bandwith in Hertz
|
||||
* @param tunerErrorListener to receive errors from this tuner controller
|
||||
*/
|
||||
public USBTunerController(int bus, String portAddress, long minimum, long maximum, int halfBandwidth, double usablePercent,
|
||||
ITunerErrorListener tunerErrorListener)
|
||||
{
|
||||
this(bus, portAddress, tunerErrorListener);
|
||||
setMinimumFrequency(minimum);
|
||||
setMaximumFrequency(maximum);
|
||||
setMiddleUnusableHalfBandwidth(halfBandwidth);
|
||||
setUsableBandwidthPercentage(usablePercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner type for this USB controller
|
||||
*/
|
||||
public abstract TunerType getTunerType();
|
||||
|
||||
/**
|
||||
* Factory for converting received streaming sample data into native buffers, as provided by sub-class.
|
||||
*/
|
||||
protected abstract INativeBufferFactory getNativeBufferFactory();
|
||||
|
||||
/**
|
||||
* Sub-class definition of transfer buffer sizes to use for the tuner. Note: this should be a power-of-two
|
||||
* value for compatibility with downstream (SIMD) operations (e.g. 65,536, 131072, 262144, etc.)
|
||||
* @return transfer buffer size.
|
||||
*/
|
||||
protected abstract int getTransferBufferSize();
|
||||
|
||||
/**
|
||||
* Sub-class method to perform additional device setup steps after the USB interface has been claimed and before any
|
||||
* transfer operations start.
|
||||
* @throws SourceException if there is an issue in configuring the device
|
||||
*/
|
||||
protected abstract void deviceStart() throws SourceException;
|
||||
|
||||
/**
|
||||
* Sub-class method to perform additional device shutdown steps after transfer processing has stopped and before
|
||||
* the USB interface is released.
|
||||
*/
|
||||
protected abstract void deviceStop();
|
||||
|
||||
/**
|
||||
* Starts or initializes this tuner.
|
||||
*
|
||||
* Note: sub-class implementations should override and invoke this method and then perform additional initialization
|
||||
* operations for the tuner.
|
||||
*
|
||||
* @throws SourceException if there is an error and/or this tuner is unusable.
|
||||
*/
|
||||
public final void start() throws SourceException
|
||||
{
|
||||
if(mDeviceContext == null)
|
||||
{
|
||||
throw new SourceException("Device cannot be reused once it has been shutdown");
|
||||
}
|
||||
|
||||
int status = LibUsb.init(mDeviceContext);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
mDevice = findDevice();
|
||||
|
||||
if(mDevice == null)
|
||||
{
|
||||
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||
}
|
||||
|
||||
mDeviceDescriptor = new DeviceDescriptor();
|
||||
status = LibUsb.getDeviceDescriptor(mDevice, mDeviceDescriptor);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
mDeviceHandle = new DeviceHandle();
|
||||
status = LibUsb.open(mDevice, mDeviceHandle);
|
||||
|
||||
//Now that we have opened the device and added an additional reference, remove the original reference placed on
|
||||
// the device during the findDevice() operation
|
||||
LibUsb.unrefDevice(mDevice);
|
||||
|
||||
if(status == LibUsb.ERROR_ACCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
|
||||
mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules");
|
||||
throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules");
|
||||
}
|
||||
else if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
|
||||
mLog.error("Can't open USB tuner - check driver or Linux udev rules");
|
||||
throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
//Detach the kernel driver if active and detach is supported. Otherwise, let the claim interface fail.
|
||||
status = LibUsb.kernelDriverActive(mDeviceHandle, USB_INTERFACE);
|
||||
|
||||
if(status == 1) //kernel driver is attached and detach operation is supported
|
||||
{
|
||||
status = LibUsb.detachKernelDriver(mDeviceHandle, USB_INTERFACE);
|
||||
|
||||
if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mLog.error("Unable to detach kernel driver for USB tuner device - bus:" + mBus + " port:" + mPortAddress);
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't detach kernel driver");
|
||||
}
|
||||
}
|
||||
|
||||
//Set the configuration which also invokes a soft reset on the device
|
||||
status = LibUsb.setConfiguration(mDeviceHandle, USB_CONFIGURATION);
|
||||
|
||||
if(status == LibUsb.ERROR_BUSY)
|
||||
{
|
||||
mLog.error("Unable to set USB configuration on tuner - device is busy (in use by another application)");
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("USB tuner is in-use by another application");
|
||||
}
|
||||
else if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't set configuration (ie reset) on the USB tuner - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
//Claim the interface
|
||||
status = LibUsb.claimInterface(mDeviceHandle, USB_INTERFACE);
|
||||
|
||||
if(status == LibUsb.ERROR_BUSY)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("USB tuner is in-use by another application");
|
||||
}
|
||||
else if(status != LibUsb.SUCCESS)
|
||||
{
|
||||
mDeviceHandle = null;
|
||||
mDeviceDescriptor = null;
|
||||
throw new SourceException("Can't claim interface on USB tuner - " + LibUsb.errorName(status));
|
||||
}
|
||||
|
||||
//Set running true for deviceStart() operations that require it.
|
||||
mRunning = true;
|
||||
|
||||
try
|
||||
{
|
||||
deviceStart();
|
||||
}
|
||||
catch(Exception se)
|
||||
{
|
||||
mRunning = false;
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the tuner for full shutdown by stopping streaming, shutdown the device, and releasing the USB resources.
|
||||
*/
|
||||
public final void stop()
|
||||
{
|
||||
mRunning = false;
|
||||
|
||||
//Spin the shutdown onto a new thread so that we can set a max wait threshold.
|
||||
Thread t = new Thread(() -> {
|
||||
stopStreaming();
|
||||
mNativeBufferBroadcaster.clear();
|
||||
deviceStop();
|
||||
});
|
||||
|
||||
t.start();
|
||||
|
||||
try
|
||||
{
|
||||
//Wait up to 500 milliseconds for the shutdown to complete ... otherwise interrupt it and finish shutdown
|
||||
t.join(500);
|
||||
|
||||
if(t.isAlive())
|
||||
{
|
||||
mLog.info("Tuner shutdown exceeded 500ms - forcing shutdown");
|
||||
t.interrupt();
|
||||
}
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
}
|
||||
|
||||
//Release transfers
|
||||
mTransferManager.freeTransfers();
|
||||
|
||||
if(mDeviceHandle != null)
|
||||
{
|
||||
LibUsb.releaseInterface(mDeviceHandle, USB_INTERFACE);
|
||||
LibUsb.close(mDeviceHandle);
|
||||
mDeviceHandle = null;
|
||||
mDevice = null;
|
||||
mDeviceDescriptor = null;
|
||||
}
|
||||
|
||||
LibUsb.exit(mDeviceContext);
|
||||
mDeviceContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts streaming data from the tuner
|
||||
*/
|
||||
private void startStreaming()
|
||||
{
|
||||
if(mStreaming.compareAndSet(false, true))
|
||||
{
|
||||
try
|
||||
{
|
||||
prepareStreaming();
|
||||
List<Transfer> transfers = mTransferManager.getTransfers();
|
||||
mEventProcessor.start();
|
||||
mTransferManager.setAutoResubmitTransfers(true);
|
||||
mTransferManager.submitTransfers(transfers);
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error starting streaming on USB tuner", se);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to start streaming. This method can be overridden by sub-class to implement additional actions
|
||||
* need to prepare before start streaming.
|
||||
*/
|
||||
protected void prepareStreaming()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop streaming data from the tuner
|
||||
*/
|
||||
private void stopStreaming()
|
||||
{
|
||||
if(mStreaming.compareAndSet(true, false))
|
||||
{
|
||||
//Turn off auto-resubmit of USB transfer buffers
|
||||
mTransferManager.setAutoResubmitTransfers(false);
|
||||
|
||||
//Stop event processing thread to put all submitted tranfers in a stable state - blocks until stopped
|
||||
mEventProcessor.stop();
|
||||
|
||||
//Cancel all currently submitted transfers
|
||||
mTransferManager.cancelTransfers();
|
||||
|
||||
//Perform final event processing iteration so LibUsb returns all of our cancelled tranfers
|
||||
mEventProcessor.handleFinalEvents();
|
||||
|
||||
streamingCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post streaming cleanup actions. This method can be overridden by sub-class to implement additional actions
|
||||
* needed to cleanup after streaming stops.
|
||||
*/
|
||||
protected void streamingCleanup()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the USB device for this tuner at the specified USB bus and port.
|
||||
* @return discovered USB device
|
||||
* @throws SourceException if there is an error or the device is not discovered.
|
||||
*/
|
||||
private Device findDevice() throws SourceException
|
||||
{
|
||||
Device foundDevice = null;
|
||||
|
||||
DeviceList deviceList = new DeviceList();
|
||||
int count = LibUsb.getDeviceList(mDeviceContext, deviceList);
|
||||
|
||||
if(count >= 0)
|
||||
{
|
||||
for(Device device: deviceList)
|
||||
{
|
||||
int bus = LibUsb.getBusNumber(device);
|
||||
int port = LibUsb.getPortNumber(device);
|
||||
|
||||
if(port > 0)
|
||||
{
|
||||
String portAddress = TunerManager.getPortAddress(device);
|
||||
|
||||
if(mBus == bus && mPortAddress != null && mPortAddress.equals(portAddress))
|
||||
{
|
||||
foundDevice = device;
|
||||
}
|
||||
else
|
||||
{
|
||||
LibUsb.unrefDevice(device);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LibUsb.unrefDevice(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Free the device list but don't auto-unref all the devices ... we already did that during iteration
|
||||
LibUsb.freeDeviceList(deviceList, false);
|
||||
|
||||
if(foundDevice != null)
|
||||
{
|
||||
return foundDevice;
|
||||
}
|
||||
|
||||
throw new SourceException("LibUsb couldn't discover USB device [" + mBus + ":" + mPortAddress +
|
||||
"] from device list" + (count < 0 ? " - error: " + LibUsb.errorName(count) : ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the discovered USB device.
|
||||
*/
|
||||
protected Device getDevice()
|
||||
{
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* LibUsb context for this device.
|
||||
*/
|
||||
protected Context getDeviceContext()
|
||||
{
|
||||
return mDeviceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* LibUsb device descriptor for this device
|
||||
*/
|
||||
protected DeviceDescriptor getDeviceDescriptor()
|
||||
{
|
||||
return mDeviceDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* USB Device Handle for the claimed device
|
||||
*/
|
||||
protected DeviceHandle getDeviceHandle()
|
||||
{
|
||||
return mDeviceHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the device handle is non-null
|
||||
*/
|
||||
protected boolean hasDeviceHandle()
|
||||
{
|
||||
return getDeviceHandle() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this device is usable, meaning it has been started and is not yet stopping.
|
||||
*
|
||||
* Note: this is a general usability flag for controlling all code that touches the USB interface(s)
|
||||
*/
|
||||
protected boolean isRunning()
|
||||
{
|
||||
return mRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the IQ buffer listener and automatically starts stream buffer transfer processing, if not already started.
|
||||
*/
|
||||
@Override
|
||||
public void addBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
if(isRunning())
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
boolean hasExistingListeners = hasBufferListeners();
|
||||
|
||||
super.addBufferListener(listener);
|
||||
|
||||
if(!hasExistingListeners)
|
||||
{
|
||||
startStreaming();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the IQ buffer listener and stops stream buffer transfer processing if there are no more listeners.
|
||||
*/
|
||||
@Override
|
||||
public void removeBufferListener(Listener<INativeBuffer> listener)
|
||||
{
|
||||
getLock().lock();
|
||||
|
||||
try
|
||||
{
|
||||
super.removeBufferListener(listener);
|
||||
|
||||
if(!hasBufferListeners())
|
||||
{
|
||||
stopStreaming();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
getLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages USB transfer (ie zero-copy) buffer processing
|
||||
*/
|
||||
class TransferManager implements TransferCallback
|
||||
{
|
||||
private List<Transfer> mAvailableTransfers;
|
||||
private LinkedTransferQueue<Transfer> mInProgressTransfers = new LinkedTransferQueue<>();
|
||||
private boolean mAutoResubmitTransfers = false;
|
||||
private int mTransferErrorCount = 0;
|
||||
private List<Transfer> mErrorTransfers = new ArrayList<>();
|
||||
private int mResubmitFailureLogCount = 0;
|
||||
|
||||
/**
|
||||
* Creates USB Transfers to carry the streaming sample data. Transfer buffers are backed by native memory
|
||||
* byte buffers outside the JVM.
|
||||
*
|
||||
* @return list of transfers
|
||||
* @throws SourceException if there is an error creating transfers
|
||||
*/
|
||||
private List<Transfer> getTransfers() throws SourceException
|
||||
{
|
||||
if(mAvailableTransfers == null)
|
||||
{
|
||||
mAvailableTransfers = new ArrayList<>();
|
||||
|
||||
for(int x = 0; x < USB_BULK_TRANSFER_BUFFER_POOL_SIZE; x++)
|
||||
{
|
||||
Transfer transfer = LibUsb.allocTransfer();
|
||||
|
||||
if(transfer == null)
|
||||
{
|
||||
throw new SourceException("Couldn't allocate USB transfer buffer - out of memory");
|
||||
}
|
||||
|
||||
final ByteBuffer buffer = ByteBuffer.allocateDirect(getTransferBufferSize());
|
||||
|
||||
LibUsb.fillBulkTransfer(transfer, mDeviceHandle, USB_BULK_TRANSFER_ENDPOINT, buffer,
|
||||
TransferManager.this, "Transfer Buffer " + x, USB_BULK_TRANSFER_TIMEOUT_MS);
|
||||
|
||||
mAvailableTransfers.add(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
return mAvailableTransfers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to stop processing transfers when stopping streaming of data.
|
||||
*/
|
||||
private void setAutoResubmitTransfers(boolean resubmit)
|
||||
{
|
||||
mAutoResubmitTransfers = resubmit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the transfers to start sample stream processing
|
||||
* @param transfers to submit
|
||||
*/
|
||||
private void submitTransfers(List<Transfer> transfers)
|
||||
{
|
||||
for(Transfer transfer: transfers)
|
||||
{
|
||||
submitTransfer(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)Submits the transfer for stream processing
|
||||
*
|
||||
* Note: synchronized used here because there can be multiple threads can invoke LibUsb.handleTimeoutEvents
|
||||
* (scheduled thread pool and a dedicated shutdown thread) during tuner shutdown and this has caused transfer
|
||||
* tracking issues.
|
||||
*
|
||||
* @param transfer to (re)submit
|
||||
*/
|
||||
private synchronized void submitTransfer(Transfer transfer)
|
||||
{
|
||||
int status = LibUsb.submitTransfer(transfer);
|
||||
|
||||
if(status == LibUsb.SUCCESS)
|
||||
{
|
||||
mInProgressTransfers.add(transfer);
|
||||
|
||||
//Attempt to resubmit any previous transfers that failed on submit
|
||||
if(!mErrorTransfers.isEmpty())
|
||||
{
|
||||
Transfer toResubmit = mErrorTransfers.remove(0);
|
||||
int resubmitStatus = LibUsb.submitTransfer(toResubmit);
|
||||
|
||||
if(resubmitStatus == LibUsb.SUCCESS)
|
||||
{
|
||||
mInProgressTransfers.add(transfer);
|
||||
|
||||
//Only log this if more than half of the total transfer buffers are in error-holding
|
||||
if(mErrorTransfers.size() >= (mAvailableTransfers.size() / 2))
|
||||
{
|
||||
mLog.info("Successfully resubmitted previous error USB transfer buffer. Current transfer buffer" +
|
||||
" status (error queue/total available) [" + mErrorTransfers.size() + "/" +
|
||||
mAvailableTransfers.size() + "]");
|
||||
}
|
||||
}
|
||||
else if(resubmitStatus == LibUsb.ERROR_BUSY)
|
||||
{
|
||||
//Ignore - this indicates the transfer was previously submitted and libusb is still working it.
|
||||
}
|
||||
else
|
||||
{
|
||||
//Add it back to the queue to try again later.
|
||||
mErrorTransfers.add(transfer);
|
||||
mTransferErrorCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(status == LibUsb.ERROR_BUSY)
|
||||
{
|
||||
//Ignore - this indicates the transfer was previously submitted and libusb is still working it. I'm not
|
||||
//sure how this happens because we give libusb a transfer and it hands it back when it's full. If
|
||||
//libusb is still working it, then why did it indicate the transfer was completed? So, we simply
|
||||
//ignore this error code. Other libraries simply ignore the submit status code altogether.
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("USB transfer [" + transfer + "] submit attempt failed with error [" + LibUsb.errorName(status) +
|
||||
"] - adding to error queue to resubmit later - this may be a temporary USB issue and has happened [" +
|
||||
mTransferErrorCount + "] time(s) so far. Current transfer error queue (error/total) [" +
|
||||
mErrorTransfers.size() + "/" + mAvailableTransfers.size() + "]");
|
||||
|
||||
mErrorTransfers.add(transfer);
|
||||
mTransferErrorCount++;
|
||||
}
|
||||
|
||||
if(mErrorTransfers.size() >= mAvailableTransfers.size())
|
||||
{
|
||||
mLog.error("Maximum USB transfer buffer errors reached - transfer buffers exhausted - shutting down USB tuner");
|
||||
ThreadPool.CACHED.submit(() -> setErrorMessage("USB Error - Transfer Buffers Exhausted"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels any in-progress transfers to prepare for shutdown.
|
||||
*
|
||||
* Note: this should only be invoked after the LibUsb event processing thread has been stopped so that the
|
||||
* transfer buffers are in a stable (submitted vs callback) state and we can then flip their cancel state and
|
||||
* then finish processing the timeout events under the control of a single (shutdown) thread.
|
||||
*/
|
||||
private void cancelTransfers()
|
||||
{
|
||||
for(Transfer transfer: mInProgressTransfers)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
for(Transfer transfer: mErrorTransfers)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees/disposes allocated USB transfer buffers.
|
||||
*/
|
||||
private void freeTransfers()
|
||||
{
|
||||
if(mAvailableTransfers != null)
|
||||
{
|
||||
for(Transfer transfer: mAvailableTransfers)
|
||||
{
|
||||
try
|
||||
{
|
||||
LibUsb.freeTransfer(transfer);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error releasing allocated USB transfer buffer during tuner shutdown: " +
|
||||
e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
mAvailableTransfers.clear();
|
||||
mAvailableTransfers = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTransfer(Transfer transfer)
|
||||
{
|
||||
mInProgressTransfers.remove(transfer);
|
||||
|
||||
if(mErrorTransfers.contains(transfer))
|
||||
{
|
||||
mLog.warn("USB transfer [" + transfer + "] that was being tracked as an error transfer, has just been " +
|
||||
"delivered as completed with transfer status [" + LibUsb.errorName(transfer.status()) +
|
||||
"] - removing it from the transfer error queue");
|
||||
mErrorTransfers.remove(transfer);
|
||||
}
|
||||
|
||||
switch(transfer.status())
|
||||
{
|
||||
case LibUsb.TRANSFER_COMPLETED:
|
||||
case LibUsb.TRANSFER_STALL:
|
||||
case LibUsb.TRANSFER_TIMED_OUT:
|
||||
case LibUsb.TRANSFER_ERROR:
|
||||
//Note: cancel flag can be set by libusb, independent of commanded cancel of transfers - we simply
|
||||
//resubmit the transfer for continued use.
|
||||
case LibUsb.TRANSFER_CANCELLED:
|
||||
int transferLength = transfer.actualLength();
|
||||
|
||||
if(transferLength > 0)
|
||||
{
|
||||
dispatchTransfer(transfer);
|
||||
}
|
||||
|
||||
transfer.buffer().rewind();
|
||||
|
||||
if(mAutoResubmitTransfers)
|
||||
{
|
||||
submitTransfer(transfer);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//Unexpected transfer error - shutdown the tuner
|
||||
transfer.buffer().rewind();
|
||||
|
||||
//Only set an error if we're not shutting down
|
||||
if(mAutoResubmitTransfers)
|
||||
{
|
||||
//spin this off onto the thread pool, so it doesn't impact the usb processor thread.
|
||||
ThreadPool.CACHED.submit(() -> setErrorMessage("LibUsb Transfer Error - stopping device - " +
|
||||
"status [" + transfer.status() + "] - " + LibUsb.errorName(transfer.status())));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of the transfer's native memory byte array payload so that the transfer can be reused.
|
||||
* Dispatches the native buffer to registered listeners.
|
||||
* @param transfer to copy and dispatch
|
||||
*/
|
||||
private void dispatchTransfer(Transfer transfer)
|
||||
{
|
||||
//Pass the transfer's byte buffer so the native buffer factory can make a copy of the byte array contents
|
||||
//and package it as a native buffer.
|
||||
INativeBuffer nativeBuffer = getNativeBufferFactory().getBuffer(transfer.buffer(), System.currentTimeMillis());
|
||||
mNativeBufferBroadcaster.broadcast(nativeBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Threaded LibUsb event processor - continuously polls LibUsb to process events exclusively for this USB tuner
|
||||
* device using the device context.
|
||||
*/
|
||||
class UsbEventProcessor implements Runnable
|
||||
{
|
||||
private Thread mThread;
|
||||
private boolean mProcessing = false;
|
||||
|
||||
/**
|
||||
* Start the event processing thread
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mThread == null)
|
||||
{
|
||||
mProcessing = true;
|
||||
mThread = new Thread(this);
|
||||
mThread.setName("sdrtrunk USB tuner - bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||
mThread.setPriority(Thread.MAX_PRIORITY);
|
||||
mThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stop processing flag and block until the thread stops, blocking up to 1000 ms.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mProcessing = false;
|
||||
|
||||
try
|
||||
{
|
||||
//Give the thread a second to stop - it should happen quickly because it's only checking transfers
|
||||
//for completed status and returning them to us to dispatch.
|
||||
mThread.join(1000);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error stopping LibUsb event processing thread - " + e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
mThread = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This performs a final handle-events invocation after the event processing thread has been shutdown and
|
||||
* transfers have been flagged as cancelled. This should cause LibUsb to return all in-progress and now
|
||||
* canceled transfers back to us via the TransferManager.processTransfer() method.
|
||||
*/
|
||||
public void handleFinalEvents()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Use a short timeout since this is a shutdown operation
|
||||
LibUsb.handleEventsTimeout(mDeviceContext, 50);
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while processing stop-streaming LibUsb timeout events", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LibUsb event/timeout processing loop
|
||||
*/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mProcessing = true;
|
||||
|
||||
while(mProcessing)
|
||||
{
|
||||
try
|
||||
{
|
||||
LibUsb.handleEventsTimeout(mDeviceContext, 250);
|
||||
}
|
||||
catch(Throwable throwable)
|
||||
{
|
||||
mLog.error("Error while processing LibUsb timeout events", throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.source.tuner.usb;
|
||||
|
||||
import javax.usb.UsbConfiguration;
|
||||
import javax.usb.UsbDevice;
|
||||
import javax.usb.UsbDisconnectedException;
|
||||
import javax.usb.UsbEndpoint;
|
||||
import javax.usb.UsbException;
|
||||
import javax.usb.UsbHostManager;
|
||||
import javax.usb.UsbHub;
|
||||
import javax.usb.UsbInterface;
|
||||
import javax.usb.UsbServices;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class UsbUtils
|
||||
{
|
||||
/**
|
||||
* Returns a List of currently connected usb devices
|
||||
*/
|
||||
public static List<UsbDevice> getDevices() throws SecurityException, UsbException, UnsupportedEncodingException
|
||||
{
|
||||
UsbServices services = UsbHostManager.getUsbServices();
|
||||
UsbHub root = services.getRootUsbHub();
|
||||
List<UsbDevice> devices = new ArrayList<UsbDevice>(getHubDevices(root));
|
||||
return devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of devices attached to the hub
|
||||
*/
|
||||
public static List<UsbDevice> getHubDevices(UsbHub hub) throws UnsupportedEncodingException, UsbException
|
||||
{
|
||||
List<UsbDevice> devices = new ArrayList<UsbDevice>();
|
||||
List<UsbDevice> children = hub.getAttachedUsbDevices();
|
||||
|
||||
for(UsbDevice child : children)
|
||||
{
|
||||
if(child.isUsbHub())
|
||||
{
|
||||
devices.addAll(getHubDevices((UsbHub) child));
|
||||
}
|
||||
else
|
||||
{
|
||||
devices.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
public static String getDeviceDetails(UsbDevice device) throws UsbException, UnsupportedEncodingException, UsbDisconnectedException
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(device.getUsbDeviceDescriptor().toString()).append("\n\n");
|
||||
|
||||
for(Object configObject : device.getUsbConfigurations())
|
||||
{
|
||||
UsbConfiguration config = (UsbConfiguration) configObject;
|
||||
|
||||
sb.append(config.getUsbConfigurationDescriptor().toString()).append("\n\n");
|
||||
|
||||
for(Object interfaceObject : config.getUsbInterfaces())
|
||||
{
|
||||
UsbInterface iface = (UsbInterface) interfaceObject;
|
||||
|
||||
sb.append(iface.getUsbInterfaceDescriptor().toString()).append("\n\n");
|
||||
|
||||
for(Object endpointObject : iface.getUsbEndpoints())
|
||||
{
|
||||
UsbEndpoint endpoint = (UsbEndpoint) endpointObject;
|
||||
|
||||
sb.append(endpoint.getUsbEndpointDescriptor().toString()).append("\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getDeviceClass(byte deviceClass)
|
||||
{
|
||||
switch(deviceClass)
|
||||
{
|
||||
case 0:
|
||||
return "Unknown Device - Class 0";
|
||||
case 2:
|
||||
return "Communications Device";
|
||||
case 3:
|
||||
return "HID Device";
|
||||
case 5:
|
||||
return "Physical Device";
|
||||
case 6:
|
||||
return "Still Imaging Device";
|
||||
case 7:
|
||||
return "Printer Device";
|
||||
case 8:
|
||||
return "Mass Storage Device";
|
||||
case 9:
|
||||
return "Hub Device";
|
||||
case 0xA:
|
||||
return "Communications Device";
|
||||
case 0xB:
|
||||
return "Smart Card Device";
|
||||
case 0xD:
|
||||
return "Content Security Device";
|
||||
case 0xE:
|
||||
return "Video Device";
|
||||
case 0xF:
|
||||
return "Personal Healthcare Device";
|
||||
case 0x10:
|
||||
return "Audio/Video Device";
|
||||
case (byte)0xDC:
|
||||
return "Diagnostic Device";
|
||||
case (byte)0xE0:
|
||||
return "Wireless Controller Device";
|
||||
case (byte)0xEF:
|
||||
return "Miscellaneous Device";
|
||||
default:
|
||||
return "Unknown Device - Class " + deviceClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
javax.usb.services = org.usb4java.javax.Services
|
||||
Reference in New Issue
Block a user