mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#39 WIP - adds support for timestamped complex buffers so that decoded
messages and events can be accurately timestamped, and more importantly, allocated traffic channels can request to be pre-loaded with buffered samples to ensure that traffic channels don't miss any samples that are currently missed due processing latency inherent in current buffer sizing. This should reduce the clipping of starting messages or voice on the traffic channels. Restructures processing path for tuner frequency and sample rate change events. The tuner now embeds these events in the sequence of buffers that are generated immediately after the event occurs since those are the buffers that will be impacted by the change. Previously, the change event would be broadcast throughout the system and would impact any buffered/cached buffers. This also reduces the number of source event change listeners that have to be managed. Note: this change is not yet implemented in all tuners ... only in the TestTuner implementation. Updates the polyphase channelizer to now buffer channel results and then dispatch the buffer to the channel output processors to improve efficiency of using float arrays versus single value complex float processing.
This commit is contained in:
@@ -1,26 +1,61 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.sample.complex;
|
||||
|
||||
import io.github.dsheirer.sample.Buffer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
public class ComplexBuffer extends Buffer
|
||||
{
|
||||
/**
|
||||
* Wrapper around float array containing interleaved I/Q samples
|
||||
*/
|
||||
public ComplexBuffer( float[] samples )
|
||||
{
|
||||
super( samples );
|
||||
}
|
||||
//Default timestamp - we use a static variable here so that there is only ever 1 instance in memory
|
||||
private static final long DEFAULT_TIMESTAMP = 0;
|
||||
|
||||
/**
|
||||
* Creates a deep copy of the buffer
|
||||
*/
|
||||
public ComplexBuffer copyOf()
|
||||
{
|
||||
float[] copy = Arrays.copyOf( mSamples, mSamples.length );
|
||||
|
||||
return new ComplexBuffer( copy );
|
||||
}
|
||||
/**
|
||||
* Wrapper around float array containing interleaved I/Q samples and an optional source event that should be
|
||||
* processed prior to processing the sample data.
|
||||
*/
|
||||
public ComplexBuffer(float[] samples, SourceEvent sourceEvent)
|
||||
{
|
||||
super(samples, sourceEvent);
|
||||
}
|
||||
/**
|
||||
* Wrapper around float array containing interleaved I/Q samples
|
||||
*/
|
||||
public ComplexBuffer(float[] samples)
|
||||
{
|
||||
super(samples);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reference timestamp for the first sample in this buffer. This functionality is intended to be implemented by a
|
||||
* subclass implementation. The default value is 0.
|
||||
*
|
||||
* @return reference timestamp or a value of 0 if hasTimestamp() indicates false.
|
||||
*/
|
||||
public long getTimestamp()
|
||||
{
|
||||
return DEFAULT_TIMESTAMP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this buffer has a timestamp that references the first sample in the buffer.
|
||||
*/
|
||||
public boolean hasTimestamp()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.sample.complex;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.channelizer.SampleTimestampManager;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
/**
|
||||
* Assembles complex float samples into a ComplexBuffer containing an array of floats. Monitors incoming sample count
|
||||
* to accurately assign a relative timestamp to each assembled buffer.
|
||||
*/
|
||||
public class TimestampedBufferAssembler
|
||||
{
|
||||
private SampleTimestampManager mTimestampManager;
|
||||
private FloatBuffer mBuffer;
|
||||
private long mCurrentBufferTimestamp;
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
|
||||
public TimestampedBufferAssembler(int bufferSize, double sampleRate)
|
||||
{
|
||||
Validate.isTrue(bufferSize % 2 == 0);
|
||||
mBuffer = FloatBuffer.allocate(bufferSize);
|
||||
mTimestampManager = new SampleTimestampManager(sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the reference timestamp to use for all subsequent samples that are assembled. This timestamp should
|
||||
* identify the relative timestamp of the first/next sample that is received by this assembler.
|
||||
* @param timestamp to use relative to the incoming sample arrays.
|
||||
*/
|
||||
public void updateTimestamp(long timestamp)
|
||||
{
|
||||
mTimestampManager.setReferenceTimestamp(timestamp);
|
||||
|
||||
if(mBuffer.position() == 0)
|
||||
{
|
||||
mCurrentBufferTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
mBuffer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the samples to this assembler. As each buffer is assembled, it will be dispatched to the registered
|
||||
* listener.
|
||||
*
|
||||
* @param samples to assemble
|
||||
*/
|
||||
public void receive(float[] samples)
|
||||
{
|
||||
if(mBuffer.remaining() >= samples.length)
|
||||
{
|
||||
mBuffer.put(samples);
|
||||
mTimestampManager.increment(samples.length / 2);
|
||||
|
||||
if(!mBuffer.hasRemaining())
|
||||
{
|
||||
flush();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
while(offset < samples.length)
|
||||
{
|
||||
int toCopy = mBuffer.remaining();
|
||||
|
||||
if((samples.length - offset) < toCopy)
|
||||
{
|
||||
toCopy = samples.length - offset;
|
||||
}
|
||||
|
||||
mBuffer.put(samples, offset, toCopy);
|
||||
|
||||
offset += toCopy;
|
||||
mTimestampManager.increment(toCopy / 2);
|
||||
|
||||
if(!mBuffer.hasRemaining())
|
||||
{
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the current buffer contents to the registered listener
|
||||
*/
|
||||
public void flush()
|
||||
{
|
||||
if(mListener != null && mBuffer.position() > 0)
|
||||
{
|
||||
float[] samples = new float[mBuffer.position()];
|
||||
mBuffer.rewind();
|
||||
mBuffer.get(samples);
|
||||
|
||||
mListener.receive(new TimestampedComplexBuffer(samples, mCurrentBufferTimestamp));
|
||||
}
|
||||
|
||||
mBuffer.rewind();
|
||||
mCurrentBufferTimestamp = mTimestampManager.getCurrentTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive the output from this buffer assembler.
|
||||
*/
|
||||
public void setListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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.sample.complex;
|
||||
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
public class TimestampedComplexBuffer extends ComplexBuffer
|
||||
{
|
||||
private long mTimestamp;
|
||||
|
||||
/**
|
||||
* Constructs a timestamped complex buffer using the specified time in milliseconds. Also includes a source event
|
||||
* that should be processed prior to processing the sample data.
|
||||
*
|
||||
* @param samples of data
|
||||
* @param sourceEvent - optional source event to embed with this buffer
|
||||
* @param timestamp relative to the first sample in the sample array
|
||||
*/
|
||||
public TimestampedComplexBuffer(float[] samples, SourceEvent sourceEvent, long timestamp)
|
||||
{
|
||||
super(samples, sourceEvent);
|
||||
mTimestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a timestamped complex buffer using the specified time in milliseconds.
|
||||
*
|
||||
* @param samples of data
|
||||
*/
|
||||
public TimestampedComplexBuffer(float[] samples, long timestamp)
|
||||
{
|
||||
this(samples, null, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a timestamped complex buffer using the current system time in milliseconds. Also includes a source
|
||||
* event that should be processed prior to processing the sample data.
|
||||
*
|
||||
* @param samples of data
|
||||
*/
|
||||
public TimestampedComplexBuffer(float[] samples, SourceEvent sourceEvent)
|
||||
{
|
||||
this(samples, sourceEvent, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a timestamped complex buffer using the current system time in milliseconds.
|
||||
*
|
||||
* @param samples of data
|
||||
*/
|
||||
public TimestampedComplexBuffer(float[] samples)
|
||||
{
|
||||
this(samples, null, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference timestamp for this complex buffer. Timestamp is relative to the first sample in the buffer.
|
||||
* @return timestamp as milliseconds since epoch
|
||||
*/
|
||||
@Override
|
||||
public long getTimestamp()
|
||||
{
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this complex buffer has a reference timestamp
|
||||
* @return true
|
||||
*/
|
||||
@Override
|
||||
public boolean hasTimestamp()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user