mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Polyphase Channelizer Viewer is fully functional again, now using the
TestTuner and TestTunerController implementations. This implementation only has support for channels that are completely centered within a channelizer output. Two output slice stitching to form a composite channel will be implemented next. Initial profiling indicates that the FFT function within the polyphase channelizer is now the dominant processing workload - 28% cpu loading when channelizing a 10 MHz sample rate input into 800 channels running at 25.0 kHz each. This is ideal for offloading to an OpenCL kernel and running on a GPU.
This commit is contained in:
@@ -23,6 +23,7 @@ import io.github.dsheirer.settings.SettingsManager;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
|
||||
@@ -44,6 +45,8 @@ import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
@@ -55,13 +58,14 @@ public class ChannelizerViewer extends JFrame
|
||||
private SettingsManager mSettingsManager = new SettingsManager(new TunerConfigurationModel());
|
||||
private JPanel mPrimaryPanel;
|
||||
private JPanel mControlPanel;
|
||||
private JLabel mToneFrequencyLabel;
|
||||
private PrimarySpectrumPanel mPrimarySpectrumPanel;
|
||||
private ChannelArrayPanel mChannelPanel;
|
||||
private int mChannelCount;
|
||||
private int mChannelsPerRow;
|
||||
private long mToneFrequency;
|
||||
private long mBaseFrequency = 100000000; //100 MHz
|
||||
private DFTSize mMainPanelDFTSize = DFTSize.FFT32768;
|
||||
private DFTSize mChannelPanelDFTSize = DFTSize.FFT01024;
|
||||
private DFTSize mChannelPanelDFTSize = DFTSize.FFT04096;
|
||||
private TestTuner mTestTuner;
|
||||
|
||||
/**
|
||||
@@ -76,16 +80,12 @@ public class ChannelizerViewer extends JFrame
|
||||
mChannelCount = (int)(mTestTuner.getTunerController().getUsableBandwidth() / CHANNEL_BANDWIDTH);
|
||||
mChannelsPerRow = channelsPerRow;
|
||||
|
||||
mToneFrequency = mTestTuner.getTunerController().getFrequency() + 19200;
|
||||
|
||||
mTestTuner.getTunerController().setToneFrequency(mToneFrequency);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setTitle("TunerChannelizer Viewer");
|
||||
setTitle("Polyphase Channelizer Viewer");
|
||||
setSize(1000, 800);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
@@ -129,11 +129,11 @@ public class ChannelizerViewer extends JFrame
|
||||
mControlPanel.setLayout(new MigLayout("insets 0 0 0 0", "", ""));
|
||||
|
||||
mControlPanel.add(new JLabel("Tone:"), "align left");
|
||||
int maximumFrequencyOffset = (int)(mTestTuner.getTunerController().getSampleRate() / 2);
|
||||
long minimumFrequency = mTestTuner.getTunerController().getFrequency() - maximumFrequencyOffset;
|
||||
long maximumFrequency = mTestTuner.getTunerController().getFrequency() + maximumFrequencyOffset;
|
||||
long minimumFrequency = -(long)mTestTuner.getTunerController().getSampleRate() / 2;
|
||||
long maximumFrequency = (long)mTestTuner.getTunerController().getSampleRate() / 2;
|
||||
long toneFrequency = 0;
|
||||
|
||||
SpinnerNumberModel model = new SpinnerNumberModel(mToneFrequency, minimumFrequency, maximumFrequency,
|
||||
SpinnerNumberModel model = new SpinnerNumberModel(toneFrequency, minimumFrequency, maximumFrequency,
|
||||
100 );
|
||||
|
||||
model.addChangeListener(new ChangeListener()
|
||||
@@ -141,8 +141,9 @@ public class ChannelizerViewer extends JFrame
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
int toneFrequency = model.getNumber().intValue();
|
||||
long toneFrequency = model.getNumber().longValue();
|
||||
mTestTuner.getTunerController().setToneFrequency(toneFrequency);
|
||||
mToneFrequencyLabel.setText(String.valueOf(getToneFrequency()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -151,15 +152,21 @@ public class ChannelizerViewer extends JFrame
|
||||
mControlPanel.add(spinner);
|
||||
mControlPanel.add(new JLabel("Hz"));
|
||||
|
||||
mControlPanel.add(new JLabel("Sample Rate: " + mTestTuner.getTunerController().getSampleRate() + " Hz"), "push,align right");
|
||||
mControlPanel.add(new JLabel("Frequency:"), "push,align right");
|
||||
mToneFrequencyLabel = new JLabel(String.valueOf(getToneFrequency()));
|
||||
mControlPanel.add(mToneFrequencyLabel, "push,align left");
|
||||
|
||||
mControlPanel.add(new JLabel("Max: " + (mTestTuner.getTunerController().getSampleRate() / 2) + " Hz"), "push,align right");
|
||||
mControlPanel.add(new JLabel("Channels: " + mChannelCount), "push,align right");
|
||||
}
|
||||
|
||||
return mControlPanel;
|
||||
}
|
||||
|
||||
private long getToneFrequency()
|
||||
{
|
||||
return mTestTuner.getTunerController().getFrequency() + mTestTuner.getTunerController().getToneFrequency();
|
||||
}
|
||||
|
||||
private ChannelArrayPanel getChannelArrayPanel()
|
||||
{
|
||||
if(mChannelPanel == null)
|
||||
@@ -192,7 +199,7 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
int channelToLog = -1;
|
||||
|
||||
long baseFrequency = 100006250; //100.006250 MHz
|
||||
long baseFrequency = mBaseFrequency + (CHANNEL_BANDWIDTH / 2);
|
||||
|
||||
for(int x = 0; x < mChannelCount - 1; x++)
|
||||
{
|
||||
@@ -215,6 +222,33 @@ public class ChannelizerViewer extends JFrame
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tuner channels that will fit within the tuner's bandwidth, minus a half channel each at the
|
||||
* lower and upper ends of the spectrum.
|
||||
*
|
||||
* @param tuner to create channels for
|
||||
* @return list of contiguous channels filling the tuner bandwidth
|
||||
*/
|
||||
public static List<TunerChannel> getTunerChannels(Tuner tuner)
|
||||
{
|
||||
List<TunerChannel> tunerChannels = new ArrayList<>();
|
||||
|
||||
long baseFrequency = tuner.getTunerController().getFrequency();
|
||||
baseFrequency -= tuner.getTunerController().getSampleRate() / 2;
|
||||
baseFrequency += (CHANNEL_BANDWIDTH / 2);
|
||||
|
||||
int channelCount = (int)(tuner.getTunerController().getSampleRate() / CHANNEL_BANDWIDTH) - 1;
|
||||
|
||||
for(int x = 0; x < channelCount; x++)
|
||||
{
|
||||
long frequency = baseFrequency + (x * CHANNEL_BANDWIDTH);
|
||||
TunerChannel tunerChannel = new TunerChannel(frequency, CHANNEL_BANDWIDTH);
|
||||
tunerChannels.add(tunerChannel);
|
||||
}
|
||||
|
||||
return tunerChannels;
|
||||
}
|
||||
|
||||
public class PrimarySpectrumPanel extends JPanel implements Listener<ComplexBuffer>, ISourceEventProcessor
|
||||
{
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
@@ -225,6 +259,7 @@ public class ChannelizerViewer extends JFrame
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
mSpectrumPanel.setSampleSize(26);
|
||||
add(mSpectrumPanel);
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
@@ -256,21 +291,20 @@ public class ChannelizerViewer extends JFrame
|
||||
private DFTProcessor mDFTProcessor = new DFTProcessor(SampleType.COMPLEX);
|
||||
private ComplexDecibelConverter mComplexDecibelConverter = new ComplexDecibelConverter();
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private long mFrequency;
|
||||
|
||||
public ChannelPanel(SettingsManager settingsManager, double sampleRate, long frequency, int bandwidth, boolean enableLogging)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[center,grow,fill]", "[grow,fill][]"));
|
||||
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||
add(mSpectrumPanel);
|
||||
mSpectrumPanel.setSampleSize(28);
|
||||
add(mSpectrumPanel, "wrap");
|
||||
add(new JLabel("Center:" + frequency));
|
||||
|
||||
mDFTProcessor.addConverter(mComplexDecibelConverter);
|
||||
mDFTProcessor.process(SourceEvent.sampleRateChange(sampleRate));
|
||||
|
||||
mComplexDecibelConverter.addListener(mSpectrumPanel);
|
||||
|
||||
mFrequency = frequency;
|
||||
|
||||
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
|
||||
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
|
||||
|
||||
@@ -326,18 +360,69 @@ public class ChannelizerViewer extends JFrame
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
int channelsPerRow = 4;
|
||||
boolean useGUI = true;
|
||||
|
||||
final ChannelizerViewer frame = new ChannelizerViewer(channelsPerRow);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
if(useGUI)
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
int channelsPerRow = 4;
|
||||
|
||||
final ChannelizerViewer frame = new ChannelizerViewer(channelsPerRow);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
frame.setVisible(true);
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
frame.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
TestTuner tuner = new TestTuner();
|
||||
|
||||
List<TunerChannel> tunerChannels = getTunerChannels(tuner);
|
||||
|
||||
List<TunerChannelSource> sources = new ArrayList<>();
|
||||
|
||||
int maxSourceCount = 30;
|
||||
int sourceCount = 0;
|
||||
for(TunerChannel tunerChannel: tunerChannels)
|
||||
{
|
||||
if(sourceCount < maxSourceCount)
|
||||
{
|
||||
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel);
|
||||
|
||||
if(source != null)
|
||||
{
|
||||
sources.add(source);
|
||||
sourceCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Couldn't get source for: " + tunerChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mLog.debug("Starting [" + sources.size() + "] tuner channel sources");
|
||||
|
||||
for(TunerChannelSource tunerChannelSource: sources)
|
||||
{
|
||||
tunerChannelSource.setListener(new Listener<ComplexBuffer>()
|
||||
{
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
//Do nothing
|
||||
}
|
||||
});
|
||||
|
||||
tunerChannelSource.start();
|
||||
}
|
||||
|
||||
while(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -197,11 +197,16 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
|
||||
int half = getChannelCount() / 2;
|
||||
|
||||
int outputOffset = getChannelCount() - half - 1;
|
||||
int outputIndex;
|
||||
int sampleIndex;
|
||||
int filterIndex;
|
||||
|
||||
for(int channel = 0; channel < half; channel++)
|
||||
{
|
||||
int outputIndex = 2 * (getChannelCount() - channel - 1);
|
||||
int sampleIndex = 2 * channel;
|
||||
int filterIndex = channel + half;
|
||||
outputIndex = 2 * (outputOffset - channel);
|
||||
sampleIndex = 2 * channel;
|
||||
filterIndex = channel + half;
|
||||
|
||||
processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap);
|
||||
processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap);
|
||||
@@ -209,12 +214,13 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
|
||||
//The second half of the sample buffer was loaded using the previous column pointer - adjust for that now
|
||||
sampleIndexMap = mSampleIndexMap[(mColumnPointer == (mSamples.length - 1) ? 0 : mColumnPointer + 1)];
|
||||
outputOffset = getChannelCount() + half - 1;
|
||||
|
||||
for(int channel = half; channel < getChannelCount(); channel++)
|
||||
{
|
||||
int outputIndex = 2 * (getChannelCount() - channel - 1);
|
||||
int sampleIndex = 2 * channel;
|
||||
int filterIndex = channel - half;
|
||||
outputIndex = 2 * (outputOffset - channel);
|
||||
sampleIndex = 2 * channel;
|
||||
filterIndex = channel - half;
|
||||
|
||||
processed[outputIndex] = filter(sampleIndex, mCoefficients[filterIndex], sampleIndexMap);
|
||||
processed[outputIndex + 1] = filter(sampleIndex + 1, mCoefficients[filterIndex], sampleIndexMap);
|
||||
|
||||
@@ -132,7 +132,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
try
|
||||
{
|
||||
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
|
||||
mLog.debug("Getting polyphase channel for: " + tunerChannel + " indexes:" + polyphaseIndexes);
|
||||
|
||||
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
|
||||
|
||||
@@ -240,7 +239,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Update channel calculator immediately so that channels can be allocated
|
||||
mLog.debug("Updating channel calculator to frequency: " + sourceEvent.getValue());
|
||||
mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
|
||||
|
||||
//Defer channelizer configuration changes to be handled on the buffer processor thread
|
||||
@@ -248,7 +246,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
//Update channel calculator immediately so that channels can be allocated
|
||||
mLog.debug("Updating channel calculator to sample rate: " + sourceEvent.getValue());
|
||||
mChannelCalculator.setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
|
||||
//Defer channelizer configuration changes to be handled on the buffer processor thread
|
||||
@@ -355,13 +352,11 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
//If the indexes size is the same then update the current processor, otherwise create a new one
|
||||
if(channelSource.getPolyphaseChannelOutputProcessor().getInputChannelCount() == indexes.size())
|
||||
{
|
||||
mLog.debug("Updating indexes to " + indexes + " for: " + channelSource.getTunerChannel());
|
||||
channelSource.getPolyphaseChannelOutputProcessor().setPolyphaseChannelIndices(indexes);
|
||||
channelSource.setFrequency(centerFrequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Creating new output processor for indexes " + indexes);
|
||||
channelSource.setPolyphaseChannelOutputProcessor(getOutputProcessor(indexes), centerFrequency);
|
||||
}
|
||||
}
|
||||
@@ -487,17 +482,13 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
switch(queuedSourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
mLog.debug("******Frequency change event");
|
||||
updateOutputProcessors(queuedSourceEvent);
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
mLog.debug("******Sample Rate change event");
|
||||
updateChannelizerSampleRate(queuedSourceEvent.getValue().intValue());
|
||||
break;
|
||||
}
|
||||
|
||||
mLog.debug("****** Source event processed ... continuing");
|
||||
|
||||
queuedSourceEvent = mQueuedSourceEvents.poll();
|
||||
}
|
||||
|
||||
|
||||
@@ -72,10 +72,10 @@ public class PolyphaseChannelResultsBuffer
|
||||
{
|
||||
throw new IllegalArgumentException("Buffer is full - cannot add new channel results");
|
||||
}
|
||||
|
||||
if(mPointer == 0)
|
||||
{
|
||||
mChannelResults = new float[mMaxSize][channelResults.length];
|
||||
mChannelResults[mPointer++] = channelResults;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -87,9 +87,9 @@ public class PolyphaseChannelResultsBuffer
|
||||
"a different length than the channel results that are already contained in this buffer [" +
|
||||
currentLength + "]");
|
||||
}
|
||||
|
||||
mChannelResults[mPointer++] = channelResults;
|
||||
}
|
||||
|
||||
mChannelResults[mPointer++] = channelResults;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,10 +97,12 @@ public class PolyphaseChannelResultsBuffer
|
||||
*/
|
||||
public float[][] getChannelResults()
|
||||
{
|
||||
//If this buffer is full, return all of the channel results
|
||||
if(mPointer == mMaxSize)
|
||||
{
|
||||
return mChannelResults;
|
||||
}
|
||||
//Otherwise, truncate the results to return
|
||||
else
|
||||
{
|
||||
return Arrays.copyOf(mChannelResults, mPointer);
|
||||
@@ -120,6 +122,7 @@ public class PolyphaseChannelResultsBuffer
|
||||
{
|
||||
throw new IllegalArgumentException("Buffer is empty - cannot extract channel [" + iChannelIndex + "]");
|
||||
}
|
||||
|
||||
if(!isValidChannelIndex(iChannelIndex))
|
||||
{
|
||||
throw new IllegalArgumentException("Channel [" + iChannelIndex + "] is not valid for the contained channel " +
|
||||
@@ -131,8 +134,10 @@ public class PolyphaseChannelResultsBuffer
|
||||
|
||||
int qChannelIndex = iChannelIndex + 1;
|
||||
|
||||
for(float[] channelResults: mChannelResults)
|
||||
for(int x = 0; x < mPointer; x++)
|
||||
{
|
||||
float[] channelResults = mChannelResults[x];
|
||||
|
||||
samples[pointer++] = channelResults[iChannelIndex];
|
||||
samples[pointer++] = channelResults[qChannelIndex];
|
||||
}
|
||||
|
||||
@@ -60,15 +60,6 @@ public class ScheduledBufferProcessor<E> implements Listener<E>
|
||||
public ScheduledBufferProcessor(int maximumSize, int resetThreshold, long distributionInterval, int maxBuffersPerInterval)
|
||||
{
|
||||
mQueue = new OverflowableTransferQueue<>(maximumSize, resetThreshold);
|
||||
|
||||
mQueue.setOverflowListener(new IOverflowListener()
|
||||
{
|
||||
@Override
|
||||
public void sourceOverflow(boolean overflow)
|
||||
{
|
||||
mLog.error("Overflow state changed - overflow:" + overflow);
|
||||
}
|
||||
});
|
||||
mDistributionInterval = distributionInterval;
|
||||
mMaxBuffersPerInterval = maxBuffersPerInterval;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
{
|
||||
super(1, sampleRate);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
mLog.debug("Sample Rate:" + sampleRate + " PolyPhase Channel Indexes:" + channelIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user