Skip to content

Commit

Permalink
#1351 Restores semi-accurate timestamps of sample buffers to ensure P…
Browse files Browse the repository at this point in the history
…25 & DMR decoded messages have accurate timestamps and MBE recording voice frames have accurate timestamps. (#1357)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
  • Loading branch information
DSheirer and Dennis Sheirer committed Dec 17, 2022
1 parent 047dec6 commit bc462b9
Show file tree
Hide file tree
Showing 79 changed files with 607 additions and 733 deletions.
70 changes: 70 additions & 0 deletions src/main/java/io/github/dsheirer/buffer/AbstractNativeBuffer.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
23 changes: 10 additions & 13 deletions src/main/java/io/github/dsheirer/buffer/ByteNativeBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* 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;
Expand All @@ -46,24 +46,24 @@ 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)
{
throw new IllegalArgumentException("Samples byte[] length [" + samples.length + "] must be an even multiple of " + FRAGMENT_SIZE);
}

mSamples = samples;
mTimestamp = timestamp;
mAverageDc = averageDc;
}

Expand All @@ -73,12 +73,6 @@ public int sampleCount()
return mSamples.length / 2;
}

@Override
public long getTimestamp()
{
return mTimestamp;
}

@Override
public Iterator<ComplexSamples> iterator()
{
Expand All @@ -104,6 +98,8 @@ public boolean hasNext()
@Override
public ComplexSamples next()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);

float[] i = new float[FRAGMENT_SIZE];
float[] q = new float[FRAGMENT_SIZE];
int samplesOffset = mSamplesPointer;
Expand All @@ -115,8 +111,7 @@ public ComplexSamples next()
}

mSamplesPointer = samplesOffset;

return new ComplexSamples(i, q);
return new ComplexSamples(i, q, timestamp);
}
}

Expand All @@ -136,6 +131,8 @@ public boolean hasNext()
@Override
public InterleavedComplexSamples next()
{
long timestamp = getFragmentTimestamp(mSamplesPointer);

float[] converted = new float[FRAGMENT_SIZE * 2];

int samplesPointer = mSamplesPointer;
Expand All @@ -147,7 +144,7 @@ public InterleavedComplexSamples next()

mSamplesPointer = samplesPointer;

return new InterleavedComplexSamples(converted, mTimestamp);
return new InterleavedComplexSamples(converted, timestamp);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
/**
* Implements a factory for creating ByteNativeBuffer instances
*/
public class ByteNativeBufferFactory implements INativeBufferFactory
public class ByteNativeBufferFactory extends AbstractNativeBufferFactory
{
private DcCorrectionManager mDcCorrectionManager = new DcCorrectionManager();

Expand All @@ -39,7 +39,7 @@ public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
calculateDc(copy);
}

return new ByteNativeBuffer(copy, timestamp, mDcCorrectionManager.getAverageDc());
return new ByteNativeBuffer(copy, timestamp, mDcCorrectionManager.getAverageDc(), getSamplesPerMillisecond());
}

/**
Expand Down
35 changes: 9 additions & 26 deletions src/main/java/io/github/dsheirer/buffer/FloatNativeBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,45 +22,34 @@
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());
}

/**
* Constructs an instance
* @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);
}

/**
Expand All @@ -69,7 +58,7 @@ public FloatNativeBuffer(ComplexSamples samples, long timestamp)
*/
public FloatNativeBuffer(InterleavedComplexSamples samples)
{
this(samples.samples(), samples.timestamp());
this(samples.samples(), samples.timestamp(), 0.0f);
}

@Override
Expand All @@ -90,12 +79,6 @@ public int sampleCount()
return mInterleavedComplexSamples.length / 2;
}

@Override
public long getTimestamp()
{
return mTimestamp;
}

private class ComplexSamplesIterator implements Iterator<ComplexSamples>
{
private boolean mEmpty;
Expand All @@ -115,7 +98,7 @@ public ComplexSamples next()
}

mEmpty = true;
return SampleUtils.deinterleave(mInterleavedComplexSamples);
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
}
}

Expand All @@ -139,7 +122,7 @@ public InterleavedComplexSamples next()
}

mEmpty = true;
return new InterleavedComplexSamples(mInterleavedComplexSamples, mTimestamp);
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit bc462b9

Please sign in to comment.