From af0972c33fceebba985a0ae5f12467403bc39450 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 25 Apr 2025 21:13:04 -0400 Subject: [PATCH] audio: SDL_PutAudioStreamPlanarData should take a channel count. Fixes #12894. --- examples/audio/05-planar-data/planar-data.c | 4 +-- include/SDL3/SDL_audio.h | 11 ++++++- src/audio/SDL_audiocvt.c | 34 +++++++++++++++------ src/dynapi/SDL_dynapi_procs.h | 2 +- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/examples/audio/05-planar-data/planar-data.c b/examples/audio/05-planar-data/planar-data.c index ab67999ab9..f06ef696ab 100644 --- a/examples/audio/05-planar-data/planar-data.c +++ b/examples/audio/05-planar-data/planar-data.c @@ -65,12 +65,12 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) const SDL_FPoint point = { event->button.x, event->button.y }; if (SDL_PointInRectFloat(&point, &rect_left_button)) { /* clicked left button? */ const Uint8 *planes[] = { left, NULL }; /* specify NULL to say "this specific channel is silent" */ - SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, SDL_arraysize(left)); + SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, -1, SDL_arraysize(left)); SDL_FlushAudioStream(stream); /* that's all we're playing until it completes. */ playing_sound = -1; /* left is playing */ } else if (SDL_PointInRectFloat(&point, &rect_right_button)) { /* clicked right button? */ const Uint8 *planes[] = { NULL, right }; /* specify NULL to say "this specific channel is silent" */ - SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, SDL_arraysize(right)); + SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, -1, SDL_arraysize(right)); SDL_FlushAudioStream(stream); /* that's all we're playing until it completes. */ playing_sound = 1; /* right is playing */ } diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 484169a6b0..53a966e928 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -1431,6 +1431,14 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, * individual array may be NULL; in this case, silence will be interleaved for * that channel. * + * `num_channels` specifies how many arrays are in `channel_buffers`. This can + * be used as a safety to prevent overflow, in case the stream format has + * changed elsewhere. If more channels are specified than the current input + * spec, they are ignored. If less channels are specified, the missing arrays + * are treated as if they are NULL (silence is written to those channels). If + * the count is -1, SDL will assume the array count matches the current input + * spec. + * * Note that `num_samples` is the number of _samples per array_. This can also * be thought of as the number of _sample frames_ to be queued. A value of 1 * with stereo arrays will queue two samples to the stream. This is different @@ -1440,6 +1448,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, * \param stream the stream the audio data is being added to. * \param channel_buffers a pointer to an array of arrays, one array per * channel. + * \param num_channels the number of arrays in `channel_buffers` or -1. * \param num_samples the number of _samples_ per array to write to the * stream. * \returns true on success or false on failure; call SDL_GetError() for more @@ -1456,7 +1465,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, * \sa SDL_GetAudioStreamData * \sa SDL_GetAudioStreamQueued */ -extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_samples); +extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_channels, int num_samples); /** * Get converted/resampled data from the stream. diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index 085308a73c..e543507094 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -910,15 +910,29 @@ GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(32) //GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.) #undef GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION -static void InterleaveAudioChannels(void *output, const void * const *channel_buffers, int num_samples, const SDL_AudioSpec *spec) +static void InterleaveAudioChannels(void *output, const void * const *channel_buffers, int channels, int num_samples, const SDL_AudioSpec *spec) { - const int channels = spec->channels; - bool have_null_channel = false; - for (int i = 0; i < channels; i++) { - if (channel_buffers[i] == NULL) { - have_null_channel = true; - break; + const void *channels_full[16]; + + // if didn't specify enough channels, pad out a channel array with NULLs. + if ((channels >= 0) && (channels < spec->channels)) { + have_null_channel = true; + SDL_assert(SDL_IsSupportedChannelCount(spec->channels)); + SDL_assert(spec->channels <= SDL_arraysize(channels_full)); + SDL_memcpy(channels_full, channel_buffers, channels * sizeof (*channel_buffers)); + SDL_memset(channels_full + channels, 0, (spec->channels - channels) * sizeof (*channel_buffers)); + channel_buffers = (const void * const *) channels_full; + } + + channels = spec->channels; // it's either < 0, needs to be clamped to spec->channels, or we just padded it out to spec->channels with channels_full. + + if (!have_null_channel) { + for (int i = 0; i < channels; i++) { + if (channel_buffers[i] == NULL) { + have_null_channel = true; + break; + } } } @@ -943,7 +957,7 @@ static void InterleaveAudioChannels(void *output, const void * const *channel_bu } } -bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_samples) +bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_channels, int num_samples) { if (!stream) { return SDL_InvalidParamError("stream"); @@ -980,7 +994,7 @@ bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *c const int len = SDL_AUDIO_FRAMESIZE(spec) * num_samples; #if DEBUG_AUDIOSTREAM - SDL_Log("AUDIOSTREAM: wants to put %d bytes of separated data", len); + SDL_Log("AUDIOSTREAM: wants to put %d bytes of planar data", len); #endif // Is the data small enough to just interleave it on the stack and put it through the normal interface? @@ -999,7 +1013,7 @@ bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *c callback = FreeAllocatedAudioBuffer; } - InterleaveAudioChannels(data, channel_buffers, num_samples, &spec); + InterleaveAudioChannels(data, channel_buffers, num_channels, num_samples, &spec); // it's okay if the stream format changed on another thread while we didn't hold the lock; PutAudioStreamBufferInternal will notice // and set up a new track with the right format, and the next SDL_PutAudioStreamData will notice that stream->src_spec doesn't diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index b4de5f9493..49187b0329 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1283,6 +1283,6 @@ SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_Textur SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGPUDeviceProperties,(SDL_GPUDevice *a),(a),return) SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_Window *a,SDL_GPUShaderFormat b,SDL_GPUDevice **c),(a,b,c),return) -SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void * const*b,int c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void * const*b,int c,int d),(a,b,c,d),return) SDL_DYNAPI_PROC(bool,SDL_SetAudioIterationCallbacks,(SDL_AudioDeviceID a,SDL_AudioIterationCallback b,SDL_AudioIterationCallback c,void *d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_GetEventDescription,(const SDL_Event *a,char *b,int c),(a,b,c),return)