#39 WIP - rolls back embedding source events in buffers. The channel

calculator must be updated immediately any time the tuner's center
frequency changes so that a channel can be allocated immediately.  So,
I changed source event handling to two parts where the channel calculator
is immediately updated and the channelizer configuration is deferred so
that it can happen on the buffer processor execution thread and we can
avoid any sync() locking on the channelizer or polyphase channel
sources.
This commit is contained in:
Denny
2018-01-13 11:43:26 -05:00
parent 05fa5c25fd
commit d4761be00a
9 changed files with 80 additions and 320 deletions

View File

@@ -1,171 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ComplexPolyphaseChannelizer extends AbstractComplexPolyphaseChannelizer
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizer.class);
private RealFIRFilter[] mInphaseFilters;
private RealFIRFilter[] mQuadratureFilters;
private int mFilterPointer = 0;
private float[] mFilteredSamples;
private FloatFFT_1D mFFT;
/**
* Maximally Decimated Polyphase Filter Bank (MDPFB) channelizer that divides the input frequency band into
* equal bandwidth channels.
*
* @param coefficients of a prototype low-pass filter designed for the inbound sample rate with a cutoff frequency
* equal to the channel bandwidth (sample rate / filters). If you need to synthesize (combine two or more
* filter outputs) a new bandwidth signal from the outputs of this filter, then the filter should be designed
* as a nyquist filter with -6 dB attenuation at the channel bandwidth cutoff frequency
*
* @param channelCount - number of channels to output.
* @param channelSampleRate - channel sample rate (after decimation)
*/
public ComplexPolyphaseChannelizer(float[] coefficients, int channelCount, int channelSampleRate)
{
super(channelCount, channelSampleRate);
initFilters(coefficients);
}
@Override
public void receive(ComplexBuffer complexBuffer)
{
float[] samples = complexBuffer.getSamples();
for(int x = 0; x < samples.length; x += 2)
{
filter(samples[x], samples[x + 1]);
}
}
protected void filter(float inphase, float quadrature)
{
int index = (getChannelCount() - mFilterPointer - 1) * 2;
mFilteredSamples[index] = mInphaseFilters[mFilterPointer].filter(inphase);
mFilteredSamples[index + 1] = mQuadratureFilters[mFilterPointer].filter(quadrature);
if(mFilterPointer == 0)
{
calculate();
mFilterPointer = getChannelCount() - 1;
}
else
{
mFilterPointer--;
}
}
private void calculate()
{
float[] samples = new float[getChannelCount() * 2];
System.arraycopy(mFilteredSamples, 0, samples, 0, mFilteredSamples.length);
//FFT is executed in-place where the output overwrites the input
mFFT.complexForward(samples);
dispatch(samples);
}
/**
* Distributes the filter taps to each of the polyphase filters
*
* @param taps
*/
private void initFilters(float[] taps)
{
int tapCount = (int)Math.ceil((double)taps.length / (double)getChannelCount());
float[][] coefficients = new float[getChannelCount()][tapCount];
for(int tap = 0; tap < tapCount; tap++)
{
for(int filter = 0; filter < getChannelCount(); filter++)
{
int index = tap * getChannelCount() + filter;
if(index < taps.length)
{
coefficients[filter][tap] = taps[index];
}
}
}
mInphaseFilters = new RealFIRFilter[getChannelCount()];
mQuadratureFilters = new RealFIRFilter[getChannelCount()];
for(int filter = 0; filter < getChannelCount(); filter++)
{
mInphaseFilters[filter] = new RealFIRFilter(coefficients[filter], 1.0f);
mQuadratureFilters[filter] = new RealFIRFilter(coefficients[filter], 1.0f);
}
mFilteredSamples = new float[getChannelCount() * 2];
mFFT = new FloatFFT_1D(getChannelCount());
mFilterPointer = getChannelCount() -1;
}
public static void main(String[] args)
{
mLog.debug("Starting ...");
int symbolRate = 4800;
int samplesPerSymbol = 2;
int channels = 800;
int channelBandwidth = 12500;
int sampleRate = channels * channelBandwidth;
float[] taps = FilterFactory.getLowPass( 10000000, 12500, 5600, Window.WindowType.BLACKMAN_HARRIS_7 );
int channelCenter = (int)(12500 * 1.5);
StringBuilder sb = new StringBuilder();
sb.append("\nPolyphase TunerChannelizer\n");
sb.append("Sample Rate:" + sampleRate + " Channels:" + channels + " Channel Rate:" + channelBandwidth + "\n");
sb.append("Tap Count:" + taps.length + "\n");
sb.append("Channel:" + channelCenter);
mLog.debug(sb.toString());
ComplexPolyphaseChannelizer channelizer = new ComplexPolyphaseChannelizer(taps, 8, channelBandwidth);
Oscillator oscillator = new Oscillator(channelCenter, 2 * sampleRate);
for(int x = 0; x < 2000; x++)
{
channelizer.filter(oscillator.getComplex().inphase(), oscillator.getComplex().quadrature());
oscillator.rotate();
}
mLog.debug("Finished!");
}
}

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.sample.complex.IComplexBufferProvider;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
@@ -32,11 +33,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Queue;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
public class PolyphaseChannelManager
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 12500.0;
@@ -224,6 +227,40 @@ public class PolyphaseChannelManager
}
}
/**
* Process source events received from the source (ie tuner controller) for frequency and sample rate change
* notifications.
* @param sourceEvent to process
* @throws SourceException
*/
@Override
public void process(SourceEvent sourceEvent) throws SourceException
{
switch(sourceEvent.getEvent())
{
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
mBufferSourceEventMonitor.receive(sourceEvent);
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
mBufferSourceEventMonitor.receive(sourceEvent);
break;
default:
mLog.info("Unrecognized source event: " + sourceEvent.toString());
break;
}
}
/**
* Creates or updates the channelizer to process the incoming sample rate and updates any channel processors.
*
@@ -235,10 +272,9 @@ public class PolyphaseChannelManager
*/
private void updateChannelizerSampleRate(double sampleRate)
{
if(mPolyphaseChannelizer == null || Math.abs(mPolyphaseChannelizer.getSampleRate() - sampleRate) > 1.0)
if(mPolyphaseChannelizer == null || Math.abs(mPolyphaseChannelizer.getSampleRate() - sampleRate) > 0.5)
{
mLog.debug("Creating or Updating Channelizer for sample rate " + sampleRate);
mChannelCalculator.setSampleRate(sampleRate);
try
{
@@ -271,21 +307,12 @@ public class PolyphaseChannelManager
}
}
private void updateChannelizerCenterFrequency(long frequency)
{
mChannelCalculator.setCenterFrequency(frequency);
mLog.debug("Updating output processors - center frequency is now: " + frequency);
updateOutputProcessors(null);
}
/**
* Updates each of the output processors for any changes in the tuner's center frequency or sample rate, which
* would cause the output processors to change the polyphase channelizer results channel(s) that the processor is
* consuming
*
* @param sourceEvent that requires an update to the output processors
* @param sourceEvent (optional-can be null) to broadcast to each output processor following the update
*/
private void updateOutputProcessors(SourceEvent sourceEvent)
{
@@ -293,13 +320,11 @@ public class PolyphaseChannelManager
{
updateOutputProcessor(channelSource);
//Notify the channel source that the frequency or sample rate has changed so that it can reset
//any frequency correction value.
//Send the non-null source event to each channel source
if(sourceEvent != null)
{
try
{
//TODO: this notification event should go to the channelizer to be embedded in the channel buffer stream
channelSource.process(sourceEvent);
}
catch(SourceException se)
@@ -429,17 +454,44 @@ public class PolyphaseChannelManager
}
/**
* Processes the incoming buffer stream from the provider to detect frequency and sample rate change source events
* and update the channelizer configuration as necessary.
* 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.
*/
public class BufferSourceEventMonitor implements Listener<ComplexBuffer>
{
private Queue<SourceEvent> mQueuedSourceEvents = new ConcurrentLinkedQueue<>();
/**
* Queues the source event for deferred execution on the buffer processing thread.
* @param event that affects configuration of the channelizer (frequency or sample rate change events)
*/
public void receive(SourceEvent event)
{
mQueuedSourceEvents.offer(event);
}
@Override
public void receive(ComplexBuffer complexBuffer)
{
if(complexBuffer.hasSourceEvent())
//Process any queued source events before processing the buffer
SourceEvent queuedSourceEvent = mQueuedSourceEvents.poll();
while(queuedSourceEvent != null)
{
process(complexBuffer.getSourceEvent());
switch(queuedSourceEvent.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
updateOutputProcessors(queuedSourceEvent);
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
updateChannelizerSampleRate(queuedSourceEvent.getValue().intValue());
break;
}
queuedSourceEvent = mQueuedSourceEvents.poll();
}
if(mPolyphaseChannelizer != null)
@@ -447,29 +499,5 @@ public class PolyphaseChannelManager
mPolyphaseChannelizer.receive(complexBuffer);
}
}
/**
* Processes frequency and sample rate change events that are embedded in the complex buffer stream from the
* tuner or sample source
*/
public void process(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_FREQUENCY_CHANGE:
//Update the index calculator and each of the output processors to update each channel frequency
mChannelCalculator.setCenterFrequency(sourceEvent.getValue().longValue());
mLog.debug("Updating output processors - center frequency is now: " + sourceEvent.getValue().longValue());
updateOutputProcessors(sourceEvent);
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
//Update the channelizer (and the output processors) for the new sample rate
updateChannelizerSampleRate(sourceEvent.getValue().intValue());
break;
default:
mLog.info("Received an unrecognized source event: " + sourceEvent.getEvent());
break;
}
}
}
}