mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#1506 Resolves multiple performance issues: tuner freezing after a period of run time, buffer overflows, degraded audio playback. (#1513)
-Reduces message and event histories per channel from 500 to 200. -Resolves issue of tuner freezing due to cancelled USB transfer buffers not being resubmitted. -Reworks the audio playback system to remove in-progress call preemption and remove thread locks which can cause glitchy behavior during audio playback. -Updates audio outputs to also use single thread pool for processing audio segments. -Updates MBECallSequenceConverter to batch convert MBE to WAV audio. -Unifies recording filename prefixes across baseband, mbe and binary recordings. -Resolves issue with registering heartbeat manager to dispatcher. -Uses single-thread thread pools for Dispatcher to support batch & process to overcome context switching consistency issue in Windows. Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -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
|
||||
@@ -122,6 +122,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<In
|
||||
mChannels.add(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error adding polyphase channel source - " + (polyphaseChannelSource == null ? "source is null" :
|
||||
"channel source is already added to this channelizer"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,6 +141,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<In
|
||||
mChannels.remove(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error removing polyphase channel source - " + (polyphaseChannelSource == null ? "source is null" :
|
||||
"channel source was not previously added to this channelizer"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -24,7 +24,6 @@ import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.util.Dispatcher;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
@@ -69,8 +68,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
|
||||
|
||||
//Sized at 152 buffers a second where max = 5 seconds
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(5 * 152);
|
||||
//Sized to process 1/20th batch of 152 buffers a second, at 40 times per second
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(152 / 20, 25);
|
||||
private FloatFFT_1D mFFT;
|
||||
private float[] mInlineSamples;
|
||||
private float[] mInlineFilter;
|
||||
@@ -407,25 +406,32 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
||||
{
|
||||
public IFFTProcessorDispatcher(int maximumSize)
|
||||
public IFFTProcessorDispatcher(int batchSize, long interval)
|
||||
{
|
||||
super(maximumSize, "sdrtrunk polyphase ifft processor", Collections.emptyList());
|
||||
super("sdrtrunk polyphase ifft processor", batchSize, interval);
|
||||
|
||||
//We create a listener interface to receive the batched channel results arrays from the scheduled thread pool
|
||||
//dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each
|
||||
//channel results array contained in each results buffer and then dispatch the buffer
|
||||
//so that it can be distributed to each channel listener.
|
||||
setListener(list -> {
|
||||
List<float[]> processedChannelResults = new ArrayList<>();
|
||||
|
||||
for(float[] channelResults: list)
|
||||
try
|
||||
{
|
||||
//Rotate each of the channels to the correct phase using the IFFT
|
||||
mFFT.complexInverse(channelResults, true);
|
||||
processedChannelResults.add(channelResults);
|
||||
}
|
||||
List<float[]> processedChannelResults = new ArrayList<>();
|
||||
|
||||
dispatch(processedChannelResults);
|
||||
for(float[] channelResults: list)
|
||||
{
|
||||
//Rotate each of the channels to the correct phase using the IFFT
|
||||
mFFT.complexInverse(channelResults, true);
|
||||
processedChannelResults.add(channelResults);
|
||||
}
|
||||
|
||||
dispatch(processedChannelResults);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error during IFFT and dispatch of processed channel results", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferProvider;
|
||||
import io.github.dsheirer.buffer.NativeBufferPoisonPill;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
@@ -28,7 +27,6 @@ import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
@@ -110,8 +108,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
|
||||
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||
mBufferDispatcher = new Dispatcher(500, "sdrtrunk polyphase buffer processor",
|
||||
new NativeBufferPoisonPill());
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 50, 10);
|
||||
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
||||
}
|
||||
|
||||
@@ -177,14 +174,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current channel sample rate which is (2 * channel bandwidth).
|
||||
*/
|
||||
public double getChannelSampleRate()
|
||||
{
|
||||
return mChannelCalculator.getChannelSampleRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current channel bandwidth/spacing.
|
||||
*/
|
||||
@@ -439,9 +428,10 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
break;
|
||||
case REQUEST_STOP_SAMPLE_STREAM:
|
||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
|
||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource channelSource)
|
||||
{
|
||||
stopChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
|
||||
stopChannelSource(channelSource);
|
||||
channelSource.dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -449,14 +439,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
||||
}
|
||||
break;
|
||||
case REQUEST_SOURCE_DISPOSE:
|
||||
Source source = sourceEvent.getSource();
|
||||
|
||||
if(source instanceof PolyphaseChannelSource)
|
||||
{
|
||||
source.dispose();
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
//Rebroadcast so that the tuner source can process this event
|
||||
mSourceEventBroadcaster.broadcast(sourceEvent);
|
||||
|
||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import java.util.ArrayList;
|
||||
@@ -42,7 +41,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
||||
private Listener<ComplexSamples> mSamplesListener;
|
||||
private double mChannelSampleRate;
|
||||
private long mIndexCenterFrequency;
|
||||
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
|
||||
@@ -64,7 +63,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
super(producerSourceEventListener, tunerChannel);
|
||||
mChannelSampleRate = channelCalculator.getChannelSampleRate();
|
||||
mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS);
|
||||
doUpdateOutputProcessor(channelCalculator, filterManager);
|
||||
}
|
||||
|
||||
@@ -108,8 +106,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
super.start();
|
||||
|
||||
mStreamHeartbeatProcessor.start();
|
||||
|
||||
if(mPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
@@ -125,8 +121,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.stop();
|
||||
}
|
||||
|
||||
mStreamHeartbeatProcessor.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,14 +129,24 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
@Override
|
||||
public void setListener(final Listener<ComplexSamples> listener)
|
||||
{
|
||||
mStreamHeartbeatProcessor.setListener(listener);
|
||||
mSamplesListener = listener;
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexSamples complexSamples)
|
||||
{
|
||||
mStreamHeartbeatProcessor.receive(complexSamples);
|
||||
if(mSamplesListener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mSamplesListener.receive(complexSamples);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error dispatching complex samples to listener [" + mSamplesListener.getClass() + "]", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,7 +222,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
case 1:
|
||||
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
||||
indexes, channelCalculator.getChannelCount());
|
||||
indexes, channelCalculator.getChannelCount(), getHeartbeatManager());
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
@@ -229,7 +233,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
|
||||
channelCalculator.getChannelBandwidth(), 2);
|
||||
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
||||
indexes, filter, channelCalculator.getChannelCount());
|
||||
indexes, filter, channelCalculator.getChannelCount(), getHeartbeatManager());
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
|
||||
@@ -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
|
||||
@@ -20,8 +20,8 @@ package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||
import io.github.dsheirer.util.Dispatcher;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -31,6 +31,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
|
||||
|
||||
private Dispatcher<List<float[]>> mChannelResultsDispatcher;
|
||||
private HeartbeatManager mHeartbeatManager;
|
||||
protected Listener<ComplexSamples> mComplexSamplesListener;
|
||||
private int mInputChannelCount;
|
||||
private long mCurrentSampleTimestamp = System.currentTimeMillis();
|
||||
@@ -42,12 +43,25 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
* @param inputChannelCount is the number of input channels for this output processor
|
||||
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
|
||||
* channel sample rate for frequency translation/correction.
|
||||
* @param heartbeatManager to receive pings on the dispatcher thread
|
||||
*/
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, HeartbeatManager heartbeatManager)
|
||||
{
|
||||
mInputChannelCount = inputChannelCount;
|
||||
mChannelResultsDispatcher = new Dispatcher<>((int)sampleRate, "sdrtrunk polyphase channel", Collections.emptyList());
|
||||
mChannelResultsDispatcher.setListener(floats -> process(floats));
|
||||
//Process 1/10th of the sample rate per second at a rate of 20 times a second (200% of anticipated rate)
|
||||
mHeartbeatManager = heartbeatManager;
|
||||
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel", (int)(sampleRate / 10),
|
||||
50, mHeartbeatManager);
|
||||
mChannelResultsDispatcher.setListener(floats -> {
|
||||
try
|
||||
{
|
||||
process(floats);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error processing channel results", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -36,10 +37,12 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing a single channel index.
|
||||
* @param gain value to apply. This is typically the same as the channelizer's channel count.
|
||||
* @param heartbeatManager to receive heartbeats on the dispatch thread
|
||||
*/
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain)
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain,
|
||||
HeartbeatManager heartbeatManager)
|
||||
{
|
||||
super(1, sampleRate);
|
||||
super(1, sampleRate, heartbeatManager);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
mMixerAssembler = new OneChannelMixerAssembler(gain);
|
||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -37,12 +38,14 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing two channel indices.
|
||||
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
|
||||
* @param heartbeatManager to be pinged on the dispatcher thread
|
||||
*/
|
||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain)
|
||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain,
|
||||
HeartbeatManager heartbeatManager)
|
||||
{
|
||||
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
|
||||
//after synthesizing both input channels
|
||||
super(2, sampleRate);
|
||||
super(2, sampleRate, heartbeatManager);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
mMixerAssembler = new TwoChannelMixerAssembler(gain);
|
||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||
|
||||
Reference in New Issue
Block a user