From f0db2865f2c74ff5b0ff14f267288c6e1552400d Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sun, 15 Jan 2023 04:12:42 -0500 Subject: [PATCH] #1397 Resolves issue where decoding channels go IDLE after period of time. Isolated the cause to a multi-threading issue where the polyphase channel manager receives notification that the tuner center frequency has changed and sets a flag to update each of the channels. This happens concurrent with a previous tuner center frequency change where the flag gets set and the second thread is updating the output processors with the previous frequency change and then unsets the frequency change flag, completely missing the second/latest frequency change update and causing the channels to all tune-out their desired signals as the tuner center frequency changes and the output channels continue mixing to the previous frequency. (#1407) Identified and resolved issue where a 2-stream polyphase synthesizer was being updated with an incorrectly designed synthesis filter which was causing massive droop on the second half of the reconstructed channel. Oddly enough, this didn't seem to impact the P25 decoders, but it may have impacted other decoding. Identified and resolved issue with concurrent modification of the modules list in the processing chain associated with recent addition of the channel FFT view in the channel power tab. Co-authored-by: Dennis Sheirer --- .../filter/channelizer/ChannelCalculator.java | 49 ++--- .../channelizer/PolyphaseChannelManager.java | 87 +++++++-- .../channelizer/PolyphaseChannelSource.java | 10 +- .../channelizer/TwoChannelSynthesizerM2.java | 18 +- .../IPolyphaseChannelOutputProcessor.java | 7 +- .../output/OneChannelOutputProcessor.java | 12 +- .../output/TwoChannelMixerAssembler.java | 15 +- .../output/TwoChannelOutputProcessor.java | 12 +- .../dsp/gain/complex/ComplexGain.java | 4 +- .../dsheirer/gui/power/ChannelPowerPanel.java | 1 + .../dsheirer/module/ProcessingChain.java | 182 +++++++++++++----- .../tuner/manager/ChannelSourceManager.java | 8 +- .../source/tuner/manager/DiscoveredTuner.java | 23 ++- .../HeterodyneChannelSourceManager.java | 19 +- .../manager/PassThroughSourceManager.java | 15 +- .../PolyphaseChannelSourceManager.java | 16 ++ .../source/tuner/ui/DiscoveredTunerModel.java | 3 +- .../source/tuner/ui/TunerViewPanel.java | 30 ++- 18 files changed, 412 insertions(+), 99 deletions(-) diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java index 8b5b0189..8636ec67 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/ChannelCalculator.java @@ -1,31 +1,34 @@ -/******************************************************************************* - * ********************************************************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2017 Dennis Sheirer +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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 - * ********************************************************************************************************************* - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.dsp.filter.channelizer; import io.github.dsheirer.source.tuner.channel.TunerChannel; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; - public class ChannelCalculator { + private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000"); private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class); /** @@ -546,13 +549,13 @@ public class ChannelCalculator public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Channel Calculator Settings"); - sb.append(" Channels:").append(getChannelCount()); - sb.append(" Sample Rate:").append(getSampleRate()); - sb.append(" Channel Bandwidth:").append(getChannelBandwidth()); - sb.append(" Channel Rate:").append(getChannelSampleRate()); - sb.append(" Min:").append(getMinimumFrequency()); - sb.append(" Max:").append(getMaximumFrequency()); + sb.append("Channel Calculator | Tuner SR:").append(FREQUENCY_FORMAT.format(getSampleRate() / 1E6D)); + sb.append(" CF:").append(FREQUENCY_FORMAT.format(getCenterFrequency() / 1E6D)); + sb.append(" MIN:").append(FREQUENCY_FORMAT.format(getMinimumFrequency() / 1E6D)); + sb.append(" MAX:").append(FREQUENCY_FORMAT.format(getMaximumFrequency() / 1E6D)); + sb.append(" | Channel COUNT:").append(getChannelCount()); + sb.append(" BW:").append(FREQUENCY_FORMAT.format(getChannelBandwidth() / 1E6D)); + sb.append(" SR:").append(FREQUENCY_FORMAT.format(getChannelSampleRate() / 1E6D)); return sb.toString(); } } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java index 382c936d..e8a8c1d0 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelManager.java @@ -35,6 +35,7 @@ import io.github.dsheirer.source.tuner.TunerController; import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.util.Dispatcher; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -43,6 +44,7 @@ import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,6 +67,7 @@ import org.slf4j.LoggerFactory; */ public class PolyphaseChannelManager implements ISourceEventProcessor { + private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000"); private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class); private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0; private static final double CHANNEL_OVERSAMPLING = 2.0; @@ -123,6 +126,34 @@ public class PolyphaseChannelManager implements ISourceEventProcessor this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate()); } + /** + * Provides a description of the state of this manager. + */ + public String getStateDescription() + { + StringBuilder sb = new StringBuilder(); + sb.append("Polyphase Channel Manager Providing [").append(mChannelSources.size()).append("] Channels"); + sb.append("\n\t").append(mChannelCalculator); + for(PolyphaseChannelSource pcs: mChannelSources) + { + List indexes = pcs.getOutputProcessorIndexes(); + double sampleRate = pcs.getSampleRate(); + long indexCenterFrequency = pcs.getIndexCenterFrequency(); + long appliedFrequencyOffset = pcs.getFrequencyOffset(); + long requestedCenterFrequency = pcs.getFrequency(); + + sb.append("\n\tPolyphase | Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d)); + sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d)); + sb.append(" BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d)); + sb.append(" | Channel CF: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d)); + sb.append(" REQUESTED CF: ").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d)); + sb.append(" MIXER:").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d)); + sb.append(" | Polyphase Indices: ").append(indexes); + } + + return sb.toString(); + } + public void stopAllChannels() { mRunning = false; @@ -278,7 +309,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor //no-op break; default: - mLog.info("Unrecognized source event: " + sourceEvent.toString()); + mLog.info("Unrecognized source event: " + sourceEvent); break; } } @@ -442,16 +473,18 @@ public class PolyphaseChannelManager implements ISourceEventProcessor /** * Processes the incoming buffer stream from the provider and transfers the buffers to the polyphase channelizer. * - * This monitor incorporates a queue for receiving source events that affect configuration of the channelizer so - * that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer - * and/or the channel sources and output processors. + * This monitor incorporates a source event handler that queues a center frequency update so that it can be + * handled on the buffer processing thread, avoiding having to lock on the output processor thread. Since we + * anticipate that these two threads will contend for access to this update required flag, we use an update lock + * to protect access to the flag. */ public class NativeBufferReceiver implements Listener { private boolean mOutputProcessorUpdateRequired = false; + private ReentrantLock mUpdateFlagLock = new ReentrantLock(); /** - * Queues the source event for deferred execution on the buffer processing thread. + * Processes tuner center frequency change source events to flag when output processors need updating. * @param event that affects configuration of the channelizer (frequency or sample rate change events) */ public void receive(SourceEvent event) @@ -460,14 +493,31 @@ public class PolyphaseChannelManager implements ISourceEventProcessor if(mChannelCalculator.getCenterFrequency() != frequency) { - mChannelCalculator.setCenterFrequency(frequency); - mOutputProcessorUpdateRequired = true; + mUpdateFlagLock.lock(); + + try + { + //Update the channel calculator frequency so that it's ready when the output processor update occurs + mChannelCalculator.setCenterFrequency(frequency); + mOutputProcessorUpdateRequired = true; + } + finally + { + mUpdateFlagLock.unlock(); + } } } + /** + * Process native buffer streams and update polyphase output channels when the parent tuner center + * frequency changes. + * @param nativeBuffer of sample to process. + */ @Override public void receive(INativeBuffer nativeBuffer) { + mUpdateFlagLock.lock(); + try { if(mOutputProcessorUpdateRequired) @@ -475,21 +525,28 @@ public class PolyphaseChannelManager implements ISourceEventProcessor updateOutputProcessors(); mOutputProcessorUpdateRequired = false; } + } + finally + { + mUpdateFlagLock.unlock(); + } - if(mPolyphaseChannelizer != null) + if(mPolyphaseChannelizer != null) + { + Iterator iterator = nativeBuffer.iteratorInterleaved(); + + while(iterator.hasNext()) { - Iterator iterator = nativeBuffer.iteratorInterleaved(); - - while(iterator.hasNext()) + try { mPolyphaseChannelizer.receive(iterator.next()); } + catch(Throwable throwable) + { + mLog.error("Error", throwable); + } } } - catch(Throwable throwable) - { - mLog.error("Error", throwable); - } } } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java index a2b00c7b..70a82569 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/PolyphaseChannelSource.java @@ -71,6 +71,14 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen updateOutputProcessor(channelCalculator, filterManager); } + /** + * Description of the state or configuration of the output processor. + */ + public String getStateDescription() + { + return mPolyphaseChannelOutputProcessor.getStateDescription(); + } + /** * Current output processor indexes. * @return indexes @@ -174,7 +182,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen { try { - float[] filter = filterManager.getFilter(channelCalculator.getSampleRate(), + float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(), channelCalculator.getChannelBandwidth(), indexes.size()); mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter); } diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java index f1929860..4d3a2ea5 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/TwoChannelSynthesizerM2.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -19,6 +19,7 @@ package io.github.dsheirer.dsp.filter.channelizer; import io.github.dsheirer.sample.complex.ComplexSamples; +import java.util.Arrays; import org.apache.commons.math3.util.FastMath; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; @@ -65,6 +66,20 @@ public class TwoChannelSynthesizerM2 init(filter); } + /** + * Description of the state/configuration of this synthesizer to support debugging. + * @return state description. + */ + public String getStateDescription() + { + StringBuilder sb = new StringBuilder(); + sb.append("Two-Channel Synthesizer"); + sb.append("\n\t\t Interleaved Filter: " + Arrays.toString(mIQInterleavedFilter)); + sb.append("\n\t\t Serpentine Buffer Length: ").append(mSerpentineDataBuffer.length); + sb.append("\n\t\t Filter Vector Product Length: ").append(mFilterVectorProduct.length); + return sb.toString(); + } + /** * Initializes the synthesizer filter and data buffers for operation. * @@ -73,7 +88,6 @@ public class TwoChannelSynthesizerM2 private void init(float[] filter) { int tapsPerChannel = (int) FastMath.ceil(filter.length / 2); - mIQInterleavedFilter = getInterleavedFilter(filter, tapsPerChannel); mSerpentineDataBuffer = new float[mIQInterleavedFilter.length]; mFilterVectorProduct = new float[mIQInterleavedFilter.length]; diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java index 1bfd7836..6da0c0bf 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/IPolyphaseChannelOutputProcessor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -24,6 +24,11 @@ import java.util.List; public interface IPolyphaseChannelOutputProcessor { + /** + * Description of the state/configuration of this output processor + */ + String getStateDescription(); + /** * Start processing channel results */ diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java index 73137095..9eda378a 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/OneChannelOutputProcessor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -45,6 +45,16 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor mMixerAssembler.getMixer().setSampleRate(sampleRate); } + @Override + public String getStateDescription() + { + StringBuilder sb = new StringBuilder(); + sb.append("One Channel Output Processor"); + sb.append("\n\tIndex (doubled) [").append(mChannelOffset).append("]"); + sb.append("\n\tMixer Assembler: none"); + return sb.toString(); + } + @Override public void setSynthesisFilter(float[] filter) { diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java index d216ccf9..d8c7ff9d 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelMixerAssembler.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -42,6 +42,19 @@ public class TwoChannelMixerAssembler extends MixerAssembler super(gain); } + /** + * Description of the state/configuration of this mixer assembler. + */ + public String getStateDescription() + { + StringBuilder sb = new StringBuilder(); + sb.append("Two Channel Mixer Assembler - State Description:"); + sb.append("\n\tTwo-Channel Synthesizer: ").append(mTwoChannelSynthesizer.getStateDescription()); + sb.append("\n\tMixer Frequency: ").append(getMixer().getFrequency()); + sb.append("\n\tGain: ").append(getGain().getGain()); + return sb.toString(); + } + /** * Sets the filter coefficients to use for two-channel synthesis * @param filter diff --git a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java index 98c2fe31..b1ed6cda 100644 --- a/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java +++ b/src/main/java/io/github/dsheirer/dsp/filter/channelizer/output/TwoChannelOutputProcessor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -49,6 +49,16 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor setSynthesisFilter(filter); } + @Override + public String getStateDescription() + { + StringBuilder sb = new StringBuilder(); + sb.append("Two Channel Output Processor"); + sb.append("\n\tIndices (doubled) 1 [").append(mChannelOffset1).append("] 2 [").append(mChannelOffset2).append("]"); + sb.append("\n\tMixer Assembler: ").append(mMixerAssembler.getStateDescription()); + return sb.toString(); + } + /** * Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband. * diff --git a/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java index 037993b4..22427e51 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/complex/ComplexGain.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -41,7 +41,7 @@ public abstract class ComplexGain * Gain value that will be applied to complex samples * @return gain value */ - protected float getGain() + public float getGain() { return mGain; } diff --git a/src/main/java/io/github/dsheirer/gui/power/ChannelPowerPanel.java b/src/main/java/io/github/dsheirer/gui/power/ChannelPowerPanel.java index 85fce2eb..8de8071d 100644 --- a/src/main/java/io/github/dsheirer/gui/power/ChannelPowerPanel.java +++ b/src/main/java/io/github/dsheirer/gui/power/ChannelPowerPanel.java @@ -190,6 +190,7 @@ public class ChannelPowerPanel extends JPanel implements Listener hbtcs) { diff --git a/src/main/java/io/github/dsheirer/module/ProcessingChain.java b/src/main/java/io/github/dsheirer/module/ProcessingChain.java index 78cdb12e..cf9935a9 100644 --- a/src/main/java/io/github/dsheirer/module/ProcessingChain.java +++ b/src/main/java/io/github/dsheirer/module/ProcessingChain.java @@ -74,9 +74,11 @@ import io.github.dsheirer.source.heartbeat.IHeartbeatListener; import io.github.dsheirer.source.heartbeat.IHeartbeatProvider; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -115,12 +117,15 @@ public class ProcessingChain implements Listener private Broadcaster mMessageBroadcaster = new Broadcaster<>(); private Broadcaster mSquelchStateEventBroadcaster = new Broadcaster<>(); private AtomicBoolean mRunning = new AtomicBoolean(); - private List mModules = new ArrayList<>(); private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500); private MessageHistory mMessageHistory = new MessageHistory(500); private AbstractChannelState mChannelState; private EventBus mEventBus; protected Source mSource; + //Lock to protect access to the modules list. + private ReentrantLock mModuleLock = new ReentrantLock(); + private List mModules = new ArrayList<>(); + /** * Creates a processing chain for managing a set of modules @@ -195,30 +200,46 @@ public class ProcessingChain implements Listener */ public void removeTrafficChannelManager() { - Iterator it = mModules.iterator(); + mModuleLock.lock(); - while(it.hasNext()) + try { - if(it.next() instanceof TrafficChannelManager) + Iterator it = mModules.iterator(); + + while(it.hasNext()) { - it.remove(); + if(it.next() instanceof TrafficChannelManager) + { + it.remove(); + } } } + finally + { + mModuleLock.unlock(); + } } public void dispose() { stop(); - List modules = new ArrayList<>(mModules); + mModuleLock.lock(); - for(Module module : modules) + try { - removeModule(module); - module.dispose(); - } + List modulesToRemove = new ArrayList<>(mModules); - mModules.clear(); + for(Module module : modulesToRemove) + { + removeModule(module); + module.dispose(); + } + } + finally + { + mModuleLock.unlock(); + } mAudioSegmentBroadcaster.dispose(); mDecodeEventBroadcaster.dispose(); @@ -282,7 +303,7 @@ public class ProcessingChain implements Listener */ public List getModules() { - return mModules; + return Collections.unmodifiableList(mModules); } /** @@ -292,13 +313,22 @@ public class ProcessingChain implements Listener { List decoderStates = new ArrayList<>(); - for(Module module : mModules) + mModuleLock.lock(); + + try { - if(module instanceof DecoderState) + for(Module module : mModules) { - decoderStates.add((DecoderState)module); + if(module instanceof DecoderState) + { + decoderStates.add((DecoderState)module); + } } } + finally + { + mModuleLock.unlock(); + } return decoderStates; } @@ -330,7 +360,17 @@ public class ProcessingChain implements Listener */ public void addModule(Module module) { - mModules.add(module); + mModuleLock.lock(); + + try + { + mModules.add(module); + } + finally + { + mModuleLock.unlock(); + } + module.setInterModuleEventBus(getEventBus()); registerListeners(module); registerProviders(module); @@ -345,7 +385,17 @@ public class ProcessingChain implements Listener unregisterListeners(module); unregisterProviders(module); module.setInterModuleEventBus(null); - mModules.remove(module); + + mModuleLock.lock(); + + try + { + mModules.remove(module); + } + finally + { + mModuleLock.unlock(); + } } /** @@ -664,18 +714,27 @@ public class ProcessingChain implements Listener + "sample type - cannot start processing chain"); } - /* Start each of the modules */ - for(Module module : mModules) + mModuleLock.lock(); + + try { - try + /* Start each of the modules */ + for(Module module : mModules) { - module.start(); - } - catch(Exception e) - { - mLog.error("Error starting module", e); + try + { + module.start(); + } + catch(Exception e) + { + mLog.error("Error starting module", e); + } } } + finally + { + mModuleLock.unlock(); + } } else { @@ -716,15 +775,28 @@ public class ProcessingChain implements Listener } /* Stop each of the remaining modules */ - for(Module module : mModules) - { - module.stop(); - } + mModuleLock.lock(); - //Reset each of the modules - for(Module module : mModules) + try { - module.reset(); + for(Module module : mModules) + { + module.stop(); + } + + //Reset each of the modules + for(Module module : mModules) + { + module.reset(); + } + } + catch(Exception e) + { + mLog.error("Error stopping or resetting modules", e); + } + finally + { + mModuleLock.unlock(); } } } @@ -736,13 +808,22 @@ public class ProcessingChain implements Listener { List eventLoggingModules = new ArrayList<>(); - for(Module module : mModules) + mModuleLock.lock(); + + try { - if(module instanceof EventLogger) + for(Module module : mModules) { - eventLoggingModules.add(module); + if(module instanceof EventLogger) + { + eventLoggingModules.add(module); + } } } + finally + { + mModuleLock.unlock(); + } for(Module eventLoggingModule : eventLoggingModules) { @@ -757,21 +838,30 @@ public class ProcessingChain implements Listener { List recordingModules = new ArrayList<>(); - for(Module module : mModules) + mModuleLock.lock(); + + try { - if(module instanceof ComplexSamplesWaveRecorder) + for(Module module : mModules) { - recordingModules.add(module); - } - else if(module instanceof MBECallSequenceRecorder) - { - recordingModules.add(module); - } - else if(module instanceof BinaryRecorder) - { - recordingModules.add(module); + if(module instanceof ComplexSamplesWaveRecorder) + { + recordingModules.add(module); + } + else if(module instanceof MBECallSequenceRecorder) + { + recordingModules.add(module); + } + else if(module instanceof BinaryRecorder) + { + recordingModules.add(module); + } } } + finally + { + mModuleLock.unlock(); + } for(Module recordingModule : recordingModules) { diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/ChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/ChannelSourceManager.java index ba001ce7..91be9a0f 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/ChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/ChannelSourceManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -52,6 +52,12 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor */ public abstract SortedSet getTunerChannels(); + /** + * State of this channel source manager for logging and debug purposes + * @return description of the state or configuration of this manager + */ + public abstract String getStateDescription(); + /** * Count of tuner channels being sourced by this source manager. * @return diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/DiscoveredTuner.java b/src/main/java/io/github/dsheirer/source/tuner/manager/DiscoveredTuner.java index dfd577f7..bd8fd3db 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/DiscoveredTuner.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/DiscoveredTuner.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -55,6 +55,27 @@ public abstract class DiscoveredTuner implements ITunerErrorListener return mTunerStatus; } + /** + * Logs current state of the tuner + */ + public void logState() + { + StringBuilder sb = new StringBuilder(); + sb.append("Discovered Tuner: ").append(getId()); + + if(hasTuner()) + { + sb.append("\n\tTuner - Frequency:").append(getTuner().getTunerController().getFrequency()); + sb.append("\n\tChannel Manager:").append(getTuner().getChannelSourceManager().getStateDescription()); + } + else + { + sb.append("\n\tTuner - no tuner"); + } + + mLog.info(sb.toString()); + } + /** * Sets the status of the discovered tuner and notifies registered listeners of the status change. * @param tunerStatus to set diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java index 029aef87..d6c0de8c 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/HeterodyneChannelSourceManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -60,6 +60,23 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager mTunerController.addListener(this); } + @Override + public String getStateDescription() + { + StringBuilder sb = new StringBuilder(); + sb.append("Heterodyne Channel Source Manager Providing [").append(mTunerChannels.size()).append("] Channels"); + sb.append("\n\tTuner Controller Frequency: ").append(mTunerController.getFrequency()); + for(HalfBandTunerChannelSource channelSource: mChannelSources) + { + sb.append("\n\tChannel [").append(channelSource.getTunerChannel()) + .append("] Frequency [").append(channelSource.getFrequency()) + .append("] Mixer [").append(channelSource.getMixerFrequency()) + .append("]"); + } + + return sb.toString(); + } + @Override public void stopAllChannels() { diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/PassThroughSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/PassThroughSourceManager.java index a1584eb2..a1981e2d 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/PassThroughSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/PassThroughSourceManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -54,6 +54,19 @@ public class PassThroughSourceManager extends ChannelSourceManager mTunerController = tunerController; } + @Override + public String getStateDescription() + { + StringBuilder sb = new StringBuilder(); + sb.append("Pass-Through Channel Source Manager Providing [").append(mTunerChannels.size()).append("] Channels"); + for(TunerChannel channel: mTunerChannels) + { + sb.append("\n\tChannel: ").append(channel); + } + + return sb.toString(); + } + @Override public void stopAllChannels() { diff --git a/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java index fc490b53..2f3c7601 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/manager/PolyphaseChannelSourceManager.java @@ -55,6 +55,17 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager mTunerController.addListener(mPolyphaseChannelManager); } + @Override + public String getStateDescription() + { + StringBuilder sb = new StringBuilder(); + sb.append("Polyphase Channel Source Manager"); + sb.append("\n\tTuner Controller Frequency: ").append(mTunerController.getFrequency()); + sb.append("\n\t").append(mPolyphaseChannelManager.getStateDescription()); + + return sb.toString(); + } + @Override public void stopAllChannels() { @@ -454,8 +465,13 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager } catch(IllegalArgumentException iae) { + mLog.error("Center frequency calculation failed", iae); //Center frequency calculation failed } + catch(Exception e) + { + mLog.error("Error getting source", e); + } } } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/ui/DiscoveredTunerModel.java b/src/main/java/io/github/dsheirer/source/tuner/ui/DiscoveredTunerModel.java index 615a1410..b8159f90 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/ui/DiscoveredTunerModel.java +++ b/src/main/java/io/github/dsheirer/source/tuner/ui/DiscoveredTunerModel.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -189,6 +189,7 @@ public class DiscoveredTunerModel extends AbstractTableModel implements Listener */ public void removeDiscoveredTuner(DiscoveredTuner discoveredTuner) { + mLog.info("Removing discovered tuner: " + discoveredTuner.getId()); mLock.lock(); try 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 b78351b6..99863df4 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 @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -39,7 +39,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.JButton; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; @@ -115,6 +117,32 @@ public class TunerViewPanel extends JPanel } }); + //Add support for right-click context menu to the tuner table + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem logStateMenuItem = new JMenuItem("Log Tuner State"); + logStateMenuItem.addActionListener(e -> { + int viewRow = mTunerTable.getSelectedRow(); + + DiscoveredTuner selected = null; + + if(viewRow >= 0) + { + int modelRow = mTunerTable.convertRowIndexToModel(viewRow); + selected = mDiscoveredTunerModel.getDiscoveredTuner(modelRow); + } + + if(selected != null) + { + selected.logState(); + } + else + { + mLog.error("Can't log state - tuner not selected"); + } + }); + popupMenu.add(logStateMenuItem); + mTunerTable.setComponentPopupMenu(popupMenu); + //Monitor for tuner removal events so we can update the editor when our selected tuner is removed mDiscoveredTunerModel.addTableModelListener(e -> {