Track offset within the current sample when resampling

main
Brick 2023-08-19 15:55:23 +01:00 committed by Ryan C. Gordon
parent d60ebb06d1
commit cdaa19869d
3 changed files with 43 additions and 42 deletions

View File

@ -68,7 +68,7 @@ static int GetHistoryBufferSampleFrames(const Sint32 required_resampler_frames)
static void ResampleAudio(const int chans, const int inrate, const int outrate, static void ResampleAudio(const int chans, const int inrate, const int outrate,
const float *lpadding, const float *rpadding, const float *lpadding, const float *rpadding,
const float *inbuf, const int inframes, const float *inbuf, const int inframes,
float *outbuf, const int outframes) float *outbuf, const int outframes, const int offset)
{ {
/* This function uses integer arithmetics to avoid precision loss caused /* This function uses integer arithmetics to avoid precision loss caused
* by large floating point numbers. For some operations, Sint32 or Sint64 * by large floating point numbers. For some operations, Sint32 or Sint64
@ -81,10 +81,11 @@ static void ResampleAudio(const int chans, const int inrate, const int outrate,
int i, j, chan; int i, j, chan;
for (i = 0; i < outframes; i++) { for (i = 0; i < outframes; i++) {
int srcindex = (int)((Sint64)i * inrate / outrate); /* Offset by outrate to avoid negative numbers (which don't round correctly) */
if (srcindex >= inframes) { // !!! FIXME: can we clamp this without an if statement on each iteration? Sint64 srcpos = ((Sint64)i * inrate) + offset + outrate;
srcindex = inframes - 1; int srcindex = (int)(srcpos / outrate) - 1;
} SDL_assert(srcindex >= -1);
SDL_assert(srcindex < inframes);
/* Calculating the following way avoids subtraction or modulo of large /* Calculating the following way avoids subtraction or modulo of large
* floats which have low result precision. * floats which have low result precision.
@ -92,7 +93,7 @@ static void ResampleAudio(const int chans, const int inrate, const int outrate,
* = (i / outrate * inrate) - floor(i / outrate * inrate) * = (i / outrate * inrate) - floor(i / outrate * inrate)
* = mod(i / outrate * inrate, 1) * = mod(i / outrate * inrate, 1)
* = mod(i * inrate, outrate) / outrate */ * = mod(i * inrate, outrate) / outrate */
const int srcfraction = ((Sint64)i) * inrate % outrate; const int srcfraction = (int)(srcpos % outrate);
const float interpolation1 = ((float)srcfraction) / ((float)outrate); const float interpolation1 = ((float)srcfraction) / ((float)outrate);
const int filterindex1 = ((Sint32)srcfraction) * RESAMPLER_SAMPLES_PER_ZERO_CROSSING / outrate; const int filterindex1 = ((Sint32)srcfraction) * RESAMPLER_SAMPLES_PER_ZERO_CROSSING / outrate;
const float interpolation2 = 1.0f - interpolation1; const float interpolation2 = 1.0f - interpolation1;
@ -105,6 +106,7 @@ static void ResampleAudio(const int chans, const int inrate, const int outrate,
for (j = 0; (filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) { for (j = 0; (filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) {
const int filt_ind = filterindex1 + j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING; const int filt_ind = filterindex1 + j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
const int srcframe = srcindex - j; const int srcframe = srcindex - j;
SDL_assert(paddinglen + srcframe >= 0);
/* !!! FIXME: we can bubble this conditional out of here by doing a pre loop. */ /* !!! FIXME: we can bubble this conditional out of here by doing a pre loop. */
const float insample = (srcframe < 0) ? lpadding[((paddinglen + srcframe) * chans) + chan] : inbuf[(srcframe * chans) + chan]; const float insample = (srcframe < 0) ? lpadding[((paddinglen + srcframe) * chans) + chan] : inbuf[(srcframe * chans) + chan];
outsample += (float) (insample * (ResamplerFilter[filt_ind] + (interpolation1 * ResamplerFilterDifference[filt_ind]))); outsample += (float) (insample * (ResamplerFilter[filt_ind] + (interpolation1 * ResamplerFilterDifference[filt_ind])));
@ -114,6 +116,7 @@ static void ResampleAudio(const int chans, const int inrate, const int outrate,
for (j = 0; (filterindex2 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) { for (j = 0; (filterindex2 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) {
const int filt_ind = filterindex2 + j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING; const int filt_ind = filterindex2 + j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
const int srcframe = srcindex + 1 + j; const int srcframe = srcindex + 1 + j;
SDL_assert(srcframe - inframes < paddinglen);
// !!! FIXME: we can bubble this conditional out of here by doing a post loop. // !!! FIXME: we can bubble this conditional out of here by doing a post loop.
const float insample = (srcframe >= inframes) ? rpadding[((srcframe - inframes) * chans) + chan] : inbuf[(srcframe * chans) + chan]; const float insample = (srcframe >= inframes) ? rpadding[((srcframe - inframes) * chans) + chan] : inbuf[(srcframe * chans) + chan];
outsample += (float) (insample * (ResamplerFilter[filt_ind] + (interpolation2 * ResamplerFilterDifference[filt_ind]))); outsample += (float) (insample * (ResamplerFilter[filt_ind] + (interpolation2 * ResamplerFilterDifference[filt_ind])));
@ -551,6 +554,7 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *sr
stream->history_buffer_allocation = history_buffer_allocation; stream->history_buffer_allocation = history_buffer_allocation;
} }
stream->resample_offset = 0;
stream->resampler_padding_frames = resampler_padding_frames; stream->resampler_padding_frames = resampler_padding_frames;
stream->history_buffer_frames = history_buffer_frames; stream->history_buffer_frames = history_buffer_frames;
stream->max_sample_frame_size = max_sample_frame_size; stream->max_sample_frame_size = max_sample_frame_size;
@ -796,30 +800,17 @@ static Uint8 *EnsureStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen)
return ptr; return ptr;
} }
static int CalculateAudioStreamWorkBufSize(const SDL_AudioStream *stream, int len) static int CalculateAudioStreamWorkBufSize(const SDL_AudioStream *stream, int input_frames, int output_frames)
{ {
int workbuflen = len; int workbuflen = SDL_max(input_frames, output_frames) * stream->max_sample_frame_size;
int workbuf_frames = len / stream->dst_sample_frame_size; // start with requested sample frames
int inputlen = workbuf_frames * stream->max_sample_frame_size;
if (inputlen > workbuflen) { if (input_frames != output_frames) {
workbuflen = inputlen;
}
if (stream->dst_spec.freq != stream->src_spec.freq) {
// calculate requested sample frames needed before resampling. Use a Uint64 so the multiplication doesn't overflow.
const int input_frames = ((int) ((((Uint64) workbuf_frames) * stream->src_spec.freq) / stream->dst_spec.freq));
inputlen = input_frames * stream->max_sample_frame_size;
if (inputlen > workbuflen) {
workbuflen = inputlen;
}
// Calculate space needed to move to format/channels used for resampling stage. // Calculate space needed to move to format/channels used for resampling stage.
inputlen = input_frames * stream->pre_resample_channels * sizeof (float); int inputlen = input_frames * stream->pre_resample_channels * sizeof (float);
if (inputlen > workbuflen) { workbuflen = SDL_max(workbuflen, inputlen);
workbuflen = inputlen;
}
// Calculate space needed after resample (which lives in a second copy in the same buffer). // Calculate space needed after resample (which lives in a second copy in the same buffer).
workbuflen += workbuf_frames * stream->pre_resample_channels * sizeof (float); workbuflen += output_frames * stream->pre_resample_channels * sizeof (float);
} }
return workbuflen; return workbuflen;
@ -844,6 +835,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
int future_buffer_filled_frames = stream->future_buffer_filled_frames; int future_buffer_filled_frames = stream->future_buffer_filled_frames;
Uint8 *future_buffer = stream->future_buffer; Uint8 *future_buffer = stream->future_buffer;
Uint8 *history_buffer = stream->history_buffer; Uint8 *history_buffer = stream->history_buffer;
int resample_offset = stream->resample_offset;
float *resample_outbuf; float *resample_outbuf;
int input_frames; int input_frames;
int output_frames; int output_frames;
@ -866,21 +858,19 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
return 0; // nothing to do. return 0; // nothing to do.
} }
// !!! FIXME: this could be less aggressive about allocation, if we decide the necessary size at each stage and select the maximum required.
workbuflen = CalculateAudioStreamWorkBufSize(stream, len);
workbuf = EnsureStreamWorkBufferSize(stream, workbuflen);
if (!workbuf) {
return -1;
}
// figure out how much data we need to fulfill the request. // figure out how much data we need to fulfill the request.
input_frames = len / dst_sample_frame_size; // total sample frames caller wants input_frames = output_frames; // total sample frames caller wants
if (dst_rate != src_rate) { if (dst_rate != src_rate) {
// calculate requested sample frames needed before resampling. Use a Uint64 so the multiplication doesn't overflow. Sint64 last_offset = ((Sint64)(input_frames - 1) * src_rate) + resample_offset;
const int resampled_input_frames = (int) ((((Uint64) input_frames) * src_rate) / dst_rate); input_frames = (int)(last_offset / dst_rate) + 1;
if (resampled_input_frames > 0) {
input_frames = resampled_input_frames; Sint64 next_offset = last_offset + src_rate;
} else { // uhoh, not enough input frames! stream->resample_offset = (int)(next_offset - ((Sint64)input_frames * dst_rate));
// SDL_Log("Fraction: %i, %i", resampler_fraction, stream->resampler_fraction);
if (input_frames == 0) { // uhoh, not enough input frames!
// if they are upsampling and we end up needing less than a frame of input, we reject it because it would cause artifacts on future reads to eat a full input frame. // if they are upsampling and we end up needing less than a frame of input, we reject it because it would cause artifacts on future reads to eat a full input frame.
// however, if the stream is flushed, we would just be padding any remaining input with silence anyhow, so use it up. // however, if the stream is flushed, we would just be padding any remaining input with silence anyhow, so use it up.
if (stream->flushed) { if (stream->flushed) {
@ -892,8 +882,15 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
} }
} }
// !!! FIXME: this could be less aggressive about allocation, if we decide the necessary size at each stage and select the maximum required.
workbuflen = CalculateAudioStreamWorkBufSize(stream, input_frames, output_frames);
workbuf = EnsureStreamWorkBufferSize(stream, workbuflen);
workbuf_frames = 0; // no input has been moved to the workbuf yet. workbuf_frames = 0; // no input has been moved to the workbuf yet.
if (!workbuf) {
return -1;
}
// move any previous right-padding to the start of the buffer to convert, as those would have been the next samples from the queue ("the future buffer"). // move any previous right-padding to the start of the buffer to convert, as those would have been the next samples from the queue ("the future buffer").
if (future_buffer_filled_frames) { if (future_buffer_filled_frames) {
const int cpyframes = SDL_min(input_frames, future_buffer_filled_frames); const int cpyframes = SDL_min(input_frames, future_buffer_filled_frames);
@ -996,10 +993,12 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
resample_outbuf = (float *) ((workbuf + stream->work_buffer_allocation) - output_bytes); // do at the end of the buffer so we have room for final convert at front. resample_outbuf = (float *) ((workbuf + stream->work_buffer_allocation) - output_bytes); // do at the end of the buffer so we have room for final convert at front.
} }
// SDL_Log("IN: %i, WORK: %i, OUT: %i", input_frames, workbuf_frames, output_frames);
ResampleAudio(pre_resample_channels, src_rate, dst_rate, ResampleAudio(pre_resample_channels, src_rate, dst_rate,
stream->left_padding, stream->right_padding, stream->left_padding, stream->right_padding,
(const float *) workbuf, input_frames, (const float *) workbuf, input_frames,
resample_outbuf, output_frames); resample_outbuf, output_frames, resample_offset);
// Get us to the final format! // Get us to the final format!
// see if we can do the conversion in-place (will fit in `buf` while in-progress), or if we need to do it in the workbuf and copy it over // see if we can do the conversion in-place (will fit in `buf` while in-progress), or if we need to do it in the workbuf and copy it over
@ -1056,7 +1055,7 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len)
} }
// we convert in chunks, so we don't end up allocating a massive work buffer, etc. // we convert in chunks, so we don't end up allocating a massive work buffer, etc.
#if 0 // !!! FIXME: see https://github.com/libsdl-org/SDL/issues/8036#issuecomment-1680708349 #if 1 // !!! FIXME: see https://github.com/libsdl-org/SDL/issues/8036#issuecomment-1680708349
int retval = 0; int retval = 0;
while (len > 0) { // didn't ask for a whole sample frame, nothing to do while (len > 0) { // didn't ask for a whole sample frame, nothing to do
const int chunk_size = 1024 * 1024; // !!! FIXME: a megabyte might be overly-aggressive. const int chunk_size = 1024 * 1024; // !!! FIXME: a megabyte might be overly-aggressive.

View File

@ -179,6 +179,7 @@ struct SDL_AudioStream
int resampler_padding_frames; int resampler_padding_frames;
int history_buffer_frames; int history_buffer_frames;
int future_buffer_filled_frames; int future_buffer_filled_frames;
int resample_offset;
SDL_AudioSpec src_spec; SDL_AudioSpec src_spec;
SDL_AudioSpec dst_spec; SDL_AudioSpec dst_spec;

View File

@ -796,6 +796,7 @@ static int audio_resampleLoss(void *arg)
} test_specs[] = { } test_specs[] = {
{ 50, 440, 0, 44100, 48000, 60, 0.0025 }, { 50, 440, 0, 44100, 48000, 60, 0.0025 },
{ 50, 5000, SDL_PI_D / 2, 20000, 10000, 65, 0.0010 }, { 50, 5000, SDL_PI_D / 2, 20000, 10000, 65, 0.0010 },
{ 50, 440, 0, 22050, 96000, 60, 0.0120 }, /* I have no idea how to tune these values */
{ 0 } { 0 }
}; };