#1351 Restores semi-accurate timestamps of sample buffers to ensure P25 & DMR decoded messages have accurate timestamps and MBE recording voice frames have accurate timestamps. (#1357)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-12-17 06:39:35 -05:00
committed by GitHub
parent 047dec6da1
commit bc462b9652
79 changed files with 607 additions and 733 deletions

View File

@@ -0,0 +1,70 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.buffer;
/**
* Base native buffer class.
*/
public abstract class AbstractNativeBuffer implements INativeBuffer
{
private long mTimestamp;
private float mSamplesPerMillisecond;
/**
* Constructs an instance
* @param timestamp for the start of this buffer
* @param samplesPerMillisecond to calculate the time offset of sub-buffers extracted from this native buffer.
*/
public AbstractNativeBuffer(long timestamp, float samplesPerMillisecond)
{
mTimestamp = timestamp;
mSamplesPerMillisecond = samplesPerMillisecond;
}
/**
* Timestamp for the start of this buffer
* @return timestamp in milliseconds
*/
@Override
public long getTimestamp()
{
return mTimestamp;
}
/**
* Quantity of samples representing one millisecond of sample data, used for calculating fragment timestamp offsets.
* @return samples per millisecond count.
*/
public float getSamplesPerMillisecond()
{
return mSamplesPerMillisecond;
}
/**
* Calculates the timestamp for a samples buffer fragment based on where it is extracted from relative to the
* native buffer starting timestamp.
* @param samplesPointer for the start of the fragment. Note: this value will be divided by 2 to account for I/Q sample pairs.
* @return timestamp adjusted to the fragment or sub-buffer start sample.
*/
protected long getFragmentTimestamp(int samplesPointer)
{
return getTimestamp() + (long)(samplesPointer / 2 / getSamplesPerMillisecond());
}
}

View File

@@ -0,0 +1,44 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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.buffer;
/**
* Base class for native buffer factories.
*/
public abstract class AbstractNativeBufferFactory implements INativeBufferFactory
{
private float mSamplesPerMillisecond = 0.0f;
@Override
public void setSamplesPerMillisecond(float samplesPerMillisecond)
{
mSamplesPerMillisecond = samplesPerMillisecond;
}
/**
* Quantity of I/Q sample pairs per milli-second at the current sample rate to use in calculating an accurate
* timestamp for sub-buffer that are generated from the native buffer.
* @return samples per millisecond.
*/
public float getSamplesPerMillisecond()
{
return mSamplesPerMillisecond;
}
}

View File

@@ -27,7 +27,7 @@ import java.util.Iterator;
* Native buffer sample array wrapper class that provides access to a stream of either interleaved or
* non-interleaved complex sample buffers converted from the raw byte sample array.
*/
public class ByteNativeBuffer implements INativeBuffer
public class ByteNativeBuffer extends AbstractNativeBuffer
{
private static final int FRAGMENT_SIZE = 2048;
private final static float[] LOOKUP_VALUES;
@@ -46,16 +46,17 @@ public class ByteNativeBuffer implements INativeBuffer
}
private byte[] mSamples;
private long mTimestamp;
/**
* Constructs an instance
* @param samples to process
* @param timestamp of the samples
* @param averageDc measured from sample stream
* @param samplesPerMillisecond to calculate derivative timestamps for sub-buffers.
*/
public ByteNativeBuffer(byte[] samples, long timestamp, float averageDc)
public ByteNativeBuffer(byte[] samples, long timestamp, float averageDc, float samplesPerMillisecond)
{
super(timestamp, samplesPerMillisecond);
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
if(samples.length % FRAGMENT_SIZE != 0)
{
@@ -63,7 +64,6 @@ public class ByteNativeBuffer implements INativeBuffer
}
mSamples = samples;
mTimestamp = timestamp;
mAverageDc = averageDc;
}
@@ -73,12 +73,6 @@ public class ByteNativeBuffer implements INativeBuffer
return mSamples.length / 2;
}
@Override
public long getTimestamp()
{
return mTimestamp;
}
@Override
public Iterator<ComplexSamples> iterator()
{
@@ -104,6 +98,8 @@ public class ByteNativeBuffer implements INativeBuffer
@Override
public ComplexSamples next()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);
float[] i = new float[FRAGMENT_SIZE];
float[] q = new float[FRAGMENT_SIZE];
int samplesOffset = mSamplesPointer;
@@ -115,8 +111,7 @@ public class ByteNativeBuffer implements INativeBuffer
}
mSamplesPointer = samplesOffset;
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}
@@ -136,6 +131,8 @@ public class ByteNativeBuffer implements INativeBuffer
@Override
public InterleavedComplexSamples next()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);
float[] converted = new float[FRAGMENT_SIZE * 2];
int samplesPointer = mSamplesPointer;
@@ -147,7 +144,7 @@ public class ByteNativeBuffer implements INativeBuffer
mSamplesPointer = samplesPointer;
return new InterleavedComplexSamples(converted, mTimestamp);
return new InterleavedComplexSamples(converted, timestamp);
}
}
}

