update SDL3 from 3.2.20 to 3.4.2

This commit is contained in:
Sven Balzer
2026-04-01 18:25:03 +02:00
parent 1daf4d79f1
commit 05b19704f8
1626 changed files with 124218 additions and 191491 deletions
+209 -157
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -77,6 +77,9 @@ static const AudioBootStrap *const bootstrap[] = {
#ifdef SDL_AUDIO_DRIVER_N3DS
&N3DSAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_NGAGE
&NGAGEAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
&EMSCRIPTENAUDIO_bootstrap,
#endif
@@ -133,11 +136,11 @@ int SDL_GetNumAudioDrivers(void)
const char *SDL_GetAudioDriver(int index)
{
if (index >= 0 && index < SDL_GetNumAudioDrivers()) {
return deduped_bootstrap[index]->name;
CHECK_PARAM(index < 0 || index >= SDL_GetNumAudioDrivers()) {
SDL_InvalidParamError("index");
return NULL;
}
SDL_InvalidParamError("index");
return NULL;
return deduped_bootstrap[index]->name;
}
const char *SDL_GetCurrentAudioDriver(void)
@@ -168,10 +171,13 @@ int SDL_GetDefaultSampleFramesFromFreq(const int freq)
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);
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;
}
@@ -183,16 +189,15 @@ void OnAudioStreamCreated(SDL_AudioStream *stream)
// 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.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.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
}
@@ -203,9 +208,8 @@ void OnAudioStreamDestroy(SDL_AudioStream *stream)
// 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.subsystem_rwlock) {
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
if (stream->prev) {
stream->prev->next = stream->next;
}
@@ -215,7 +219,7 @@ void OnAudioStreamDestroy(SDL_AudioStream *stream)
if (stream == current_audio.existing_streams) {
current_audio.existing_streams = stream->next;
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
}
@@ -366,14 +370,28 @@ static SDL_AudioDeviceID AssignAudioDeviceInstanceId(bool recording, bool islogi
bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid)
{
// bit #1 of devid is set for physical devices and unset for logical.
return (devid & (1 << 1)) != 0;
}
static bool SDL_IsAudioDeviceLogical(SDL_AudioDeviceID devid)
{
// bit #1 of devid is set for physical devices and unset for logical.
return (devid & (1 << 1)) == 0;
}
bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid)
{
// bit #0 of devid is set for playback devices and unset for recording.
return (devid & (1 << 0)) != 0;
}
static bool SDL_IsAudioDeviceRecording(SDL_AudioDeviceID devid)
{
// bit #0 of devid is set for playback devices and unset for recording.
return (devid & (1 << 0)) == 0;
}
static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE
{
if (device) {
@@ -404,21 +422,19 @@ static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid,
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 (SDL_IsAudioDeviceLogical(devid)) { // don't bother looking if it's not a logical device id value.
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); // reference it, in case the logical device migrates to a new default.
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (logdev) {
// we have to release the device_hash_lock before we take the device lock, to avoid deadlocks, so do a loop
// we have to release the subsystem_rwlock 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);
@@ -451,17 +467,15 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // !
{
SDL_AudioDevice *device = NULL;
// bit #1 of devid is set for physical devices and unset for logical.
const bool islogical = !(devid & (1<<1));
if (islogical) {
if (SDL_IsAudioDeviceLogical(devid)) {
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_assert(device->instance_id == devid);
SDL_UnlockRWLock(current_audio.device_hash_lock);
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");
@@ -483,13 +497,13 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI
const SDL_AudioDeviceID orig_devid = devid;
while (true) {
SDL_LockRWLockForReading(current_audio.device_hash_lock);
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.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (devid == 0) {
SDL_SetError("No default audio device available");
@@ -503,13 +517,13 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI
// 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);
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.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (got_it) {
return device;
@@ -526,10 +540,10 @@ static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceI
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);
if (current_audio.device_hash_logical) { // will be NULL while shutting down.
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);
}
// remove ourselves from the physical device's list of logical devices.
@@ -590,11 +604,11 @@ 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_LockRWLockForWriting(current_audio.subsystem_rwlock);
if (SDL_RemoveFromHashTable(current_audio.device_hash_physical, (const void *) (uintptr_t) device->instance_id)) {
SDL_AddAtomicInt(device->recording ? &current_audio.recording_device_count : &current_audio.playback_device_count, -1);
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
DestroyPhysicalAudioDevice(device); // ...and nuke it.
}
}
@@ -608,9 +622,9 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi
{
SDL_assert(name != NULL);
SDL_LockRWLockForReading(current_audio.device_hash_lock);
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
const int shutting_down = SDL_GetAtomicInt(&current_audio.shutting_down);
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (shutting_down) {
return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment.
}
@@ -652,8 +666,8 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi
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_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);
@@ -662,7 +676,7 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recordi
SDL_free(device);
device = NULL;
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
RefPhysicalAudioDevice(device); // unref'd on device disconnect.
return device;
@@ -710,12 +724,12 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_
p->type = SDL_EVENT_AUDIO_DEVICE_ADDED;
p->devid = device->instance_id;
p->next = NULL;
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
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.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
}
@@ -723,11 +737,10 @@ SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_
}
// 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)
static void SDLCALL SDL_AudioDeviceDisconnected_OnMainThread(void *userdata)
{
if (!device) {
return;
}
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_assert(device != NULL);
// 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
@@ -739,10 +752,10 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
ObtainPhysicalAudioDeviceObj(device);
SDL_LockRWLockForReading(current_audio.device_hash_lock);
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.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1);
if (first_disconnect) { // if already disconnected this device, don't do it twice.
@@ -787,16 +800,33 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
if (first_disconnect) {
if (pending.next) { // NULL if event is disabled or disaster struck.
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
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.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
UnrefPhysicalAudioDevice(device);
}
// We always ref this in SDL_AudioDeviceDisconnected(), so if multiple attempts
// to disconnect are queued, the pointer stays valid until the last one comes
// through.
UnrefPhysicalAudioDevice(device);
}
void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
{
// lots of risk of various audio backends deadlocking because they're calling
// this while holding a backend-specific lock, which causes problems when we
// want to obtain the device lock while its audio thread is also waiting for
// that lock to be released. So just queue the work on the main thread.
if (device) {
RefPhysicalAudioDevice(device);
SDL_RunOnMainThread(SDL_AudioDeviceDisconnected_OnMainThread, device, false);
}
}
@@ -878,11 +908,8 @@ static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *tabl
{
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)) {
SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical.
if ((SDL_IsAudioDeviceRecording(devid) == data->recording) && (devid < data->highest)) {
data->highest = devid;
data->result = (SDL_AudioDevice *) value;
SDL_assert(data->result->instance_id == devid);
@@ -896,9 +923,9 @@ static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording)
// (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);
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
SDL_IterateHashTable(current_audio.device_hash_physical, FindLowestDeviceID, &data);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
return data.result;
}
@@ -922,14 +949,21 @@ bool SDL_InitAudio(const char *driver_name)
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) {
SDL_RWLock *subsystem_rwlock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem.
if (!subsystem_rwlock) {
return false;
}
SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL);
if (!device_hash) {
SDL_DestroyRWLock(device_hash_lock);
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;
}
@@ -946,8 +980,9 @@ bool SDL_InitAudio(const char *driver_name)
const char *driver_attempt = driver_name_copy;
if (!driver_name_copy) {
SDL_DestroyRWLock(device_hash_lock);
SDL_DestroyHashTable(device_hash);
SDL_DestroyRWLock(subsystem_rwlock);
SDL_DestroyHashTable(device_hash_physical);
SDL_DestroyHashTable(device_hash_logical);
return false;
}
@@ -969,8 +1004,9 @@ bool SDL_InitAudio(const char *driver_name)
tried_to_init = true;
SDL_zero(current_audio);
current_audio.pending_events_tail = &current_audio.pending_events;
current_audio.device_hash_lock = device_hash_lock;
current_audio.device_hash = device_hash;
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(&current_audio.impl)) {
current_audio.name = bootstrap[i]->name;
current_audio.desc = bootstrap[i]->desc;
@@ -993,8 +1029,9 @@ bool SDL_InitAudio(const char *driver_name)
tried_to_init = true;
SDL_zero(current_audio);
current_audio.pending_events_tail = &current_audio.pending_events;
current_audio.device_hash_lock = device_hash_lock;
current_audio.device_hash = device_hash;
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(&current_audio.impl)) {
current_audio.name = bootstrap[i]->name;
current_audio.desc = bootstrap[i]->desc;
@@ -1003,7 +1040,9 @@ bool SDL_InitAudio(const char *driver_name)
}
}
if (!initialized) {
if (initialized) {
SDL_DebugLogBackend("audio", current_audio.name);
} else {
// specific drivers will set the error message if they fail, but otherwise we do it here.
if (!tried_to_init) {
if (driver_name) {
@@ -1013,8 +1052,9 @@ bool SDL_InitAudio(const char *driver_name)
}
}
SDL_DestroyRWLock(device_hash_lock);
SDL_DestroyHashTable(device_hash);
SDL_DestroyRWLock(subsystem_rwlock);
SDL_DestroyHashTable(device_hash_physical);
SDL_DestroyHashTable(device_hash_logical);
SDL_zero(current_audio);
return false; // No driver was available, so fail.
}
@@ -1050,15 +1090,11 @@ bool SDL_InitAudio(const char *driver_name)
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) {
SDL_AudioDevice *dev = (SDL_AudioDevice *) value;
SDL_assert(dev->instance_id == devid);
DestroyPhysicalAudioDevice(dev);
}
SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical.
SDL_AudioDevice *dev = (SDL_AudioDevice *) value;
SDL_assert(dev->instance_id == devid);
DestroyPhysicalAudioDevice(dev);
return true; // keep iterating.
}
@@ -1070,20 +1106,28 @@ void SDL_QuitAudio(void)
current_audio.impl.DeinitializeStart();
// Destroy any audio streams that still exist...
while (current_audio.existing_streams) {
SDL_DestroyAudioStream(current_audio.existing_streams);
// Destroy any audio streams that still exist...unless app asked to keep it.
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.device_hash_lock);
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
SDL_SetAtomicInt(&current_audio.shutting_down, 1);
SDL_HashTable *device_hash = current_audio.device_hash;
current_audio.device_hash = NULL;
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(&current_audio.playback_device_count, 0);
SDL_SetAtomicInt(&current_audio.recording_device_count, 0);
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
SDL_PendingAudioDeviceEvent *pending_next = NULL;
for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) {
@@ -1091,13 +1135,15 @@ void SDL_QuitAudio(void)
SDL_free(i);
}
SDL_IterateHashTable(device_hash, DestroyOnePhysicalAudioDevice, NULL);
SDL_IterateHashTable(device_hash_physical, DestroyOnePhysicalAudioDevice, NULL);
// device_hash_* will _not_ be empty because we nulled them out in current_audio, but all their items are now free'd pointers. Just destroy the hashes, below.
// Free the driver data
current_audio.impl.Deinitialize();
SDL_DestroyRWLock(current_audio.device_hash_lock);
SDL_DestroyHashTable(device_hash);
SDL_DestroyRWLock(current_audio.subsystem_rwlock);
SDL_DestroyHashTable(device_hash_physical);
SDL_DestroyHashTable(device_hash_logical);
SDL_zero(current_audio);
}
@@ -1152,11 +1198,9 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
// We should have updated this elsewhere if the format changed!
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) { // 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.
@@ -1254,7 +1298,11 @@ void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device)
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);
int delay = ((frames * 1000) / device->spec.freq) * 2;
if (delay > 100) {
delay = 100;
}
SDL_Delay(delay);
}
current_audio.impl.ThreadDeinit(device);
SDL_AudioThreadFinalize(device);
@@ -1407,13 +1455,10 @@ static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table
{
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(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical.
if (SDL_IsAudioDeviceRecording(devid) == data->recording) {
SDL_assert(data->devs_seen < data->num_devices);
SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the device_hash_lock here.
SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the subsystem_rwlock here.
const bool zombie = SDL_GetAtomicInt(&device->zombie) != 0;
if (zombie) {
data->devs_skipped++;
@@ -1430,19 +1475,19 @@ static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording)
int num_devices = 0;
if (SDL_GetCurrentAudioDriver()) {
SDL_LockRWLockForReading(current_audio.device_hash_lock);
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
{
num_devices = SDL_GetAtomicInt(recording ? &current_audio.recording_device_count : &current_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, CountAudioDevices, &data);
SDL_IterateHashTable(current_audio.device_hash_physical, CountAudioDevices, &data);
SDL_assert((data.devs_seen + data.devs_skipped) == num_devices);
num_devices = data.devs_seen; // might be less if we skipped any.
result[num_devices] = 0; // null-terminated.
}
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
} else {
SDL_SetError("Audio subsystem is not initialized");
}
@@ -1478,15 +1523,12 @@ static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTabl
{
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;
SDL_assert(data->retval->instance_id == devid);
return false; // stop iterating, we found it.
}
SDL_assert(SDL_IsAudioDevicePhysical(devid)); // should only be iterating device_hash_physical.
SDL_AudioDevice *device = (SDL_AudioDevice *) value;
if (data->callback(device, data->userdata)) { // found it?
data->retval = device;
SDL_assert(data->retval->instance_id == devid);
return false; // stop iterating, we found it.
}
return true; // keep iterating.
}
@@ -1500,9 +1542,9 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_Audi
}
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);
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");
@@ -1524,19 +1566,28 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle)
const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)
{
// bit #1 of devid is set for physical devices and unset for logical.
const bool islogical = !(devid & (1<<1));
const char *result = NULL;
const void *vdev = NULL;
if (!SDL_GetCurrentAudioDriver()) {
SDL_SetError("Audio subsystem is not initialized");
} else {
const bool islogical = SDL_IsAudioDeviceLogical(devid);
const void *vdev = NULL;
// This does not call ObtainPhysicalAudioDevice() because the device's name never changes, so
// it doesn't have to lock the whole device. However, just to make sure the device pointer itself
// remains valid (in case the device is unplugged at the wrong moment), we hold the
// device_hash_lock while we copy the string.
SDL_LockRWLockForReading(current_audio.device_hash_lock);
SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, &vdev);
// subsystem_rwlock while we copy the string.
SDL_LockRWLockForReading(current_audio.subsystem_rwlock);
// Allow default device IDs to be used, just return the current default physical device's name.
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) {
@@ -1548,7 +1599,7 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)
SDL_assert(device->instance_id == devid);
result = SDL_GetPersistentString(device->name);
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
return result;
@@ -1556,7 +1607,7 @@ const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)
bool SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames)
{
if (!spec) {
CHECK_PARAM(!spec) {
return SDL_InvalidParamError("spec");
}
@@ -1581,9 +1632,7 @@ int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count)
SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
if (device) {
channels = device->spec.channels;
if (channels > 0 && device->chmap) {
result = SDL_ChannelMapDup(device->chmap, channels);
}
result = SDL_ChannelMapDup(device->chmap, channels);
}
ReleaseAudioDevice(device);
@@ -1823,8 +1872,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp
// 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) {
if ((wants_default || SDL_IsAudioDevicePhysical(devid))) {
device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
} else {
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
@@ -1861,9 +1909,9 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp
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);
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;
@@ -1918,7 +1966,7 @@ float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid)
bool SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain)
{
if (gain < 0.0f) {
CHECK_PARAM(gain < 0.0f) {
return SDL_InvalidParamError("gain");
}
@@ -1938,8 +1986,9 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba
{
SDL_AudioDevice *device = NULL;
SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
bool result = true;
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) {
@@ -1960,18 +2009,21 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba
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) {
}
CHECK_PARAM(num_streams < 0) {
return SDL_InvalidParamError("num_streams");
} else if (!streams) {
}
CHECK_PARAM(!streams) {
return SDL_InvalidParamError("streams");
} else if (!islogical) {
}
CHECK_PARAM(SDL_IsAudioDevicePhysical(devid)) {
return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices");
}
@@ -2131,7 +2183,7 @@ SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream *stream)
{
SDL_AudioDeviceID result = 0;
if (!stream) {
CHECK_PARAM(!stream) {
SDL_InvalidParamError("stream");
return 0;
}
@@ -2312,7 +2364,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
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);
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) {
@@ -2322,7 +2374,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
current_audio.default_playback_device_id = new_default_device->instance_id;
}
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
if (is_already_default) {
return; // this is already the default.
@@ -2388,8 +2440,8 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
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);
// now migrate the logical device. Hold subsystem_rwlock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition.
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
if (logdev->next) {
logdev->next->prev = logdev->prev;
}
@@ -2404,7 +2456,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *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_UnlockRWLock(current_audio.subsystem_rwlock);
SDL_assert(SDL_GetAtomicInt(&current_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);
@@ -2446,12 +2498,12 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
}
if (pending.next) {
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
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.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
}
@@ -2528,12 +2580,12 @@ bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SD
}
if (pending.next) {
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
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.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
}
}
@@ -2555,20 +2607,20 @@ bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *
// ("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_LockRWLockForReading(current_audio.subsystem_rwlock);
SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next;
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
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);
SDL_LockRWLockForWriting(current_audio.subsystem_rwlock);
pending_events = current_audio.pending_events.next; // in case this changed...
current_audio.pending_events.next = NULL;
current_audio.pending_events_tail = &current_audio.pending_events;
SDL_UnlockRWLock(current_audio.device_hash_lock);
SDL_UnlockRWLock(current_audio.subsystem_rwlock);
SDL_PendingAudioDeviceEvent *pending_next = NULL;
for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) {
@@ -2578,7 +2630,7 @@ void SDL_UpdateAudio(void)
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.
event.adevice.recording = SDL_IsAudioDeviceRecording(i->devid); // bit #0 of devid is set for playback devices and unset for recording.
SDL_PushEvent(&event);
}
SDL_free(i);
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+305 -96
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -280,7 +280,7 @@ void ConvertAudio(int num_frames,
// swizzle input to "standard" format if necessary.
if (src_map) {
void* buf = scratch ? scratch : dst; // use scratch if available, since it has to be big enough to hold src, unless it's NULL, then dst has to be.
void *buf = scratch ? scratch : dst; // use scratch if available, since it has to be big enough to hold src, unless it's NULL, then dst has to be.
SwizzleAudio(num_frames, buf, src, src_channels, src_map, src_format);
src = buf;
}
@@ -318,7 +318,7 @@ void ConvertAudio(int num_frames,
// get us to float format.
if (srcconvert) {
void* buf = (channelconvert || dstconvert) ? scratch : dst;
void *buf = (channelconvert || dstconvert) ? scratch : dst;
ConvertAudioToFloat((float *) buf, src, num_frames * src_channels, src_format);
src = buf;
}
@@ -332,7 +332,7 @@ void ConvertAudio(int num_frames,
buf[i] *= gain;
}
} else {
float *fsrc = (float *)src;
const float *fsrc = (const float *)src;
for (int i = 0; i < total_samples; i++) {
buf[i] = fsrc[i] * gain;
}
@@ -368,7 +368,7 @@ void ConvertAudio(int num_frames,
channel_converter = override;
}
void* buf = dstconvert ? scratch : dst;
void *buf = dstconvert ? scratch : dst;
channel_converter((float *) buf, (const float *) src, num_frames);
src = buf;
}
@@ -399,7 +399,7 @@ static int CalculateMaxFrameSize(SDL_AudioFormat src_format, int src_channels, S
return max_format_size * max_channels;
}
static Sint64 GetAudioStreamResampleRate(SDL_AudioStream* stream, int src_freq, Sint64 resample_offset)
static Sint64 GetAudioStreamResampleRate(SDL_AudioStream *stream, int src_freq, Sint64 resample_offset)
{
src_freq = (int)((float)src_freq * stream->freq_ratio);
@@ -474,10 +474,11 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_
SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
SDL_InvalidParamError("stream");
return 0;
}
SDL_LockMutex(stream->lock);
if (stream->props == 0) {
stream->props = SDL_CreateProperties();
@@ -488,9 +489,10 @@ SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream)
bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
SDL_LockMutex(stream->lock);
stream->get_callback = callback;
stream->get_callback_userdata = userdata;
@@ -500,9 +502,10 @@ bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallb
bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
SDL_LockMutex(stream->lock);
stream->put_callback = callback;
stream->put_callback_userdata = userdata;
@@ -512,25 +515,33 @@ bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallb
bool SDL_LockAudioStream(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
SDL_LockMutex(stream->lock);
return true;
}
bool SDL_UnlockAudioStream(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
SDL_UnlockMutex(stream->lock);
return true;
}
bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec)
{
if (!stream) {
CHECK_PARAM(!stream) {
if (src_spec) {
SDL_zerop(src_spec);
}
if (dst_spec) {
SDL_zerop(dst_spec);
}
return SDL_InvalidParamError("stream");
}
@@ -554,7 +565,7 @@ bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec,
bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
@@ -563,21 +574,25 @@ bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_
// like 196608000Hz. File a bug. :P
if (src_spec) {
if (!SDL_IsSupportedAudioFormat(src_spec->format)) {
CHECK_PARAM(!SDL_IsSupportedAudioFormat(src_spec->format)) {
return SDL_InvalidParamError("src_spec->format");
} else if (!SDL_IsSupportedChannelCount(src_spec->channels)) {
}
CHECK_PARAM(!SDL_IsSupportedChannelCount(src_spec->channels)) {
return SDL_InvalidParamError("src_spec->channels");
} else if (src_spec->freq <= 0) {
}
CHECK_PARAM(src_spec->freq <= 0) {
return SDL_InvalidParamError("src_spec->freq");
}
}
if (dst_spec) {
if (!SDL_IsSupportedAudioFormat(dst_spec->format)) {
CHECK_PARAM(!SDL_IsSupportedAudioFormat(dst_spec->format)) {
return SDL_InvalidParamError("dst_spec->format");
} else if (!SDL_IsSupportedChannelCount(dst_spec->channels)) {
}
CHECK_PARAM(!SDL_IsSupportedChannelCount(dst_spec->channels)) {
return SDL_InvalidParamError("dst_spec->channels");
} else if (dst_spec->freq <= 0) {
}
CHECK_PARAM(dst_spec->freq <= 0) {
return SDL_InvalidParamError("dst_spec->freq");
}
}
@@ -616,7 +631,7 @@ bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_
bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
@@ -702,7 +717,7 @@ int *SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count)
float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
SDL_InvalidParamError("stream");
return 0.0f;
}
@@ -716,7 +731,7 @@ float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream)
bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
@@ -739,7 +754,7 @@ bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio)
float SDL_GetAudioStreamGain(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
SDL_InvalidParamError("stream");
return -1.0f;
}
@@ -753,9 +768,10 @@ float SDL_GetAudioStreamGain(SDL_AudioStream *stream)
bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
} else if (gain < 0.0f) {
}
CHECK_PARAM(gain < 0.0f) {
return SDL_InvalidParamError("gain");
}
@@ -768,16 +784,48 @@ bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain)
static bool CheckAudioStreamIsFullySetup(SDL_AudioStream *stream)
{
if (stream->src_spec.format == 0) {
if (stream->src_spec.format == SDL_AUDIO_UNKNOWN) {
return SDL_SetError("Stream has no source format");
} else if (stream->dst_spec.format == 0) {
} else if (stream->dst_spec.format == SDL_AUDIO_UNKNOWN) {
return SDL_SetError("Stream has no destination format");
}
return true;
}
static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata)
// you MUST hold `stream->lock` when calling this, and validate your parameters!
static bool PutAudioStreamBufferInternal(SDL_AudioStream *stream, const SDL_AudioSpec *spec, const int *chmap, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void *userdata)
{
SDL_AudioTrack *track = NULL;
if (callback) {
track = SDL_CreateAudioTrack(stream->queue, spec, chmap, (Uint8 *)buf, len, len, callback, userdata);
if (!track) {
return false;
}
}
const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
bool retval = true;
if (track) {
SDL_AddTrackToAudioQueue(stream->queue, track);
} else {
retval = SDL_WriteToAudioQueue(stream->queue, spec, chmap, (const Uint8 *)buf, len);
}
if (retval) {
if (stream->put_callback) {
const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail);
}
}
return retval;
}
static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void *userdata)
{
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: wants to put %d bytes", len);
@@ -795,53 +843,31 @@ static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int l
return SDL_SetError("Can't add partial sample frames");
}
SDL_AudioTrack* track = NULL;
if (callback) {
track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, stream->src_chmap, (Uint8 *)buf, len, len, callback, userdata);
if (!track) {
SDL_UnlockMutex(stream->lock);
return false;
}
}
const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
bool result = true;
if (track) {
SDL_AddTrackToAudioQueue(stream->queue, track);
} else {
result = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, stream->src_chmap, (const Uint8 *)buf, len);
}
if (result) {
if (stream->put_callback) {
const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail);
}
}
const bool retval = PutAudioStreamBufferInternal(stream, &stream->src_spec, stream->src_chmap, buf, len, callback, userdata);
SDL_UnlockMutex(stream->lock);
return result;
return retval;
}
static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len)
{
SDL_free((void*) buf);
SDL_free((void *)buf);
}
bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
} else if (!buf) {
}
CHECK_PARAM(!buf) {
return SDL_InvalidParamError("buf");
} else if (len < 0) {
}
CHECK_PARAM(len < 0) {
return SDL_InvalidParamError("len");
} else if (len == 0) {
}
if (len == 0) {
return true; // nothing to do.
}
@@ -857,9 +883,8 @@ bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
}
SDL_memcpy(data, buf, len);
buf = data;
bool ret = PutAudioStreamBuffer(stream, buf, len, FreeAllocatedAudioBuffer, NULL);
bool ret = PutAudioStreamBuffer(stream, data, len, FreeAllocatedAudioBuffer, NULL);
if (!ret) {
SDL_free(data);
}
@@ -869,9 +894,192 @@ bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
return PutAudioStreamBuffer(stream, buf, len, NULL, NULL);
}
#define GENERIC_INTERLEAVE_FUNCTION(bits) \
static void InterleaveAudioChannelsGeneric##bits(void *output, const void * const *channel_buffers, const int channels, int num_samples) { \
Uint##bits *dst = (Uint##bits *) output; \
const Uint##bits * const *srcs = (const Uint##bits * const *) channel_buffers; \
for (int frame = 0; frame < num_samples; frame++) { \
for (int channel = 0; channel < channels; channel++) { \
*(dst++) = srcs[channel][frame]; \
} \
} \
}
GENERIC_INTERLEAVE_FUNCTION(8)
GENERIC_INTERLEAVE_FUNCTION(16)
GENERIC_INTERLEAVE_FUNCTION(32)
//GENERIC_INTERLEAVE_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.)
#undef GENERIC_INTERLEAVE_FUNCTION
#define GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(bits) \
static void InterleaveAudioChannelsWithNullsGeneric##bits(void *output, const void * const *channel_buffers, const int channels, int num_samples, const int isilence) { \
const Uint##bits silence = (Uint##bits) isilence; \
Uint##bits *dst = (Uint##bits *) output; \
const Uint##bits * const *srcs = (const Uint##bits * const *) channel_buffers; \
for (int frame = 0; frame < num_samples; frame++) { \
for (int channel = 0; channel < channels; channel++) { \
*(dst++) = srcs[channel] ? srcs[channel][frame] : silence; \
} \
} \
}
GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(8)
GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(16)
GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(32)
//GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.)
#undef GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION
static void InterleaveAudioChannels(void *output, const void * const *channel_buffers, int channels, int num_samples, const SDL_AudioSpec *spec)
{
bool have_null_channel = false;
void *channels_full[16];
// if didn't specify enough channels, pad out a channel array with NULLs.
if ((channels >= 0) && (channels < spec->channels)) {
have_null_channel = true;
SDL_assert(SDL_IsSupportedChannelCount(spec->channels));
SDL_assert(spec->channels <= SDL_arraysize(channels_full));
SDL_memcpy(channels_full, channel_buffers, channels * sizeof (*channel_buffers));
SDL_memset(channels_full + channels, 0, (spec->channels - channels) * sizeof (*channel_buffers));
channel_buffers = (const void * const *) channels_full;
}
channels = spec->channels; // it's either < 0, needs to be clamped to spec->channels, or we just padded it out to spec->channels with channels_full.
if (!have_null_channel) {
for (int i = 0; i < channels; i++) {
if (channel_buffers[i] == NULL) {
have_null_channel = true;
break;
}
}
}
if (have_null_channel) {
const int silence = SDL_GetSilenceValueForFormat(spec->format);
switch (SDL_AUDIO_BITSIZE(spec->format)) {
case 8: InterleaveAudioChannelsWithNullsGeneric8(output, channel_buffers, channels, num_samples, silence); break;
case 16: InterleaveAudioChannelsWithNullsGeneric16(output, channel_buffers, channels, num_samples, silence); break;
case 32: InterleaveAudioChannelsWithNullsGeneric32(output, channel_buffers, channels, num_samples, silence); break;
//case 64: InterleaveAudioChannelsGeneric64(output, channel_buffers, channels, num_samples); break; (we don't have any 64-bit audio data types at the moment.)
default: SDL_assert(!"Missing needed generic audio interleave function!"); SDL_memset(output, 0, SDL_AUDIO_FRAMESIZE(*spec) * num_samples); break;
}
} else {
// !!! FIXME: it would be possible to do this really well in SIMD for stereo data, using unpack (intel) or zip (arm) instructions, etc.
switch (SDL_AUDIO_BITSIZE(spec->format)) {
case 8: InterleaveAudioChannelsGeneric8(output, channel_buffers, channels, num_samples); break;
case 16: InterleaveAudioChannelsGeneric16(output, channel_buffers, channels, num_samples); break;
case 32: InterleaveAudioChannelsGeneric32(output, channel_buffers, channels, num_samples); break;
//case 64: InterleaveAudioChannelsGeneric64(output, channel_buffers, channels, num_samples); break; (we don't have any 64-bit audio data types at the moment.)
default: SDL_assert(!"Missing needed generic audio interleave function!"); SDL_memset(output, 0, SDL_AUDIO_FRAMESIZE(*spec) * num_samples); break;
}
}
}
bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_channels, int num_samples)
{
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
CHECK_PARAM(!channel_buffers) {
return SDL_InvalidParamError("channel_buffers");
}
CHECK_PARAM(num_samples < 0) {
return SDL_InvalidParamError("num_samples");
}
if (num_samples == 0) {
return true; // nothing to do.
}
// we do the interleaving up front without the lock held, so the audio device doesn't starve while we work.
// but we _do_ need to know the current input spec.
SDL_AudioSpec spec;
int chmap_copy[SDL_MAX_CHANNELMAP_CHANNELS];
int *chmap = NULL;
SDL_LockMutex(stream->lock);
if (!CheckAudioStreamIsFullySetup(stream)) {
SDL_UnlockMutex(stream->lock);
return false;
}
SDL_copyp(&spec, &stream->src_spec);
if (stream->src_chmap) {
chmap = chmap_copy;
SDL_memcpy(chmap, stream->src_chmap, sizeof (*chmap) * spec.channels);
}
SDL_UnlockMutex(stream->lock);
if (spec.channels == 1) { // nothing to interleave, just use the usual function.
return SDL_PutAudioStreamData(stream, channel_buffers[0], SDL_AUDIO_FRAMESIZE(spec) * num_samples);
}
bool retval = false;
const int len = SDL_AUDIO_FRAMESIZE(spec) * num_samples;
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: wants to put %d bytes of planar data", len);
#endif
// Is the data small enough to just interleave it on the stack and put it through the normal interface?
#define INTERLEAVE_STACK_SIZE 1024
Uint8 stackbuf[INTERLEAVE_STACK_SIZE];
void *data = stackbuf;
SDL_ReleaseAudioBufferCallback callback = NULL;
if (len > INTERLEAVE_STACK_SIZE) {
// too big for the stack? Just SDL_malloc a block and interleave into that. To avoid the extra copy, we'll just set it as a
// new track in the queue (the distinction is specifying a callback to PutAudioStreamBufferInternal, to release the buffer).
data = SDL_malloc(len);
if (!data) {
return false;
}
callback = FreeAllocatedAudioBuffer;
}
InterleaveAudioChannels(data, channel_buffers, num_channels, num_samples, &spec);
// it's okay if the stream format changed on another thread while we didn't hold the lock; PutAudioStreamBufferInternal will notice
// and set up a new track with the right format, and the next SDL_PutAudioStreamData will notice that stream->src_spec doesn't
// match the new track and set up a new one again. It's a bad idea to change the format on another thread while putting here,
// but everything _will_ work out with the format that was (presumably) expected.
SDL_LockMutex(stream->lock);
retval = PutAudioStreamBufferInternal(stream, &spec, chmap, data, len, callback, NULL);
SDL_UnlockMutex(stream->lock);
return retval;
}
static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len)
{
// We don't own the buffer, but know it will outlive the stream
}
bool SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int len, SDL_AudioStreamDataCompleteCallback callback, void *userdata)
{
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
CHECK_PARAM(!buf) {
return SDL_InvalidParamError("buf");
}
CHECK_PARAM(len < 0) {
return SDL_InvalidParamError("len");
}
if (len == 0) {
if (callback) {
callback(userdata, buf, len);
}
return true; // nothing to do.
}
return PutAudioStreamBuffer(stream, buf, len, callback ? callback : DontFreeThisAudioBuffer, userdata);
}
bool SDL_FlushAudioStream(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
@@ -901,8 +1109,8 @@ static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t ne
return ptr;
}
static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter,
Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed)
static Sint64 NextAudioStreamIter(SDL_AudioStream *stream, void **inout_iter,
Sint64 *inout_resample_offset, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed)
{
SDL_AudioSpec spec;
bool flushed;
@@ -956,9 +1164,9 @@ static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter,
return output_frames;
}
static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out_resample_offset)
static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream *stream, Sint64 *out_resample_offset)
{
void* iter = SDL_BeginAudioQueueIter(stream->queue);
void *iter = SDL_BeginAudioQueueIter(stream->queue);
Sint64 resample_offset = stream->resample_offset;
Sint64 output_frames = 0;
@@ -980,9 +1188,9 @@ static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out
return output_frames;
}
static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed)
static Sint64 GetAudioStreamHead(SDL_AudioStream *stream, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed)
{
void* iter = SDL_BeginAudioQueueIter(stream->queue);
void *iter = SDL_BeginAudioQueueIter(stream->queue);
if (!iter) {
SDL_zerop(out_spec);
@@ -998,8 +1206,8 @@ static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spe
// Enough input data MUST be available!
static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int output_frames, float gain)
{
const SDL_AudioSpec* src_spec = &stream->input_spec;
const SDL_AudioSpec* dst_spec = &stream->dst_spec;
const SDL_AudioSpec *src_spec = &stream->input_spec;
const SDL_AudioSpec *dst_spec = &stream->dst_spec;
const SDL_AudioFormat src_format = src_spec->format;
const int src_channels = src_spec->channels;
@@ -1019,7 +1227,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o
// Not resampling? It's an easy conversion (and maybe not even that!)
if (resample_rate == 0) {
Uint8* work_buffer = NULL;
Uint8 *work_buffer = NULL;
// Ensure we have enough scratch space for any conversions
if ((src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f)) {
@@ -1089,7 +1297,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o
work_buffer_capacity += resample_bytes;
}
Uint8* work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity);
Uint8 *work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity);
if (!work_buffer) {
return false;
@@ -1101,7 +1309,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o
const float postresample_gain = (input_frames > output_frames) ? gain : 1.0f;
// (dst channel map is NULL because we'll do the final swizzle on ConvertAudio after resample.)
const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue,
const Uint8 *input_buffer = SDL_ReadFromAudioQueue(stream->queue,
NULL, resample_format, resample_channels, NULL,
padding_frames, input_frames, padding_frames, work_buffer, preresample_gain);
@@ -1112,11 +1320,11 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o
input_buffer += padding_frames * resample_frame_size;
// Decide where the resampled output goes
void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf;
void *resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf;
SDL_ResampleAudio(resample_channels,
(const float *) input_buffer, input_frames,
(float*) resample_buffer, output_frames,
(const float *)input_buffer, input_frames,
(float *)resample_buffer, output_frames,
resample_rate, &stream->resample_offset);
// Convert to the final format, if necessary (src channel map is NULL because SDL_ReadFromAudioQueue already handled this).
@@ -1134,16 +1342,20 @@ int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int
SDL_Log("AUDIOSTREAM: want to get %d converted bytes", len);
#endif
if (!stream) {
CHECK_PARAM(!stream) {
SDL_InvalidParamError("stream");
return -1;
} else if (!buf) {
}
CHECK_PARAM(!buf) {
SDL_InvalidParamError("buf");
return -1;
} else if (len < 0) {
}
CHECK_PARAM(len < 0) {
SDL_InvalidParamError("len");
return -1;
} else if (len == 0) {
}
if (len == 0) {
return 0; // nothing to do.
}
@@ -1241,7 +1453,7 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len)
// number of converted/resampled bytes available for output
int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
SDL_InvalidParamError("stream");
return -1;
}
@@ -1267,7 +1479,7 @@ int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
// number of sample frames that are currently queued as input.
int SDL_GetAudioStreamQueued(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
SDL_InvalidParamError("stream");
return -1;
}
@@ -1284,7 +1496,7 @@ int SDL_GetAudioStreamQueued(SDL_AudioStream *stream)
bool SDL_ClearAudioStream(SDL_AudioStream *stream)
{
if (!stream) {
CHECK_PARAM(!stream) {
return SDL_InvalidParamError("stream");
}
@@ -1326,11 +1538,6 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream)
SDL_free(stream);
}
static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len)
{
// We don't own the buffer, but know it will outlive the stream
}
bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len)
{
if (dst_data) {
@@ -1341,13 +1548,16 @@ bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_dat
*dst_len = 0;
}
if (!src_data) {
CHECK_PARAM(!src_data) {
return SDL_InvalidParamError("src_data");
} else if (src_len < 0) {
}
CHECK_PARAM(src_len < 0) {
return SDL_InvalidParamError("src_len");
} else if (!dst_data) {
}
CHECK_PARAM(!dst_data) {
return SDL_InvalidParamError("dst_data");
} else if (!dst_len) {
}
CHECK_PARAM(!dst_len) {
return SDL_InvalidParamError("dst_len");
}
@@ -1357,8 +1567,7 @@ bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_dat
SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec);
if (stream) {
if (PutAudioStreamBuffer(stream, src_data, src_len, DontFreeThisAudioBuffer, NULL) &&
SDL_FlushAudioStream(stream)) {
if (SDL_PutAudioStreamDataNoCopy(stream, src_data, src_len, NULL, NULL) && SDL_FlushAudioStream(stream)) {
dstlen = SDL_GetAudioStreamAvailable(stream);
if (dstlen >= 0) {
dst = (Uint8 *)SDL_malloc(dstlen);
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+2 -2
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -25,7 +25,7 @@
// Internal functions used by SDL_AudioStream for queueing audio.
typedef void (SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen);
typedef SDL_AudioStreamDataCompleteCallback SDL_ReleaseAudioBufferCallback;
typedef struct SDL_AudioQueue SDL_AudioQueue;
typedef struct SDL_AudioTrack SDL_AudioTrack;
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+50 -50
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -185,7 +185,7 @@ static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num
#undef SIGNMASK
static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_samples)
static void SDL_Convert_Swap16_Scalar(Uint16 *dst, const Uint16 *src, int num_samples)
{
int i;
@@ -194,7 +194,7 @@ static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_sa
}
}
static void SDL_Convert_Swap32_Scalar(Uint32* dst, const Uint32* src, int num_samples)
static void SDL_Convert_Swap32_Scalar(Uint32 *dst, const Uint32 *src, int num_samples)
{
int i;
@@ -375,7 +375,7 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const f
const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
_mm_store_si128((__m128i*)&dst[i], bytes);
_mm_store_si128((__m128i *)&dst[i], bytes);
})
}
@@ -409,7 +409,7 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const f
const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
_mm_store_si128((__m128i*)&dst[i], bytes);
_mm_store_si128((__m128i *)&dst[i], bytes);
})
}
@@ -441,8 +441,8 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const
const __m128i shorts0 = _mm_packs_epi32(ints0, ints1);
const __m128i shorts1 = _mm_packs_epi32(ints2, ints3);
_mm_store_si128((__m128i*)&dst[i], shorts0);
_mm_store_si128((__m128i*)&dst[i + 8], shorts1);
_mm_store_si128((__m128i *)&dst[i], shorts0);
_mm_store_si128((__m128i *)&dst[i + 8], shorts1);
})
}
@@ -477,55 +477,55 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const
const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit)));
const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit)));
_mm_store_si128((__m128i*)&dst[i], ints0);
_mm_store_si128((__m128i*)&dst[i + 4], ints1);
_mm_store_si128((__m128i*)&dst[i + 8], ints2);
_mm_store_si128((__m128i*)&dst[i + 12], ints3);
_mm_store_si128((__m128i *)&dst[i], ints0);
_mm_store_si128((__m128i *)&dst[i + 4], ints1);
_mm_store_si128((__m128i *)&dst[i + 8], ints2);
_mm_store_si128((__m128i *)&dst[i + 12], ints3);
})
}
#endif
// FIXME: SDL doesn't have SSSE3 detection, so use the next one up
#ifdef SDL_SSE4_1_INTRINSICS
static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16* dst, const Uint16* src, int num_samples)
static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16 *dst, const Uint16 *src, int num_samples)
{
const __m128i shuffle = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
CONVERT_16_FWD({
dst[i] = SDL_Swap16(src[i]);
}, {
__m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]);
__m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 8]);
__m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]);
__m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
ints0 = _mm_shuffle_epi8(ints0, shuffle);
ints1 = _mm_shuffle_epi8(ints1, shuffle);
_mm_store_si128((__m128i*)&dst[i], ints0);
_mm_store_si128((__m128i*)&dst[i + 8], ints1);
_mm_store_si128((__m128i *)&dst[i], ints0);
_mm_store_si128((__m128i *)&dst[i + 8], ints1);
})
}
static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32* dst, const Uint32* src, int num_samples)
static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32 *dst, const Uint32 *src, int num_samples)
{
const __m128i shuffle = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
CONVERT_16_FWD({
dst[i] = SDL_Swap32(src[i]);
}, {
__m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]);
__m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 4]);
__m128i ints2 = _mm_loadu_si128((const __m128i*)&src[i + 8]);
__m128i ints3 = _mm_loadu_si128((const __m128i*)&src[i + 12]);
__m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]);
__m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 4]);
__m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
__m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 12]);
ints0 = _mm_shuffle_epi8(ints0, shuffle);
ints1 = _mm_shuffle_epi8(ints1, shuffle);
ints2 = _mm_shuffle_epi8(ints2, shuffle);
ints3 = _mm_shuffle_epi8(ints3, shuffle);
_mm_store_si128((__m128i*)&dst[i], ints0);
_mm_store_si128((__m128i*)&dst[i + 4], ints1);
_mm_store_si128((__m128i*)&dst[i + 8], ints2);
_mm_store_si128((__m128i*)&dst[i + 12], ints3);
_mm_store_si128((__m128i *)&dst[i], ints0);
_mm_store_si128((__m128i *)&dst[i + 4], ints1);
_mm_store_si128((__m128i *)&dst[i + 8], ints2);
_mm_store_si128((__m128i *)&dst[i + 12], ints3);
})
}
#endif
@@ -776,41 +776,41 @@ static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_s
fesetenv(&fenv);
}
static void SDL_Convert_Swap16_NEON(Uint16* dst, const Uint16* src, int num_samples)
static void SDL_Convert_Swap16_NEON(Uint16 *dst, const Uint16 *src, int num_samples)
{
CONVERT_16_FWD({
dst[i] = SDL_Swap16(src[i]);
}, {
uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]);
uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 8]);
uint8x16_t ints0 = vld1q_u8((const Uint8 *)&src[i]);
uint8x16_t ints1 = vld1q_u8((const Uint8 *)&src[i + 8]);
ints0 = vrev16q_u8(ints0);
ints1 = vrev16q_u8(ints1);
vst1q_u8((Uint8*)&dst[i], ints0);
vst1q_u8((Uint8*)&dst[i + 8], ints1);
vst1q_u8((Uint8 *)&dst[i], ints0);
vst1q_u8((Uint8 *)&dst[i + 8], ints1);
})
}
static void SDL_Convert_Swap32_NEON(Uint32* dst, const Uint32* src, int num_samples)
static void SDL_Convert_Swap32_NEON(Uint32 *dst, const Uint32 *src, int num_samples)
{
CONVERT_16_FWD({
dst[i] = SDL_Swap32(src[i]);
}, {
uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]);
uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 4]);
uint8x16_t ints2 = vld1q_u8((const Uint8*)&src[i + 8]);
uint8x16_t ints3 = vld1q_u8((const Uint8*)&src[i + 12]);
uint8x16_t ints0 = vld1q_u8((const Uint8 *)&src[i]);
uint8x16_t ints1 = vld1q_u8((const Uint8 *)&src[i + 4]);
uint8x16_t ints2 = vld1q_u8((const Uint8 *)&src[i + 8]);
uint8x16_t ints3 = vld1q_u8((const Uint8 *)&src[i + 12]);
ints0 = vrev32q_u8(ints0);
ints1 = vrev32q_u8(ints1);
ints2 = vrev32q_u8(ints2);
ints3 = vrev32q_u8(ints3);
vst1q_u8((Uint8*)&dst[i], ints0);
vst1q_u8((Uint8*)&dst[i + 4], ints1);
vst1q_u8((Uint8*)&dst[i + 8], ints2);
vst1q_u8((Uint8*)&dst[i + 12], ints3);
vst1q_u8((Uint8 *)&dst[i], ints0);
vst1q_u8((Uint8 *)&dst[i + 4], ints1);
vst1q_u8((Uint8 *)&dst[i + 8], ints2);
vst1q_u8((Uint8 *)&dst[i + 12], ints3);
})
}
@@ -843,8 +843,8 @@ static void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_sampl
static void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL;
static void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL;
static void (*SDL_Convert_Swap16)(Uint16* dst, const Uint16* src, int num_samples) = NULL;
static void (*SDL_Convert_Swap32)(Uint32* dst, const Uint32* src, int num_samples) = NULL;
static void (*SDL_Convert_Swap16)(Uint16 *dst, const Uint16 *src, int num_samples) = NULL;
static void (*SDL_Convert_Swap32)(Uint32 *dst, const Uint32 *src, int num_samples) = NULL;
void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt)
{
@@ -862,7 +862,7 @@ void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_Audio
break;
case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples);
SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)src, num_samples);
SDL_Convert_S16_to_F32(dst, (const Sint16 *) dst, num_samples);
break;
@@ -871,12 +871,12 @@ void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_Audio
break;
case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples);
SDL_Convert_S32_to_F32(dst, (const Sint32 *) dst, num_samples);
break;
case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples);
break;
default: SDL_assert(!"Unexpected audio format!"); break;
@@ -900,7 +900,7 @@ void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_Aud
case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples);
SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) dst, num_samples);
SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)dst, num_samples);
break;
case SDL_AUDIO_S32:
@@ -909,22 +909,22 @@ void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_Aud
case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) dst, num_samples);
SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)dst, num_samples);
break;
case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples);
break;
default: SDL_assert(!"Unexpected audio format!"); break;
}
}
void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize)
void ConvertAudioSwapEndian(void *dst, const void *src, int num_samples, int bitsize)
{
switch (bitsize) {
case 16: SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples); break;
case 32: SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); break;
case 16: SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)src, num_samples); break;
case 32: SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); break;
default: SDL_assert(!"Unexpected audio format!"); break;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+9 -7
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -112,7 +112,7 @@ extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device);
extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt);
extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt);
extern void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize);
extern void ConvertAudioSwapEndian(void *dst, const void *src, int num_samples, int bitsize);
extern bool SDL_ChannelMapIsDefault(const int *map, int channels);
extern bool SDL_ChannelMapIsBogus(const int *map, int channels);
@@ -121,7 +121,7 @@ extern bool SDL_ChannelMapIsBogus(const int *map, int channels);
extern void ConvertAudio(int num_frames,
const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
void* scratch, float gain);
void *scratch, float gain);
// Compare two SDL_AudioSpecs, return true if they match exactly.
// Using SDL_memcmp directly isn't safe, since potential padding might not be initialized.
@@ -183,8 +183,9 @@ typedef struct SDL_AudioDriver
const char *name; // The name of this audio driver
const char *desc; // The description of this audio driver
SDL_AudioDriverImpl impl; // the backend's interface
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash`
SDL_HashTable *device_hash; // the collection of currently-available audio devices (recording, playback, logical and physical!)
SDL_RWLock *subsystem_rwlock; // A rwlock that protects several things in the audio subsystem (device hashtables, etc).
SDL_HashTable *device_hash_physical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_AudioDevice*.
SDL_HashTable *device_hash_logical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_LogicalAudioDevice*.
SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams.
SDL_AudioDeviceID default_playback_device_id;
SDL_AudioDeviceID default_recording_device_id;
@@ -201,7 +202,7 @@ struct SDL_AudioQueue; // forward decl.
struct SDL_AudioStream
{
SDL_Mutex* lock;
SDL_Mutex *lock;
SDL_PropertiesID props;
@@ -217,7 +218,7 @@ struct SDL_AudioStream
float freq_ratio;
float gain;
struct SDL_AudioQueue* queue;
struct SDL_AudioQueue *queue;
SDL_AudioSpec input_spec; // The spec of input data currently being processed
int *input_chmap;
@@ -386,6 +387,7 @@ extern AudioBootStrap PS2AUDIO_bootstrap;
extern AudioBootStrap PSPAUDIO_bootstrap;
extern AudioBootStrap VITAAUD_bootstrap;
extern AudioBootStrap N3DSAUDIO_bootstrap;
extern AudioBootStrap NGAGEAUDIO_bootstrap;
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
extern AudioBootStrap QSAAUDIO_bootstrap;
+19 -7
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -813,7 +813,7 @@ static bool IMA_ADPCM_Init(WaveFile *file, size_t datalength)
if (format->formattag == EXTENSIBLE_CODE) {
/* There's no specification for this, but it's basically the same
* format because the extensible header has wSampePerBlocks too.
* format because the extensible header has wSamplePerBlocks too.
*/
} else {
// The Standards Update says there 'should' be 2 bytes for wSamplesPerBlock.
@@ -1775,6 +1775,7 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin
int result;
Uint32 chunkcount = 0;
Uint32 chunkcountlimit = 10000;
const Sint64 flen = SDL_GetIOSize(src); // this might be -1 if the IOStream can't determine the total size.
const char *hint;
Sint64 RIFFstart, RIFFend, lastchunkpos;
bool RIFFlengthknown = false;
@@ -1852,7 +1853,7 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin
/* Step through all chunks and save information on the fmt, data, and fact
* chunks. Ignore the chunks we don't know as per specification. This
* currently also ignores cue, list, and slnt chunks.
* currently also ignores cue, list, and inst chunks.
*/
while ((Uint64)RIFFend > (Uint64)chunk->position + chunk->length + (chunk->length & 1)) {
// Abort after too many chunks or else corrupt files may waste time.
@@ -1883,6 +1884,14 @@ static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uin
fmtchunk = *chunk;
}
} else if (chunk->fourcc == DATA) {
/* If the data chunk is bigger than the file, it might be corrupt
or the file is truncated. Try to recover by clamping the file
size. This also means a malicious file can't allocate 4 gigabytes
for the chunks without actually supplying a 4 gigabyte file. */
if ((flen > 0) && ((chunk->position + chunk->length) > flen)) {
chunk->length = (Uint32) (flen - chunk->position);
}
/* Only use the first data chunk. Handling the wavl list madness
* may require a different approach.
*/
@@ -2092,16 +2101,19 @@ bool SDL_LoadWAV_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8
}
// Make sure we are passed a valid data source
if (!src) {
CHECK_PARAM(!src) {
SDL_InvalidParamError("src");
goto done;
} else if (!spec) {
}
CHECK_PARAM(!spec) {
SDL_InvalidParamError("spec");
goto done;
} else if (!audio_buf) {
}
CHECK_PARAM(!audio_buf) {
SDL_InvalidParamError("audio_buf");
goto done;
} else if (!audio_len) {
}
CHECK_PARAM(!audio_len) {
SDL_InvalidParamError("audio_len");
goto done;
}
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+38 -16
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -53,6 +53,13 @@ struct SDL_PrivateAudioData
#define LIB_AAUDIO_SO "libaaudio.so"
SDL_ELF_NOTE_DLOPEN(
"audio-aaudio",
"Support for audio through AAudio",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
LIB_AAUDIO_SO
)
typedef struct AAUDIO_Data
{
SDL_SharedObject *handle;
@@ -65,11 +72,16 @@ static bool AAUDIO_LoadFunctions(AAUDIO_Data *data)
{
#define SDL_PROC(ret, func, params) \
do { \
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
if (!data->func) { \
return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \
} \
} while (0);
#define SDL_PROC_OPTIONAL(ret, func, params) \
do { \
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); /* if it fails, okay. */ \
} while (0);
#include "SDL_aaudiofuncs.h"
return true;
}
@@ -253,7 +265,7 @@ static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen
static void AAUDIO_CloseDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
LOGI(__func__);
LOGI(SDL_FUNCTION);
if (hidden) {
if (hidden->stream) {
@@ -308,12 +320,16 @@ static bool BuildAAudioStream(SDL_AudioDevice *device)
ctx.AAudioStreamBuilder_setFormat(builder, format);
ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq);
ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
// If no specific buffer size has been requested, the device will pick the optimal
if(SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) {
ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * device->sample_frames); // AAudio requires that the buffer capacity is at least
ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); // twice the size of the data callback buffer size
}
int32_t sample_frames;
if (SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) {
sample_frames = device->sample_frames;
} else {
// Use 20 ms for the default audio buffer size
sample_frames = (device->spec.freq / 50);
}
ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * sample_frames); // AAudio requires that the buffer capacity is at least
ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, sample_frames); // twice the size of the data callback buffer size
const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
ctx.AAudioStreamBuilder_setDirection(builder, direction);
@@ -327,6 +343,12 @@ static bool BuildAAudioStream(SDL_AudioDevice *device)
SDL_Log("Low latency audio disabled");
}
if (recording && ctx.AAudioStreamBuilder_setInputPreset) { // optional API: requires Android 28
// try to use a microphone that is for recording external audio. Otherwise Android might choose the mic used for talking
// on the telephone when held to the user's ear, which is often not useful at any distance from the device.
ctx.AAudioStreamBuilder_setInputPreset(builder, AAUDIO_INPUT_PRESET_CAMCORDER);
}
LOGI("AAudio Try to open %u hz %s %u channels samples %u",
device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
device->spec.channels, device->sample_frames);
@@ -335,7 +357,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device)
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
ctx.AAudioStreamBuilder_delete(builder);
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res));
}
ctx.AAudioStreamBuilder_delete(builder);
@@ -385,7 +407,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device)
res = ctx.AAudioStream_requestStart(hidden->stream);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording);
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res));
}
LOGI("SDL AAudioStream_requestStart OK");
@@ -405,7 +427,7 @@ static bool AAUDIO_OpenDevice(SDL_AudioDevice *device)
SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
#endif
LOGI(__func__);
LOGI(SDL_FUNCTION);
if (device->recording) {
// !!! FIXME: make this non-blocking!
@@ -449,7 +471,7 @@ static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestPause %d", res);
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res));
}
}
}
@@ -473,7 +495,7 @@ static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestStart %d", res);
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res));
}
}
}
@@ -491,7 +513,7 @@ static void AAUDIO_Deinitialize(void)
{
Android_StopAudioHotplug();
LOGI(__func__);
LOGI(SDL_FUNCTION);
if (ctx.handle) {
SDL_UnloadObject(ctx.handle);
}
@@ -502,7 +524,7 @@ static void AAUDIO_Deinitialize(void)
static bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
{
LOGI(__func__);
LOGI(SDL_FUNCTION);
/* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release,
* so don't use it until 8.1.
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+7 -2
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright , (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright , (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -19,6 +19,10 @@
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_PROC_OPTIONAL
#define SDL_PROC_OPTIONAL(ret, func, params) SDL_PROC(ret, func, params)
#endif
#define SDL_PROC_UNUSED(ret, func, params)
SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
@@ -35,7 +39,7 @@ SDL_PROC(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuild
SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28
SDL_PROC_OPTIONAL(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30
@@ -80,3 +84,4 @@ SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream))
#undef SDL_PROC
#undef SDL_PROC_UNUSED
#undef SDL_PROC_OPTIONAL
+115 -32
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -38,6 +38,7 @@
#include "../SDL_sysaudio.h"
#include "SDL_alsa_audio.h"
#include "../../core/linux/SDL_udev.h"
#if SDL_ALSA_DEBUG
#define LOGDEBUG(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "ALSA: " __VA_ARGS__)
@@ -81,13 +82,14 @@ static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int);
static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
static snd_pcm_state_t (*ALSA_snd_pcm_state)(snd_pcm_t *);
static int (*ALSA_snd_device_name_hint)(int, const char *, void ***);
static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *);
static int (*ALSA_snd_device_name_free_hint)(void **);
static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
static size_t (*ALSA_snd_ctl_card_info_sizeof)(void);
static size_t (*ALSA_snd_pcm_info_sizeof)(void);
static int (*ALSA_snd_card_next)(int*);
static int (*ALSA_snd_card_next)(int *);
static int (*ALSA_snd_ctl_open)(snd_ctl_t **,const char *,int);
static int (*ALSA_snd_ctl_close)(snd_ctl_t *);
static int (*ALSA_snd_ctl_card_info)(snd_ctl_t *, snd_ctl_card_info_t *);
@@ -97,7 +99,6 @@ static void (*ALSA_snd_pcm_info_set_device)(snd_pcm_info_t *, unsigned int);
static void (*ALSA_snd_pcm_info_set_subdevice)(snd_pcm_info_t *, unsigned int);
static void (*ALSA_snd_pcm_info_set_stream)(snd_pcm_info_t *, snd_pcm_stream_t);
static int (*ALSA_snd_ctl_pcm_info)(snd_ctl_t *, snd_pcm_info_t *);
static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
static const char *(*ALSA_snd_ctl_card_info_get_id)(const snd_ctl_card_info_t *);
static const char *(*ALSA_snd_pcm_info_get_name)(const snd_pcm_info_t *);
static const char *(*ALSA_snd_pcm_info_get_subdevice_name)(const snd_pcm_info_t *);
@@ -171,6 +172,7 @@ static bool load_alsa_syms(void)
SDL_ALSA_SYM(snd_pcm_wait);
SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
SDL_ALSA_SYM(snd_pcm_reset);
SDL_ALSA_SYM(snd_pcm_state);
SDL_ALSA_SYM(snd_device_name_hint);
SDL_ALSA_SYM(snd_device_name_get_hint);
SDL_ALSA_SYM(snd_device_name_free_hint);
@@ -207,6 +209,13 @@ static bool load_alsa_syms(void)
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
SDL_ELF_NOTE_DLOPEN(
"audio-libalsa",
"Support for audio through libalsa",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
SDL_AUDIO_DRIVER_ALSA_DYNAMIC
)
static void UnloadALSALibrary(void)
{
if (alsa_handle) {
@@ -345,31 +354,42 @@ static char *get_pcm_str(void *handle)
return pcm_str;
}
static int RecoverALSADevice(snd_pcm_t *pcm, int errnum)
{
const snd_pcm_state_t prerecovery = ALSA_snd_pcm_state(pcm);
const int status = ALSA_snd_pcm_recover(pcm, errnum, 0); // !!! FIXME: third parameter is non-zero to prevent libasound from printing error messages. Should we do that?
if (status == 0) {
const snd_pcm_state_t postrecovery = ALSA_snd_pcm_state(pcm);
if ((prerecovery == SND_PCM_STATE_XRUN) && (postrecovery == SND_PCM_STATE_PREPARED)) {
ALSA_snd_pcm_start(pcm); // restart the device if it stopped due to an overrun or underrun.
}
}
return status;
}
// This function waits until it is possible to write a full sound buffer
static bool ALSA_WaitDevice(SDL_AudioDevice *device)
{
const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq);
const int delay = SDL_max(fulldelay, 10);
const int sample_frames = device->sample_frames;
const int fulldelay = (int) ((((Uint64) sample_frames) * 1000) / device->spec.freq);
const int delay = SDL_clamp(fulldelay, 1, 5);
while (!SDL_GetAtomicInt(&device->shutdown)) {
const int rc = ALSA_snd_pcm_wait(device->hidden->pcm, delay);
if (rc < 0 && (rc != -EAGAIN)) {
const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
const int rc = ALSA_snd_pcm_avail(device->hidden->pcm);
if (rc < 0) {
const int status = RecoverALSADevice(device->hidden->pcm, rc);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
return false;
}
continue;
}
if (rc > 0) {
break; // ready to go!
if (rc >= sample_frames) {
break;
}
// Timed out! Make sure we aren't shutting down and then wait again.
SDL_Delay(delay);
}
return true;
}
@@ -386,7 +406,7 @@ static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu
SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space.
if (rc < 0) {
SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
const int status = RecoverALSADevice(device->hidden->pcm, rc);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc));
@@ -431,14 +451,17 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
SDL_assert((buflen % frame_size) == 0);
const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm);
const int total_frames = SDL_min(buflen / frame_size, total_available);
if (total_available == 0) {
return 0; // go back to WaitDevice and try again.
}
const int total_frames = SDL_min(buflen / frame_size, total_available);
const int rc = ALSA_snd_pcm_readi(device->hidden->pcm, buffer, total_frames);
SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
if (rc < 0) {
const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
const int status = RecoverALSADevice(device->hidden->pcm, rc);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc));
@@ -461,8 +484,6 @@ static void ALSA_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->pcm) {
// Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that.
SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2);
ALSA_snd_pcm_close(device->hidden->pcm);
}
SDL_free(device->hidden->mixbuf);
@@ -651,7 +672,7 @@ static void swizzle_map_compute_alsa_subscan(const struct ALSA_pcm_cfg_ctx *ctx,
}
}
// XXX: this must stay playback/recording symetric.
// XXX: this must stay playback/recording symmetric.
static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle)
{
*needs_swizzle = false;
@@ -671,7 +692,7 @@ static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle
static int alsa_chmap_install(struct ALSA_pcm_cfg_ctx *ctx, const unsigned int *chmap)
{
bool isstack;
snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t*)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack);
snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t *)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack);
if (!chmap_to_install) {
return -1;
}
@@ -1000,7 +1021,7 @@ static int ALSA_pcm_cfg_hw_chans_n_scan(struct ALSA_pcm_cfg_ctx *ctx, unsigned i
SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status));
return -1;
}
// let approximate the minimun number of periods per buffer (we target a double buffer)
// let approximate the minimum number of periods per buffer (we target a double buffer)
ctx->periods = 2;
status = ALSA_snd_pcm_hw_params_set_periods_min(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL);
if (status < 0) {
@@ -1068,7 +1089,7 @@ static bool ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx)
}
// Here, status == CHANS_N_NOT_CONFIGURED
return SDL_SetError("ALSA: Coudn't configure targetting any SDL supported channel number");
return SDL_SetError("ALSA: Couldn't configure targeting any SDL supported channel number");
}
#undef CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N
#undef CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
@@ -1149,7 +1170,7 @@ static bool ALSA_OpenDevice(SDL_AudioDevice *device)
goto err_close_pcm;
}
// from here, we get only the alsa chmap queries in cfg_ctx to explicitely clean, hwparams is
// from here, we get only the alsa chmap queries in cfg_ctx to explicitly clean, hwparams is
// uninstalled upon pcm closing
// This is useful for debugging
@@ -1215,7 +1236,7 @@ static int hotplug_device_process(snd_ctl_t *ctl, snd_ctl_card_info_t *ctl_card_
unsigned int subdev_idx = 0;
const bool recording = direction == SND_PCM_STREAM_CAPTURE ? true : false; // used for the unicity of the device
bool isstack;
snd_pcm_info_t *pcm_info = (snd_pcm_info_t*)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack);
snd_pcm_info_t *pcm_info = (snd_pcm_info_t *)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack);
SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof());
while (true) {
@@ -1445,6 +1466,65 @@ static int SDLCALL ALSA_HotplugThread(void *arg)
}
#endif
#ifdef SDL_USE_LIBUDEV
static bool udev_initialized;
static void ALSA_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
{
if (!devpath) {
return;
}
switch (udev_type) {
case SDL_UDEV_DEVICEADDED:
ALSA_HotplugIteration(NULL, NULL);
break;
case SDL_UDEV_DEVICEREMOVED:
ALSA_HotplugIteration(NULL, NULL);
break;
default:
break;
}
}
static bool ALSA_start_udev(void)
{
udev_initialized = SDL_UDEV_Init();
if (udev_initialized) {
// Set up the udev callback
if (!SDL_UDEV_AddCallback(ALSA_udev_callback)) {
SDL_UDEV_Quit();
udev_initialized = false;
}
}
return udev_initialized;
}
static void ALSA_stop_udev(void)
{
if (udev_initialized) {
SDL_UDEV_DelCallback(ALSA_udev_callback);
SDL_UDEV_Quit();
udev_initialized = false;
}
}
#else
static bool ALSA_start_udev(void)
{
return false;
}
static void ALSA_stop_udev(void)
{
}
#endif // SDL_USE_LIBUDEV
static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
ALSA_guess_device_prefix();
@@ -1454,17 +1534,19 @@ static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevi
bool has_default_playback = false, has_default_recording = false;
ALSA_HotplugIteration(&has_default_playback, &has_default_recording); // run once now before a thread continues to check.
if (has_default_playback) {
*default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void*)&default_playback_handle);
*default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void *)&default_playback_handle);
}
if (has_default_recording) {
*default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void*)&default_recording_handle);
*default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void *)&default_recording_handle);
}
if (!ALSA_start_udev()) {
#if SDL_ALSA_HOTPLUG_THREAD
SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0);
ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
// if the thread doesn't spin, oh well, you just don't get further hotplug events.
SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0);
ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
// if the thread doesn't spin, oh well, you just don't get further hotplug events.
#endif
}
}
static void ALSA_DeinitializeStart(void)
@@ -1479,6 +1561,7 @@ static void ALSA_DeinitializeStart(void)
ALSA_hotplug_thread = NULL;
}
#endif
ALSA_stop_udev();
// Shutting down! Clean up any data we've gathered.
for (dev = hotplug_devices; dev; dev = next) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -341,7 +341,7 @@ static void ResumeAudioDevices(void)
static void InterruptionBegin(SDL_AudioDevice *device)
{
if (device != NULL && device->hidden->audioQueue != NULL) {
if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL) {
device->hidden->interrupted = true;
AudioQueuePause(device->hidden->audioQueue);
}
@@ -366,7 +366,7 @@ static void InterruptionEnd(SDL_AudioDevice *device)
{
@synchronized(self) {
NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
if (type && (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan)) {
InterruptionBegin(self.device);
} else {
InterruptionEnd(self.device);
@@ -420,7 +420,8 @@ static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_pl
hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY);
if (hint) {
if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) {
if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0 ||
SDL_strcasecmp(hint, "ambient") == 0) {
category = AVAudioSessionCategoryAmbient;
} else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) {
category = AVAudioSessionCategorySoloAmbient;
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -40,16 +40,16 @@ static bool SupportsIMMDevice = false;
// DirectX function pointers for audio
static SDL_SharedObject *DSoundDLL = NULL;
typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN);
typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID);
static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
static fnGetDeviceID pGetDeviceID = NULL;
typedef HRESULT (WINAPI *pfnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
typedef HRESULT (WINAPI *pfnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
typedef HRESULT (WINAPI *pfnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN);
typedef HRESULT (WINAPI *pfnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
typedef HRESULT (WINAPI *pfnGetDeviceID)(LPCGUID, LPGUID);
static pfnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
static pfnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
static pfnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
static pfnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
static pfnGetDeviceID pGetDeviceID = NULL;
#include <initguid.h>
DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
@@ -85,7 +85,7 @@ static bool DSOUND_Load(void)
// Now make sure we have DirectX 8 or better...
#define DSOUNDLOAD(f) \
{ \
p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \
p##f = (pfn##f)SDL_LoadFunction(DSoundDLL, #f); \
if (!p##f) \
loaded = false; \
}
@@ -206,7 +206,7 @@ static void DSOUND_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDe
{
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording, SDL_AUDIO_UNKNOWN);
SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording, SDL_AUDIO_UNKNOWN, false);
} else
#endif
{
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+14 -2
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -100,6 +100,16 @@ static const char *get_filename(const bool recording)
return devname;
}
static const char *AudioFormatString(SDL_AudioFormat fmt)
{
const char *str = SDL_GetAudioFormatName(fmt);
SDL_assert(str);
if (SDL_strncmp(str, "SDL_AUDIO_", 10) == 0) {
str += 10; // so we return "S8" instead of "SDL_AUDIO_S8", etc.
}
return str;
}
static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
{
bool recording = device->recording;
@@ -136,7 +146,9 @@ static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
}
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!");
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].", recording ? "Reading from" : "Writing to", fname);
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s], format=%s channels=%d freq=%d.",
recording ? "Reading from" : "Writing to", fname,
AudioFormatString(device->spec.format), device->spec.channels, device->spec.freq);
return true; // We're ready to rock and roll. :-)
}
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+8 -11
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -25,7 +25,7 @@
#include "../SDL_sysaudio.h"
#include "SDL_dummyaudio.h"
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
#if defined(SDL_PLATFORM_EMSCRIPTEN)
#include <emscripten/emscripten.h>
#endif
@@ -59,8 +59,8 @@ static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
}
}
// on Emscripten without threads, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
// on Emscripten, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN)
MAIN_THREAD_EM_ASM({
var a = Module['SDL3'].dummy_audio;
if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
@@ -74,8 +74,8 @@ static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
// on Emscripten without threads, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
// on Emscripten, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN)
MAIN_THREAD_EM_ASM({
var a = Module['SDL3'].dummy_audio;
if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
@@ -113,12 +113,9 @@ static bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl)
impl->OnlyHasDefaultRecordingDevice = true;
impl->HasRecordingSupport = true;
// on Emscripten without threads, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
// on Emscripten, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN)
MAIN_THREAD_EM_ASM({
if (typeof(Module['SDL3']) === 'undefined') {
Module['SDL3'] = {};
}
Module['SDL3'].dummy_audio = {};
Module['SDL3'].dummy_audio.timers = [];
Module['SDL3'].dummy_audio.timers[0] = undefined;
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -41,13 +41,8 @@ static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buf
const int framelen = SDL_AUDIO_FRAMESIZE(device->spec);
MAIN_THREAD_EM_ASM({
/* Convert incoming buf pointer to a HEAPF32 offset. */
#ifdef __wasm64__
var buf = $0 / 4;
#else
var buf = $0 >>> 2;
#endif
var SDL3 = Module['SDL3'];
var buf = SDL3.CPtrToHeap32Index($0);
var numChannels = SDL3.audio_playback.currentPlaybackBuffer['numberOfChannels'];
for (var c = 0; c < numChannels; ++c) {
var channelData = SDL3.audio_playback.currentPlaybackBuffer['getChannelData'](c);
@@ -56,7 +51,7 @@ static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buf
}
for (var j = 0; j < $1; ++j) {
channelData[j] = HEAPF32[buf + (j*numChannels + c)];
channelData[j] = HEAPF32[buf + (j * numChannels + c)];
}
}
}, buffer, buffer_size / framelen);
@@ -151,13 +146,11 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
// create context
const bool result = MAIN_THREAD_EM_ASM_INT({
if (typeof(Module['SDL3']) === 'undefined') {
Module['SDL3'] = {};
}
var SDL3 = Module['SDL3'];
if (!$0) {
if (typeof(SDL3.audio_playback) === 'undefined') {
SDL3.audio_playback = {};
} else {
}
if (typeof(SDL3.audio_recording) === 'undefined') {
SDL3.audio_recording = {};
}
@@ -174,7 +167,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
}
}
return (SDL3.audioContext !== undefined);
}, device->recording);
});
if (!result) {
return SDL_SetError("Web Audio API is not available!");
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+8 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -50,6 +50,13 @@ static bool load_jack_syms(void);
#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
SDL_ELF_NOTE_DLOPEN(
"audio-libjack",
"Support for audio through libjack",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
SDL_AUDIO_DRIVER_JACK_DYNAMIC
)
static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
static SDL_SharedObject *jack_handle = NULL;
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+103
View File
@@ -0,0 +1,103 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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"
#ifdef SDL_AUDIO_DRIVER_NGAGE
#include "../SDL_sysaudio.h"
#include "SDL_ngageaudio.h"
static SDL_AudioDevice *devptr = NULL;
SDL_AudioDevice *NGAGE_GetAudioDeviceAddr()
{
return devptr;
}
static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device)
{
SDL_PrivateAudioData *phdata = SDL_calloc(1, sizeof(SDL_PrivateAudioData));
if (!phdata) {
SDL_OutOfMemory();
return false;
}
device->hidden = phdata;
phdata->buffer = SDL_calloc(1, device->buffer_size);
if (!phdata->buffer) {
SDL_OutOfMemory();
SDL_free(phdata);
return false;
}
devptr = device;
// Since the phone can change the sample rate during a phone call,
// we set the sample rate to 8KHz to be safe. Even though it
// might be possible to adjust the sample rate dynamically, it's
// not supported by the current implementation.
device->spec.format = SDL_AUDIO_S16LE;
device->spec.channels = 1;
device->spec.freq = 8000;
SDL_UpdatedAudioDeviceFormat(device);
return true;
}
static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
if (!phdata) {
*buffer_size = 0;
return 0;
}
*buffer_size = device->buffer_size;
return phdata->buffer;
}
static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
SDL_free(device->hidden->buffer);
SDL_free(device->hidden);
}
return;
}
static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = NGAGEAUDIO_OpenDevice;
impl->GetDeviceBuf = NGAGEAUDIO_GetDeviceBuf;
impl->CloseDevice = NGAGEAUDIO_CloseDevice;
impl->ProvidesOwnCallbackThread = true;
impl->OnlyHasDefaultPlaybackDevice = true;
return true;
}
AudioBootStrap NGAGEAUDIO_bootstrap = { "N-Gage", "N-Gage audio driver", NGAGEAUDIO_Init, false };
#endif // SDL_AUDIO_DRIVER_NGAGE
@@ -0,0 +1,368 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include "SDL_ngageaudio.h"
#include "../SDL_sysaudio.h"
#include "SDL_internal.h"
#ifdef __cplusplus
}
#endif
#ifdef SDL_AUDIO_DRIVER_NGAGE
#include "SDL_ngageaudio.hpp"
CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {}
CAudio *CAudio::NewL(TInt aLatency)
{
CAudio *self = new (ELeave) CAudio();
CleanupStack::PushL(self);
self->ConstructL(aLatency);
CleanupStack::Pop(self);
return self;
}
void CAudio::ConstructL(TInt aLatency)
{
CActiveScheduler::Add(this);
User::LeaveIfError(iTimer.CreateLocal());
iTimerCreated = ETrue;
iStream = CMdaAudioOutputStream::NewL(*this);
if (!iStream) {
SDL_Log("Error: Failed to create audio stream");
User::Leave(KErrNoMemory);
}
iLatency = aLatency;
iLatencySamples = aLatency * 8; // 8kHz.
// Determine minimum and maximum number of samples to write with one
// WriteL request.
iMinWrite = iLatencySamples / 8;
iMaxWrite = iLatencySamples / 2;
// Set defaults.
iState = EStateNone;
iTimerCreated = EFalse;
iTimerActive = EFalse;
}
CAudio::~CAudio()
{
if (iStream) {
iStream->Stop();
while (iState != EStateDone) {
User::After(100000); // 100ms.
}
delete iStream;
}
}
void CAudio::Start()
{
if (iStream) {
// Set to 8kHz mono audio.
iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono;
iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz;
iStream->Open(&iStreamSettings);
iState = EStateOpening;
} else {
SDL_Log("Error: Failed to open audio stream");
}
}
// Feeds more processed data to the audio stream.
void CAudio::Feed()
{
// If a WriteL is already in progress, or we aren't even playing;
// do nothing!
if ((iState != EStateWriting) && (iState != EStatePlaying)) {
return;
}
// Figure out the number of samples that really have been played
// through the output.
TTimeIntervalMicroSeconds pos = iStream->Position();
TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz.
played += iBaseSamplesPlayed;
// Determine the difference between the number of samples written to
// CMdaAudioOutputStream and the number of samples it has played.
// The difference is the amount of data in the buffers.
if (played < 0) {
played = 0;
}
TInt buffered = iSamplesWritten - played;
if (buffered < 0) {
buffered = 0;
}
if (iState == EStateWriting) {
return;
}
// The trick for low latency: Do not let the buffers fill up beyond the
// latency desired! We write as many samples as the difference between
// the latency target (in samples) and the amount of data buffered.
TInt samplesToWrite = iLatencySamples - buffered;
// Do not write very small blocks. This should improve efficiency, since
// writes to the streaming API are likely to be expensive.
if (samplesToWrite < iMinWrite) {
// Not enough data to write, set up a timer to fire after a while.
// Try againwhen it expired.
if (iTimerActive) {
return;
}
iTimerActive = ETrue;
SetActive();
iTimer.After(iStatus, (1000 * iLatency) / 8);
return;
}
// Do not write more than the set number of samples at once.
int numSamples = samplesToWrite;
if (numSamples > iMaxWrite) {
numSamples = iMaxWrite;
}
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
if (device) {
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples);
iStream->WriteL(iBufDes);
iState = EStateWriting;
// Keep track of the number of samples written (for latency calculations).
iSamplesWritten += numSamples;
} else {
// Output device not ready yet. Let's go for another round.
if (iTimerActive) {
return;
}
iTimerActive = ETrue;
SetActive();
iTimer.After(iStatus, (1000 * iLatency) / 8);
}
}
void CAudio::RunL()
{
iTimerActive = EFalse;
Feed();
}
void CAudio::DoCancel()
{
iTimerActive = EFalse;
iTimer.Cancel();
}
void CAudio::StartThread()
{
TInt heapMinSize = 8192; // 8 KB initial heap size.
TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size.
TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this);
if (err == KErrNone) {
iProcess.SetPriority(EPriorityLess);
iProcess.Resume();
} else {
SDL_Log("Error: Failed to create audio processing thread: %d", err);
}
}
void CAudio::StopThread()
{
if (iStreamStarted) {
iProcess.Kill(KErrNone);
iProcess.Close();
iStreamStarted = EFalse;
}
}
TInt CAudio::ProcessThreadCB(TAny *aPtr)
{
CAudio *self = static_cast<CAudio *>(aPtr);
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
while (self->iStreamStarted) {
if (device) {
SDL_PlaybackAudioThreadIterate(device);
} else {
device = NGAGE_GetAudioDeviceAddr();
}
User::After(100000); // 100ms.
}
return KErrNone;
}
void CAudio::MaoscOpenComplete(TInt aError)
{
if (aError == KErrNone) {
iStream->SetVolume(1);
iStreamStarted = ETrue;
StartThread();
} else {
SDL_Log("Error: Failed to open audio stream: %d", aError);
}
}
void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
{
if (aError == KErrNone) {
iState = EStatePlaying;
Feed();
} else if (aError == KErrAbort) {
// The stream has been stopped.
iState = EStateDone;
} else {
SDL_Log("Error: Failed to copy audio buffer: %d", aError);
}
}
void CAudio::MaoscPlayComplete(TInt aError)
{
// If we finish due to an underflow, we'll need to restart playback.
// Normally KErrUnderlow is raised at stream end, but in our case the API
// should never see the stream end -- we are continuously feeding it more
// data! Many underflow errors mean that the latency target is too low.
if (aError == KErrUnderflow) {
// The number of samples played gets reset to zero when we restart
// playback after underflow.
iBaseSamplesPlayed = iSamplesWritten;
iStream->Stop();
Cancel();
iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono);
iState = EStatePlaying;
Feed();
return;
} else if (aError != KErrNone) {
// Handle error.
}
// We shouldn't get here.
SDL_Log("%s: %d", SDL_FUNCTION, aError);
}
static TBool gAudioRunning;
TBool AudioIsReady()
{
return gAudioRunning;
}
TInt AudioThreadCB(TAny *aParams)
{
CTrapCleanup *cleanup = CTrapCleanup::New();
if (!cleanup) {
return KErrNoMemory;
}
CActiveScheduler *scheduler = new CActiveScheduler();
if (!scheduler) {
delete cleanup;
return KErrNoMemory;
}
CActiveScheduler::Install(scheduler);
TRAPD(err,
{
TInt latency = *(TInt *)aParams;
CAudio *audio = CAudio::NewL(latency);
CleanupStack::PushL(audio);
gAudioRunning = ETrue;
audio->Start();
TBool once = EFalse;
while (gAudioRunning) {
// Allow active scheduler to process any events.
TInt error;
CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
if (!once) {
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
if (device) {
// Stream ready; start feeding audio data.
// After feeding it once, the callbacks will take over.
audio->iState = CAudio::EStatePlaying;
audio->Feed();
once = ETrue;
}
}
User::After(100000); // 100ms.
}
CleanupStack::PopAndDestroy(audio);
});
delete scheduler;
delete cleanup;
return err;
}
RThread audioThread;
void InitAudio(TInt *aLatency)
{
_LIT(KAudioThreadName, "AudioThread");
TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);
if (err != KErrNone) {
User::Leave(err);
}
audioThread.Resume();
}
void DeinitAudio()
{
gAudioRunning = EFalse;
TRequestStatus status;
audioThread.Logon(status);
User::WaitForRequest(status);
audioThread.Close();
}
#endif // SDL_AUDIO_DRIVER_NGAGE
@@ -0,0 +1,44 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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"
#ifndef SDL_ngageaudio_h
#define SDL_ngageaudio_h
typedef struct SDL_PrivateAudioData
{
Uint8 *buffer;
} SDL_PrivateAudioData;
#ifdef __cplusplus
extern "C" {
#endif
#include "../SDL_sysaudio.h"
SDL_AudioDevice *NGAGE_GetAudioDeviceAddr();
#ifdef __cplusplus
}
#endif
#endif // SDL_ngageaudio_h
@@ -0,0 +1,98 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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.
*/
#ifndef SDL_ngageaudio_hpp
#define SDL_ngageaudio_hpp
#include <e32base.h>
#include <e32std.h>
#include <mda/common/audio.h>
#include <mdaaudiooutputstream.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "../SDL_sysaudio.h"
#include "SDL_ngageaudio.h"
#ifdef __cplusplus
}
#endif
TBool AudioIsReady();
void InitAudio(TInt *aLatency);
void DeinitAudio();
class CAudio : public CActive, public MMdaAudioOutputStreamCallback
{
public:
static CAudio *NewL(TInt aLatency);
~CAudio();
void ConstructL(TInt aLatency);
void Start();
void Feed();
void RunL();
void DoCancel();
static TInt ProcessThreadCB(TAny * /*aPtr*/);
// From MMdaAudioOutputStreamCallback
void MaoscOpenComplete(TInt aError);
void MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer);
void MaoscPlayComplete(TInt aError);
enum
{
EStateNone = 0,
EStateOpening,
EStatePlaying,
EStateWriting,
EStateDone
} iState;
private:
CAudio();
void StartThread();
void StopThread();
CMdaAudioOutputStream *iStream;
TMdaAudioDataSettings iStreamSettings;
TBool iStreamStarted;
TPtr8 iBufDes; // Descriptor for the buffer.
TInt iLatency; // Latency target in ms
TInt iLatencySamples; // Latency target in samples.
TInt iMinWrite; // Min number of samples to write per turn.
TInt iMaxWrite; // Max number of samples to write per turn.
TInt iBaseSamplesPlayed; // amples played before last restart.
TInt iSamplesWritten; // Number of samples written so far.
RTimer iTimer;
TBool iTimerCreated;
TBool iTimerActive;
RThread iProcess;
};
#endif // SDL_ngageaudio_hpp
+16 -15
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -223,9 +223,7 @@ static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device)
audiodata->playsem = NULL;
}
if (audiodata->mixbuff) {
SDL_free(audiodata->mixbuff);
}
SDL_free(audiodata->mixbuff);
}
// !!! FIXME: make this non-blocking!
@@ -419,35 +417,38 @@ static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device)
audiodata->playsem = NULL;
}
if (audiodata->mixbuff) {
SDL_free(audiodata->mixbuff);
}
SDL_free(audiodata->mixbuff);
}
static bool OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device)
{
/* If we want to add floating point audio support (requires API level 21)
it can be done as described here:
https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point
*/
/* according to https://developer.android.com/ndk/guides/audio/opensl/opensl-for-android,
Android's OpenSL ES only supports Uint8 and _littleendian_ Sint16.
(and float32, with an extension we use, below.) */
if (SDL_GetAndroidSDKVersion() >= 21) {
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
SDL_AudioFormat test_format;
while ((test_format = *(closefmts++)) != 0) {
if (SDL_AUDIO_ISSIGNED(test_format)) {
switch (test_format) {
case SDL_AUDIO_U8:
case SDL_AUDIO_S16LE:
case SDL_AUDIO_F32:
break;
default:
continue;
}
break;
}
if (!test_format) {
// Didn't find a compatible format :
LOGI("No compatible audio format, using signed 16-bit audio");
test_format = SDL_AUDIO_S16;
LOGI("No compatible audio format, using signed 16-bit LE audio");
test_format = SDL_AUDIO_S16LE;
}
device->spec.format = test_format;
} else {
// Just go with signed 16-bit audio as it's the most compatible
device->spec.format = SDL_AUDIO_S16;
device->spec.format = SDL_AUDIO_S16LE;
}
// Update the fragment size as size in bytes
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+13 -14
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -93,6 +93,13 @@ static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *,
#ifdef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC
SDL_ELF_NOTE_DLOPEN(
"audio-libpipewire",
"Support for audio through libpipewire",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC
)
static const char *pipewire_library = SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC;
static SDL_SharedObject *pipewire_handle = NULL;
@@ -266,13 +273,11 @@ static bool pipewire_core_version_at_least(int major, int minor, int patch)
static bool io_list_check_add(struct io_node *node)
{
struct io_node *n;
bool ret = true;
// See if the node is already in the list
spa_list_for_each (n, &hotplug_io_list, link) {
if (n->id == node->id) {
ret = false;
goto dup_found;
return false;
}
}
@@ -283,9 +288,7 @@ static bool io_list_check_add(struct io_node *node)
SDL_AddAudioDevice(node->recording, node->name, &node->spec, PW_ID_TO_HANDLE(node->id));
}
dup_found:
return ret;
return true;
}
static void io_list_remove(Uint32 id)
@@ -550,7 +553,7 @@ static void node_event_info(void *object, const struct pw_node_info *info)
// Need to parse the parameters to get the sample rate
for (i = 0; i < info->n_params; ++i) {
pw_node_enum_params((struct pw_node*)node->proxy, 0, info->params[i].id, 0, 0, NULL);
pw_node_enum_params((struct pw_node *)node->proxy, 0, info->params[i].id, 0, 0, NULL);
}
hotplug_core_sync(node);
@@ -632,16 +635,12 @@ static int metadata_property(void *object, Uint32 subject, const char *key, cons
if (subject == PW_ID_CORE && key && value) {
if (!SDL_strcmp(key, "default.audio.sink")) {
if (pipewire_default_sink_id) {
SDL_free(pipewire_default_sink_id);
}
SDL_free(pipewire_default_sink_id);
pipewire_default_sink_id = get_name_from_json(value);
node->persist = true;
change_default_device(pipewire_default_sink_id);
} else if (!SDL_strcmp(key, "default.audio.source")) {
if (pipewire_default_source_id) {
SDL_free(pipewire_default_source_id);
}
SDL_free(pipewire_default_source_id);
pipewire_default_source_id = get_name_from_json(value);
node->persist = true;
change_default_device(pipewire_default_source_id);
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -133,6 +133,13 @@ static bool load_pulseaudio_syms(void);
#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
SDL_ELF_NOTE_DLOPEN(
"audio-libpulseaudio",
"Support for audio through libpulseaudio",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
)
static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC;
static SDL_SharedObject *pulseaudio_handle = NULL;
@@ -595,6 +602,71 @@ static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata)
PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details.
}
// Channel maps that match the order in SDL_Audio.h
static const pa_channel_position_t Pulse_map_1[] = { PA_CHANNEL_POSITION_MONO };
static const pa_channel_position_t Pulse_map_2[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT };
static const pa_channel_position_t Pulse_map_3[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_LFE };
static const pa_channel_position_t Pulse_map_4[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT };
static const pa_channel_position_t Pulse_map_5[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT };
static const pa_channel_position_t Pulse_map_6[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT };
static const pa_channel_position_t Pulse_map_7[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_CENTER,
PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT };
static const pa_channel_position_t Pulse_map_8[] = { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT };
#define COPY_CHANNEL_MAP(c) SDL_memcpy(pacmap->map, Pulse_map_##c, sizeof(Pulse_map_##c))
static void PulseCreateChannelMap(pa_channel_map *pacmap, uint8_t channels)
{
SDL_assert(channels <= PA_CHANNELS_MAX);
pacmap->channels = channels;
switch (channels) {
case 1:
COPY_CHANNEL_MAP(1);
break;
case 2:
COPY_CHANNEL_MAP(2);
break;
case 3:
COPY_CHANNEL_MAP(3);
break;
case 4:
COPY_CHANNEL_MAP(4);
break;
case 5:
COPY_CHANNEL_MAP(5);
break;
case 6:
COPY_CHANNEL_MAP(6);
break;
case 7:
COPY_CHANNEL_MAP(7);
break;
case 8:
COPY_CHANNEL_MAP(8);
break;
}
}
static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device)
{
const bool recording = device->recording;
@@ -683,9 +755,8 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device)
PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
// The SDL ALSA output hints us that we use Windows' channel mapping
// https://bugzilla.libsdl.org/show_bug.cgi?id=110
PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->spec.channels, PA_CHANNEL_MAP_WAVEEX);
PulseCreateChannelMap(&pacmap, device->spec.channels);
h->stream = PULSEAUDIO_pa_stream_new(
pulseaudio_context,
@@ -732,7 +803,7 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device)
if (!actual_bufattr) {
result = SDL_SetError("Could not determine connected PulseAudio stream's buffer attributes");
} else {
device->buffer_size = (int) recording ? actual_bufattr->tlength : actual_bufattr->fragsize;
device->buffer_size = (int) recording ? actual_bufattr->fragsize : actual_bufattr->tlength;
device->sample_frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec);
}
}
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+3 -3
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -89,8 +89,8 @@ static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars)
static bool QSA_WaitDevice(SDL_AudioDevice *device)
{
// Setup timeout for playing one fragment equal to 2 seconds
// If timeout occurred than something wrong with hardware or driver
// For example, Vortex 8820 audio driver stucks on second DAC because
// If timeout occurred then something wrong with hardware or driver
// For example, Vortex 8820 audio driver hangs on second DAC because
// it doesn't exist !
const int result = SDL_IOReady(device->hidden->audio_fd,
device->recording ? SDL_IOR_READ : SDL_IOR_WRITE,
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+8 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -108,6 +108,13 @@ static bool load_sndio_syms(void)
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
SDL_ELF_NOTE_DLOPEN(
"audio-libsndio",
"Support for audio through libsndio",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
)
static void UnloadSNDIOLibrary(void)
{
if (sndio_handle) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+63 -29
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -45,8 +45,8 @@
// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency).
static HMODULE libavrt = NULL;
typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
typedef HANDLE (WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
typedef BOOL (WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
@@ -54,11 +54,15 @@ static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NUL
static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
#ifdef __IAudioClient2_INTERFACE_DEFINED__
static const IID SDL_IID_IAudioClient2 = { 0x726778cd, 0xf60a, 0x4EDA, { 0x82, 0xde, 0xe4, 0x76, 0x10, 0xcd, 0x78, 0xaa } };
#endif //
#ifdef __IAudioClient3_INTERFACE_DEFINED__
static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } };
#endif //
static bool immdevice_initialized = false;
static bool supports_recording_on_playback_devices = false;
// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
// so we proxy various stuff to a single background thread to manage.
@@ -164,21 +168,10 @@ bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, b
return true; // successfully added (and possibly executed)!
}
static bool mgmtthrtask_AudioDeviceDisconnected(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_AudioDeviceDisconnected(device);
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
return true;
}
static void AudioDeviceDisconnected(SDL_AudioDevice *device)
{
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
if (device) {
RefPhysicalAudioDevice(device); // make sure this lives until the task completes.
WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL);
}
WASAPI_DisconnectDevice(device);
}
static bool mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
@@ -337,7 +330,7 @@ typedef struct
static bool mgmtthrtask_DetectDevices(void *userdata)
{
mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording, SDL_AUDIO_F32);
SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording, SDL_AUDIO_F32, supports_recording_on_playback_devices);
return true;
}
@@ -351,19 +344,11 @@ static void WASAPI_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDe
WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
}
static bool mgmtthrtask_DisconnectDevice(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_AudioDeviceDisconnected(device);
UnrefPhysicalAudioDevice(device);
return true;
}
void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
{
if (SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1)) {
RefPhysicalAudioDevice(device); // will unref when the task ends.
WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL);
// don't block in here; IMMDevice's own thread needs to return or everything will deadlock.
if (device && (!device->hidden || SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1))) {
SDL_AudioDeviceDisconnected(device); // this proxies the work to the main thread now, so no point in proxying to the management thread.
}
}
@@ -462,6 +447,8 @@ static bool mgmtthrtask_ActivateDevice(void *userdata)
return false; // This is already set by SDL_IMMDevice_Get
}
device->hidden->isplayback = !SDL_IMMDevice_GetIsCapture(immdevice);
// this is _not_ async in standard win32, yay!
HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
IMMDevice_Release(immdevice);
@@ -605,7 +592,7 @@ static int WASAPI_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen
UINT32 frames = 0;
DWORD flags = 0;
while (device->hidden->capture) {
while (device->hidden->capture && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) {
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL3.
@@ -741,16 +728,62 @@ static bool mgmtthrtask_PrepDevice(void *userdata)
newspec.freq = waveformat->nSamplesPerSec;
if (device->recording && device->hidden->isplayback) {
streamflags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
}
streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
int new_sample_frames = 0;
bool iaudioclient3_initialized = false;
#ifdef __IAudioClient2_INTERFACE_DEFINED__
IAudioClient2 *client2 = NULL;
ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient2, (void **)&client2);
if (SUCCEEDED(ret)) {
AudioClientProperties audioProps;
SDL_zero(audioProps);
audioProps.cbSize = sizeof(audioProps);
// Setting AudioCategory_GameChat breaks audio on several devices, including Behringer U-PHORIA UM2 and RODE NT-USB Mini.
// We'll disable this for now until we understand more about what's happening.
#if 0
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE);
if (hint && *hint) {
if (SDL_strcasecmp(hint, "Communications") == 0) {
audioProps.eCategory = AudioCategory_Communications;
} else if (SDL_strcasecmp(hint, "Game") == 0) {
// We'll add support for GameEffects as distinct from GameMedia later when we add stream roles
audioProps.eCategory = AudioCategory_GameEffects;
} else if (SDL_strcasecmp(hint, "GameChat") == 0) {
audioProps.eCategory = AudioCategory_GameChat;
} else if (SDL_strcasecmp(hint, "Movie") == 0) {
audioProps.eCategory = AudioCategory_Movie;
} else if (SDL_strcasecmp(hint, "Media") == 0) {
audioProps.eCategory = AudioCategory_Media;
}
}
#endif
if (SDL_GetHintBoolean(SDL_HINT_AUDIO_DEVICE_RAW_STREAM, false)) {
audioProps.Options = AUDCLNT_STREAMOPTIONS_RAW;
}
ret = IAudioClient2_SetClientProperties(client2, &audioProps);
if (FAILED(ret)) {
// This isn't fatal, let's log it instead of failing
SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO, "IAudioClient2_SetClientProperties failed: 0x%lx", ret);
}
IAudioClient2_Release(client2);
}
#endif
#ifdef __IAudioClient3_INTERFACE_DEFINED__
// Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED
if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
IAudioClient3 *client3 = NULL;
ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void**)&client3);
ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void **)&client3);
if (SUCCEEDED(ret)) {
UINT32 default_period_in_frames = 0;
UINT32 fundamental_period_in_frames = 0;
@@ -952,6 +985,7 @@ static bool WASAPI_Init(SDL_AudioDriverImpl *impl)
impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
impl->HasRecordingSupport = true;
supports_recording_on_playback_devices = SDL_GetHintBoolean(SDL_HINT_AUDIO_INCLUDE_MONITORS, false);
return true;
}
+2 -1
View File
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -43,6 +43,7 @@ struct SDL_PrivateAudioData
SDL_AtomicInt device_disconnecting;
bool device_lost;
bool device_dead;
bool isplayback;
};
// win32 implementation calls into these.