Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions include/SDL3/SDL_audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,21 @@ extern SDL_DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDevic
*/
extern SDL_DECLSPEC bool SDLCALL SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid);

/**
* Get the physical audio device associated with a logical audio device.
*
* If `devid` is already a physical device, this function returns `devid`.
* If `devid` is an invalid device, it returns 0.
*
* \param devid the device ID to query.
* \returns the physical device ID, or 0 on error.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.5.0.
*/
extern SDL_DECLSPEC SDL_AudioDeviceID SDLCALL SDL_GetPhysicalAudioDevice(SDL_AudioDeviceID devid);

/**
* Determine if an audio device is a playback device (instead of recording).
*
Expand Down Expand Up @@ -862,8 +877,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_AudioDevicePaused(SDL_AudioDeviceID devid);
*
* Audio devices default to a gain of 1.0f (no change in output).
*
* Physical devices may not have their gain changed, only logical devices, and
* this function will always return -1.0f when used on physical devices.
* Physical device gain support depends on the backend. -1.0f will be returned
* if it is not supported.
*
* \param devid the audio device to query.
* \returns the gain of the device or -1.0f on failure; call SDL_GetError()
Expand All @@ -885,18 +900,16 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid
*
* Audio devices default to a gain of 1.0f (no change in output).
*
* Physical devices may not have their gain changed, only logical devices, and
* this function will always return false when used on physical devices. While
* it might seem attractive to adjust several logical devices at once in this
* way, it would allow an app or library to interfere with another portion of
* the program's otherwise-isolated devices.
*
* This is applied, along with any per-audiostream gain, during playback to
* the hardware, and can be continuously changed to create various effects. On
* recording devices, this will adjust the gain before passing the data into
* an audiostream; that recording audiostream can then adjust its gain further
* when outputting the data elsewhere, if it likes, but that second gain is
* not applied until the data leaves the audiostream again.
* Support for gain changes of physical devices depends on the backend. It will
* return false if it is not supported; likely you should fall back to changing
* the logical device gain in that case.
*
* For logical devices this is applied, along with any per-audiostream gain,
* during playback to the hardware, and can be continuously changed to create
* various effects. On recording devices, this will adjust the gain before
* passing the data into an audiostream; that recording audiostream can then
* adjust its gain further when outputting the data elsewhere, if it likes, but
* that second gain is not applied until the data leaves the audiostream again.
*
* \param devid the audio device on which to change gain.
* \param gain the gain. 1.0f is no change, 0.0f is silence.
Expand Down
3 changes: 2 additions & 1 deletion include/SDL3/SDL_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ typedef enum SDL_EventType
SDL_EVENT_AUDIO_DEVICE_ADDED = 0x1100, /**< A new audio device is available */
SDL_EVENT_AUDIO_DEVICE_REMOVED, /**< An audio device has been removed. */
SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED, /**< An audio device's format has been changed by the system. */
SDL_EVENT_AUDIO_DEVICE_GAIN_CHANGED, /**< An audio device's gain has been changed by the system. */

/* Sensor events */
SDL_EVENT_SENSOR_UPDATE = 0x1200, /**< A sensor was updated */
Expand Down Expand Up @@ -741,7 +742,7 @@ typedef struct SDL_GamepadCapSenseEvent
*/
typedef struct SDL_AudioDeviceEvent
{
SDL_EventType type; /**< SDL_EVENT_AUDIO_DEVICE_ADDED, or SDL_EVENT_AUDIO_DEVICE_REMOVED, or SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED */
SDL_EventType type; /**< SDL_EVENT_AUDIO_DEVICE_ADDED, or SDL_EVENT_AUDIO_DEVICE_REMOVED, or SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED, or SDL_EVENT_AUDIO_DEVICE_GAIN_CHANGED */
Uint32 reserved;
Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */
SDL_AudioDeviceID which; /**< SDL_AudioDeviceID for the device being added or removed or changing */
Expand Down
115 changes: 106 additions & 9 deletions src/audio/SDL_audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,20 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI
return NULL;
}

SDL_AudioDeviceID SDL_GetPhysicalAudioDevice(SDL_AudioDeviceID devid)
{
if (SDL_IsAudioDevicePhysical(devid)) {
return devid;
}

SDL_AudioDeviceID result = 0;
SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
if (device) {
result = device->instance_id;
ReleaseAudioDevice(device);
}
return result;
}
// this assumes you hold the _physical_ device lock for this logical device! This will not unlock the lock or close the physical device!
// It also will not unref the physical device, since we might be shutting down; SDL_CloseAudioDevice handles the unref.
static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev)
Expand Down Expand Up @@ -820,6 +834,56 @@ static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata)
UnrefPhysicalAudioDevice(device);
}


