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 1f98427da..b4e856faf 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
@@ -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);
}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspNativeBuffer.java b/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspNativeBuffer.java
index e8cb792b8..749d45b50 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspNativeBuffer.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspNativeBuffer.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
@@ -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.
@@ -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;
diff --git a/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspNativeBufferFactory.java b/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspNativeBufferFactory.java
new file mode 100644
index 000000000..277cc5bd7
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspNativeBufferFactory.java
@@ -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
+ * ****************************************************************************
+ */
+
+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 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 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;
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspSampleRate.java b/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspSampleRate.java
index deeae0b27..d2750102f 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspSampleRate.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspSampleRate.java
@@ -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
*/
diff --git a/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspTunerController.java
index 152f13998..581d46648 100644
--- a/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspTunerController.java
+++ b/src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspTunerController.java
@@ -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;
@@ -48,6 +49,7 @@ public abstract class RspTunerController 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
@@ -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 buffers = mNativeBufferFactory.get(inphase, quadrature, System.currentTimeMillis());
+
+ for(RspNativeBuffer buffer: buffers)
+ {
+ mNativeBufferBroadcaster.broadcast(buffer);
+ }
if(reset)
{
@@ -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