Skip to content

Commit f3d2ebe

Browse files
committed
audio: Added SDL_SetAudioIterationCallbacks().
1 parent 3b91017 commit f3d2ebe

File tree

6 files changed

+127
-2
lines changed

6 files changed

+127
-2
lines changed

include/SDL3/SDL_audio.h

+78
Original file line numberDiff line numberDiff line change
@@ -1887,6 +1887,84 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream)
18871887
*/
18881888
extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata);
18891889

1890+
/**
1891+
* A callback that fires around an audio device's processing work.
1892+
*
1893+
* This callback fires when a logical audio device is about to start
1894+
* accessing its bound audio streams, and fires again when it has
1895+
* finished accessing them. It covers the range of one "iteration" of
1896+
* the audio device.
1897+
*
1898+
* It can be useful to use this callback to update state that must
1899+
* apply to all bound audio streams atomically: to make sure state
1900+
* changes don't happen while half of the streams are already processed
1901+
* for the latest audio buffer.
1902+
*
1903+
* This callback should run as quickly as possible and not block for any
1904+
* significant time, as this callback delays submission of data to the audio
1905+
* device, which can cause audio playback problems. This callback delays all
1906+
* audio processing across a single physical audio device: all its logical
1907+
* devices and all bound audio streams. Use it carefully.
1908+
*
1909+
* \param userdata a pointer provided by the app through
1910+
* SDL_SetAudioPostmixCallback, for its own use.
1911+
* \param devid the audio device this callback is running for.
1912+
* \param start true if this is the start of the iteration, false if the end.
1913+
*
1914+
* \threadsafety This will run from a background thread owned by SDL. The
1915+
* application is responsible for locking resources the callback
1916+
* touches that need to be protected.
1917+
*
1918+
* \since This datatype is available since SDL 3.4.0.
1919+
*
1920+
* \sa SDL_SetAudioIterationCallbacks
1921+
*/
1922+
typedef void (SDLCALL *SDL_AudioIterationCallback)(void *userdata, SDL_AudioDeviceID devid, bool start);
1923+
1924+
/**
1925+
* Set callbacks that fire around a new iteration of audio device processing.
1926+
*
1927+
* Two callbacks are provided here: one that runs when a device is about to
1928+
* process its bound audio streams, and another that runs when the device has
1929+
* finished processing them.
1930+
*
1931+
* These callbacks can run at any time, and from any thread; if you need to
1932+
* serialize access to your app's data, you should provide and use a mutex or
1933+
* other synchronization device.
1934+
*
1935+
* Generally these callbacks are used to apply state that applies to multiple
1936+
* bound audio streams, with a guarantee that the audio device's thread isn't
1937+
* halfway through processing them. Generally a finer-grained lock through
1938+
* SDL_LockAudioStream() is more appropriate.
1939+
*
1940+
* The callbacks are extremely time-sensitive; the callback should do the
1941+
* least amount of work possible and return as quickly as it can. The longer
1942+
* the callback runs, the higher the risk of audio dropouts or other problems.
1943+
*
1944+
* This function will block until the audio device is in between iterations,
1945+
* so any existing callback that might be running will finish before this
1946+
* function sets the new callback and returns.
1947+
*
1948+
* Physical devices do not accept these callbacks, only logical devices
1949+
* created through SDL_OpenAudioDevice() can be.
1950+
*
1951+
* Setting a NULL callback function disables any previously-set callback.
1952+
* Either callback may be NULL, and the same callback is permitted to be used
1953+
* for both.
1954+
*
1955+
* \param devid the ID of an opened audio device.
1956+
* \param start a callback function to be called at the start of an iteration. Can be NULL.
1957+
* \param end a callback function to be called at the end of an iteration. Can be NULL.
1958+
* \param userdata app-controlled pointer passed to callback. Can be NULL.
1959+
* \returns true on success or false on failure; call SDL_GetError() for more
1960+
* information.
1961+
*
1962+
* \threadsafety It is safe to call this function from any thread.
1963+
*
1964+
* \since This function is available since SDL 3.4.0.
1965+
*/
1966+
extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioIterationCallbacks(SDL_AudioDeviceID devid, SDL_AudioIterationCallback start, SDL_AudioIterationCallback end, void *userdata);
1967+
18901968
/**
18911969
* A callback that fires when data is about to be fed to an audio device.
18921970
*

src/audio/SDL_audio.c

+39-2
Original file line numberDiff line numberDiff line change
@@ -1147,7 +1147,20 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
11471147
// We should have updated this elsewhere if the format changed!
11481148
SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, NULL, NULL));
11491149

1150-
const int br = SDL_GetAtomicInt(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain);
1150+
int br = 0;
1151+
1152+
if (!SDL_GetAtomicInt(&logdev->paused)) {
1153+
if (logdev->iteration_start) {
1154+
logdev->iteration_start(logdev->iteration_userdata, logdev->instance_id, true);
1155+
}
1156+
1157+
br = SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain);
1158+
1159+
if (logdev->iteration_end) {
1160+
logdev->iteration_end(logdev->iteration_userdata, logdev->instance_id, false);
1161+
}
1162+
}
1163+
11511164
if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow.
11521165
failed = true;
11531166
SDL_memset(device_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die.
@@ -1185,6 +1198,10 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
11851198
SDL_memset(mix_buffer, '\0', work_buffer_size); // start with silence.
11861199
}
11871200

1201+
if (logdev->iteration_start) {
1202+
logdev->iteration_start(logdev->iteration_userdata, logdev->instance_id, true);
1203+
}
1204+
11881205
for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) {
11891206
// We should have updated this elsewhere if the format changed!
11901207
SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, NULL, NULL));
@@ -1207,6 +1224,10 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
12071224
}
12081225
}
12091226

1227+
if (logdev->iteration_end) {
1228+
logdev->iteration_end(logdev->iteration_userdata, logdev->instance_id, false);
1229+
}
1230+
12101231
if (postmix) {
12111232
SDL_assert(mix_buffer == device->postmix_buffer);
12121233
postmix(logdev->postmix_userdata, &outspec, mix_buffer, work_buffer_size);
@@ -1889,8 +1910,9 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba
18891910
{
18901911
SDL_AudioDevice *device = NULL;
18911912
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1892-
bool result = true;
1913+
bool result = false;
18931914
if (logdev) {
1915+
result = true;
18941916
if (callback && !device->postmix_buffer) {
18951917
device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size);
18961918
if (!device->postmix_buffer) {
@@ -1909,6 +1931,21 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba
19091931
return result;
19101932
}
19111933

1934+
bool SDL_SetAudioIterationCallbacks(SDL_AudioDeviceID devid, SDL_AudioIterationCallback iter_start, SDL_AudioIterationCallback iter_end, void *userdata)
1935+
{
1936+
SDL_AudioDevice *device = NULL;
1937+
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1938+
bool result = false;
1939+
if (logdev) {
1940+
logdev->iteration_start = iter_start;
1941+
logdev->iteration_end = iter_end;
1942+
logdev->iteration_userdata = userdata;
1943+
result = true;
1944+
}
1945+
ReleaseAudioDevice(device);
1946+
return result;
1947+
}
1948+
19121949
bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams)
19131950
{
19141951
const bool islogical = !(devid & (1<<1));

src/audio/SDL_sysaudio.h

+7
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,13 @@ struct SDL_LogicalAudioDevice
264264
// true if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc).
265265
bool simplified;
266266

267+
// If non-NULL, callback into the app that alerts it to start/end of device iteration.
268+
SDL_AudioIterationCallback iteration_start;
269+
SDL_AudioIterationCallback iteration_end;
270+
271+
// App-supplied pointer for iteration callbacks.
272+
void *iteration_userdata;
273+
267274
// If non-NULL, callback into the app that lets them access the final postmix buffer.
268275
SDL_AudioPostmixCallback postmix;
269276

src/dynapi/SDL_dynapi.sym

+1
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,7 @@ SDL3_0.0.0 {
12491249
SDL_SetRenderTextureAddressMode;
12501250
SDL_GetRenderTextureAddressMode;
12511251
SDL_GetGPUDeviceProperties;
1252+
SDL_SetAudioIterationCallbacks;
12521253
# extra symbols go here (don't modify this line)
12531254
local: *;
12541255
};

src/dynapi/SDL_dynapi_overrides.h

+1
Original file line numberDiff line numberDiff line change
@@ -1274,3 +1274,4 @@
12741274
#define SDL_SetRenderTextureAddressMode SDL_SetRenderTextureAddressMode_REAL
12751275
#define SDL_GetRenderTextureAddressMode SDL_GetRenderTextureAddressMode_REAL
12761276
#define SDL_GetGPUDeviceProperties SDL_GetGPUDeviceProperties_REAL
1277+
#define SDL_SetAudioIterationCallbacks SDL_SetAudioIterationCallbacks_REAL

src/dynapi/SDL_dynapi_procs.h

+1
Original file line numberDiff line numberDiff line change
@@ -1282,3 +1282,4 @@ SDL_DYNAPI_PROC(float,SDL_GetWindowProgressValue,(SDL_Window *a),(a),return)
12821282
SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode b,SDL_TextureAddressMode c),(a,b,c),return)
12831283
SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return)
12841284
SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGPUDeviceProperties,(SDL_GPUDevice *a),(a),return)
1285+
SDL_DYNAPI_PROC(bool,SDL_SetAudioIterationCallbacks,(SDL_AudioDeviceID a,SDL_AudioIterationCallback b,SDL_AudioIterationCallback c,void *d),(a,b,c,d),return)

0 commit comments

Comments
 (0)