diff --git a/.idea/misc.xml b/.idea/misc.xml
index 48d2c0be..dced427a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/source/SourceEvent.java b/src/main/java/io/github/dsheirer/source/SourceEvent.java
index 41a761c4..e12ded96 100644
--- a/src/main/java/io/github/dsheirer/source/SourceEvent.java
+++ b/src/main/java/io/github/dsheirer/source/SourceEvent.java
@@ -197,7 +197,7 @@ public class SourceEvent
/**
* Creates a new locked state change event
*/
- public static SourceEvent lockedState()
+ public static SourceEvent lockedSampleRateState()
{
return new SourceEvent(Event.NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED, 1);
}
@@ -205,7 +205,7 @@ public class SourceEvent
/**
* Creates a new unlocked state change event
*/
- public static SourceEvent unlockedState()
+ public static SourceEvent unlockedSampleRateState()
{
return new SourceEvent(Event.NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED, 0);
}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
index 90671fb7..cede08c0 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java
@@ -36,11 +36,11 @@ import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.frequency.FrequencyController;
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
import io.github.dsheirer.source.tuner.manager.FrequencyErrorCorrectionManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.text.DecimalFormat;
import java.util.SortedSet;
+import java.util.concurrent.locks.ReentrantLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener,
INativeBufferProvider, Listener, ITunerErrorListener
@@ -70,6 +70,17 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
mFrequencyErrorCorrectionManager = new FrequencyErrorCorrectionManager(this);
}
+ /**
+ * Lock for the frequency controller. This should only be used by the channel source manager to lock access to the
+ * frequency controller while creating a channel source, to block multi-threaded access to the frequency controller
+ * which might put the center tuned frequency value in an indeterminant state.
+ * @return frequency controller lock
+ */
+ public ReentrantLock getFrequencyControllerLock()
+ {
+ return mFrequencyController.getFrequencyControllerLock();
+ }
+
/**
* Frequency correction manager for this tuner controller.
*/
@@ -182,29 +193,30 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
}
/**
- * Indicates if the frequency and sample rate controls are locked by another process. User interface controls
- * should monitor source events and check the locked state via this method to correctly render the enabled state
- * of the frequency and sample rate controls.
+ * Indicates if the sample rate control is locked by another process. User interface controls should monitor source
+ * events and check the locked state via this method to correctly render the enabled state of the sample rate control.
*
* @return true if the tuner controller is locked.
*/
- public boolean isLocked()
+ public boolean isLockedSampleRate()
{
- return mFrequencyController.isLocked();
+ //Note: this access is not protected by the mFrequencyControllerLock
+ return mFrequencyController.isSampleRateLocked();
}
/**
- * Sets the frequency and sample rate locked state to the locked argument value. This should only be changed
- * by a downstream consumer of samples to prevent users or other processes from modifying the center frequency
- * and/or sample rate of the tuner while processing samples.
+ * Sets the sample rate locked state to the locked argument value. This should only be changed by a downstream
+ * consumer of samples to prevent users or other processes from modifying the sample rate of the tuner while
+ * processing samples.
*
* @param locked true to indicate the tuner controller is in a locked state, otherwise false.
*/
- public void setLocked(boolean locked)
+ public void setLockedSampleRate(boolean locked)
{
try
{
- mFrequencyController.setLocked(locked);
+ //Note: this access is not protected by the mFrequencyControllerLock
+ mFrequencyController.setSampleRateLocked(locked);
}
catch(SourceException se)
{
@@ -214,6 +226,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
public int getBandwidth()
{
+ //Note: this access is not protected by the mFrequencyControllerLock
return mFrequencyController.getBandwidth();
}
@@ -225,7 +238,15 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public void setFrequency(long frequency) throws SourceException
{
- mFrequencyController.setFrequency(frequency);
+ try
+ {
+ getFrequencyControllerLock().lock();
+ mFrequencyController.setFrequency(frequency);
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
}
/**
@@ -235,28 +256,73 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public long getFrequency()
{
- return mFrequencyController.getFrequency();
+ long frequency;
+
+ try
+ {
+ getFrequencyControllerLock().lock();
+ frequency = mFrequencyController.getFrequency();
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
+
+ return frequency;
}
@Override
public boolean canTune(long frequency)
{
- return mFrequencyController.canTune(frequency);
+ boolean canTune;
+
+ try
+ {
+ getFrequencyControllerLock().lock();
+ canTune = mFrequencyController.canTune(frequency);
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
+
+ return canTune;
}
public double getSampleRate()
{
+ //Note: this access is not protected by the mFrequencyControllerLock
return mFrequencyController.getSampleRate();
}
public double getFrequencyCorrection()
{
- return mFrequencyController.getFrequencyCorrection();
+ double correction;
+
+ try
+ {
+ getFrequencyControllerLock().lock();
+ correction = mFrequencyController.getFrequencyCorrection();
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
+
+ return correction;
}
public void setFrequencyCorrection(double correction) throws SourceException
{
- mFrequencyController.setFrequencyCorrection(correction);
+ try
+ {
+ getFrequencyControllerLock().lock();
+ mFrequencyController.setFrequencyCorrection(correction);
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
}
/**
@@ -265,7 +331,19 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public long getMinimumFrequency()
{
- return mFrequencyController.getMinimumFrequency();
+ long minimum;
+
+ try
+ {
+ getFrequencyControllerLock().lock();
+ minimum = mFrequencyController.getMinimumFrequency();
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
+
+ return minimum;
}
/**
@@ -274,7 +352,15 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public void setMinimumFrequency(long minimum)
{
- mFrequencyController.setMinimumFrequency(minimum);
+ try
+ {
+ getFrequencyControllerLock().lock();
+ mFrequencyController.setMinimumFrequency(minimum);
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
}
/**
@@ -283,7 +369,19 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public long getMaximumFrequency()
{
- return mFrequencyController.getMaximumFrequency();
+ long maximum;
+
+ try
+ {
+ getFrequencyControllerLock().lock();
+ maximum = mFrequencyController.getMaximumFrequency();
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
+
+ return maximum;
}
/**
@@ -292,17 +390,49 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public void setMaximumFrequency(long maximum)
{
- mFrequencyController.setMaximumFrequency(maximum);
+ try
+ {
+ getFrequencyControllerLock().lock();
+ mFrequencyController.setMaximumFrequency(maximum);
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
}
public long getMinTunedFrequency() throws SourceException
{
- return mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
+ long minTuned;
+
+ try
+ {
+ getFrequencyControllerLock().lock();
+ minTuned = mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
+
+ return minTuned;
}
public long getMaxTunedFrequency() throws SourceException
{
- return mFrequencyController.getFrequency() + (getUsableBandwidth() / 2);
+ long maxTuned;
+
+ try
+ {
+ getFrequencyControllerLock().lock();
+ maxTuned = mFrequencyController.getFrequency() + (getUsableBandwidth() / 2);
+ }
+ finally
+ {
+ getFrequencyControllerLock().unlock();
+ }
+
+ return maxTuned;
}
/**
@@ -425,7 +555,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public void addListener( ISourceEventProcessor processor )
{
- mFrequencyController.addListener(processor);
+ mFrequencyController.addSourceEventProcessor(processor);
}
/**
@@ -433,7 +563,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public void removeListener( ISourceEventProcessor processor )
{
- mFrequencyController.removeFrequencyChangeProcessor(processor);
+ mFrequencyController.removeSourceEventProcessor(processor);
}
/**
diff --git a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java
index 0313db3c..619201c0 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/airspy/AirspyTunerEditor.java
@@ -25,6 +25,9 @@ import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.manager.TunerStatus;
import io.github.dsheirer.source.tuner.ui.TunerEditor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,9 +43,6 @@ import javax.swing.JSlider;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.List;
/**
* Airspy tuner editor/controller
@@ -102,7 +102,7 @@ public class AirspyTunerEditor extends TunerEditor mProcessors = new ArrayList<>();
+
+ /**
+ * Lock protecting access to frequency control plane, source event processor subscriptions and event broadcasting
+ */
private ReentrantLock mLock = new ReentrantLock();
/**
@@ -53,6 +56,15 @@ public class FrequencyController
mTunable = tunable;
}
+
+ /**
+ * Lock for controlling access to frequency control plane, event processor subscriptions and source event broadcasts.
+ */
+ public ReentrantLock getFrequencyControllerLock()
+ {
+ return mLock;
+ }
+
/**
* Prepare for disposal of this instance.
*/
@@ -63,36 +75,36 @@ public class FrequencyController
}
/**
- * Indicates if this frequency controller is locked by a remote process. A locked state indicates that neither
- * the frequency nor the sample rate should be changed. The remote process will typically be a downstream
+ * Indicates if the sample rate for this frequency controller is locked by a remote process. A locked state
+ * indicates that the sample rate cannot be changed. The remote process will typically be a downstream
* consumer of the data produced by the device being controlled (ie polyphase channelizer) that does not want
- * the sample rate or frequency to be changed while processing is ongoing.
+ * the sample rate to be changed while processing (ie channels have been allocated) is ongoing.
*
* @return true if locked or false if not locked.
*/
- public boolean isLocked()
+ public boolean isSampleRateLocked()
{
- return mLocked;
+ return mSampleRateLocked;
}
/**
* Sets the lock state for this frequency controller. Set to true when a consumer process requires that the
- * frequency and sample rate controls be locked so that only the singular consumer process makes changes. This
+ * sample rate control be locked so that only the single consumer process makes changes to the sample rate. This
* is primarily to lock down user interface controls when the polyphase channelizer is producing channels.
*
- * @param locked state
+ * @param sampleRateLocked state
*/
- public void setLocked(boolean locked) throws SourceException
+ public void setSampleRateLocked(boolean sampleRateLocked) throws SourceException
{
- mLocked = locked;
+ mSampleRateLocked = sampleRateLocked;
- if(mLocked)
+ if(mSampleRateLocked)
{
- broadcast(SourceEvent.lockedState());
+ broadcast(SourceEvent.lockedSampleRateState());
}
else
{
- broadcast(SourceEvent.unlockedState());
+ broadcast(SourceEvent.unlockedSampleRateState());
}
}
@@ -111,7 +123,7 @@ public class FrequencyController
{
if(sampleRate != mSampleRate)
{
- if(!mLocked)
+ if(!mSampleRateLocked)
{
mSampleRate = sampleRate;
@@ -284,7 +296,7 @@ public class FrequencyController
/**
* Adds listener to receive frequency change events
*/
- public void addListener(ISourceEventProcessor processor)
+ public void addSourceEventProcessor(ISourceEventProcessor processor)
{
mLock.lock();
@@ -304,7 +316,7 @@ public class FrequencyController
/**
* Removes listener from receiving frequency change events
*/
- public void removeFrequencyChangeProcessor(ISourceEventProcessor processor)
+ public void removeSourceEventProcessor(ISourceEventProcessor processor)
{
mLock.lock();
diff --git a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerEditor.java
index f547c90e..66b6ce6c 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerEditor.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/hackrf/HackRFTunerEditor.java
@@ -120,7 +120,7 @@ public class HackRFTunerEditor extends TunerEditor tunerChannels = getTunerChannels();
-
- //Add the requested channel to the list
- tunerChannels.add(tunerChannel);
-
- if(canTune(tunerChannels))
+ mTunerController.getFrequencyControllerLock().lock();
+ if(isTunable(tunerChannel))
{
- long currentCenterFrequency = mTunerController.getFrequency();
- long updatedCenterFrequency = 0;
+ //Get a new set of currently tuned channels
+ SortedSet tunerChannels = getTunerChannels();
- //Attempt to adjust the center frequency before we allocate the channel
- try
+ //Add the requested channel to the list
+ tunerChannels.add(tunerChannel);
+
+ if(canTune(tunerChannels))
{
- updatedCenterFrequency = getCenterFrequency(tunerChannels, currentCenterFrequency);
+ long currentCenterFrequency = mTunerController.getFrequency();
+ long updatedCenterFrequency = 0;
- if(updatedCenterFrequency != currentCenterFrequency && updatedCenterFrequency != 0)
+ //Attempt to adjust the center frequency before we allocate the channel
+ try
{
- mTunerController.setFrequency(updatedCenterFrequency);
- }
+ updatedCenterFrequency = getCenterFrequency(tunerChannels, currentCenterFrequency);
- //If we're successful to here, allocate the channel
- return mPolyphaseChannelManager.getChannel(tunerChannel);
- }
- catch(SourceException se)
- {
- //Tuner controller threw an error trying to tune to the updated frequency
- mLog.error("Error while updating tuner controller with new center frequency [" +
- updatedCenterFrequency + "] - unable to allocate new tuner channel", se);
- }
- catch(IllegalArgumentException iae)
- {
- //Center frequency calculation failed
-// mLog.debug("Couldn't calculate new tuner center frequency for new tuner channel [" +
-// tunerChannel + "] unable to allocate new tuner channel");
+ if(updatedCenterFrequency != currentCenterFrequency && updatedCenterFrequency != 0)
+ {
+ mTunerController.setFrequency(updatedCenterFrequency);
+ }
+
+ //If we're successful to here, allocate the channel
+ tunerChannelSource = mPolyphaseChannelManager.getChannel(tunerChannel);
+ }
+ catch(SourceException se)
+ {
+ //Tuner controller threw an error trying to tune to the updated frequency
+ mLog.error("Error while updating tuner controller with new center frequency [" +
+ updatedCenterFrequency + "] - unable to allocate new tuner channel", se);
+ }
+ catch(IllegalArgumentException iae)
+ {
+ //Center frequency calculation failed
+ }
}
}
}
+ finally
+ {
+ mTunerController.getFrequencyControllerLock().unlock();
+ }
- return null;
+ return tunerChannelSource;
}
/**
@@ -474,7 +481,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
broadcast(sourceEvent);
//Lock the frequency and sample rate controls on the tuner controller so users can't change them
//when the polyphase manager has channels allocated
- mTunerController.setLocked(getTunerChannelCount() > 0);
+ mTunerController.setLockedSampleRate(getTunerChannelCount() > 0);
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
//Rebroadcast these frequency measurement errors to the tuner and tuner controller
diff --git a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerEditor.java
index 60ee7bc4..acca6164 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerEditor.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/rtl/e4k/E4KTunerEditor.java
@@ -29,6 +29,8 @@ import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KLNAGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KMixerGain;
import io.github.dsheirer.source.tuner.ui.TunerEditor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,8 +43,6 @@ import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import javax.swing.SpinnerNumberModel;
import javax.usb.UsbException;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
/**
* E4000 tuner editor
@@ -383,7 +383,7 @@ public class E4KTunerEditor extends TunerEditor
{
getFrequencyControl().clearListeners();
getFrequencyControl().addListener(mFrequencyAndCorrectionChangeListener);
- getFrequencyControl().setEnabled(hasTuner() && !getTuner().getTunerController().isLocked());
- getTunerLockedStatusLabel().setVisible(hasTuner() && getTuner().getTunerController().isLocked());
+ getFrequencyControl().setEnabled(hasTuner() && !getTuner().getTunerController().isLockedSampleRate());
+ getTunerLockedStatusLabel().setVisible(hasTuner() && getTuner().getTunerController().isLockedSampleRate());
getFrequencyCorrectionSpinner().setEnabled(hasTuner());
getAutoPPMCheckBox().setEnabled(hasTuner());
diff --git a/src/main/java/io/github/dsheirer/source/tuner/ui/TunerViewPanel.java b/src/main/java/io/github/dsheirer/source/tuner/ui/TunerViewPanel.java
index 52bd416f..b78351b6 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/ui/TunerViewPanel.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/ui/TunerViewPanel.java
@@ -143,7 +143,7 @@ public class TunerViewPanel extends JPanel
if(selectedTuner != null && selectedTuner.hasTuner() && tunerEvent.getTuner() == selectedTuner.getTuner())
{
- mDiscoveredTunerEditor.setTunerLockState(selectedTuner.getTuner().getTunerController().isLocked());
+ mDiscoveredTunerEditor.setTunerLockState(selectedTuner.getTuner().getTunerController().isLockedSampleRate());
}
}
}