From e5f5df74ad182de998552ca7e5f0e5ef7b56290c Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sat, 24 Jun 2023 09:09:30 -0400 Subject: [PATCH] #1563 Updates RSP tuner to produce sample buffers with optimal power-of-2 length to enable downstream vectorized operations. (#1583) Co-authored-by: Dennis Sheirer --- .../HeterodyneChannelSourceManager.java | 9 +- .../source/tuner/sdrplay/RspNativeBuffer.java | 5 +- .../tuner/sdrplay/RspNativeBufferFactory.java | 134 ++++++++++++++++++ .../source/tuner/sdrplay/RspSampleRate.java | 9 ++ .../tuner/sdrplay/RspTunerController.java | 15 +- 5 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/source/tuner/sdrplay/RspNativeBufferFactory.java 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