We’ve implemented some audio streaming code that sends floats from point A to point B in chunks of X amount of samples, then re-samples the chunks to the destination sampleRate if conversion is needed, and we’re running into a problem where it’s mostly working fine but there are “micro-stutters” introduced when listening to the stream if it was resampled.
Our suspicion is that we’re losing a sample in the final buffer due to a rounding error possibly, but we can’t seem to nail this down and it’s driving me crazy. Here is the code which performs the resample on the incoming chunks (in our case we are only working with mono/stereo so don’t mind the hard-coded channel logic)
I’ve seen various examples where sometimes there is no cast to double and instead a ceil is used on the result, but it doesn’t seem to matter which we choose; the microstutters are still there after recombining the buffers. If the entire source buffer is sent at once there are no audio issues so it has something to do with the resampling and recombining of chunks for sure.
Does anyone have any ideas?
void SampleManager::ResampleBuffer(double p_dblRatio, AudioSampleBuffer& p_roBuffer, int p_iChannels, bool p_CreateNew)
{
// Adjust the buffers size based on the sample rate ratio.
jassert(p_dblRatio > 0);
int AdjustedNumSamples = (double)p_roBuffer.getNumSamples() / p_dblRatio;
AudioSampleBuffer temp;
temp.clear();
temp.setSize(p_iChannels, AdjustedNumSamples, true, false);
const float** inputs = p_roBuffer.getArrayOfReadPointers();
float** outputs = temp.getArrayOfWritePointers();
if (p_CreateNew)
{
std::unique_ptr<LagrangeInterpolator> resamptemp1 = std::make_unique<LagrangeInterpolator>();
resamptemp1->reset();
resamptemp1->process(p_dblRatio, inputs[0], outputs[0], temp.getNumSamples());
if (p_iChannels > 1)
{
std::unique_ptr<LagrangeInterpolator> resamptemp2 = std::make_unique<LagrangeInterpolator>();
resamptemp2->reset();
resamptemp2->process(p_dblRatio, inputs[1], outputs[1], temp.getNumSamples());
}
}
else
{
resampler1->processAdding(p_dblRatio, inputs[0], outputs[0], temp.getNumSamples(), 1.0f);
if (p_iChannels > 1)
{
resampler2->processAdding(p_dblRatio, inputs[1], outputs[1], temp.getNumSamples(), 1.0f);
}
}
p_roBuffer = temp;
}
EDIT: Here’s another method I found online OpenShot Library | libopenshot: AudioResampler.cpp Source File
Couple of oddities in this one
- new_num_of_samples = round(num_of_samples * dest_ratio) - 1; ← What is the difference between this and the method I posted above? Should there not be just one way of calculating this?
- // Prepare to play the audio sources (and set the # of samples per chunk to a little more than expected)
resample_source->prepareToPlay(num_of_samples + 10, 0); ← why +10? Where does this magic number come from?