Skip to content

Commit

Permalink
#1563 Updates RSP tuner to produce sample buffers with optimal power-…
Browse files Browse the repository at this point in the history
…of-2 length to enable downstream vectorized operations. (#1583)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
  • Loading branch information
DSheirer and Dennis Sheirer committed Jun 24, 2023
1 parent b3837f5 commit e5f5df7
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 8 deletions.
Expand Up @@ -262,7 +262,14 @@ private void startDelayBuffer()
{
if(mSampleDelayBuffer == null)
{
int delayBufferSize = (int)(DELAY_BUFFER_DURATION_MILLISECONDS / mTunerController.getBufferDuration());
long bufferDuration = mTunerController.getBufferDuration();

if(bufferDuration <= 0)
{
bufferDuration = 1;
}

int delayBufferSize = (int)(DELAY_BUFFER_DURATION_MILLISECONDS / bufferDuration);
mSampleDelayBuffer = new NativeSampleDelayBuffer(delayBufferSize, mTunerController.getBufferDuration());
mTunerController.addBufferListener(mSampleDelayBuffer);
}
Expand Down
@@ -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
Expand All @@ -23,8 +23,6 @@
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Native buffer implementation for RSP tuner I/Q sample buffers.
Expand All @@ -33,7 +31,6 @@
*/
public class RspNativeBuffer extends AbstractNativeBuffer
{
private static final Logger mLog = LoggerFactory.getLogger(RspNativeBuffer.class);
private static final float SAMPLE_TO_FLOAT = 1.0f / 32768.0f;
private short[] mISamples;
private short[] mQSamples;
Expand Down
@@ -0,0 +1,134 @@
/*
* *****************************************************************************
* 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 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.source.tuner.sdrplay;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* Native buffer factory for SDRPlay RSP tuners.
*
* The SDRPlay API automatically changes the length of sample buffers according to the sample rate. This causes
* problems for down-stream processing components that may be optimized for vector operations and depend on the
* sample arrays being a power-of-2 length. This class repackages the incoming sample stream into arrays of
* power-of-2 length.
*/
public class RspNativeBufferFactory
{
private RspSampleRate mRspSampleRate;
private short[] mIResidual = new short[0];
private short[] mQResidual = new short[0];
private long mResidualTimestamp = System.currentTimeMillis();
private int mIncomingBufferLength = 0;
private int mOptimalBufferLength = 128;
private float mSamplesPerMillisecond;

/**
* Constructs an instance.
* @param sampleRate of the tuner
*/
public RspNativeBufferFactory(RspSampleRate sampleRate)
{
setSampleRate(sampleRate);
}

/**
* Set or update the sample rate.
* @param sampleRate to set.
*/
public void setSampleRate(RspSampleRate sampleRate)
{
mRspSampleRate = sampleRate;
mSamplesPerMillisecond = sampleRate.getSamplesPerMillisecond();
}

/**
* Repackages the samples into optimal length buffers and returns zero or more RSP native buffers.
* @param i samples
* @param q samples
* @param timestamp of samples
* @return zero or more repackaged RSP native buffers
*/
public List<RspNativeBuffer> get(short[] i, short[] q, long timestamp)
{
updateBufferLength(i.length);

short[] iCombined = new short[mIResidual.length + i.length];
System.arraycopy(mIResidual, 0, iCombined, 0, mIResidual.length);
System.arraycopy(i, 0, iCombined, mIResidual.length, i.length);

short[] qCombined = new short[mQResidual.length + q.length];
System.arraycopy(mQResidual, 0, qCombined, 0, mQResidual.length);
System.arraycopy(q, 0, qCombined, mQResidual.length, q.length);

if(iCombined.length < mOptimalBufferLength)
{
mIResidual = iCombined;
mQResidual = qCombined;
return Collections.emptyList();
}

List<RspNativeBuffer> buffers = new ArrayList<>();

while(iCombined.length >= mOptimalBufferLength)
{
short[] iOptimal = Arrays.copyOf(iCombined, mOptimalBufferLength);
iCombined = Arrays.copyOfRange(iCombined, mOptimalBufferLength, iCombined.length);

short[] qOptimal = Arrays.copyOf(qCombined, mOptimalBufferLength);
qCombined = Arrays.copyOfRange(qCombined, mOptimalBufferLength, qCombined.length);

RspNativeBuffer buffer = new RspNativeBuffer(iOptimal, qOptimal, mResidualTimestamp, mSamplesPerMillisecond);
buffers.add(buffer);
mResidualTimestamp += (long)(mOptimalBufferLength / mSamplesPerMillisecond);
}

mIResidual = iCombined;
mQResidual = qCombined;

//For simplicity, just update the residual timestamp to be the timestamp for this latest update
mResidualTimestamp = timestamp;

return buffers;
}

/**
* Updates the optimal native buffer length based on the incoming buffer size. Optimal length is a power-of-2
* value that is closest to the buffer length to minimize the quantity of residual samples from each arriving buffer.
* @param length
*/
private void updateBufferLength(int length)
{
if(mIncomingBufferLength != length)
{
int optimal = 128;

while((optimal * 2) < length)
{
optimal *= 2;
}

mOptimalBufferLength = optimal;
mIncomingBufferLength = length;
}
}
}
Expand Up @@ -156,6 +156,15 @@ public long getEffectiveSampleRate()
}
}

/**
* Number of samples per millisecond.
* @return samples per millisecond.
*/
public float getSamplesPerMillisecond()
{
return getEffectiveSampleRate() / 1000.0f;
}

/**
* Bandwidth entry
*/
Expand Down
Expand Up @@ -34,6 +34,7 @@
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.event.GainCallbackParameters;
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.event.PowerOverloadCallbackParameters;
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.event.RspDuoModeCallbackParameters;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -48,6 +49,7 @@ public abstract class RspTunerController<I extends IControlRsp> extends TunerCon
protected static final long MAXIMUM_FREQUENCY = 2_000_000_000;
protected static final int MIDDLE_UNUSABLE_BANDWIDTH = 0;
private I mControlRsp;
private RspNativeBufferFactory mNativeBufferFactory = new RspNativeBufferFactory(RspSampleRate.RATE_8_000);

/**
* Abstract tuner controller class. The tuner controller manages frequency bandwidth and currently tuned channels
Expand Down Expand Up @@ -116,9 +118,15 @@ public TunerSelect getTunerSelect()
public void processStream(short[] inphase, short[] quadrature,
StreamCallbackParameters parameters, boolean reset)
{
//RSP I/Q sample buffers are small and don't currently get fragmented by the RspNativeBuffer, so we don't
//calculate the sub-buffer samples per millisecond value -- just use a constant value of 0.
mNativeBufferBroadcaster.broadcast(new RspNativeBuffer(inphase, quadrature, System.currentTimeMillis(), 0.0f));
//The native buffer factory repackages the samples into buffers with the largest power-of-2 length that is
//smaller than the incoming buffer length and no smaller than 128 to ensure that down-stream vector optimized
//functions can process the data.
List<RspNativeBuffer> buffers = mNativeBufferFactory.get(inphase, quadrature, System.currentTimeMillis());

for(RspNativeBuffer buffer: buffers)
{
mNativeBufferBroadcaster.broadcast(buffer);
}

if(reset)
{
Expand Down Expand Up @@ -272,6 +280,7 @@ public void setSampleRate(RspSampleRate rspSampleRate) throws SDRPlayException

//Update the usable bandwidth based on the sample rate and filtered bandwidth
setUsableBandwidthPercentage(rspSampleRate.getUsableBandwidth());
mNativeBufferFactory.setSampleRate(rspSampleRate);
}

@Override
Expand Down

0 comments on commit e5f5df7

Please sign in to comment.