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()); } } }