update SDL3 from 3.2.20 to 3.4.2
This commit is contained in:
+209
-157
@@ -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 ? ¤t_audio.recording_device_count : ¤t_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(¤t_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 = ¤t_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(¤t_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 = ¤t_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(¤t_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(¤t_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(¤t_audio.playback_device_count, 0);
|
||||
SDL_SetAtomicInt(¤t_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 ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count);
|
||||
result = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID));
|
||||
if (result) {
|
||||
CountAudioDevicesData data = { 0, 0, num_devices, result, recording };
|
||||
SDL_IterateHashTable(current_audio.device_hash, 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(¤t_default_device->refcount) > 1); // we should hold at least one extra reference to this device, beyond logical devices, during this phase...
|
||||
RefPhysicalAudioDevice(new_default_device);
|
||||
@@ -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 = ¤t_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,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
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,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
|
||||
@@ -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,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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,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
|
||||
@@ -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,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
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,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,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
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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,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
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
@@ -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,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user