From 30f41c02aec763d32e62351452da9ef582bc3472 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 6 Mar 2026 13:30:59 -0800 Subject: Move contrib libraries to contrib repo --- contrib/SDL-3.2.8/src/audio/SDL_audio.c | 2534 ------------------------------- 1 file changed, 2534 deletions(-) delete mode 100644 contrib/SDL-3.2.8/src/audio/SDL_audio.c (limited to 'contrib/SDL-3.2.8/src/audio/SDL_audio.c') diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audio.c b/contrib/SDL-3.2.8/src/audio/SDL_audio.c deleted file mode 100644 index 583a159..0000000 --- a/contrib/SDL-3.2.8/src/audio/SDL_audio.c +++ /dev/null @@ -1,2534 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ -#include "SDL_internal.h" - -#include "SDL_audio_c.h" -#include "SDL_sysaudio.h" -#include "../thread/SDL_systhread.h" - -// Available audio drivers -static const AudioBootStrap *const bootstrap[] = { -#ifdef SDL_AUDIO_DRIVER_PRIVATE - &PRIVATEAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO -#ifdef SDL_AUDIO_DRIVER_PIPEWIRE - &PIPEWIRE_PREFERRED_bootstrap, -#endif - &PULSEAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_PIPEWIRE - &PIPEWIRE_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_ALSA - &ALSA_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_SNDIO - &SNDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_NETBSD - &NETBSDAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_WASAPI - &WASAPI_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_DSOUND - &DSOUND_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_HAIKU - &HAIKUAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_COREAUDIO - &COREAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_AAUDIO - &AAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_OPENSLES - &OPENSLES_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_PS2 - &PS2AUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_PSP - &PSPAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_VITA - &VITAAUD_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_N3DS - &N3DSAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN - &EMSCRIPTENAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_JACK - &JACK_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_OSS - &DSP_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_QNX - &QSAAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_DISK - &DISKAUDIO_bootstrap, -#endif -#ifdef SDL_AUDIO_DRIVER_DUMMY - &DUMMYAUDIO_bootstrap, -#endif - NULL -}; - -static SDL_AudioDriver current_audio; - -// Deduplicated list of audio bootstrap drivers. -static const AudioBootStrap *deduped_bootstrap[SDL_arraysize(bootstrap) - 1]; - -int SDL_GetNumAudioDrivers(void) -{ - static int num_drivers = -1; - - if (num_drivers >= 0) { - return num_drivers; - } - - num_drivers = 0; - - // Build a list of unique audio drivers. - for (int i = 0; bootstrap[i] != NULL; ++i) { - bool duplicate = false; - for (int j = 0; j < i; ++j) { - if (SDL_strcmp(bootstrap[i]->name, bootstrap[j]->name) == 0) { - duplicate = true; - break; - } - } - - if (!duplicate) { - deduped_bootstrap[num_drivers++] = bootstrap[i]; - } - } - - return num_drivers; -} - -const char *SDL_GetAudioDriver(int index) -{ - if (index >= 0 && index < SDL_GetNumAudioDrivers()) { - return deduped_bootstrap[index]->name; - } - SDL_InvalidParamError("index"); - return NULL; -} - -const char *SDL_GetCurrentAudioDriver(void) -{ - return current_audio.name; -} - -int SDL_GetDefaultSampleFramesFromFreq(const int freq) -{ - const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES); - if (hint) { - const int val = SDL_atoi(hint); - if (val > 0) { - return val; - } - } - - if (freq <= 22050) { - return 512; - } else if (freq <= 48000) { - return 1024; - } else if (freq <= 96000) { - return 2048; - } else { - return 4096; - } -} - -int *SDL_ChannelMapDup(const int *origchmap, int channels) -{ - const size_t chmaplen = sizeof (*origchmap) * channels; - int *chmap = (int *)SDL_malloc(chmaplen); - if (chmap) { - SDL_memcpy(chmap, origchmap, chmaplen); - } - return chmap; -} - -void OnAudioStreamCreated(SDL_AudioStream *stream) -{ - SDL_assert(stream != NULL); - - // NOTE that you can create an audio stream without initializing the audio subsystem, - // but it will not be automatically destroyed during a later call to SDL_Quit! - // You must explicitly destroy it yourself! - if (current_audio.device_hash_lock) { - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (current_audio.existing_streams) { - current_audio.existing_streams->prev = stream; - } - stream->prev = NULL; - stream->next = current_audio.existing_streams; - current_audio.existing_streams = stream; - SDL_UnlockRWLock(current_audio.device_hash_lock); - } -} - -void OnAudioStreamDestroy(SDL_AudioStream *stream) -{ - SDL_assert(stream != NULL); - - // NOTE that you can create an audio stream without initializing the audio subsystem, - // but it will not be automatically destroyed during a later call to SDL_Quit! - // You must explicitly destroy it yourself! - if (current_audio.device_hash_lock) { - // this isn't really part of the "device list" but it's a convenient lock to use here. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (stream->prev) { - stream->prev->next = stream->next; - } - if (stream->next) { - stream->next->prev = stream->prev; - } - if (stream == current_audio.existing_streams) { - current_audio.existing_streams = stream->next; - } - SDL_UnlockRWLock(current_audio.device_hash_lock); - } -} - -// device should be locked when calling this. -static bool AudioDeviceCanUseSimpleCopy(SDL_AudioDevice *device) -{ - SDL_assert(device != NULL); - return ( - device->logical_devices && // there's a logical device - !device->logical_devices->next && // there's only _ONE_ logical device - !device->logical_devices->postmix && // there isn't a postmix callback - device->logical_devices->bound_streams && // there's a bound stream - !device->logical_devices->bound_streams->next_binding // there's only _ONE_ bound stream. - ); -} - -// should hold device->lock before calling. -static void UpdateAudioStreamFormatsPhysical(SDL_AudioDevice *device) -{ - if (!device) { - return; - } - - const bool recording = device->recording; - SDL_AudioSpec spec; - SDL_copyp(&spec, &device->spec); - - const SDL_AudioFormat devformat = spec.format; - - if (!recording) { - const bool simple_copy = AudioDeviceCanUseSimpleCopy(device); - device->simple_copy = simple_copy; - if (!simple_copy) { - spec.format = SDL_AUDIO_F32; // mixing and postbuf operates in float32 format. - } - } - - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { - if (recording) { - const bool need_float32 = (logdev->postmix || logdev->gain != 1.0f); - spec.format = need_float32 ? SDL_AUDIO_F32 : devformat; - } - - for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { - // set the proper end of the stream to the device's format. - // SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec. - SDL_AudioSpec *streamspec = recording ? &stream->src_spec : &stream->dst_spec; - int **streamchmap = recording ? &stream->src_chmap : &stream->dst_chmap; - SDL_LockMutex(stream->lock); - SDL_copyp(streamspec, &spec); - SetAudioStreamChannelMap(stream, streamspec, streamchmap, device->chmap, device->spec.channels, -1); // this should be fast for normal cases, though! - SDL_UnlockMutex(stream->lock); - } - } -} - -bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b, const int *channel_map_a, const int *channel_map_b) -{ - if ((a->format != b->format) || (a->channels != b->channels) || (a->freq != b->freq) || ((channel_map_a != NULL) != (channel_map_b != NULL))) { - return false; - } else if (channel_map_a && (SDL_memcmp(channel_map_a, channel_map_b, sizeof (*channel_map_a) * a->channels) != 0)) { - return false; - } - return true; -} - -bool SDL_AudioChannelMapsEqual(int channels, const int *channel_map_a, const int *channel_map_b) -{ - if (channel_map_a == channel_map_b) { - return true; - } else if ((channel_map_a != NULL) != (channel_map_b != NULL)) { - return false; - } else if (channel_map_a && (SDL_memcmp(channel_map_a, channel_map_b, sizeof (*channel_map_a) * channels) != 0)) { - return false; - } - return true; -} - - -// Zombie device implementation... - -// These get used when a device is disconnected or fails, so audiostreams don't overflow with data that isn't being -// consumed and apps relying on audio callbacks don't stop making progress. -static bool ZombieWaitDevice(SDL_AudioDevice *device) -{ - if (!SDL_GetAtomicInt(&device->shutdown)) { - const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); - SDL_Delay((frames * 1000) / device->spec.freq); - } - return true; -} - -static bool ZombiePlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) -{ - return true; // no-op, just throw the audio away. -} - -static Uint8 *ZombieGetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) -{ - return device->work_buffer; -} - -static int ZombieRecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) -{ - // return a full buffer of silence every time. - SDL_memset(buffer, device->silence_value, buflen); - return buflen; -} - -static void ZombieFlushRecording(SDL_AudioDevice *device) -{ - // no-op, this is all imaginary. -} - - - -// device management and hotplug... - - -/* SDL_AudioDevice, in SDL3, represents a piece of physical hardware, whether it is in use or not, so these objects exist as long as - the system-level device is available. - - Physical devices get destroyed for three reasons: - - They were lost to the system (a USB cable is kicked out, etc). - - They failed for some other unlikely reason at the API level (which is _also_ probably a USB cable being kicked out). - - We are shutting down, so all allocated resources are being freed. - - They are _not_ destroyed because we are done using them (when we "close" a playing device). -*/ -static void ClosePhysicalAudioDevice(SDL_AudioDevice *device); - - -SDL_COMPILE_TIME_ASSERT(check_lowest_audio_default_value, SDL_AUDIO_DEVICE_DEFAULT_RECORDING < SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK); - -static SDL_AtomicInt last_device_instance_id; // increments on each device add to provide unique instance IDs -static SDL_AudioDeviceID AssignAudioDeviceInstanceId(bool recording, bool islogical) -{ - /* Assign an instance id! Start at 2, in case there are things from the SDL2 era that still think 1 is a special value. - Also, make sure we don't assign SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, etc. */ - - // The bottom two bits of the instance id tells you if it's an playback device (1<<0), and if it's a physical device (1<<1). - const SDL_AudioDeviceID flags = (recording ? 0 : (1<<0)) | (islogical ? 0 : (1<<1)); - - const SDL_AudioDeviceID instance_id = (((SDL_AudioDeviceID) (SDL_AtomicIncRef(&last_device_instance_id) + 1)) << 2) | flags; - SDL_assert( (instance_id >= 2) && (instance_id < SDL_AUDIO_DEVICE_DEFAULT_RECORDING) ); - return instance_id; -} - -bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid) -{ - return (devid & (1 << 1)) != 0; -} - -bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid) -{ - return (devid & (1 << 0)) != 0; -} - -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); - - if (!SDL_GetCurrentAudioDriver()) { - SDL_SetError("Audio subsystem is not initialized"); - *_device = NULL; - return NULL; - } - - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = NULL; - - // bit #1 of devid is set for physical devices and unset for logical. - const bool islogical = !(devid & (1<<1)); - 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; - SDL_assert(device != NULL); - RefPhysicalAudioDevice(device); // reference it, in case the logical device migrates to a new default. - } - SDL_UnlockRWLock(current_audio.device_hash_lock); - - if (logdev) { - // we have to release the device_hash_lock before we take the device lock, to avoid deadlocks, so do a loop - // to make sure the correct physical device gets locked, in case we're in a race with the default changing. - while (true) { - SDL_LockMutex(device->lock); - SDL_AudioDevice *recheck_device = (SDL_AudioDevice *) SDL_GetAtomicPointer((void **) &logdev->physical_device); - if (device == recheck_device) { - break; - } - - // default changed from under us! Try again! - RefPhysicalAudioDevice(recheck_device); - SDL_UnlockMutex(device->lock); - UnrefPhysicalAudioDevice(device); - device = recheck_device; - } - } - } - - if (!logdev) { - SDL_SetError("Invalid audio device instance ID"); - } - - *_device = device; - 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 bool islogical = !(devid & (1<<1)); - 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 bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) || (devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING)); - if (!wants_default) { - return ObtainPhysicalAudioDevice(devid); - } - - const SDL_AudioDeviceID orig_devid = devid; - - while (true) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); - if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) { - devid = current_audio.default_playback_device_id; - } else if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) { - devid = current_audio.default_recording_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... - bool got_it = false; - SDL_LockRWLockForReading(current_audio.device_hash_lock); - if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) && (devid == current_audio.default_playback_device_id)) { - got_it = true; - } else if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) && (devid == current_audio.default_recording_device_id)) { - got_it = 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! -// 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) -{ - // Remove ourselves from the device_hash hashtable. - if (current_audio.device_hash) { // will be NULL while shutting down. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) logdev->instance_id); - SDL_UnlockRWLock(current_audio.device_hash_lock); - } - - // remove ourselves from the physical device's list of logical devices. - if (logdev->next) { - logdev->next->prev = logdev->prev; - } - if (logdev->prev) { - logdev->prev->next = logdev->next; - } - if (logdev->physical_device->logical_devices == logdev) { - logdev->physical_device->logical_devices = logdev->next; - } - - // unbind any still-bound streams... - SDL_AudioStream *next; - for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = next) { - SDL_LockMutex(stream->lock); - next = stream->next_binding; - stream->next_binding = NULL; - stream->prev_binding = NULL; - stream->bound_device = NULL; - SDL_UnlockMutex(stream->lock); - } - - UpdateAudioStreamFormatsPhysical(logdev->physical_device); - SDL_free(logdev); -} - -// this must not be called while `device` is still in a device list, or while a device's audio thread is still running. -static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device) -{ - if (!device) { - return; - } - - // Destroy any logical devices that still exist... - SDL_LockMutex(device->lock); // don't use ObtainPhysicalAudioDeviceObj because we don't want to change refcounts while destroying. - while (device->logical_devices) { - DestroyLogicalAudioDevice(device->logical_devices); - } - - ClosePhysicalAudioDevice(device); - - current_audio.impl.FreeDeviceHandle(device); - - SDL_UnlockMutex(device->lock); // don't use ReleaseAudioDevice because we don't want to change refcounts while destroying. - - SDL_DestroyMutex(device->lock); - SDL_DestroyCondition(device->close_cond); - SDL_free(device->work_buffer); - SDL_free(device->chmap); - SDL_free(device->name); - SDL_free(device); -} - -// Don't hold the device lock when calling this, as we may destroy the device! -void UnrefPhysicalAudioDevice(SDL_AudioDevice *device) -{ - if (SDL_AtomicDecRef(&device->refcount)) { - // take it out of the device list. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id)) { - SDL_AddAtomicInt(device->recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count, -1); - } - SDL_UnlockRWLock(current_audio.device_hash_lock); - DestroyPhysicalAudioDevice(device); // ...and nuke it. - } -} - -void RefPhysicalAudioDevice(SDL_AudioDevice *device) -{ - SDL_AtomicIncRef(&device->refcount); -} - -static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recording, const SDL_AudioSpec *spec, void *handle, SDL_AtomicInt *device_count) -{ - SDL_assert(name != NULL); - - SDL_LockRWLockForReading(current_audio.device_hash_lock); - const int shutting_down = SDL_GetAtomicInt(¤t_audio.shutting_down); - SDL_UnlockRWLock(current_audio.device_hash_lock); - if (shutting_down) { - return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment. - } - - SDL_AudioDevice *device = (SDL_AudioDevice *)SDL_calloc(1, sizeof(SDL_AudioDevice)); - if (!device) { - return NULL; - } - - device->name = SDL_strdup(name); - if (!device->name) { - SDL_free(device); - return NULL; - } - - device->lock = SDL_CreateMutex(); - if (!device->lock) { - SDL_free(device->name); - SDL_free(device); - return NULL; - } - - device->close_cond = SDL_CreateCondition(); - if (!device->close_cond) { - SDL_DestroyMutex(device->lock); - SDL_free(device->name); - SDL_free(device); - return NULL; - } - - SDL_SetAtomicInt(&device->shutdown, 0); - SDL_SetAtomicInt(&device->zombie, 0); - device->recording = recording; - SDL_copyp(&device->spec, spec); - SDL_copyp(&device->default_spec, spec); - device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq); - device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); - device->handle = handle; - - device->instance_id = AssignAudioDeviceInstanceId(recording, /*islogical=*/false); - - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - if (SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) { - SDL_AddAtomicInt(device_count, 1); - } else { - SDL_DestroyCondition(device->close_cond); - SDL_DestroyMutex(device->lock); - SDL_free(device->name); - SDL_free(device); - device = NULL; - } - SDL_UnlockRWLock(current_audio.device_hash_lock); - - RefPhysicalAudioDevice(device); // unref'd on device disconnect. - return device; -} - -static SDL_AudioDevice *CreateAudioRecordingDevice(const char *name, const SDL_AudioSpec *spec, void *handle) -{ - SDL_assert(current_audio.impl.HasRecordingSupport); - return CreatePhysicalAudioDevice(name, true, spec, handle, ¤t_audio.recording_device_count); -} - -static SDL_AudioDevice *CreateAudioPlaybackDevice(const char *name, const SDL_AudioSpec *spec, void *handle) -{ - return CreatePhysicalAudioDevice(name, false, spec, handle, ¤t_audio.playback_device_count); -} - -// The audio backends call this when a new device is plugged in. -SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_AudioSpec *inspec, void *handle) -{ - // device handles MUST be unique! If the target reuses the same handle for hardware with both recording and playback interfaces, wrap it in a pointer you SDL_malloc'd! - SDL_assert(SDL_FindPhysicalAudioDeviceByHandle(handle) == NULL); - - const SDL_AudioFormat default_format = recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT; - const int default_channels = recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS; - const int default_freq = recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY; - - SDL_AudioSpec spec; - SDL_zero(spec); - if (!inspec) { - spec.format = default_format; - spec.channels = default_channels; - spec.freq = default_freq; - } else { - spec.format = (inspec->format != 0) ? inspec->format : default_format; - spec.channels = (inspec->channels != 0) ? inspec->channels : default_channels; - spec.freq = (inspec->freq != 0) ? inspec->freq : default_freq; - } - - SDL_AudioDevice *device = recording ? CreateAudioRecordingDevice(name, &spec, handle) : CreateAudioPlaybackDevice(name, &spec, handle); - - // Add a device add event to the pending list, to be pushed when the event queue is pumped (away from any of our internal threads). - if (device) { - SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent)); - if (p) { // if allocation fails, you won't get an event, but we can't help that. - p->type = SDL_EVENT_AUDIO_DEVICE_ADDED; - p->devid = device->instance_id; - p->next = NULL; - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_assert(current_audio.pending_events_tail != NULL); - SDL_assert(current_audio.pending_events_tail->next == NULL); - current_audio.pending_events_tail->next = p; - current_audio.pending_events_tail = p; - SDL_UnlockRWLock(current_audio.device_hash_lock); - } - } - - return device; -} - -// Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. -void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) -{ - if (!device) { - return; - } - - // 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; - - ObtainPhysicalAudioDeviceObj(device); - - SDL_LockRWLockForReading(current_audio.device_hash_lock); - const SDL_AudioDeviceID devid = device->instance_id; - const bool is_default_device = ((devid == current_audio.default_playback_device_id) || (devid == current_audio.default_recording_device_id)); - SDL_UnlockRWLock(current_audio.device_hash_lock); - - const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&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 - // 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 - // progress will freeze, etc. - device->WaitDevice = ZombieWaitDevice; - device->GetDeviceBuf = ZombieGetDeviceBuf; - device->PlayDevice = ZombiePlayDevice; - device->WaitRecordingDevice = ZombieWaitDevice; - device->RecordDevice = ZombieRecordDevice; - device->FlushRecording = ZombieFlushRecording; - - // 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. - // (by "dump" we mean send a REMOVED event; the zombie will keep consuming audio data for these logical devices until explicitly closed.) - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { - if (!is_default_device || !logdev->opened_as_default) { // if opened as a default, leave it on the zombie device for later migration. - SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent)); - if (p) { // if this failed, no event for you, but you have deeper problems anyhow. - p->type = SDL_EVENT_AUDIO_DEVICE_REMOVED; - p->devid = logdev->instance_id; - p->next = NULL; - pending_tail->next = p; - pending_tail = p; - } - } - } - - SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent)); - if (p) { // if this failed, no event for you, but you have deeper problems anyhow. - p->type = SDL_EVENT_AUDIO_DEVICE_REMOVED; - p->devid = device->instance_id; - p->next = NULL; - pending_tail->next = p; - pending_tail = p; - } - } - - ReleaseAudioDevice(device); - - if (first_disconnect) { - if (pending.next) { // NULL if event is disabled or disaster struck. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_assert(current_audio.pending_events_tail != NULL); - SDL_assert(current_audio.pending_events_tail->next == NULL); - current_audio.pending_events_tail->next = pending.next; - current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); - } - - UnrefPhysicalAudioDevice(device); - } -} - - -// stubs for audio drivers that don't need a specific entry point... - -static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ } -static bool SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { return true; /* no-op. */ } -static bool SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { return true; /* no-op. */ } -static bool SDL_AudioWaitRecordingDevice_Default(SDL_AudioDevice *device) { return true; /* no-op. */ } -static void SDL_AudioFlushRecording_Default(SDL_AudioDevice *device) { /* no-op. */ } -static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } -static void SDL_AudioDeinitializeStart_Default(void) { /* no-op. */ } -static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ } -static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ } - -static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) -{ - SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL); -} - -static void SDL_AudioDetectDevices_Default(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) -{ - // you have to write your own implementation if these assertions fail. - SDL_assert(current_audio.impl.OnlyHasDefaultPlaybackDevice); - SDL_assert(current_audio.impl.OnlyHasDefaultRecordingDevice || !current_audio.impl.HasRecordingSupport); - - *default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)((size_t)0x1)); - if (current_audio.impl.HasRecordingSupport) { - *default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)((size_t)0x2)); - } -} - -static Uint8 *SDL_AudioGetDeviceBuf_Default(SDL_AudioDevice *device, int *buffer_size) -{ - *buffer_size = 0; - return NULL; -} - -static int SDL_AudioRecordDevice_Default(SDL_AudioDevice *device, void *buffer, int buflen) -{ - SDL_Unsupported(); - return -1; -} - -static bool SDL_AudioOpenDevice_Default(SDL_AudioDevice *device) -{ - return SDL_Unsupported(); -} - -// Fill in stub functions for unused driver entry points. This lets us blindly call them without having to check for validity first. -static void CompleteAudioEntryPoints(void) -{ - #define FILL_STUB(x) if (!current_audio.impl.x) { current_audio.impl.x = SDL_Audio##x##_Default; } - FILL_STUB(DetectDevices); - FILL_STUB(OpenDevice); - FILL_STUB(ThreadInit); - FILL_STUB(ThreadDeinit); - FILL_STUB(WaitDevice); - FILL_STUB(PlayDevice); - FILL_STUB(GetDeviceBuf); - FILL_STUB(WaitRecordingDevice); - FILL_STUB(RecordDevice); - FILL_STUB(FlushRecording); - FILL_STUB(CloseDevice); - FILL_STUB(FreeDeviceHandle); - FILL_STUB(DeinitializeStart); - FILL_STUB(Deinitialize); - #undef FILL_STUB -} - -typedef struct FindLowestDeviceIDData -{ - const bool recording; - SDL_AudioDeviceID highest; - SDL_AudioDevice *result; -} FindLowestDeviceIDData; - -static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *table, const void *key, const void *value) -{ - FindLowestDeviceIDData *data = (FindLowestDeviceIDData *) userdata; - const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #0 of devid is set for playback devices and unset for recording. - // bit #1 of devid is set for physical devices and unset for logical. - const bool devid_recording = !(devid & (1 << 0)); - const bool isphysical = !!(devid & (1 << 1)); - if (isphysical && (devid_recording == data->recording) && (devid < data->highest)) { - data->highest = devid; - data->result = (SDL_AudioDevice *) value; - } - return true; // keep iterating. -} - -static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording) -{ - const SDL_AudioDeviceID highest = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; // According to AssignAudioDeviceInstanceId, nothing can have a value this large. - - // (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.) - FindLowestDeviceIDData data = { recording, highest, NULL }; - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_IterateHashTable(current_audio.device_hash, FindLowestDeviceID, &data); - SDL_UnlockRWLock(current_audio.device_hash_lock); - return data.result; -} - -static Uint32 SDLCALL HashAudioDeviceID(void *userdata, const void *key) -{ - // shift right 2, to dump the first two bits, since these are flags - // (recording vs playback, logical vs physical) and the rest are unique incrementing integers. - return ((Uint32) ((uintptr_t) key)) >> 2; -} - -// !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match. -bool SDL_InitAudio(const char *driver_name) -{ - if (SDL_GetCurrentAudioDriver()) { - SDL_QuitAudio(); // shutdown driver if already running. - } - - // make sure device IDs start at 2 (because of SDL2 legacy interface), but don't reset the counter on each init, in case the app is holding an old device ID somewhere. - SDL_CompareAndSwapAtomicInt(&last_device_instance_id, 0, 2); - - SDL_ChooseAudioConverters(); - SDL_SetupAudioResampler(); - - SDL_RWLock *device_hash_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. - if (!device_hash_lock) { - return false; - } - - SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL); - if (!device_hash) { - SDL_DestroyRWLock(device_hash_lock); - return false; - } - - // Select the proper audio driver - if (!driver_name) { - driver_name = SDL_GetHint(SDL_HINT_AUDIO_DRIVER); - } - - bool initialized = false; - bool tried_to_init = false; - - if (driver_name && *driver_name != 0) { - char *driver_name_copy = SDL_strdup(driver_name); - const char *driver_attempt = driver_name_copy; - - if (!driver_name_copy) { - SDL_DestroyRWLock(device_hash_lock); - SDL_DestroyHashTable(device_hash); - return false; - } - - while (driver_attempt && *driver_attempt != 0 && !initialized) { - char *driver_attempt_end = SDL_strchr(driver_attempt, ','); - if (driver_attempt_end) { - *driver_attempt_end = '\0'; - } - - // SDL 1.2 uses the name "dsound", so we'll support both. - if (SDL_strcmp(driver_attempt, "dsound") == 0) { - driver_attempt = "directsound"; - } else if (SDL_strcmp(driver_attempt, "pulse") == 0) { // likewise, "pulse" was renamed to "pulseaudio" - driver_attempt = "pulseaudio"; - } - - for (int i = 0; bootstrap[i]; ++i) { - if (!bootstrap[i]->is_preferred && SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) { - tried_to_init = true; - SDL_zero(current_audio); - current_audio.pending_events_tail = ¤t_audio.pending_events; - current_audio.device_hash_lock = device_hash_lock; - current_audio.device_hash = device_hash; - if (bootstrap[i]->init(¤t_audio.impl)) { - current_audio.name = bootstrap[i]->name; - current_audio.desc = bootstrap[i]->desc; - initialized = true; - break; - } - } - } - - driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL; - } - - SDL_free(driver_name_copy); - } else { - for (int i = 0; (!initialized) && (bootstrap[i]); ++i) { - if (bootstrap[i]->demand_only) { - continue; - } - - tried_to_init = true; - SDL_zero(current_audio); - current_audio.pending_events_tail = ¤t_audio.pending_events; - current_audio.device_hash_lock = device_hash_lock; - current_audio.device_hash = device_hash; - if (bootstrap[i]->init(¤t_audio.impl)) { - current_audio.name = bootstrap[i]->name; - current_audio.desc = bootstrap[i]->desc; - initialized = true; - } - } - } - - if (!initialized) { - // specific drivers will set the error message if they fail, but otherwise we do it here. - if (!tried_to_init) { - if (driver_name) { - SDL_SetError("Audio target '%s' not available", driver_name); - } else { - SDL_SetError("No available audio device"); - } - } - - SDL_DestroyRWLock(device_hash_lock); - SDL_DestroyHashTable(device_hash); - SDL_zero(current_audio); - return false; // No driver was available, so fail. - } - - CompleteAudioEntryPoints(); - - // Make sure we have a list of devices available at startup... - SDL_AudioDevice *default_playback = NULL; - SDL_AudioDevice *default_recording = NULL; - current_audio.impl.DetectDevices(&default_playback, &default_recording); - - // If no default was _ever_ specified, just take the first device we see, if any. - if (!default_playback) { - default_playback = GetFirstAddedAudioDevice(/*recording=*/false); - } - - if (!default_recording) { - default_recording = GetFirstAddedAudioDevice(/*recording=*/true); - } - - if (default_playback) { - current_audio.default_playback_device_id = default_playback->instance_id; - RefPhysicalAudioDevice(default_playback); // extra ref on default devices. - } - - if (default_recording) { - current_audio.default_recording_device_id = default_recording->instance_id; - RefPhysicalAudioDevice(default_recording); // extra ref on default devices. - } - - return true; -} - -static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_HashTable *table, const void *key, const void *value) -{ - // bit #1 of devid is set for physical devices and unset for logical. - const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - const bool isphysical = !!(devid & (1<<1)); - if (isphysical) { - DestroyPhysicalAudioDevice((SDL_AudioDevice *) value); - } - return true; // keep iterating. -} - -void SDL_QuitAudio(void) -{ - if (!current_audio.name) { // not initialized?! - return; - } - - current_audio.impl.DeinitializeStart(); - - // Destroy any audio streams that still exist... - while (current_audio.existing_streams) { - SDL_DestroyAudioStream(current_audio.existing_streams); - } - - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_SetAtomicInt(¤t_audio.shutting_down, 1); - SDL_HashTable *device_hash = current_audio.device_hash; - current_audio.device_hash = NULL; - SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; - current_audio.pending_events.next = NULL; - SDL_SetAtomicInt(¤t_audio.playback_device_count, 0); - SDL_SetAtomicInt(¤t_audio.recording_device_count, 0); - SDL_UnlockRWLock(current_audio.device_hash_lock); - - SDL_PendingAudioDeviceEvent *pending_next = NULL; - for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { - pending_next = i->next; - SDL_free(i); - } - - SDL_IterateHashTable(device_hash, DestroyOnePhysicalAudioDevice, NULL); - - // Free the driver data - current_audio.impl.Deinitialize(); - - SDL_DestroyRWLock(current_audio.device_hash_lock); - SDL_DestroyHashTable(device_hash); - - SDL_zero(current_audio); -} - - -void SDL_AudioThreadFinalize(SDL_AudioDevice *device) -{ -} - -static void MixFloat32Audio(float *dst, const float *src, const int buffer_size) -{ - if (!SDL_MixAudio((Uint8 *) dst, (const Uint8 *) src, SDL_AUDIO_F32, buffer_size, 1.0f)) { - SDL_assert(!"This shouldn't happen."); - } -} - - -// Playback device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. - -void SDL_PlaybackAudioThreadSetup(SDL_AudioDevice *device) -{ - SDL_assert(!device->recording); - current_audio.impl.ThreadInit(device); -} - -bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) -{ - SDL_assert(!device->recording); - - SDL_LockMutex(device->lock); - - if (SDL_GetAtomicInt(&device->shutdown)) { - SDL_UnlockMutex(device->lock); - return false; // we're done, shut it down. - } - - bool failed = false; - int buffer_size = device->buffer_size; - Uint8 *device_buffer = device->GetDeviceBuf(device, &buffer_size); - if (buffer_size == 0) { - // WASAPI (maybe others, later) does this to say "just abandon this iteration and try again next time." - } else if (!device_buffer) { - failed = true; - } else { - SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more. - SDL_assert(AudioDeviceCanUseSimpleCopy(device) == device->simple_copy); // make sure this hasn't gotten out of sync. - - // can we do a basic copy without silencing/mixing the buffer? This is an extremely likely scenario, so we special-case it. - if (device->simple_copy) { - SDL_LogicalAudioDevice *logdev = device->logical_devices; - SDL_AudioStream *stream = logdev->bound_streams; - - // We should have updated this elsewhere if the format changed! - SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, NULL, NULL)); - - const int br = SDL_GetAtomicInt(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain); - if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. - failed = true; - SDL_memset(device_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die. - } else if (br < buffer_size) { - SDL_memset(device_buffer + br, device->silence_value, buffer_size - br); // silence whatever we didn't write to. - } - - // generally channel maps will line up, but if the audio stream's chmap has been explicitly changed, do a final swizzle to device layout. - if ((br > 0) && (!SDL_AudioChannelMapsEqual(device->spec.channels, stream->dst_chmap, device->chmap))) { - ConvertAudio(br / SDL_AUDIO_FRAMESIZE(device->spec), device_buffer, device->spec.format, device->spec.channels, NULL, - device_buffer, device->spec.format, device->spec.channels, device->chmap, NULL, 1.0f); - } - } else { // need to actually mix (or silence the buffer) - float *final_mix_buffer = (float *) ((device->spec.format == SDL_AUDIO_F32) ? device_buffer : device->mix_buffer); - const int needed_samples = buffer_size / SDL_AUDIO_BYTESIZE(device->spec.format); - const int work_buffer_size = needed_samples * sizeof (float); - SDL_AudioSpec outspec; - - SDL_assert(work_buffer_size <= device->work_buffer_size); - - SDL_copyp(&outspec, &device->spec); - outspec.format = SDL_AUDIO_F32; - - SDL_memset(final_mix_buffer, '\0', work_buffer_size); // start with silence. - - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { - if (SDL_GetAtomicInt(&logdev->paused)) { - continue; // paused? Skip this logical device. - } - - const SDL_AudioPostmixCallback postmix = logdev->postmix; - float *mix_buffer = final_mix_buffer; - if (postmix) { - mix_buffer = device->postmix_buffer; - SDL_memset(mix_buffer, '\0', work_buffer_size); // start with silence. - } - - for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { - // We should have updated this elsewhere if the format changed! - SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, NULL, NULL)); - - /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams - for iterating here because the binding linked list can only change while the device lock is held. - (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind - the same stream to different devices at the same time, though.) */ - const int br = SDL_GetAudioStreamDataAdjustGain(stream, device->work_buffer, work_buffer_size, logdev->gain); - if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. - failed = true; - break; - } else if (br > 0) { // it's okay if we get less than requested, we mix what we have. - // generally channel maps will line up, but if the audio stream's chmap has been explicitly changed, do a final swizzle to device layout. - if (!SDL_AudioChannelMapsEqual(device->spec.channels, stream->dst_chmap, device->chmap)) { - ConvertAudio(br / SDL_AUDIO_FRAMESIZE(device->spec), device->work_buffer, device->spec.format, device->spec.channels, NULL, - device->work_buffer, device->spec.format, device->spec.channels, device->chmap, NULL, 1.0f); - } - MixFloat32Audio(mix_buffer, (float *) device->work_buffer, br); - } - } - - if (postmix) { - SDL_assert(mix_buffer == device->postmix_buffer); - postmix(logdev->postmix_userdata, &outspec, mix_buffer, work_buffer_size); - MixFloat32Audio(final_mix_buffer, mix_buffer, work_buffer_size); - } - } - - if (((Uint8 *) final_mix_buffer) != device_buffer) { - // !!! FIXME: we can't promise the device buf is aligned/padded for SIMD. - //ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device_buffer, device->spec.format, device->spec.channels, NULL, NULL, 1.0f); - ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device->work_buffer, device->spec.format, device->spec.channels, NULL, NULL, 1.0f); - SDL_memcpy(device_buffer, device->work_buffer, buffer_size); - } - } - - // PlayDevice SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead! - if (!device->PlayDevice(device, device_buffer, buffer_size)) { - failed = true; - } - } - - SDL_UnlockMutex(device->lock); - - if (failed) { - SDL_AudioDeviceDisconnected(device); // doh. - } - - return true; // always go on if not shutting down, even if device failed. -} - -void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device) -{ - SDL_assert(!device->recording); - const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec); - // Wait for the audio to drain if device didn't die. - if (!SDL_GetAtomicInt(&device->zombie)) { - SDL_Delay(((frames * 1000) / device->spec.freq) * 2); - } - current_audio.impl.ThreadDeinit(device); - SDL_AudioThreadFinalize(device); -} - -static int SDLCALL PlaybackAudioThread(void *devicep) // thread entry point -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)devicep; - SDL_assert(device != NULL); - SDL_assert(!device->recording); - SDL_PlaybackAudioThreadSetup(device); - - do { - if (!device->WaitDevice(device)) { - SDL_AudioDeviceDisconnected(device); // doh. (but don't break out of the loop, just be a zombie for now!) - } - } while (SDL_PlaybackAudioThreadIterate(device)); - - SDL_PlaybackAudioThreadShutdown(device); - return 0; -} - - - -// Recording device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. - -void SDL_RecordingAudioThreadSetup(SDL_AudioDevice *device) -{ - SDL_assert(device->recording); - current_audio.impl.ThreadInit(device); -} - -bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device) -{ - SDL_assert(device->recording); - - SDL_LockMutex(device->lock); - - if (SDL_GetAtomicInt(&device->shutdown)) { - SDL_UnlockMutex(device->lock); - return false; // we're done, shut it down. - } - - bool failed = false; - - if (!device->logical_devices) { - device->FlushRecording(device); // nothing wants data, dump anything pending. - } else { - // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitRecordingDevice! - int br = device->RecordDevice(device, device->work_buffer, device->buffer_size); - if (br < 0) { // uhoh, device failed for some reason! - failed = true; - } else if (br > 0) { // queue the new data to each bound stream. - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { - if (SDL_GetAtomicInt(&logdev->paused)) { - continue; // paused? Skip this logical device. - } - - void *output_buffer = device->work_buffer; - - // I don't know why someone would want a postmix on a recording device, but we offer it for API consistency. - if (logdev->postmix || (logdev->gain != 1.0f)) { - // move to float format. - SDL_AudioSpec outspec; - SDL_copyp(&outspec, &device->spec); - outspec.format = SDL_AUDIO_F32; - output_buffer = device->postmix_buffer; - const int frames = br / SDL_AUDIO_FRAMESIZE(device->spec); - br = frames * SDL_AUDIO_FRAMESIZE(outspec); - ConvertAudio(frames, device->work_buffer, device->spec.format, outspec.channels, NULL, device->postmix_buffer, SDL_AUDIO_F32, outspec.channels, NULL, NULL, logdev->gain); - if (logdev->postmix) { - logdev->postmix(logdev->postmix_userdata, &outspec, device->postmix_buffer, br); - } - } - - for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { - // We should have updated this elsewhere if the format changed! - SDL_assert(stream->src_spec.format == ((logdev->postmix || (logdev->gain != 1.0f)) ? SDL_AUDIO_F32 : device->spec.format)); - SDL_assert(stream->src_spec.channels == device->spec.channels); - SDL_assert(stream->src_spec.freq == device->spec.freq); - - void *final_buf = output_buffer; - - // generally channel maps will line up, but if the audio stream's chmap has been explicitly changed, do a final swizzle to stream layout. - if (!SDL_AudioChannelMapsEqual(device->spec.channels, stream->src_chmap, device->chmap)) { - final_buf = device->mix_buffer; // this is otherwise unused on recording devices, so it makes convenient scratch space here. - ConvertAudio(br / SDL_AUDIO_FRAMESIZE(device->spec), output_buffer, device->spec.format, device->spec.channels, NULL, - final_buf, device->spec.format, device->spec.channels, stream->src_chmap, NULL, 1.0f); - } - - /* this will hold a lock on `stream` while putting. We don't explicitly lock the streams - for iterating here because the binding linked list can only change while the device lock is held. - (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind - the same stream to different devices at the same time, though.) */ - if (!SDL_PutAudioStreamData(stream, final_buf, br)) { - // oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. - failed = true; - break; - } - } - } - } - } - - SDL_UnlockMutex(device->lock); - - if (failed) { - SDL_AudioDeviceDisconnected(device); // doh. - } - - return true; // always go on if not shutting down, even if device failed. -} - -void SDL_RecordingAudioThreadShutdown(SDL_AudioDevice *device) -{ - SDL_assert(device->recording); - device->FlushRecording(device); - current_audio.impl.ThreadDeinit(device); - SDL_AudioThreadFinalize(device); -} - -static int SDLCALL RecordingAudioThread(void *devicep) // thread entry point -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)devicep; - SDL_assert(device != NULL); - SDL_assert(device->recording); - SDL_RecordingAudioThreadSetup(device); - - do { - if (!device->WaitRecordingDevice(device)) { - SDL_AudioDeviceDisconnected(device); // doh. (but don't break out of the loop, just be a zombie for now!) - } - } while (SDL_RecordingAudioThreadIterate(device)); - - SDL_RecordingAudioThreadShutdown(device); - return 0; -} - -typedef struct CountAudioDevicesData -{ - int devs_seen; - const int num_devices; - SDL_AudioDeviceID *result; - const bool recording; -} CountAudioDevicesData; - -static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table, const void *key, const void *value) -{ - CountAudioDevicesData *data = (CountAudioDevicesData *) userdata; - const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #0 of devid is set for playback devices and unset for recording. - // bit #1 of devid is set for physical devices and unset for logical. - const bool devid_recording = !(devid & (1<<0)); - const bool isphysical = !!(devid & (1<<1)); - if (isphysical && (devid_recording == data->recording)) { - SDL_assert(data->devs_seen < data->num_devices); - data->result[data->devs_seen++] = devid; - } - return true; // keep iterating. -} - -static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording) -{ - SDL_AudioDeviceID *result = NULL; - int num_devices = 0; - - if (SDL_GetCurrentAudioDriver()) { - SDL_LockRWLockForReading(current_audio.device_hash_lock); - { - num_devices = SDL_GetAtomicInt(recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count); - result = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); - if (result) { - CountAudioDevicesData data = { 0, num_devices, result, recording }; - SDL_IterateHashTable(current_audio.device_hash, CountAudioDevices, &data); - SDL_assert(data.devs_seen == num_devices); - result[data.devs_seen] = 0; // null-terminated. - } - } - SDL_UnlockRWLock(current_audio.device_hash_lock); - } else { - SDL_SetError("Audio subsystem is not initialized"); - } - - if (count) { - if (result) { - *count = num_devices; - } else { - *count = 0; - } - } - return result; -} - -SDL_AudioDeviceID *SDL_GetAudioPlaybackDevices(int *count) -{ - return GetAudioDevices(count, false); -} - -SDL_AudioDeviceID *SDL_GetAudioRecordingDevices(int *count) -{ - return GetAudioDevices(count, true); -} - -typedef struct FindAudioDeviceByCallbackData -{ - bool (*callback)(SDL_AudioDevice *device, void *userdata); - void *userdata; - SDL_AudioDevice *retval; -} FindAudioDeviceByCallbackData; - -static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value) -{ - FindAudioDeviceByCallbackData *data = (FindAudioDeviceByCallbackData *) userdata; - const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; - // bit #1 of devid is set for physical devices and unset for logical. - const bool isphysical = !!(devid & (1<<1)); - if (isphysical) { - SDL_AudioDevice *device = (SDL_AudioDevice *) value; - if (data->callback(device, data->userdata)) { // found it? - data->retval = device; - return false; // stop iterating, we found it. - } - } - return true; // keep iterating. -} - -// !!! FIXME: SDL convention is for userdata to come first in the callback's params. Fix this at some point. -SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata) -{ - if (!SDL_GetCurrentAudioDriver()) { - SDL_SetError("Audio subsystem is not initialized"); - return NULL; - } - - FindAudioDeviceByCallbackData data = { callback, userdata, NULL }; - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_IterateHashTable(current_audio.device_hash, FindAudioDeviceByCallback, &data); - SDL_UnlockRWLock(current_audio.device_hash_lock); - - if (!data.retval) { - SDL_SetError("Device not found"); - } - - return data.retval; -} - -static bool TestDeviceHandleCallback(SDL_AudioDevice *device, void *handle) -{ - return device->handle == handle; -} - -SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) -{ - return SDL_FindPhysicalAudioDeviceByCallback(TestDeviceHandleCallback, handle); -} - -const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) -{ - const char *result = NULL; - SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); - if (device) { - result = SDL_GetPersistentString(device->name); - } - ReleaseAudioDevice(device); - - return result; -} - -bool SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames) -{ - if (!spec) { - return SDL_InvalidParamError("spec"); - } - - bool result = false; - SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); - if (device) { - SDL_copyp(spec, &device->spec); - if (sample_frames) { - *sample_frames = device->sample_frames; - } - result = true; - } - ReleaseAudioDevice(device); - - return result; -} - -int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count) -{ - int *result = NULL; - int channels = 0; - SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); - if (device) { - channels = device->spec.channels; - result = SDL_ChannelMapDup(device->chmap, channels); - } - ReleaseAudioDevice(device); - - if (count) { - *count = channels; - } - - return result; -} - - -// this is awkward, but this makes sure we can release the device lock -// so the device thread can terminate but also not have two things -// race to close or open the device while the lock is unprotected. -// you hold the lock when calling this, it will release the lock and -// wait while the shutdown flag is set. -// BE CAREFUL WITH THIS. -static void SerializePhysicalDeviceClose(SDL_AudioDevice *device) -{ - while (SDL_GetAtomicInt(&device->shutdown)) { - SDL_WaitCondition(device->close_cond, device->lock); - } -} - -// this expects the device lock to be held. -static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) -{ - SerializePhysicalDeviceClose(device); - - SDL_SetAtomicInt(&device->shutdown, 1); - - // YOU MUST PROTECT KEY POINTS WITH SerializePhysicalDeviceClose() WHILE THE THREAD JOINS - SDL_UnlockMutex(device->lock); - - if (device->thread) { - SDL_WaitThread(device->thread, NULL); - device->thread = NULL; - } - - if (device->currently_opened) { - current_audio.impl.CloseDevice(device); // if ProvidesOwnCallbackThread, this must join on any existing device thread before returning! - device->currently_opened = false; - device->hidden = NULL; // just in case. - } - - SDL_LockMutex(device->lock); - SDL_SetAtomicInt(&device->shutdown, 0); // ready to go again. - SDL_BroadcastCondition(device->close_cond); // release anyone waiting in SerializePhysicalDeviceClose; they'll still block until we release device->lock, though. - - SDL_aligned_free(device->work_buffer); - device->work_buffer = NULL; - - SDL_aligned_free(device->mix_buffer); - device->mix_buffer = NULL; - - SDL_aligned_free(device->postmix_buffer); - device->postmix_buffer = NULL; - - SDL_copyp(&device->spec, &device->default_spec); - device->sample_frames = 0; - device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); -} - -void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) -{ - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - if (logdev) { - DestroyLogicalAudioDevice(logdev); - } - - if (device) { - if (!device->logical_devices) { // no more logical devices? Close the physical device, too. - ClosePhysicalAudioDevice(device); - } - UnrefPhysicalAudioDevice(device); // one reference for each logical device. - } - - ReleaseAudioDevice(device); -} - - -static SDL_AudioFormat ParseAudioFormatString(const char *string) -{ - if (string) { - #define CHECK_FMT_STRING(x) if (SDL_strcmp(string, #x) == 0) { return SDL_AUDIO_##x; } - CHECK_FMT_STRING(U8); - CHECK_FMT_STRING(S8); - CHECK_FMT_STRING(S16LE); - CHECK_FMT_STRING(S16BE); - CHECK_FMT_STRING(S16); - CHECK_FMT_STRING(S32LE); - CHECK_FMT_STRING(S32BE); - CHECK_FMT_STRING(S32); - CHECK_FMT_STRING(F32LE); - CHECK_FMT_STRING(F32BE); - CHECK_FMT_STRING(F32); - #undef CHECK_FMT_STRING - } - return SDL_AUDIO_UNKNOWN; -} - -static void PrepareAudioFormat(bool recording, SDL_AudioSpec *spec) -{ - if (spec->freq == 0) { - spec->freq = recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY; - - const char *hint = SDL_GetHint(SDL_HINT_AUDIO_FREQUENCY); - if (hint) { - const int val = SDL_atoi(hint); - if (val > 0) { - spec->freq = val; - } - } - } - - if (spec->channels == 0) { - spec->channels = recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS; - - const char *hint = SDL_GetHint(SDL_HINT_AUDIO_CHANNELS); - if (hint) { - const int val = SDL_atoi(hint); - if (val > 0) { - spec->channels = val; - } - } - } - - if (spec->format == 0) { - const SDL_AudioFormat val = ParseAudioFormatString(SDL_GetHint(SDL_HINT_AUDIO_FORMAT)); - spec->format = (val != SDL_AUDIO_UNKNOWN) ? val : (recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT); - } -} - -void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device) -{ - device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); - device->buffer_size = device->sample_frames * SDL_AUDIO_FRAMESIZE(device->spec); - device->work_buffer_size = device->sample_frames * sizeof (float) * device->spec.channels; - device->work_buffer_size = SDL_max(device->buffer_size, device->work_buffer_size); // just in case we end up with a 64-bit audio format at some point. -} - -char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen) -{ - (void)SDL_snprintf(buf, buflen, "SDLAudio%c%d", (device->recording) ? 'C' : 'P', (int) device->instance_id); - return buf; -} - - -// this expects the device lock to be held. -static bool OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) -{ - SerializePhysicalDeviceClose(device); // make sure another thread that's closing didn't release the lock to let the device thread join... - - if (device->currently_opened) { - return true; // we're already good. - } - - // Just pretend to open a zombie device. It can still collect logical devices on a default device under the assumption they will all migrate when the default device is officially changed. - if (SDL_GetAtomicInt(&device->zombie)) { - return true; // Braaaaaaaaains. - } - - // These start with the backend's implementation, but we might swap them out with zombie versions later. - device->WaitDevice = current_audio.impl.WaitDevice; - device->PlayDevice = current_audio.impl.PlayDevice; - device->GetDeviceBuf = current_audio.impl.GetDeviceBuf; - device->WaitRecordingDevice = current_audio.impl.WaitRecordingDevice; - device->RecordDevice = current_audio.impl.RecordDevice; - device->FlushRecording = current_audio.impl.FlushRecording; - - SDL_AudioSpec spec; - SDL_copyp(&spec, inspec ? inspec : &device->default_spec); - PrepareAudioFormat(device->recording, &spec); - - /* We allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents - something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later. - (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). - These are just requests! The backend may change any of these values during OpenDevice method! */ - device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format; - device->spec.freq = SDL_max(device->default_spec.freq, spec.freq); - device->spec.channels = SDL_max(device->default_spec.channels, spec.channels); - device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq); - SDL_UpdatedAudioDeviceFormat(device); // start this off sane. - - device->currently_opened = true; // mark this true even if impl.OpenDevice fails, so we know to clean up. - if (!current_audio.impl.OpenDevice(device)) { - ClosePhysicalAudioDevice(device); // clean up anything the backend left half-initialized. - return false; - } - - SDL_UpdatedAudioDeviceFormat(device); // in case the backend changed things and forgot to call this. - - // Allocate a scratch audio buffer - device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size); - if (!device->work_buffer) { - ClosePhysicalAudioDevice(device); - return false; - } - - if (device->spec.format != SDL_AUDIO_F32) { - device->mix_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size); - if (!device->mix_buffer) { - ClosePhysicalAudioDevice(device); - return false; - } - } - - // Start the audio thread if necessary - if (!current_audio.impl.ProvidesOwnCallbackThread) { - char threadname[64]; - SDL_GetAudioThreadName(device, threadname, sizeof (threadname)); - device->thread = SDL_CreateThread(device->recording ? RecordingAudioThread : PlaybackAudioThread, threadname, device); - - if (!device->thread) { - ClosePhysicalAudioDevice(device); - return SDL_SetError("Couldn't create audio thread"); - } - } - - return true; -} - -SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec) -{ - if (!SDL_GetCurrentAudioDriver()) { - SDL_SetError("Audio subsystem is not initialized"); - return 0; - } - - bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) || (devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING)); - - // 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 bool islogical = (!wants_default && !(devid & (1<<1))); - if (!islogical) { - device = ObtainPhysicalAudioDeviceDefaultAllowed(devid); - } else { - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - if (logdev) { - wants_default = logdev->opened_as_default; // was the original logical device meant to be a default? Make this one, too. - } - } - - SDL_AudioDeviceID result = 0; - - if (device) { - SDL_LogicalAudioDevice *logdev = NULL; - if (!wants_default && SDL_GetAtomicInt(&device->zombie)) { - // uhoh, this device is undead, and just waiting to be cleaned up. Refuse explicit opens. - SDL_SetError("Device was already lost and can't accept new opens"); - } else if ((logdev = (SDL_LogicalAudioDevice *) SDL_calloc(1, sizeof (SDL_LogicalAudioDevice))) == NULL) { - // SDL_calloc already called SDL_OutOfMemory - } else if (!OpenPhysicalAudioDevice(device, spec)) { // if this is the first thing using this physical device, open at the OS level if necessary... - SDL_free(logdev); - } else { - RefPhysicalAudioDevice(device); // unref'd on successful SDL_CloseAudioDevice - SDL_SetAtomicInt(&logdev->paused, 0); - result = logdev->instance_id = AssignAudioDeviceInstanceId(device->recording, /*islogical=*/true); - logdev->physical_device = device; - logdev->gain = 1.0f; - logdev->opened_as_default = wants_default; - logdev->next = device->logical_devices; - if (device->logical_devices) { - device->logical_devices->prev = logdev; - } - device->logical_devices = logdev; - UpdateAudioStreamFormatsPhysical(device); - } - ReleaseAudioDevice(device); - - if (result) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) result, logdev, false); - SDL_UnlockRWLock(current_audio.device_hash_lock); - if (!inserted) { - SDL_CloseAudioDevice(result); - result = 0; - } - } - } - - return result; -} - -static bool SetLogicalAudioDevicePauseState(SDL_AudioDeviceID devid, int value) -{ - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - if (logdev) { - SDL_SetAtomicInt(&logdev->paused, value); - } - ReleaseAudioDevice(device); - return logdev ? true : false; // ObtainLogicalAudioDevice will have set an error. -} - -bool SDL_PauseAudioDevice(SDL_AudioDeviceID devid) -{ - return SetLogicalAudioDevicePauseState(devid, 1); -} - -bool SDLCALL SDL_ResumeAudioDevice(SDL_AudioDeviceID devid) -{ - return SetLogicalAudioDevicePauseState(devid, 0); -} - -bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid) -{ - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - bool result = false; - if (logdev && SDL_GetAtomicInt(&logdev->paused)) { - result = true; - } - ReleaseAudioDevice(device); - return result; -} - -float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid) -{ - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - const float result = logdev ? logdev->gain : -1.0f; - ReleaseAudioDevice(device); - return result; -} - -bool SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain) -{ - if (gain < 0.0f) { - return SDL_InvalidParamError("gain"); - } - - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - bool result = false; - if (logdev) { - logdev->gain = gain; - UpdateAudioStreamFormatsPhysical(device); - result = true; - } - ReleaseAudioDevice(device); - return result; -} - -bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata) -{ - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - bool result = true; - if (logdev) { - if (callback && !device->postmix_buffer) { - device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size); - if (!device->postmix_buffer) { - result = false; - } - } - - if (result) { - logdev->postmix = callback; - logdev->postmix_userdata = userdata; - } - - UpdateAudioStreamFormatsPhysical(device); - } - ReleaseAudioDevice(device); - return result; -} - -bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams) -{ - const bool islogical = !(devid & (1<<1)); - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = NULL; - bool result = true; - - if (num_streams == 0) { - return true; // nothing to do - } else if (num_streams < 0) { - return SDL_InvalidParamError("num_streams"); - } else if (!streams) { - return SDL_InvalidParamError("streams"); - } else if (!islogical) { - return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices"); - } - - logdev = ObtainLogicalAudioDevice(devid, &device); - if (!logdev) { - result = false; // ObtainLogicalAudioDevice set the error string. - } else if (logdev->simplified) { - result = 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: Actually, why do we allow there to be an invalid format, again? - - // make sure start of list is sane. - SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL)); - - // 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++) { - SDL_AudioStream *stream = streams[i]; - if (!stream) { - SDL_SetError("Stream #%d is NULL", i); - result = false; // to pacify the static analyzer, that doesn't realize SDL_SetError() always returns false. - } else { - SDL_LockMutex(stream->lock); - SDL_assert((stream->bound_device == NULL) == ((stream->prev_binding == NULL) || (stream->next_binding == NULL))); - if (stream->bound_device) { - result = SDL_SetError("Stream #%d is already bound to a device", i); - } else if (stream->simplified) { // You can get here if you closed the device instead of destroying the stream. - result = SDL_SetError("Cannot change binding on a stream created with SDL_OpenAudioDeviceStream"); - } - } - - if (!result) { - int j; - for (j = 0; j < i; j++) { - SDL_UnlockMutex(streams[j]->lock); - } - if (stream) { - SDL_UnlockMutex(stream->lock); - } - break; - } - } - } - - if (result) { - // Now that everything is verified, chain everything together. - for (int i = 0; i < num_streams; i++) { - SDL_AudioStream *stream = streams[i]; - if (stream) { // shouldn't be NULL, but just in case... - stream->bound_device = logdev; - stream->prev_binding = NULL; - stream->next_binding = logdev->bound_streams; - if (logdev->bound_streams) { - logdev->bound_streams->prev_binding = stream; - } - logdev->bound_streams = stream; - SDL_UnlockMutex(stream->lock); - } - } - } - - UpdateAudioStreamFormatsPhysical(device); - - ReleaseAudioDevice(device); - - return result; -} - -bool SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream) -{ - return SDL_BindAudioStreams(devid, &stream, 1); -} - -// !!! FIXME: this and BindAudioStreams are mutex nightmares. :/ -void SDL_UnbindAudioStreams(SDL_AudioStream * const *streams, int num_streams) -{ - if (num_streams <= 0 || !streams) { - return; // nothing to do - } - - /* 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. - But this means we have an unlikely, pathological case where a stream could change its binding between when we lookup its bound device and when we lock everything, - so we double-check here. */ - for (int i = 0; i < num_streams; i++) { - SDL_AudioStream *stream = streams[i]; - if (!stream) { - continue; // nothing to do, it's a NULL stream. - } - - while (true) { - SDL_LockMutex(stream->lock); // lock to check this and then release it, in case the device isn't locked yet. - SDL_LogicalAudioDevice *bounddev = stream->bound_device; - SDL_UnlockMutex(stream->lock); - - // lock in correct order. - if (bounddev) { - SDL_LockMutex(bounddev->physical_device->lock); // this requires recursive mutexes, since we're likely locking the same device multiple times. - } - SDL_LockMutex(stream->lock); - - if (bounddev == stream->bound_device) { - break; // the binding didn't change in the small window where it could, so we're good. - } else { - SDL_UnlockMutex(stream->lock); // it changed bindings! Try again. - if (bounddev) { - SDL_UnlockMutex(bounddev->physical_device->lock); - } - } - } - } - - // everything is locked, start unbinding streams. - for (int i = 0; i < num_streams; i++) { - SDL_AudioStream *stream = streams[i]; - // don't allow unbinding from "simplified" devices (opened with SDL_OpenAudioDeviceStream). Just ignore them. - if (stream && stream->bound_device && !stream->bound_device->simplified) { - if (stream->bound_device->bound_streams == stream) { - SDL_assert(!stream->prev_binding); - stream->bound_device->bound_streams = stream->next_binding; - } - if (stream->prev_binding) { - stream->prev_binding->next_binding = stream->next_binding; - } - if (stream->next_binding) { - stream->next_binding->prev_binding = stream->prev_binding; - } - stream->prev_binding = stream->next_binding = NULL; - } - } - - // Finalize and unlock everything. - for (int i = 0; i < num_streams; i++) { - SDL_AudioStream *stream = streams[i]; - if (stream) { - SDL_LogicalAudioDevice *logdev = stream->bound_device; - stream->bound_device = NULL; - SDL_UnlockMutex(stream->lock); - if (logdev) { - UpdateAudioStreamFormatsPhysical(logdev->physical_device); - SDL_UnlockMutex(logdev->physical_device->lock); - } - } - } -} - -void SDL_UnbindAudioStream(SDL_AudioStream *stream) -{ - SDL_UnbindAudioStreams(&stream, 1); -} - -SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream *stream) -{ - SDL_AudioDeviceID result = 0; - - if (!stream) { - SDL_InvalidParamError("stream"); - return 0; - } - - SDL_LockMutex(stream->lock); - if (stream->bound_device) { - result = stream->bound_device->instance_id; - } else { - SDL_SetError("Audio stream not bound to an audio device"); - } - SDL_UnlockMutex(stream->lock); - - return result; -} - -SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata) -{ - SDL_AudioDeviceID logdevid = SDL_OpenAudioDevice(devid, spec); - if (!logdevid) { - return NULL; // error string should already be set. - } - - bool failed = false; - SDL_AudioStream *stream = NULL; - SDL_AudioDevice *device = NULL; - SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(logdevid, &device); - if (!logdev) { // this shouldn't happen, but just in case. - failed = true; - } else { - SDL_SetAtomicInt(&logdev->paused, 1); // start the device paused, to match SDL2. - - SDL_assert(device != NULL); - const bool recording = device->recording; - - // if the app didn't request a format _at all_, just make a stream that does no conversion; they can query for it later. - SDL_AudioSpec tmpspec; - if (!spec) { - SDL_copyp(&tmpspec, &device->spec); - spec = &tmpspec; - } - - if (recording) { - stream = SDL_CreateAudioStream(&device->spec, spec); - } else { - stream = SDL_CreateAudioStream(spec, &device->spec); - } - - if (!stream) { - failed = true; - } else { - // don't do all the complicated validation and locking of SDL_BindAudioStream just to set a few fields here. - logdev->bound_streams = stream; - logdev->simplified = true; // forbid further binding changes on this logical device. - - stream->bound_device = logdev; - stream->simplified = true; // so we know to close the audio device when this is destroyed. - - UpdateAudioStreamFormatsPhysical(device); - - if (callback) { - bool rc; - if (recording) { - rc = SDL_SetAudioStreamPutCallback(stream, callback, userdata); - } else { - rc = SDL_SetAudioStreamGetCallback(stream, callback, userdata); - } - SDL_assert(rc); // should only fail if stream==NULL atm. - } - } - } - - ReleaseAudioDevice(device); - - if (failed) { - SDL_DestroyAudioStream(stream); - SDL_CloseAudioDevice(logdevid); - stream = NULL; - } - - return stream; -} - -bool SDL_PauseAudioStreamDevice(SDL_AudioStream *stream) -{ - SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream); - if (!devid) { - return false; - } - - return SDL_PauseAudioDevice(devid); -} - -bool SDL_ResumeAudioStreamDevice(SDL_AudioStream *stream) -{ - SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream); - if (!devid) { - return false; - } - - return SDL_ResumeAudioDevice(devid); -} - -bool SDL_AudioStreamDevicePaused(SDL_AudioStream *stream) -{ - SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream); - if (!devid) { - return false; - } - - return SDL_AudioDevicePaused(devid); -} - -#if SDL_BYTEORDER == SDL_LIL_ENDIAN -#define NATIVE(type) SDL_AUDIO_##type##LE -#define SWAPPED(type) SDL_AUDIO_##type##BE -#else -#define NATIVE(type) SDL_AUDIO_##type##BE -#define SWAPPED(type) SDL_AUDIO_##type##LE -#endif - -#define NUM_FORMATS 8 -// always favor Float32 in native byte order, since we're probably going to convert to that for processing anyhow. -static const SDL_AudioFormat format_list[NUM_FORMATS][NUM_FORMATS + 1] = { - { SDL_AUDIO_U8, NATIVE(F32), SWAPPED(F32), SDL_AUDIO_S8, NATIVE(S16), SWAPPED(S16), NATIVE(S32), SWAPPED(S32), SDL_AUDIO_UNKNOWN }, - { SDL_AUDIO_S8, NATIVE(F32), SWAPPED(F32), SDL_AUDIO_U8, NATIVE(S16), SWAPPED(S16), NATIVE(S32), SWAPPED(S32), SDL_AUDIO_UNKNOWN }, - { NATIVE(S16), NATIVE(F32), SWAPPED(F32), SWAPPED(S16), NATIVE(S32), SWAPPED(S32), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN }, - { SWAPPED(S16), NATIVE(F32), SWAPPED(F32), NATIVE(S16), SWAPPED(S32), NATIVE(S32), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN }, - { NATIVE(S32), NATIVE(F32), SWAPPED(F32), SWAPPED(S32), NATIVE(S16), SWAPPED(S16), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN }, - { SWAPPED(S32), NATIVE(F32), SWAPPED(F32), NATIVE(S32), SWAPPED(S16), NATIVE(S16), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN }, - { NATIVE(F32), SWAPPED(F32), NATIVE(S32), SWAPPED(S32), NATIVE(S16), SWAPPED(S16), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN }, - { SWAPPED(F32), NATIVE(F32), SWAPPED(S32), NATIVE(S32), SWAPPED(S16), NATIVE(S16), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN }, -}; - -#undef NATIVE -#undef SWAPPED - -const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format) -{ - for (int i = 0; i < NUM_FORMATS; i++) { - if (format_list[i][0] == format) { - return &format_list[i][0]; - } - } - return &format_list[0][NUM_FORMATS]; // not found; return what looks like a list with only a zero in it. -} - -const char *SDL_GetAudioFormatName(SDL_AudioFormat format) -{ - switch (format) { -#define CASE(X) \ - case X: return #X; - CASE(SDL_AUDIO_U8) - CASE(SDL_AUDIO_S8) - CASE(SDL_AUDIO_S16LE) - CASE(SDL_AUDIO_S16BE) - CASE(SDL_AUDIO_S32LE) - CASE(SDL_AUDIO_S32BE) - CASE(SDL_AUDIO_F32LE) - CASE(SDL_AUDIO_F32BE) -#undef CASE - default: - return "SDL_AUDIO_UNKNOWN"; - } -} - -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) { // !!! FIXME: what should we do in this case? Maybe all devices are lost, so there _isn't_ a default? - return; // uhoh. - } - - const bool recording = new_default_device->recording; - - // 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 = recording ? current_audio.default_recording_device_id : current_audio.default_playback_device_id; - const bool is_already_default = (new_default_device->instance_id == current_devid); - if (!is_already_default) { - if (recording) { - current_audio.default_recording_device_id = new_default_device->instance_id; - } else { - current_audio.default_playback_device_id = new_default_device->instance_id; - } - } - SDL_UnlockRWLock(current_audio.device_hash_lock); - - if (is_already_default) { - return; // this is already the default. - } - - // Queue up events to push to the queue next time it pumps (presumably - // in a safer thread). - // !!! FIXME: this duplicates some code we could probably refactor. - SDL_PendingAudioDeviceEvent pending; - pending.next = NULL; - SDL_PendingAudioDeviceEvent *pending_tail = &pending; - - // 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); - - if (current_default_device) { - // migrate any logical devices that were opened as a default to the new physical device... - - SDL_assert(current_default_device->recording == recording); - - // See if we have to open the new physical device, and if so, find the best audiospec for it. - SDL_AudioSpec spec; - bool needs_migration = false; - SDL_zero(spec); - - for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev; logdev = logdev->next) { - if (logdev->opened_as_default) { - needs_migration = true; - for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { - const SDL_AudioSpec *streamspec = recording ? &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) { - // New default physical device not been opened yet? Open at the OS level... - if (!OpenPhysicalAudioDevice(new_default_device, &spec)) { - needs_migration = false; // uhoh, just leave everything on the old default, nothing to be done. - } - } - - if (needs_migration) { - // we don't currently report channel map changes, so we'll leave them as NULL for now. - const bool spec_changed = !SDL_AudioSpecsEqual(¤t_default_device->spec, &new_default_device->spec, NULL, NULL); - SDL_LogicalAudioDevice *next = NULL; - for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev; logdev = next) { - next = logdev->next; - - if (!logdev->opened_as_default) { - continue; // not opened as a default, leave it on the current physical 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) { - 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; - SDL_UnlockRWLock(current_audio.device_hash_lock); - - SDL_assert(SDL_GetAtomicInt(¤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); - UnrefPhysicalAudioDevice(current_default_device); - - SDL_SetAudioPostmixCallback(logdev->instance_id, logdev->postmix, logdev->postmix_userdata); - - SDL_PendingAudioDeviceEvent *p; - - // Queue an event for each logical device we moved. - if (spec_changed) { - p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent)); - if (p) { // if this failed, no event for you, but you have deeper problems anyhow. - p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED; - p->devid = logdev->instance_id; - p->next = NULL; - pending_tail->next = p; - pending_tail = p; - } - } - } - - UpdateAudioStreamFormatsPhysical(current_default_device); - UpdateAudioStreamFormatsPhysical(new_default_device); - - if (!current_default_device->logical_devices) { // nothing left on the current physical device, close it. - ClosePhysicalAudioDevice(current_default_device); - } - } - - ReleaseAudioDevice(current_default_device); - } - - ReleaseAudioDevice(new_default_device); - - // Default device gets an extra ref, so it lives until a new default replaces it, even if disconnected. - if (current_default_device) { // (despite the name, it's no longer current at this point) - UnrefPhysicalAudioDevice(current_default_device); - } - - if (pending.next) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_assert(current_audio.pending_events_tail != NULL); - SDL_assert(current_audio.pending_events_tail->next == NULL); - current_audio.pending_events_tail->next = pending.next; - current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); - } -} - -bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames) -{ - const int orig_work_buffer_size = device->work_buffer_size; - - // we don't currently have any place where channel maps change from under you, but we can check that if necessary later. - if (SDL_AudioSpecsEqual(&device->spec, newspec, NULL, NULL) && (new_sample_frames == device->sample_frames)) { - return true; // we're already in that format. - } - - SDL_copyp(&device->spec, newspec); - UpdateAudioStreamFormatsPhysical(device); - - bool kill_device = false; - - device->sample_frames = new_sample_frames; - SDL_UpdatedAudioDeviceFormat(device); - if (device->work_buffer && (device->work_buffer_size > orig_work_buffer_size)) { - SDL_aligned_free(device->work_buffer); - device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size); - if (!device->work_buffer) { - kill_device = true; - } - - if (device->postmix_buffer) { - SDL_aligned_free(device->postmix_buffer); - device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size); - if (!device->postmix_buffer) { - kill_device = true; - } - } - - SDL_aligned_free(device->mix_buffer); - device->mix_buffer = NULL; - if (device->spec.format != SDL_AUDIO_F32) { - device->mix_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size); - if (!device->mix_buffer) { - kill_device = true; - } - } - } - - // Post an event for the physical device, and each logical device on this physical device. - if (!kill_device) { - // Queue up events to push to the queue next time it pumps (presumably - // in a safer thread). - // !!! FIXME: this duplicates some code we could probably refactor. - SDL_PendingAudioDeviceEvent pending; - pending.next = NULL; - SDL_PendingAudioDeviceEvent *pending_tail = &pending; - - SDL_PendingAudioDeviceEvent *p; - - p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent)); - if (p) { // if this failed, no event for you, but you have deeper problems anyhow. - p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED; - p->devid = device->instance_id; - p->next = NULL; - pending_tail->next = p; - pending_tail = p; - } - - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) { - p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent)); - if (p) { // if this failed, no event for you, but you have deeper problems anyhow. - p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED; - p->devid = logdev->instance_id; - p->next = NULL; - pending_tail->next = p; - pending_tail = p; - } - } - - if (pending.next) { - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - SDL_assert(current_audio.pending_events_tail != NULL); - SDL_assert(current_audio.pending_events_tail->next == NULL); - current_audio.pending_events_tail->next = pending.next; - current_audio.pending_events_tail = pending_tail; - SDL_UnlockRWLock(current_audio.device_hash_lock); - } - } - - if (kill_device) { - return false; - } - return true; -} - -bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames) -{ - ObtainPhysicalAudioDeviceObj(device); - const bool result = SDL_AudioDeviceFormatChangedAlreadyLocked(device, newspec, new_sample_frames); - ReleaseAudioDevice(device); - return result; -} - -// This is an internal function, so SDL_PumpEvents() can check for pending audio device events. -// ("UpdateSubsystem" is the same naming that the other things that hook into PumpEvents use.) -void SDL_UpdateAudio(void) -{ - SDL_LockRWLockForReading(current_audio.device_hash_lock); - SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next; - SDL_UnlockRWLock(current_audio.device_hash_lock); - - if (!pending_events) { - return; // nothing to do, check next time. - } - - // okay, let's take this whole list of events so we can dump the lock, and new ones can queue up for a later update. - SDL_LockRWLockForWriting(current_audio.device_hash_lock); - pending_events = current_audio.pending_events.next; // in case this changed... - current_audio.pending_events.next = NULL; - current_audio.pending_events_tail = ¤t_audio.pending_events; - SDL_UnlockRWLock(current_audio.device_hash_lock); - - SDL_PendingAudioDeviceEvent *pending_next = NULL; - for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) { - pending_next = i->next; - if (SDL_EventEnabled(i->type)) { - SDL_Event event; - SDL_zero(event); - event.type = i->type; - event.adevice.which = (Uint32) i->devid; - event.adevice.recording = ((i->devid & (1<<0)) == 0); // bit #0 of devid is set for playback devices and unset for recording. - SDL_PushEvent(&event); - } - SDL_free(i); - } -} - -- cgit v1.2.3