diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index c6a5dbc691..b90887d124 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -1414,6 +1414,82 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamOutputChannelMap(SDL_AudioStr */ extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len); +/** + * A callback that fires for completed SDL_PutAudioStreamDataNoCopy() data. + * + * When using SDL_PutAudioStreamDataNoCopy() to provide data to an + * SDL_AudioStream, it's not safe to dispose of the data until the stream + * has completely consumed it. Often times it's difficult to know exactly + * when this has happened. + * + * This callback fires once when the stream no longer needs the buffer, + * allowing the app to easily free or reuse it. + * + * \param userdata an opaque pointer provided by the app for their personal + * use. + * \param buf the pointer provided to SDL_PutAudioStreamDataNoCopy(). + * \param buflen the size of buffer, in bytes, provided to + * SDL_PutAudioStreamDataNoCopy(). + * + * \threadsafety This callbacks may run from any thread, so if you need to + * protect shared data, you should use SDL_LockAudioStream to + * serialize access; this lock will be held before your callback + * is called, so your callback does not need to manage the lock + * explicitly. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_SetAudioStreamGetCallback + * \sa SDL_SetAudioStreamPutCallback + */ +typedef void (SDLCALL *SDL_AudioStreamDataCompleteCallback)(void *userdata, const void *buf, int buflen); + +/** + * Add constant data to the stream. + * + * Unlike SDL_PutAudioStreamData(), this function does not make a copy of the + * provided data, instead storing the provided pointer. This means that the + * put operation does not need to allocate and copy the data, but the original + * data must remain available until the stream is done with it, either by + * being read from the stream in its entirety, or a call to + * SDL_ClearAudioStream() or SDL_DestroyAudioStream(). + * + * The data must match the format/channels/samplerate specified in the latest + * call to SDL_SetAudioStreamFormat, or the format specified when creating the + * stream if it hasn't been changed. + * + * An optional callback may be provided, which is called when the stream no + * longer needs the data. Once this callback fires, the stream will not + * access the data again. + * + * Note that there is still an allocation to store tracking information, + * so this function is more efficient for larger blocks of data. If you're + * planning to put a few samples at a time, it will be more efficient to use + * SDL_PutAudioStreamData(), which allocates and buffers in blocks. + * + * \param stream the stream the audio data is being added to. + * \param buf a pointer to the audio data to add. + * \param len the number of bytes to write to the stream. + * \param callback the callback function to call when the data is no longer + * needed by the stream. May be NULL. + * \param userdata an opaque pointer provided to the callback for its own + * personal use. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread, but if the + * stream has a callback set, the caller might need to manage + * extra locking. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_ClearAudioStream + * \sa SDL_FlushAudioStream + * \sa SDL_GetAudioStreamData + * \sa SDL_GetAudioStreamQueued + */ +extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int len, SDL_AudioStreamDataCompleteCallback callback, void *userdata); + /** * Add data to the stream with each channel in a separate array. * diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index d05fd915aa..13888fd246 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -1026,6 +1026,29 @@ bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *c return retval; } +static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len) +{ + // We don't own the buffer, but know it will outlive the stream +} + +bool SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int len, SDL_AudioStreamDataCompleteCallback callback, void *userdata) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } else if (!buf) { + return SDL_InvalidParamError("buf"); + } else if (len < 0) { + return SDL_InvalidParamError("len"); + } else if (len == 0) { + if (callback) { + callback(userdata, buf, len); + } + return true; // nothing to do. + } + + return PutAudioStreamBuffer(stream, buf, len, callback ? callback : DontFreeThisAudioBuffer, userdata); +} + bool SDL_FlushAudioStream(SDL_AudioStream *stream) { if (!stream) { @@ -1483,11 +1506,6 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) SDL_free(stream); } -static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len) -{ - // We don't own the buffer, but know it will outlive the stream -} - bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) { if (dst_data) { @@ -1514,8 +1532,7 @@ bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_dat SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec); if (stream) { - if (PutAudioStreamBuffer(stream, src_data, src_len, DontFreeThisAudioBuffer, NULL) && - SDL_FlushAudioStream(stream)) { + if (SDL_PutAudioStreamDataNoCopy(stream, src_data, src_len, NULL, NULL) && SDL_FlushAudioStream(stream)) { dstlen = SDL_GetAudioStreamAvailable(stream); if (dstlen >= 0) { dst = (Uint8 *)SDL_malloc(dstlen); diff --git a/src/audio/SDL_audioqueue.h b/src/audio/SDL_audioqueue.h index 466294622a..dc3c71c20c 100644 --- a/src/audio/SDL_audioqueue.h +++ b/src/audio/SDL_audioqueue.h @@ -25,7 +25,7 @@ // Internal functions used by SDL_AudioStream for queueing audio. -typedef void (SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen); +typedef SDL_AudioStreamDataCompleteCallback SDL_ReleaseAudioBufferCallback; typedef struct SDL_AudioQueue SDL_AudioQueue; typedef struct SDL_AudioTrack SDL_AudioTrack; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index a3e47763ea..91bd4300c7 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1253,6 +1253,7 @@ SDL3_0.0.0 { SDL_PutAudioStreamPlanarData; SDL_SetAudioIterationCallbacks; SDL_GetEventDescription; + SDL_PutAudioStreamDataNoCopy; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 4242dab7e9..fcc0bad7b2 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1278,3 +1278,4 @@ #define SDL_PutAudioStreamPlanarData SDL_PutAudioStreamPlanarData_REAL #define SDL_SetAudioIterationCallbacks SDL_SetAudioIterationCallbacks_REAL #define SDL_GetEventDescription SDL_GetEventDescription_REAL +#define SDL_PutAudioStreamDataNoCopy SDL_PutAudioStreamDataNoCopy_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 49187b0329..60e0dfea57 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1286,3 +1286,4 @@ SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_Window *a,SDL_GPUShader 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) +SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamDataNoCopy,(SDL_AudioStream *a,const void *b,int c,SDL_AudioStreamDataCompleteCallback d,void *e),(a,b,c,d,e),return)