Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1563 RSP Tuners Don't Work With Heterodyne Channelizer #1583

Merged
merged 1 commit into from
Jun 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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