Bugfix-#2383-audio-trapped-in-output-buffer-of-resampler (#2384)

Closes #2383

Resolves issue with audio samples trapped in the resampler output buffer. Includes improvement provided by @bazineta
This commit is contained in:
Randy
2026-07-11 03:27:15 -06:00
committed by GitHub
parent 788d994ad7
commit ccae1d05e7
3 changed files with 64 additions and 13 deletions

View File

@@ -21,8 +21,6 @@ package io.github.dsheirer.dsp.filter.resample;
import com.laszlosystems.libresample4j.Resampler;
import com.laszlosystems.libresample4j.SampleBuffers;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -34,7 +32,6 @@ import java.util.List;
*/
public class RealResampler
{
protected static final Logger mLog = LoggerFactory.getLogger(RealResampler.class);
private Resampler mResampler;
private Listener<float[]> mResampledListener;
@@ -109,6 +106,19 @@ public class RealResampler
{
mBufferManager.load(samples);
mResampler.process(mResampleFactor, mBufferManager, lastBatch);
if(lastBatch)
{
mBufferManager.flushPartialOutput();
}
}
/**
* Flushes any buffered resampler output for the current stream.
*/
public void flush()
{
resample(new float[0], true);
}
/**
@@ -181,19 +191,46 @@ public class RealResampler
public void consumeOutput(float[] samples, int offset, int length)
{
mOutputBuffer.put(samples, offset, length);
dispatchFullOutputBuffers();
}
/**
* Dispatches any complete resampled output buffers.
*/
private void dispatchFullOutputBuffers()
{
while(mOutputBuffer.position() > mOutputArrayLength)
{
float[] resampled = new float[mOutputArrayLength];
dispatchOutputBuffer(mOutputArrayLength);
}
}
mOutputBuffer.flip();
mOutputBuffer.get(resampled);
mOutputBuffer.compact();
/**
* Flushes any remaining partial output by zero-padding to a full output buffer length.
*/
public void flushPartialOutput()
{
if(mOutputBuffer.position() > 0)
{
dispatchOutputBuffer(mOutputBuffer.position());
}
}
if(mResampledListener != null)
{
mResampledListener.receive(resampled);
}
/**
* Dispatches the requested number of queued output samples, zero-padding the remainder of the fixed-size
* output block when fewer than {@code mOutputArrayLength} samples are available.
*/
private void dispatchOutputBuffer(int sampleCount)
{
float[] resampled = new float[mOutputArrayLength];
mOutputBuffer.flip();
mOutputBuffer.get(resampled, 0, sampleCount);
mOutputBuffer.compact();
if(mResampledListener != null)
{
mResampledListener.receive(resampled);
}
}
}

View File

@@ -86,6 +86,10 @@ public class NoiseSquelch implements INoiseSquelchController
*/
public boolean isSquelched()
{
if(mSquelchOverride)
{
return false;
}
return mSquelch;
}

View File

@@ -84,8 +84,18 @@ public class NBFMDecoder extends SquelchControlDecoder implements ISourceEventLi
//Send squelch controlled audio to the resampler and notify the decoder state that the call continues.
mNoiseSquelch.setAudioListener(audio -> {
mResampler.resample(audio);
notifyCallContinuation();
// if squelch is closing (it hasn't propagated yet to mute the audio)
// call the resampler with lastBatch set to true. This will zero pad the input buffer and ensure
// the output buffer gets emptied.
if(mNoiseSquelch.isSquelched())
{
mResampler.resample(audio, true);
}
else
{
mResampler.resample(audio); // this method will set lastBatch to false
notifyCallContinuation();
}
});
//Notify the decoder state of call starts and ends