audio: SDL_AudioStream now has callbacks for Get and Put operations.

This allows code to feed a stream (or feed from a stream) on-demand,
which is to say: it can efficiently simulate the SDL2 audio callback.
This commit is contained in:
Ryan C. Gordon
2023-05-28 22:39:39 -04:00
parent 905c4fff5b
commit 56b1bc2198
8 changed files with 286 additions and 10 deletions

View File

@@ -629,6 +629,40 @@ SDL_AudioStream *SDL_CreateAudioStream(SDL_AudioFormat src_format,
return retval;
}
int SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata)
{
if (!stream) {
return SDL_InvalidParamError("stream");
}
SDL_LockMutex(stream->lock);
stream->get_callback = callback;
stream->get_callback_userdata = userdata;
SDL_UnlockMutex(stream->lock);
return 0;
}
int SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata)
{
if (!stream) {
return SDL_InvalidParamError("stream");
}
SDL_LockMutex(stream->lock);
stream->put_callback = callback;
stream->put_callback_userdata = userdata;
SDL_UnlockMutex(stream->lock);
return 0;
}
int SDL_LockAudioStream(SDL_AudioStream *stream)
{
return stream ? SDL_LockMutex(stream->lock) : SDL_InvalidParamError("stream");
}
int SDL_UnlockAudioStream(SDL_AudioStream *stream)
{
return stream ? SDL_UnlockMutex(stream->lock) : SDL_InvalidParamError("stream");
}
int SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat *src_format, int *src_channels, int *src_rate, SDL_AudioFormat *dst_format, int *dst_channels, int *dst_rate)
{
if (!stream) {
@@ -696,6 +730,8 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
SDL_LockMutex(stream->lock);
const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
if ((len % stream->src_sample_frame_size) != 0) {
SDL_UnlockMutex(stream->lock);
return SDL_SetError("Can't add partial sample frames");
@@ -704,6 +740,11 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
/* just queue the data, we convert/resample when dequeueing. */
retval = SDL_WriteToDataQueue(stream->queue, buf, len);
stream->flushed = SDL_FALSE;
if (stream->put_callback) {
stream->put_callback(stream, SDL_GetAudioStreamAvailable(stream) - prev_available, stream->put_callback_userdata);
}
SDL_UnlockMutex(stream->lock);
return retval;
@@ -978,6 +1019,23 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len)
len -= len % stream->dst_sample_frame_size; /* chop off any fractional sample frame. */
// give the callback a chance to fill in more stream data if it wants.
if (stream->get_callback) {
int approx_request = len / stream->dst_sample_frame_size; /* start with sample frames desired */
if (stream->src_rate != stream->dst_rate) {
/* calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. */
approx_request = (size_t) ((((Uint64) approx_request) * stream->src_rate) / stream->dst_rate);
if (!stream->flushed) { /* do we need to fill the future buffer to accomodate this, too? */
approx_request += stream->future_buffer_filled_frames - stream->resampler_padding_frames;
}
}
approx_request *= stream->src_sample_frame_size; // convert sample frames to bytes.
const int already_have = SDL_GetAudioStreamAvailable(stream);
approx_request -= SDL_min(approx_request, already_have); // we definitely have this much output already packed in.
stream->get_callback(stream, approx_request, stream->get_callback_userdata);
}
/* we convert in chunks, so we don't end up allocating a massive work buffer, etc. */
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. */

View File

@@ -137,6 +137,11 @@ struct SDL_AudioStream
SDL_DataQueue *queue;
SDL_Mutex *lock; /* this is just a copy of `queue`'s mutex. We share a lock. */
SDL_AudioStreamRequestCallback get_callback;
void *get_callback_userdata;
SDL_AudioStreamRequestCallback put_callback;
void *put_callback_userdata;
Uint8 *work_buffer; /* used for scratch space during data conversion/resampling. */
Uint8 *history_buffer; /* history for left padding and future sample rate changes. */
Uint8 *future_buffer; /* stuff that left the queue for the right padding and will be next read's data. */

View File

@@ -879,6 +879,10 @@ SDL3_0.0.0 {
SDL_MixAudioFormat;
SDL_ConvertAudioSamples;
SDL_GetSilenceValueForFormat;
SDL_LockAudioStream;
SDL_UnlockAudioStream;
SDL_SetAudioStreamGetCallback;
SDL_SetAudioStreamPutCallback;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@@ -905,3 +905,7 @@
#define SDL_MixAudioFormat SDL_MixAudioFormat_REAL
#define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL
#define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL
#define SDL_LockAudioStream SDL_LockAudioStream_REAL
#define SDL_UnlockAudioStream SDL_UnlockAudioStream_REAL
#define SDL_SetAudioStreamGetCallback SDL_SetAudioStreamGetCallback_REAL
#define SDL_SetAudioStreamPutCallback SDL_SetAudioStreamPutCallback_REAL

View File

@@ -950,3 +950,7 @@ SDL_DYNAPI_PROC(int,SDL_LoadWAV_RW,(SDL_RWops *a, int b, SDL_AudioFormat *c, int
SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),return)
SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(SDL_AudioFormat a, int b, int c, const Uint8 *d, int e, SDL_AudioFormat f, int g, int h, Uint8 **i, int *j),(a,b,c,d,e,f,g,h,i,j),return)
SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return)
SDL_DYNAPI_PROC(int,SDL_LockAudioStream,(SDL_AudioStream *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_UnlockAudioStream,(SDL_AudioStream *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_SetAudioStreamGetCallback,(SDL_AudioStream *a, SDL_AudioStreamRequestCallback b, void *c),(a,b,c),return)
SDL_DYNAPI_PROC(int,SDL_SetAudioStreamPutCallback,(SDL_AudioStream *a, SDL_AudioStreamRequestCallback b, void *c),(a,b,c),return)