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.Resampler;
import com.laszlosystems.libresample4j.SampleBuffers; import com.laszlosystems.libresample4j.SampleBuffers;
import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
@@ -34,7 +32,6 @@ import java.util.List;
*/ */
public class RealResampler public class RealResampler
{ {
protected static final Logger mLog = LoggerFactory.getLogger(RealResampler.class);
private Resampler mResampler; private Resampler mResampler;
private Listener<float[]> mResampledListener; private Listener<float[]> mResampledListener;
@@ -109,6 +106,19 @@ public class RealResampler
{ {
mBufferManager.load(samples); mBufferManager.load(samples);
mResampler.process(mResampleFactor, mBufferManager, lastBatch); 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) public void consumeOutput(float[] samples, int offset, int length)
{ {
mOutputBuffer.put(samples, offset, length); mOutputBuffer.put(samples, offset, length);
dispatchFullOutputBuffers();
}
/**
* Dispatches any complete resampled output buffers.
*/
private void dispatchFullOutputBuffers()
{
while(mOutputBuffer.position() > mOutputArrayLength) while(mOutputBuffer.position() > mOutputArrayLength)
{ {
float[] resampled = new float[mOutputArrayLength]; dispatchOutputBuffer(mOutputArrayLength);
}
}
mOutputBuffer.flip(); /**
mOutputBuffer.get(resampled); * Flushes any remaining partial output by zero-padding to a full output buffer length.
mOutputBuffer.compact(); */
public void flushPartialOutput()
{
if(mOutputBuffer.position() > 0)
{
dispatchOutputBuffer(mOutputBuffer.position());
}
}
if(mResampledListener != null) /**
{ * Dispatches the requested number of queued output samples, zero-padding the remainder of the fixed-size
mResampledListener.receive(resampled); * 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() public boolean isSquelched()
{ {
if(mSquelchOverride)
{
return false;
}
return mSquelch; 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. //Send squelch controlled audio to the resampler and notify the decoder state that the call continues.
mNoiseSquelch.setAudioListener(audio -> { mNoiseSquelch.setAudioListener(audio -> {
mResampler.resample(audio); // if squelch is closing (it hasn't propagated yet to mute the audio)
notifyCallContinuation(); // 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 //Notify the decoder state of call starts and ends