#include "SDL_internal.h"
#include "SDL_audio_c.h"
#include "SDL_sysaudio.h"
#include "../thread/SDL_systhread.h"
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_NGAGE
&NGAGEAUDIO_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;
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;
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)
{
CHECK_PARAM(index < 0 || index >= SDL_GetNumAudioDrivers()) {
SDL_InvalidParamError("index");
return NULL;
}
return deduped_bootstrap[index]->name;
}
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)
{
int *chmap = NULL;
if ((channels > 0) && origchmap) {
const size_t chmaplen = sizeof (*origchmap) * channels;
chmap = (int *)SDL_malloc(chmaplen);
if (chmap) {
SDL_memcpy(chmap, origchmap, chmaplen);
}
}
return chmap;
}
void OnAudioStreamCreated(SDL_AudioStream *stream)
{
SDL_assert(stream != NULL);
if (current_audio.subsystem_rwlock) {
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
}
}
void OnAudioStreamDestroy(SDL_AudioStream *stream)
{
SDL_assert(stream != NULL);
if (current_audio.subsystem_rwlock) {
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
}
}
static bool AudioDeviceCanUseSimpleCopy(SDL_AudioDevice *device)
{
SDL_assert(device != NULL);
return (
device->logical_devices && !device->logical_devices->next && !device->logical_devices->postmix && device->logical_devices->bound_streams && !device->logical_devices->bound_streams->next_binding );
}
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; }
}
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) {
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); 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;
}
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; }
static Uint8 *ZombieGetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->work_buffer;
}
static int ZombieRecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
SDL_memset(buffer, device->silence_value, buflen);
return buflen;
}
static void ZombieFlushRecording(SDL_AudioDevice *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; static SDL_AudioDeviceID AssignAudioDeviceInstanceId(bool recording, bool islogical)
{
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;
}
static bool SDL_IsAudioDeviceLogical(SDL_AudioDeviceID devid)
{
return (devid & (1 << 1)) == 0;
}
bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid)
{
return (devid & (1 << 0)) != 0;
}
static bool SDL_IsAudioDeviceRecording(SDL_AudioDeviceID devid)
{
return (devid & (1 << 0)) == 0;
}
static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS {
if (device) {
RefPhysicalAudioDevice(device);
SDL_LockMutex(device->lock);
}
}
static void ReleaseAudioDevice(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS {
if (device) {
SDL_UnlockMutex(device->lock);
UnrefPhysicalAudioDevice(device);
}
}
static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_AudioDevice **_device) SDL_NO_THREAD_SAFETY_ANALYSIS {
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;
if (SDL_IsAudioDeviceLogical(devid)) { SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
SDL_FindInHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) devid, (const void **) &logdev);
if (logdev) {
SDL_assert(logdev->instance_id == devid);
device = logdev->physical_device;
SDL_assert(device != NULL);
RefPhysicalAudioDevice(device); }
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (logdev) {
while (true) {
SDL_LockMutex(device->lock);
SDL_AudioDevice *recheck_device = (SDL_AudioDevice *) SDL_GetAtomicPointer((void **) &logdev->physical_device);
if (device == recheck_device) {
break;
}
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;
}
static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) {
SDL_AudioDevice *device = NULL;
if (SDL_IsAudioDeviceLogical(devid)) {
ObtainLogicalAudioDevice(devid, &device);
} else if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized");
} else {
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
SDL_FindInHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) devid, (const void **) &device);
SDL_assert(!device || (device->instance_id == devid));
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (!device) {
SDL_SetError("Invalid audio device instance ID");
} else {
ObtainPhysicalAudioDeviceObj(device);
}
}
return device;
}
static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceID devid) {
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.subsystem_rwlock);
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.subsystem_rwlock);
if (devid == 0) {
SDL_SetError("No default audio device available");
break;
}
SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid);
if (!device) {
break;
}
bool got_it = false;
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
if (got_it) {
return device;
}
ReleaseAudioDevice(device); }
return NULL;
}
static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev)
{
if (current_audio.device_hash_logical) { SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
SDL_RemoveFromHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) logdev->instance_id);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
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;
}
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);
}
static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device)
{
if (!device) {
return;
}
SDL_LockMutex(device->lock); while (device->logical_devices) {
DestroyLogicalAudioDevice(device->logical_devices);
}
ClosePhysicalAudioDevice(device);
current_audio.impl.FreeDeviceHandle(device);
SDL_UnlockMutex(device->lock);
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);
}
void UnrefPhysicalAudioDevice(SDL_AudioDevice *device)
{
if (SDL_AtomicDecRef(&device->refcount)) {
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
if (SDL_RemoveFromHashTable(current_audio.device_hash_physical, (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.subsystem_rwlock);
DestroyPhysicalAudioDevice(device); }
}
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.subsystem_rwlock);
const int shutting_down = SDL_GetAtomicInt(¤t_audio.shutting_down);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (shutting_down) {
return NULL; }
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, false);
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
if (SDL_InsertIntoHashTable(current_audio.device_hash_physical, (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.subsystem_rwlock);
RefPhysicalAudioDevice(device); 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);
}
SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_AudioSpec *inspec, void *handle)
{
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);
if (device) {
SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent));
if (p) { p->type = SDL_EVENT_AUDIO_DEVICE_ADDED;
p->devid = device->instance_id;
p->next = NULL;
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
}
}
return device;
}
static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_assert(device != NULL);
SDL_PendingAudioDeviceEvent pending;
pending.next = NULL;
SDL_PendingAudioDeviceEvent *pending_tail = &pending;
ObtainPhysicalAudioDeviceObj(device);
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1);
if (first_disconnect) { device->WaitDevice = ZombieWaitDevice;
device->GetDeviceBuf = ZombieGetDeviceBuf;
device->PlayDevice = ZombiePlayDevice;
device->WaitRecordingDevice = ZombieWaitDevice;
device->RecordDevice = ZombieRecordDevice;
device->FlushRecording = ZombieFlushRecording;
for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
if (!is_default_device || !logdev->opened_as_default) { SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent));
if (p) { 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) { 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) { SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
}
UnrefPhysicalAudioDevice(device);
}
UnrefPhysicalAudioDevice(device);
}
void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
{
if (device) {
RefPhysicalAudioDevice(device);
SDL_RunOnMainThread(SDL_AudioDeviceDisconnected_OnMainThread, device, false);
}
}
static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { }
static bool SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { return true; }
static bool SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { return true; }
static bool SDL_AudioWaitRecordingDevice_Default(SDL_AudioDevice *device) { return true; }
static void SDL_AudioFlushRecording_Default(SDL_AudioDevice *device) { }
static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { }
static void SDL_AudioDeinitializeStart_Default(void) { }
static void SDL_AudioDeinitialize_Default(void) { }
static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { }
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)
{
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();
}
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;
SDL_assert(SDL_IsAudioDevicePhysical(devid)); if ((SDL_IsAudioDeviceRecording(devid) == data->recording) && (devid < data->highest)) {
data->highest = devid;
data->result = (SDL_AudioDevice *) value;
SDL_assert(data->result->instance_id == devid);
}
return true; }
static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording)
{
const SDL_AudioDeviceID highest = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
FindLowestDeviceIDData data = { recording, highest, NULL };
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
SDL_IterateHashTable(current_audio.device_hash_physical, FindLowestDeviceID, &data);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
return data.result;
}
static Uint32 SDLCALL HashAudioDeviceID(void *userdata, const void *key)
{
return ((Uint32) ((uintptr_t) key)) >> 2;
}
bool SDL_InitAudio(const char *driver_name)
{
if (SDL_GetCurrentAudioDriver()) {
SDL_QuitAudio(); }
SDL_CompareAndSwapAtomicInt(&last_device_instance_id, 0, 2);
SDL_ChooseAudioConverters();
SDL_SetupAudioResampler();
SDL_RWLock *subsystem_rwlock = SDL_CreateRWLock(); if (!subsystem_rwlock) {
return false;
}
SDL_HashTable *device_hash_physical = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL);
if (!device_hash_physical) {
SDL_DestroyRWLock(subsystem_rwlock);
return false;
}
SDL_HashTable *device_hash_logical = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL);
if (!device_hash_logical) {
SDL_DestroyHashTable(device_hash_physical);
SDL_DestroyRWLock(subsystem_rwlock);
return false;
}
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(subsystem_rwlock);
SDL_DestroyHashTable(device_hash_physical);
SDL_DestroyHashTable(device_hash_logical);
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';
}
if (SDL_strcmp(driver_attempt, "dsound") == 0) {
driver_attempt = "directsound";
} else if (SDL_strcmp(driver_attempt, "pulse") == 0) { 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.subsystem_rwlock = subsystem_rwlock;
current_audio.device_hash_physical = device_hash_physical;
current_audio.device_hash_logical = device_hash_logical;
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.subsystem_rwlock = subsystem_rwlock;
current_audio.device_hash_physical = device_hash_physical;
current_audio.device_hash_logical = device_hash_logical;
if (bootstrap[i]->init(¤t_audio.impl)) {
current_audio.name = bootstrap[i]->name;
current_audio.desc = bootstrap[i]->desc;
initialized = true;
}
}
}
if (initialized) {
SDL_DebugLogBackend("audio", current_audio.name);
} else {
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(subsystem_rwlock);
SDL_DestroyHashTable(device_hash_physical);
SDL_DestroyHashTable(device_hash_logical);
SDL_zero(current_audio);
return false; }
CompleteAudioEntryPoints();
SDL_AudioDevice *default_playback = NULL;
SDL_AudioDevice *default_recording = NULL;
current_audio.impl.DetectDevices(&default_playback, &default_recording);
if (!default_playback) {
default_playback = GetFirstAddedAudioDevice(false);
}
if (!default_recording) {
default_recording = GetFirstAddedAudioDevice(true);
}
if (default_playback) {
current_audio.default_playback_device_id = default_playback->instance_id;
RefPhysicalAudioDevice(default_playback); }
if (default_recording) {
current_audio.default_recording_device_id = default_recording->instance_id;
RefPhysicalAudioDevice(default_recording); }
return true;
}
static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key;
SDL_assert(SDL_IsAudioDevicePhysical(devid)); SDL_AudioDevice *dev = (SDL_AudioDevice *) value;
SDL_assert(dev->instance_id == devid);
DestroyPhysicalAudioDevice(dev);
return true; }
void SDL_QuitAudio(void)
{
if (!current_audio.name) { return;
}
current_audio.impl.DeinitializeStart();
SDL_AudioStream *next = NULL;
for (SDL_AudioStream *i = current_audio.existing_streams; i; i = next) {
next = i->next;
if (i->simplified || SDL_GetBooleanProperty(i->props, SDL_PROP_AUDIOSTREAM_AUTO_CLEANUP_BOOLEAN, true)) {
SDL_DestroyAudioStream(i);
} else {
i->prev = NULL;
i->next = NULL;
}
}
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
SDL_SetAtomicInt(¤t_audio.shutting_down, 1);
SDL_HashTable *device_hash_physical = current_audio.device_hash_physical;
SDL_HashTable *device_hash_logical = current_audio.device_hash_logical;
current_audio.device_hash_physical = current_audio.device_hash_logical = 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.subsystem_rwlock);
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_physical, DestroyOnePhysicalAudioDevice, NULL);
current_audio.impl.Deinitialize();
SDL_DestroyRWLock(current_audio.subsystem_rwlock);
SDL_DestroyHashTable(device_hash_physical);
SDL_DestroyHashTable(device_hash_logical);
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.");
}
}
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; }
bool failed = false;
int buffer_size = device->buffer_size;
Uint8 *device_buffer = device->GetDeviceBuf(device, &buffer_size);
if (buffer_size == 0) {
} else if (!device_buffer) {
failed = true;
} else {
SDL_assert(buffer_size <= device->buffer_size); SDL_assert(AudioDeviceCanUseSimpleCopy(device) == device->simple_copy);
if (device->simple_copy) {
SDL_LogicalAudioDevice *logdev = device->logical_devices;
SDL_AudioStream *stream = logdev->bound_streams;
SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, NULL, NULL));
SDL_assert(stream->src_spec.format != SDL_AUDIO_UNKNOWN);
const int br = SDL_GetAtomicInt(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain);
if (br < 0) { failed = true;
SDL_memset(device_buffer, device->silence_value, buffer_size); } else if (br < buffer_size) {
SDL_memset(device_buffer + br, device->silence_value, buffer_size - br); }
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 { 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);
for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
if (SDL_GetAtomicInt(&logdev->paused)) {
continue; }
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); }
for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) {
SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, NULL, NULL));
SDL_assert(stream->src_spec.format != SDL_AUDIO_UNKNOWN);
const int br = SDL_GetAudioStreamDataAdjustGain(stream, device->work_buffer, work_buffer_size, logdev->gain);
if (br < 0) { failed = true;
break;
} else if (br > 0) { 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) {
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);
}
}
if (!device->PlayDevice(device, device_buffer, buffer_size)) {
failed = true;
}
}
SDL_UnlockMutex(device->lock);
if (failed) {
SDL_AudioDeviceDisconnected(device); }
return true; }
void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device)
{
SDL_assert(!device->recording);
const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec);
if (!SDL_GetAtomicInt(&device->zombie)) {
int delay = ((frames * 1000) / device->spec.freq) * 2;
if (delay > 100) {
delay = 100;
}
SDL_Delay(delay);
}
current_audio.impl.ThreadDeinit(device);
SDL_AudioThreadFinalize(device);
}
static int SDLCALL PlaybackAudioThread(void *devicep) {
SDL_AudioDevice *device = (SDL_AudioDevice *)devicep;
SDL_assert(device != NULL);
SDL_assert(!device->recording);
SDL_PlaybackAudioThreadSetup(device);
while (SDL_PlaybackAudioThreadIterate(device)) {
if (!device->WaitDevice(device)) {
SDL_AudioDeviceDisconnected(device); }
}
SDL_PlaybackAudioThreadShutdown(device);
return 0;
}
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; }
bool failed = false;
if (!device->logical_devices) {
device->FlushRecording(device); } else {
int br = device->RecordDevice(device, device->work_buffer, device->buffer_size);
if (br < 0) { failed = true;
} else if (br > 0) { for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
if (SDL_GetAtomicInt(&logdev->paused)) {
continue; }
void *output_buffer = device->work_buffer;
if (logdev->postmix || (logdev->gain != 1.0f)) {
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) {
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);
SDL_assert(stream->dst_spec.format != SDL_AUDIO_UNKNOWN);
void *final_buf = output_buffer;
if (!SDL_AudioChannelMapsEqual(device->spec.channels, stream->src_chmap, device->chmap)) {
final_buf = device->mix_buffer; 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);
}
if (!SDL_PutAudioStreamData(stream, final_buf, br)) {
failed = true;
break;
}
}
}
}
}
SDL_UnlockMutex(device->lock);
if (failed) {
SDL_AudioDeviceDisconnected(device); }
return true; }
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) {
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); }
} while (SDL_RecordingAudioThreadIterate(device));
SDL_RecordingAudioThreadShutdown(device);
return 0;
}
typedef struct CountAudioDevicesData
{
int devs_seen;
int devs_skipped;
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;
SDL_assert(SDL_IsAudioDevicePhysical(devid)); if (SDL_IsAudioDeviceRecording(devid) == data->recording) {
SDL_assert(data->devs_seen < data->num_devices);
SDL_AudioDevice *device = (SDL_AudioDevice *) value; const bool zombie = SDL_GetAtomicInt(&device->zombie) != 0;
if (zombie) {
data->devs_skipped++;
} else {
data->result[data->devs_seen++] = devid;
}
}
return true; }
static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording)
{
SDL_AudioDeviceID *result = NULL;
int num_devices = 0;
if (SDL_GetCurrentAudioDriver()) {
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
{
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, 0, num_devices, result, recording };
SDL_IterateHashTable(current_audio.device_hash_physical, CountAudioDevices, &data);
SDL_assert((data.devs_seen + data.devs_skipped) == num_devices);
num_devices = data.devs_seen; result[num_devices] = 0; }
}
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
} 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;
SDL_assert(SDL_IsAudioDevicePhysical(devid)); SDL_AudioDevice *device = (SDL_AudioDevice *) value;
if (data->callback(device, data->userdata)) { data->retval = device;
SDL_assert(data->retval->instance_id == devid);
return false; }
return true; }
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.subsystem_rwlock);
SDL_IterateHashTable(current_audio.device_hash_physical, FindAudioDeviceByCallback, &data);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
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;
if (!SDL_GetCurrentAudioDriver()) {
SDL_SetError("Audio subsystem is not initialized");
} else {
const bool islogical = SDL_IsAudioDeviceLogical(devid);
const void *vdev = NULL;
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
if (devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) {
devid = current_audio.default_playback_device_id;
} else if (devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) {
devid = current_audio.default_recording_device_id;
}
SDL_FindInHashTable(islogical ? current_audio.device_hash_logical : current_audio.device_hash_physical, (const void *) (uintptr_t) devid, &vdev);
if (!vdev) {
SDL_SetError("Invalid audio device instance ID");
} else if (islogical) {
const SDL_LogicalAudioDevice *logdev = (const SDL_LogicalAudioDevice *) vdev;
SDL_assert(logdev->instance_id == devid);
result = SDL_GetPersistentString(logdev->physical_device->name);
} else {
const SDL_AudioDevice *device = (const SDL_AudioDevice *) vdev;
SDL_assert(device->instance_id == devid);
result = SDL_GetPersistentString(device->name);
}
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
return result;
}
bool SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames)
{
CHECK_PARAM(!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;
}
static void SerializePhysicalDeviceClose(SDL_AudioDevice *device)
{
while (SDL_GetAtomicInt(&device->shutdown)) {
SDL_WaitCondition(device->close_cond, device->lock);
}
}
static void ClosePhysicalAudioDevice(SDL_AudioDevice *device)
{
SerializePhysicalDeviceClose(device);
SDL_SetAtomicInt(&device->shutdown, 1);
SDL_UnlockMutex(device->lock);
if (device->thread) {
SDL_WaitThread(device->thread, NULL);
device->thread = NULL;
}
if (device->currently_opened) {
current_audio.impl.CloseDevice(device); device->currently_opened = false;
device->hidden = NULL; }
SDL_LockMutex(device->lock);
SDL_SetAtomicInt(&device->shutdown, 0); SDL_BroadcastCondition(device->close_cond);
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) { ClosePhysicalAudioDevice(device);
}
UnrefPhysicalAudioDevice(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); }
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;
}
static bool OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec)
{
SerializePhysicalDeviceClose(device);
if (device->currently_opened) {
return true; }
if (SDL_GetAtomicInt(&device->zombie)) {
return true; }
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);
const SDL_AudioFormat minimum_format = device->recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT;
const int minimum_channels = device->recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS;
const int minimum_freq = device->recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY;
device->spec.format = (SDL_AUDIO_BITSIZE(minimum_format) >= SDL_AUDIO_BITSIZE(spec.format)) ? minimum_format : spec.format;
device->spec.channels = SDL_max(minimum_channels, spec.channels);
device->spec.freq = SDL_max(minimum_freq, spec.freq);
device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq);
SDL_UpdatedAudioDeviceFormat(device);
device->currently_opened = true; if (!current_audio.impl.OpenDevice(device)) {
ClosePhysicalAudioDevice(device); return false;
}
SDL_UpdatedAudioDeviceFormat(device);
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;
}
}
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));
SDL_AudioDevice *device = NULL;
if ((wants_default || SDL_IsAudioDevicePhysical(devid))) {
device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
} else {
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
if (logdev) {
wants_default = logdev->opened_as_default; }
}
SDL_AudioDeviceID result = 0;
if (device) {
SDL_LogicalAudioDevice *logdev = NULL;
if (!wants_default && SDL_GetAtomicInt(&device->zombie)) {
SDL_SetError("Device was already lost and can't accept new opens");
} else if ((logdev = (SDL_LogicalAudioDevice *) SDL_calloc(1, sizeof (SDL_LogicalAudioDevice))) == NULL) {
} else if (!OpenPhysicalAudioDevice(device, spec)) { SDL_free(logdev);
} else {
RefPhysicalAudioDevice(device); SDL_SetAtomicInt(&logdev->paused, 0);
result = logdev->instance_id = AssignAudioDeviceInstanceId(device->recording, 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.subsystem_rwlock);
const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash_logical, (const void *) (uintptr_t) result, logdev, false);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
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; }
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)
{
CHECK_PARAM(gain < 0.0f) {
return SDL_InvalidParamError("gain");
}
SDL_AudioDevice *device = NULL;
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
bool result = false;
if (logdev) {
logdev->gain = gain;
UpdateAudioStreamFormatsPhysical(device);
result = true;
}
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 = false;
if (logdev) {
result = true;
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)
{
SDL_AudioDevice *device = NULL;
SDL_LogicalAudioDevice *logdev = NULL;
bool result = true;
if (num_streams == 0) {
return true; }
CHECK_PARAM(num_streams < 0) {
return SDL_InvalidParamError("num_streams");
}
CHECK_PARAM(!streams) {
return SDL_InvalidParamError("streams");
}
CHECK_PARAM(SDL_IsAudioDevicePhysical(devid)) {
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; } else if (logdev->simplified) {
result = SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream");
} else {
SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL));
for (int i = 0; i < num_streams; i++) {
SDL_AudioStream *stream = streams[i];
if (!stream) {
SDL_SetError("Stream #%d is NULL", i);
result = 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) { 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) {
const bool recording = device->recording;
for (int i = 0; i < num_streams; i++) {
SDL_AudioStream *stream = streams[i];
if (stream) { if (recording && (stream->dst_spec.format == SDL_AUDIO_UNKNOWN)) {
SDL_copyp(&stream->dst_spec, &device->spec);
} else if (!recording && (stream->src_spec.format == SDL_AUDIO_UNKNOWN)) {
SDL_copyp(&stream->src_spec, &device->spec);
}
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);
}
void SDL_UnbindAudioStreams(SDL_AudioStream * const *streams, int num_streams)
{
if (num_streams <= 0 || !streams) {
return; }
for (int i = 0; i < num_streams; i++) {
SDL_AudioStream *stream = streams[i];
if (!stream) {
continue; }
while (true) {
SDL_LockMutex(stream->lock); SDL_LogicalAudioDevice *bounddev = stream->bound_device;
SDL_UnlockMutex(stream->lock);
if (bounddev) {
SDL_LockMutex(bounddev->physical_device->lock); }
SDL_LockMutex(stream->lock);
if (bounddev == stream->bound_device) {
break; } else {
SDL_UnlockMutex(stream->lock); if (bounddev) {
SDL_UnlockMutex(bounddev->physical_device->lock);
}
}
}
}
for (int i = 0; i < num_streams; i++) {
SDL_AudioStream *stream = streams[i];
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;
}
}
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;
CHECK_PARAM(!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; }
bool failed = false;
SDL_AudioStream *stream = NULL;
SDL_AudioDevice *device = NULL;
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(logdevid, &device);
if (!logdev) { failed = true;
} else {
SDL_SetAtomicInt(&logdev->paused, 1);
SDL_assert(device != NULL);
const bool recording = device->recording;
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 {
logdev->bound_streams = stream;
logdev->simplified = true;
stream->bound_device = logdev;
stream->simplified = true;
UpdateAudioStreamFormatsPhysical(device);
if (callback) {
bool rc;
if (recording) {
rc = SDL_SetAudioStreamPutCallback(stream, callback, userdata);
} else {
rc = SDL_SetAudioStreamGetCallback(stream, callback, userdata);
}
SDL_assert(rc); }
}
}
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
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]; }
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;
}
void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
{
if (!new_default_device) { return; }
const bool recording = new_default_device->recording;
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
if (is_already_default) {
return; }
SDL_PendingAudioDeviceEvent pending;
pending.next = NULL;
SDL_PendingAudioDeviceEvent *pending_tail = &pending;
RefPhysicalAudioDevice(new_default_device);
ObtainPhysicalAudioDeviceObj(new_default_device);
SDL_AudioDevice *current_default_device = ObtainPhysicalAudioDevice(current_devid);
if (current_default_device) {
SDL_assert(current_default_device->recording == recording);
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) {
if (!OpenPhysicalAudioDevice(new_default_device, &spec)) {
needs_migration = false; }
}
if (needs_migration) {
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; }
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
SDL_assert(SDL_GetAtomicInt(¤t_default_device->refcount) > 1); RefPhysicalAudioDevice(new_default_device);
UnrefPhysicalAudioDevice(current_default_device);
SDL_SetAudioPostmixCallback(logdev->instance_id, logdev->postmix, logdev->postmix_userdata);
SDL_PendingAudioDeviceEvent *p;
if (spec_changed) {
p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent));
if (p) { 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) { ClosePhysicalAudioDevice(current_default_device);
}
}
ReleaseAudioDevice(current_default_device);
}
ReleaseAudioDevice(new_default_device);
if (current_default_device) { UnrefPhysicalAudioDevice(current_default_device);
}
if (pending.next) {
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
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.subsystem_rwlock);
}
}
bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
{
const int orig_work_buffer_size = device->work_buffer_size;
if (SDL_AudioSpecsEqual(&device->spec, newspec, NULL, NULL) && (new_sample_frames == device->sample_frames)) {
return true; }
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;
}
}
}
if (!kill_device) {
SDL_PendingAudioDeviceEvent pending;
pending.next = NULL;
SDL_PendingAudioDeviceEvent *pending_tail = &pending;
SDL_PendingAudioDeviceEvent *p;
p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent));
if (p) { 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) { 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.subsystem_rwlock);
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.subsystem_rwlock);
}
}
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;
}
void SDL_UpdateAudio(void)
{
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next;
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (!pending_events) {
return; }
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
pending_events = current_audio.pending_events.next; current_audio.pending_events.next = NULL;
current_audio.pending_events_tail = ¤t_audio.pending_events;
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
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 = SDL_IsAudioDeviceRecording(i->devid); SDL_PushEvent(&event);
}
SDL_free(i);
}
}