mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#1694 Resolves memory leak in SDRPlay RSP tuner implementation. Sample stream and event callback functions were using a global arena for memory allocation where memory was not being de-allocated. Changed to on-demand arena creation and deterministic memory de-allocation. Also, when using a reduced sample rate from the RSP tuner, the dispatcher was not keeping up with the inbound sample buffer rate, resulting in a buildup of RSP native sample buffers over time and causing growing audio delays with broken/stilted audio during playback.
This commit is contained in:
@@ -74,8 +74,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
||||
mQDecimationFilter = DecimationFilterFactory.getRealDecimationFilter(decimation);
|
||||
|
||||
//Set dispatcher to process 1/10 of estimated sample arrival rate, 20 times per second (up to 200% per interval)
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk heterodyne channel " + tunerChannel.getFrequency(),
|
||||
(int)(sampleRate / 10), 50, getHeartbeatManager());
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk heterodyne channel " + tunerChannel.getFrequency(), 50, getHeartbeatManager());
|
||||
mBufferDispatcher.setListener(new NativeBufferProcessor());
|
||||
|
||||
//Setup the frequency mixer to the current source frequency
|
||||
|
||||
@@ -55,7 +55,7 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
||||
super(listener, tunerChannel);
|
||||
mTunerController = tunerController;
|
||||
mBufferDispatcher = new Dispatcher<>("sdrtrunk pass-through channel " + tunerChannel.getFrequency(),
|
||||
250, 50, getHeartbeatManager());
|
||||
50, getHeartbeatManager());
|
||||
mBufferDispatcher.setListener(new BufferProcessor());
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class SDRplay
|
||||
private static final Logger mLog = LoggerFactory.getLogger(SDRplay.class);
|
||||
|
||||
/**
|
||||
* Foreign memory arenaallocation resource scope
|
||||
* Foreign memory arena allocation resource scope
|
||||
*/
|
||||
private final Arena mArena = Arena.openShared();
|
||||
|
||||
|
||||
@@ -37,24 +37,24 @@ public class CallbackFunctions
|
||||
|
||||
/**
|
||||
* Constructs a callback functions for single-tuner use.
|
||||
* @param arena for native memory allocation
|
||||
* @param longLifecycleArena for native memory allocation for long lifecycle objects.
|
||||
* @param deviceEventListener for device events
|
||||
* @param streamListener for streaming samples
|
||||
* @param streamCallbackListener for streaming events
|
||||
*/
|
||||
public CallbackFunctions(Arena arena, IDeviceEventListener deviceEventListener,
|
||||
public CallbackFunctions(Arena longLifecycleArena, IDeviceEventListener deviceEventListener,
|
||||
IStreamListener streamListener, IStreamCallbackListener streamCallbackListener)
|
||||
{
|
||||
//Create the event callback function
|
||||
mDeviceEventAdapter = new DeviceEventAdapter(arena, deviceEventListener);
|
||||
MemorySegment eventFunction = sdrplay_api_EventCallback_t.allocate(mDeviceEventAdapter, arena.scope());
|
||||
mDeviceEventAdapter = new DeviceEventAdapter(deviceEventListener);
|
||||
MemorySegment eventFunction = sdrplay_api_EventCallback_t.allocate(mDeviceEventAdapter, longLifecycleArena.scope());
|
||||
|
||||
//Create the stream A callback function
|
||||
mStreamACallbackAdapter = new StreamCallbackAdapter(arena, streamListener, streamCallbackListener);
|
||||
MemorySegment streamAFunction = sdrplay_api_StreamCallback_t.allocate(mStreamACallbackAdapter, arena.scope());
|
||||
mStreamACallbackAdapter = new StreamCallbackAdapter(streamListener, streamCallbackListener);
|
||||
MemorySegment streamAFunction = sdrplay_api_StreamCallback_t.allocate(mStreamACallbackAdapter, longLifecycleArena.scope());
|
||||
|
||||
//Create the callback functions union and populate the callback functions
|
||||
mCallbackFunctionsMemorySegment = sdrplay_api_CallbackFnsT.allocate(arena);
|
||||
mCallbackFunctionsMemorySegment = sdrplay_api_CallbackFnsT.allocate(longLifecycleArena);
|
||||
sdrplay_api_CallbackFnsT.EventCbFn$set(mCallbackFunctionsMemorySegment, eventFunction);
|
||||
sdrplay_api_CallbackFnsT.StreamACbFn$set(mCallbackFunctionsMemorySegment, streamAFunction);
|
||||
}
|
||||
@@ -72,15 +72,15 @@ public class CallbackFunctions
|
||||
IStreamCallbackListener streamCallbackListener)
|
||||
{
|
||||
//Create the event callback function
|
||||
mDeviceEventAdapter = new DeviceEventAdapter(arena, deviceEventListener);
|
||||
mDeviceEventAdapter = new DeviceEventAdapter(deviceEventListener);
|
||||
MemorySegment eventFunction = sdrplay_api_EventCallback_t.allocate(mDeviceEventAdapter, arena.scope());
|
||||
|
||||
//Create the stream A callback function
|
||||
mStreamACallbackAdapter = new StreamCallbackAdapter(arena, streamAListener, streamCallbackListener);
|
||||
mStreamACallbackAdapter = new StreamCallbackAdapter(streamAListener, streamCallbackListener);
|
||||
MemorySegment streamAFunction = sdrplay_api_StreamCallback_t.allocate(mStreamACallbackAdapter, arena.scope());
|
||||
|
||||
//Create the stream B callback function
|
||||
mStreamBCallbackAdapter = new StreamCallbackAdapter(arena, streamBListener, streamCallbackListener);
|
||||
mStreamBCallbackAdapter = new StreamCallbackAdapter(streamBListener, streamCallbackListener);
|
||||
MemorySegment streamBFunction = sdrplay_api_StreamCallback_t.allocate(mStreamBCallbackAdapter, arena.scope());
|
||||
|
||||
//Create the callback functions union and populate the callback functions
|
||||
|
||||
@@ -36,24 +36,14 @@ import org.slf4j.LoggerFactory;
|
||||
public class DeviceEventAdapter implements sdrplay_api_EventCallback_t
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DeviceEventAdapter.class);
|
||||
|
||||
private Arena mArena;
|
||||
private IDeviceEventListener mDeviceEventListener;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param arena to use in creating foreign memory segments to access foreign structures.
|
||||
* @param listener to receive translated device events.
|
||||
*/
|
||||
public DeviceEventAdapter(Arena arena, IDeviceEventListener listener)
|
||||
public DeviceEventAdapter(IDeviceEventListener listener)
|
||||
{
|
||||
if(arena == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Resource scope must be non-null");
|
||||
}
|
||||
|
||||
mArena = arena;
|
||||
|
||||
setListener(listener);
|
||||
}
|
||||
|
||||
@@ -75,35 +65,44 @@ public class DeviceEventAdapter implements sdrplay_api_EventCallback_t
|
||||
public void apply(int eventTypeId, int tunerSelectId, MemorySegment eventParametersPointer,
|
||||
MemorySegment callbackContext)
|
||||
{
|
||||
MemorySegment memorySegment = sdrplay_api_EventParamsT.ofAddress(eventParametersPointer, mArena.scope());
|
||||
EventType eventType = EventType.fromValue(eventTypeId);
|
||||
TunerSelect tunerSelect = TunerSelect.fromValue(tunerSelectId);
|
||||
|
||||
switch(eventType)
|
||||
try(Arena arena = Arena.openConfined())
|
||||
{
|
||||
case GAIN_CHANGE -> {
|
||||
mDeviceEventListener.processGainChange(tunerSelect,
|
||||
EventParametersFactory.createGainCallbackParameters(memorySegment));
|
||||
}
|
||||
case POWER_OVERLOAD_CHANGE -> {
|
||||
mDeviceEventListener.processPowerOverload(tunerSelect,
|
||||
EventParametersFactory.createPowerOverloadCallbackParameters(memorySegment));
|
||||
}
|
||||
case DEVICE_REMOVED -> {
|
||||
mDeviceEventListener.processDeviceRemoval(tunerSelect);
|
||||
}
|
||||
case RSP_DUO_MODE_CHANGE -> {
|
||||
mDeviceEventListener.processRspDuoModeChange(tunerSelect,
|
||||
EventParametersFactory.createRspDuoModeCallbackParameters(memorySegment));
|
||||
}
|
||||
case UNKNOWN -> {
|
||||
mLog.warn("Unknown device event callback ignored. Please contact the library developer as this may " +
|
||||
"indicate a change to the SDRPlay API change. Tuner:" + tunerSelect + " Event Type ID:" +
|
||||
eventTypeId);
|
||||
mDeviceEventListener.processEvent(eventType, tunerSelect);
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalStateException("DeviceEventAdapter must be updated handle EventType." + eventType);
|
||||
MemorySegment memorySegment = sdrplay_api_EventParamsT.ofAddress(eventParametersPointer, arena.scope());
|
||||
EventType eventType = EventType.fromValue(eventTypeId);
|
||||
TunerSelect tunerSelect = TunerSelect.fromValue(tunerSelectId);
|
||||
|
||||
switch(eventType)
|
||||
{
|
||||
case GAIN_CHANGE ->
|
||||
{
|
||||
mDeviceEventListener.processGainChange(tunerSelect,
|
||||
EventParametersFactory.createGainCallbackParameters(memorySegment));
|
||||
}
|
||||
case POWER_OVERLOAD_CHANGE ->
|
||||
{
|
||||
mDeviceEventListener.processPowerOverload(tunerSelect,
|
||||
EventParametersFactory.createPowerOverloadCallbackParameters(memorySegment));
|
||||
}
|
||||
case DEVICE_REMOVED ->
|
||||
{
|
||||
mDeviceEventListener.processDeviceRemoval(tunerSelect);
|
||||
}
|
||||
case RSP_DUO_MODE_CHANGE ->
|
||||
{
|
||||
mDeviceEventListener.processRspDuoModeChange(tunerSelect,
|
||||
EventParametersFactory.createRspDuoModeCallbackParameters(memorySegment));
|
||||
}
|
||||
case UNKNOWN ->
|
||||
{
|
||||
mLog.warn("Unknown device event callback ignored. Please contact the library developer as this may " +
|
||||
"indicate a change to the SDRPlay API change. Tuner:" + tunerSelect + " Event Type ID:" +
|
||||
eventTypeId);
|
||||
mDeviceEventListener.processEvent(eventType, tunerSelect);
|
||||
}
|
||||
default ->
|
||||
{
|
||||
throw new IllegalStateException("DeviceEventAdapter must be updated handle EventType." + eventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,24 +35,16 @@ import org.slf4j.LoggerFactory;
|
||||
public class StreamCallbackAdapter implements sdrplay_api_StreamCallback_t
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(StreamCallbackAdapter.class);
|
||||
private Arena mArena;
|
||||
private IStreamListener mStreamListener;
|
||||
private IStreamCallbackListener mStreamCallbackListener;
|
||||
|
||||
/**
|
||||
* Constructs an instance of the callback implementation
|
||||
* @param arena for defining new foreign memory segments
|
||||
* @param streamListener to receive transferred I/Q samples and event details
|
||||
* @param listener to receive callback parameters
|
||||
*/
|
||||
public StreamCallbackAdapter(Arena arena, IStreamListener streamListener, IStreamCallbackListener listener)
|
||||
public StreamCallbackAdapter(IStreamListener streamListener, IStreamCallbackListener listener)
|
||||
{
|
||||
if(arena == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Arena must be non-null");
|
||||
}
|
||||
|
||||
mArena = arena;
|
||||
mStreamCallbackListener = listener;
|
||||
setListener(streamListener);
|
||||
}
|
||||
@@ -81,24 +73,27 @@ public class StreamCallbackAdapter implements sdrplay_api_StreamCallback_t
|
||||
{
|
||||
if(mStreamListener != null || mStreamCallbackListener != null)
|
||||
{
|
||||
//Translate the callback parameters pointer to a memory segment and re-construct the parameters as a Java object
|
||||
StreamCallbackParameters parameters = new StreamCallbackParameters(sdrplay_api_StreamCbParamsT
|
||||
.ofAddress(parametersPointer, mArena.scope()));
|
||||
|
||||
if(mStreamCallbackListener != null)
|
||||
try(Arena arena = Arena.openConfined())
|
||||
{
|
||||
mStreamCallbackListener.process(mStreamListener.getTunerSelect(), parameters, reset);
|
||||
}
|
||||
//Translate the callback parameters pointer to a memory segment and re-construct the parameters as a Java object
|
||||
StreamCallbackParameters parameters = new StreamCallbackParameters(sdrplay_api_StreamCbParamsT
|
||||
.ofAddress(parametersPointer, arena.scope()));
|
||||
|
||||
if(mStreamListener != null)
|
||||
{
|
||||
//Allocate memory segments from I/Q pointers, transfer from native to JVM array, and send to listener
|
||||
long arrayByteSize = ValueLayout.JAVA_SHORT.byteSize() * sampleCount;
|
||||
MemorySegment iSamples = MemorySegment.ofAddress(iSamplesPointer.address(), arrayByteSize, mArena.scope());
|
||||
MemorySegment qSamples = MemorySegment.ofAddress(qSamplesPointer.address(), arrayByteSize, mArena.scope());
|
||||
short[] i = iSamples.toArray(ValueLayout.JAVA_SHORT);
|
||||
short[] q = qSamples.toArray(ValueLayout.JAVA_SHORT);
|
||||
mStreamListener.processStream(i, q, parameters, Flag.evaluate(reset));
|
||||
if(mStreamCallbackListener != null)
|
||||
{
|
||||
mStreamCallbackListener.process(mStreamListener.getTunerSelect(), parameters, reset);
|
||||
}
|
||||
|
||||
if(mStreamListener != null)
|
||||
{
|
||||
//Allocate memory segments from I/Q pointers, transfer from native to JVM array, and send to listener
|
||||
long arrayByteSize = ValueLayout.JAVA_SHORT.byteSize() * sampleCount;
|
||||
MemorySegment iSamples = MemorySegment.ofAddress(iSamplesPointer.address(), arrayByteSize, arena.scope());
|
||||
MemorySegment qSamples = MemorySegment.ofAddress(qSamplesPointer.address(), arrayByteSize, arena.scope());
|
||||
short[] i = iSamples.toArray(ValueLayout.JAVA_SHORT);
|
||||
short[] q = qSamples.toArray(ValueLayout.JAVA_SHORT);
|
||||
mStreamListener.processStream(i, q, parameters, Flag.evaluate(reset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user