audio: reworked audio streams to have right-hand resampling padding available.

Fixes Bugzilla #3851.
Ryan C. Gordon 2017-10-10 16:12:56 -04:00
parent 28149e11c8
commit 37d89aa10f
1 changed files with 135 additions and 79 deletions

View File

@ -31,6 +31,8 @@
#include "../SDL_dataqueue.h" #include "../SDL_dataqueue.h"
#include "SDL_cpuinfo.h" #include "SDL_cpuinfo.h"
#define DEBUG_AUDIOSTREAM 0
#ifdef __SSE3__ #ifdef __SSE3__
#define HAVE_SSE3_INTRINSICS 1 #define HAVE_SSE3_INTRINSICS 1
#endif #endif
@ -467,14 +469,20 @@ SDL_FreeResampleFilter(void)
static int static int
ResamplerPadding(const int inrate, const int outrate) ResamplerPadding(const int inrate, const int outrate)
{ {
return (inrate > outrate) ? (int) SDL_ceil(((float) (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * inrate) / ((float) outrate))) : RESAMPLER_SAMPLES_PER_ZERO_CROSSING; if (inrate == outrate) {
return 0;
} else if (inrate > outrate) {
return (int) SDL_ceil(((float) (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * inrate) / ((float) outrate)));
}
return RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
} }
/* lpadding and rpadding are expected to be buffers of (ResamplePadding(inrate, outrate) * chans * sizeof (float)) bytes. */ /* lpadding and rpadding are expected to be buffers of (ResamplePadding(inrate, outrate) * chans * sizeof (float)) bytes. */
static int static int
SDL_ResampleAudio(const int chans, const int inrate, const int outrate, SDL_ResampleAudio(const int chans, const int inrate, const int outrate,
float *lpadding, float *rpadding, const float *inbuf, const float *lpadding, const float *rpadding,
const int inbuflen, float *outbuf, const int outbuflen) const float *inbuf, const int inbuflen,
float *outbuf, const int outbuflen)
{ {
const float outtimeincr = 1.0f / ((float) outrate); const float outtimeincr = 1.0f / ((float) outrate);
const float ratio = ((float) outrate) / ((float) inrate); const float ratio = ((float) outrate) / ((float) inrate);
@ -483,7 +491,7 @@ SDL_ResampleAudio(const int chans, const int inrate, const int outrate,
const int inframes = inbuflen / framelen; const int inframes = inbuflen / framelen;
const int wantedoutframes = (int) ((inbuflen / framelen) * ratio); /* outbuflen isn't total to write, it's total available. */ const int wantedoutframes = (int) ((inbuflen / framelen) * ratio); /* outbuflen isn't total to write, it's total available. */
const int maxoutframes = outbuflen / framelen; const int maxoutframes = outbuflen / framelen;
const int outframes = (wantedoutframes < maxoutframes) ? wantedoutframes : maxoutframes; const int outframes = SDL_min(wantedoutframes, maxoutframes);
float *dst = outbuf; float *dst = outbuf;
float outtime = 0.0f; float outtime = 0.0f;
int i, j, chan; int i, j, chan;
@ -1076,6 +1084,7 @@ struct SDL_AudioStream
SDL_AudioCVT cvt_before_resampling; SDL_AudioCVT cvt_before_resampling;
SDL_AudioCVT cvt_after_resampling; SDL_AudioCVT cvt_after_resampling;
SDL_DataQueue *queue; SDL_DataQueue *queue;
SDL_bool first_run;
Uint8 *work_buffer_base; /* maybe unaligned pointer from SDL_realloc(). */ Uint8 *work_buffer_base; /* maybe unaligned pointer from SDL_realloc(). */
int work_buffer_len; int work_buffer_len;
int src_sample_frame_size; int src_sample_frame_size;
@ -1089,6 +1098,8 @@ struct SDL_AudioStream
double rate_incr; double rate_incr;
Uint8 pre_resample_channels; Uint8 pre_resample_channels;
int packetlen; int packetlen;
int resampler_padding_samples;
float *resampler_padding;
void *resampler_state; void *resampler_state;
SDL_ResampleAudioStreamFunc resampler_func; SDL_ResampleAudioStreamFunc resampler_func;
SDL_ResetAudioStreamResamplerFunc reset_resampler_func; SDL_ResetAudioStreamResamplerFunc reset_resampler_func;
@ -1129,16 +1140,7 @@ SDL_ResampleAudioStream_SRC(SDL_AudioStream *stream, const void *_inbuf, const i
SRC_DATA data; SRC_DATA data;
int result; int result;
if (inbuf == ((const float *) outbuf)) { /* libsamplerate can't work in-place. */ SDL_assert(inbuf != ((const float *) outbuf)); /* SDL_AudioStreamPut() shouldn't allow in-place resamples. */
Uint8 *ptr = EnsureStreamBufferSize(stream, inbuflen + outbuflen);
if (ptr == NULL) {
SDL_OutOfMemory();
return 0;
}
SDL_memcpy(ptr + outbuflen, ptr, inbuflen);
inbuf = (const float *) (ptr + outbuflen);
outbuf = (float *) ptr;
}
data.data_in = (float *)inbuf; /* Older versions of libsamplerate had a non-const pointer, but didn't write to it */ data.data_in = (float *)inbuf; /* Older versions of libsamplerate had a non-const pointer, but didn't write to it */
data.input_frames = inbuflen / framelen; data.input_frames = inbuflen / framelen;
@ -1213,54 +1215,32 @@ SetupLibSampleRateResampling(SDL_AudioStream *stream)
static int static int
SDL_ResampleAudioStream(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen) SDL_ResampleAudioStream(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
{ {
const Uint8 *inbufend = ((const Uint8 *) _inbuf) + inbuflen;
const float *inbuf = (const float *) _inbuf; const float *inbuf = (const float *) _inbuf;
float *outbuf = (float *) _outbuf; float *outbuf = (float *) _outbuf;
const int chans = (int) stream->pre_resample_channels; const int chans = (int) stream->pre_resample_channels;
const int inrate = stream->src_rate; const int inrate = stream->src_rate;
const int outrate = stream->dst_rate; const int outrate = stream->dst_rate;
const int paddingsamples = ResamplerPadding(inrate, outrate) * chans; const int paddingsamples = stream->resampler_padding_samples;
const int paddingbytes = paddingsamples * sizeof (float); const int paddingbytes = paddingsamples * sizeof (float);
float *lpadding = (float *) stream->resampler_state; float *lpadding = (float *) stream->resampler_state;
float *rpadding; const float *rpadding = (const float *) inbufend; /* we set this up so there are valid padding samples at the end of the input buffer. */
int retval; int retval;
if (inbuf == ((const float *) outbuf)) { /* !!! FIXME can't work in-place (for now!). */ SDL_assert(inbuf != ((const float *) outbuf)); /* SDL_AudioStreamPut() shouldn't allow in-place resamples. */
Uint8 *ptr = EnsureStreamBufferSize(stream, inbuflen + outbuflen);
if (ptr == NULL) {
SDL_OutOfMemory();
return 0;
}
SDL_memcpy(ptr + outbuflen, ptr, inbuflen);
inbuf = (const float *) (ptr + outbuflen);
outbuf = (float *) ptr;
}
/* !!! FIXME: streaming current resamples on Put, because of probably good reasons I can't remember right now, but if we resample on Get, we'd be able to access legit right padding values. */
rpadding = SDL_stack_alloc(float, paddingsamples);
if (!rpadding) {
SDL_OutOfMemory();
return 0;
}
SDL_memset(rpadding, '\0', paddingbytes);
retval = SDL_ResampleAudio(chans, inrate, outrate, lpadding, rpadding, inbuf, inbuflen, outbuf, outbuflen); retval = SDL_ResampleAudio(chans, inrate, outrate, lpadding, rpadding, inbuf, inbuflen, outbuf, outbuflen);
SDL_stack_free(rpadding);
/* update our left padding with end of current input, for next run. */ /* update our left padding with end of current input, for next run. */
SDL_memcpy(lpadding, ((const Uint8 *) inbuf) + (inbuflen - paddingbytes), paddingbytes); SDL_memcpy(lpadding, inbufend - paddingbytes, paddingbytes);
return retval; return retval;
} }
static void static void
SDL_ResetAudioStreamResampler(SDL_AudioStream *stream) SDL_ResetAudioStreamResampler(SDL_AudioStream *stream)
{ {
/* set all the left padding to silence. */ /* set all the padding to silence. */
const int inrate = stream->src_rate; const int len = stream->resampler_padding_samples;
const int outrate = stream->dst_rate;
const int chans = (int) stream->pre_resample_channels;
const int len = ResamplerPadding(inrate, outrate) * chans;
SDL_memset(stream->resampler_state, '\0', len * sizeof (float)); SDL_memset(stream->resampler_state, '\0', len * sizeof (float));
} }
@ -1293,6 +1273,7 @@ SDL_NewAudioStream(const SDL_AudioFormat src_format,
the resampled data (!!! FIXME: decide if that works in practice, though!). */ the resampled data (!!! FIXME: decide if that works in practice, though!). */
pre_resample_channels = SDL_min(src_channels, dst_channels); pre_resample_channels = SDL_min(src_channels, dst_channels);
retval->first_run = SDL_TRUE;
retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels; retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
retval->src_format = src_format; retval->src_format = src_format;
retval->src_channels = src_channels; retval->src_channels = src_channels;
@ -1304,6 +1285,14 @@ SDL_NewAudioStream(const SDL_AudioFormat src_format,
retval->pre_resample_channels = pre_resample_channels; retval->pre_resample_channels = pre_resample_channels;
retval->packetlen = packetlen; retval->packetlen = packetlen;
retval->rate_incr = ((double) dst_rate) / ((double) src_rate); retval->rate_incr = ((double) dst_rate) / ((double) src_rate);
retval->resampler_padding_samples = ResamplerPadding(retval->src_rate, retval->dst_rate) * pre_resample_channels;
retval->resampler_padding = (float *) SDL_calloc(retval->resampler_padding_samples, sizeof (float));
if (retval->resampler_padding == NULL) {
SDL_FreeAudioStream(retval);
SDL_OutOfMemory();
return NULL;
}
/* Not resampling? It's an easy conversion (and maybe not even that!). */ /* Not resampling? It's an easy conversion (and maybe not even that!). */
if (src_rate == dst_rate) { if (src_rate == dst_rate) {
@ -1325,9 +1314,7 @@ SDL_NewAudioStream(const SDL_AudioFormat src_format,
#endif #endif
if (!retval->resampler_func) { if (!retval->resampler_func) {
const int chans = (int) pre_resample_channels; retval->resampler_state = SDL_calloc(retval->resampler_padding_samples, sizeof (float));
const int len = ResamplerPadding(src_rate, dst_rate) * chans;
retval->resampler_state = SDL_calloc(len, sizeof (float));
if (!retval->resampler_state) { if (!retval->resampler_state) {
SDL_FreeAudioStream(retval); SDL_FreeAudioStream(retval);
SDL_OutOfMemory(); SDL_OutOfMemory();
@ -1366,7 +1353,12 @@ int
SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _buflen) SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _buflen)
{ {
int buflen = (int) _buflen; int buflen = (int) _buflen;
const void *origbuf = buf; int workbuflen;
Uint8 *workbuf;
Uint8 *resamplebuf = NULL;
int resamplebuflen = 0;
const int neededpaddingbytes = stream ? stream->resampler_padding_samples * sizeof (float) : 0;
int paddingbytes;
/* !!! FIXME: several converters can take advantage of SIMD, but only /* !!! FIXME: several converters can take advantage of SIMD, but only
!!! FIXME: if the data is aligned to 16 bytes. EnsureStreamBufferSize() !!! FIXME: if the data is aligned to 16 bytes. EnsureStreamBufferSize()
@ -1376,6 +1368,10 @@ SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _bufle
!!! FIXME: isn't a multiple of 16. In these cases, we should chop off !!! FIXME: isn't a multiple of 16. In these cases, we should chop off
!!! FIXME: a few samples at the end and convert them separately. */ !!! FIXME: a few samples at the end and convert them separately. */
#if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: wants to put %d preconverted bytes\n", buflen);
#endif
if (!stream) { if (!stream) {
return SDL_InvalidParamError("stream"); return SDL_InvalidParamError("stream");
} else if (!buf) { } else if (!buf) {
@ -1384,60 +1380,114 @@ SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _bufle
return 0; /* nothing to do. */ return 0; /* nothing to do. */
} else if ((buflen % stream->src_sample_frame_size) != 0) { } else if ((buflen % stream->src_sample_frame_size) != 0) {
return SDL_SetError("Can't add partial sample frames"); return SDL_SetError("Can't add partial sample frames");
} else if (buflen < (neededpaddingbytes * 2)) {
return SDL_SetError("Need to put a larger buffer");
} }
/* no padding prepended on first run. */
paddingbytes = stream->first_run ? 0 : neededpaddingbytes;
stream->first_run = SDL_FALSE;
if (!stream->cvt_before_resampling.needed &&
(stream->dst_rate == stream->src_rate) &&
!stream->cvt_after_resampling.needed) {
#if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: no conversion needed at all, queueing %d bytes.\n", buflen);
#endif
return SDL_WriteToDataQueue(stream->queue, buf, buflen);
}
/* Make sure the work buffer can hold all the data we need at once... */
workbuflen = buflen;
if (stream->cvt_before_resampling.needed) { if (stream->cvt_before_resampling.needed) {
const int workbuflen = buflen * stream->cvt_before_resampling.len_mult; /* will be "* 1" if not needed */ workbuflen *= stream->cvt_before_resampling.len_mult;
Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen); }
if (workbuf == NULL) {
if (stream->dst_rate != stream->src_rate) {
/* resamples can't happen in place, so make space for second buf. */
const int framesize = stream->pre_resample_channels * sizeof (float);
const int frames = workbuflen / framesize;
resamplebuflen = ((int) SDL_ceil(frames * stream->rate_incr)) * framesize;
#if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: will resample %d bytes to %d (ratio=%.6f)\n", workbuflen, resamplebuflen, stream->rate_incr);
#endif
workbuflen += resamplebuflen;
}
if (stream->cvt_after_resampling.needed) {
/* !!! FIXME: buffer might be big enough already? */
workbuflen *= stream->cvt_after_resampling.len_mult;
}
workbuflen += neededpaddingbytes;
#if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: Putting %d bytes of preconverted audio, need %d byte work buffer\n", buflen, workbuflen);
#endif
workbuf = EnsureStreamBufferSize(stream, workbuflen);
if (!workbuf) {
return -1; /* probably out of memory. */ return -1; /* probably out of memory. */
} }
SDL_assert(buf == origbuf);
SDL_memcpy(workbuf, buf, buflen); resamplebuf = workbuf; /* default if not resampling. */
stream->cvt_before_resampling.buf = workbuf;
SDL_memcpy(workbuf + paddingbytes, buf, buflen);
if (stream->cvt_before_resampling.needed) {
stream->cvt_before_resampling.buf = workbuf + paddingbytes;
stream->cvt_before_resampling.len = buflen; stream->cvt_before_resampling.len = buflen;
if (SDL_ConvertAudio(&stream->cvt_before_resampling) == -1) { if (SDL_ConvertAudio(&stream->cvt_before_resampling) == -1) {
return -1; /* uhoh! */ return -1; /* uhoh! */
} }
buf = workbuf;
buflen = stream->cvt_before_resampling.len_cvt; buflen = stream->cvt_before_resampling.len_cvt;
#if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: After initial conversion we have %d bytes\n", buflen);
#endif
} }
if (stream->dst_rate != stream->src_rate) { if (stream->dst_rate != stream->src_rate) {
const int workbuflen = buflen * ((int) SDL_ceil(stream->rate_incr)); /* save off some samples at the end; they are used for padding now so
Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen); the resampler is coherent and then used at the start of the next
if (workbuf == NULL) { put operation. Prepend last put operation's padding, too. */
return -1; /* probably out of memory. */
/* prepend prior put's padding. :P */
if (paddingbytes) {
SDL_memcpy(workbuf, stream->resampler_padding, paddingbytes);
buflen += paddingbytes;
} }
/* don't SDL_memcpy(workbuf, buf, buflen) here; our resampler can work inplace or not,
libsamplerate needs buffers to be separate; either way, avoid a copy here if possible. */ /* save off the data at the end for the next run. */
if (buf != origbuf) { SDL_memcpy(stream->resampler_padding, workbuf + (buflen - neededpaddingbytes), neededpaddingbytes);
buf = workbuf; /* in case we realloc()'d the pointer. */
} resamplebuf = workbuf + buflen; /* skip to second piece of workbuf. */
buflen = stream->resampler_func(stream, buf, buflen, workbuf, workbuflen); buflen = stream->resampler_func(stream, workbuf, buflen - neededpaddingbytes, resamplebuf, resamplebuflen);
buf = EnsureStreamBufferSize(stream, workbuflen);
SDL_assert(buf != NULL); /* shouldn't be growing, just aligning. */ #if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: After resampling we have %d bytes\n", buflen);
#endif
} }
if (stream->cvt_after_resampling.needed) { if (stream->cvt_after_resampling.needed) {
const int workbuflen = buflen * stream->cvt_after_resampling.len_mult; /* will be "* 1" if not needed */ stream->cvt_after_resampling.buf = resamplebuf;
Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
if (workbuf == NULL) {
return -1; /* probably out of memory. */
}
if (buf == origbuf) { /* copy if we haven't before. */
SDL_memcpy(workbuf, origbuf, buflen);
}
stream->cvt_after_resampling.buf = workbuf;
stream->cvt_after_resampling.len = buflen; stream->cvt_after_resampling.len = buflen;
if (SDL_ConvertAudio(&stream->cvt_after_resampling) == -1) { if (SDL_ConvertAudio(&stream->cvt_after_resampling) == -1) {
return -1; /* uhoh! */ return -1; /* uhoh! */
} }
buf = workbuf;
buflen = stream->cvt_after_resampling.len_cvt; buflen = stream->cvt_after_resampling.len_cvt;
#if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: After final conversion we have %d bytes\n", buflen);
#endif
} }
return SDL_WriteToDataQueue(stream->queue, buf, buflen); #if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: Final output is %d bytes\n", buflen);
#endif
/* resamplebuf holds the final output, even if we didn't resample. */
return SDL_WriteToDataQueue(stream->queue, resamplebuf, buflen);
} }
void void
@ -1450,6 +1500,7 @@ SDL_AudioStreamClear(SDL_AudioStream *stream)
if (stream->reset_resampler_func) { if (stream->reset_resampler_func) {
stream->reset_resampler_func(stream); stream->reset_resampler_func(stream);
} }
stream->first_run = SDL_TRUE;
} }
} }
@ -1458,6 +1509,10 @@ SDL_AudioStreamClear(SDL_AudioStream *stream)
int int
SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, const Uint32 len) SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, const Uint32 len)
{ {
#if DEBUG_AUDIOSTREAM
printf("AUDIOSTREAM: want to get %u converted bytes\n", (unsigned int) len);
#endif
if (!stream) { if (!stream) {
return SDL_InvalidParamError("stream"); return SDL_InvalidParamError("stream");
} else if (!buf) { } else if (!buf) {
@ -1488,6 +1543,7 @@ SDL_FreeAudioStream(SDL_AudioStream *stream)
} }
SDL_FreeDataQueue(stream->queue); SDL_FreeDataQueue(stream->queue);
SDL_free(stream->work_buffer_base); SDL_free(stream->work_buffer_base);
SDL_free(stream->resampler_padding);
SDL_free(stream); SDL_free(stream);
} }
} }