#1340 Updates tuner and frequency controllers to apply locking to ensure multi-threaded access to freuqency control planes doesn't allow center tuned frequency to become indeterminant which can result in channels being mistuned and stop decoding. (#1341)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-11-20 07:55:30 -05:00
committed by GitHub
parent ac61b681e6
commit a24c3736f4
14 changed files with 299 additions and 128 deletions

2
.idea/misc.xml generated
View File

@@ -4,7 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" default="true" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

View File

@@ -197,7 +197,7 @@ public class SourceEvent
/** /**
* Creates a new locked state change event * 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); 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 * 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); return new SourceEvent(Event.NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED, 0);
} }

View File

@@ -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;
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable; import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
import io.github.dsheirer.source.tuner.manager.FrequencyErrorCorrectionManager; import io.github.dsheirer.source.tuner.manager.FrequencyErrorCorrectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.SortedSet; 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, public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener,
INativeBufferProvider, Listener<INativeBuffer>, ITunerErrorListener INativeBufferProvider, Listener<INativeBuffer>, ITunerErrorListener
@@ -70,6 +70,17 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
mFrequencyErrorCorrectionManager = new FrequencyErrorCorrectionManager(this); 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. * 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 * Indicates if the sample rate control is locked by another process. User interface controls should monitor source
* should monitor source events and check the locked state via this method to correctly render the enabled state * events and check the locked state via this method to correctly render the enabled state of the sample rate control.
* of the frequency and sample rate controls.
* *
* @return true if the tuner controller is locked. * @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 * Sets the sample rate locked state to the locked argument value. This should only be changed by a downstream
* by a downstream consumer of samples to prevent users or other processes from modifying the center frequency * consumer of samples to prevent users or other processes from modifying the sample rate of the tuner while
* and/or sample rate of the tuner while processing samples. * processing samples.
* *
* @param locked true to indicate the tuner controller is in a locked state, otherwise false. * @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 try
{ {
mFrequencyController.setLocked(locked); //Note: this access is not protected by the mFrequencyControllerLock
mFrequencyController.setSampleRateLocked(locked);
} }
catch(SourceException se) catch(SourceException se)
{ {
@@ -214,6 +226,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
public int getBandwidth() public int getBandwidth()
{ {
//Note: this access is not protected by the mFrequencyControllerLock
return mFrequencyController.getBandwidth(); return mFrequencyController.getBandwidth();
} }
@@ -225,7 +238,15 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/ */
public void setFrequency(long frequency) throws SourceException 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() public long getFrequency()
{ {
return mFrequencyController.getFrequency(); long frequency;
try
{
getFrequencyControllerLock().lock();
frequency = mFrequencyController.getFrequency();
}
finally
{
getFrequencyControllerLock().unlock();
}
return frequency;
} }
@Override @Override
public boolean canTune(long frequency) 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() public double getSampleRate()
{ {
//Note: this access is not protected by the mFrequencyControllerLock
return mFrequencyController.getSampleRate(); return mFrequencyController.getSampleRate();
} }
public double getFrequencyCorrection() 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 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() 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) 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() 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) public void setMaximumFrequency(long maximum)
{ {
mFrequencyController.setMaximumFrequency(maximum); try
{
getFrequencyControllerLock().lock();
mFrequencyController.setMaximumFrequency(maximum);
}
finally
{
getFrequencyControllerLock().unlock();
}
} }
public long getMinTunedFrequency() throws SourceException 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 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 ) 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 ) public void removeListener( ISourceEventProcessor processor )
{ {
mFrequencyController.removeFrequencyChangeProcessor(processor); mFrequencyController.removeSourceEventProcessor(processor);
} }
/** /**

View File

@@ -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.TunerManager;
import io.github.dsheirer.source.tuner.manager.TunerStatus; import io.github.dsheirer.source.tuner.manager.TunerStatus;
import io.github.dsheirer.source.tuner.ui.TunerEditor; 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 net.miginfocom.swing.MigLayout;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -40,9 +43,6 @@ import javax.swing.JSlider;
import javax.swing.SpinnerNumberModel; import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
/** /**
* Airspy tuner editor/controller * Airspy tuner editor/controller
@@ -102,7 +102,7 @@ public class AirspyTunerEditor extends TunerEditor<AirspyTuner, AirspyTunerConfi
getTunerStatusLabel().setText(status); getTunerStatusLabel().setText(status);
getButtonPanel().updateControls(); getButtonPanel().updateControls();
getFrequencyPanel().updateControls(); getFrequencyPanel().updateControls();
getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLocked()); getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLockedSampleRate());
getTunerInfoButton().setEnabled(hasTuner()); getTunerInfoButton().setEnabled(hasTuner());
updateGainComponents((hasTuner() && hasConfiguration()) ? getConfiguration().getGain() : null); updateGainComponents((hasTuner() && hasConfiguration()) ? getConfiguration().getGain() : null);
@@ -626,7 +626,7 @@ public class AirspyTunerEditor extends TunerEditor<AirspyTuner, AirspyTunerConfi
*/ */
private void updateSampleRateToolTip() private void updateSampleRateToolTip()
{ {
if(hasTuner() && getTuner().getController().isLocked()) if(hasTuner() && getTuner().getController().isLockedSampleRate())
{ {
mSampleRateCombo.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock."); mSampleRateCombo.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
} }

View File

@@ -22,12 +22,11 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.InvalidFrequencyException; import io.github.dsheirer.source.InvalidFrequencyException;
import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.SourceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FrequencyController public class FrequencyController
{ {
@@ -40,8 +39,12 @@ public class FrequencyController
private long mMaximumFrequency = 0; private long mMaximumFrequency = 0;
private double mFrequencyCorrection = 0.0d; private double mFrequencyCorrection = 0.0d;
private double mSampleRate = 0.0d; private double mSampleRate = 0.0d;
private boolean mLocked = false; private boolean mSampleRateLocked = false;
private List<ISourceEventProcessor> mProcessors = new ArrayList<>(); private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
/**
* Lock protecting access to frequency control plane, source event processor subscriptions and event broadcasting
*/
private ReentrantLock mLock = new ReentrantLock(); private ReentrantLock mLock = new ReentrantLock();
/** /**
@@ -53,6 +56,15 @@ public class FrequencyController
mTunable = tunable; 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. * 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 * Indicates if the sample rate for this frequency controller is locked by a remote process. A locked state
* the frequency nor the sample rate should be changed. The remote process will typically be a downstream * 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 * 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. * @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 * 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. * 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 else
{ {
broadcast(SourceEvent.unlockedState()); broadcast(SourceEvent.unlockedSampleRateState());
} }
} }
@@ -111,7 +123,7 @@ public class FrequencyController
{ {
if(sampleRate != mSampleRate) if(sampleRate != mSampleRate)
{ {
if(!mLocked) if(!mSampleRateLocked)
{ {
mSampleRate = sampleRate; mSampleRate = sampleRate;
@@ -284,7 +296,7 @@ public class FrequencyController
/** /**
* Adds listener to receive frequency change events * Adds listener to receive frequency change events
*/ */
public void addListener(ISourceEventProcessor processor) public void addSourceEventProcessor(ISourceEventProcessor processor)
{ {
mLock.lock(); mLock.lock();
@@ -304,7 +316,7 @@ public class FrequencyController
/** /**
* Removes listener from receiving frequency change events * Removes listener from receiving frequency change events
*/ */
public void removeFrequencyChangeProcessor(ISourceEventProcessor processor) public void removeSourceEventProcessor(ISourceEventProcessor processor)
{ {
mLock.lock(); mLock.lock();

View File

@@ -120,7 +120,7 @@ public class HackRFTunerEditor extends TunerEditor<HackRFTuner,HackRFTunerConfig
getTunerStatusLabel().setText(status); getTunerStatusLabel().setText(status);
getButtonPanel().updateControls(); getButtonPanel().updateControls();
getFrequencyPanel().updateControls(); getFrequencyPanel().updateControls();
getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLocked()); getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLockedSampleRate());
updateSampleRateToolTip(); updateSampleRateToolTip();
getTunerInfoButton().setEnabled(hasTuner()); getTunerInfoButton().setEnabled(hasTuner());
@@ -295,7 +295,7 @@ public class HackRFTunerEditor extends TunerEditor<HackRFTuner,HackRFTunerConfig
*/ */
private void updateSampleRateToolTip() private void updateSampleRateToolTip()
{ {
if(hasTuner() && getTuner().getController().isLocked()) if(hasTuner() && getTuner().getController().isLockedSampleRate())
{ {
mSampleRateCombo.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock."); mSampleRateCombo.setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
} }

View File

@@ -93,38 +93,48 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
return null; return null;
} }
if(CenterFrequencyCalculator.canTune(tunerChannel, mTunerController, mTunerChannels)) TunerChannelSource source = null;
try
{ {
try mTunerController.getFrequencyControllerLock().lock();
if(CenterFrequencyCalculator.canTune(tunerChannel, mTunerController, mTunerChannels))
{ {
//Attempt to create the channel source first, in case we get a filter design exception try
HalfBandTunerChannelSource tunerChannelSource = new HalfBandTunerChannelSource(mChannelSourceEventProcessor, {
tunerChannel, mTunerController.getSampleRate(), channelSpecification); //Attempt to create the channel source first, in case we get a filter design exception
HalfBandTunerChannelSource tunerChannelSource = new HalfBandTunerChannelSource(mChannelSourceEventProcessor,
tunerChannel, mTunerController.getSampleRate(), channelSpecification);
//Add to the list of channel sources so that it will receive the tuner frequency change //Add to the list of channel sources so that it will receive the tuner frequency change
mChannelSources.add(tunerChannelSource); mChannelSources.add(tunerChannelSource);
//Set the current tuner frequency //Set the current tuner frequency
tunerChannelSource.setFrequency(mTunerController.getFrequency()); tunerChannelSource.setFrequency(mTunerController.getFrequency());
//Add to the channel list and update the tuner center frequency as needed //Add to the channel list and update the tuner center frequency as needed
mTunerChannels.add(tunerChannel); mTunerChannels.add(tunerChannel);
updateTunerFrequency(); updateTunerFrequency();
//Lock the tuner controller frequency and sample rate //Lock the tuner controller frequency and sample rate
mTunerController.setLocked(true); mTunerController.setLockedSampleRate(true);
broadcast(SourceEvent.channelCountChange(getTunerChannelCount())); broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
return tunerChannelSource; source = tunerChannelSource;
} }
catch(FilterDesignException fde) catch(FilterDesignException fde)
{ {
mLog.error("Error creating CIC tuner channel source - couldn't design cleanup filter", fde); mLog.error("Error creating CIC tuner channel source - couldn't design cleanup filter", fde);
}
} }
} }
finally
{
mTunerController.getFrequencyControllerLock().unlock();
}
return null; return source;
} }
@Override @Override
@@ -294,7 +304,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
//Unlock the tuner controller if there are no more channels //Unlock the tuner controller if there are no more channels
if(getTunerChannelCount() == 0) if(getTunerChannelCount() == 0)
{ {
mTunerController.setLocked(false); mTunerController.setLockedSampleRate(false);
} }
broadcast(SourceEvent.channelCountChange(getTunerChannelCount())); broadcast(SourceEvent.channelCountChange(getTunerChannelCount()));
} }

View File

@@ -82,18 +82,30 @@ public class PassThroughSourceManager extends ChannelSourceManager
@Override @Override
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification) public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{ {
if(!mRunning) if(!mRunning)
{ {
return null; return null;
} }
PassThroughChannelSource channelSource = new PassThroughChannelSource(new SourceEventProxy(), TunerChannelSource source = null;
mTunerController, tunerChannel);
mTunerChannels.add(tunerChannel); try
mTunerChannelSources.add(channelSource); {
mTunerController.getFrequencyControllerLock().lock();
PassThroughChannelSource channelSource = new PassThroughChannelSource(new SourceEventProxy(),
mTunerController, tunerChannel);
return channelSource; mTunerChannels.add(tunerChannel);
mTunerChannelSources.add(channelSource);
source = channelSource;
}
finally
{
mTunerController.getFrequencyControllerLock().unlock();
}
return source;
} }
@Override @Override

View File

@@ -25,12 +25,11 @@ import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification; import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.util.SortedSet;
import org.apache.commons.math3.util.FastMath; import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.SortedSet;
/** /**
* PolyphaseChannelSourceManager is responsible for managing the tuner's center tuned frequency and providing access to * 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 * polyphase tuner channel sources (ie DDCs). This class is responsible for determining IF a requested channel can
@@ -416,48 +415,56 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
@Override @Override
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification) public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{ {
if(isTunable(tunerChannel)) TunerChannelSource tunerChannelSource = null;
try
{ {
//Get a new set of currently tuned channels mTunerController.getFrequencyControllerLock().lock();
SortedSet<TunerChannel> tunerChannels = getTunerChannels(); if(isTunable(tunerChannel))
//Add the requested channel to the list
tunerChannels.add(tunerChannel);
if(canTune(tunerChannels))
{ {
long currentCenterFrequency = mTunerController.getFrequency(); //Get a new set of currently tuned channels
long updatedCenterFrequency = 0; SortedSet<TunerChannel> tunerChannels = getTunerChannels();
//Attempt to adjust the center frequency before we allocate the channel //Add the requested channel to the list
try 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 if(updatedCenterFrequency != currentCenterFrequency && updatedCenterFrequency != 0)
return mPolyphaseChannelManager.getChannel(tunerChannel); {
} mTunerController.setFrequency(updatedCenterFrequency);
catch(SourceException se) }
{
//Tuner controller threw an error trying to tune to the updated frequency //If we're successful to here, allocate the channel
mLog.error("Error while updating tuner controller with new center frequency [" + tunerChannelSource = mPolyphaseChannelManager.getChannel(tunerChannel);
updatedCenterFrequency + "] - unable to allocate new tuner channel", se); }
} catch(SourceException se)
catch(IllegalArgumentException iae) {
{ //Tuner controller threw an error trying to tune to the updated frequency
//Center frequency calculation failed mLog.error("Error while updating tuner controller with new center frequency [" +
// mLog.debug("Couldn't calculate new tuner center frequency for new tuner channel [" + updatedCenterFrequency + "] - unable to allocate new tuner channel", se);
// tunerChannel + "] unable to allocate new tuner channel"); }
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); broadcast(sourceEvent);
//Lock the frequency and sample rate controls on the tuner controller so users can't change them //Lock the frequency and sample rate controls on the tuner controller so users can't change them
//when the polyphase manager has channels allocated //when the polyphase manager has channels allocated
mTunerController.setLocked(getTunerChannelCount() > 0); mTunerController.setLockedSampleRate(getTunerChannelCount() > 0);
break; break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED: case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
//Rebroadcast these frequency measurement errors to the tuner and tuner controller //Rebroadcast these frequency measurement errors to the tuner and tuner controller

View File

@@ -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.E4KLNAGain;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KMixerGain; import io.github.dsheirer.source.tuner.rtl.e4k.E4KEmbeddedTuner.E4KMixerGain;
import io.github.dsheirer.source.tuner.ui.TunerEditor; import io.github.dsheirer.source.tuner.ui.TunerEditor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -41,8 +43,6 @@ import javax.swing.JOptionPane;
import javax.swing.JSeparator; import javax.swing.JSeparator;
import javax.swing.SpinnerNumberModel; import javax.swing.SpinnerNumberModel;
import javax.usb.UsbException; import javax.usb.UsbException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/** /**
* E4000 tuner editor * E4000 tuner editor
@@ -383,7 +383,7 @@ public class E4KTunerEditor extends TunerEditor<RTL2832Tuner, E4KTunerConfigurat
*/ */
private void updateSampleRateToolTip() private void updateSampleRateToolTip()
{ {
if(hasTuner() && getTuner().getTunerController().isLocked()) if(hasTuner() && getTuner().getTunerController().isLockedSampleRate())
{ {
getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock."); getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
} }

View File

@@ -416,7 +416,7 @@ public class R820TTunerEditor extends TunerEditor<RTL2832Tuner,R820TTunerConfigu
*/ */
private void updateSampleRateToolTip() private void updateSampleRateToolTip()
{ {
if(hasTuner() && getTuner().getTunerController().isLocked()) if(hasTuner() && getTuner().getTunerController().isLockedSampleRate())
{ {
getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock."); getSampleRateCombo().setToolTipText("Sample Rate is locked. Disable decoding channels to unlock.");
} }

View File

@@ -25,10 +25,6 @@ import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
import io.github.dsheirer.source.tuner.manager.DiscoveredUSBTuner; import io.github.dsheirer.source.tuner.manager.DiscoveredUSBTuner;
import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener; import io.github.dsheirer.source.tuner.manager.IDiscoveredTunerStatusListener;
import io.github.dsheirer.source.tuner.manager.TunerStatus; import io.github.dsheirer.source.tuner.manager.TunerStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
@@ -36,6 +32,10 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
/** /**
* Model for discovered tuners * Model for discovered tuners
@@ -449,7 +449,7 @@ public class DiscoveredTunerModel extends AbstractTableModel implements Listener
if(discoveredTuner.hasTuner()) if(discoveredTuner.hasTuner())
{ {
int channelCount = discoveredTuner.getTuner().getChannelSourceManager().getTunerChannelCount(); int channelCount = discoveredTuner.getTuner().getChannelSourceManager().getTunerChannelCount();
return channelCount + " (" + (discoveredTuner.getTuner().getTunerController().isLocked() ? "LOCKED)" : "UNLOCKED)"); return channelCount + " (" + (discoveredTuner.getTuner().getTunerController().isLockedSampleRate() ? "LOCKED)" : "UNLOCKED)");
} }
else else
{ {

View File

@@ -619,8 +619,8 @@ public abstract class TunerEditor<T extends Tuner,C extends TunerConfiguration>
{ {
getFrequencyControl().clearListeners(); getFrequencyControl().clearListeners();
getFrequencyControl().addListener(mFrequencyAndCorrectionChangeListener); getFrequencyControl().addListener(mFrequencyAndCorrectionChangeListener);
getFrequencyControl().setEnabled(hasTuner() && !getTuner().getTunerController().isLocked()); getFrequencyControl().setEnabled(hasTuner() && !getTuner().getTunerController().isLockedSampleRate());
getTunerLockedStatusLabel().setVisible(hasTuner() && getTuner().getTunerController().isLocked()); getTunerLockedStatusLabel().setVisible(hasTuner() && getTuner().getTunerController().isLockedSampleRate());
getFrequencyCorrectionSpinner().setEnabled(hasTuner()); getFrequencyCorrectionSpinner().setEnabled(hasTuner());
getAutoPPMCheckBox().setEnabled(hasTuner()); getAutoPPMCheckBox().setEnabled(hasTuner());

View File

@@ -143,7 +143,7 @@ public class TunerViewPanel extends JPanel
if(selectedTuner != null && selectedTuner.hasTuner() && tunerEvent.getTuner() == selectedTuner.getTuner()) if(selectedTuner != null && selectedTuner.hasTuner() && tunerEvent.getTuner() == selectedTuner.getTuner())
{ {
mDiscoveredTunerEditor.setTunerLockState(selectedTuner.getTuner().getTunerController().isLocked()); mDiscoveredTunerEditor.setTunerLockState(selectedTuner.getTuner().getTunerController().isLockedSampleRate());
} }
} }
} }