View File

@@ -24,7 +24,7 @@ import java.nio.ByteBuffer;
/**
* Implements a factory for creating ByteNativeBuffer instances
*/
public class ByteNativeBufferFactory implements INativeBufferFactory
public class ByteNativeBufferFactory extends AbstractNativeBufferFactory
{
private DcCorrectionManager mDcCorrectionManager = new DcCorrectionManager();
@@ -39,7 +39,7 @@ public class ByteNativeBufferFactory implements INativeBufferFactory
calculateDc(copy);
}
return new ByteNativeBuffer(copy, timestamp, mDcCorrectionManager.getAverageDc());
return new ByteNativeBuffer(copy, timestamp, mDcCorrectionManager.getAverageDc(), getSamplesPerMillisecond());
}
/**

View File

@@ -22,35 +22,24 @@ package io.github.dsheirer.buffer;
import io.github.dsheirer.sample.SampleUtils;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import java.util.Iterator;
/**
* Simple native buffer implementation that simply wraps a single, existing, non-native sample buffer.
*/
public class FloatNativeBuffer implements INativeBuffer
public class FloatNativeBuffer extends AbstractNativeBuffer
{
private float[] mInterleavedComplexSamples;
private long mTimestamp;
/**
* Constructs an instance
* @param complexSamples to wrap
* @param complexSamples interleaved to wrap
* @param timestamp for the buffer
*/
public FloatNativeBuffer(float[] complexSamples, long timestamp)
public FloatNativeBuffer(float[] complexSamples, long timestamp, float samplesPerMillisecond)
{
super(timestamp, samplesPerMillisecond);
mInterleavedComplexSamples = complexSamples;
mTimestamp = timestamp;
}
/**
* Constructs an instance
* @param complexSamples to wrap
*/
public FloatNativeBuffer(float[] complexSamples)
{
this(complexSamples, System.currentTimeMillis());
}
/**
@@ -58,9 +47,9 @@ public class FloatNativeBuffer implements INativeBuffer
* @param samples to wrap
* @param timestamp for the buffer
*/
public FloatNativeBuffer(ComplexSamples samples, long timestamp)
public FloatNativeBuffer(ComplexSamples samples)
{
this(SampleUtils.interleave(samples), timestamp);
this(SampleUtils.interleave(samples), samples.timestamp(), 0.0f);
}
/**
@@ -69,7 +58,7 @@ public class FloatNativeBuffer implements INativeBuffer
*/
public FloatNativeBuffer(InterleavedComplexSamples samples)
{
this(samples.samples(), samples.timestamp());
this(samples.samples(), samples.timestamp(), 0.0f);
}
@Override
@@ -90,12 +79,6 @@ public class FloatNativeBuffer implements INativeBuffer
return mInterleavedComplexSamples.length / 2;
}
@Override
public long getTimestamp()
{
return mTimestamp;
}
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
{
private boolean mEmpty;
@@ -115,7 +98,7 @@ public class FloatNativeBuffer implements INativeBuffer
}
mEmpty = true;
return SampleUtils.deinterleave(mInterleavedComplexSamples);
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
}
}
@@ -139,7 +122,7 @@ public class FloatNativeBuffer implements INativeBuffer
}
mEmpty = true;
return new InterleavedComplexSamples(mInterleavedComplexSamples, mTimestamp);
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
}
}
}