static void SDLCALL SDL_AudioDeviceGainChanged_OnMainThread(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_assert(device != NULL);

SDL_PendingAudioDeviceEvent pending;
pending.next = NULL;
SDL_PendingAudioDeviceEvent *pending_tail = &pending;

ObtainPhysicalAudioDeviceObj(device);

const SDL_AudioDeviceID devid = device->instance_id;

if (!devid) {
ReleaseAudioDevice(device);
UnrefPhysicalAudioDevice(device);
return;
}

SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent));
if (p) {
p->type = SDL_EVENT_AUDIO_DEVICE_GAIN_CHANGED;
p->devid = devid;
p->next = NULL;
pending_tail->next = p;
pending_tail = p;
}

ReleaseAudioDevice(device);

if (pending.next) {
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
SDL_PendingAudioDeviceEvent *tail = current_audio.pending_events_tail;
SDL_assert(tail->next == NULL);
tail->next = pending.next;
current_audio.pending_events_tail = pending_tail;
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
UnrefPhysicalAudioDevice(device);
}

void SDL_AudioDeviceGainChanged(SDL_AudioDevice *device)
{
if (device) {
RefPhysicalAudioDevice(device);
SDL_RunOnMainThread(SDL_AudioDeviceGainChanged_OnMainThread, device, false);
}
}

void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
{
// lots of risk of various audio backends deadlocking because they're calling
Expand All @@ -844,6 +908,8 @@ static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */
static void SDL_AudioDeinitializeStart_Default(void) { /* no-op. */ }
static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ }
static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ }
static bool SDL_AudioSetDeviceGain_Default(SDL_AudioDevice *device, float gain) { return false; /* no-op. */ }
static float SDL_AudioGetDeviceGain_Default(SDL_AudioDevice *device) { return -1.0f; /* no-op. */ }

static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device)
{
Expand Down Expand Up @@ -897,6 +963,8 @@ static void CompleteAudioEntryPoints(void)
FILL_STUB(FreeDeviceHandle);
FILL_STUB(DeinitializeStart);
FILL_STUB(Deinitialize);
FILL_STUB(SetDeviceGain);
FILL_STUB(GetDeviceGain);
#undef FILL_STUB
}

Expand Down Expand Up @@ -1960,9 +2028,27 @@ bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid)

float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid)
{
float result = -1.0f;
SDL_AudioDevice *device = NULL;
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
const float result = logdev ? logdev->gain : -1.0f;

if (!SDL_IsAudioDeviceLogical(devid)) {
if (!current_audio.impl.HasBackendVolumeControl) {
return result;
}
device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
if (!device) {
return result;
}
float backend_gain = current_audio.impl.GetDeviceGain(device);
if (backend_gain >= 0.0f) {
result = backend_gain;
}
} else {
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
if (logdev) {
result = logdev->gain;
}
}
ReleaseAudioDevice(device);
return result;
}
Expand All @@ -1972,14 +2058,25 @@ bool SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain)
CHECK_PARAM(gain < 0.0f) {
return SDL_InvalidParamError("gain");
}

SDL_AudioDevice *device = NULL;
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
bool result = false;
if (logdev) {
logdev->gain = gain;
UpdateAudioStreamFormatsPhysical(device);
result = true;
SDL_AudioDevice *device = NULL;

if (!SDL_IsAudioDeviceLogical(devid)) {
if (!current_audio.impl.HasBackendVolumeControl) {
return result;
}
device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
if (!device) {
return result;
}
result = current_audio.impl.SetDeviceGain(device, gain);
} else {
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
if (logdev) {
logdev->gain = gain;
result = true;
UpdateAudioStreamFormatsPhysical(device);
}
}
ReleaseAudioDevice(device);
return result;
Expand Down
6 changes: 6 additions & 0 deletions src/audio/SDL_sysaudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ extern SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, con
This can happen due to i/o errors, or a device being unplugged, etc. */
extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);

// Backends can notify applications of physical device gain changes
extern void SDL_AudioDeviceGainChanged(SDL_AudioDevice *device);

// Backends should call this if the system default device changes.
extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);

Expand Down Expand Up @@ -162,12 +165,15 @@ typedef struct SDL_AudioDriverImpl
void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice()
void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection.
void (*Deinitialize)(void);
bool (*SetDeviceGain)(SDL_AudioDevice *device, float gain); // Tell the backend that a device's gain has changed
float (*GetDeviceGain)(SDL_AudioDevice *device); // Retrieve the current hardware/server gain

// Some flags to push duplicate code into the core and reduce #ifdefs.
bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore.
bool HasRecordingSupport;
bool OnlyHasDefaultPlaybackDevice;
bool OnlyHasDefaultRecordingDevice; // !!! FIXME: is there ever a time where you'd have a default playback and not a default recording (or vice versa)?
bool HasBackendVolumeControl;
} SDL_AudioDriverImpl;


Expand Down
Loading
Loading