From 729329068b31dc1bb0d61474bd7c976c3601883e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 19 Oct 2017 18:05:42 -0400 Subject: [PATCH] audio: Added SDL_AudioStreamFlush(). --- include/SDL_audio.h | 22 ++++++++++- src/audio/SDL_audiocvt.c | 66 +++++++++++++++++++++++++++++-- src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/include/SDL_audio.h b/include/SDL_audio.h index b30c4092698bf..a242aced4c9e0 100644 --- a/include/SDL_audio.h +++ b/include/SDL_audio.h @@ -545,16 +545,36 @@ extern DECLSPEC int SDLCALL SDL_AudioStreamPut(SDL_AudioStream *stream, const vo extern DECLSPEC int SDLCALL SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, int len); /** - * Get the number of converted/resampled bytes available + * Get the number of converted/resampled bytes available. The stream may be + * buffering data behind the scenes until it has enough to resample + * correctly, so this number might be lower than what you expect, or even + * be zero. Add more data or flush the stream if you need the data now. * * \sa SDL_NewAudioStream * \sa SDL_AudioStreamPut * \sa SDL_AudioStreamGet * \sa SDL_AudioStreamClear + * \sa SDL_AudioStreamFlush * \sa SDL_FreeAudioStream */ extern DECLSPEC int SDLCALL SDL_AudioStreamAvailable(SDL_AudioStream *stream); +/** + * Tell the stream that you're done sending data, and anything being buffered + * should be converted/resampled and made available immediately. + * + * It is legal to add more data to a stream after flushing, but there will + * be audio gaps in the output. Generally this is intended to signal the + * end of input, so the complete output becomes available. + * + * \sa SDL_NewAudioStream + * \sa SDL_AudioStreamPut + * \sa SDL_AudioStreamGet + * \sa SDL_AudioStreamClear + * \sa SDL_FreeAudioStream + */ +extern DECLSPEC int SDLCALL SDL_AudioStreamFlush(SDL_AudioStream *stream); + /** * Clear any pending data in the stream without converting it * diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index 3a10fc9f064dd..9dba98088d617 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -1362,7 +1362,7 @@ SDL_NewAudioStream(const SDL_AudioFormat src_format, } static int -SDL_AudioStreamPutInternal(SDL_AudioStream *stream, const void *buf, int len) +SDL_AudioStreamPutInternal(SDL_AudioStream *stream, const void *buf, int len, int *maxputbytes) { int buflen = len; int workbuflen; @@ -1479,6 +1479,13 @@ SDL_AudioStreamPutInternal(SDL_AudioStream *stream, const void *buf, int len) printf("AUDIOSTREAM: Final output is %d bytes\n", buflen); #endif + if (maxputbytes) { + const int maxbytes = *maxputbytes; + if (buflen > maxbytes) + buflen = maxbytes; + *maxputbytes -= buflen; + } + /* resamplebuf holds the final output, even if we didn't resample. */ return buflen ? SDL_WriteToDataQueue(stream->queue, resamplebuf, buflen) : 0; } @@ -1524,7 +1531,7 @@ SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, int len) we don't need to store it for later, skip the staging process. */ if (!stream->staging_buffer_filled && len >= stream->staging_buffer_size) { - return SDL_AudioStreamPutInternal(stream, buf, len); + return SDL_AudioStreamPutInternal(stream, buf, len, NULL); } /* If there's not enough data to fill the staging buffer, just save it */ @@ -1539,7 +1546,7 @@ SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, int len) SDL_assert(amount > 0); SDL_memcpy(stream->staging_buffer + stream->staging_buffer_filled, buf, amount); stream->staging_buffer_filled = 0; - if (SDL_AudioStreamPutInternal(stream, stream->staging_buffer, stream->staging_buffer_size) < 0) { + if (SDL_AudioStreamPutInternal(stream, stream->staging_buffer, stream->staging_buffer_size, NULL) < 0) { return -1; } buf = (void *)((Uint8 *)buf + amount); @@ -1548,6 +1555,58 @@ SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, int len) return 0; } +int SDL_AudioStreamFlush(SDL_AudioStream *stream) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } + + #if DEBUG_AUDIOSTREAM + printf("AUDIOSTREAM: flushing! staging_buffer_filled=%d bytes\n", stream->staging_buffer_filled); + #endif + + /* shouldn't use a staging buffer if we're not resampling. */ + SDL_assert((stream->dst_rate != stream->src_rate) || (stream->staging_buffer_filled == 0)); + + if (stream->staging_buffer_filled > 0) { + /* push the staging buffer + silence. We need to flush out not just + the staging buffer, but the piece that the stream was saving off + for right-side resampler padding. */ + const SDL_bool first_run = stream->first_run; + const int filled = stream->staging_buffer_filled; + int actual_input_frames = filled / stream->src_sample_frame_size; + if (!first_run) + actual_input_frames += stream->resampler_padding_samples / stream->pre_resample_channels; + + if (actual_input_frames > 0) { /* don't bother if nothing to flush. */ + /* This is how many bytes we're expecting without silence appended. */ + int flush_remaining = ((int) SDL_ceil(actual_input_frames * stream->rate_incr)) * stream->dst_sample_frame_size; + + #if DEBUG_AUDIOSTREAM + printf("AUDIOSTREAM: flushing with padding to get max %d bytes!\n", flush_remaining); + #endif + + SDL_memset(stream->staging_buffer + filled, '\0', stream->staging_buffer_size - filled); + if (SDL_AudioStreamPutInternal(stream, stream->staging_buffer, stream->staging_buffer_size, &flush_remaining) < 0) { + return -1; + } + + /* we have flushed out (or initially filled) the pending right-side + resampler padding, but we need to push more silence to guarantee + the staging buffer is fully flushed out, too. */ + SDL_memset(stream->staging_buffer, '\0', filled); + if (SDL_AudioStreamPutInternal(stream, stream->staging_buffer, stream->staging_buffer_size, &flush_remaining) < 0) { + return -1; + } + } + } + + stream->staging_buffer_filled = 0; + stream->first_run = SDL_TRUE; + + return 0; +} + /* get converted/resampled data from the stream */ int SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, int len) @@ -1587,6 +1646,7 @@ SDL_AudioStreamClear(SDL_AudioStream *stream) stream->reset_resampler_func(stream); } stream->first_run = SDL_TRUE; + stream->staging_buffer_filled = 0; } } diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 07705127f6f13..ac3980f5feab4 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -646,3 +646,4 @@ #define SDL_AudioStreamClear SDL_AudioStreamClear_REAL #define SDL_AudioStreamAvailable SDL_AudioStreamAvailable_REAL #define SDL_FreeAudioStream SDL_FreeAudioStream_REAL +#define SDL_AudioStreamFlush SDL_AudioStreamFlush_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index e1372ef5b9772..8c0f8b7154dca 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -680,3 +680,4 @@ SDL_DYNAPI_PROC(int,SDL_AudioStreamGet,(SDL_AudioStream *a, void *b, int c),(a,b SDL_DYNAPI_PROC(void,SDL_AudioStreamClear,(SDL_AudioStream *a),(a),) SDL_DYNAPI_PROC(int,SDL_AudioStreamAvailable,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(void,SDL_FreeAudioStream,(SDL_AudioStream *a),(a),) +SDL_DYNAPI_PROC(int,SDL_AudioStreamFlush,(SDL_AudioStream *a),(a),return)