View File

@@ -33,7 +33,15 @@ public interface INativeBufferFactory
*
* @param samples byte array copied from native memory
* @param timestamp of the samples
* @param samplesPerMillisecond to calculate timestamp offset for child buffers.
* @return instance
*/
INativeBuffer getBuffer(ByteBuffer samples, long timestamp);
/**
* Sets the samples per millisecond rate based on the current sample rate.
*
* @param samplesPerMillisecond to calculate timestamp offset for child buffers.
*/
void setSamplesPerMillisecond(float samplesPerMillisecond);
}

View File

@@ -21,14 +21,13 @@ package io.github.dsheirer.buffer;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import java.util.Iterator;
/**
* Native buffer sample array wrapper class that provides access to a stream of either interleaved or
* non-interleaved complex sample buffers converted from the raw byte sample array.
*/
public class SignedByteNativeBuffer implements INativeBuffer
public class SignedByteNativeBuffer extends AbstractNativeBuffer
{
private static final int FRAGMENT_SIZE = 2048;
private final static float[] LOOKUP_VALUES;
@@ -47,7 +46,6 @@ public class SignedByteNativeBuffer implements INativeBuffer
}
private byte[] mSamples;
private long mTimestamp;
private float mIAverageDc;
private float mQAverageDc;
@@ -57,9 +55,12 @@ public class SignedByteNativeBuffer implements INativeBuffer
* @param timestamp of the samples
* @param iAverageDc of the sample stream
* @param qAverageDc of the sample stream
* @param samplesPerMillisecond to calculate sub-buffer timestamps
*/
public SignedByteNativeBuffer(byte[] samples, long timestamp, float iAverageDc, float qAverageDc)
public SignedByteNativeBuffer(byte[] samples, long timestamp, float iAverageDc, float qAverageDc, float samplesPerMillisecond)
{
super(timestamp, samplesPerMillisecond);
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
if(samples.length % FRAGMENT_SIZE != 0)
{
@@ -67,17 +68,10 @@ public class SignedByteNativeBuffer implements INativeBuffer
}
mSamples = samples;
mTimestamp = timestamp;
mIAverageDc = iAverageDc;
mQAverageDc = qAverageDc;
}
@Override
public long getTimestamp()
{
return mTimestamp;
}
@Override
public int sampleCount()
{
@@ -112,6 +106,8 @@ public class SignedByteNativeBuffer implements INativeBuffer
@Override
public ComplexSamples next()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);
float[] i = new float[FRAGMENT_SIZE];
float[] q = new float[FRAGMENT_SIZE];
int samplesOffset = mSamplesPointer;
@@ -123,7 +119,7 @@ public class SignedByteNativeBuffer implements INativeBuffer
}
mSamplesPointer = samplesOffset;
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}
@@ -143,6 +139,8 @@ public class SignedByteNativeBuffer implements INativeBuffer
@Override
public InterleavedComplexSamples next()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);
float[] converted = new float[FRAGMENT_SIZE * 2];
int samplesOffset = mSamplesPointer;
@@ -155,7 +153,7 @@ public class SignedByteNativeBuffer implements INativeBuffer
mSamplesPointer = samplesOffset;
return new InterleavedComplexSamples(converted, mTimestamp);
return new InterleavedComplexSamples(converted, timestamp);
}
}
}

View File

