Skip to content

Commit

Permalink
audio: First shot at the SDL3 audio subsystem redesign!
Browse files Browse the repository at this point in the history
This is a work in progress! (and this commit will probably get
force-pushed over at some point).
  • Loading branch information
icculus committed May 29, 2023
1 parent 1da5b2e commit a0d369e
Show file tree
Hide file tree
Showing 19 changed files with 1,828 additions and 2,323 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,12 @@ option_string(SDL_VENDOR_INFO "Vendor name and/or version to add to SDL_REV
set_option(SDL_CCACHE "Use Ccache to speed up build" OFF)
set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF)

set(SDL_OSS OFF)
set(SDL_ALSA OFF)
set(SDL_JACK OFF)
set(SDL_PIPEWIRE OFF)
set(SDL_SNDIO OFF)

option(SDL_WERROR "Enable -Werror" OFF)

option(SDL_SHARED "Build a shared version of the library" ${SDL_SHARED_ENABLED_BY_DEFAULT})
Expand Down
105 changes: 100 additions & 5 deletions docs/README-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,98 @@ The following structures have been renamed:

## SDL_audio.h

SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSytem() and SDL_QuitSubSytem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint.
The audio subsystem in SDL3 is dramatically different than SDL2. There is no longer an audio callback; instead you bind SDL_AudioStreams to devices.

SDL_PauseAudioDevice() is only used to pause audio playback. Use SDL_PlayAudioDevice() to start playing audio.
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.

In SDL2, you might have done something like this to play audio:

```c
void SDLCALL MyAudioCallback(void *userdata, Uint8 * stream, int len)
{
/* calculate a little more audio here, maybe using `userdata`, write it to `stream` */
}

/* ...somewhere near startup... */
SDL_AudioSpec my_desired_audio_format;
SDL_zero(my_desired_audio_format);
my_desired_audio_format.format = AUDIO_S16;
my_desired_audio_format.channels = 2;
my_desired_audio_format.freq = 44100;
my_desired_audio_format.samples = 1024;
my_desired_audio_format.callback = MyAudioCallback;
my_desired_audio_format.userdata = &my_audio_callback_user_data;
SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(NULL, 0, &my_desired_audio_format, NULL, 0);
SDL_PauseAudioDevice(my_audio_device, 0);
```
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);
/* ...in your main loop... */
/* calculate a little more audio into `buf`, add it to `stream` */
SDL_PutAudioStreamData(stream, buf, buflen);
```

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.

SDL_AudioDeviceID no longer represents an open audio device's handle, it's now the device's instance ID that the device owns as long as it exists on the system. The separation between device instances and device indexes is gone.

Devices are opened by device instance ID, and a new handle is not generated by the open operation; instead, opens of the same device instance are reference counted. This allows any device to be opened multiple times, possibly by unrelated pieces of code.

Devices are not opened by an arbitrary string name anymore, but by device instance ID (or 0 to request a reasonable default, like a NULL string in SDL2). In SDL2, the string was used to open both a standard list of system devices, but also allowed for arbitrary devices, such as hostnames of network sound servers. In SDL3, many of the backends that supported arbitrary device names are obsolete and have been removed; of those that remain, arbitrary devices will be opened with a device ID of 0 and an SDL_hint, so specific end-users can set an environment variable to fit their needs and apps don't have to concern themselves with it.

Many functions that would accept a device index and an `iscapture` parameter now just take an SDL_AudioDeviceID, as they are unique across all devices, instead of separate indices into output and capture device lists.

Rather than iterating over audio devices using a device index, there is a new function, SDL_GetAudioDevices(), to get the current list of devices, and new functions to get information about devices from their instance ID:

```c
{
if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) {
int i, num_devices;
SDL_AudioDeviceID *devices = SDL_GetAudioDevices(/*iscapture=*/SDL_FALSE, &num_devices);
if (devices) {
for (i = 0; i < num_devices; ++i) {
SDL_AudioDeviceID instance_id = devices[i];
char *name = SDL_GetAudioDeviceName(instance_id);
SDL_Log("AudioDevice %" SDL_PRIu32 ": %s\n", instance_id, name);
SDL_free(name);
}
SDL_free(devices);
}
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
}
```

SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. Internally, SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread.

SDL_PauseAudioDevice() has been removed; unbinding an audio stream from a device with SDL_UnbindAudioStream() will leave the stream still containing any unconsumed data, effectively pausing it until rebound with SDL_BindAudioStream() again. Devices act like they are "paused" after open, like SDL2, until a stream is bound to it.

SDL_GetAudioDeviceStatus() has been removed; there is no more concept of "pausing" a device, just whether streams are bound, so please keep track of your audio streams!

SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAudioSize() have been removed; an SDL_AudioStream bound to a device provides the exact same functionality.

APIs that use channel counts used to use a Uint8 for the channel; now they use int.

SDL_AudioSpec has been removed; things that used it have simply started taking separate arguments for format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's `silence` field. The other SDL_AudioSpec fields aren't relevant anymore.

SDL_GetAudioDeviceSpec() is removed; use SDL_GetAudioDeviceFormat() instead.

SDL_MixAudio() has been removed, as it relied on legacy SDL 1.2 quirks; SDL_MixAudioFormat() remains and offers the same functionality.

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.

SDL_FreeWAV has been removed and calls can be replaced with SDL_free.

SDL_AudioCVT interface is removed, SDL_AudioStream interface or SDL_ConvertAudioSamples() helper function can be used.
SDL_AudioCVT interface has been removed, the SDL_AudioStream interface (for audio supplied in pieces) or the new SDL_ConvertAudioSamples() function (for converting a complete audio buffer in one call) can be used instead.

Code that used to look like this:
```c
Expand Down Expand Up @@ -103,6 +188,8 @@ If you need to convert U16 audio data to a still-supported format at runtime, th
}
```
All remaining `AUDIO_*` symbols have been renamed to `SDL_AUDIO_*` for API consistency, but othewise are identical in value and usage.
In SDL2, SDL_AudioStream would convert/resample audio data during input (via SDL_AudioStreamPut). In SDL3, it does this work when requesting audio (via SDL_GetAudioStreamData, which would have been SDL_AudioStreamPut in SDL2. The way you use an AudioStream is roughly the same, just be aware that the workload moved to a different phase.
In SDL2, SDL_AudioStreamAvailable() returns 0 if passed a NULL stream. In SDL3, the equivalent SDL_GetAudioStreamAvailable() call returns -1 and sets an error string, which matches other audiostream APIs' behavior.
Expand All @@ -118,17 +205,25 @@ The following functions have been renamed:
The following functions have been removed:
* SDL_GetNumAudioDevices()
* SDL_GetAudioDeviceSpec()
* SDL_ConvertAudio()
* SDL_BuildAudioCVT()
* SDL_OpenAudio()
* SDL_CloseAudio()
* SDL_PauseAudio()
* SDL_PauseAudioDevice
* SDL_GetAudioStatus()
* SDL_GetAudioDeviceStatus()
* SDL_LockAudio()
* SDL_LockAudioDevice()
* SDL_UnlockAudio()
* SDL_UnlockAudioDevice()
* SDL_MixAudio()
Use the SDL_AudioDevice functions instead.
* SDL_QueueAudio()
* SDL_DequeueAudio()
* SDL_ClearAudioQueue()
* SDL_GetQueuedAudioSize()
The following symbols have been renamed:
* AUDIO_F32 => SDL_AUDIO_F32
Expand Down
Loading

0 comments on commit a0d369e

Please sign in to comment.