#1190 Tuner management redesign. Updates all tuners with direct threaded support. Incorporates USB Hotplug support. Adds black/white listing of tuners. Updates all tuner editors to have a common look and feel. Changes location of tuner configuration file and changes format to JSON. (#1208)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-04-18 07:30:58 -04:00
committed by GitHub
parent 0fb148d31e
commit 96d3996bec
259 changed files with 11974 additions and 12454 deletions

View File

@@ -1,18 +1,21 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* 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 free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.source.tuner.manager;
import io.github.dsheirer.source.tuner.TunerController;
@@ -133,7 +136,7 @@ public class CenterFrequencyCalculator
public static boolean canTune(TunerChannel channel, TunerController tunerController, SortedSet<TunerChannel> channels)
{
//Make sure we're within the tunable frequency range of this tuner
if(tunerController.getMinFrequency() < channel.getMinFrequency() && tunerController.getMaxFrequency() > channel.getMaxFrequency())
if(tunerController.getMinimumFrequency() < channel.getMinFrequency() && tunerController.getMaximumFrequency() > channel.getMaxFrequency())
{
//If this is the first lock, then we're good
if(channels.isEmpty())

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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/>
* * *****************************************************************************
* 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.manager;
@@ -50,6 +47,11 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
*/
public abstract int getTunerChannelCount();
/**
* Tells this channel source manager to shutdown all tuner channel sources to prepare for tuner shutdown.
*/
public abstract void stopAllChannels();
/**
* Obtains a source for the tuner channel or returns null if the channel cannot be sourced by this tuner.
*

View File

@@ -0,0 +1,276 @@
/*
* *****************************************************************************
* 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.manager;
import io.github.dsheirer.source.SourceException;
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.configuration.TunerConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A discovered tuner that may be accessible for use.
*/
public abstract class DiscoveredTuner implements ITunerErrorListener
{
private Logger mLog = LoggerFactory.getLogger(DiscoveredTuner.class);
private TunerStatus mTunerStatus = TunerStatus.ENABLED;
private boolean mEnabled = true;
private String mErrorMessage;
private List<IDiscoveredTunerStatusListener> mListeners = new CopyOnWriteArrayList();
protected Tuner mTuner;
protected TunerConfiguration mTunerConfiguration;
/**
* Tuner Class
*/
public abstract TunerClass getTunerClass();
/**
* Current status of the discovered tuner
*/
public TunerStatus getTunerStatus()
{
return mTunerStatus;
}
/**
* Sets the status of the discovered tuner and notifies registered listeners of the status change.
* @param tunerStatus to set
*/
protected void setTunerStatus(TunerStatus tunerStatus)
{
TunerStatus previous = mTunerStatus;
mTunerStatus = tunerStatus;
broadcast(this, previous, mTunerStatus);
}
/**
* Indicates if this discovered tuner is enabled and usable.
*/
public boolean isEnabled()
{
return mEnabled;
}
/**
* Sets the enabled state of this discovered tuner
*/
public void setEnabled(boolean enabled)
{
//If there was a change in state
if(mEnabled ^ enabled)
{
mEnabled = enabled;
if(mEnabled)
{
setTunerStatus(TunerStatus.ENABLED);
start();
}
else
{
stop();
setTunerStatus(TunerStatus.DISABLED);
}
}
}
/**
* Indicates if this discovered tuner is available and usable. Use this method to check if a discovered tuner is
* available prior to access the tuner directly via the getTuner() method.
*/
public boolean isAvailable()
{
return getTunerStatus().isAvailable();
}
/**
* An identifier for this discovered tuner where this identifier when combined with the discovered tuner type
* is a globally unique value.
*
* Note: this identifier will be used to persist the enabled/disabled state of each discoverable tuner. Therefore,
* this identifier must be consistent across application run cycles in order to correctly manage disabled tuners.
* @return globally unique string identifier for the discovered tuner.
*/
public abstract String getId();
/**
* Access a started and initialized.
*
* Use the isAvailable() method to check if this tuner is available prior to invoking this method to avoid a
* null tuner instance.
*
* @return started and initialized tuner
*/
public Tuner getTuner()
{
return mTuner;
}
/**
* Indicates if this discovered tuner is started and has a fully constructed tuner instance
*/
public boolean hasTuner()
{
return getTuner() != null;
}
/**
* Tuner configuration for this tuner
*/
public TunerConfiguration getTunerConfiguration()
{
return mTunerConfiguration;
}
/**
* Sets the tuner configuration for this tuner.
*/
public void setTunerConfiguration(TunerConfiguration tunerConfiguration)
{
mTunerConfiguration = tunerConfiguration;
if(hasTuner())
{
try
{
getTuner().getTunerController().apply(mTunerConfiguration);
}
catch(SourceException se)
{
mLog.error("Error applying tuner configuration [" + mTunerConfiguration.getClass() +
"] to discovered tuner [" + getId() + "}");
}
}
}
/**
* Indicates if this discovered tuner has a tuner configuration
*/
public boolean hasTunerConfiguration()
{
return mTunerConfiguration != null;
}
/**
* Adds a tuner status change listener to monitor this discovered tuner for changes in status.
*/
public void addTunerStatusListener(IDiscoveredTunerStatusListener listener)
{
mListeners.add(listener);
}
/**
* Removes a tuner status change listener from monitoring this discovered tuner for changes in status.
*/
public void removeTunerStatusListener(IDiscoveredTunerStatusListener listener)
{
mListeners.remove(listener);
}
/**
* Broadcasts the tuner status change to all registered listeners.
* @param tuner that was changed
* @param previous tuner status
* @param current tuner status
*/
private void broadcast(DiscoveredTuner tuner, TunerStatus previous, TunerStatus current)
{
for(IDiscoveredTunerStatusListener listener: mListeners)
{
listener.tunerStatusUpdated(tuner, previous, current);
}
}
/**
* Sets this tuner to an error state and applies the error message
* @param errorMessage to set
*/
@Override
public void setErrorMessage(String errorMessage)
{
mErrorMessage = errorMessage;
setTunerStatus(TunerStatus.ERROR);
stop();
}
/**
* Indicates if this tuner has an error message.
*/
public boolean hasErrorMessage()
{
return mErrorMessage != null;
}
/**
* Optional error message.
* @return error message if there is one, or null if there is not.
*/
public String getErrorMessage()
{
return mErrorMessage;
}
/**
* Fully instantiate and start this discovered tuner to make it usable within the application. Implementations
* should attempt to instantiate the tuner and assign it to mTuner variable. If there is an error, invoke the
* setErrorMessage() to signal the tuner is unusable.
*/
public abstract void start();
/**
* Attempts to restart a tuner that's currently in an error state
*/
public void reset()
{
if(getTunerStatus() == TunerStatus.ERROR)
{
mErrorMessage = null;
if(isEnabled())
{
setTunerStatus(TunerStatus.ENABLED);
}
else
{
setTunerStatus(TunerStatus.DISABLED);
}
}
}
/**
* Stop this discovered tuner, notify registered listeners/consumers and release any resources that it is using.
*/
public void stop()
{
if(hasTuner())
{
mLog.info("Stopping Tuner: " + getId());
getTuner().stop();
mTuner = null;
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* *****************************************************************************
* 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.manager;
import io.github.dsheirer.preference.source.ChannelizerType;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A USB tuner that is discovered during the USB device traversal process that has a VID and PID that match a known
* USB tuner type that is currently supported by the software.
*/
public class DiscoveredUSBTuner extends DiscoveredTuner
{
private Logger mLog = LoggerFactory.getLogger(DiscoveredUSBTuner.class);
private TunerClass mTunerClass;
private int mBus;
private int mPort;
private ChannelizerType mChannelizerType;
/**
* Constructs an instance
* @param bus (USB) number
* @param port (USB) number
* @param channelizerType to use with the tuner
*/
public DiscoveredUSBTuner(TunerClass tunerClass, int bus, int port, ChannelizerType channelizerType)
{
mTunerClass = tunerClass;
mBus = bus;
mPort = port;
mChannelizerType = channelizerType;
}
/**
* Indicates if this USB tuner is plugged into the specified bus and port.
* @param bus number
* @param port number
* @return true if there is a match
*/
public boolean isAt(int bus, int port)
{
return mBus == bus && mPort == port;
}
/**
* Tuner class as determined from the USB VID and PID values
*/
public TunerClass getTunerClass()
{
return mTunerClass;
}
/**
* USB bus number
*/
public int getBus()
{
return mBus;
}
/**
* USB port number
*/
public int getPort()
{
return mPort;
}
@Override
public String getId()
{
StringBuilder sb = new StringBuilder();
sb.append(getTunerClass());
sb.append(" USB Bus:").append(getBus());
sb.append(" Port:").append(getPort());
return sb.toString();
}
@Override
public void start()
{
if(isAvailable() && !hasTuner())
{
try
{
mTuner = TunerFactory.getUsbTuner(getTunerClass(), getPort(), getBus(), this, mChannelizerType);
mTuner.start();
}
catch(SourceException se)
{
//Set error message to flag this tuner with error status and invoke stop()
setErrorMessage(se.getMessage());
mLog.error("Unable to start tuner [" + getTunerClass() + "] - error: " + getErrorMessage());
try
{
stop();
}
catch(Exception e)
{
//No-op on attempted cleanup
}
}
}
}
@Override
public String toString()
{
return "USB Tuner - " + getId();
}
}

View File

@@ -0,0 +1,164 @@
/*
* *****************************************************************************
* 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.manager;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
/**
* Monitors measured frequency error PPM values from the tuner and (when enabled) applies the current
* error measurement to the tuner controller to adjust the tuner's PPM and align the frequency value.
*/
public class FrequencyErrorCorrectionManager
{
private final static Logger mLog = LoggerFactory.getLogger(FrequencyErrorCorrectionManager.class);
private static final double FREQUENCY_CORRECTION_ERROR_THRESHOLD = 0.4;
private static final long AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS = 30 * 1000; //30 seconds
private long mObservationPeriodStart;
private double mPPMRequired;
private boolean mEnabled = true;
private DecimalFormat mDecimalFormat = new DecimalFormat("0.0");
private TunerController mTunerController;
/**
* Constructs a frequency error correction manager for the specified tuner controller.
*/
public FrequencyErrorCorrectionManager(TunerController tunerController)
{
mTunerController = tunerController;
}
/**
* Enables or disables automatic frequency PPM correction.
* @param enabled to true to turn on automatic PPM adjustments.
*/
public void setEnabled(boolean enabled)
{
mEnabled = enabled;
}
/**
* Indicates if Auto-PPM is enabled
*/
public boolean isEnabled()
{
return mEnabled;
}
/**
* Resets monitoring
*/
public void reset()
{
mObservationPeriodStart = 0;
mPPMRequired = 0;
}
/**
* Applies a PPM adjustment value to the tuner's current PPM setting, when auto-correction is enabled.
*/
private void applyCorrection()
{
if(mEnabled && mTunerController.hasMeasuredFrequencyError())
{
double frequencyCorrection = mTunerController.getFrequencyCorrection();
frequencyCorrection -= mTunerController.getPPMFrequencyError();
try
{
mLog.info("Auto-Correcting Tuner PPM to [" + mDecimalFormat.format(frequencyCorrection) + "]");
mTunerController.setFrequencyCorrection(frequencyCorrection);
reset();
}
catch(SourceException se)
{
mLog.error("Error while applying automatic frequency error PPM correction value: " + frequencyCorrection);
}
}
}
/**
* Updates the monitor with a new PPM frequency error measurement from the tuner.
*/
public void updatePPM(double ppm)
{
if(ppm > FREQUENCY_CORRECTION_ERROR_THRESHOLD)
{
if(mObservationPeriodStart == 0)
{
mObservationPeriodStart = System.currentTimeMillis();
mPPMRequired = ppm;
}
else
{
if(mPPMRequired > 0)
{
mPPMRequired = ppm;
if(mObservationPeriodStart + AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS < System.currentTimeMillis())
{
applyCorrection();
}
}
else
{
//Opposite polarity ppm value ... restart the timer
mObservationPeriodStart = System.currentTimeMillis();
mPPMRequired = ppm;
}
}
}
else if(ppm < -FREQUENCY_CORRECTION_ERROR_THRESHOLD)
{
if(mObservationPeriodStart == 0)
{
mObservationPeriodStart = System.currentTimeMillis();
mPPMRequired = ppm;
}
else
{
if(mPPMRequired < 0)
{
mPPMRequired = ppm;
if(mObservationPeriodStart + AUTO_CORRECTION_OBSERVATION_PERIOD_MILLISECONDS < System.currentTimeMillis())
{
applyCorrection();
}
}
else
{
//Opposite polarity ppm value ... restart the timer
mObservationPeriodStart = System.currentTimeMillis();
mPPMRequired = ppm;
}
}
}
else
{
reset();
}
}
}

View File

@@ -31,6 +31,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -45,12 +46,12 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
private final static int DELAY_BUFFER_DURATION_MILLISECONDS = 2000;
// private List<CICTunerChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
private List<HalfBandTunerChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
private SortedSet<TunerChannel> mTunerChannels = new TreeSet<>();
private TunerController mTunerController;
private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor();
private NativeSampleDelayBuffer mSampleDelayBuffer;
private boolean mRunning = true;
public HeterodyneChannelSourceManager(TunerController tunerController)
{
@@ -58,6 +59,26 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
mTunerController.addListener(this);
}
@Override
public void stopAllChannels()
{
mRunning = false;
List<TunerChannelSource> toStop = new ArrayList<>(mChannelSources);
for(TunerChannelSource tunerChannelSource: toStop)
{
try
{
tunerChannelSource.process(SourceEvent.tunerShutdown(tunerChannelSource));
}
catch(SourceException se)
{
mLog.error("Error stopping tuner channel source for tuner shutdown");
}
}
}
@Override
public SortedSet<TunerChannel> getTunerChannels()
{
@@ -73,14 +94,16 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
@Override
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{
if(!mRunning)
{
return null;
}
if(CenterFrequencyCalculator.canTune(tunerChannel, mTunerController, mTunerChannels))
{
try
{
//Attempt to create the channel source first, in case we get a filter design exception
// CICTunerChannelSource tunerChannelSource = new CICTunerChannelSource(mChannelSourceEventProcessor,
// tunerChannel, mTunerController.getSampleRate(), channelSpecification);
HalfBandTunerChannelSource tunerChannelSource = new HalfBandTunerChannelSource(mChannelSourceEventProcessor,
tunerChannel, mTunerController.getSampleRate(), channelSpecification);

View File

@@ -0,0 +1,34 @@
/*
* *****************************************************************************
* 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.manager;
/**
* Discovered tuner status change listener interface
*/
public interface IDiscoveredTunerStatusListener
{
/**
* Notification that a discovered tuner's status has changed.
* @param discoveredTuner that has a status change.
* @param previous tuner status
* @param current tuner status
*/
void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current);
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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/>
* * *****************************************************************************
* 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.manager;
@@ -33,6 +30,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -48,12 +46,33 @@ public class PassThroughSourceManager extends ChannelSourceManager
private TunerController mTunerController;
private SortedSet<TunerChannel> mTunerChannels = new TreeSet<>();
private List<TunerChannelSource> mTunerChannelSources = new CopyOnWriteArrayList<>();
private boolean mRunning = true;
public PassThroughSourceManager(TunerController tunerController)
{
mTunerController = tunerController;
}
@Override
public void stopAllChannels()
{
mRunning = false;
List<TunerChannelSource> toStop = new ArrayList<>(mTunerChannelSources);
for(TunerChannelSource tunerChannelSource: toStop)
{
try
{
tunerChannelSource.process(SourceEvent.tunerShutdown(tunerChannelSource));
}
catch(SourceException se)
{
mLog.error("Error stopping tuner channel source for tuner shutdown");
}
}
}
@Override
public SortedSet<TunerChannel> getTunerChannels()
{
@@ -69,6 +88,11 @@ public class PassThroughSourceManager extends ChannelSourceManager
@Override
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{
if(!mRunning)
{
return null;
}
PassThroughChannelSource channelSource = new PassThroughChannelSource(new SourceEventProxy(),
mTunerController, tunerChannel);

View File

@@ -31,6 +31,11 @@ import org.slf4j.LoggerFactory;
import java.util.SortedSet;
/**
* PolyphaseChannelSourceManager is responsible for managing the tuner's center tuned frequency and providing access to
* polyphase tuner channel sources (ie DDCs). This class is responsible for determining IF a requested channel can
* be provided and then adjusting the center frequency and provisioning a DDC Polyphase Tuner channel source.
*/
public class PolyphaseChannelSourceManager extends ChannelSourceManager
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSourceManager.class);
@@ -38,10 +43,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
private TunerController mTunerController;
/**
* PolyphaseChannelSourceManager is responsible for managing the tuner's center tuned frequency and providing access to
* polyphase tuner channel sources (ie DDCs). This class is responsible for determining IF a requested channel can
* be provided and then adjusting the center frequency and provisioning a DDC Polyphase Tuner channel source.
*
* Constructs an instance
* @param tunerController with a center tuned frequency that will be managed by this instance
*/
public PolyphaseChannelSourceManager(TunerController tunerController)
@@ -54,6 +56,12 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
mTunerController.addListener(mPolyphaseChannelManager);
}
@Override
public void stopAllChannels()
{
mPolyphaseChannelManager.stopAllChannels();
}
/**
* Indicates if the channel min/max frequencies are within the tunable frequency range of the tuner controller
*

View File

@@ -0,0 +1,560 @@
/*
* *****************************************************************************
* 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.manager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.source.ChannelizerType;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.mixer.MixerManager;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.MultiFrequencyTunerChannelSource;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationManager;
import io.github.dsheirer.source.tuner.ui.DiscoveredTunerModel;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.usb4java.Context;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceList;
import org.usb4java.HotplugCallback;
import org.usb4java.HotplugCallbackHandle;
import org.usb4java.LibUsb;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Tuner manager provides access to tuners using USB, recording, sound-card and system-daemon accessible devices. This
* manager also supports hot-plug detection and black-listing of discovered tuners so that they can be used with other
* software applications.
*/
public class TunerManager implements IDiscoveredTunerStatusListener
{
private static final Logger mLog = LoggerFactory.getLogger(TunerManager.class);
private static final int MAXIMUM_USB_2_DATA_RATE = 480000000;
private UserPreferences mUserPreferences;
private DiscoveredTunerModel mDiscoveredTunerModel = new DiscoveredTunerModel();
private TunerConfigurationManager mTunerConfigurationManager;
private HotplugEventSupport mHotplugEventSupport = new HotplugEventSupport();
private Context mLibUsbApplicationContext = new Context();
private boolean mLibUsbInitialized = false;
/**
* Constructs an instance
* @param userPreferences for preferences
*/
public TunerManager(UserPreferences userPreferences)
{
mUserPreferences = userPreferences;
mTunerConfigurationManager = new TunerConfigurationManager(userPreferences);
}
/**
* Discovered tuner model
*/
public DiscoveredTunerModel getDiscoveredTunerModel()
{
return mDiscoveredTunerModel;
}
/**
* Starts the tuner manager and loads all available tuners
*/
public void start()
{
mLog.info("LibUSB API Version: " + LibUsb.getApiVersion());
mLog.info("LibUSB Version: " + LibUsb.getVersion());
int status = LibUsb.init(mLibUsbApplicationContext);
if(status == LibUsb.SUCCESS)
{
mLibUsbInitialized = true;
// LibUsb.setOption(mLibUsbApplicationContext, LibUsb.OPTION_LOG_LEVEL, LibUsb.LOG_LEVEL_DEBUG);
mHotplugEventSupport.start();
discoverUSBTuners();
}
discoverSdrPlayTuners();
discoverSoundCardTuners();
discoverRecordingTuners();
}
/**
* Stops or shuts down the tuner manager and releases all devices
*/
public void stop()
{
//Stop all tuners
mDiscoveredTunerModel.releaseDiscoveredTuners();
//Shutdown LibUsb
if(mLibUsbInitialized)
{
mHotplugEventSupport.stop();
LibUsb.exit(mLibUsbApplicationContext);
mLibUsbInitialized = false;
}
}
/**
* Requests to save the current state of the tuner configurations
*/
public void saveConfigurations()
{
mTunerConfigurationManager.saveConfigurations();
}
/**
* Discover or rediscovers USB tuners
*/
private void discoverUSBTuners()
{
if(mLibUsbInitialized)
{
try
{
DeviceList deviceList = new DeviceList();
int deviceCount = LibUsb.getDeviceList(mLibUsbApplicationContext, deviceList);
if(deviceCount >= 0)
{
for(Device device: deviceList)
{
addUsbTuner(device);
}
}
LibUsb.freeDeviceList(deviceList, true);
}
catch(Exception e)
{
mLog.error("LibUsb - error during USB device discovery", e);
}
}
}
/**
* Determines if the USB device is a supported tuner and add if it has not already been added/discovered.
* @param device to inspect and optionally add as a discovered tuner
*/
private void addUsbTuner(Device device)
{
int bus = LibUsb.getBusNumber(device);
int port = LibUsb.getPortNumber(device);
DeviceDescriptor deviceDescriptor = new DeviceDescriptor();
int status = LibUsb.getDeviceDescriptor(device, deviceDescriptor);
if(status == LibUsb.SUCCESS)
{
TunerClass tunerClass = TunerClass.lookup(deviceDescriptor.idVendor(), deviceDescriptor.idProduct());
if(tunerClass.isSupportedUsbTuner() && !mDiscoveredTunerModel.hasUsbTuner(bus, port))
{
mLog.info("Adding USB Bus [" + bus + "] Port [" + port + "] ID [" +
String.format("%04X", deviceDescriptor.idVendor()) + ":" +
String.format("%04X", deviceDescriptor.idProduct()) + "] Tuner Class [" + tunerClass + "]");
ChannelizerType channelizerType = mUserPreferences.getTunerPreference().getChannelizerType();
DiscoveredUSBTuner discoveredUSBTuner = new DiscoveredUSBTuner(tunerClass, bus, port, channelizerType);
discoveredUSBTuner.addTunerStatusListener(this);
//Set the tuner to disabled if the user has previously blacklisted the tuner
if(mTunerConfigurationManager.isDisabled(discoveredUSBTuner))
{
discoveredUSBTuner.setEnabled(false);
}
else
{
//Attempt to start the discovered tuner so we can determine the tuner type
discoveredUSBTuner.start();
if(discoveredUSBTuner.hasTuner())
{
TunerType tunerType = discoveredUSBTuner.getTuner().getTunerType();
TunerConfiguration tunerConfiguration = mTunerConfigurationManager
.getTunerConfiguration(tunerType, discoveredUSBTuner.getId());
if(tunerConfiguration != null)
{
mLog.info("Applying tuner configuration to " + discoveredUSBTuner.getId());
discoveredUSBTuner.setTunerConfiguration(tunerConfiguration);
mTunerConfigurationManager.saveConfigurations();
}
}
}
mDiscoveredTunerModel.addDiscoveredTuner(discoveredUSBTuner);
mLog.info("Tuner Added: " + discoveredUSBTuner);
}
}
else
{
mLog.error("LibUsb - unable to get device descriptor for device on bus [" + bus +
"] port [" + port + "] - status [" + status + "] - " + LibUsb.errorName(status));
}
}
/**
* Stops and removes the USB tuner if it is currently discovered.
* @param bus usb for the tuner
* @param port usb for the tuner
*/
private void removeUsbTuner(int bus, int port)
{
mDiscoveredTunerModel.removeUsbTuner(bus, port);
}
/**
* Discover sound card tuners like the Funcube Dongle (Pro)
*/
private void discoverSoundCardTuners()
{
}
/**
* Discover SDRPlay RSP tuners
*/
private void discoverSdrPlayTuners()
{
//placeholder ...
}
/**
* Discover recording based tuners
*/
private void discoverRecordingTuners()
{
//placeholder
}
/**
* Handles tuner status change events. Events are sent to the tuner configuration manager so that it can save
* configuration updates and events are also monitored to detect when a user changes the tuner state of a tuner
* so that we can auto-start the tuner and apply a tuner configuration.
*
* @param discoveredTuner that has a status change.
* @param previous tuner status
* @param current tuner status
*/
@Override
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
{
mTunerConfigurationManager.tunerStatusUpdated(discoveredTuner, previous, current);
if(previous != TunerStatus.ENABLED && current == TunerStatus.ENABLED)
{
discoveredTuner.start();
if(discoveredTuner.hasTuner())
{
TunerType tunerType = discoveredTuner.getTuner().getTunerType();
TunerConfiguration tunerConfiguration = mTunerConfigurationManager
.getTunerConfiguration(tunerType, discoveredTuner.getId());
if(tunerConfiguration != null)
{
discoveredTuner.setTunerConfiguration(tunerConfiguration);
mTunerConfigurationManager.saveConfigurations();
}
}
}
}
/**
* Find a tuner that matches the name argument
*
* @param preferredTunerName of the tuner
* @return named tuner or null
*/
public DiscoveredTuner getDiscoveredTuner(String preferredTunerName)
{
if(preferredTunerName != null)
{
for(DiscoveredTuner discoveredTuner : getDiscoveredTunerModel().getAvailableTuners())
{
if(discoveredTuner.isAvailable() &&
discoveredTuner.getTuner().getPreferredName().equalsIgnoreCase(preferredTunerName))
{
return discoveredTuner;
}
}
}
return null;
}
/**
* Sorted list of preferred tuner names
*/
public List<String> getPreferredTunerNames()
{
List<String> preferredNames = new ArrayList<>();
List<DiscoveredTuner> availableTuners = getAvailableTuners();
for(DiscoveredTuner discoveredTuner: availableTuners)
{
String preferredName = discoveredTuner.getTuner().getPreferredName();
if(!preferredNames.contains(preferredName))
{
preferredNames.add(preferredName);
}
}
Collections.sort(preferredNames);
return preferredNames;
}
/**
* Tuners currently available for use
*/
public List<DiscoveredTuner> getAvailableTuners()
{
return mDiscoveredTunerModel.getAvailableTuners();
}
public Source getSource(SourceConfiguration config, ChannelSpecification channelSpecification) throws SourceException
{
Source retVal = null;
switch(config.getSourceType())
{
case MIXER:
retVal = MixerManager.getSource(config);
break;
case TUNER:
if(config instanceof SourceConfigTuner)
{
SourceConfigTuner sourceConfigTuner = (SourceConfigTuner)config;
TunerChannel tunerChannel = sourceConfigTuner.getTunerChannel(channelSpecification.getBandwidth());
String preferredTuner = sourceConfigTuner.getPreferredTuner();
retVal = getSource(tunerChannel, channelSpecification, preferredTuner);
}
break;
case TUNER_MULTIPLE_FREQUENCIES:
if(config instanceof SourceConfigTunerMultipleFrequency)
{
SourceConfigTunerMultipleFrequency sourceConfigTuner = (SourceConfigTunerMultipleFrequency)config;
TunerChannel tunerChannel = sourceConfigTuner.getTunerChannel(channelSpecification.getBandwidth());
String preferredTuner = sourceConfigTuner.getPreferredTuner();
Source source = getSource(tunerChannel, channelSpecification, preferredTuner);
if(source instanceof TunerChannelSource)
{
retVal = new MultiFrequencyTunerChannelSource(this, (TunerChannelSource)source,
sourceConfigTuner.getFrequencies(), channelSpecification, sourceConfigTuner.getPreferredTuner());
}
}
break;
default:
break;
}
return retVal;
}
/**
* Iterates current available tuners to get a tuner channel source for the specified frequency and bandwidth
*
* Returns null if no tuner can source the channel
*/
public Source getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification, String preferredTuner)
{
TunerChannelSource source = null;
if(tunerChannel != null && channelSpecification != null)
{
DiscoveredTuner discoveredTuner;
if(preferredTuner != null)
{
discoveredTuner = getDiscoveredTuner(preferredTuner);
if(discoveredTuner != null)
{
try
{
source = discoveredTuner.getTuner().getChannelSourceManager().getSource(tunerChannel, channelSpecification);
if(source != null)
{
return source;
}
}
catch(Exception e)
{
//Fall through to logger below
}
}
mLog.info("Unable to source channel [" + tunerChannel.getFrequency() + "] from preferred tuner [" +
preferredTuner + "] - searching for another tuner");
}
Iterator<DiscoveredTuner> it = mDiscoveredTunerModel.getAvailableTuners().iterator();
while(it.hasNext() && source == null)
{
discoveredTuner = it.next();
if(discoveredTuner.hasTuner())
{
try
{
source = discoveredTuner.getTuner().getChannelSourceManager().getSource(tunerChannel, channelSpecification);
}
catch(Exception e)
{
mLog.error("Error obtaining channel from tuner [" + discoveredTuner.getTuner().getPreferredName() + "]", e);
}
}
}
}
return source;
}
/**
* USB hotplug event listener to register, unregister and detect when USB devices are added or removed.
*
* Note: hotplug is not supported on all platforms.
*/
public class HotplugEventSupport implements HotplugCallback
{
private static final int HOTPLUG_CONTINUE_EVENT_SUPPORT = 0;
private HotplugCallbackHandle mHotplugCallbackHandle;
private ScheduledFuture<?> mEventProcessorFuture;
/**
* LibUsb hotplug event notification
* @param context used for hotplug event registration
* @param device that arrived or left
* @param event value, arrived event or left event
* @param userData not used
* @return
*/
@Override
public int processEvent(Context context, Device device, int event, Object userData)
{
int bus = LibUsb.getBusNumber(device);
int port = LibUsb.getPortNumber(device);
switch(event)
{
case LibUsb.HOTPLUG_EVENT_DEVICE_ARRIVED:
addUsbTuner(device);
break;
case LibUsb.HOTPLUG_EVENT_DEVICE_LEFT:
removeUsbTuner(bus, port);
break;
}
return HOTPLUG_CONTINUE_EVENT_SUPPORT;
}
/**
* Registers for hotplug event notifications, if the capability is supported on this platform and starts an
* event processing timer to process hotplug events.
*/
public void start()
{
if(LibUsb.hasCapability(LibUsb.CAP_HAS_HOTPLUG))
{
mHotplugCallbackHandle = new HotplugCallbackHandle();
int events = LibUsb.HOTPLUG_EVENT_DEVICE_ARRIVED | LibUsb.HOTPLUG_EVENT_DEVICE_LEFT;
int flags = LibUsb.HOTPLUG_ENUMERATE;
int vendorId = LibUsb.HOTPLUG_MATCH_ANY;
int productId = LibUsb.HOTPLUG_MATCH_ANY;
int deviceClass = LibUsb.HOTPLUG_MATCH_ANY;
int status = LibUsb.hotplugRegisterCallback(mLibUsbApplicationContext, events, flags, vendorId,
productId, deviceClass, this, "sdrtrunk hotplug support", mHotplugCallbackHandle);
if(status == LibUsb.SUCCESS)
{
Runnable eventHandler = () -> LibUsb.handleEvents(mLibUsbApplicationContext);
mEventProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(eventHandler,
0, 100, TimeUnit.MILLISECONDS);
}
else
{
mHotplugCallbackHandle = null;
mLog.info("LibUsb - unable to register device hotplug listener - " + LibUsb.errorName(status));
}
}
else
{
mLog.info("LibUsb Hotplug event notification Is Not Supported on this platform.");
}
}
/**
* Unregisters from hotplug event notifications and stops the event processing timer
*/
public void stop()
{
if(mHotplugCallbackHandle != null)
{
LibUsb.hotplugDeregisterCallback(mLibUsbApplicationContext, mHotplugCallbackHandle);
mHotplugCallbackHandle = null;
}
if(mEventProcessorFuture != null)
{
mEventProcessorFuture.cancel(true);
mEventProcessorFuture = null;
}
}
}
public static void main(String[] args)
{
mLog.info("Starting ...");
UserPreferences userPreferences = new UserPreferences();
TunerManager tunerManager = new TunerManager(userPreferences);
tunerManager.start();
while(true);
// mLog.info("Finished!");
}
}

View File

@@ -0,0 +1,66 @@
/*
* *****************************************************************************
* 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.manager;
/**
* Current status of a discovered tuner.
*/
public enum TunerStatus
{
/**
* A tuner that is enabled and usable, but not currently in-use.
*/
ENABLED("Enabled"),
/**
* A tuner that has been disabled or black-listed for use
*/
DISABLED("Disabled"),
/**
* A tuner that has an error state and is unusable
*/
ERROR("Error");
private String mLabel;
/**
* Constructs an instance
* @param label
*/
TunerStatus(String label)
{
mLabel = label;
}
@Override
public String toString()
{
return mLabel;
}
/**
* Indicates if this tuner is available and can be started
*/
public boolean isAvailable()
{
return this == ENABLED;
}
}