@@ -25,7 +25,7 @@ import java.util.concurrent.TimeUnit;
/**
* Implements a factory for creating SignedByteNativeBuffer instances
*/
public class SignedByteNativeBufferFactory implements INativeBufferFactory
public class SignedByteNativeBufferFactory extends AbstractNativeBufferFactory
{
/**
* DC removal calculations will run once a minute
@@ -78,7 +78,7 @@ public class SignedByteNativeBufferFactory implements INativeBufferFactory
calculateDc(copy);
}
return new SignedByteNativeBuffer(copy, timestamp, mIAverageDc, mQAverageDc);
return new SignedByteNativeBuffer(copy, timestamp, mIAverageDc, mQAverageDc, getSamplesPerMillisecond());
}
/**

View File

@@ -20,7 +20,6 @@
package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
import java.util.Iterator;
/**
@@ -42,8 +41,9 @@ public abstract class AirspyBufferIterator<T> implements Iterator<T>
protected float[] mQBuffer = new float[FRAGMENT_SIZE + Q_OVERLAP];
protected short[] mSamples;
protected int mSamplesPointer = 0;
protected long mTimestamp;
protected float mAverageDc;
private long mTimestamp;
private float mSamplesPerMillisecond;
/**
* Constructs an instance
@@ -52,8 +52,10 @@ public abstract class AirspyBufferIterator<T> implements Iterator<T>
* @param residualQ samples from previous buffer
* @param averageDc as measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
public AirspyBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp,
float samplesPerMillisecond)
{
if(residualI.length != I_OVERLAP || residualQ.length != Q_OVERLAP)
{
@@ -73,6 +75,7 @@ public abstract class AirspyBufferIterator<T> implements Iterator<T>
mAverageDc = averageDc;
mSamples = samples;
mTimestamp = timestamp;
mSamplesPerMillisecond = samplesPerMillisecond;
//Transfer and scale the residual I & Q samples from the previous buffer
for(int i = 0; i < residualI.length; i++)
@@ -86,6 +89,17 @@ public abstract class AirspyBufferIterator<T> implements Iterator<T>
}
}
/**
* Calculates the timestamp for a samples buffer fragment based on where it is extracted from relative to the
* native buffer starting timestamp.
* @param samplesPointer for the start of the fragment. Note: this value will be divided by 2 to account for I/Q sample pairs.
* @return timestamp adjusted to the fragment or sub-buffer start sample.
*/
protected long getFragmentTimestamp(int samplesPointer)
{
return mTimestamp + (long)(samplesPointer / 2 / mSamplesPerMillisecond);
}
@Override
public boolean hasNext()
{

View File

@@ -20,7 +20,6 @@
package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
/**
@@ -36,10 +35,11 @@ public class AirspyBufferIteratorScalar extends AirspyBufferIterator<ComplexSamp
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
public AirspyBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
@@ -50,6 +50,8 @@ public class AirspyBufferIteratorScalar extends AirspyBufferIterator<ComplexSamp
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
for(int x = 0; x < FRAGMENT_SIZE; x++)
@@ -91,6 +93,6 @@ public class AirspyBufferIteratorScalar extends AirspyBufferIterator<ComplexSamp
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -20,12 +20,11 @@
package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
/**
* Vector SIMD implementation for non-packed Airspy native buffers
*/
@@ -41,10 +40,12 @@ public class AirspyBufferIteratorVector128Bits extends AirspyBufferIterator<Comp
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
public AirspyBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
@@ -55,6 +56,7 @@ public class AirspyBufferIteratorVector128Bits extends AirspyBufferIterator<Comp
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
@@ -121,6 +123,6 @@ public class AirspyBufferIteratorVector128Bits extends AirspyBufferIterator<Comp
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -20,12 +20,11 @@
package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
/**
* Vector SIMD implementation for non-packed Airspy native buffers
*/
@@ -41,10 +40,12 @@ public class AirspyBufferIteratorVector256Bits extends AirspyBufferIterator<Comp
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
public AirspyBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
@@ -55,6 +56,7 @@ public class AirspyBufferIteratorVector256Bits extends AirspyBufferIterator<Comp
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
@@ -115,6 +117,6 @@ public class AirspyBufferIteratorVector256Bits extends AirspyBufferIterator<Comp
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -20,12 +20,11 @@
package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
/**
* Vector SIMD implementation for non-packed Airspy native buffers
*/
@@ -42,10 +41,12 @@ public class AirspyBufferIteratorVector512Bits extends AirspyBufferIterator<Comp
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
public AirspyBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
mFilterPart2 = new float[16];
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
@@ -59,6 +60,8 @@ public class AirspyBufferIteratorVector512Bits extends AirspyBufferIterator<Comp
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
@@ -117,6 +120,6 @@ public class AirspyBufferIteratorVector512Bits extends AirspyBufferIterator<Comp
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -20,12 +20,11 @@
package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
/**
* Vector SIMD implementation for non-packed Airspy native buffers
*/
@@ -41,10 +40,12 @@ public class AirspyBufferIteratorVector64Bits extends AirspyBufferIterator<Compl
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp)
public AirspyBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
@@ -55,6 +56,8 @@ public class AirspyBufferIteratorVector64Bits extends AirspyBufferIterator<Compl
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
@@ -133,6 +136,6 @@ public class AirspyBufferIteratorVector64Bits extends AirspyBufferIterator<Compl
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

View File

@@ -34,11 +34,12 @@ public class AirspyInterleavedBufferIteratorScalar extends AirspyBufferIterator<
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp)
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
@@ -49,6 +50,7 @@ public class AirspyInterleavedBufferIteratorScalar extends AirspyBufferIterator<
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
for(int x = 0; x < FRAGMENT_SIZE; x++)
@@ -89,6 +91,6 @@ public class AirspyInterleavedBufferIteratorScalar extends AirspyBufferIterator<
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, mTimestamp);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -39,11 +39,12 @@ public class AirspyInterleavedBufferIteratorVector128Bits extends AirspyBufferIt
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp)
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
@@ -54,6 +55,7 @@ public class AirspyInterleavedBufferIteratorVector128Bits extends AirspyBufferIt
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
@@ -118,6 +120,6 @@ public class AirspyInterleavedBufferIteratorVector128Bits extends AirspyBufferIt
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, mTimestamp);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -39,11 +39,12 @@ public class AirspyInterleavedBufferIteratorVector256Bits extends AirspyBufferIt
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp)
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
@@ -54,6 +55,7 @@ public class AirspyInterleavedBufferIteratorVector256Bits extends AirspyBufferIt
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
@@ -112,6 +114,6 @@ public class AirspyInterleavedBufferIteratorVector256Bits extends AirspyBufferIt
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, mTimestamp);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -40,11 +40,12 @@ public class AirspyInterleavedBufferIteratorVector512Bits extends AirspyBufferIt
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp)
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
mFilterPart2 = new float[16];
@@ -59,6 +60,7 @@ public class AirspyInterleavedBufferIteratorVector512Bits extends AirspyBufferIt
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
@@ -115,6 +117,6 @@ public class AirspyInterleavedBufferIteratorVector512Bits extends AirspyBufferIt
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, mTimestamp);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -39,11 +39,12 @@ public class AirspyInterleavedBufferIteratorVector64Bits extends AirspyBufferIte
* @param residualQ samples from last buffer
* @param averageDc measured
* @param timestamp of the buffer
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
*/
public AirspyInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp)
long timestamp, float samplesPerMillisecond)
{
super(samples, residualI, residualQ, averageDc, timestamp);
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
}
@Override
@@ -54,6 +55,7 @@ public class AirspyInterleavedBufferIteratorVector64Bits extends AirspyBufferIte
throw new IllegalStateException("End of buffer exceeded");
}
long timestamp = getFragmentTimestamp(mSamplesPointer);
int offset = mSamplesPointer;
int fragmentPointer = 0;
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
@@ -130,6 +132,6 @@ public class AirspyInterleavedBufferIteratorVector64Bits extends AirspyBufferIte
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
return new InterleavedComplexSamples(samples, mTimestamp);
return new InterleavedComplexSamples(samples, timestamp);
}
}

