mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Implements a complex sample delay buffer integrated into the tuner
channel manager so that channel source requests can have a start timestamp specified and the channel can be preloaded with samples from the delay buffer. If no timestamp is specified, current time is assumed. This feature requires corresponding updates to any decoder that wishes to implement the timestamped sample preloading functionality.
This commit is contained in:
@@ -56,20 +56,28 @@ public class SourceEvent
|
||||
/**
|
||||
* Private constructor. Use the static constructor methods to create an event.
|
||||
*/
|
||||
private SourceEvent(Event event, Number value, String eventDescription)
|
||||
private SourceEvent(Event event, Source source, Number value, String eventDescription)
|
||||
{
|
||||
mEvent = event;
|
||||
mSource = source;
|
||||
mValue = value;
|
||||
mEventDescription = eventDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor. Use the static constructor methods to create an event.
|
||||
*/
|
||||
private SourceEvent(Event event, Number value, String eventDescription)
|
||||
{
|
||||
this(event, null, value, eventDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor. Use the static constructor methods to create an event.
|
||||
*/
|
||||
private SourceEvent(Event event, Number value)
|
||||
{
|
||||
mEvent = event;
|
||||
mValue = value;
|
||||
this(event, value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,8 +85,12 @@ public class SourceEvent
|
||||
*/
|
||||
private SourceEvent(Event event, Source source)
|
||||
{
|
||||
mEvent = event;
|
||||
mSource = source;
|
||||
this(event, source, 0);
|
||||
}
|
||||
|
||||
private SourceEvent(Event event, Source source, Number value)
|
||||
{
|
||||
this(event, source, value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,11 +295,22 @@ public class SourceEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new start sample stream request event
|
||||
* Creates a new start sample stream request event. This method uses the current system time in
|
||||
* milliseconds as the requested sample start time.
|
||||
*/
|
||||
public static SourceEvent startSampleStreamRequest(Source source)
|
||||
{
|
||||
return new SourceEvent(Event.REQUEST_START_SAMPLE_STREAM, source);
|
||||
return startSampleStreamRequest(source, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new start sample stream request event and requests that samples (when available) for the
|
||||
* specified timestamp be pre-loaded into the source. The timestamp only applies for sources that incorporate
|
||||
* a sample delay buffer.
|
||||
*/
|
||||
public static SourceEvent startSampleStreamRequest(Source source, long timestamp)
|
||||
{
|
||||
return new SourceEvent(Event.REQUEST_START_SAMPLE_STREAM, source, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,7 +322,7 @@ public class SourceEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new stop sample stream request event
|
||||
* Creates a new stop sample stream notification event
|
||||
*/
|
||||
public static SourceEvent stopSampleStreamNotification(Source source)
|
||||
{
|
||||
|
||||
@@ -47,6 +47,14 @@ public class ComplexMixer
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio format used by the underlying mixer reader
|
||||
*/
|
||||
public AudioFormat getAudioFormat()
|
||||
{
|
||||
return mMixerReader.getAudioFormat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the buffer size in bytes per buffer for each read interval.
|
||||
* @param bufferSize in bytes
|
||||
|
||||
@@ -72,6 +72,14 @@ public class MixerReader<T extends ReusableFloatBuffer> implements Runnable
|
||||
this(audioFormat, targetDataLine, abstractSampleAdapter, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio format used by this reader
|
||||
*/
|
||||
public AudioFormat getAudioFormat()
|
||||
{
|
||||
return mAudioFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of buffers to use. This reader will read from target data line 20 times per second.
|
||||
* @param bytesPerBuffer to size for each buffer read.
|
||||
|
||||
@@ -67,6 +67,21 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
*/
|
||||
public abstract void dispose();
|
||||
|
||||
/**
|
||||
* Number of samples contained in each complex buffer provided by this tuner.
|
||||
*
|
||||
* @return number of complex sample in each buffer.
|
||||
*/
|
||||
public abstract int getBufferSampleCount();
|
||||
|
||||
/**
|
||||
* Duration in milliseconds for each sample buffer provided by this tuner
|
||||
*/
|
||||
public long getBufferDuration()
|
||||
{
|
||||
return (long)(1000.0 / (getSampleRate() / (double)getBufferSampleCount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the ISourceEventListener interface to receive requests from sample consumers
|
||||
*/
|
||||
|
||||
@@ -107,6 +107,13 @@ public class AirspyTunerController extends USBTunerController
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSampleCount()
|
||||
{
|
||||
//Each airspy complex sample is 4 bytes, so sample count is buffer size / 4
|
||||
return USB_TRANSFER_BUFFER_SIZE / 4;
|
||||
}
|
||||
|
||||
public void init() throws SourceException
|
||||
{
|
||||
mDeviceHandle = new DeviceHandle();
|
||||
|
||||
@@ -54,7 +54,6 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene
|
||||
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource");
|
||||
private IOscillator mFrequencyCorrectionMixer;
|
||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||
private Listener<ReusableComplexBuffer> mListener;
|
||||
private List<ReusableComplexBuffer> mSampleBuffers = new ArrayList<>();
|
||||
private double mChannelSampleRate;
|
||||
private long mChannelFrequencyCorrection = 0;
|
||||
|
||||
@@ -90,11 +90,17 @@ public abstract class FCDTunerController extends TunerController
|
||||
mComplexMixer = new ComplexMixer( mixerTDL.getTargetDataLine(), audioFormat, tunerName,
|
||||
new ComplexShortAdapter(mixerTDL.getMixerTunerType().getDisplayString()));
|
||||
|
||||
mComplexMixer.setBufferSize(audioFormat.getFrameSize() * (int)(audioFormat.getSampleRate() * 0.1));
|
||||
mComplexMixer.setBufferSize(audioFormat.getFrameSize() * getBufferSampleCount());
|
||||
|
||||
mComplexMixer.setBufferListener(mReusableBufferBroadcaster);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSampleCount()
|
||||
{
|
||||
return (int)(mComplexMixer.getAudioFormat().getSampleRate() * 0.1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the super class functionality to auto-start the complex mixer and provide samples to listeners
|
||||
*/
|
||||
|
||||
@@ -109,6 +109,12 @@ public class HackRFTunerController extends USBTunerController
|
||||
mDeviceDescriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSampleCount()
|
||||
{
|
||||
return USB_TRANSFER_BUFFER_SIZE / 2;
|
||||
}
|
||||
|
||||
public void init() throws SourceException
|
||||
{
|
||||
mDeviceHandle = new DeviceHandle();
|
||||
|
||||
@@ -17,6 +17,7 @@ package io.github.dsheirer.source.tuner.manager;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableComplexDelayBuffer;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
@@ -38,12 +39,14 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(HeterodyneChannelSourceManager.class);
|
||||
|
||||
private final static int OBJECTIVE_CHANNEL_SAMPLE_RATE = 30000;
|
||||
private final static int OBJECTIVE_CHANNEL_SAMPLE_RATE = 25000;
|
||||
private final static int DELAY_BUFFER_DURATION_MILLISECONDS = 2000;
|
||||
|
||||
private List<CICTunerChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
|
||||
private SortedSet<TunerChannel> mTunerChannels = new TreeSet<>();
|
||||
private TunerController mTunerController;
|
||||
private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor();
|
||||
private ReusableComplexDelayBuffer mSampleDelayBuffer;
|
||||
|
||||
public HeterodyneChannelSourceManager(TunerController tunerController)
|
||||
{
|
||||
@@ -136,6 +139,12 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Tuner center frequency has changed - update channels
|
||||
updateTunerFrequency(tunerSourceEvent.getValue().longValue());
|
||||
|
||||
//Clear the delay buffer since any delayed samples will be centered on the previous frequency
|
||||
if(mSampleDelayBuffer != null)
|
||||
{
|
||||
mSampleDelayBuffer.clear();
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
//The tuner is self-correcting for PPM error - relay to channels
|
||||
@@ -182,6 +191,39 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a complex sample delay buffer and registers it with the tuner controller to start the flow
|
||||
* of complex sample buffers from the tuner.
|
||||
*/
|
||||
private void startDelayBuffer()
|
||||
{
|
||||
if(mSampleDelayBuffer == null)
|
||||
{
|
||||
int delayBufferSize = (int)(DELAY_BUFFER_DURATION_MILLISECONDS / mTunerController.getBufferDuration());
|
||||
mSampleDelayBuffer = new ReusableComplexDelayBuffer(delayBufferSize, mTunerController.getBufferDuration());
|
||||
|
||||
mLog.debug("Created/registered complex sample delay buffer of size [" + delayBufferSize +
|
||||
"] buffers and delay duration [" + DELAY_BUFFER_DURATION_MILLISECONDS +
|
||||
"ms] for tuner controller [" + mTunerController.getClass().getName() +
|
||||
"] with buffer duration [" + mTunerController.getBufferDuration() + "]");
|
||||
|
||||
mTunerController.addBufferListener(mSampleDelayBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregisters the complex sample delay buffer and disposes of any queued reusable buffers.
|
||||
*/
|
||||
private void stopDelayBuffer()
|
||||
{
|
||||
if(mSampleDelayBuffer != null && !mSampleDelayBuffer.hasListeners())
|
||||
{
|
||||
mTunerController.removeBufferListener(mSampleDelayBuffer);
|
||||
mSampleDelayBuffer.dispose();
|
||||
mSampleDelayBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes channel source events
|
||||
*/
|
||||
@@ -195,13 +237,20 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
case REQUEST_START_SAMPLE_STREAM:
|
||||
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
|
||||
{
|
||||
mTunerController.addBufferListener((CICTunerChannelSource)sourceEvent.getSource());
|
||||
startDelayBuffer();
|
||||
|
||||
//The start sample stream request contains a start timestamp and the delay buffer
|
||||
//will preload the channel with delayed sample buffers that either contain the
|
||||
//timestamp or occur later/newer than the timestamp.
|
||||
mSampleDelayBuffer.addListener((CICTunerChannelSource)sourceEvent.getSource(),
|
||||
sourceEvent.getValue().longValue());
|
||||
}
|
||||
break;
|
||||
case REQUEST_STOP_SAMPLE_STREAM:
|
||||
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
|
||||
{
|
||||
mTunerController.removeBufferListener((CICTunerChannelSource)sourceEvent.getSource());
|
||||
mSampleDelayBuffer.removeListener((CICTunerChannelSource)sourceEvent.getSource());
|
||||
stopDelayBuffer();
|
||||
}
|
||||
break;
|
||||
case REQUEST_SOURCE_DISPOSE:
|
||||
|
||||
@@ -95,6 +95,12 @@ public abstract class RTL2832TunerController extends USBTunerController
|
||||
mDeviceDescriptor = deviceDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSampleCount()
|
||||
{
|
||||
return getUSBTransferBufferSize(getSampleRate()) / 2; //2 bytes per complex sample
|
||||
}
|
||||
|
||||
|
||||
public void init() throws SourceException
|
||||
{
|
||||
@@ -987,17 +993,23 @@ public abstract class RTL2832TunerController extends USBTunerController
|
||||
|
||||
if(mUSBTransferProcessor != null)
|
||||
{
|
||||
if(sampleRate.getRate() > 300000)
|
||||
{
|
||||
mUSBTransferProcessor.setBufferSize(USB_TRANSFER_BUFFER_SIZE_HIGH_SAMPLE_RATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
mUSBTransferProcessor.setBufferSize(USB_TRANSFER_BUFFER_SIZE_LOW_SAMPLE_RATE);
|
||||
}
|
||||
mUSBTransferProcessor.setBufferSize(getUSBTransferBufferSize(sampleRate.getRate()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the size of USB transfer buffers according to the sample rate
|
||||
*/
|
||||
private int getUSBTransferBufferSize(double sampleRate)
|
||||
{
|
||||
if(sampleRate > 300000)
|
||||
{
|
||||
return USB_TRANSFER_BUFFER_SIZE_HIGH_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
return USB_TRANSFER_BUFFER_SIZE_LOW_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
public void setSampleRateFrequencyCorrection(int ppm) throws SourceException
|
||||
{
|
||||
int offset = -ppm * TWO_TO_22_POWER / 1000000;
|
||||
|
||||
@@ -65,6 +65,12 @@ public class TestTunerController extends TunerController
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSampleCount()
|
||||
{
|
||||
return SAMPLE_RATE / SPECTRAL_FRAME_RATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
|
||||
@@ -209,6 +209,11 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
|
||||
|
||||
if(broadcast && mListener != null)
|
||||
{
|
||||
if(samplesRead < 0)
|
||||
{
|
||||
throw new IOException("End of file reached");
|
||||
}
|
||||
|
||||
if(samplesRead < buffer.length)
|
||||
{
|
||||
buffer = Arrays.copyOf(buffer, samplesRead);
|
||||
|
||||
Reference in New Issue
Block a user