diff --git a/include/SDL_audio.h b/include/SDL_audio.h index 01d197878..d759dfad2 100644 --- a/include/SDL_audio.h +++ b/include/SDL_audio.h @@ -487,6 +487,7 @@ extern DECLSPEC int SDLCALL SDL_GetNumAudioDevices(int iscapture); * \since This function is available since SDL 2.0.0. * * \sa SDL_GetNumAudioDevices + * \sa SDL_GetDefaultAudioInfo */ extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index, int iscapture); @@ -512,12 +513,48 @@ extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index, * \since This function is available since SDL 2.0.16. * * \sa SDL_GetNumAudioDevices + * \sa SDL_GetDefaultAudioInfo */ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceSpec(int index, int iscapture, SDL_AudioSpec *spec); +/** + * Get the name and preferred format of the default audio device. + * + * Some (but not all!) platforms have an isolated mechanism to get information + * about the "default" device. This can actually be a completely different + * device that's not in the list you get from SDL_GetAudioDeviceSpec(). It can + * even be a network address! (This is discussed in SDL_OpenAudioDevice().) + * + * As a result, this call is not guaranteed to be performant, as it can query + * the sound server directly every time, unlike the other query functions. You + * should call this function sparingly! + * + * `spec` will be filled with the sample rate, sample format, and channel + * count, if a default device exists on the system. If `name` is provided, will + * be filled with either a dynamically-allocated UTF-8 string or NULL. + * + * \param name A pointer to be filled with the name of the default device (can + be NULL). Please call SDL_free() when you are done with this + pointer! + * \param spec The SDL_AudioSpec to be initialized by this function. + * \param iscapture non-zero to query the default recording device, zero to + * query the default output device. + * \returns 0 on success, nonzero on error + * + * \since This function is available since SDL 2.24.0. + * + * \sa SDL_GetAudioDeviceName + * \sa SDL_GetAudioDeviceSpec + * \sa SDL_OpenAudioDevice + */ +extern DECLSPEC int SDLCALL SDL_GetDefaultAudioInfo(char **name, + SDL_AudioSpec *spec, + int iscapture); + + /** * Open a specific audio device. * diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 3ce3cb9d5..e2306bc03 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1154,6 +1154,24 @@ SDL_GetAudioDeviceSpec(int index, int iscapture, SDL_AudioSpec *spec) } +int +SDL_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +{ + if (spec == NULL) { + return SDL_InvalidParamError("spec"); + } + + if (!SDL_GetCurrentAudioDriver()) { + return SDL_SetError("Audio subsystem is not initialized"); + } + + if (current_audio.impl.GetDefaultAudioInfo == NULL) { + return SDL_Unsupported(); + } + return current_audio.impl.GetDefaultAudioInfo(name, spec, iscapture); +} + + static void close_audio_device(SDL_AudioDevice * device) { diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index d7e168534..0f98f90a9 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -78,6 +78,7 @@ typedef struct SDL_AudioDriverImpl void (*UnlockDevice) (_THIS); void (*FreeDeviceHandle) (void *handle); /**< SDL is done with handle from SDL_AddAudioDevice() */ void (*Deinitialize) (void); + int (*GetDefaultAudioInfo) (char **name, SDL_AudioSpec *spec, int iscapture); /* !!! FIXME: add pause(), so we can optimize instead of mixing silence. */ diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c index b4b2fa2a6..19f115415 100644 --- a/src/audio/directsound/SDL_directsound.c +++ b/src/audio/directsound/SDL_directsound.c @@ -156,6 +156,17 @@ DSOUND_FreeDeviceHandle(void *handle) SDL_free(handle); } +static int +DSOUND_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +{ +#if HAVE_MMDEVICEAPI_H + if (SupportsIMMDevice) { + return SDL_IMMDevice_GetDefaultAudioInfo(name, spec, iscapture); + } +#endif /* HAVE_MMDEVICEAPI_H */ + return SDL_Unsupported(); +} + static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data) { @@ -615,6 +626,7 @@ DSOUND_Init(SDL_AudioDriverImpl * impl) impl->CloseDevice = DSOUND_CloseDevice; impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle; impl->Deinitialize = DSOUND_Deinitialize; + impl->GetDefaultAudioInfo = DSOUND_GetDefaultAudioInfo; impl->HasCaptureSupport = SDL_TRUE; diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index 387028edb..3d4bec3b0 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -30,6 +30,7 @@ #include #include +#include /* * The following keys are defined for compatability when building against older versions of Pipewire @@ -249,7 +250,11 @@ struct io_node SDL_bool is_capture; SDL_AudioSpec spec; - char name[]; + /* FIXME: These sizes are arbitrary! */ + #define MAX_FRIENDLY_NAME 256 + #define MAX_IDENTIFIER_PATH 256 + char name[MAX_FRIENDLY_NAME]; /* Friendly name */ + char path[MAX_IDENTIFIER_PATH]; /* OS identifier (i.e. ALSA endpoint) */ }; /* The global hotplug thread and associated objects. */ @@ -265,8 +270,8 @@ static int hotplug_init_seq_val; static SDL_bool hotplug_init_complete; static SDL_bool hotplug_events_enabled; -static Uint32 pipewire_default_sink_id = SPA_ID_INVALID; -static Uint32 pipewire_default_source_id = SPA_ID_INVALID; +static char *pipewire_default_sink_id = NULL; +static char *pipewire_default_source_id = NULL; /* The active node list */ static SDL_bool @@ -324,10 +329,10 @@ io_list_sort() /* Find and move the default nodes to the beginning of the list */ spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { - if (n->id == pipewire_default_sink_id) { + if (pipewire_default_sink_id != NULL && SDL_strcmp(n->path, pipewire_default_sink_id) == 0) { default_sink = n; spa_list_remove(&n->link); - } else if (n->id == pipewire_default_source_id) { + } else if (pipewire_default_source_id != NULL && SDL_strcmp(n->path, pipewire_default_source_id) == 0) { default_source = n; spa_list_remove(&n->link); } @@ -353,6 +358,18 @@ io_list_clear() } } +static struct io_node* +io_list_get(char *path) +{ + struct io_node *n, *temp; + spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { + if (SDL_strcmp(n->path, path) == 0) { + return n; + } + } + return NULL; +} + static void node_object_destroy(struct node_object *node) { @@ -591,17 +608,43 @@ node_event_param(void *object, int seq, uint32_t id, uint32_t index, uint32_t ne static const struct pw_node_events interface_node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info, .param = node_event_param }; +static char* +get_name_from_json(const char *json) +{ + struct spa_json parser[2]; + char key[7]; /* "name" */ + char value[MAX_IDENTIFIER_PATH]; + spa_json_init(&parser[0], json, SDL_strlen(json)); + if (spa_json_enter_object(&parser[0], &parser[1]) <= 0) { + /* Not actually JSON */ + return NULL; + } + if (spa_json_get_string(&parser[1], key, sizeof(key)) <= 0) { + /* Not actually a key/value pair */ + return NULL; + } + if (spa_json_get_string(&parser[1], value, sizeof(value)) <= 0) { + /* Somehow had a key with no value? */ + return NULL; + } + return SDL_strdup(value); +} + /* Metadata node callback */ static int metadata_property(void *object, Uint32 subject, const char *key, const char *type, const char *value) { if (subject == PW_ID_CORE && key != NULL && value != NULL) { - Uint32 val = SDL_atoi(value); - if (!SDL_strcmp(key, "default.audio.sink")) { - pipewire_default_sink_id = val; + if (pipewire_default_sink_id != NULL) { + SDL_free(pipewire_default_sink_id); + } + pipewire_default_sink_id = get_name_from_json(value); } else if (!SDL_strcmp(key, "default.audio.source")) { - pipewire_default_source_id = val; + if (pipewire_default_source_id != NULL) { + SDL_free(pipewire_default_source_id); + } + pipewire_default_source_id = get_name_from_json(value); } } @@ -623,9 +666,9 @@ registry_event_global_callback(void *object, uint32_t id, uint32_t permissions, if (media_class) { const char *node_desc; + const char *node_path; struct io_node *io; SDL_bool is_capture; - int str_buffer_len; /* Just want sink and capture */ if (!SDL_strcasecmp(media_class, "Audio/Sink")) { @@ -637,8 +680,9 @@ registry_event_global_callback(void *object, uint32_t id, uint32_t permissions, } node_desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); + node_path = spa_dict_lookup(props, PW_KEY_NODE_NAME); - if (node_desc) { + if (node_desc && node_path) { node = node_object_new(id, type, version, &interface_node_events, &interface_core_events); if (node == NULL) { SDL_SetError("Pipewire: Failed to allocate interface node"); @@ -646,8 +690,7 @@ registry_event_global_callback(void *object, uint32_t id, uint32_t permissions, } /* Allocate and initialize the I/O node information struct */ - str_buffer_len = SDL_strlen(node_desc) + 1; - node->userdata = io = SDL_calloc(1, sizeof(struct io_node) + str_buffer_len); + node->userdata = io = SDL_calloc(1, sizeof(struct io_node)); if (io == NULL) { node_object_destroy(node); SDL_OutOfMemory(); @@ -658,7 +701,8 @@ registry_event_global_callback(void *object, uint32_t id, uint32_t permissions, io->id = id; io->is_capture = is_capture; io->spec.format = AUDIO_F32; /* Pipewire uses floats internally, other formats require conversion. */ - SDL_strlcpy(io->name, node_desc, str_buffer_len); + SDL_strlcpy(io->name, node_desc, sizeof(io->name)); + SDL_strlcpy(io->path, node_path, sizeof(io->path)); /* Update sync points */ hotplug_core_sync(node); @@ -744,8 +788,14 @@ hotplug_loop_destroy() hotplug_init_complete = SDL_FALSE; hotplug_events_enabled = SDL_FALSE; - pipewire_default_sink_id = SPA_ID_INVALID; - pipewire_default_source_id = SPA_ID_INVALID; + if (pipewire_default_sink_id != NULL) { + SDL_free(pipewire_default_sink_id); + pipewire_default_sink_id = NULL; + } + if (pipewire_default_source_id != NULL) { + SDL_free(pipewire_default_source_id); + pipewire_default_source_id = NULL; + } if (hotplug_registry) { PIPEWIRE_pw_proxy_destroy((struct pw_proxy *)hotplug_registry); @@ -1228,6 +1278,38 @@ static void PIPEWIRE_CloseDevice(_THIS) SDL_free(this->hidden); } +static int +PIPEWIRE_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +{ + struct io_node *node; + char *target; + if (iscapture) { + if (pipewire_default_source_id == NULL) { + return SDL_SetError("PipeWire could not find a default source"); + } + target = pipewire_default_source_id; + } else { + if (pipewire_default_sink_id == NULL) { + return SDL_SetError("PipeWire could not find a default sink"); + } + target = pipewire_default_sink_id; + } + + PIPEWIRE_pw_thread_loop_lock(hotplug_loop); + node = io_list_get(target); + if (node == NULL) { + PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); + return SDL_SetError("PipeWire device list is out of sync with defaults"); + } + + if (name != NULL) { + *name = SDL_strdup(node->name); + } + SDL_copyp(spec, &node->spec); + PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); + return 0; +} + static void PIPEWIRE_Deinitialize() { @@ -1255,10 +1337,11 @@ PIPEWIRE_Init(SDL_AudioDriverImpl *impl) } /* Set the function pointers */ - impl->DetectDevices = PIPEWIRE_DetectDevices; - impl->OpenDevice = PIPEWIRE_OpenDevice; - impl->CloseDevice = PIPEWIRE_CloseDevice; - impl->Deinitialize = PIPEWIRE_Deinitialize; + impl->DetectDevices = PIPEWIRE_DetectDevices; + impl->OpenDevice = PIPEWIRE_OpenDevice; + impl->CloseDevice = PIPEWIRE_CloseDevice; + impl->Deinitialize = PIPEWIRE_Deinitialize; + impl->GetDefaultAudioInfo = PIPEWIRE_GetDefaultAudioInfo; impl->HasCaptureSupport = SDL_TRUE; impl->ProvidesOwnCallbackThread = SDL_TRUE; diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 85e1386c2..b61d99894 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -118,6 +118,7 @@ static pa_operation * (*PULSEAUDIO_pa_stream_flush) (pa_stream *, static int (*PULSEAUDIO_pa_stream_disconnect) (pa_stream *); static void (*PULSEAUDIO_pa_stream_unref) (pa_stream *); static void (*PULSEAUDIO_pa_stream_set_write_callback)(pa_stream *, pa_stream_request_cb_t, void *); +static pa_operation * (*PULSEAUDIO_pa_context_get_server_info)(pa_context *, pa_server_info_cb_t, void *); static int load_pulseaudio_syms(void); @@ -230,6 +231,7 @@ load_pulseaudio_syms(void) SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto); SDL_PULSEAUDIO_SYM(pa_strerror); SDL_PULSEAUDIO_SYM(pa_stream_set_write_callback); + SDL_PULSEAUDIO_SYM(pa_context_get_server_info); return 0; } @@ -527,7 +529,7 @@ SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int is_last, vo static SDL_bool FindDeviceName(struct SDL_PrivateAudioData *h, const SDL_bool iscapture, void *handle) { - const uint32_t idx = ((uint32_t) ((size_t) handle)) - 1; + const uint32_t idx = ((uint32_t) ((intptr_t) handle)) - 1; if (handle == NULL) { /* NULL == default device. */ return SDL_TRUE; @@ -691,6 +693,13 @@ static pa_mainloop *hotplug_mainloop = NULL; static pa_context *hotplug_context = NULL; static SDL_Thread *hotplug_thread = NULL; +/* 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. */ +static char *default_sink_name = NULL; +static char *default_source_name = NULL; + /* device handles are device index + 1, cast to void*, so we never pass a NULL. */ static SDL_AudioFormat @@ -721,6 +730,7 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) { SDL_AudioSpec spec; + SDL_bool add = (SDL_bool) ((intptr_t) data); if (i) { spec.freq = i->sample_spec.rate; spec.channels = i->sample_spec.channels; @@ -731,7 +741,16 @@ SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) spec.callback = NULL; spec.userdata = NULL; - SDL_AddAudioDevice(SDL_FALSE, i->description, &spec, (void *) ((size_t) i->index+1)); + if (add) { + 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) { + if (default_sink_name != NULL) { + SDL_free(default_sink_name); + } + default_sink_name = SDL_strdup(i->description); + } } } @@ -740,6 +759,7 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) { SDL_AudioSpec spec; + SDL_bool add = (SDL_bool) ((intptr_t) data); if (i) { /* Maybe skip "monitor" sources. These are just output from other sinks. */ if (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX)) { @@ -752,30 +772,59 @@ SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *da spec.callback = NULL; spec.userdata = NULL; - SDL_AddAudioDevice(SDL_TRUE, i->description, &spec, (void *) ((size_t) i->index+1)); + if (add) { + 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) { + if (default_source_name != NULL) { + SDL_free(default_source_name); + } + default_source_name = SDL_strdup(i->description); + } } } } +static void +ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data) +{ + if (default_sink_path != NULL) { + SDL_free(default_sink_path); + } + if (default_source_path != NULL) { + SDL_free(default_source_path); + } + default_sink_path = SDL_strdup(i->default_sink_name); + default_source_path = SDL_strdup(i->default_source_name); +} + /* 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) { /* we only care about add/remove events. */ + 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... */ - if (added && sink) { - PULSEAUDIO_pa_context_get_sink_info_by_index(hotplug_context, idx, SinkInfoCallback, NULL); - } else if (added && source) { - PULSEAUDIO_pa_context_get_source_info_by_index(hotplug_context, idx, SourceInfoCallback, NULL); + if ((added || changed) && sink) { + if (changed) { + PULSEAUDIO_pa_context_get_server_info(hotplug_context, ServerInfoCallback, NULL); + } + PULSEAUDIO_pa_context_get_sink_info_by_index(hotplug_context, idx, SinkInfoCallback, (void*) ((intptr_t) added)); + } else if ((added || changed) && source) { + if (changed) { + PULSEAUDIO_pa_context_get_server_info(hotplug_context, ServerInfoCallback, NULL); + } + PULSEAUDIO_pa_context_get_source_info_by_index(hotplug_context, idx, SourceInfoCallback, (void*) ((intptr_t) added)); } else if (removed && (sink || source)) { /* removes we can handle just with the device index. */ - SDL_RemoveAudioDevice(source != 0, (void *) ((size_t) idx+1)); + SDL_RemoveAudioDevice(source != 0, (void *) ((intptr_t) idx+1)); } } } @@ -796,13 +845,46 @@ HotplugThread(void *data) static void PULSEAUDIO_DetectDevices() { - WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_sink_info_list(hotplug_context, SinkInfoCallback, NULL)); - WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_source_info_list(hotplug_context, SourceInfoCallback, NULL)); + WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_server_info(hotplug_context, ServerInfoCallback, NULL)); + WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_sink_info_list(hotplug_context, SinkInfoCallback, (void*) ((intptr_t) SDL_TRUE))); + WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_source_info_list(hotplug_context, SourceInfoCallback, (void*) ((intptr_t) SDL_TRUE))); /* ok, we have a sane list, let's set up hotplug notifications now... */ hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, NULL); } +static int +PULSEAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +{ + int i; + int numdevices; + + char *target; + if (iscapture) { + if (default_source_name == NULL) { + return SDL_SetError("PulseAudio could not find a default source"); + } + target = default_source_name; + } else { + if (default_sink_name == NULL) { + return SDL_SetError("PulseAudio could not find a default sink"); + } + target = default_sink_name; + } + + numdevices = SDL_GetNumAudioDevices(iscapture); + for (i = 0; i < numdevices; i += 1) { + if (SDL_strcmp(SDL_GetAudioDeviceName(i, iscapture), target) == 0) { + if (name != NULL) { + *name = SDL_strdup(target); + } + SDL_GetAudioDeviceSpec(i, iscapture, spec); + return 0; + } + } + return SDL_SetError("Could not find default PulseAudio device"); +} + static void PULSEAUDIO_Deinitialize(void) { @@ -816,6 +898,23 @@ PULSEAUDIO_Deinitialize(void) hotplug_mainloop = NULL; hotplug_context = NULL; + if (default_sink_path != NULL) { + SDL_free(default_sink_path); + default_sink_path = NULL; + } + if (default_source_path != NULL) { + SDL_free(default_source_path); + default_source_path = NULL; + } + if (default_sink_name != NULL) { + SDL_free(default_sink_name); + default_sink_name = NULL; + } + if (default_source_name != NULL) { + SDL_free(default_source_name); + default_source_name = NULL; + } + UnloadPulseAudioLibrary(); } @@ -843,6 +942,7 @@ PULSEAUDIO_Init(SDL_AudioDriverImpl * impl) impl->Deinitialize = PULSEAUDIO_Deinitialize; impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice; impl->FlushCapture = PULSEAUDIO_FlushCapture; + impl->GetDefaultAudioInfo = PULSEAUDIO_GetDefaultAudioInfo; impl->HasCaptureSupport = SDL_TRUE; diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c index a678ca3e5..5b6b82164 100644 --- a/src/audio/wasapi/SDL_wasapi.c +++ b/src/audio/wasapi/SDL_wasapi.c @@ -634,6 +634,7 @@ WASAPI_Init(SDL_AudioDriverImpl * impl) impl->FlushCapture = WASAPI_FlushCapture; impl->CloseDevice = WASAPI_CloseDevice; impl->Deinitialize = WASAPI_Deinitialize; + impl->GetDefaultAudioInfo = WASAPI_GetDefaultAudioInfo; impl->HasCaptureSupport = SDL_TRUE; return SDL_TRUE; /* this audio target is available. */ diff --git a/src/audio/wasapi/SDL_wasapi.h b/src/audio/wasapi/SDL_wasapi.h index d4e81a351..69060677d 100644 --- a/src/audio/wasapi/SDL_wasapi.h +++ b/src/audio/wasapi/SDL_wasapi.h @@ -64,6 +64,7 @@ void WASAPI_UnrefDevice(_THIS); int WASAPI_PlatformInit(void); void WASAPI_PlatformDeinit(void); void WASAPI_EnumerateEndpoints(void); +int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture); int WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery); void WASAPI_PlatformThreadInit(_THIS); void WASAPI_PlatformThreadDeinit(_THIS); diff --git a/src/audio/wasapi/SDL_wasapi_win32.c b/src/audio/wasapi/SDL_wasapi_win32.c index ba8962f29..d76c80374 100644 --- a/src/audio/wasapi/SDL_wasapi_win32.c +++ b/src/audio/wasapi/SDL_wasapi_win32.c @@ -145,6 +145,12 @@ WASAPI_EnumerateEndpoints(void) SDL_IMMDevice_EnumerateEndpoints(SDL_FALSE); } +int +WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +{ + return SDL_IMMDevice_GetDefaultAudioInfo(name, spec, iscapture); +} + void WASAPI_PlatformDeleteActivationHandler(void *handler) { diff --git a/src/audio/wasapi/SDL_wasapi_winrt.cpp b/src/audio/wasapi/SDL_wasapi_winrt.cpp index 11ea0de06..acaaf0be5 100644 --- a/src/audio/wasapi/SDL_wasapi_winrt.cpp +++ b/src/audio/wasapi/SDL_wasapi_winrt.cpp @@ -243,6 +243,12 @@ WASAPI_PlatformDeleteActivationHandler(void *handler) ((SDL_WasapiActivationHandler *) handler)->Release(); } +int +WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +{ + return SDL_Unsupported(); +} + int WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery) { diff --git a/src/core/windows/SDL_immdevice.c b/src/core/windows/SDL_immdevice.c index 3b5714c23..d626ddf79 100644 --- a/src/core/windows/SDL_immdevice.c +++ b/src/core/windows/SDL_immdevice.c @@ -471,6 +471,40 @@ SDL_IMMDevice_EnumerateEndpoints(SDL_bool useguid) IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); } +int +SDL_IMMDevice_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +{ + WAVEFORMATEXTENSIBLE fmt; + IMMDevice *device = NULL; + char *filler; + GUID morefiller; + const EDataFlow dataflow = iscapture ? eCapture : eRender; + HRESULT ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_IMMDevice_role, &device); + + if (FAILED(ret)) { + SDL_assert(device == NULL); + return WIN_SetErrorFromHRESULT("WASAPI can't find default audio endpoint", ret); + } + + if (name == NULL) { + name = &filler; + } + + SDL_zero(fmt); + GetMMDeviceInfo(device, name, &fmt, &morefiller); + IMMDevice_Release(device); + + if (name == &filler) { + SDL_free(filler); + } + + SDL_zerop(spec); + spec->channels = (Uint8)fmt.Format.nChannels; + spec->freq = fmt.Format.nSamplesPerSec; + spec->format = WaveFormatToSDLFormat((WAVEFORMATEX *) &fmt); + return 0; +} + SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat) { diff --git a/src/core/windows/SDL_immdevice.h b/src/core/windows/SDL_immdevice.h index cdb87c5fb..007775f9e 100644 --- a/src/core/windows/SDL_immdevice.h +++ b/src/core/windows/SDL_immdevice.h @@ -33,6 +33,7 @@ int SDL_IMMDevice_Init(void); void SDL_IMMDevice_Quit(void); int SDL_IMMDevice_Get(LPCWSTR devid, IMMDevice **device, SDL_bool iscapture); void SDL_IMMDevice_EnumerateEndpoints(SDL_bool useguid); +int SDL_IMMDevice_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture); SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat); diff --git a/src/dynapi/SDL2.exports b/src/dynapi/SDL2.exports index 082de1001..e5aed22d0 100644 --- a/src/dynapi/SDL2.exports +++ b/src/dynapi/SDL2.exports @@ -853,3 +853,4 @@ # ++'_SDL_GDKRunApp'.'SDL2.dll'.'SDL_GDKRunApp' ++'_SDL_GetOriginalMemoryFunctions'.'SDL2.dll'.'SDL_GetOriginalMemoryFunctions' ++'_SDL_ResetKeyboard'.'SDL2.dll'.'SDL_ResetKeyboard' +++'_SDL_GetDefaultAudioInfo'.'SDL2.dll'.'SDL_GetDefaultAudioInfo' diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 18e406e5f..8e26c34ac 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -879,3 +879,4 @@ #define SDL_GDKRunApp SDL_GDKRunApp_REAL #define SDL_GetOriginalMemoryFunctions SDL_GetOriginalMemoryFunctions_REAL #define SDL_ResetKeyboard SDL_ResetKeyboard_REAL +#define SDL_GetDefaultAudioInfo SDL_GetDefaultAudioInfo_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index a76806acc..6b4c5c7dd 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -962,3 +962,4 @@ SDL_DYNAPI_PROC(int,SDL_GDKRunApp,(SDL_main_func a, void *b),(a,b),return) #endif SDL_DYNAPI_PROC(void,SDL_GetOriginalMemoryFunctions,(SDL_malloc_func *a, SDL_calloc_func *b, SDL_realloc_func *c, SDL_free_func *d),(a,b,c,d),) SDL_DYNAPI_PROC(void,SDL_ResetKeyboard,(void),(),) +SDL_DYNAPI_PROC(int,SDL_GetDefaultAudioInfo,(char **a, SDL_AudioSpec *b, int c),(a,b,c),return)