View File

@@ -19,23 +19,21 @@
package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.buffer.AbstractNativeBuffer;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import io.github.dsheirer.vector.calibrate.Implementation;
import java.util.Iterator;
/**
* Native buffer scalar implementation for Airspy non-packed samples.
*/
public class AirspyNativeBuffer implements INativeBuffer
public class AirspyNativeBuffer extends AbstractNativeBuffer
{
private short[] mSamples;
private short[] mResidualI;
private short[] mResidualQ;
private float mAverageDc;
private long mTimestamp;
private Implementation mInterleavedImplementation;
private Implementation mNonInterleavedImplementation;
@@ -48,11 +46,14 @@ public class AirspyNativeBuffer implements INativeBuffer
* @param timestamp of the buffer
* @param interleavedImplementation optimal, scalar vs vector SIMD
* @param nonInterleavedImplementation optimal, scalar vs vector SIMD
* @param samplesPerMillisecond used to calculate sub-buffer fragment timestamp offsets from the start of this buffer.
*/
public AirspyNativeBuffer(short[] samples, short[] residualI, short[] residualQ, float averageDc,
long timestamp, Implementation interleavedImplementation,
Implementation nonInterleavedImplementation)
Implementation nonInterleavedImplementation, float samplesPerMillisecond)
{
super(timestamp, samplesPerMillisecond);
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
if(samples.length % AirspyBufferIterator.FRAGMENT_SIZE != 0)
{
@@ -64,7 +65,6 @@ public class AirspyNativeBuffer implements INativeBuffer
mResidualI = residualI;
mResidualQ = residualQ;
mAverageDc = averageDc;
mTimestamp = timestamp;
mInterleavedImplementation = interleavedImplementation;
mNonInterleavedImplementation = nonInterleavedImplementation;
}
@@ -75,22 +75,16 @@ public class AirspyNativeBuffer implements INativeBuffer
return mSamples.length / 2;
}
@Override
public long getTimestamp()
{
return mTimestamp;
}
@Override
public Iterator<ComplexSamples> iterator()
{
return switch(mInterleavedImplementation)
{
case VECTOR_SIMD_512 -> new AirspyBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
case VECTOR_SIMD_256-> new AirspyBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
case VECTOR_SIMD_128-> new AirspyBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
case VECTOR_SIMD_64 ->new AirspyBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
default -> new AirspyBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
case VECTOR_SIMD_512 -> new AirspyBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_256-> new AirspyBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_128-> new AirspyBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_64 ->new AirspyBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
default -> new AirspyBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
};
}
@@ -99,11 +93,11 @@ public class AirspyNativeBuffer implements INativeBuffer
{
return switch(mInterleavedImplementation)
{
case VECTOR_SIMD_512 -> new AirspyInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
case VECTOR_SIMD_256-> new AirspyInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
case VECTOR_SIMD_128-> new AirspyInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
case VECTOR_SIMD_64 ->new AirspyInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
default -> new AirspyInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, mTimestamp);
case VECTOR_SIMD_512 -> new AirspyInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_256-> new AirspyInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_128-> new AirspyInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
case VECTOR_SIMD_64 ->new AirspyInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
default -> new AirspyInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
};
}
}

View File

@@ -19,19 +19,18 @@
package io.github.dsheirer.buffer.airspy;
import io.github.dsheirer.buffer.AbstractNativeBufferFactory;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.buffer.INativeBufferFactory;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import io.github.dsheirer.vector.calibrate.CalibrationType;
import io.github.dsheirer.vector.calibrate.Implementation;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Implements a factory for creating SignedByteNativeBuffer instances
*/
public class AirspyNativeBufferFactory implements INativeBufferFactory
public class AirspyNativeBufferFactory extends AbstractNativeBufferFactory
{
private boolean mSamplePacking = false;
private short[] mResidualI = new short[AirspyBufferIterator.I_OVERLAP];
@@ -106,7 +105,7 @@ public class AirspyNativeBufferFactory implements INativeBufferFactory
INativeBuffer nativeBuffer = new AirspyNativeBuffer(samples,
Arrays.copyOf(mResidualI, mResidualI.length),
Arrays.copyOf(mResidualQ, mResidualQ.length), mConverter.getAverageDc(), timestamp,
mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation);
mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation, getSamplesPerMillisecond());
extractResidual(samples);