39 [WIP] - restructured the polyphase filter process. Previous version

was not computationally feasible with my 3rd Gen i7 processor running
and airspy at 10 MHz.  Restructured the polyphase channelizer to use
vector iteration structures that the JDK 9 JIT compiler can translate
into SIMD instructions.  Separated the IFFT processing from the filter
process so that they can be executed on separate threads and this allows
the processing to occur real time.

P25 C4FM decoding at 25 kHz channel sample rate using an airspy
10MHz sample stream seems to be working nicely with ~25% CPU usage and
~300 to 500 MB memory usage.  Incremental (parallel) channel decoding
should have a minimal increase in CPU loading since all of the 12.5 kHz
channels are available in the polyphase channelizer and the only
additional workload is the decoding of the 25 kHz sample stream.

There is still an issue with stopping channels and the channelizer keeps
 processing, and occasionally when starting traffic channels something
 deadlocks and causes out of memory.
This commit is contained in:
Denny
2018-04-15 09:05:06 -04:00
parent ff9395e4b9
commit e971ad4582
18 changed files with 637 additions and 702 deletions

View File

@@ -0,0 +1,143 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.sample.buffer;
import java.util.LinkedList;
import java.util.List;
public class ReusableChannelResultsBuffer extends AbstractReusableBuffer
{
private LinkedList<float[]> mEmptyBuffers = new LinkedList<>();
private LinkedList<float[]> mFilledBuffers = new LinkedList<>();
private Integer mLength;
/**
* Reusable buffer for storing polyphase channelizer results arrays. This buffer is NOT thread safe and is
* designed to be used by only one consumer. The consumer must invoke the recycle() method to indicate that this
* buffer can be reused.
*
* @param disposedListener to receive notification when this buffer is ready for recycle
*/
ReusableChannelResultsBuffer(IReusableBufferDisposedListener disposedListener)
{
super(disposedListener);
}
/**
* Recycles this buffer for reuse.
*/
protected void recycle()
{
mEmptyBuffers.addAll(mFilledBuffers);
mFilledBuffers.clear();
mLength = null;
}
/**
* Gets an empty channel results array correctly sized to the requested length. This method should be used to
* obtain all empty channel results buffers to fill with information and add back to this buffer so that the results
* arrays can be reused.
*
* @param length of the requested array
* @return a float array of the requested length that may contain stale data from previous use.
*/
public float[] getEmptyBuffer(int length)
{
float[] emptyBuffer = mEmptyBuffers.poll();
if(emptyBuffer == null || emptyBuffer.length != length)
{
emptyBuffer = new float[length];
}
return emptyBuffer;
}
/**
* Adds the channel results to this buffer. Note: you should only use channel results arrays obtained via the
* getEmptyBuffer() method and filled with data and then added back to this buffer via this method, so that array
* reuse can be accurately controlled.
*
* @param channelResults to add back to this buffer
*/
public void addChannelResults(float[] channelResults)
{
if(mLength == null)
{
mLength = channelResults.length;
}
else if(mLength != channelResults.length)
{
throw new IllegalArgumentException("Channel results length must be the same for all added results");
}
mFilledBuffers.offer(channelResults);
}
/**
* Channel results array contained in this buffer
*/
public List<float[]> getChannelResults()
{
return mFilledBuffers;
}
/**
* Extracts a single I/Q interleaved sample buffer from this buffer of channel results arrays
*
* @param iChannelIndex to extract for the inphase sample. The q index is assumed to be one greater than i.
* @return float[] containing interleaved I and Q samples
* @throws IllegalArgumentException if this buffer doesn't contain any samples or if the requested channel is
* not within the channel range of the contained channel results.
*/
public float[] getChannel(int iChannelIndex)
{
if(mFilledBuffers.isEmpty())
{
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 " +
"results -- max channel is " + mLength);
}
float[] samples = new float[mFilledBuffers.size() * 2];
int pointer = 0;
int qChannelIndex = iChannelIndex + 1;
for(float[] channelResults: mFilledBuffers)
{
samples[pointer++] = channelResults[iChannelIndex];
samples[pointer++] = channelResults[qChannelIndex];
}
return samples;
}
/**
* Indicates if the channel argument is valid for the contained channel results.
*
* @param channelIndex number (0 to N) to check
* @return false if the buffer is empty or if the channel is greater than the contained channel results
*/
private boolean isValidChannelIndex(int channelIndex)
{
return (mLength != null) && (channelIndex <= mLength);
}
}