audio: Massive reworking on thread locking.
This cleans up a ton of race conditions, and starts moving towards something we can use with Clang's -Wthread-safety (but that has a ways to go still).main
parent
40fb76196c
commit
0e614d9179
|
@ -289,6 +289,134 @@ static SDL_AudioDeviceID AssignAudioDeviceInstanceId(SDL_bool iscapture, SDL_boo
|
||||||
return instance_id;
|
return instance_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE
|
||||||
|
{
|
||||||
|
if (device) {
|
||||||
|
RefPhysicalAudioDevice(device);
|
||||||
|
SDL_LockMutex(device->lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReleaseAudioDevice(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_RELEASE
|
||||||
|
{
|
||||||
|
if (device) {
|
||||||
|
SDL_UnlockMutex(device->lock);
|
||||||
|
UnrefPhysicalAudioDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If found, this locks _the physical device_ this logical device is associated with, before returning.
|
||||||
|
static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_AudioDevice **device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_ACQUIRE
|
||||||
|
{
|
||||||
|
SDL_assert(device != NULL);
|
||||||
|
|
||||||
|
*device = NULL;
|
||||||
|
|
||||||
|
if (!SDL_GetCurrentAudioDriver()) {
|
||||||
|
SDL_SetError("Audio subsystem is not initialized");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogicalAudioDevice *logdev = NULL;
|
||||||
|
|
||||||
|
// bit #1 of devid is set for physical devices and unset for logical.
|
||||||
|
const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE;
|
||||||
|
if (islogical) { // don't bother looking if it's not a logical device id value.
|
||||||
|
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
||||||
|
SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev);
|
||||||
|
if (logdev) {
|
||||||
|
*device = logdev->physical_device;
|
||||||
|
RefPhysicalAudioDevice(*device); // reference it, in case the logical device migrates to a new default.
|
||||||
|
}
|
||||||
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!logdev) {
|
||||||
|
SDL_SetError("Invalid audio device instance ID");
|
||||||
|
} else {
|
||||||
|
SDL_assert(*device != NULL);
|
||||||
|
SDL_LockMutex((*device)->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logdev;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* this finds the physical device associated with `devid` and locks it for use.
|
||||||
|
Note that a logical device instance id will return its associated physical device! */
|
||||||
|
static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // !!! FIXME: SDL_ACQUIRE
|
||||||
|
{
|
||||||
|
SDL_AudioDevice *device = NULL;
|
||||||
|
|
||||||
|
// bit #1 of devid is set for physical devices and unset for logical.
|
||||||
|
const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE;
|
||||||
|
if (islogical) {
|
||||||
|
ObtainLogicalAudioDevice(devid, &device);
|
||||||
|
} else if (!SDL_GetCurrentAudioDriver()) { // (the `islogical` path, above, checks this in ObtainLogicalAudioDevice.)
|
||||||
|
SDL_SetError("Audio subsystem is not initialized");
|
||||||
|
} else {
|
||||||
|
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
||||||
|
SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device);
|
||||||
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
SDL_SetError("Invalid audio device instance ID");
|
||||||
|
} else {
|
||||||
|
ObtainPhysicalAudioDeviceObj(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceID devid) // !!! FIXME: SDL_ACQUIRE
|
||||||
|
{
|
||||||
|
const SDL_bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) || (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE)) ? SDL_TRUE : SDL_FALSE;
|
||||||
|
if (!wants_default) {
|
||||||
|
return ObtainPhysicalAudioDevice(devid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_AudioDeviceID orig_devid = devid;
|
||||||
|
|
||||||
|
while (SDL_TRUE) {
|
||||||
|
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
||||||
|
if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) {
|
||||||
|
devid = current_audio.default_output_device_id;
|
||||||
|
} else if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) {
|
||||||
|
devid = current_audio.default_capture_device_id;
|
||||||
|
}
|
||||||
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
|
|
||||||
|
if (devid == 0) {
|
||||||
|
SDL_SetError("No default audio device available");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid);
|
||||||
|
if (!device) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the default didn't change while we were waiting for the lock...
|
||||||
|
SDL_bool got_it = SDL_FALSE;
|
||||||
|
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
||||||
|
if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) && (devid == current_audio.default_output_device_id)) {
|
||||||
|
got_it = SDL_TRUE;
|
||||||
|
} else if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) && (devid == current_audio.default_capture_device_id)) {
|
||||||
|
got_it = SDL_TRUE;
|
||||||
|
}
|
||||||
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
|
|
||||||
|
if (got_it) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseAudioDevice(device); // let it go and try again.
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// this assumes you hold the _physical_ device lock for this logical device! This will not unlock the lock or close the physical device!
|
// 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.
|
// 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)
|
static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev)
|
||||||
|
@ -334,11 +462,11 @@ static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy any logical devices that still exist...
|
// Destroy any logical devices that still exist...
|
||||||
SDL_LockMutex(device->lock);
|
SDL_LockMutex(device->lock); // don't use ObtainPhysicalAudioDeviceObj because we don't want to change refcounts while destroying.
|
||||||
while (device->logical_devices != NULL) {
|
while (device->logical_devices != NULL) {
|
||||||
DestroyLogicalAudioDevice(device->logical_devices);
|
DestroyLogicalAudioDevice(device->logical_devices);
|
||||||
}
|
}
|
||||||
SDL_UnlockMutex(device->lock);
|
SDL_UnlockMutex(device->lock); // don't use ReleaseAudioDevice because we don't want to change refcounts while destroying.
|
||||||
|
|
||||||
// it's safe to not hold the lock for this (we can't anyhow, or the audio thread won't quit), because we shouldn't be in the device list at this point.
|
// it's safe to not hold the lock for this (we can't anyhow, or the audio thread won't quit), because we shouldn't be in the device list at this point.
|
||||||
ClosePhysicalAudioDevice(device);
|
ClosePhysicalAudioDevice(device);
|
||||||
|
@ -483,13 +611,23 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LockMutex(device->lock);
|
// Save off removal info in a list so we can send events for each, next
|
||||||
|
// time the event queue pumps, in case something tries to close a device
|
||||||
|
// from an event filter, as this would risk deadlocks and other disasters
|
||||||
|
// if done from the device thread.
|
||||||
|
SDL_PendingAudioDeviceEvent pending;
|
||||||
|
pending.next = NULL;
|
||||||
|
SDL_PendingAudioDeviceEvent *pending_tail = &pending;
|
||||||
|
|
||||||
if (!SDL_AtomicCAS(&device->zombie, 0, 1)) {
|
ObtainPhysicalAudioDeviceObj(device);
|
||||||
SDL_UnlockMutex(device->lock);
|
|
||||||
return; // already disconnected this device, don't do it twice.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
||||||
|
const SDL_AudioDeviceID devid = device->instance_id;
|
||||||
|
const SDL_bool is_default_device = ((devid == current_audio.default_output_device_id) || (devid == current_audio.default_capture_device_id)) ? SDL_TRUE : SDL_FALSE;
|
||||||
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
|
|
||||||
|
const SDL_bool first_disconnect = SDL_AtomicCAS(&device->zombie, 0, 1);
|
||||||
|
if (first_disconnect) { // if already disconnected this device, don't do it twice.
|
||||||
// Swap in "Zombie" versions of the usual platform interfaces, so the device will keep
|
// Swap in "Zombie" versions of the usual platform interfaces, so the device will keep
|
||||||
// making progress until the app closes it. Otherwise, streams might continue to
|
// making progress until the app closes it. Otherwise, streams might continue to
|
||||||
// accumulate waste data that never drains, apps that depend on audio callbacks to
|
// accumulate waste data that never drains, apps that depend on audio callbacks to
|
||||||
|
@ -501,17 +639,6 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
|
||||||
device->CaptureFromDevice = ZombieCaptureFromDevice;
|
device->CaptureFromDevice = ZombieCaptureFromDevice;
|
||||||
device->FlushCapture = ZombieFlushCapture;
|
device->FlushCapture = ZombieFlushCapture;
|
||||||
|
|
||||||
const SDL_AudioDeviceID devid = device->instance_id;
|
|
||||||
const SDL_bool is_default_device = ((devid == current_audio.default_output_device_id) || (devid == current_audio.default_capture_device_id)) ? SDL_TRUE : SDL_FALSE;
|
|
||||||
|
|
||||||
// Save off removal info in a list so we can send events for each, next
|
|
||||||
// time the event queue pumps, in case something tries to close a device
|
|
||||||
// from an event filter, as this would risk deadlocks and other disasters
|
|
||||||
// if done from the device thread.
|
|
||||||
SDL_PendingAudioDeviceEvent pending;
|
|
||||||
pending.next = NULL;
|
|
||||||
SDL_PendingAudioDeviceEvent *pending_tail = &pending;
|
|
||||||
|
|
||||||
// on default devices, dump any logical devices that explicitly opened this device. Things that opened the system default can stay.
|
// on default devices, dump any logical devices that explicitly opened this device. Things that opened the system default can stay.
|
||||||
// on non-default devices, dump everything.
|
// on non-default devices, dump everything.
|
||||||
// (by "dump" we mean send a REMOVED event; the zombie will keep consuming audio data for these logical devices until explicitly closed.)
|
// (by "dump" we mean send a REMOVED event; the zombie will keep consuming audio data for these logical devices until explicitly closed.)
|
||||||
|
@ -536,9 +663,11 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
|
||||||
pending_tail->next = p;
|
pending_tail->next = p;
|
||||||
pending_tail = p;
|
pending_tail = p;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SDL_UnlockMutex(device->lock);
|
ReleaseAudioDevice(device);
|
||||||
|
|
||||||
|
if (first_disconnect) {
|
||||||
if (pending.next) { // NULL if event is disabled or disaster struck.
|
if (pending.next) { // NULL if event is disabled or disaster struck.
|
||||||
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
|
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
|
||||||
SDL_assert(current_audio.pending_events_tail != NULL);
|
SDL_assert(current_audio.pending_events_tail != NULL);
|
||||||
|
@ -548,9 +677,6 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
|
||||||
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this a non-default device? We can unref it now.
|
|
||||||
// Otherwise, we'll unref it when a new default device is chosen.
|
|
||||||
if (!is_default_device) {
|
|
||||||
UnrefPhysicalAudioDevice(device);
|
UnrefPhysicalAudioDevice(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -622,9 +748,10 @@ static void CompleteAudioEntryPoints(void)
|
||||||
#undef FILL_STUB
|
#undef FILL_STUB
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_AudioDeviceID GetFirstAddedAudioDeviceID(const SDL_bool iscapture)
|
static SDL_AudioDevice *GetFirstAddedAudioDevice(const SDL_bool iscapture)
|
||||||
{
|
{
|
||||||
SDL_AudioDeviceID retval = (SDL_AudioDeviceID) SDL_AUDIO_DEVICE_DEFAULT_OUTPUT; // According to AssignAudioDeviceInstanceId, nothing can have a value this large.
|
SDL_AudioDeviceID highest = (SDL_AudioDeviceID) SDL_AUDIO_DEVICE_DEFAULT_OUTPUT; // According to AssignAudioDeviceInstanceId, nothing can have a value this large.
|
||||||
|
SDL_AudioDevice *retval = NULL;
|
||||||
|
|
||||||
// (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.)
|
// (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.)
|
||||||
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
||||||
|
@ -633,16 +760,17 @@ static SDL_AudioDeviceID GetFirstAddedAudioDeviceID(const SDL_bool iscapture)
|
||||||
const void *value;
|
const void *value;
|
||||||
void *iter = NULL;
|
void *iter = NULL;
|
||||||
while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) {
|
while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) {
|
||||||
const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) value;
|
const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key;
|
||||||
// bit #1 of devid is set for physical devices and unset for logical.
|
// bit #1 of devid is set for physical devices and unset for logical.
|
||||||
const SDL_bool isphysical = (devid & (1<<1)) ? SDL_TRUE : SDL_FALSE;
|
const SDL_bool isphysical = (devid & (1<<1)) ? SDL_TRUE : SDL_FALSE;
|
||||||
if (isphysical && (devid < retval)) {
|
if (isphysical && (devid < highest)) {
|
||||||
retval = devid;
|
highest = devid;
|
||||||
|
retval = (SDL_AudioDevice *) value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
return (retval == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) ? 0 : retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Uint32 HashAudioDeviceID(const void *key, void *data)
|
static Uint32 HashAudioDeviceID(const void *key, void *data)
|
||||||
|
@ -779,20 +907,23 @@ int SDL_InitAudio(const char *driver_name)
|
||||||
SDL_AudioDevice *default_capture = NULL;
|
SDL_AudioDevice *default_capture = NULL;
|
||||||
current_audio.impl.DetectDevices(&default_output, &default_capture);
|
current_audio.impl.DetectDevices(&default_output, &default_capture);
|
||||||
|
|
||||||
// these are only set if default_* is non-NULL, in case the backend just called SDL_DefaultAudioDeviceChanged directly during DetectDevices.
|
// If no default was _ever_ specified, just take the first device we see, if any.
|
||||||
if (default_output) {
|
if (!default_output) {
|
||||||
current_audio.default_output_device_id = default_output->instance_id;
|
default_output = GetFirstAddedAudioDevice(/*iscapture=*/SDL_FALSE);
|
||||||
}
|
|
||||||
if (default_capture) {
|
|
||||||
current_audio.default_capture_device_id = default_capture->instance_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no default was _ever_ specified, just take the first device we see, if any.
|
if (!default_capture) {
|
||||||
if (!current_audio.default_output_device_id) {
|
default_capture = GetFirstAddedAudioDevice(/*iscapture=*/SDL_TRUE);
|
||||||
current_audio.default_output_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_FALSE);
|
|
||||||
}
|
}
|
||||||
if (!current_audio.default_capture_device_id) {
|
|
||||||
current_audio.default_capture_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_TRUE);
|
if (default_output) {
|
||||||
|
current_audio.default_output_device_id = default_output->instance_id;
|
||||||
|
RefPhysicalAudioDevice(default_output); // extra ref on default devices.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (default_capture) {
|
||||||
|
current_audio.default_capture_device_id = default_capture->instance_id;
|
||||||
|
RefPhysicalAudioDevice(default_capture); // extra ref on default devices.
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1171,62 +1302,6 @@ SDL_AudioDeviceID *SDL_GetAudioCaptureDevices(int *count)
|
||||||
return GetAudioDevices(count, SDL_TRUE);
|
return GetAudioDevices(count, SDL_TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If found, this locks _the physical device_ this logical device is associated with, before returning.
|
|
||||||
static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid)
|
|
||||||
{
|
|
||||||
if (!SDL_GetCurrentAudioDriver()) {
|
|
||||||
SDL_SetError("Audio subsystem is not initialized");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_LogicalAudioDevice *logdev = NULL;
|
|
||||||
|
|
||||||
// bit #1 of devid is set for physical devices and unset for logical.
|
|
||||||
const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE;
|
|
||||||
if (islogical) { // don't bother looking if it's not a logical device id value.
|
|
||||||
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
|
||||||
SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev);
|
|
||||||
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!logdev) {
|
|
||||||
SDL_SetError("Invalid audio device instance ID");
|
|
||||||
} else {
|
|
||||||
SDL_LockMutex(logdev->physical_device->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return logdev;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* this finds the physical device associated with `devid` and locks it for use.
|
|
||||||
Note that a logical device instance id will return its associated physical device! */
|
|
||||||
static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid)
|
|
||||||
{
|
|
||||||
SDL_AudioDevice *device = NULL;
|
|
||||||
|
|
||||||
// bit #1 of devid is set for physical devices and unset for logical.
|
|
||||||
const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE;
|
|
||||||
if (islogical) {
|
|
||||||
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid);
|
|
||||||
if (logdev) {
|
|
||||||
device = logdev->physical_device;
|
|
||||||
}
|
|
||||||
} else if (!SDL_GetCurrentAudioDriver()) { // (the `islogical` path, above, checks this in ObtainLogicalAudioDevice.)
|
|
||||||
SDL_SetError("Audio subsystem is not initialized");
|
|
||||||
} else {
|
|
||||||
SDL_LockRWLockForReading(current_audio.device_hash_lock);
|
|
||||||
SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device);
|
|
||||||
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
|
||||||
|
|
||||||
if (!device) {
|
|
||||||
SDL_SetError("Invalid audio device instance ID");
|
|
||||||
} else {
|
|
||||||
SDL_LockMutex(device->lock); // caller must unlock.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata)
|
SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata)
|
||||||
{
|
{
|
||||||
|
@ -1270,17 +1345,15 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle)
|
||||||
|
|
||||||
char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)
|
char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)
|
||||||
{
|
{
|
||||||
|
char *retval = NULL;
|
||||||
SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid);
|
SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid);
|
||||||
if (!device) {
|
if (device) {
|
||||||
return NULL;
|
retval = SDL_strdup(device->name);
|
||||||
}
|
|
||||||
|
|
||||||
char *retval = SDL_strdup(device->name);
|
|
||||||
if (!retval) {
|
if (!retval) {
|
||||||
SDL_OutOfMemory();
|
SDL_OutOfMemory();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SDL_UnlockMutex(device->lock);
|
ReleaseAudioDevice(device);
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
@ -1291,31 +1364,18 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *
|
||||||
return SDL_InvalidParamError("spec");
|
return SDL_InvalidParamError("spec");
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_bool wants_default = SDL_FALSE;
|
int retval = -1;
|
||||||
if (devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) {
|
SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); // !!! FIXME: this needs an ObtainBlahDefaultAllowed to catch default device changes.
|
||||||
devid = current_audio.default_output_device_id;
|
if (device) {
|
||||||
wants_default = SDL_TRUE;
|
|
||||||
} else if (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) {
|
|
||||||
devid = current_audio.default_capture_device_id;
|
|
||||||
wants_default = SDL_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((devid == 0) && wants_default) {
|
|
||||||
return SDL_SetError("No default audio device available");
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid);
|
|
||||||
if (!device) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_copyp(spec, &device->spec);
|
SDL_copyp(spec, &device->spec);
|
||||||
if (sample_frames) {
|
if (sample_frames) {
|
||||||
*sample_frames = device->sample_frames;
|
*sample_frames = device->sample_frames;
|
||||||
}
|
}
|
||||||
SDL_UnlockMutex(device->lock);
|
retval = 0;
|
||||||
|
}
|
||||||
|
ReleaseAudioDevice(device);
|
||||||
|
|
||||||
return 0;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this expects the device lock to be held. !!! FIXME: no it doesn't...?
|
// this expects the device lock to be held. !!! FIXME: no it doesn't...?
|
||||||
|
@ -1350,20 +1410,21 @@ static void ClosePhysicalAudioDevice(SDL_AudioDevice *device)
|
||||||
|
|
||||||
void SDL_CloseAudioDevice(SDL_AudioDeviceID devid)
|
void SDL_CloseAudioDevice(SDL_AudioDeviceID devid)
|
||||||
{
|
{
|
||||||
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid);
|
SDL_bool close_physical = SDL_FALSE;
|
||||||
|
SDL_AudioDevice *device = NULL;
|
||||||
|
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
|
||||||
if (logdev) {
|
if (logdev) {
|
||||||
SDL_AudioDevice *device = logdev->physical_device;
|
|
||||||
DestroyLogicalAudioDevice(logdev);
|
DestroyLogicalAudioDevice(logdev);
|
||||||
|
close_physical = (device->logical_devices == NULL); // no more logical devices? Close the physical device, too.
|
||||||
const SDL_bool close_physical = (device->logical_devices == NULL); // no more logical devices? Close the physical device, too.
|
}
|
||||||
|
|
||||||
// !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it.
|
// !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it.
|
||||||
SDL_UnlockMutex(device->lock); // can't hold the lock or the audio thread will deadlock while we WaitThread it. If not closing, we're done anyhow.
|
ReleaseAudioDevice(device); // can't hold the lock or the audio thread will deadlock while we WaitThread it. If not closing, we're done anyhow.
|
||||||
|
|
||||||
|
if (device) {
|
||||||
if (close_physical) {
|
if (close_physical) {
|
||||||
ClosePhysicalAudioDevice(device);
|
ClosePhysicalAudioDevice(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
UnrefPhysicalAudioDevice(device); // one reference for each logical device.
|
UnrefPhysicalAudioDevice(device); // one reference for each logical device.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1514,30 +1575,17 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_bool wants_default = SDL_FALSE;
|
SDL_bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) || (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE)) ? SDL_TRUE : SDL_FALSE;
|
||||||
if (devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) {
|
|
||||||
devid = current_audio.default_output_device_id;
|
|
||||||
wants_default = SDL_TRUE;
|
|
||||||
} else if (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) {
|
|
||||||
devid = current_audio.default_capture_device_id;
|
|
||||||
wants_default = SDL_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((devid == 0) && wants_default) {
|
|
||||||
SDL_SetError("No default audio device available");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this will let you use a logical device to make a new logical device on the parent physical device. Could be useful?
|
// this will let you use a logical device to make a new logical device on the parent physical device. Could be useful?
|
||||||
SDL_AudioDevice *device = NULL;
|
SDL_AudioDevice *device = NULL;
|
||||||
const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE;
|
const SDL_bool islogical = (wants_default || (devid & (1<<1))) ? SDL_FALSE : SDL_TRUE;
|
||||||
if (!islogical) {
|
if (!islogical) {
|
||||||
device = ObtainPhysicalAudioDevice(devid);
|
device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
|
||||||
} else {
|
} else {
|
||||||
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); // this locks the physical device, too.
|
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
|
||||||
if (logdev) {
|
if (logdev) {
|
||||||
wants_default = logdev->opened_as_default; // was the original logical device meant to be a default? Make this one, too.
|
wants_default = logdev->opened_as_default; // was the original logical device meant to be a default? Make this one, too.
|
||||||
device = logdev->physical_device;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1565,7 +1613,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp
|
||||||
device->logical_devices = logdev;
|
device->logical_devices = logdev;
|
||||||
UpdateAudioStreamFormatsPhysical(device);
|
UpdateAudioStreamFormatsPhysical(device);
|
||||||
}
|
}
|
||||||
SDL_UnlockMutex(device->lock);
|
ReleaseAudioDevice(device);
|
||||||
|
|
||||||
if (retval) {
|
if (retval) {
|
||||||
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
|
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
|
||||||
|
@ -1583,13 +1631,13 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp
|
||||||
|
|
||||||
static int SetLogicalAudioDevicePauseState(SDL_AudioDeviceID devid, int value)
|
static int SetLogicalAudioDevicePauseState(SDL_AudioDeviceID devid, int value)
|
||||||
{
|
{
|
||||||
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid);
|
SDL_AudioDevice *device = NULL;
|
||||||
if (!logdev) {
|
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
|
||||||
return -1; // ObtainLogicalAudioDevice will have set an error.
|
if (logdev) {
|
||||||
}
|
|
||||||
SDL_AtomicSet(&logdev->paused, value);
|
SDL_AtomicSet(&logdev->paused, value);
|
||||||
SDL_UnlockMutex(logdev->physical_device->lock);
|
}
|
||||||
return 0;
|
ReleaseAudioDevice(device);
|
||||||
|
return logdev ? 0 : -1; // ObtainLogicalAudioDevice will have set an error.
|
||||||
}
|
}
|
||||||
|
|
||||||
int SDL_PauseAudioDevice(SDL_AudioDeviceID devid)
|
int SDL_PauseAudioDevice(SDL_AudioDeviceID devid)
|
||||||
|
@ -1604,23 +1652,22 @@ int SDLCALL SDL_ResumeAudioDevice(SDL_AudioDeviceID devid)
|
||||||
|
|
||||||
SDL_bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid)
|
SDL_bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid)
|
||||||
{
|
{
|
||||||
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid);
|
SDL_AudioDevice *device = NULL;
|
||||||
|
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
|
||||||
SDL_bool retval = SDL_FALSE;
|
SDL_bool retval = SDL_FALSE;
|
||||||
if (logdev) {
|
if (logdev && SDL_AtomicGet(&logdev->paused)) {
|
||||||
if (SDL_AtomicGet(&logdev->paused)) {
|
|
||||||
retval = SDL_TRUE;
|
retval = SDL_TRUE;
|
||||||
}
|
}
|
||||||
SDL_UnlockMutex(logdev->physical_device->lock);
|
ReleaseAudioDevice(device);
|
||||||
}
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata)
|
int SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata)
|
||||||
{
|
{
|
||||||
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid);
|
SDL_AudioDevice *device = NULL;
|
||||||
|
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
if (logdev) {
|
if (logdev) {
|
||||||
SDL_AudioDevice *device = logdev->physical_device;
|
|
||||||
if (callback && !device->postmix_buffer) {
|
if (callback && !device->postmix_buffer) {
|
||||||
device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->work_buffer_size);
|
device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->work_buffer_size);
|
||||||
if (device->postmix_buffer == NULL) {
|
if (device->postmix_buffer == NULL) {
|
||||||
|
@ -1644,15 +1691,17 @@ int SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallbac
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateAudioStreamFormatsPhysical(device);
|
UpdateAudioStreamFormatsPhysical(device);
|
||||||
SDL_UnlockMutex(device->lock);
|
|
||||||
}
|
}
|
||||||
|
ReleaseAudioDevice(device);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams)
|
int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams)
|
||||||
{
|
{
|
||||||
const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE;
|
const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE;
|
||||||
SDL_LogicalAudioDevice *logdev;
|
SDL_AudioDevice *device = NULL;
|
||||||
|
SDL_LogicalAudioDevice *logdev = NULL;
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
if (num_streams == 0) {
|
if (num_streams == 0) {
|
||||||
return 0; // nothing to do
|
return 0; // nothing to do
|
||||||
|
@ -1662,23 +1711,21 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int
|
||||||
return SDL_InvalidParamError("streams");
|
return SDL_InvalidParamError("streams");
|
||||||
} else if (!islogical) {
|
} else if (!islogical) {
|
||||||
return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices");
|
return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices");
|
||||||
} else if ((logdev = ObtainLogicalAudioDevice(devid)) == NULL) {
|
|
||||||
return -1; // ObtainLogicalAudioDevice set the error message.
|
|
||||||
} else if (logdev->simplified) {
|
|
||||||
SDL_UnlockMutex(logdev->physical_device->lock);
|
|
||||||
return SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logdev = ObtainLogicalAudioDevice(devid, &device);
|
||||||
|
if (!logdev) {
|
||||||
|
retval = -1; // ObtainLogicalAudioDevice set the error string.
|
||||||
|
} else if (logdev->simplified) {
|
||||||
|
retval = SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream");
|
||||||
|
} else {
|
||||||
|
|
||||||
// !!! FIXME: We'll set the device's side's format below, but maybe we should refuse to bind a stream if the app's side doesn't have a format set yet.
|
// !!! FIXME: We'll set the device's side's format below, but maybe we should refuse to bind a stream if the app's side doesn't have a format set yet.
|
||||||
// !!! FIXME: Actually, why do we allow there to be an invalid format, again?
|
// !!! FIXME: Actually, why do we allow there to be an invalid format, again?
|
||||||
|
|
||||||
// make sure start of list is sane.
|
// make sure start of list is sane.
|
||||||
SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL));
|
SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL));
|
||||||
|
|
||||||
SDL_AudioDevice *device = logdev->physical_device;
|
|
||||||
const SDL_bool iscapture = device->iscapture;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
// lock all the streams upfront, so we can verify they aren't bound elsewhere and add them all in one block, as this is intended to add everything or nothing.
|
// lock all the streams upfront, so we can verify they aren't bound elsewhere and add them all in one block, as this is intended to add everything or nothing.
|
||||||
for (int i = 0; i < num_streams; i++) {
|
for (int i = 0; i < num_streams; i++) {
|
||||||
SDL_AudioStream *stream = streams[i];
|
SDL_AudioStream *stream = streams[i];
|
||||||
|
@ -1702,9 +1749,11 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (retval == 0) {
|
if (retval == 0) {
|
||||||
// Now that everything is verified, chain everything together.
|
// Now that everything is verified, chain everything together.
|
||||||
|
const SDL_bool iscapture = device->iscapture;
|
||||||
for (int i = 0; i < num_streams; i++) {
|
for (int i = 0; i < num_streams; i++) {
|
||||||
SDL_AudioStream *stream = streams[i];
|
SDL_AudioStream *stream = streams[i];
|
||||||
|
|
||||||
|
@ -1729,7 +1778,7 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int
|
||||||
|
|
||||||
UpdateAudioStreamFormatsPhysical(device);
|
UpdateAudioStreamFormatsPhysical(device);
|
||||||
|
|
||||||
SDL_UnlockMutex(device->lock);
|
ReleaseAudioDevice(device);
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
@ -1739,6 +1788,7 @@ int SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream)
|
||||||
return SDL_BindAudioStreams(devid, &stream, 1);
|
return SDL_BindAudioStreams(devid, &stream, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// !!! FIXME: this and BindAudioStreams are mutex nightmares. :/
|
||||||
void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams)
|
void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams)
|
||||||
{
|
{
|
||||||
/* to prevent deadlock when holding both locks, we _must_ lock the device first, and the stream second, as that is the order the audio thread will do it.
|
/* to prevent deadlock when holding both locks, we _must_ lock the device first, and the stream second, as that is the order the audio thread will do it.
|
||||||
|
@ -1831,37 +1881,27 @@ SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_Au
|
||||||
return NULL; // error string should already be set.
|
return NULL; // error string should already be set.
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(logdevid);
|
SDL_bool failed = SDL_FALSE;
|
||||||
if (logdev == NULL) { // this shouldn't happen, but just in case.
|
|
||||||
SDL_CloseAudioDevice(logdevid);
|
|
||||||
return NULL; // error string should already be set.
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AudioDevice *physdevice = logdev->physical_device;
|
|
||||||
SDL_assert(physdevice != NULL);
|
|
||||||
|
|
||||||
SDL_AtomicSet(&logdev->paused, 1); // start the device paused, to match SDL2.
|
|
||||||
SDL_UnlockMutex(physdevice->lock); // we don't need to hold the lock for any of this.
|
|
||||||
|
|
||||||
const SDL_bool iscapture = physdevice->iscapture;
|
|
||||||
|
|
||||||
SDL_AudioStream *stream = NULL;
|
SDL_AudioStream *stream = NULL;
|
||||||
if (iscapture) {
|
SDL_AudioDevice *device = NULL;
|
||||||
stream = SDL_CreateAudioStream(&physdevice->spec, spec);
|
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(logdevid, &device);
|
||||||
|
if (logdev == NULL) { // this shouldn't happen, but just in case.
|
||||||
|
failed = SDL_TRUE;
|
||||||
} else {
|
} else {
|
||||||
stream = SDL_CreateAudioStream(spec, &physdevice->spec);
|
SDL_AtomicSet(&logdev->paused, 1); // start the device paused, to match SDL2.
|
||||||
}
|
|
||||||
|
SDL_assert(device != NULL);
|
||||||
if (!stream) {
|
const SDL_bool iscapture = device->iscapture;
|
||||||
SDL_CloseAudioDevice(logdevid);
|
|
||||||
return NULL; // error string should already be set.
|
if (iscapture) {
|
||||||
}
|
stream = SDL_CreateAudioStream(&device->spec, spec);
|
||||||
if (SDL_BindAudioStream(logdevid, stream) == -1) {
|
} else {
|
||||||
SDL_DestroyAudioStream(stream);
|
stream = SDL_CreateAudioStream(spec, &device->spec);
|
||||||
SDL_CloseAudioDevice(logdevid);
|
|
||||||
return NULL; // error string should already be set.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!stream || (SDL_BindAudioStream(logdevid, stream) == -1)) {
|
||||||
|
failed = SDL_TRUE;
|
||||||
|
} else {
|
||||||
logdev->simplified = SDL_TRUE; // forbid further binding changes on this logical device.
|
logdev->simplified = SDL_TRUE; // forbid further binding changes on this logical device.
|
||||||
stream->simplified = SDL_TRUE; // so we know to close the audio device when this is destroyed.
|
stream->simplified = SDL_TRUE; // so we know to close the audio device when this is destroyed.
|
||||||
|
|
||||||
|
@ -1874,8 +1914,18 @@ SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_Au
|
||||||
}
|
}
|
||||||
SDL_assert(rc == 0); // should only fail if stream==NULL atm.
|
SDL_assert(rc == 0); // should only fail if stream==NULL atm.
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return stream; // ready to rock.
|
ReleaseAudioDevice(device);
|
||||||
|
|
||||||
|
if (failed) {
|
||||||
|
SDL_DestroyAudioStream(stream);
|
||||||
|
SDL_CloseAudioDevice(logdevid);
|
||||||
|
stream = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define NUM_FORMATS 8
|
#define NUM_FORMATS 8
|
||||||
|
@ -1913,9 +1963,21 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SDL_bool iscapture = new_default_device->iscapture;
|
const SDL_bool iscapture = new_default_device->iscapture;
|
||||||
const SDL_AudioDeviceID current_devid = iscapture ? current_audio.default_capture_device_id : current_audio.default_output_device_id;
|
|
||||||
|
|
||||||
if (new_default_device->instance_id == current_devid) {
|
// change the official default over right away, so new opens will go to the new device.
|
||||||
|
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
|
||||||
|
const SDL_AudioDeviceID current_devid = iscapture ? current_audio.default_capture_device_id : current_audio.default_output_device_id;
|
||||||
|
const SDL_bool is_already_default = (new_default_device->instance_id == current_devid) ? SDL_TRUE : SDL_FALSE;
|
||||||
|
if (!is_already_default) {
|
||||||
|
if (iscapture) {
|
||||||
|
current_audio.default_capture_device_id = new_default_device->instance_id;
|
||||||
|
} else {
|
||||||
|
current_audio.default_output_device_id = new_default_device->instance_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
|
|
||||||
|
if (is_already_default) {
|
||||||
return; // this is already the default.
|
return; // this is already the default.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1926,18 +1988,13 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
|
||||||
pending.next = NULL;
|
pending.next = NULL;
|
||||||
SDL_PendingAudioDeviceEvent *pending_tail = &pending;
|
SDL_PendingAudioDeviceEvent *pending_tail = &pending;
|
||||||
|
|
||||||
SDL_LockMutex(new_default_device->lock);
|
// Default device gets an extra ref, so it lives until a new default replaces it, even if disconnected.
|
||||||
|
RefPhysicalAudioDevice(new_default_device);
|
||||||
|
|
||||||
|
ObtainPhysicalAudioDeviceObj(new_default_device);
|
||||||
|
|
||||||
SDL_AudioDevice *current_default_device = ObtainPhysicalAudioDevice(current_devid);
|
SDL_AudioDevice *current_default_device = ObtainPhysicalAudioDevice(current_devid);
|
||||||
|
|
||||||
/* change the official default ID over while we have locks on both devices, so if something raced to open the default during
|
|
||||||
this, it either gets the new device or is ready on the old and can be migrated. */
|
|
||||||
if (iscapture) {
|
|
||||||
current_audio.default_capture_device_id = new_default_device->instance_id;
|
|
||||||
} else {
|
|
||||||
current_audio.default_output_device_id = new_default_device->instance_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_default_device) {
|
if (current_default_device) {
|
||||||
// migrate any logical devices that were opened as a default to the new physical device...
|
// migrate any logical devices that were opened as a default to the new physical device...
|
||||||
|
|
||||||
|
@ -1984,7 +2041,8 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
|
||||||
continue; // not opened as a default, leave it on the current physical device.
|
continue; // not opened as a default, leave it on the current physical device.
|
||||||
}
|
}
|
||||||
|
|
||||||
// now migrate the logical device.
|
// now migrate the logical device. Hold device_hash_lock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition.
|
||||||
|
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
|
||||||
if (logdev->next) {
|
if (logdev->next) {
|
||||||
logdev->next->prev = logdev->prev;
|
logdev->next->prev = logdev->prev;
|
||||||
}
|
}
|
||||||
|
@ -1999,6 +2057,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
|
||||||
logdev->prev = NULL;
|
logdev->prev = NULL;
|
||||||
logdev->next = new_default_device->logical_devices;
|
logdev->next = new_default_device->logical_devices;
|
||||||
new_default_device->logical_devices = logdev;
|
new_default_device->logical_devices = logdev;
|
||||||
|
SDL_UnlockRWLock(current_audio.device_hash_lock);
|
||||||
|
|
||||||
SDL_assert(SDL_AtomicGet(¤t_default_device->refcount) > 1); // we should hold at least one extra reference to this device, beyond logical devices, during this phase...
|
SDL_assert(SDL_AtomicGet(¤t_default_device->refcount) > 1); // we should hold at least one extra reference to this device, beyond logical devices, during this phase...
|
||||||
RefPhysicalAudioDevice(new_default_device);
|
RefPhysicalAudioDevice(new_default_device);
|
||||||
|
@ -2024,19 +2083,21 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
|
||||||
|
|
||||||
if (current_default_device->logical_devices == NULL) { // nothing left on the current physical device, close it.
|
if (current_default_device->logical_devices == NULL) { // nothing left on the current physical device, close it.
|
||||||
// !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it.
|
// !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it.
|
||||||
SDL_UnlockMutex(current_default_device->lock); // can't hold the lock or the audio thread will deadlock while we WaitThread it.
|
RefPhysicalAudioDevice(current_default_device); // hold a temp ref for a moment while we release...
|
||||||
|
ReleaseAudioDevice(current_default_device); // can't hold the lock or the audio thread will deadlock while we WaitThread it.
|
||||||
ClosePhysicalAudioDevice(current_default_device);
|
ClosePhysicalAudioDevice(current_default_device);
|
||||||
SDL_LockMutex(current_default_device->lock); // we're about to unlock this again, so make sure the locks match.
|
ObtainPhysicalAudioDeviceObj(current_default_device); // we're about to unlock this again, so make sure the locks match.
|
||||||
|
UnrefPhysicalAudioDevice(current_default_device); // drop temp ref.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UnlockMutex(current_default_device->lock);
|
ReleaseAudioDevice(current_default_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UnlockMutex(new_default_device->lock);
|
ReleaseAudioDevice(new_default_device);
|
||||||
|
|
||||||
// was current device already dead and just kept around to migrate to a new default device? Now we can kill it. Aim for the brain.
|
// Default device gets an extra ref, so it lives until a new default replaces it, even if disconnected.
|
||||||
if (current_default_device && SDL_AtomicGet(¤t_default_device->zombie)) {
|
if (current_default_device) { // (despite the name, it's no longer current at this point)
|
||||||
UnrefPhysicalAudioDevice(current_default_device);
|
UnrefPhysicalAudioDevice(current_default_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2136,9 +2197,9 @@ int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL
|
||||||
|
|
||||||
int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
|
int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
|
||||||
{
|
{
|
||||||
SDL_LockMutex(device->lock);
|
ObtainPhysicalAudioDeviceObj(device);
|
||||||
const int retval = SDL_AudioDeviceFormatChangedAlreadyLocked(device, newspec, new_sample_frames);
|
const int retval = SDL_AudioDeviceFormatChangedAlreadyLocked(device, newspec, new_sample_frames);
|
||||||
SDL_UnlockMutex(device->lock);
|
ReleaseAudioDevice(device);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue