audio: An enormous amount of work on managing default devices.

main
Ryan C. Gordon 2023-06-23 02:37:48 -04:00
parent c7a44eea83
commit ee10bab3cd
No known key found for this signature in database
GPG Key ID: FA148B892AB48044
5 changed files with 314 additions and 87 deletions

View File

@ -301,6 +301,9 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
return;
}
// !!! FIXME: if this was the default device, we should figure out how to migrate the appropriate logical devices instead of declaring them dead.
// !!! FIXME: (right now we rely on the backends to change the default device before disconnecting this one, but that's probably not practical.)
SDL_bool was_live = SDL_FALSE;
// take it out of the device list.
@ -347,6 +350,16 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
event.adevice.which = device->instance_id;
event.adevice.iscapture = device->iscapture ? 1 : 0;
SDL_PushEvent(&event);
// post an event for each logical device, too.
for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) {
SDL_zero(event);
event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED;
event.common.timestamp = 0;
event.adevice.which = logdev->instance_id;
event.adevice.iscapture = device->iscapture ? 1 : 0;
SDL_PushEvent(&event);
}
}
if (should_destroy) {
@ -366,15 +379,15 @@ static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */
static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ }
static void SDL_AudioFreeDeviceHandle_Default(void *handle) { /* no-op. */ }
static void SDL_AudioDetectDevices_Default(void)
static void SDL_AudioDetectDevices_Default(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
// you have to write your own implementation if these assertions fail.
SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice);
SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport);
SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)((size_t)0x1));
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)((size_t)0x1));
if (current_audio.impl.HasCaptureSupport) {
SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)((size_t)0x2));
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)((size_t)0x2));
}
}
@ -512,7 +525,14 @@ int SDL_InitAudio(const char *driver_name)
CompleteAudioEntryPoints();
// Make sure we have a list of devices available at startup...
current_audio.impl.DetectDevices();
SDL_AudioDevice *default_output = NULL;
SDL_AudioDevice *default_capture = NULL;
current_audio.impl.DetectDevices(&default_output, &default_capture);
current_audio.default_output_device_id = default_output ? default_output->instance_id : 0;
current_audio.default_capture_device_id = default_capture ? default_capture->instance_id : 0;
// !!! FIXME: if a default is zero but there are devices available, should we just pick the first one?
return 0;
}
@ -1121,17 +1141,25 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec
SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec)
{
if (!SDL_GetCurrentAudioDriver()) {
SDL_SetError("Audio subsystem is not initialized");
return 0;
}
SDL_bool is_default = SDL_FALSE;
if (devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) {
// !!! FIXME: needs to be hooked up.
//devid = current_audio.default_output_device_id;
devid = current_audio.default_output_device_id;
is_default = SDL_TRUE;
} else if (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) {
// !!! FIXME: needs to be hooked up.
//devid = current_audio.default_capture_device_id;
devid = current_audio.default_capture_device_id;
is_default = SDL_TRUE;
}
if ((devid == 0) && is_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?
SDL_AudioDevice *device = NULL;
const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE;
@ -1433,3 +1461,108 @@ int SDL_GetSilenceValueForFormat(SDL_AudioFormat format)
return (format == SDL_AUDIO_U8) ? 0x80 : 0x00;
}
// called internally by backends when the system default device changes.
void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
{
if (new_default_device == NULL) { // !!! FIXME: what should we do in this case? Maybe all devices are lost, so there _isn't_ a default?
return; // uhoh.
}
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) {
return; // this is already the default.
}
SDL_LockMutex(new_default_device->lock);
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) {
// migrate any logical devices that were opened as a default to the new physical device...
SDL_assert(current_default_device->iscapture == iscapture);
// See if we have to open the new physical device, and if so, find the best audiospec for it.
SDL_AudioSpec spec;
SDL_bool needs_migration = SDL_FALSE;
SDL_zero(spec);
for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev != NULL; logdev = logdev->next) {
if (logdev->is_default) {
needs_migration = SDL_TRUE;
for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) {
const SDL_AudioSpec *streamspec = iscapture ? &stream->dst_spec : &stream->src_spec;
if (SDL_AUDIO_BITSIZE(streamspec->format) > SDL_AUDIO_BITSIZE(spec.format)) {
spec.format = streamspec->format;
}
if (streamspec->channels > spec.channels) {
spec.channels = streamspec->channels;
}
if (streamspec->freq > spec.freq) {
spec.freq = streamspec->freq;
}
}
}
}
if (needs_migration) {
if (new_default_device->logical_devices == NULL) { // New default physical device not been opened yet? Open at the OS level...
if (OpenPhysicalAudioDevice(new_default_device, &spec) == -1) {
needs_migration = SDL_FALSE; // uhoh, just leave everything on the old default, nothing to be done.
}
}
}
if (needs_migration) {
SDL_LogicalAudioDevice *next = NULL;
for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev != NULL; logdev = next) {
next = logdev->next;
if (!logdev->is_default) {
continue; // not opened as a default, leave it on the current physical device.
}
// make sure all our streams are targeting the new device's format.
for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) {
SDL_SetAudioStreamFormat(stream, iscapture ? &new_default_device->spec : NULL, iscapture ? NULL : &new_default_device->spec);
}
// now migrate the logical device.
if (logdev->next) {
logdev->next->prev = logdev->prev;
}
if (logdev->prev) {
logdev->prev->next = logdev->next;
}
if (current_default_device->logical_devices == logdev) {
current_default_device->logical_devices = logdev->next;
}
logdev->physical_device = new_default_device;
logdev->prev = NULL;
logdev->next = new_default_device->logical_devices;
new_default_device->logical_devices = logdev;
}
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.
SDL_UnlockMutex(current_default_device->lock); // can't hold the lock or the audio thread will deadlock while we WaitThread it.
ClosePhysicalAudioDevice(current_default_device);
SDL_LockMutex(current_default_device->lock); // we're about to unlock this again, so make sure the locks match.
}
}
SDL_UnlockMutex(current_default_device->lock);
}
SDL_UnlockMutex(new_default_device->lock);
}

View File

@ -70,15 +70,18 @@ const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format);
/* Must be called at least once before using converters (SDL_CreateAudioStream will call it !!! FIXME but probably shouldn't). */
extern void SDL_ChooseAudioConverters(void);
/* Audio targets should call this as devices are added to the system (such as
/* Backends should call this as devices are added to the system (such as
a USB headset being plugged in), and should also be called for
for every device found during DetectDevices(). */
extern SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *spec, void *handle);
/* Audio targets should call this if an opened audio device is lost.
/* Backends should call this if an opened audio device is lost.
This can happen due to i/o errors, or a device being unplugged, etc. */
extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
// Backends should call this if the system default device changes.
extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
/* Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. Locks the device! You must unlock!! */
extern SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle);
@ -96,7 +99,7 @@ extern void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device);
typedef struct SDL_AudioDriverImpl
{
void (*DetectDevices)(void);
void (*DetectDevices)(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
int (*OpenDevice)(SDL_AudioDevice *device);
void (*ThreadInit)(SDL_AudioDevice *device); /* Called by audio thread at start */
void (*ThreadDeinit)(SDL_AudioDevice *device); /* Called by audio thread at end */
@ -126,6 +129,8 @@ typedef struct SDL_AudioDriver
SDL_RWLock *device_list_lock; /* A mutex for device detection */
SDL_AudioDevice *output_devices; /* the list of currently-available audio output devices. */
SDL_AudioDevice *capture_devices; /* the list of currently-available audio capture devices. */
SDL_AudioDeviceID default_output_device_id;
SDL_AudioDeviceID default_capture_device_id;
SDL_AtomicInt output_device_count;
SDL_AtomicInt capture_device_count;
SDL_AtomicInt last_device_instance_id; /* increments on each device add to provide unique instance IDs */

View File

@ -151,10 +151,10 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
return 0;
}
static void DISKAUDIO_DetectDevices(void)
static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1);
SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2);
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1);
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2);
}
static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)

View File

@ -43,13 +43,15 @@ static pa_context *pulseaudio_context = NULL;
static SDL_Thread *pulseaudio_hotplug_thread = NULL;
static SDL_AtomicInt pulseaudio_hotplug_thread_active;
/* These are the OS identifiers (i.e. ALSA strings)... */
// These are the OS identifiers (i.e. ALSA strings)...
static char *default_sink_path = NULL;
static char *default_source_path = NULL;
/* ... and these are the descriptions we use in GetDefaultAudioInfo. */
// ... and these are the descriptions we use in GetDefaultAudioInfo...
static char *default_sink_name = NULL;
static char *default_source_name = NULL;
// ... and these are the PulseAudio device indices of the default devices.
static uint32_t default_sink_index = 0;
static uint32_t default_source_index = 0;
static const char *(*PULSEAUDIO_pa_get_library_version)(void);
static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)(
@ -555,12 +557,12 @@ static void SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int
static SDL_bool FindDeviceName(struct SDL_PrivateAudioData *h, const SDL_bool iscapture, void *handle)
{
const uint32_t idx = ((uint32_t)((intptr_t)handle)) - 1;
if (handle == NULL) { /* NULL == default device. */
return SDL_TRUE;
}
const uint32_t idx = ((uint32_t)((intptr_t)handle)) - 1;
if (iscapture) {
WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceDeviceNameCallback, &h->device_name));
} else {
@ -685,11 +687,8 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device)
PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL);
/* now that we have multi-device support, don't move a stream from
a device that was unplugged to something else, unless we're default. */
if (h->device_name != NULL) {
flags |= PA_STREAM_DONT_MOVE;
}
// SDL manages device moves if the default changes, so don't ever let Pulse automatically migrate this stream.
flags |= PA_STREAM_DONT_MOVE;
if (iscapture) {
PULSEAUDIO_pa_stream_set_read_callback(h->stream, ReadCallback, h);
@ -745,46 +744,50 @@ static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format)
}
}
/* This is called when PulseAudio adds an output ("sink") device. */
// This is called when PulseAudio adds an output ("sink") device.
// !!! FIXME: this is almost identical to SourceInfoCallback, merge the two.
static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
{
if (i) {
const SDL_bool add = (SDL_bool)((intptr_t)data);
SDL_AudioSpec spec;
spec.format = PulseFormatToSDLFormat(i->sample_spec.format);
spec.channels = i->sample_spec.channels;
spec.freq = i->sample_spec.rate;
const SDL_bool add = (SDL_bool) ((intptr_t)data);
if (add) {
SDL_AudioSpec spec;
spec.format = PulseFormatToSDLFormat(i->sample_spec.format);
spec.channels = i->sample_spec.channels;
spec.freq = i->sample_spec.rate;
SDL_AddAudioDevice(SDL_FALSE, i->description, &spec, (void *)((intptr_t)i->index + 1));
}
if (default_sink_path != NULL && SDL_strcmp(i->name, default_sink_path) == 0) {
SDL_free(default_sink_name);
default_sink_name = SDL_strdup(i->description);
default_sink_index = i->index;
}
}
PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
}
/* This is called when PulseAudio adds a capture ("source") device. */
// !!! FIXME: this is almost identical to SinkInfoCallback, merge the two.
static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
{
/* Maybe skip "monitor" sources. These are just output from other sinks. */
if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) {
const SDL_bool add = (SDL_bool)((intptr_t)data);
SDL_AudioSpec spec;
spec.format = PulseFormatToSDLFormat(i->sample_spec.format);
spec.channels = i->sample_spec.channels;
spec.freq = i->sample_spec.rate;
const SDL_bool add = (SDL_bool) ((intptr_t)data);
if (add) {
SDL_AudioSpec spec;
spec.format = PulseFormatToSDLFormat(i->sample_spec.format);
spec.channels = i->sample_spec.channels;
spec.freq = i->sample_spec.rate;
SDL_AddAudioDevice(SDL_TRUE, i->description, &spec, (void *)((intptr_t)i->index + 1));
}
if (default_source_path != NULL && SDL_strcmp(i->name, default_source_path) == 0) {
SDL_free(default_source_name);
default_source_name = SDL_strdup(i->description);
default_source_index = i->index;
}
}
PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
@ -792,80 +795,152 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_la
static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data)
{
SDL_free(default_sink_path);
SDL_free(default_source_path);
default_sink_path = SDL_strdup(i->default_sink_name);
default_source_path = SDL_strdup(i->default_source_name);
if (!default_sink_path || (SDL_strcmp(i->default_sink_name, default_sink_path) != 0)) {
printf("DEFAULT SINK PATH CHANGED TO '%s'\n", i->default_sink_name);
SDL_free(default_sink_path);
default_sink_path = SDL_strdup(i->default_sink_name);
}
if (!default_source_path || (SDL_strcmp(i->default_source_name, default_source_path) != 0)) {
printf("DEFAULT SOURCE PATH CHANGED TO '%s'\n", i->default_source_name);
SDL_free(default_source_path);
default_source_path = SDL_strdup(i->default_source_name);
}
PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
}
/* This is called when PulseAudio has a device connected/removed/changed. */
typedef struct PulseHotplugEvent
{
pa_subscription_event_type_t t;
uint32_t idx;
} PulseHotplugEvent;
typedef struct PulseHotplugEvents
{
PulseHotplugEvent *events;
int num_events;
int allocated_events;
SDL_bool need_server_info_refresh;
} PulseHotplugEvents;
// This is called when PulseAudio has a device connected/removed/changed.
static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data)
{
const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW);
const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE);
const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE);
if (added || removed || changed) { /* we only care about add/remove events. */
const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK);
const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
/* adds need sink details from the PulseAudio server. Another callback... */
/* (just unref all these operations right away, because we aren't going to wait on them and their callbacks will handle any work, so they can free as soon as that happens.) */
if ((added || changed) && sink) {
if (changed) {
PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL));
}
PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, (void *)((intptr_t)added)));
} else if ((added || changed) && source) {
if (changed) {
PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL));
}
PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added)));
} else if (removed && (sink || source)) {
/* removes we can handle just with the device index. */
SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1)); /* !!! FIXME: maybe just have a "disconnect by handle" function instead. */
if (device) {
SDL_UnlockMutex(device->lock); /* AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. */
SDL_AudioDeviceDisconnected(device);
}
// We can't block in here for new ServerInfo, but we need that info to resolve first, since we can
// migrate default devices during a removal, as long as we know the new default. So just queue up
// the data and let HotplugThread handle it, where we can call WaitForPulseOperation.
PulseHotplugEvents *events = (PulseHotplugEvents *) data;
if (events->allocated_events <= events->num_events) {
const int new_allocation = events->num_events + 16;
void *ptr = SDL_realloc(events->events, new_allocation * sizeof (PulseHotplugEvent));
if (!ptr) {
return; // oh well.
}
events->events = (PulseHotplugEvent *) ptr;
events->allocated_events = new_allocation;
}
events->events[events->num_events].t = t;
events->events[events->num_events].idx = idx;
events->num_events++;
const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE);
if (changed) {
events->need_server_info_refresh = SDL_TRUE;
}
PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
}
/* this runs as a thread while the Pulse target is initialized to catch hotplug events. */
static void CheckDefaultDevice(int prev_default, int new_default)
{
if (prev_default != new_default) {
SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)new_default + 1));
if (device) {
SDL_UnlockMutex(device->lock);
SDL_DefaultAudioDeviceChanged(device);
}
}
}
// this runs as a thread while the Pulse target is initialized to catch hotplug events.
static int SDLCALL HotplugThread(void *data)
{
pa_operation *op;
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW);
PulseHotplugEvents events;
SDL_zero(events);
PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL);
/* don't WaitForPulseOperation on the subscription; when it's done we'll be able to get hotplug events, but waiting doesn't changing anything. */
op = PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL);
SDL_PostSemaphore((SDL_Semaphore *) data);
PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, &events);
WaitForPulseOperation(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL));
while (SDL_AtomicGet(&pulseaudio_hotplug_thread_active)) {
PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
if (op && PULSEAUDIO_pa_operation_get_state(op) != PA_OPERATION_RUNNING) {
PULSEAUDIO_pa_operation_unref(op);
op = NULL;
if (events.need_server_info_refresh) {
events.need_server_info_refresh = SDL_FALSE;
WaitForPulseOperation(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL));
} else if (events.num_events) { // don't process events until we didn't get anything new that needs a server info update.
const uint32_t prev_default_sink_index = default_sink_index;
const uint32_t prev_default_source_index = default_source_index;
PulseHotplugEvents eventscpy;
SDL_memcpy(&eventscpy, &events, sizeof (PulseHotplugEvents));
SDL_zero(events); // make sure the current array isn't touched in case new events fire.
// process adds and changes first, so we definitely have default device changes processed. Then remove devices after.
for (int i = 0; i < eventscpy.num_events; i++) {
const pa_subscription_event_type_t t = eventscpy.events[i].t;
const uint32_t idx = eventscpy.events[i].idx;
const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW);
const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE);
const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK);
const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
if (added || changed) {
if (sink) {
WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, (void *)((intptr_t)added)));
} else if (source) {
WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added)));
}
}
}
// don't hold the pulse lock during this, since it could deadlock vs a playing device that we're about to lock here.
PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
CheckDefaultDevice(prev_default_sink_index, default_sink_index);
CheckDefaultDevice(prev_default_source_index, default_source_index);
// okay, default devices are sane, migrations were made if necessary...now remove lost devices.
for (int i = 0; i < eventscpy.num_events; i++) {
const pa_subscription_event_type_t t = eventscpy.events[i].t;
const uint32_t idx = eventscpy.events[i].idx;
const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE);
const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK);
const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
if (removed && (sink || source)) {
SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1));
if (device) {
SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now.
SDL_AudioDeviceDisconnected(device);
}
}
}
PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
SDL_free(eventscpy.events);
}
}
if (op) {
PULSEAUDIO_pa_operation_unref(op);
}
PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL);
PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL); // Don't fire HotplugCallback again.
PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
SDL_free(events.events); // should be NULL, but just in case.
return 0;
}
static void PULSEAUDIO_DetectDevices(void)
static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
SDL_Semaphore *ready_sem = SDL_CreateSemaphore(0);
@ -875,6 +950,19 @@ static void PULSEAUDIO_DetectDevices(void)
WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_list(pulseaudio_context, SourceInfoCallback, (void *)((intptr_t)SDL_TRUE)));
PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
SDL_AudioDevice *device;
device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)default_sink_index + 1));
if (device) {
SDL_UnlockMutex(device->lock);
*default_output = device;
}
device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)default_source_index + 1));
if (device) {
SDL_UnlockMutex(device->lock);
*default_capture = device;
}
/* ok, we have a sane list, let's set up hotplug notifications now... */
SDL_AtomicSet(&pulseaudio_hotplug_thread_active, 1);
pulseaudio_hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, ready_sem); /* !!! FIXME: this can probably survive in significantly less stack space. */
@ -937,6 +1025,9 @@ static void PULSEAUDIO_Deinitialize(void)
SDL_free(default_source_name);
default_source_name = NULL;
default_source_index = 0;
default_sink_index = 0;
UnloadPulseAudioLibrary();
}

View File

@ -86,9 +86,7 @@ close_audio(void)
static void
open_audio(void)
{
SDL_AudioDeviceID *devices = SDL_GetAudioOutputDevices(NULL);
device = devices ? SDL_OpenAudioDevice(devices[0], &wave.spec) : 0;
SDL_free(devices);
device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec);
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError());
SDL_free(wave.sound);