mirror of https://github.com/libsdl-org/SDL
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:
parent
905c4fff5b
commit
56b1bc2198
|
@ -53,11 +53,11 @@ The following structures have been renamed:
|
|||
|
||||
## SDL_audio.h
|
||||
|
||||
The audio subsystem in SDL3 is dramatically different than SDL2. There is no longer an audio callback; instead you bind SDL_AudioStreams to devices.
|
||||
The audio subsystem in SDL3 is dramatically different than SDL2. The primary way to play audio is no longer an audio callback; instead you bind SDL_AudioStreams to devices.
|
||||
|
||||
The SDL 1.2 audio compatibility API has also been removed, as it was a simplified version of the audio callback interface.
|
||||
|
||||
If your app depends on the callback method, you can use the single-header library at https://github.com/libsdl-org/SDL3_audio_callback (to be written!) to simulate it on top of SDL3's new API.
|
||||
If your app depends on the callback method, there is a similar approach you can take. But first, this is the new approach:
|
||||
|
||||
In SDL2, you might have done something like this to play audio:
|
||||
|
||||
|
@ -84,7 +84,6 @@ in SDL3:
|
|||
|
||||
```c
|
||||
/* ...somewhere near startup... */
|
||||
my_desired_audio_format.callback = MyAudioCallback; /* etc */
|
||||
SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(0, SDL_AUDIO_S16, 2, 44100);
|
||||
SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, SDL_AUDIO_S16, 2, 44100);
|
||||
|
||||
|
@ -93,6 +92,23 @@ in SDL3:
|
|||
SDL_PutAudioStreamData(stream, buf, buflen);
|
||||
```
|
||||
|
||||
If you absolutely require the callback method, SDL_AudioStreams can use a callback whenever more data is to be read from them, which can be used to simulate SDL2 semantics:
|
||||
|
||||
```c
|
||||
void SDLCALL MyAudioCallback(SDL_AudioStream *stream, int len, void *userdata)
|
||||
{
|
||||
/* calculate a little more audio here, maybe using `userdata`, write it to `stream` */
|
||||
SDL_PutAudioStreamData(stream, newdata, len);
|
||||
}
|
||||
|
||||
/* ...somewhere near startup... */
|
||||
SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(0, SDL_AUDIO_S16, 2, 44100);
|
||||
SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, SDL_AUDIO_S16, 2, 44100);
|
||||
SDL_SetAudioStreamGetCallback(stream, MyAudioCallback);
|
||||
|
||||
/* MyAudioCallback will be called whenever the device requests more audio data. */
|
||||
```
|
||||
|
||||
SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint.
|
||||
|
||||
The `SDL_AUDIO_ALLOW_*` symbols have been removed; now one may request the format they desire from the audio device, but ultimately SDL_AudioStream will manage the difference. One can use SDL_GetAudioDeviceFormat() to see what the final format is, if any "allowed" changes should be accomodated by the app.
|
||||
|
|
|
@ -713,6 +713,171 @@ extern DECLSPEC int SDLCALL SDL_FlushAudioStream(SDL_AudioStream *stream);
|
|||
*/
|
||||
extern DECLSPEC int SDLCALL SDL_ClearAudioStream(SDL_AudioStream *stream);
|
||||
|
||||
/**
|
||||
* Lock an audio stream for serialized access.
|
||||
*
|
||||
* Each SDL_AudioStream has an internal mutex it uses to
|
||||
* protect its data structures from threading conflicts. This function
|
||||
* allows an app to lock that mutex, which could be useful if
|
||||
* registering callbacks on this stream.
|
||||
*
|
||||
* One does not need to lock a stream to use in it most cases,
|
||||
* as the stream manages this lock internally. However, this lock
|
||||
* is held during callbacks, which may run from arbitrary threads
|
||||
* at any time, so if an app needs to protect shared data during
|
||||
* those callbacks, locking the stream guarantees that the
|
||||
* callback is not running while the lock is held.
|
||||
*
|
||||
* As this is just a wrapper over SDL_LockMutex for an internal
|
||||
* lock, it has all the same attributes (recursive locks are
|
||||
* allowed, etc).
|
||||
*
|
||||
* \param stream The audio stream to lock.
|
||||
* \returns 0 on success or a negative error code on failure; call
|
||||
* SDL_GetError() for more information.
|
||||
*
|
||||
* \since This function is available since SDL 3.0.0.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \sa SDL_UnlockAudioStream
|
||||
* \sa SDL_SetAudioStreamPutCallback
|
||||
* \sa SDL_SetAudioStreamGetCallback
|
||||
*/
|
||||
extern DECLSPEC int SDLCALL SDL_LockAudioStream(SDL_AudioStream *stream);
|
||||
|
||||
|
||||
/**
|
||||
* Unlock an audio stream for serialized access.
|
||||
*
|
||||
* This unlocks an audio stream after a call to SDL_LockAudioStream.
|
||||
*
|
||||
* \param stream The audio stream to unlock.
|
||||
* \returns 0 on success or a negative error code on failure; call
|
||||
* SDL_GetError() for more information.
|
||||
*
|
||||
* \since This function is available since SDL 3.0.0.
|
||||
*
|
||||
* \threadsafety You should only call this from the same thread that
|
||||
* previously called SDL_LockAudioStream.
|
||||
*
|
||||
* \sa SDL_LockAudioStream
|
||||
* \sa SDL_SetAudioStreamPutCallback
|
||||
* \sa SDL_SetAudioStreamGetCallback
|
||||
*/
|
||||
extern DECLSPEC int SDLCALL SDL_UnlockAudioStream(SDL_AudioStream *stream);
|
||||
|
||||
/**
|
||||
* A callback that fires when data passes through an SDL_AudioStream.
|
||||
*
|
||||
* Apps can (optionally) register a callback with an audio stream that
|
||||
* is called when data is added with SDL_PutAudioStreamData, or requested
|
||||
* with SDL_GetAudioStreamData. These 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 by
|
||||
* before your callback is called, so your callback does not need to
|
||||
* manage the lock explicitly.
|
||||
*
|
||||
* \param stream The SDL audio stream associated with this callback.
|
||||
* \param approx_request The _approximate_ amout of data, in bytes, that is requested.
|
||||
* This might be slightly overestimated due to buffering or
|
||||
* resampling, and may change from call to call anyhow.
|
||||
* \param userdata An opaque pointer provided by the app for their personal use.
|
||||
*/
|
||||
typedef void (SDLCALL *SDL_AudioStreamRequestCallback)(SDL_AudioStream *stream, int approx_request, void *userdata);
|
||||
|
||||
/**
|
||||
* Set a callback that runs when data is requested from an audio stream.
|
||||
*
|
||||
* This callback is called _before_ data is obtained from the stream,
|
||||
* giving the callback the chance to add more on-demand.
|
||||
*
|
||||
* The callback can (optionally) call SDL_PutAudioStreamData() to add
|
||||
* more audio to the stream during this call; if needed, the request
|
||||
* that triggered this callback will obtain the new data immediately.
|
||||
*
|
||||
* The callback's `approx_request` argument is roughly how many bytes
|
||||
* of _unconverted_ data (in the stream's input format) is needed by
|
||||
* the caller, although this may overestimate a little for safety.
|
||||
* This takes into account how much is already in the stream and only
|
||||
* asks for any extra necessary to resolve the request, which means
|
||||
* the callback may be asked for zero bytes, and a different amount
|
||||
* on each call.
|
||||
*
|
||||
* The callback is not required to supply exact amounts; it is allowed
|
||||
* to supply too much or too little or none at all. The caller will
|
||||
* get what's available, up to the amount they requested, regardless
|
||||
* of this callback's outcome.
|
||||
*
|
||||
* Clearing or flushing an audio stream does not call this callback.
|
||||
*
|
||||
* This function obtains the stream's lock, which means any existing
|
||||
* callback (get or put) in progress will finish running before setting
|
||||
* the new callback.
|
||||
*
|
||||
* Setting a NULL function turns off the callback.
|
||||
*
|
||||
* \param stream the audio stream to set the new callback on.
|
||||
* \param callback the new callback function to call when data is added to the stream.
|
||||
* \param userdata an opaque pointer provided to the callback for its own personal use.
|
||||
* \returns 0 on success, -1 on error. This only fails if `stream` is NULL.
|
||||
*
|
||||
* \since This function is available since SDL 3.0.0.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \sa SDL_SetAudioStreamPutCallback
|
||||
*/
|
||||
extern DECLSPEC int SDLCALL SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata);
|
||||
|
||||
/**
|
||||
* Set a callback that runs when data is added to an audio stream.
|
||||
*
|
||||
* This callback is called _after_ the data is added to the stream,
|
||||
* giving the callback the chance to obtain it immediately.
|
||||
*
|
||||
* The callback can (optionally) call SDL_GetAudioStreamData() to
|
||||
* obtain audio from the stream during this call.
|
||||
*
|
||||
* The callback's `approx_request` argument is how many bytes
|
||||
* of _converted_ data (in the stream's output format) was provided
|
||||
* by the caller, although this may underestimate a little for safety.
|
||||
* This value might be less than what is currently available in the
|
||||
* stream, if data was already there, and might be less than the
|
||||
* caller provided if the stream needs to keep a buffer to aid in
|
||||
* resampling. Which means the callback may be provided with zero
|
||||
* bytes, and a different amount on each call.
|
||||
*
|
||||
* The callback may call SDL_GetAudioStreamAvailable to see the
|
||||
* total amount currently available to read from the stream, instead
|
||||
* of the total provided by the current call.
|
||||
*
|
||||
* The callback is not required to obtain all data. It is allowed
|
||||
* to read less or none at all. Anything not read now simply remains
|
||||
* in the stream for later access.
|
||||
*
|
||||
* Clearing or flushing an audio stream does not call this callback.
|
||||
*
|
||||
* This function obtains the stream's lock, which means any existing
|
||||
* callback (get or put) in progress will finish running before setting
|
||||
* the new callback.
|
||||
*
|
||||
* Setting a NULL function turns off the callback.
|
||||
*
|
||||
* \param stream the audio stream to set the new callback on.
|
||||
* \param callback the new callback function to call when data is added to the stream.
|
||||
* \param userdata an opaque pointer provided to the callback for its own personal use.
|
||||
* \returns 0 on success, -1 on error. This only fails if `stream` is NULL.
|
||||
*
|
||||
* \since This function is available since SDL 3.0.0.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \sa SDL_SetAudioStreamGetCallback
|
||||
*/
|
||||
extern DECLSPEC int SDLCALL SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata);
|
||||
|
||||
|
||||
/**
|
||||
* Free an audio stream
|
||||
*
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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: *;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -33,11 +33,36 @@ static struct
|
|||
int freq;
|
||||
Uint8 *sound; /* Pointer to wave data */
|
||||
Uint32 soundlen; /* Length of wave data */
|
||||
Uint32 soundpos;
|
||||
} wave;
|
||||
|
||||
static SDL_AudioDeviceID device;
|
||||
static SDL_AudioStream *stream;
|
||||
|
||||
static void SDLCALL
|
||||
fillerup(SDL_AudioStream *stream, int len, void *unused)
|
||||
{
|
||||
Uint8 *waveptr;
|
||||
int waveleft;
|
||||
|
||||
/*SDL_Log("CALLBACK WANTS %d MORE BYTES!", len);*/
|
||||
|
||||
/* Set up the pointers */
|
||||
waveptr = wave.sound + wave.soundpos;
|
||||
waveleft = wave.soundlen - wave.soundpos;
|
||||
|
||||
/* Go! */
|
||||
while (waveleft <= len) {
|
||||
SDL_PutAudioStreamData(stream, waveptr, waveleft);
|
||||
len -= waveleft;
|
||||
waveptr = wave.sound;
|
||||
waveleft = wave.soundlen;
|
||||
wave.soundpos = 0;
|
||||
}
|
||||
SDL_PutAudioStreamData(stream, waveptr, len);
|
||||
wave.soundpos += len;
|
||||
}
|
||||
|
||||
/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
|
||||
static void
|
||||
quit(int rc)
|
||||
|
@ -78,6 +103,8 @@ open_audio(void)
|
|||
SDL_free(wave.sound);
|
||||
quit(2);
|
||||
}
|
||||
|
||||
SDL_SetAudioStreamGetCallback(stream, fillerup, NULL);
|
||||
}
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
|
@ -91,12 +118,6 @@ static void reopen_audio(void)
|
|||
|
||||
static int done = 0;
|
||||
|
||||
static void fillerup(void)
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(stream) < (wave.soundlen / 2)) {
|
||||
SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
|
@ -192,7 +213,6 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
}
|
||||
|
||||
fillerup();
|
||||
SDL_Delay(100);
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue