summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/audio
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/audio
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/audio')
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audio.c2534
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audio_c.h27
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audio_channel_converters.h1068
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audiocvt.c1381
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audiodev.c124
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audiodev_c.h41
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audioqueue.c652
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audioqueue.h79
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audioresample.c706
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audioresample.h43
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_audiotypecvt.c925
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_mixer.c290
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_sysaudio.h392
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_wave.c2151
-rw-r--r--contrib/SDL-3.2.8/src/audio/SDL_wave.h151
-rw-r--r--contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c551
-rw-r--r--contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.h38
-rw-r--r--contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudiofuncs.h82
-rw-r--r--contrib/SDL-3.2.8/src/audio/alsa/SDL_alsa_audio.c1519
-rw-r--r--contrib/SDL-3.2.8/src/audio/alsa/SDL_alsa_audio.h41
-rw-r--r--contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h68
-rw-r--r--contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m1040
-rw-r--r--contrib/SDL-3.2.8/src/audio/directsound/SDL_directsound.c680
-rw-r--r--contrib/SDL-3.2.8/src/audio/directsound/SDL_directsound.h43
-rw-r--r--contrib/SDL-3.2.8/src/audio/disk/SDL_diskaudio.c171
-rw-r--r--contrib/SDL-3.2.8/src/audio/disk/SDL_diskaudio.h36
-rw-r--r--contrib/SDL-3.2.8/src/audio/dsp/SDL_dspaudio.c303
-rw-r--r--contrib/SDL-3.2.8/src/audio/dsp/SDL_dspaudio.h37
-rw-r--r--contrib/SDL-3.2.8/src/audio/dummy/SDL_dummyaudio.c135
-rw-r--r--contrib/SDL-3.2.8/src/audio/dummy/SDL_dummyaudio.h34
-rw-r--r--contrib/SDL-3.2.8/src/audio/emscripten/SDL_emscriptenaudio.c359
-rw-r--r--contrib/SDL-3.2.8/src/audio/emscripten/SDL_emscriptenaudio.h33
-rw-r--r--contrib/SDL-3.2.8/src/audio/haiku/SDL_haikuaudio.cc222
-rw-r--r--contrib/SDL-3.2.8/src/audio/haiku/SDL_haikuaudio.h35
-rw-r--r--contrib/SDL-3.2.8/src/audio/jack/SDL_jackaudio.c435
-rw-r--r--contrib/SDL-3.2.8/src/audio/jack/SDL_jackaudio.h35
-rw-r--r--contrib/SDL-3.2.8/src/audio/n3ds/SDL_n3dsaudio.c287
-rw-r--r--contrib/SDL-3.2.8/src/audio/n3ds/SDL_n3dsaudio.h40
-rw-r--r--contrib/SDL-3.2.8/src/audio/netbsd/SDL_netbsdaudio.c328
-rw-r--r--contrib/SDL-3.2.8/src/audio/netbsd/SDL_netbsdaudio.h44
-rw-r--r--contrib/SDL-3.2.8/src/audio/openslES/SDL_openslES.c807
-rw-r--r--contrib/SDL-3.2.8/src/audio/openslES/SDL_openslES.h38
-rw-r--r--contrib/SDL-3.2.8/src/audio/pipewire/SDL_pipewire.c1349
-rw-r--r--contrib/SDL-3.2.8/src/audio/pipewire/SDL_pipewire.h43
-rw-r--r--contrib/SDL-3.2.8/src/audio/ps2/SDL_ps2audio.c159
-rw-r--r--contrib/SDL-3.2.8/src/audio/ps2/SDL_ps2audio.h42
-rw-r--r--contrib/SDL-3.2.8/src/audio/psp/SDL_pspaudio.c183
-rw-r--r--contrib/SDL-3.2.8/src/audio/psp/SDL_pspaudio.h41
-rw-r--r--contrib/SDL-3.2.8/src/audio/pulseaudio/SDL_pulseaudio.c1037
-rw-r--r--contrib/SDL-3.2.8/src/audio/pulseaudio/SDL_pulseaudio.h44
-rw-r--r--contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c451
-rw-r--r--contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.h40
-rw-r--r--contrib/SDL-3.2.8/src/audio/sndio/SDL_sndioaudio.c356
-rw-r--r--contrib/SDL-3.2.8/src/audio/sndio/SDL_sndioaudio.h38
-rw-r--r--contrib/SDL-3.2.8/src/audio/vita/SDL_vitaaudio.c238
-rw-r--r--contrib/SDL-3.2.8/src/audio/vita/SDL_vitaaudio.h41
-rw-r--r--contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.c963
-rw-r--r--contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.h61
58 files changed, 23091 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audio.c b/contrib/SDL-3.2.8/src/audio/SDL_audio.c
new file mode 100644
index 0000000..583a159
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audio.c
@@ -0,0 +1,2534 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_audio_c.h"
24#include "SDL_sysaudio.h"
25#include "../thread/SDL_systhread.h"
26
27// Available audio drivers
28static const AudioBootStrap *const bootstrap[] = {
29#ifdef SDL_AUDIO_DRIVER_PRIVATE
30 &PRIVATEAUDIO_bootstrap,
31#endif
32#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO
33#ifdef SDL_AUDIO_DRIVER_PIPEWIRE
34 &PIPEWIRE_PREFERRED_bootstrap,
35#endif
36 &PULSEAUDIO_bootstrap,
37#endif
38#ifdef SDL_AUDIO_DRIVER_PIPEWIRE
39 &PIPEWIRE_bootstrap,
40#endif
41#ifdef SDL_AUDIO_DRIVER_ALSA
42 &ALSA_bootstrap,
43#endif
44#ifdef SDL_AUDIO_DRIVER_SNDIO
45 &SNDIO_bootstrap,
46#endif
47#ifdef SDL_AUDIO_DRIVER_NETBSD
48 &NETBSDAUDIO_bootstrap,
49#endif
50#ifdef SDL_AUDIO_DRIVER_WASAPI
51 &WASAPI_bootstrap,
52#endif
53#ifdef SDL_AUDIO_DRIVER_DSOUND
54 &DSOUND_bootstrap,
55#endif
56#ifdef SDL_AUDIO_DRIVER_HAIKU
57 &HAIKUAUDIO_bootstrap,
58#endif
59#ifdef SDL_AUDIO_DRIVER_COREAUDIO
60 &COREAUDIO_bootstrap,
61#endif
62#ifdef SDL_AUDIO_DRIVER_AAUDIO
63 &AAUDIO_bootstrap,
64#endif
65#ifdef SDL_AUDIO_DRIVER_OPENSLES
66 &OPENSLES_bootstrap,
67#endif
68#ifdef SDL_AUDIO_DRIVER_PS2
69 &PS2AUDIO_bootstrap,
70#endif
71#ifdef SDL_AUDIO_DRIVER_PSP
72 &PSPAUDIO_bootstrap,
73#endif
74#ifdef SDL_AUDIO_DRIVER_VITA
75 &VITAAUD_bootstrap,
76#endif
77#ifdef SDL_AUDIO_DRIVER_N3DS
78 &N3DSAUDIO_bootstrap,
79#endif
80#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
81 &EMSCRIPTENAUDIO_bootstrap,
82#endif
83#ifdef SDL_AUDIO_DRIVER_JACK
84 &JACK_bootstrap,
85#endif
86#ifdef SDL_AUDIO_DRIVER_OSS
87 &DSP_bootstrap,
88#endif
89#ifdef SDL_AUDIO_DRIVER_QNX
90 &QSAAUDIO_bootstrap,
91#endif
92#ifdef SDL_AUDIO_DRIVER_DISK
93 &DISKAUDIO_bootstrap,
94#endif
95#ifdef SDL_AUDIO_DRIVER_DUMMY
96 &DUMMYAUDIO_bootstrap,
97#endif
98 NULL
99};
100
101static SDL_AudioDriver current_audio;
102
103// Deduplicated list of audio bootstrap drivers.
104static const AudioBootStrap *deduped_bootstrap[SDL_arraysize(bootstrap) - 1];
105
106int SDL_GetNumAudioDrivers(void)
107{
108 static int num_drivers = -1;
109
110 if (num_drivers >= 0) {
111 return num_drivers;
112 }
113
114 num_drivers = 0;
115
116 // Build a list of unique audio drivers.
117 for (int i = 0; bootstrap[i] != NULL; ++i) {
118 bool duplicate = false;
119 for (int j = 0; j < i; ++j) {
120 if (SDL_strcmp(bootstrap[i]->name, bootstrap[j]->name) == 0) {
121 duplicate = true;
122 break;
123 }
124 }
125
126 if (!duplicate) {
127 deduped_bootstrap[num_drivers++] = bootstrap[i];
128 }
129 }
130
131 return num_drivers;
132}
133
134const char *SDL_GetAudioDriver(int index)
135{
136 if (index >= 0 && index < SDL_GetNumAudioDrivers()) {
137 return deduped_bootstrap[index]->name;
138 }
139 SDL_InvalidParamError("index");
140 return NULL;
141}
142
143const char *SDL_GetCurrentAudioDriver(void)
144{
145 return current_audio.name;
146}
147
148int SDL_GetDefaultSampleFramesFromFreq(const int freq)
149{
150 const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES);
151 if (hint) {
152 const int val = SDL_atoi(hint);
153 if (val > 0) {
154 return val;
155 }
156 }
157
158 if (freq <= 22050) {
159 return 512;
160 } else if (freq <= 48000) {
161 return 1024;
162 } else if (freq <= 96000) {
163 return 2048;
164 } else {
165 return 4096;
166 }
167}
168
169int *SDL_ChannelMapDup(const int *origchmap, int channels)
170{
171 const size_t chmaplen = sizeof (*origchmap) * channels;
172 int *chmap = (int *)SDL_malloc(chmaplen);
173 if (chmap) {
174 SDL_memcpy(chmap, origchmap, chmaplen);
175 }
176 return chmap;
177}
178
179void OnAudioStreamCreated(SDL_AudioStream *stream)
180{
181 SDL_assert(stream != NULL);
182
183 // NOTE that you can create an audio stream without initializing the audio subsystem,
184 // but it will not be automatically destroyed during a later call to SDL_Quit!
185 // You must explicitly destroy it yourself!
186 if (current_audio.device_hash_lock) {
187 // this isn't really part of the "device list" but it's a convenient lock to use here.
188 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
189 if (current_audio.existing_streams) {
190 current_audio.existing_streams->prev = stream;
191 }
192 stream->prev = NULL;
193 stream->next = current_audio.existing_streams;
194 current_audio.existing_streams = stream;
195 SDL_UnlockRWLock(current_audio.device_hash_lock);
196 }
197}
198
199void OnAudioStreamDestroy(SDL_AudioStream *stream)
200{
201 SDL_assert(stream != NULL);
202
203 // NOTE that you can create an audio stream without initializing the audio subsystem,
204 // but it will not be automatically destroyed during a later call to SDL_Quit!
205 // You must explicitly destroy it yourself!
206 if (current_audio.device_hash_lock) {
207 // this isn't really part of the "device list" but it's a convenient lock to use here.
208 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
209 if (stream->prev) {
210 stream->prev->next = stream->next;
211 }
212 if (stream->next) {
213 stream->next->prev = stream->prev;
214 }
215 if (stream == current_audio.existing_streams) {
216 current_audio.existing_streams = stream->next;
217 }
218 SDL_UnlockRWLock(current_audio.device_hash_lock);
219 }
220}
221
222// device should be locked when calling this.
223static bool AudioDeviceCanUseSimpleCopy(SDL_AudioDevice *device)
224{
225 SDL_assert(device != NULL);
226 return (
227 device->logical_devices && // there's a logical device
228 !device->logical_devices->next && // there's only _ONE_ logical device
229 !device->logical_devices->postmix && // there isn't a postmix callback
230 device->logical_devices->bound_streams && // there's a bound stream
231 !device->logical_devices->bound_streams->next_binding // there's only _ONE_ bound stream.
232 );
233}
234
235// should hold device->lock before calling.
236static void UpdateAudioStreamFormatsPhysical(SDL_AudioDevice *device)
237{
238 if (!device) {
239 return;
240 }
241
242 const bool recording = device->recording;
243 SDL_AudioSpec spec;
244 SDL_copyp(&spec, &device->spec);
245
246 const SDL_AudioFormat devformat = spec.format;
247
248 if (!recording) {
249 const bool simple_copy = AudioDeviceCanUseSimpleCopy(device);
250 device->simple_copy = simple_copy;
251 if (!simple_copy) {
252 spec.format = SDL_AUDIO_F32; // mixing and postbuf operates in float32 format.
253 }
254 }
255
256 for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
257 if (recording) {
258 const bool need_float32 = (logdev->postmix || logdev->gain != 1.0f);
259 spec.format = need_float32 ? SDL_AUDIO_F32 : devformat;
260 }
261
262 for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) {
263 // set the proper end of the stream to the device's format.
264 // SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec.
265 SDL_AudioSpec *streamspec = recording ? &stream->src_spec : &stream->dst_spec;
266 int **streamchmap = recording ? &stream->src_chmap : &stream->dst_chmap;
267 SDL_LockMutex(stream->lock);
268 SDL_copyp(streamspec, &spec);
269 SetAudioStreamChannelMap(stream, streamspec, streamchmap, device->chmap, device->spec.channels, -1); // this should be fast for normal cases, though!
270 SDL_UnlockMutex(stream->lock);
271 }
272 }
273}
274
275bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b, const int *channel_map_a, const int *channel_map_b)
276{
277 if ((a->format != b->format) || (a->channels != b->channels) || (a->freq != b->freq) || ((channel_map_a != NULL) != (channel_map_b != NULL))) {
278 return false;
279 } else if (channel_map_a && (SDL_memcmp(channel_map_a, channel_map_b, sizeof (*channel_map_a) * a->channels) != 0)) {
280 return false;
281 }
282 return true;
283}
284
285bool SDL_AudioChannelMapsEqual(int channels, const int *channel_map_a, const int *channel_map_b)
286{
287 if (channel_map_a == channel_map_b) {
288 return true;
289 } else if ((channel_map_a != NULL) != (channel_map_b != NULL)) {
290 return false;
291 } else if (channel_map_a && (SDL_memcmp(channel_map_a, channel_map_b, sizeof (*channel_map_a) * channels) != 0)) {
292 return false;
293 }
294 return true;
295}
296
297
298// Zombie device implementation...
299
300// These get used when a device is disconnected or fails, so audiostreams don't overflow with data that isn't being
301// consumed and apps relying on audio callbacks don't stop making progress.
302static bool ZombieWaitDevice(SDL_AudioDevice *device)
303{
304 if (!SDL_GetAtomicInt(&device->shutdown)) {
305 const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec);
306 SDL_Delay((frames * 1000) / device->spec.freq);
307 }
308 return true;
309}
310
311static bool ZombiePlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
312{
313 return true; // no-op, just throw the audio away.
314}
315
316static Uint8 *ZombieGetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
317{
318 return device->work_buffer;
319}
320
321static int ZombieRecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
322{
323 // return a full buffer of silence every time.
324 SDL_memset(buffer, device->silence_value, buflen);
325 return buflen;
326}
327
328static void ZombieFlushRecording(SDL_AudioDevice *device)
329{
330 // no-op, this is all imaginary.
331}
332
333
334
335// device management and hotplug...
336
337
338/* SDL_AudioDevice, in SDL3, represents a piece of physical hardware, whether it is in use or not, so these objects exist as long as
339 the system-level device is available.
340
341 Physical devices get destroyed for three reasons:
342 - They were lost to the system (a USB cable is kicked out, etc).
343 - They failed for some other unlikely reason at the API level (which is _also_ probably a USB cable being kicked out).
344 - We are shutting down, so all allocated resources are being freed.
345
346 They are _not_ destroyed because we are done using them (when we "close" a playing device).
347*/
348static void ClosePhysicalAudioDevice(SDL_AudioDevice *device);
349
350
351SDL_COMPILE_TIME_ASSERT(check_lowest_audio_default_value, SDL_AUDIO_DEVICE_DEFAULT_RECORDING < SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK);
352
353static SDL_AtomicInt last_device_instance_id; // increments on each device add to provide unique instance IDs
354static SDL_AudioDeviceID AssignAudioDeviceInstanceId(bool recording, bool islogical)
355{
356 /* Assign an instance id! Start at 2, in case there are things from the SDL2 era that still think 1 is a special value.
357 Also, make sure we don't assign SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, etc. */
358
359 // The bottom two bits of the instance id tells you if it's an playback device (1<<0), and if it's a physical device (1<<1).
360 const SDL_AudioDeviceID flags = (recording ? 0 : (1<<0)) | (islogical ? 0 : (1<<1));
361
362 const SDL_AudioDeviceID instance_id = (((SDL_AudioDeviceID) (SDL_AtomicIncRef(&last_device_instance_id) + 1)) << 2) | flags;
363 SDL_assert( (instance_id >= 2) && (instance_id < SDL_AUDIO_DEVICE_DEFAULT_RECORDING) );
364 return instance_id;
365}
366
367bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid)
368{
369 return (devid & (1 << 1)) != 0;
370}
371
372bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid)
373{
374 return (devid & (1 << 0)) != 0;
375}
376
377static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE
378{
379 if (device) {
380 RefPhysicalAudioDevice(device);
381 SDL_LockMutex(device->lock);
382 }
383}
384
385static void ReleaseAudioDevice(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_RELEASE
386{
387 if (device) {
388 SDL_UnlockMutex(device->lock);
389 UnrefPhysicalAudioDevice(device);
390 }
391}
392
393// If found, this locks _the physical device_ this logical device is associated with, before returning.
394static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_AudioDevice **_device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_ACQUIRE
395{
396 SDL_assert(_device != NULL);
397
398 if (!SDL_GetCurrentAudioDriver()) {
399 SDL_SetError("Audio subsystem is not initialized");
400 *_device = NULL;
401 return NULL;
402 }
403
404 SDL_AudioDevice *device = NULL;
405 SDL_LogicalAudioDevice *logdev = NULL;
406
407 // bit #1 of devid is set for physical devices and unset for logical.
408 const bool islogical = !(devid & (1<<1));
409 if (islogical) { // don't bother looking if it's not a logical device id value.
410 SDL_LockRWLockForReading(current_audio.device_hash_lock);
411 SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev);
412 if (logdev) {
413 device = logdev->physical_device;
414 SDL_assert(device != NULL);
415 RefPhysicalAudioDevice(device); // reference it, in case the logical device migrates to a new default.
416 }
417 SDL_UnlockRWLock(current_audio.device_hash_lock);
418
419 if (logdev) {
420 // we have to release the device_hash_lock before we take the device lock, to avoid deadlocks, so do a loop
421 // to make sure the correct physical device gets locked, in case we're in a race with the default changing.
422 while (true) {
423 SDL_LockMutex(device->lock);
424 SDL_AudioDevice *recheck_device = (SDL_AudioDevice *) SDL_GetAtomicPointer((void **) &logdev->physical_device);
425 if (device == recheck_device) {
426 break;
427 }
428
429 // default changed from under us! Try again!
430 RefPhysicalAudioDevice(recheck_device);
431 SDL_UnlockMutex(device->lock);
432 UnrefPhysicalAudioDevice(device);
433 device = recheck_device;
434 }
435 }
436 }
437
438 if (!logdev) {
439 SDL_SetError("Invalid audio device instance ID");
440 }
441
442 *_device = device;
443 return logdev;
444}
445
446
447/* this finds the physical device associated with `devid` and locks it for use.
448 Note that a logical device instance id will return its associated physical device! */
449static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // !!! FIXME: SDL_ACQUIRE
450{
451 SDL_AudioDevice *device = NULL;
452
453 // bit #1 of devid is set for physical devices and unset for logical.
454 const bool islogical = !(devid & (1<<1));
455 if (islogical) {
456 ObtainLogicalAudioDevice(devid, &device);
457 } else if (!SDL_GetCurrentAudioDriver()) { // (the `islogical` path, above, checks this in ObtainLogicalAudioDevice.)
458 SDL_SetError("Audio subsystem is not initialized");
459 } else {
460 SDL_LockRWLockForReading(current_audio.device_hash_lock);
461 SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device);
462 SDL_UnlockRWLock(current_audio.device_hash_lock);
463
464 if (!device) {
465 SDL_SetError("Invalid audio device instance ID");
466 } else {
467 ObtainPhysicalAudioDeviceObj(device);
468 }
469 }
470
471 return device;
472}
473
474static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceID devid) // !!! FIXME: SDL_ACQUIRE
475{
476 const bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) || (devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING));
477 if (!wants_default) {
478 return ObtainPhysicalAudioDevice(devid);
479 }
480
481 const SDL_AudioDeviceID orig_devid = devid;
482
483 while (true) {
484 SDL_LockRWLockForReading(current_audio.device_hash_lock);
485 if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) {
486 devid = current_audio.default_playback_device_id;
487 } else if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) {
488 devid = current_audio.default_recording_device_id;
489 }
490 SDL_UnlockRWLock(current_audio.device_hash_lock);
491
492 if (devid == 0) {
493 SDL_SetError("No default audio device available");
494 break;
495 }
496
497 SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid);
498 if (!device) {
499 break;
500 }
501
502 // make sure the default didn't change while we were waiting for the lock...
503 bool got_it = false;
504 SDL_LockRWLockForReading(current_audio.device_hash_lock);
505 if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) && (devid == current_audio.default_playback_device_id)) {
506 got_it = true;
507 } else if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) && (devid == current_audio.default_recording_device_id)) {
508 got_it = true;
509 }
510 SDL_UnlockRWLock(current_audio.device_hash_lock);
511
512 if (got_it) {
513 return device;
514 }
515
516 ReleaseAudioDevice(device); // let it go and try again.
517 }
518
519 return NULL;
520}
521
522// this assumes you hold the _physical_ device lock for this logical device! This will not unlock the lock or close the physical device!
523// It also will not unref the physical device, since we might be shutting down; SDL_CloseAudioDevice handles the unref.
524static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev)
525{
526 // Remove ourselves from the device_hash hashtable.
527 if (current_audio.device_hash) { // will be NULL while shutting down.
528 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
529 SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) logdev->instance_id);
530 SDL_UnlockRWLock(current_audio.device_hash_lock);
531 }
532
533 // remove ourselves from the physical device's list of logical devices.
534 if (logdev->next) {
535 logdev->next->prev = logdev->prev;
536 }
537 if (logdev->prev) {
538 logdev->prev->next = logdev->next;
539 }
540 if (logdev->physical_device->logical_devices == logdev) {
541 logdev->physical_device->logical_devices = logdev->next;
542 }
543
544 // unbind any still-bound streams...
545 SDL_AudioStream *next;
546 for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = next) {
547 SDL_LockMutex(stream->lock);
548 next = stream->next_binding;
549 stream->next_binding = NULL;
550 stream->prev_binding = NULL;
551 stream->bound_device = NULL;
552 SDL_UnlockMutex(stream->lock);
553 }
554
555 UpdateAudioStreamFormatsPhysical(logdev->physical_device);
556 SDL_free(logdev);
557}
558
559// this must not be called while `device` is still in a device list, or while a device's audio thread is still running.
560static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device)
561{
562 if (!device) {
563 return;
564 }
565
566 // Destroy any logical devices that still exist...
567 SDL_LockMutex(device->lock); // don't use ObtainPhysicalAudioDeviceObj because we don't want to change refcounts while destroying.
568 while (device->logical_devices) {
569 DestroyLogicalAudioDevice(device->logical_devices);
570 }
571
572 ClosePhysicalAudioDevice(device);
573
574 current_audio.impl.FreeDeviceHandle(device);
575
576 SDL_UnlockMutex(device->lock); // don't use ReleaseAudioDevice because we don't want to change refcounts while destroying.
577
578 SDL_DestroyMutex(device->lock);
579 SDL_DestroyCondition(device->close_cond);
580 SDL_free(device->work_buffer);
581 SDL_free(device->chmap);
582 SDL_free(device->name);
583 SDL_free(device);
584}
585
586// Don't hold the device lock when calling this, as we may destroy the device!
587void UnrefPhysicalAudioDevice(SDL_AudioDevice *device)
588{
589 if (SDL_AtomicDecRef(&device->refcount)) {
590 // take it out of the device list.
591 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
592 if (SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id)) {
593 SDL_AddAtomicInt(device->recording ? &current_audio.recording_device_count : &current_audio.playback_device_count, -1);
594 }
595 SDL_UnlockRWLock(current_audio.device_hash_lock);
596 DestroyPhysicalAudioDevice(device); // ...and nuke it.
597 }
598}
599
600void RefPhysicalAudioDevice(SDL_AudioDevice *device)
601{
602 SDL_AtomicIncRef(&device->refcount);
603}
604
605static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, bool recording, const SDL_AudioSpec *spec, void *handle, SDL_AtomicInt *device_count)
606{
607 SDL_assert(name != NULL);
608
609 SDL_LockRWLockForReading(current_audio.device_hash_lock);
610 const int shutting_down = SDL_GetAtomicInt(&current_audio.shutting_down);
611 SDL_UnlockRWLock(current_audio.device_hash_lock);
612 if (shutting_down) {
613 return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment.
614 }
615
616 SDL_AudioDevice *device = (SDL_AudioDevice *)SDL_calloc(1, sizeof(SDL_AudioDevice));
617 if (!device) {
618 return NULL;
619 }
620
621 device->name = SDL_strdup(name);
622 if (!device->name) {
623 SDL_free(device);
624 return NULL;
625 }
626
627 device->lock = SDL_CreateMutex();
628 if (!device->lock) {
629 SDL_free(device->name);
630 SDL_free(device);
631 return NULL;
632 }
633
634 device->close_cond = SDL_CreateCondition();
635 if (!device->close_cond) {
636 SDL_DestroyMutex(device->lock);
637 SDL_free(device->name);
638 SDL_free(device);
639 return NULL;
640 }
641
642 SDL_SetAtomicInt(&device->shutdown, 0);
643 SDL_SetAtomicInt(&device->zombie, 0);
644 device->recording = recording;
645 SDL_copyp(&device->spec, spec);
646 SDL_copyp(&device->default_spec, spec);
647 device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq);
648 device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format);
649 device->handle = handle;
650
651 device->instance_id = AssignAudioDeviceInstanceId(recording, /*islogical=*/false);
652
653 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
654 if (SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) {
655 SDL_AddAtomicInt(device_count, 1);
656 } else {
657 SDL_DestroyCondition(device->close_cond);
658 SDL_DestroyMutex(device->lock);
659 SDL_free(device->name);
660 SDL_free(device);
661 device = NULL;
662 }
663 SDL_UnlockRWLock(current_audio.device_hash_lock);
664
665 RefPhysicalAudioDevice(device); // unref'd on device disconnect.
666 return device;
667}
668
669static SDL_AudioDevice *CreateAudioRecordingDevice(const char *name, const SDL_AudioSpec *spec, void *handle)
670{
671 SDL_assert(current_audio.impl.HasRecordingSupport);
672 return CreatePhysicalAudioDevice(name, true, spec, handle, &current_audio.recording_device_count);
673}
674
675static SDL_AudioDevice *CreateAudioPlaybackDevice(const char *name, const SDL_AudioSpec *spec, void *handle)
676{
677 return CreatePhysicalAudioDevice(name, false, spec, handle, &current_audio.playback_device_count);
678}
679
680// The audio backends call this when a new device is plugged in.
681SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_AudioSpec *inspec, void *handle)
682{
683 // device handles MUST be unique! If the target reuses the same handle for hardware with both recording and playback interfaces, wrap it in a pointer you SDL_malloc'd!
684 SDL_assert(SDL_FindPhysicalAudioDeviceByHandle(handle) == NULL);
685
686 const SDL_AudioFormat default_format = recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT;
687 const int default_channels = recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS;
688 const int default_freq = recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY;
689
690 SDL_AudioSpec spec;
691 SDL_zero(spec);
692 if (!inspec) {
693 spec.format = default_format;
694 spec.channels = default_channels;
695 spec.freq = default_freq;
696 } else {
697 spec.format = (inspec->format != 0) ? inspec->format : default_format;
698 spec.channels = (inspec->channels != 0) ? inspec->channels : default_channels;
699 spec.freq = (inspec->freq != 0) ? inspec->freq : default_freq;
700 }
701
702 SDL_AudioDevice *device = recording ? CreateAudioRecordingDevice(name, &spec, handle) : CreateAudioPlaybackDevice(name, &spec, handle);
703
704 // Add a device add event to the pending list, to be pushed when the event queue is pumped (away from any of our internal threads).
705 if (device) {
706 SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent));
707 if (p) { // if allocation fails, you won't get an event, but we can't help that.
708 p->type = SDL_EVENT_AUDIO_DEVICE_ADDED;
709 p->devid = device->instance_id;
710 p->next = NULL;
711 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
712 SDL_assert(current_audio.pending_events_tail != NULL);
713 SDL_assert(current_audio.pending_events_tail->next == NULL);
714 current_audio.pending_events_tail->next = p;
715 current_audio.pending_events_tail = p;
716 SDL_UnlockRWLock(current_audio.device_hash_lock);
717 }
718 }
719
720 return device;
721}
722
723// Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread.
724void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
725{
726 if (!device) {
727 return;
728 }
729
730 // Save off removal info in a list so we can send events for each, next
731 // time the event queue pumps, in case something tries to close a device
732 // from an event filter, as this would risk deadlocks and other disasters
733 // if done from the device thread.
734 SDL_PendingAudioDeviceEvent pending;
735 pending.next = NULL;
736 SDL_PendingAudioDeviceEvent *pending_tail = &pending;
737
738 ObtainPhysicalAudioDeviceObj(device);
739
740 SDL_LockRWLockForReading(current_audio.device_hash_lock);
741 const SDL_AudioDeviceID devid = device->instance_id;
742 const bool is_default_device = ((devid == current_audio.default_playback_device_id) || (devid == current_audio.default_recording_device_id));
743 SDL_UnlockRWLock(current_audio.device_hash_lock);
744
745 const bool first_disconnect = SDL_CompareAndSwapAtomicInt(&device->zombie, 0, 1);
746 if (first_disconnect) { // if already disconnected this device, don't do it twice.
747 // Swap in "Zombie" versions of the usual platform interfaces, so the device will keep
748 // making progress until the app closes it. Otherwise, streams might continue to
749 // accumulate waste data that never drains, apps that depend on audio callbacks to
750 // progress will freeze, etc.
751 device->WaitDevice = ZombieWaitDevice;
752 device->GetDeviceBuf = ZombieGetDeviceBuf;
753 device->PlayDevice = ZombiePlayDevice;
754 device->WaitRecordingDevice = ZombieWaitDevice;
755 device->RecordDevice = ZombieRecordDevice;
756 device->FlushRecording = ZombieFlushRecording;
757
758 // on default devices, dump any logical devices that explicitly opened this device. Things that opened the system default can stay.
759 // on non-default devices, dump everything.
760 // (by "dump" we mean send a REMOVED event; the zombie will keep consuming audio data for these logical devices until explicitly closed.)
761 for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
762 if (!is_default_device || !logdev->opened_as_default) { // if opened as a default, leave it on the zombie device for later migration.
763 SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent));
764 if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
765 p->type = SDL_EVENT_AUDIO_DEVICE_REMOVED;
766 p->devid = logdev->instance_id;
767 p->next = NULL;
768 pending_tail->next = p;
769 pending_tail = p;
770 }
771 }
772 }
773
774 SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent));
775 if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
776 p->type = SDL_EVENT_AUDIO_DEVICE_REMOVED;
777 p->devid = device->instance_id;
778 p->next = NULL;
779 pending_tail->next = p;
780 pending_tail = p;
781 }
782 }
783
784 ReleaseAudioDevice(device);
785
786 if (first_disconnect) {
787 if (pending.next) { // NULL if event is disabled or disaster struck.
788 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
789 SDL_assert(current_audio.pending_events_tail != NULL);
790 SDL_assert(current_audio.pending_events_tail->next == NULL);
791 current_audio.pending_events_tail->next = pending.next;
792 current_audio.pending_events_tail = pending_tail;
793 SDL_UnlockRWLock(current_audio.device_hash_lock);
794 }
795
796 UnrefPhysicalAudioDevice(device);
797 }
798}
799
800
801// stubs for audio drivers that don't need a specific entry point...
802
803static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ }
804static bool SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { return true; /* no-op. */ }
805static bool SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { return true; /* no-op. */ }
806static bool SDL_AudioWaitRecordingDevice_Default(SDL_AudioDevice *device) { return true; /* no-op. */ }
807static void SDL_AudioFlushRecording_Default(SDL_AudioDevice *device) { /* no-op. */ }
808static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
809static void SDL_AudioDeinitializeStart_Default(void) { /* no-op. */ }
810static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ }
811static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ }
812
813static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device)
814{
815 SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
816}
817
818static void SDL_AudioDetectDevices_Default(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
819{
820 // you have to write your own implementation if these assertions fail.
821 SDL_assert(current_audio.impl.OnlyHasDefaultPlaybackDevice);
822 SDL_assert(current_audio.impl.OnlyHasDefaultRecordingDevice || !current_audio.impl.HasRecordingSupport);
823
824 *default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)((size_t)0x1));
825 if (current_audio.impl.HasRecordingSupport) {
826 *default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)((size_t)0x2));
827 }
828}
829
830static Uint8 *SDL_AudioGetDeviceBuf_Default(SDL_AudioDevice *device, int *buffer_size)
831{
832 *buffer_size = 0;
833 return NULL;
834}
835
836static int SDL_AudioRecordDevice_Default(SDL_AudioDevice *device, void *buffer, int buflen)
837{
838 SDL_Unsupported();
839 return -1;
840}
841
842static bool SDL_AudioOpenDevice_Default(SDL_AudioDevice *device)
843{
844 return SDL_Unsupported();
845}
846
847// Fill in stub functions for unused driver entry points. This lets us blindly call them without having to check for validity first.
848static void CompleteAudioEntryPoints(void)
849{
850 #define FILL_STUB(x) if (!current_audio.impl.x) { current_audio.impl.x = SDL_Audio##x##_Default; }
851 FILL_STUB(DetectDevices);
852 FILL_STUB(OpenDevice);
853 FILL_STUB(ThreadInit);
854 FILL_STUB(ThreadDeinit);
855 FILL_STUB(WaitDevice);
856 FILL_STUB(PlayDevice);
857 FILL_STUB(GetDeviceBuf);
858 FILL_STUB(WaitRecordingDevice);
859 FILL_STUB(RecordDevice);
860 FILL_STUB(FlushRecording);
861 FILL_STUB(CloseDevice);
862 FILL_STUB(FreeDeviceHandle);
863 FILL_STUB(DeinitializeStart);
864 FILL_STUB(Deinitialize);
865 #undef FILL_STUB
866}
867
868typedef struct FindLowestDeviceIDData
869{
870 const bool recording;
871 SDL_AudioDeviceID highest;
872 SDL_AudioDevice *result;
873} FindLowestDeviceIDData;
874
875static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
876{
877 FindLowestDeviceIDData *data = (FindLowestDeviceIDData *) userdata;
878 const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key;
879 // bit #0 of devid is set for playback devices and unset for recording.
880 // bit #1 of devid is set for physical devices and unset for logical.
881 const bool devid_recording = !(devid & (1 << 0));
882 const bool isphysical = !!(devid & (1 << 1));
883 if (isphysical && (devid_recording == data->recording) && (devid < data->highest)) {
884 data->highest = devid;
885 data->result = (SDL_AudioDevice *) value;
886 }
887 return true; // keep iterating.
888}
889
890static SDL_AudioDevice *GetFirstAddedAudioDevice(const bool recording)
891{
892 const SDL_AudioDeviceID highest = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; // According to AssignAudioDeviceInstanceId, nothing can have a value this large.
893
894 // (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.)
895 FindLowestDeviceIDData data = { recording, highest, NULL };
896 SDL_LockRWLockForReading(current_audio.device_hash_lock);
897 SDL_IterateHashTable(current_audio.device_hash, FindLowestDeviceID, &data);
898 SDL_UnlockRWLock(current_audio.device_hash_lock);
899 return data.result;
900}
901
902static Uint32 SDLCALL HashAudioDeviceID(void *userdata, const void *key)
903{
904 // shift right 2, to dump the first two bits, since these are flags
905 // (recording vs playback, logical vs physical) and the rest are unique incrementing integers.
906 return ((Uint32) ((uintptr_t) key)) >> 2;
907}
908
909// !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match.
910bool SDL_InitAudio(const char *driver_name)
911{
912 if (SDL_GetCurrentAudioDriver()) {
913 SDL_QuitAudio(); // shutdown driver if already running.
914 }
915
916 // make sure device IDs start at 2 (because of SDL2 legacy interface), but don't reset the counter on each init, in case the app is holding an old device ID somewhere.
917 SDL_CompareAndSwapAtomicInt(&last_device_instance_id, 0, 2);
918
919 SDL_ChooseAudioConverters();
920 SDL_SetupAudioResampler();
921
922 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.
923 if (!device_hash_lock) {
924 return false;
925 }
926
927 SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, HashAudioDeviceID, SDL_KeyMatchID, NULL, NULL);
928 if (!device_hash) {
929 SDL_DestroyRWLock(device_hash_lock);
930 return false;
931 }
932
933 // Select the proper audio driver
934 if (!driver_name) {
935 driver_name = SDL_GetHint(SDL_HINT_AUDIO_DRIVER);
936 }
937
938 bool initialized = false;
939 bool tried_to_init = false;
940
941 if (driver_name && *driver_name != 0) {
942 char *driver_name_copy = SDL_strdup(driver_name);
943 const char *driver_attempt = driver_name_copy;
944
945 if (!driver_name_copy) {
946 SDL_DestroyRWLock(device_hash_lock);
947 SDL_DestroyHashTable(device_hash);
948 return false;
949 }
950
951 while (driver_attempt && *driver_attempt != 0 && !initialized) {
952 char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
953 if (driver_attempt_end) {
954 *driver_attempt_end = '\0';
955 }
956
957 // SDL 1.2 uses the name "dsound", so we'll support both.
958 if (SDL_strcmp(driver_attempt, "dsound") == 0) {
959 driver_attempt = "directsound";
960 } else if (SDL_strcmp(driver_attempt, "pulse") == 0) { // likewise, "pulse" was renamed to "pulseaudio"
961 driver_attempt = "pulseaudio";
962 }
963
964 for (int i = 0; bootstrap[i]; ++i) {
965 if (!bootstrap[i]->is_preferred && SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) {
966 tried_to_init = true;
967 SDL_zero(current_audio);
968 current_audio.pending_events_tail = &current_audio.pending_events;
969 current_audio.device_hash_lock = device_hash_lock;
970 current_audio.device_hash = device_hash;
971 if (bootstrap[i]->init(&current_audio.impl)) {
972 current_audio.name = bootstrap[i]->name;
973 current_audio.desc = bootstrap[i]->desc;
974 initialized = true;
975 break;
976 }
977 }
978 }
979
980 driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
981 }
982
983 SDL_free(driver_name_copy);
984 } else {
985 for (int i = 0; (!initialized) && (bootstrap[i]); ++i) {
986 if (bootstrap[i]->demand_only) {
987 continue;
988 }
989
990 tried_to_init = true;
991 SDL_zero(current_audio);
992 current_audio.pending_events_tail = &current_audio.pending_events;
993 current_audio.device_hash_lock = device_hash_lock;
994 current_audio.device_hash = device_hash;
995 if (bootstrap[i]->init(&current_audio.impl)) {
996 current_audio.name = bootstrap[i]->name;
997 current_audio.desc = bootstrap[i]->desc;
998 initialized = true;
999 }
1000 }
1001 }
1002
1003 if (!initialized) {
1004 // specific drivers will set the error message if they fail, but otherwise we do it here.
1005 if (!tried_to_init) {
1006 if (driver_name) {
1007 SDL_SetError("Audio target '%s' not available", driver_name);
1008 } else {
1009 SDL_SetError("No available audio device");
1010 }
1011 }
1012
1013 SDL_DestroyRWLock(device_hash_lock);
1014 SDL_DestroyHashTable(device_hash);
1015 SDL_zero(current_audio);
1016 return false; // No driver was available, so fail.
1017 }
1018
1019 CompleteAudioEntryPoints();
1020
1021 // Make sure we have a list of devices available at startup...
1022 SDL_AudioDevice *default_playback = NULL;
1023 SDL_AudioDevice *default_recording = NULL;
1024 current_audio.impl.DetectDevices(&default_playback, &default_recording);
1025
1026 // If no default was _ever_ specified, just take the first device we see, if any.
1027 if (!default_playback) {
1028 default_playback = GetFirstAddedAudioDevice(/*recording=*/false);
1029 }
1030
1031 if (!default_recording) {
1032 default_recording = GetFirstAddedAudioDevice(/*recording=*/true);
1033 }
1034
1035 if (default_playback) {
1036 current_audio.default_playback_device_id = default_playback->instance_id;
1037 RefPhysicalAudioDevice(default_playback); // extra ref on default devices.
1038 }
1039
1040 if (default_recording) {
1041 current_audio.default_recording_device_id = default_recording->instance_id;
1042 RefPhysicalAudioDevice(default_recording); // extra ref on default devices.
1043 }
1044
1045 return true;
1046}
1047
1048static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
1049{
1050 // bit #1 of devid is set for physical devices and unset for logical.
1051 const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key;
1052 const bool isphysical = !!(devid & (1<<1));
1053 if (isphysical) {
1054 DestroyPhysicalAudioDevice((SDL_AudioDevice *) value);
1055 }
1056 return true; // keep iterating.
1057}
1058
1059void SDL_QuitAudio(void)
1060{
1061 if (!current_audio.name) { // not initialized?!
1062 return;
1063 }
1064
1065 current_audio.impl.DeinitializeStart();
1066
1067 // Destroy any audio streams that still exist...
1068 while (current_audio.existing_streams) {
1069 SDL_DestroyAudioStream(current_audio.existing_streams);
1070 }
1071
1072 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
1073 SDL_SetAtomicInt(&current_audio.shutting_down, 1);
1074 SDL_HashTable *device_hash = current_audio.device_hash;
1075 current_audio.device_hash = NULL;
1076 SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next;
1077 current_audio.pending_events.next = NULL;
1078 SDL_SetAtomicInt(&current_audio.playback_device_count, 0);
1079 SDL_SetAtomicInt(&current_audio.recording_device_count, 0);
1080 SDL_UnlockRWLock(current_audio.device_hash_lock);
1081
1082 SDL_PendingAudioDeviceEvent *pending_next = NULL;
1083 for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) {
1084 pending_next = i->next;
1085 SDL_free(i);
1086 }
1087
1088 SDL_IterateHashTable(device_hash, DestroyOnePhysicalAudioDevice, NULL);
1089
1090 // Free the driver data
1091 current_audio.impl.Deinitialize();
1092
1093 SDL_DestroyRWLock(current_audio.device_hash_lock);
1094 SDL_DestroyHashTable(device_hash);
1095
1096 SDL_zero(current_audio);
1097}
1098
1099
1100void SDL_AudioThreadFinalize(SDL_AudioDevice *device)
1101{
1102}
1103
1104static void MixFloat32Audio(float *dst, const float *src, const int buffer_size)
1105{
1106 if (!SDL_MixAudio((Uint8 *) dst, (const Uint8 *) src, SDL_AUDIO_F32, buffer_size, 1.0f)) {
1107 SDL_assert(!"This shouldn't happen.");
1108 }
1109}
1110
1111
1112// Playback device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort.
1113
1114void SDL_PlaybackAudioThreadSetup(SDL_AudioDevice *device)
1115{
1116 SDL_assert(!device->recording);
1117 current_audio.impl.ThreadInit(device);
1118}
1119
1120bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
1121{
1122 SDL_assert(!device->recording);
1123
1124 SDL_LockMutex(device->lock);
1125
1126 if (SDL_GetAtomicInt(&device->shutdown)) {
1127 SDL_UnlockMutex(device->lock);
1128 return false; // we're done, shut it down.
1129 }
1130
1131 bool failed = false;
1132 int buffer_size = device->buffer_size;
1133 Uint8 *device_buffer = device->GetDeviceBuf(device, &buffer_size);
1134 if (buffer_size == 0) {
1135 // WASAPI (maybe others, later) does this to say "just abandon this iteration and try again next time."
1136 } else if (!device_buffer) {
1137 failed = true;
1138 } else {
1139 SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more.
1140 SDL_assert(AudioDeviceCanUseSimpleCopy(device) == device->simple_copy); // make sure this hasn't gotten out of sync.
1141
1142 // can we do a basic copy without silencing/mixing the buffer? This is an extremely likely scenario, so we special-case it.
1143 if (device->simple_copy) {
1144 SDL_LogicalAudioDevice *logdev = device->logical_devices;
1145 SDL_AudioStream *stream = logdev->bound_streams;
1146
1147 // We should have updated this elsewhere if the format changed!
1148 SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, NULL, NULL));
1149
1150 const int br = SDL_GetAtomicInt(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain);
1151 if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow.
1152 failed = true;
1153 SDL_memset(device_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die.
1154 } else if (br < buffer_size) {
1155 SDL_memset(device_buffer + br, device->silence_value, buffer_size - br); // silence whatever we didn't write to.
1156 }
1157
1158 // generally channel maps will line up, but if the audio stream's chmap has been explicitly changed, do a final swizzle to device layout.
1159 if ((br > 0) && (!SDL_AudioChannelMapsEqual(device->spec.channels, stream->dst_chmap, device->chmap))) {
1160 ConvertAudio(br / SDL_AUDIO_FRAMESIZE(device->spec), device_buffer, device->spec.format, device->spec.channels, NULL,
1161 device_buffer, device->spec.format, device->spec.channels, device->chmap, NULL, 1.0f);
1162 }
1163 } else { // need to actually mix (or silence the buffer)
1164 float *final_mix_buffer = (float *) ((device->spec.format == SDL_AUDIO_F32) ? device_buffer : device->mix_buffer);
1165 const int needed_samples = buffer_size / SDL_AUDIO_BYTESIZE(device->spec.format);
1166 const int work_buffer_size = needed_samples * sizeof (float);
1167 SDL_AudioSpec outspec;
1168
1169 SDL_assert(work_buffer_size <= device->work_buffer_size);
1170
1171 SDL_copyp(&outspec, &device->spec);
1172 outspec.format = SDL_AUDIO_F32;
1173
1174 SDL_memset(final_mix_buffer, '\0', work_buffer_size); // start with silence.
1175
1176 for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
1177 if (SDL_GetAtomicInt(&logdev->paused)) {
1178 continue; // paused? Skip this logical device.
1179 }
1180
1181 const SDL_AudioPostmixCallback postmix = logdev->postmix;
1182 float *mix_buffer = final_mix_buffer;
1183 if (postmix) {
1184 mix_buffer = device->postmix_buffer;
1185 SDL_memset(mix_buffer, '\0', work_buffer_size); // start with silence.
1186 }
1187
1188 for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) {
1189 // We should have updated this elsewhere if the format changed!
1190 SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, NULL, NULL));
1191
1192 /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams
1193 for iterating here because the binding linked list can only change while the device lock is held.
1194 (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind
1195 the same stream to different devices at the same time, though.) */
1196 const int br = SDL_GetAudioStreamDataAdjustGain(stream, device->work_buffer, work_buffer_size, logdev->gain);
1197 if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow.
1198 failed = true;
1199 break;
1200 } else if (br > 0) { // it's okay if we get less than requested, we mix what we have.
1201 // generally channel maps will line up, but if the audio stream's chmap has been explicitly changed, do a final swizzle to device layout.
1202 if (!SDL_AudioChannelMapsEqual(device->spec.channels, stream->dst_chmap, device->chmap)) {
1203 ConvertAudio(br / SDL_AUDIO_FRAMESIZE(device->spec), device->work_buffer, device->spec.format, device->spec.channels, NULL,
1204 device->work_buffer, device->spec.format, device->spec.channels, device->chmap, NULL, 1.0f);
1205 }
1206 MixFloat32Audio(mix_buffer, (float *) device->work_buffer, br);
1207 }
1208 }
1209
1210 if (postmix) {
1211 SDL_assert(mix_buffer == device->postmix_buffer);
1212 postmix(logdev->postmix_userdata, &outspec, mix_buffer, work_buffer_size);
1213 MixFloat32Audio(final_mix_buffer, mix_buffer, work_buffer_size);
1214 }
1215 }
1216
1217 if (((Uint8 *) final_mix_buffer) != device_buffer) {
1218 // !!! FIXME: we can't promise the device buf is aligned/padded for SIMD.
1219 //ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device_buffer, device->spec.format, device->spec.channels, NULL, NULL, 1.0f);
1220 ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device->work_buffer, device->spec.format, device->spec.channels, NULL, NULL, 1.0f);
1221 SDL_memcpy(device_buffer, device->work_buffer, buffer_size);
1222 }
1223 }
1224
1225 // PlayDevice SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead!
1226 if (!device->PlayDevice(device, device_buffer, buffer_size)) {
1227 failed = true;
1228 }
1229 }
1230
1231 SDL_UnlockMutex(device->lock);
1232
1233 if (failed) {
1234 SDL_AudioDeviceDisconnected(device); // doh.
1235 }
1236
1237 return true; // always go on if not shutting down, even if device failed.
1238}
1239
1240void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device)
1241{
1242 SDL_assert(!device->recording);
1243 const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec);
1244 // Wait for the audio to drain if device didn't die.
1245 if (!SDL_GetAtomicInt(&device->zombie)) {
1246 SDL_Delay(((frames * 1000) / device->spec.freq) * 2);
1247 }
1248 current_audio.impl.ThreadDeinit(device);
1249 SDL_AudioThreadFinalize(device);
1250}
1251
1252static int SDLCALL PlaybackAudioThread(void *devicep) // thread entry point
1253{
1254 SDL_AudioDevice *device = (SDL_AudioDevice *)devicep;
1255 SDL_assert(device != NULL);
1256 SDL_assert(!device->recording);
1257 SDL_PlaybackAudioThreadSetup(device);
1258
1259 do {
1260 if (!device->WaitDevice(device)) {
1261 SDL_AudioDeviceDisconnected(device); // doh. (but don't break out of the loop, just be a zombie for now!)
1262 }
1263 } while (SDL_PlaybackAudioThreadIterate(device));
1264
1265 SDL_PlaybackAudioThreadShutdown(device);
1266 return 0;
1267}
1268
1269
1270
1271// Recording device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort.
1272
1273void SDL_RecordingAudioThreadSetup(SDL_AudioDevice *device)
1274{
1275 SDL_assert(device->recording);
1276 current_audio.impl.ThreadInit(device);
1277}
1278
1279bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device)
1280{
1281 SDL_assert(device->recording);
1282
1283 SDL_LockMutex(device->lock);
1284
1285 if (SDL_GetAtomicInt(&device->shutdown)) {
1286 SDL_UnlockMutex(device->lock);
1287 return false; // we're done, shut it down.
1288 }
1289
1290 bool failed = false;
1291
1292 if (!device->logical_devices) {
1293 device->FlushRecording(device); // nothing wants data, dump anything pending.
1294 } else {
1295 // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitRecordingDevice!
1296 int br = device->RecordDevice(device, device->work_buffer, device->buffer_size);
1297 if (br < 0) { // uhoh, device failed for some reason!
1298 failed = true;
1299 } else if (br > 0) { // queue the new data to each bound stream.
1300 for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
1301 if (SDL_GetAtomicInt(&logdev->paused)) {
1302 continue; // paused? Skip this logical device.
1303 }
1304
1305 void *output_buffer = device->work_buffer;
1306
1307 // I don't know why someone would want a postmix on a recording device, but we offer it for API consistency.
1308 if (logdev->postmix || (logdev->gain != 1.0f)) {
1309 // move to float format.
1310 SDL_AudioSpec outspec;
1311 SDL_copyp(&outspec, &device->spec);
1312 outspec.format = SDL_AUDIO_F32;
1313 output_buffer = device->postmix_buffer;
1314 const int frames = br / SDL_AUDIO_FRAMESIZE(device->spec);
1315 br = frames * SDL_AUDIO_FRAMESIZE(outspec);
1316 ConvertAudio(frames, device->work_buffer, device->spec.format, outspec.channels, NULL, device->postmix_buffer, SDL_AUDIO_F32, outspec.channels, NULL, NULL, logdev->gain);
1317 if (logdev->postmix) {
1318 logdev->postmix(logdev->postmix_userdata, &outspec, device->postmix_buffer, br);
1319 }
1320 }
1321
1322 for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) {
1323 // We should have updated this elsewhere if the format changed!
1324 SDL_assert(stream->src_spec.format == ((logdev->postmix || (logdev->gain != 1.0f)) ? SDL_AUDIO_F32 : device->spec.format));
1325 SDL_assert(stream->src_spec.channels == device->spec.channels);
1326 SDL_assert(stream->src_spec.freq == device->spec.freq);
1327
1328 void *final_buf = output_buffer;
1329
1330 // generally channel maps will line up, but if the audio stream's chmap has been explicitly changed, do a final swizzle to stream layout.
1331 if (!SDL_AudioChannelMapsEqual(device->spec.channels, stream->src_chmap, device->chmap)) {
1332 final_buf = device->mix_buffer; // this is otherwise unused on recording devices, so it makes convenient scratch space here.
1333 ConvertAudio(br / SDL_AUDIO_FRAMESIZE(device->spec), output_buffer, device->spec.format, device->spec.channels, NULL,
1334 final_buf, device->spec.format, device->spec.channels, stream->src_chmap, NULL, 1.0f);
1335 }
1336
1337 /* this will hold a lock on `stream` while putting. We don't explicitly lock the streams
1338 for iterating here because the binding linked list can only change while the device lock is held.
1339 (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind
1340 the same stream to different devices at the same time, though.) */
1341 if (!SDL_PutAudioStreamData(stream, final_buf, br)) {
1342 // oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow.
1343 failed = true;
1344 break;
1345 }
1346 }
1347 }
1348 }
1349 }
1350
1351 SDL_UnlockMutex(device->lock);
1352
1353 if (failed) {
1354 SDL_AudioDeviceDisconnected(device); // doh.
1355 }
1356
1357 return true; // always go on if not shutting down, even if device failed.
1358}
1359
1360void SDL_RecordingAudioThreadShutdown(SDL_AudioDevice *device)
1361{
1362 SDL_assert(device->recording);
1363 device->FlushRecording(device);
1364 current_audio.impl.ThreadDeinit(device);
1365 SDL_AudioThreadFinalize(device);
1366}
1367
1368static int SDLCALL RecordingAudioThread(void *devicep) // thread entry point
1369{
1370 SDL_AudioDevice *device = (SDL_AudioDevice *)devicep;
1371 SDL_assert(device != NULL);
1372 SDL_assert(device->recording);
1373 SDL_RecordingAudioThreadSetup(device);
1374
1375 do {
1376 if (!device->WaitRecordingDevice(device)) {
1377 SDL_AudioDeviceDisconnected(device); // doh. (but don't break out of the loop, just be a zombie for now!)
1378 }
1379 } while (SDL_RecordingAudioThreadIterate(device));
1380
1381 SDL_RecordingAudioThreadShutdown(device);
1382 return 0;
1383}
1384
1385typedef struct CountAudioDevicesData
1386{
1387 int devs_seen;
1388 const int num_devices;
1389 SDL_AudioDeviceID *result;
1390 const bool recording;
1391} CountAudioDevicesData;
1392
1393static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
1394{
1395 CountAudioDevicesData *data = (CountAudioDevicesData *) userdata;
1396 const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key;
1397 // bit #0 of devid is set for playback devices and unset for recording.
1398 // bit #1 of devid is set for physical devices and unset for logical.
1399 const bool devid_recording = !(devid & (1<<0));
1400 const bool isphysical = !!(devid & (1<<1));
1401 if (isphysical && (devid_recording == data->recording)) {
1402 SDL_assert(data->devs_seen < data->num_devices);
1403 data->result[data->devs_seen++] = devid;
1404 }
1405 return true; // keep iterating.
1406}
1407
1408static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording)
1409{
1410 SDL_AudioDeviceID *result = NULL;
1411 int num_devices = 0;
1412
1413 if (SDL_GetCurrentAudioDriver()) {
1414 SDL_LockRWLockForReading(current_audio.device_hash_lock);
1415 {
1416 num_devices = SDL_GetAtomicInt(recording ? &current_audio.recording_device_count : &current_audio.playback_device_count);
1417 result = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID));
1418 if (result) {
1419 CountAudioDevicesData data = { 0, num_devices, result, recording };
1420 SDL_IterateHashTable(current_audio.device_hash, CountAudioDevices, &data);
1421 SDL_assert(data.devs_seen == num_devices);
1422 result[data.devs_seen] = 0; // null-terminated.
1423 }
1424 }
1425 SDL_UnlockRWLock(current_audio.device_hash_lock);
1426 } else {
1427 SDL_SetError("Audio subsystem is not initialized");
1428 }
1429
1430 if (count) {
1431 if (result) {
1432 *count = num_devices;
1433 } else {
1434 *count = 0;
1435 }
1436 }
1437 return result;
1438}
1439
1440SDL_AudioDeviceID *SDL_GetAudioPlaybackDevices(int *count)
1441{
1442 return GetAudioDevices(count, false);
1443}
1444
1445SDL_AudioDeviceID *SDL_GetAudioRecordingDevices(int *count)
1446{
1447 return GetAudioDevices(count, true);
1448}
1449
1450typedef struct FindAudioDeviceByCallbackData
1451{
1452 bool (*callback)(SDL_AudioDevice *device, void *userdata);
1453 void *userdata;
1454 SDL_AudioDevice *retval;
1455} FindAudioDeviceByCallbackData;
1456
1457static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
1458{
1459 FindAudioDeviceByCallbackData *data = (FindAudioDeviceByCallbackData *) userdata;
1460 const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key;
1461 // bit #1 of devid is set for physical devices and unset for logical.
1462 const bool isphysical = !!(devid & (1<<1));
1463 if (isphysical) {
1464 SDL_AudioDevice *device = (SDL_AudioDevice *) value;
1465 if (data->callback(device, data->userdata)) { // found it?
1466 data->retval = device;
1467 return false; // stop iterating, we found it.
1468 }
1469 }
1470 return true; // keep iterating.
1471}
1472
1473// !!! FIXME: SDL convention is for userdata to come first in the callback's params. Fix this at some point.
1474SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata)
1475{
1476 if (!SDL_GetCurrentAudioDriver()) {
1477 SDL_SetError("Audio subsystem is not initialized");
1478 return NULL;
1479 }
1480
1481 FindAudioDeviceByCallbackData data = { callback, userdata, NULL };
1482 SDL_LockRWLockForReading(current_audio.device_hash_lock);
1483 SDL_IterateHashTable(current_audio.device_hash, FindAudioDeviceByCallback, &data);
1484 SDL_UnlockRWLock(current_audio.device_hash_lock);
1485
1486 if (!data.retval) {
1487 SDL_SetError("Device not found");
1488 }
1489
1490 return data.retval;
1491}
1492
1493static bool TestDeviceHandleCallback(SDL_AudioDevice *device, void *handle)
1494{
1495 return device->handle == handle;
1496}
1497
1498SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle)
1499{
1500 return SDL_FindPhysicalAudioDeviceByCallback(TestDeviceHandleCallback, handle);
1501}
1502
1503const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)
1504{
1505 const char *result = NULL;
1506 SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid);
1507 if (device) {
1508 result = SDL_GetPersistentString(device->name);
1509 }
1510 ReleaseAudioDevice(device);
1511
1512 return result;
1513}
1514
1515bool SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames)
1516{
1517 if (!spec) {
1518 return SDL_InvalidParamError("spec");
1519 }
1520
1521 bool result = false;
1522 SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
1523 if (device) {
1524 SDL_copyp(spec, &device->spec);
1525 if (sample_frames) {
1526 *sample_frames = device->sample_frames;
1527 }
1528 result = true;
1529 }
1530 ReleaseAudioDevice(device);
1531
1532 return result;
1533}
1534
1535int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count)
1536{
1537 int *result = NULL;
1538 int channels = 0;
1539 SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
1540 if (device) {
1541 channels = device->spec.channels;
1542 result = SDL_ChannelMapDup(device->chmap, channels);
1543 }
1544 ReleaseAudioDevice(device);
1545
1546 if (count) {
1547 *count = channels;
1548 }
1549
1550 return result;
1551}
1552
1553
1554// this is awkward, but this makes sure we can release the device lock
1555// so the device thread can terminate but also not have two things
1556// race to close or open the device while the lock is unprotected.
1557// you hold the lock when calling this, it will release the lock and
1558// wait while the shutdown flag is set.
1559// BE CAREFUL WITH THIS.
1560static void SerializePhysicalDeviceClose(SDL_AudioDevice *device)
1561{
1562 while (SDL_GetAtomicInt(&device->shutdown)) {
1563 SDL_WaitCondition(device->close_cond, device->lock);
1564 }
1565}
1566
1567// this expects the device lock to be held.
1568static void ClosePhysicalAudioDevice(SDL_AudioDevice *device)
1569{
1570 SerializePhysicalDeviceClose(device);
1571
1572 SDL_SetAtomicInt(&device->shutdown, 1);
1573
1574 // YOU MUST PROTECT KEY POINTS WITH SerializePhysicalDeviceClose() WHILE THE THREAD JOINS
1575 SDL_UnlockMutex(device->lock);
1576
1577 if (device->thread) {
1578 SDL_WaitThread(device->thread, NULL);
1579 device->thread = NULL;
1580 }
1581
1582 if (device->currently_opened) {
1583 current_audio.impl.CloseDevice(device); // if ProvidesOwnCallbackThread, this must join on any existing device thread before returning!
1584 device->currently_opened = false;
1585 device->hidden = NULL; // just in case.
1586 }
1587
1588 SDL_LockMutex(device->lock);
1589 SDL_SetAtomicInt(&device->shutdown, 0); // ready to go again.
1590 SDL_BroadcastCondition(device->close_cond); // release anyone waiting in SerializePhysicalDeviceClose; they'll still block until we release device->lock, though.
1591
1592 SDL_aligned_free(device->work_buffer);
1593 device->work_buffer = NULL;
1594
1595 SDL_aligned_free(device->mix_buffer);
1596 device->mix_buffer = NULL;
1597
1598 SDL_aligned_free(device->postmix_buffer);
1599 device->postmix_buffer = NULL;
1600
1601 SDL_copyp(&device->spec, &device->default_spec);
1602 device->sample_frames = 0;
1603 device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format);
1604}
1605
1606void SDL_CloseAudioDevice(SDL_AudioDeviceID devid)
1607{
1608 SDL_AudioDevice *device = NULL;
1609 SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1610 if (logdev) {
1611 DestroyLogicalAudioDevice(logdev);
1612 }
1613
1614 if (device) {
1615 if (!device->logical_devices) { // no more logical devices? Close the physical device, too.
1616 ClosePhysicalAudioDevice(device);
1617 }
1618 UnrefPhysicalAudioDevice(device); // one reference for each logical device.
1619 }
1620
1621 ReleaseAudioDevice(device);
1622}
1623
1624
1625static SDL_AudioFormat ParseAudioFormatString(const char *string)
1626{
1627 if (string) {
1628 #define CHECK_FMT_STRING(x) if (SDL_strcmp(string, #x) == 0) { return SDL_AUDIO_##x; }
1629 CHECK_FMT_STRING(U8);
1630 CHECK_FMT_STRING(S8);
1631 CHECK_FMT_STRING(S16LE);
1632 CHECK_FMT_STRING(S16BE);
1633 CHECK_FMT_STRING(S16);
1634 CHECK_FMT_STRING(S32LE);
1635 CHECK_FMT_STRING(S32BE);
1636 CHECK_FMT_STRING(S32);
1637 CHECK_FMT_STRING(F32LE);
1638 CHECK_FMT_STRING(F32BE);
1639 CHECK_FMT_STRING(F32);
1640 #undef CHECK_FMT_STRING
1641 }
1642 return SDL_AUDIO_UNKNOWN;
1643}
1644
1645static void PrepareAudioFormat(bool recording, SDL_AudioSpec *spec)
1646{
1647 if (spec->freq == 0) {
1648 spec->freq = recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY;
1649
1650 const char *hint = SDL_GetHint(SDL_HINT_AUDIO_FREQUENCY);
1651 if (hint) {
1652 const int val = SDL_atoi(hint);
1653 if (val > 0) {
1654 spec->freq = val;
1655 }
1656 }
1657 }
1658
1659 if (spec->channels == 0) {
1660 spec->channels = recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS;
1661
1662 const char *hint = SDL_GetHint(SDL_HINT_AUDIO_CHANNELS);
1663 if (hint) {
1664 const int val = SDL_atoi(hint);
1665 if (val > 0) {
1666 spec->channels = val;
1667 }
1668 }
1669 }
1670
1671 if (spec->format == 0) {
1672 const SDL_AudioFormat val = ParseAudioFormatString(SDL_GetHint(SDL_HINT_AUDIO_FORMAT));
1673 spec->format = (val != SDL_AUDIO_UNKNOWN) ? val : (recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT);
1674 }
1675}
1676
1677void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device)
1678{
1679 device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format);
1680 device->buffer_size = device->sample_frames * SDL_AUDIO_FRAMESIZE(device->spec);
1681 device->work_buffer_size = device->sample_frames * sizeof (float) * device->spec.channels;
1682 device->work_buffer_size = SDL_max(device->buffer_size, device->work_buffer_size); // just in case we end up with a 64-bit audio format at some point.
1683}
1684
1685char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen)
1686{
1687 (void)SDL_snprintf(buf, buflen, "SDLAudio%c%d", (device->recording) ? 'C' : 'P', (int) device->instance_id);
1688 return buf;
1689}
1690
1691
1692// this expects the device lock to be held.
1693static bool OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec)
1694{
1695 SerializePhysicalDeviceClose(device); // make sure another thread that's closing didn't release the lock to let the device thread join...
1696
1697 if (device->currently_opened) {
1698 return true; // we're already good.
1699 }
1700
1701 // Just pretend to open a zombie device. It can still collect logical devices on a default device under the assumption they will all migrate when the default device is officially changed.
1702 if (SDL_GetAtomicInt(&device->zombie)) {
1703 return true; // Braaaaaaaaains.
1704 }
1705
1706 // These start with the backend's implementation, but we might swap them out with zombie versions later.
1707 device->WaitDevice = current_audio.impl.WaitDevice;
1708 device->PlayDevice = current_audio.impl.PlayDevice;
1709 device->GetDeviceBuf = current_audio.impl.GetDeviceBuf;
1710 device->WaitRecordingDevice = current_audio.impl.WaitRecordingDevice;
1711 device->RecordDevice = current_audio.impl.RecordDevice;
1712 device->FlushRecording = current_audio.impl.FlushRecording;
1713
1714 SDL_AudioSpec spec;
1715 SDL_copyp(&spec, inspec ? inspec : &device->default_spec);
1716 PrepareAudioFormat(device->recording, &spec);
1717
1718 /* We allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents
1719 something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later.
1720 (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first).
1721 These are just requests! The backend may change any of these values during OpenDevice method! */
1722 device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format;
1723 device->spec.freq = SDL_max(device->default_spec.freq, spec.freq);
1724 device->spec.channels = SDL_max(device->default_spec.channels, spec.channels);
1725 device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq);
1726 SDL_UpdatedAudioDeviceFormat(device); // start this off sane.
1727
1728 device->currently_opened = true; // mark this true even if impl.OpenDevice fails, so we know to clean up.
1729 if (!current_audio.impl.OpenDevice(device)) {
1730 ClosePhysicalAudioDevice(device); // clean up anything the backend left half-initialized.
1731 return false;
1732 }
1733
1734 SDL_UpdatedAudioDeviceFormat(device); // in case the backend changed things and forgot to call this.
1735
1736 // Allocate a scratch audio buffer
1737 device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size);
1738 if (!device->work_buffer) {
1739 ClosePhysicalAudioDevice(device);
1740 return false;
1741 }
1742
1743 if (device->spec.format != SDL_AUDIO_F32) {
1744 device->mix_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size);
1745 if (!device->mix_buffer) {
1746 ClosePhysicalAudioDevice(device);
1747 return false;
1748 }
1749 }
1750
1751 // Start the audio thread if necessary
1752 if (!current_audio.impl.ProvidesOwnCallbackThread) {
1753 char threadname[64];
1754 SDL_GetAudioThreadName(device, threadname, sizeof (threadname));
1755 device->thread = SDL_CreateThread(device->recording ? RecordingAudioThread : PlaybackAudioThread, threadname, device);
1756
1757 if (!device->thread) {
1758 ClosePhysicalAudioDevice(device);
1759 return SDL_SetError("Couldn't create audio thread");
1760 }
1761 }
1762
1763 return true;
1764}
1765
1766SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec)
1767{
1768 if (!SDL_GetCurrentAudioDriver()) {
1769 SDL_SetError("Audio subsystem is not initialized");
1770 return 0;
1771 }
1772
1773 bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) || (devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING));
1774
1775 // this will let you use a logical device to make a new logical device on the parent physical device. Could be useful?
1776 SDL_AudioDevice *device = NULL;
1777 const bool islogical = (!wants_default && !(devid & (1<<1)));
1778 if (!islogical) {
1779 device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
1780 } else {
1781 SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1782 if (logdev) {
1783 wants_default = logdev->opened_as_default; // was the original logical device meant to be a default? Make this one, too.
1784 }
1785 }
1786
1787 SDL_AudioDeviceID result = 0;
1788
1789 if (device) {
1790 SDL_LogicalAudioDevice *logdev = NULL;
1791 if (!wants_default && SDL_GetAtomicInt(&device->zombie)) {
1792 // uhoh, this device is undead, and just waiting to be cleaned up. Refuse explicit opens.
1793 SDL_SetError("Device was already lost and can't accept new opens");
1794 } else if ((logdev = (SDL_LogicalAudioDevice *) SDL_calloc(1, sizeof (SDL_LogicalAudioDevice))) == NULL) {
1795 // SDL_calloc already called SDL_OutOfMemory
1796 } else if (!OpenPhysicalAudioDevice(device, spec)) { // if this is the first thing using this physical device, open at the OS level if necessary...
1797 SDL_free(logdev);
1798 } else {
1799 RefPhysicalAudioDevice(device); // unref'd on successful SDL_CloseAudioDevice
1800 SDL_SetAtomicInt(&logdev->paused, 0);
1801 result = logdev->instance_id = AssignAudioDeviceInstanceId(device->recording, /*islogical=*/true);
1802 logdev->physical_device = device;
1803 logdev->gain = 1.0f;
1804 logdev->opened_as_default = wants_default;
1805 logdev->next = device->logical_devices;
1806 if (device->logical_devices) {
1807 device->logical_devices->prev = logdev;
1808 }
1809 device->logical_devices = logdev;
1810 UpdateAudioStreamFormatsPhysical(device);
1811 }
1812 ReleaseAudioDevice(device);
1813
1814 if (result) {
1815 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
1816 const bool inserted = SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) result, logdev, false);
1817 SDL_UnlockRWLock(current_audio.device_hash_lock);
1818 if (!inserted) {
1819 SDL_CloseAudioDevice(result);
1820 result = 0;
1821 }
1822 }
1823 }
1824
1825 return result;
1826}
1827
1828static bool SetLogicalAudioDevicePauseState(SDL_AudioDeviceID devid, int value)
1829{
1830 SDL_AudioDevice *device = NULL;
1831 SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1832 if (logdev) {
1833 SDL_SetAtomicInt(&logdev->paused, value);
1834 }
1835 ReleaseAudioDevice(device);
1836 return logdev ? true : false; // ObtainLogicalAudioDevice will have set an error.
1837}
1838
1839bool SDL_PauseAudioDevice(SDL_AudioDeviceID devid)
1840{
1841 return SetLogicalAudioDevicePauseState(devid, 1);
1842}
1843
1844bool SDLCALL SDL_ResumeAudioDevice(SDL_AudioDeviceID devid)
1845{
1846 return SetLogicalAudioDevicePauseState(devid, 0);
1847}
1848
1849bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid)
1850{
1851 SDL_AudioDevice *device = NULL;
1852 SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1853 bool result = false;
1854 if (logdev && SDL_GetAtomicInt(&logdev->paused)) {
1855 result = true;
1856 }
1857 ReleaseAudioDevice(device);
1858 return result;
1859}
1860
1861float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid)
1862{
1863 SDL_AudioDevice *device = NULL;
1864 SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1865 const float result = logdev ? logdev->gain : -1.0f;
1866 ReleaseAudioDevice(device);
1867 return result;
1868}
1869
1870bool SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain)
1871{
1872 if (gain < 0.0f) {
1873 return SDL_InvalidParamError("gain");
1874 }
1875
1876 SDL_AudioDevice *device = NULL;
1877 SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1878 bool result = false;
1879 if (logdev) {
1880 logdev->gain = gain;
1881 UpdateAudioStreamFormatsPhysical(device);
1882 result = true;
1883 }
1884 ReleaseAudioDevice(device);
1885 return result;
1886}
1887
1888bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata)
1889{
1890 SDL_AudioDevice *device = NULL;
1891 SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device);
1892 bool result = true;
1893 if (logdev) {
1894 if (callback && !device->postmix_buffer) {
1895 device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size);
1896 if (!device->postmix_buffer) {
1897 result = false;
1898 }
1899 }
1900
1901 if (result) {
1902 logdev->postmix = callback;
1903 logdev->postmix_userdata = userdata;
1904 }
1905
1906 UpdateAudioStreamFormatsPhysical(device);
1907 }
1908 ReleaseAudioDevice(device);
1909 return result;
1910}
1911
1912bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams)
1913{
1914 const bool islogical = !(devid & (1<<1));
1915 SDL_AudioDevice *device = NULL;
1916 SDL_LogicalAudioDevice *logdev = NULL;
1917 bool result = true;
1918
1919 if (num_streams == 0) {
1920 return true; // nothing to do
1921 } else if (num_streams < 0) {
1922 return SDL_InvalidParamError("num_streams");
1923 } else if (!streams) {
1924 return SDL_InvalidParamError("streams");
1925 } else if (!islogical) {
1926 return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices");
1927 }
1928
1929 logdev = ObtainLogicalAudioDevice(devid, &device);
1930 if (!logdev) {
1931 result = false; // ObtainLogicalAudioDevice set the error string.
1932 } else if (logdev->simplified) {
1933 result = SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream");
1934 } else {
1935
1936 // !!! FIXME: We'll set the device's side's format below, but maybe we should refuse to bind a stream if the app's side doesn't have a format set yet.
1937 // !!! FIXME: Actually, why do we allow there to be an invalid format, again?
1938
1939 // make sure start of list is sane.
1940 SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL));
1941
1942 // lock all the streams upfront, so we can verify they aren't bound elsewhere and add them all in one block, as this is intended to add everything or nothing.
1943 for (int i = 0; i < num_streams; i++) {
1944 SDL_AudioStream *stream = streams[i];
1945 if (!stream) {
1946 SDL_SetError("Stream #%d is NULL", i);
1947 result = false; // to pacify the static analyzer, that doesn't realize SDL_SetError() always returns false.
1948 } else {
1949 SDL_LockMutex(stream->lock);
1950 SDL_assert((stream->bound_device == NULL) == ((stream->prev_binding == NULL) || (stream->next_binding == NULL)));
1951 if (stream->bound_device) {
1952 result = SDL_SetError("Stream #%d is already bound to a device", i);
1953 } else if (stream->simplified) { // You can get here if you closed the device instead of destroying the stream.
1954 result = SDL_SetError("Cannot change binding on a stream created with SDL_OpenAudioDeviceStream");
1955 }
1956 }
1957
1958 if (!result) {
1959 int j;
1960 for (j = 0; j < i; j++) {
1961 SDL_UnlockMutex(streams[j]->lock);
1962 }
1963 if (stream) {
1964 SDL_UnlockMutex(stream->lock);
1965 }
1966 break;
1967 }
1968 }
1969 }
1970
1971 if (result) {
1972 // Now that everything is verified, chain everything together.
1973 for (int i = 0; i < num_streams; i++) {
1974 SDL_AudioStream *stream = streams[i];
1975 if (stream) { // shouldn't be NULL, but just in case...
1976 stream->bound_device = logdev;
1977 stream->prev_binding = NULL;
1978 stream->next_binding = logdev->bound_streams;
1979 if (logdev->bound_streams) {
1980 logdev->bound_streams->prev_binding = stream;
1981 }
1982 logdev->bound_streams = stream;
1983 SDL_UnlockMutex(stream->lock);
1984 }
1985 }
1986 }
1987
1988 UpdateAudioStreamFormatsPhysical(device);
1989
1990 ReleaseAudioDevice(device);
1991
1992 return result;
1993}
1994
1995bool SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream)
1996{
1997 return SDL_BindAudioStreams(devid, &stream, 1);
1998}
1999
2000// !!! FIXME: this and BindAudioStreams are mutex nightmares. :/
2001void SDL_UnbindAudioStreams(SDL_AudioStream * const *streams, int num_streams)
2002{
2003 if (num_streams <= 0 || !streams) {
2004 return; // nothing to do
2005 }
2006
2007 /* to prevent deadlock when holding both locks, we _must_ lock the device first, and the stream second, as that is the order the audio thread will do it.
2008 But this means we have an unlikely, pathological case where a stream could change its binding between when we lookup its bound device and when we lock everything,
2009 so we double-check here. */
2010 for (int i = 0; i < num_streams; i++) {
2011 SDL_AudioStream *stream = streams[i];
2012 if (!stream) {
2013 continue; // nothing to do, it's a NULL stream.
2014 }
2015
2016 while (true) {
2017 SDL_LockMutex(stream->lock); // lock to check this and then release it, in case the device isn't locked yet.
2018 SDL_LogicalAudioDevice *bounddev = stream->bound_device;
2019 SDL_UnlockMutex(stream->lock);
2020
2021 // lock in correct order.
2022 if (bounddev) {
2023 SDL_LockMutex(bounddev->physical_device->lock); // this requires recursive mutexes, since we're likely locking the same device multiple times.
2024 }
2025 SDL_LockMutex(stream->lock);
2026
2027 if (bounddev == stream->bound_device) {
2028 break; // the binding didn't change in the small window where it could, so we're good.
2029 } else {
2030 SDL_UnlockMutex(stream->lock); // it changed bindings! Try again.
2031 if (bounddev) {
2032 SDL_UnlockMutex(bounddev->physical_device->lock);
2033 }
2034 }
2035 }
2036 }
2037
2038 // everything is locked, start unbinding streams.
2039 for (int i = 0; i < num_streams; i++) {
2040 SDL_AudioStream *stream = streams[i];
2041 // don't allow unbinding from "simplified" devices (opened with SDL_OpenAudioDeviceStream). Just ignore them.
2042 if (stream && stream->bound_device && !stream->bound_device->simplified) {
2043 if (stream->bound_device->bound_streams == stream) {
2044 SDL_assert(!stream->prev_binding);
2045 stream->bound_device->bound_streams = stream->next_binding;
2046 }
2047 if (stream->prev_binding) {
2048 stream->prev_binding->next_binding = stream->next_binding;
2049 }
2050 if (stream->next_binding) {
2051 stream->next_binding->prev_binding = stream->prev_binding;
2052 }
2053 stream->prev_binding = stream->next_binding = NULL;
2054 }
2055 }
2056
2057 // Finalize and unlock everything.
2058 for (int i = 0; i < num_streams; i++) {
2059 SDL_AudioStream *stream = streams[i];
2060 if (stream) {
2061 SDL_LogicalAudioDevice *logdev = stream->bound_device;
2062 stream->bound_device = NULL;
2063 SDL_UnlockMutex(stream->lock);
2064 if (logdev) {
2065 UpdateAudioStreamFormatsPhysical(logdev->physical_device);
2066 SDL_UnlockMutex(logdev->physical_device->lock);
2067 }
2068 }
2069 }
2070}
2071
2072void SDL_UnbindAudioStream(SDL_AudioStream *stream)
2073{
2074 SDL_UnbindAudioStreams(&stream, 1);
2075}
2076
2077SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream *stream)
2078{
2079 SDL_AudioDeviceID result = 0;
2080
2081 if (!stream) {
2082 SDL_InvalidParamError("stream");
2083 return 0;
2084 }
2085
2086 SDL_LockMutex(stream->lock);
2087 if (stream->bound_device) {
2088 result = stream->bound_device->instance_id;
2089 } else {
2090 SDL_SetError("Audio stream not bound to an audio device");
2091 }
2092 SDL_UnlockMutex(stream->lock);
2093
2094 return result;
2095}
2096
2097SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata)
2098{
2099 SDL_AudioDeviceID logdevid = SDL_OpenAudioDevice(devid, spec);
2100 if (!logdevid) {
2101 return NULL; // error string should already be set.
2102 }
2103
2104 bool failed = false;
2105 SDL_AudioStream *stream = NULL;
2106 SDL_AudioDevice *device = NULL;
2107 SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(logdevid, &device);
2108 if (!logdev) { // this shouldn't happen, but just in case.
2109 failed = true;
2110 } else {
2111 SDL_SetAtomicInt(&logdev->paused, 1); // start the device paused, to match SDL2.
2112
2113 SDL_assert(device != NULL);
2114 const bool recording = device->recording;
2115
2116 // if the app didn't request a format _at all_, just make a stream that does no conversion; they can query for it later.
2117 SDL_AudioSpec tmpspec;
2118 if (!spec) {
2119 SDL_copyp(&tmpspec, &device->spec);
2120 spec = &tmpspec;
2121 }
2122
2123 if (recording) {
2124 stream = SDL_CreateAudioStream(&device->spec, spec);
2125 } else {
2126 stream = SDL_CreateAudioStream(spec, &device->spec);
2127 }
2128
2129 if (!stream) {
2130 failed = true;
2131 } else {
2132 // don't do all the complicated validation and locking of SDL_BindAudioStream just to set a few fields here.
2133 logdev->bound_streams = stream;
2134 logdev->simplified = true; // forbid further binding changes on this logical device.
2135
2136 stream->bound_device = logdev;
2137 stream->simplified = true; // so we know to close the audio device when this is destroyed.
2138
2139 UpdateAudioStreamFormatsPhysical(device);
2140
2141 if (callback) {
2142 bool rc;
2143 if (recording) {
2144 rc = SDL_SetAudioStreamPutCallback(stream, callback, userdata);
2145 } else {
2146 rc = SDL_SetAudioStreamGetCallback(stream, callback, userdata);
2147 }
2148 SDL_assert(rc); // should only fail if stream==NULL atm.
2149 }
2150 }
2151 }
2152
2153 ReleaseAudioDevice(device);
2154
2155 if (failed) {
2156 SDL_DestroyAudioStream(stream);
2157 SDL_CloseAudioDevice(logdevid);
2158 stream = NULL;
2159 }
2160
2161 return stream;
2162}
2163
2164bool SDL_PauseAudioStreamDevice(SDL_AudioStream *stream)
2165{
2166 SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream);
2167 if (!devid) {
2168 return false;
2169 }
2170
2171 return SDL_PauseAudioDevice(devid);
2172}
2173
2174bool SDL_ResumeAudioStreamDevice(SDL_AudioStream *stream)
2175{
2176 SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream);
2177 if (!devid) {
2178 return false;
2179 }
2180
2181 return SDL_ResumeAudioDevice(devid);
2182}
2183
2184bool SDL_AudioStreamDevicePaused(SDL_AudioStream *stream)
2185{
2186 SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream);
2187 if (!devid) {
2188 return false;
2189 }
2190
2191 return SDL_AudioDevicePaused(devid);
2192}
2193
2194#if SDL_BYTEORDER == SDL_LIL_ENDIAN
2195#define NATIVE(type) SDL_AUDIO_##type##LE
2196#define SWAPPED(type) SDL_AUDIO_##type##BE
2197#else
2198#define NATIVE(type) SDL_AUDIO_##type##BE
2199#define SWAPPED(type) SDL_AUDIO_##type##LE
2200#endif
2201
2202#define NUM_FORMATS 8
2203// always favor Float32 in native byte order, since we're probably going to convert to that for processing anyhow.
2204static const SDL_AudioFormat format_list[NUM_FORMATS][NUM_FORMATS + 1] = {
2205 { SDL_AUDIO_U8, NATIVE(F32), SWAPPED(F32), SDL_AUDIO_S8, NATIVE(S16), SWAPPED(S16), NATIVE(S32), SWAPPED(S32), SDL_AUDIO_UNKNOWN },
2206 { SDL_AUDIO_S8, NATIVE(F32), SWAPPED(F32), SDL_AUDIO_U8, NATIVE(S16), SWAPPED(S16), NATIVE(S32), SWAPPED(S32), SDL_AUDIO_UNKNOWN },
2207 { NATIVE(S16), NATIVE(F32), SWAPPED(F32), SWAPPED(S16), NATIVE(S32), SWAPPED(S32), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN },
2208 { SWAPPED(S16), NATIVE(F32), SWAPPED(F32), NATIVE(S16), SWAPPED(S32), NATIVE(S32), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN },
2209 { NATIVE(S32), NATIVE(F32), SWAPPED(F32), SWAPPED(S32), NATIVE(S16), SWAPPED(S16), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN },
2210 { SWAPPED(S32), NATIVE(F32), SWAPPED(F32), NATIVE(S32), SWAPPED(S16), NATIVE(S16), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN },
2211 { NATIVE(F32), SWAPPED(F32), NATIVE(S32), SWAPPED(S32), NATIVE(S16), SWAPPED(S16), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN },
2212 { SWAPPED(F32), NATIVE(F32), SWAPPED(S32), NATIVE(S32), SWAPPED(S16), NATIVE(S16), SDL_AUDIO_U8, SDL_AUDIO_S8, SDL_AUDIO_UNKNOWN },
2213};
2214
2215#undef NATIVE
2216#undef SWAPPED
2217
2218const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format)
2219{
2220 for (int i = 0; i < NUM_FORMATS; i++) {
2221 if (format_list[i][0] == format) {
2222 return &format_list[i][0];
2223 }
2224 }
2225 return &format_list[0][NUM_FORMATS]; // not found; return what looks like a list with only a zero in it.
2226}
2227
2228const char *SDL_GetAudioFormatName(SDL_AudioFormat format)
2229{
2230 switch (format) {
2231#define CASE(X) \
2232 case X: return #X;
2233 CASE(SDL_AUDIO_U8)
2234 CASE(SDL_AUDIO_S8)
2235 CASE(SDL_AUDIO_S16LE)
2236 CASE(SDL_AUDIO_S16BE)
2237 CASE(SDL_AUDIO_S32LE)
2238 CASE(SDL_AUDIO_S32BE)
2239 CASE(SDL_AUDIO_F32LE)
2240 CASE(SDL_AUDIO_F32BE)
2241#undef CASE
2242 default:
2243 return "SDL_AUDIO_UNKNOWN";
2244 }
2245}
2246
2247int SDL_GetSilenceValueForFormat(SDL_AudioFormat format)
2248{
2249 return (format == SDL_AUDIO_U8) ? 0x80 : 0x00;
2250}
2251
2252// called internally by backends when the system default device changes.
2253void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
2254{
2255 if (!new_default_device) { // !!! FIXME: what should we do in this case? Maybe all devices are lost, so there _isn't_ a default?
2256 return; // uhoh.
2257 }
2258
2259 const bool recording = new_default_device->recording;
2260
2261 // change the official default over right away, so new opens will go to the new device.
2262 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
2263 const SDL_AudioDeviceID current_devid = recording ? current_audio.default_recording_device_id : current_audio.default_playback_device_id;
2264 const bool is_already_default = (new_default_device->instance_id == current_devid);
2265 if (!is_already_default) {
2266 if (recording) {
2267 current_audio.default_recording_device_id = new_default_device->instance_id;
2268 } else {
2269 current_audio.default_playback_device_id = new_default_device->instance_id;
2270 }
2271 }
2272 SDL_UnlockRWLock(current_audio.device_hash_lock);
2273
2274 if (is_already_default) {
2275 return; // this is already the default.
2276 }
2277
2278 // Queue up events to push to the queue next time it pumps (presumably
2279 // in a safer thread).
2280 // !!! FIXME: this duplicates some code we could probably refactor.
2281 SDL_PendingAudioDeviceEvent pending;
2282 pending.next = NULL;
2283 SDL_PendingAudioDeviceEvent *pending_tail = &pending;
2284
2285 // Default device gets an extra ref, so it lives until a new default replaces it, even if disconnected.
2286 RefPhysicalAudioDevice(new_default_device);
2287
2288 ObtainPhysicalAudioDeviceObj(new_default_device);
2289
2290 SDL_AudioDevice *current_default_device = ObtainPhysicalAudioDevice(current_devid);
2291
2292 if (current_default_device) {
2293 // migrate any logical devices that were opened as a default to the new physical device...
2294
2295 SDL_assert(current_default_device->recording == recording);
2296
2297 // See if we have to open the new physical device, and if so, find the best audiospec for it.
2298 SDL_AudioSpec spec;
2299 bool needs_migration = false;
2300 SDL_zero(spec);
2301
2302 for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev; logdev = logdev->next) {
2303 if (logdev->opened_as_default) {
2304 needs_migration = true;
2305 for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) {
2306 const SDL_AudioSpec *streamspec = recording ? &stream->dst_spec : &stream->src_spec;
2307 if (SDL_AUDIO_BITSIZE(streamspec->format) > SDL_AUDIO_BITSIZE(spec.format)) {
2308 spec.format = streamspec->format;
2309 }
2310 if (streamspec->channels > spec.channels) {
2311 spec.channels = streamspec->channels;
2312 }
2313 if (streamspec->freq > spec.freq) {
2314 spec.freq = streamspec->freq;
2315 }
2316 }
2317 }
2318 }
2319
2320 if (needs_migration) {
2321 // New default physical device not been opened yet? Open at the OS level...
2322 if (!OpenPhysicalAudioDevice(new_default_device, &spec)) {
2323 needs_migration = false; // uhoh, just leave everything on the old default, nothing to be done.
2324 }
2325 }
2326
2327 if (needs_migration) {
2328 // we don't currently report channel map changes, so we'll leave them as NULL for now.
2329 const bool spec_changed = !SDL_AudioSpecsEqual(&current_default_device->spec, &new_default_device->spec, NULL, NULL);
2330 SDL_LogicalAudioDevice *next = NULL;
2331 for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev; logdev = next) {
2332 next = logdev->next;
2333
2334 if (!logdev->opened_as_default) {
2335 continue; // not opened as a default, leave it on the current physical device.
2336 }
2337
2338 // now migrate the logical device. Hold device_hash_lock so ObtainLogicalAudioDevice doesn't get a device in the middle of transition.
2339 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
2340 if (logdev->next) {
2341 logdev->next->prev = logdev->prev;
2342 }
2343 if (logdev->prev) {
2344 logdev->prev->next = logdev->next;
2345 }
2346 if (current_default_device->logical_devices == logdev) {
2347 current_default_device->logical_devices = logdev->next;
2348 }
2349
2350 logdev->physical_device = new_default_device;
2351 logdev->prev = NULL;
2352 logdev->next = new_default_device->logical_devices;
2353 new_default_device->logical_devices = logdev;
2354 SDL_UnlockRWLock(current_audio.device_hash_lock);
2355
2356 SDL_assert(SDL_GetAtomicInt(&current_default_device->refcount) > 1); // we should hold at least one extra reference to this device, beyond logical devices, during this phase...
2357 RefPhysicalAudioDevice(new_default_device);
2358 UnrefPhysicalAudioDevice(current_default_device);
2359
2360 SDL_SetAudioPostmixCallback(logdev->instance_id, logdev->postmix, logdev->postmix_userdata);
2361
2362 SDL_PendingAudioDeviceEvent *p;
2363
2364 // Queue an event for each logical device we moved.
2365 if (spec_changed) {
2366 p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent));
2367 if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
2368 p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED;
2369 p->devid = logdev->instance_id;
2370 p->next = NULL;
2371 pending_tail->next = p;
2372 pending_tail = p;
2373 }
2374 }
2375 }
2376
2377 UpdateAudioStreamFormatsPhysical(current_default_device);
2378 UpdateAudioStreamFormatsPhysical(new_default_device);
2379
2380 if (!current_default_device->logical_devices) { // nothing left on the current physical device, close it.
2381 ClosePhysicalAudioDevice(current_default_device);
2382 }
2383 }
2384
2385 ReleaseAudioDevice(current_default_device);
2386 }
2387
2388 ReleaseAudioDevice(new_default_device);
2389
2390 // Default device gets an extra ref, so it lives until a new default replaces it, even if disconnected.
2391 if (current_default_device) { // (despite the name, it's no longer current at this point)
2392 UnrefPhysicalAudioDevice(current_default_device);
2393 }
2394
2395 if (pending.next) {
2396 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
2397 SDL_assert(current_audio.pending_events_tail != NULL);
2398 SDL_assert(current_audio.pending_events_tail->next == NULL);
2399 current_audio.pending_events_tail->next = pending.next;
2400 current_audio.pending_events_tail = pending_tail;
2401 SDL_UnlockRWLock(current_audio.device_hash_lock);
2402 }
2403}
2404
2405bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
2406{
2407 const int orig_work_buffer_size = device->work_buffer_size;
2408
2409 // we don't currently have any place where channel maps change from under you, but we can check that if necessary later.
2410 if (SDL_AudioSpecsEqual(&device->spec, newspec, NULL, NULL) && (new_sample_frames == device->sample_frames)) {
2411 return true; // we're already in that format.
2412 }
2413
2414 SDL_copyp(&device->spec, newspec);
2415 UpdateAudioStreamFormatsPhysical(device);
2416
2417 bool kill_device = false;
2418
2419 device->sample_frames = new_sample_frames;
2420 SDL_UpdatedAudioDeviceFormat(device);
2421 if (device->work_buffer && (device->work_buffer_size > orig_work_buffer_size)) {
2422 SDL_aligned_free(device->work_buffer);
2423 device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size);
2424 if (!device->work_buffer) {
2425 kill_device = true;
2426 }
2427
2428 if (device->postmix_buffer) {
2429 SDL_aligned_free(device->postmix_buffer);
2430 device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size);
2431 if (!device->postmix_buffer) {
2432 kill_device = true;
2433 }
2434 }
2435
2436 SDL_aligned_free(device->mix_buffer);
2437 device->mix_buffer = NULL;
2438 if (device->spec.format != SDL_AUDIO_F32) {
2439 device->mix_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size);
2440 if (!device->mix_buffer) {
2441 kill_device = true;
2442 }
2443 }
2444 }
2445
2446 // Post an event for the physical device, and each logical device on this physical device.
2447 if (!kill_device) {
2448 // Queue up events to push to the queue next time it pumps (presumably
2449 // in a safer thread).
2450 // !!! FIXME: this duplicates some code we could probably refactor.
2451 SDL_PendingAudioDeviceEvent pending;
2452 pending.next = NULL;
2453 SDL_PendingAudioDeviceEvent *pending_tail = &pending;
2454
2455 SDL_PendingAudioDeviceEvent *p;
2456
2457 p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent));
2458 if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
2459 p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED;
2460 p->devid = device->instance_id;
2461 p->next = NULL;
2462 pending_tail->next = p;
2463 pending_tail = p;
2464 }
2465
2466 for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
2467 p = (SDL_PendingAudioDeviceEvent *)SDL_malloc(sizeof(SDL_PendingAudioDeviceEvent));
2468 if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
2469 p->type = SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED;
2470 p->devid = logdev->instance_id;
2471 p->next = NULL;
2472 pending_tail->next = p;
2473 pending_tail = p;
2474 }
2475 }
2476
2477 if (pending.next) {
2478 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
2479 SDL_assert(current_audio.pending_events_tail != NULL);
2480 SDL_assert(current_audio.pending_events_tail->next == NULL);
2481 current_audio.pending_events_tail->next = pending.next;
2482 current_audio.pending_events_tail = pending_tail;
2483 SDL_UnlockRWLock(current_audio.device_hash_lock);
2484 }
2485 }
2486
2487 if (kill_device) {
2488 return false;
2489 }
2490 return true;
2491}
2492
2493bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
2494{
2495 ObtainPhysicalAudioDeviceObj(device);
2496 const bool result = SDL_AudioDeviceFormatChangedAlreadyLocked(device, newspec, new_sample_frames);
2497 ReleaseAudioDevice(device);
2498 return result;
2499}
2500
2501// This is an internal function, so SDL_PumpEvents() can check for pending audio device events.
2502// ("UpdateSubsystem" is the same naming that the other things that hook into PumpEvents use.)
2503void SDL_UpdateAudio(void)
2504{
2505 SDL_LockRWLockForReading(current_audio.device_hash_lock);
2506 SDL_PendingAudioDeviceEvent *pending_events = current_audio.pending_events.next;
2507 SDL_UnlockRWLock(current_audio.device_hash_lock);
2508
2509 if (!pending_events) {
2510 return; // nothing to do, check next time.
2511 }
2512
2513 // 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.
2514 SDL_LockRWLockForWriting(current_audio.device_hash_lock);
2515 pending_events = current_audio.pending_events.next; // in case this changed...
2516 current_audio.pending_events.next = NULL;
2517 current_audio.pending_events_tail = &current_audio.pending_events;
2518 SDL_UnlockRWLock(current_audio.device_hash_lock);
2519
2520 SDL_PendingAudioDeviceEvent *pending_next = NULL;
2521 for (SDL_PendingAudioDeviceEvent *i = pending_events; i; i = pending_next) {
2522 pending_next = i->next;
2523 if (SDL_EventEnabled(i->type)) {
2524 SDL_Event event;
2525 SDL_zero(event);
2526 event.type = i->type;
2527 event.adevice.which = (Uint32) i->devid;
2528 event.adevice.recording = ((i->devid & (1<<0)) == 0); // bit #0 of devid is set for playback devices and unset for recording.
2529 SDL_PushEvent(&event);
2530 }
2531 SDL_free(i);
2532 }
2533}
2534
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audio_c.h b/contrib/SDL-3.2.8/src/audio/SDL_audio_c.h
new file mode 100644
index 0000000..0e673f1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audio_c.h
@@ -0,0 +1,27 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_audio_c_h_
23#define SDL_audio_c_h_
24
25extern void SDL_UpdateAudio(void);
26
27#endif // SDL_audio_c_h_
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audio_channel_converters.h b/contrib/SDL-3.2.8/src/audio/SDL_audio_channel_converters.h
new file mode 100644
index 0000000..ab682e6
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audio_channel_converters.h
@@ -0,0 +1,1068 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22// DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_channel_conversion.c
23
24
25typedef void (*SDL_AudioChannelConverter)(float *dst, const float *src, int num_frames);
26
27static void SDL_ConvertMonoToStereo(float *dst, const float *src, int num_frames)
28{
29 int i;
30
31 LOG_DEBUG_AUDIO_CONVERT("mono", "stereo");
32
33 // convert backwards, since output is growing in-place.
34 src += (num_frames-1);
35 dst += (num_frames-1) * 2;
36 for (i = num_frames; i; i--, src--, dst -= 2) {
37 const float srcFC = src[0];
38 dst[1] /* FR */ = srcFC;
39 dst[0] /* FL */ = srcFC;
40 }
41
42}
43
44static void SDL_ConvertMonoTo21(float *dst, const float *src, int num_frames)
45{
46 int i;
47
48 LOG_DEBUG_AUDIO_CONVERT("mono", "2.1");
49
50 // convert backwards, since output is growing in-place.
51 src += (num_frames-1);
52 dst += (num_frames-1) * 3;
53 for (i = num_frames; i; i--, src--, dst -= 3) {
54 const float srcFC = src[0];
55 dst[2] /* LFE */ = 0.0f;
56 dst[1] /* FR */ = srcFC;
57 dst[0] /* FL */ = srcFC;
58 }
59
60}
61
62static void SDL_ConvertMonoToQuad(float *dst, const float *src, int num_frames)
63{
64 int i;
65
66 LOG_DEBUG_AUDIO_CONVERT("mono", "quad");
67
68 // convert backwards, since output is growing in-place.
69 src += (num_frames-1);
70 dst += (num_frames-1) * 4;
71 for (i = num_frames; i; i--, src--, dst -= 4) {
72 const float srcFC = src[0];
73 dst[3] /* BR */ = 0.0f;
74 dst[2] /* BL */ = 0.0f;
75 dst[1] /* FR */ = srcFC;
76 dst[0] /* FL */ = srcFC;
77 }
78
79}
80
81static void SDL_ConvertMonoTo41(float *dst, const float *src, int num_frames)
82{
83 int i;
84
85 LOG_DEBUG_AUDIO_CONVERT("mono", "4.1");
86
87 // convert backwards, since output is growing in-place.
88 src += (num_frames-1);
89 dst += (num_frames-1) * 5;
90 for (i = num_frames; i; i--, src--, dst -= 5) {
91 const float srcFC = src[0];
92 dst[4] /* BR */ = 0.0f;
93 dst[3] /* BL */ = 0.0f;
94 dst[2] /* LFE */ = 0.0f;
95 dst[1] /* FR */ = srcFC;
96 dst[0] /* FL */ = srcFC;
97 }
98
99}
100
101static void SDL_ConvertMonoTo51(float *dst, const float *src, int num_frames)
102{
103 int i;
104
105 LOG_DEBUG_AUDIO_CONVERT("mono", "5.1");
106
107 // convert backwards, since output is growing in-place.
108 src += (num_frames-1);
109 dst += (num_frames-1) * 6;
110 for (i = num_frames; i; i--, src--, dst -= 6) {
111 const float srcFC = src[0];
112 dst[5] /* BR */ = 0.0f;
113 dst[4] /* BL */ = 0.0f;
114 dst[3] /* LFE */ = 0.0f;
115 dst[2] /* FC */ = 0.0f;
116 dst[1] /* FR */ = srcFC;
117 dst[0] /* FL */ = srcFC;
118 }
119
120}
121
122static void SDL_ConvertMonoTo61(float *dst, const float *src, int num_frames)
123{
124 int i;
125
126 LOG_DEBUG_AUDIO_CONVERT("mono", "6.1");
127
128 // convert backwards, since output is growing in-place.
129 src += (num_frames-1);
130 dst += (num_frames-1) * 7;
131 for (i = num_frames; i; i--, src--, dst -= 7) {
132 const float srcFC = src[0];
133 dst[6] /* SR */ = 0.0f;
134 dst[5] /* SL */ = 0.0f;
135 dst[4] /* BC */ = 0.0f;
136 dst[3] /* LFE */ = 0.0f;
137 dst[2] /* FC */ = 0.0f;
138 dst[1] /* FR */ = srcFC;
139 dst[0] /* FL */ = srcFC;
140 }
141
142}
143
144static void SDL_ConvertMonoTo71(float *dst, const float *src, int num_frames)
145{
146 int i;
147
148 LOG_DEBUG_AUDIO_CONVERT("mono", "7.1");
149
150 // convert backwards, since output is growing in-place.
151 src += (num_frames-1);
152 dst += (num_frames-1) * 8;
153 for (i = num_frames; i; i--, src--, dst -= 8) {
154 const float srcFC = src[0];
155 dst[7] /* SR */ = 0.0f;
156 dst[6] /* SL */ = 0.0f;
157 dst[5] /* BR */ = 0.0f;
158 dst[4] /* BL */ = 0.0f;
159 dst[3] /* LFE */ = 0.0f;
160 dst[2] /* FC */ = 0.0f;
161 dst[1] /* FR */ = srcFC;
162 dst[0] /* FL */ = srcFC;
163 }
164
165}
166
167static void SDL_ConvertStereoToMono(float *dst, const float *src, int num_frames)
168{
169 int i;
170
171 LOG_DEBUG_AUDIO_CONVERT("stereo", "mono");
172
173 for (i = num_frames; i; i--, src += 2, dst++) {
174 dst[0] /* FC */ = (src[0] * 0.500000000f) + (src[1] * 0.500000000f);
175 }
176
177}
178
179static void SDL_ConvertStereoTo21(float *dst, const float *src, int num_frames)
180{
181 int i;
182
183 LOG_DEBUG_AUDIO_CONVERT("stereo", "2.1");
184
185 // convert backwards, since output is growing in-place.
186 src += (num_frames-1) * 2;
187 dst += (num_frames-1) * 3;
188 for (i = num_frames; i; i--, src -= 2, dst -= 3) {
189 dst[2] /* LFE */ = 0.0f;
190 dst[1] /* FR */ = src[1];
191 dst[0] /* FL */ = src[0];
192 }
193
194}
195
196static void SDL_ConvertStereoToQuad(float *dst, const float *src, int num_frames)
197{
198 int i;
199
200 LOG_DEBUG_AUDIO_CONVERT("stereo", "quad");
201
202 // convert backwards, since output is growing in-place.
203 src += (num_frames-1) * 2;
204 dst += (num_frames-1) * 4;
205 for (i = num_frames; i; i--, src -= 2, dst -= 4) {
206 dst[3] /* BR */ = 0.0f;
207 dst[2] /* BL */ = 0.0f;
208 dst[1] /* FR */ = src[1];
209 dst[0] /* FL */ = src[0];
210 }
211
212}
213
214static void SDL_ConvertStereoTo41(float *dst, const float *src, int num_frames)
215{
216 int i;
217
218 LOG_DEBUG_AUDIO_CONVERT("stereo", "4.1");
219
220 // convert backwards, since output is growing in-place.
221 src += (num_frames-1) * 2;
222 dst += (num_frames-1) * 5;
223 for (i = num_frames; i; i--, src -= 2, dst -= 5) {
224 dst[4] /* BR */ = 0.0f;
225 dst[3] /* BL */ = 0.0f;
226 dst[2] /* LFE */ = 0.0f;
227 dst[1] /* FR */ = src[1];
228 dst[0] /* FL */ = src[0];
229 }
230
231}
232
233static void SDL_ConvertStereoTo51(float *dst, const float *src, int num_frames)
234{
235 int i;
236
237 LOG_DEBUG_AUDIO_CONVERT("stereo", "5.1");
238
239 // convert backwards, since output is growing in-place.
240 src += (num_frames-1) * 2;
241 dst += (num_frames-1) * 6;
242 for (i = num_frames; i; i--, src -= 2, dst -= 6) {
243 dst[5] /* BR */ = 0.0f;
244 dst[4] /* BL */ = 0.0f;
245 dst[3] /* LFE */ = 0.0f;
246 dst[2] /* FC */ = 0.0f;
247 dst[1] /* FR */ = src[1];
248 dst[0] /* FL */ = src[0];
249 }
250
251}
252
253static void SDL_ConvertStereoTo61(float *dst, const float *src, int num_frames)
254{
255 int i;
256
257 LOG_DEBUG_AUDIO_CONVERT("stereo", "6.1");
258
259 // convert backwards, since output is growing in-place.
260 src += (num_frames-1) * 2;
261 dst += (num_frames-1) * 7;
262 for (i = num_frames; i; i--, src -= 2, dst -= 7) {
263 dst[6] /* SR */ = 0.0f;
264 dst[5] /* SL */ = 0.0f;
265 dst[4] /* BC */ = 0.0f;
266 dst[3] /* LFE */ = 0.0f;
267 dst[2] /* FC */ = 0.0f;
268 dst[1] /* FR */ = src[1];
269 dst[0] /* FL */ = src[0];
270 }
271
272}
273
274static void SDL_ConvertStereoTo71(float *dst, const float *src, int num_frames)
275{
276 int i;
277
278 LOG_DEBUG_AUDIO_CONVERT("stereo", "7.1");
279
280 // convert backwards, since output is growing in-place.
281 src += (num_frames-1) * 2;
282 dst += (num_frames-1) * 8;
283 for (i = num_frames; i; i--, src -= 2, dst -= 8) {
284 dst[7] /* SR */ = 0.0f;
285 dst[6] /* SL */ = 0.0f;
286 dst[5] /* BR */ = 0.0f;
287 dst[4] /* BL */ = 0.0f;
288 dst[3] /* LFE */ = 0.0f;
289 dst[2] /* FC */ = 0.0f;
290 dst[1] /* FR */ = src[1];
291 dst[0] /* FL */ = src[0];
292 }
293
294}
295
296static void SDL_Convert21ToMono(float *dst, const float *src, int num_frames)
297{
298 int i;
299
300 LOG_DEBUG_AUDIO_CONVERT("2.1", "mono");
301
302 for (i = num_frames; i; i--, src += 3, dst++) {
303 dst[0] /* FC */ = (src[0] * 0.333333343f) + (src[1] * 0.333333343f) + (src[2] * 0.333333343f);
304 }
305
306}
307
308static void SDL_Convert21ToStereo(float *dst, const float *src, int num_frames)
309{
310 int i;
311
312 LOG_DEBUG_AUDIO_CONVERT("2.1", "stereo");
313
314 for (i = num_frames; i; i--, src += 3, dst += 2) {
315 const float srcLFE = src[2];
316 dst[0] /* FL */ = (src[0] * 0.800000012f) + (srcLFE * 0.200000003f);
317 dst[1] /* FR */ = (src[1] * 0.800000012f) + (srcLFE * 0.200000003f);
318 }
319
320}
321
322static void SDL_Convert21ToQuad(float *dst, const float *src, int num_frames)
323{
324 int i;
325
326 LOG_DEBUG_AUDIO_CONVERT("2.1", "quad");
327
328 // convert backwards, since output is growing in-place.
329 src += (num_frames-1) * 3;
330 dst += (num_frames-1) * 4;
331 for (i = num_frames; i; i--, src -= 3, dst -= 4) {
332 const float srcLFE = src[2];
333 dst[3] /* BR */ = (srcLFE * 0.111111112f);
334 dst[2] /* BL */ = (srcLFE * 0.111111112f);
335 dst[1] /* FR */ = (srcLFE * 0.111111112f) + (src[1] * 0.888888896f);
336 dst[0] /* FL */ = (srcLFE * 0.111111112f) + (src[0] * 0.888888896f);
337 }
338
339}
340
341static void SDL_Convert21To41(float *dst, const float *src, int num_frames)
342{
343 int i;
344
345 LOG_DEBUG_AUDIO_CONVERT("2.1", "4.1");
346
347 // convert backwards, since output is growing in-place.
348 src += (num_frames-1) * 3;
349 dst += (num_frames-1) * 5;
350 for (i = num_frames; i; i--, src -= 3, dst -= 5) {
351 dst[4] /* BR */ = 0.0f;
352 dst[3] /* BL */ = 0.0f;
353 dst[2] /* LFE */ = src[2];
354 dst[1] /* FR */ = src[1];
355 dst[0] /* FL */ = src[0];
356 }
357
358}
359
360static void SDL_Convert21To51(float *dst, const float *src, int num_frames)
361{
362 int i;
363
364 LOG_DEBUG_AUDIO_CONVERT("2.1", "5.1");
365
366 // convert backwards, since output is growing in-place.
367 src += (num_frames-1) * 3;
368 dst += (num_frames-1) * 6;
369 for (i = num_frames; i; i--, src -= 3, dst -= 6) {
370 dst[5] /* BR */ = 0.0f;
371 dst[4] /* BL */ = 0.0f;
372 dst[3] /* LFE */ = src[2];
373 dst[2] /* FC */ = 0.0f;
374 dst[1] /* FR */ = src[1];
375 dst[0] /* FL */ = src[0];
376 }
377
378}
379
380static void SDL_Convert21To61(float *dst, const float *src, int num_frames)
381{
382 int i;
383
384 LOG_DEBUG_AUDIO_CONVERT("2.1", "6.1");
385
386 // convert backwards, since output is growing in-place.
387 src += (num_frames-1) * 3;
388 dst += (num_frames-1) * 7;
389 for (i = num_frames; i; i--, src -= 3, dst -= 7) {
390 dst[6] /* SR */ = 0.0f;
391 dst[5] /* SL */ = 0.0f;
392 dst[4] /* BC */ = 0.0f;
393 dst[3] /* LFE */ = src[2];
394 dst[2] /* FC */ = 0.0f;
395 dst[1] /* FR */ = src[1];
396 dst[0] /* FL */ = src[0];
397 }
398
399}
400
401static void SDL_Convert21To71(float *dst, const float *src, int num_frames)
402{
403 int i;
404
405 LOG_DEBUG_AUDIO_CONVERT("2.1", "7.1");
406
407 // convert backwards, since output is growing in-place.
408 src += (num_frames-1) * 3;
409 dst += (num_frames-1) * 8;
410 for (i = num_frames; i; i--, src -= 3, dst -= 8) {
411 dst[7] /* SR */ = 0.0f;
412 dst[6] /* SL */ = 0.0f;
413 dst[5] /* BR */ = 0.0f;
414 dst[4] /* BL */ = 0.0f;
415 dst[3] /* LFE */ = src[2];
416 dst[2] /* FC */ = 0.0f;
417 dst[1] /* FR */ = src[1];
418 dst[0] /* FL */ = src[0];
419 }
420
421}
422
423static void SDL_ConvertQuadToMono(float *dst, const float *src, int num_frames)
424{
425 int i;
426
427 LOG_DEBUG_AUDIO_CONVERT("quad", "mono");
428
429 for (i = num_frames; i; i--, src += 4, dst++) {
430 dst[0] /* FC */ = (src[0] * 0.250000000f) + (src[1] * 0.250000000f) + (src[2] * 0.250000000f) + (src[3] * 0.250000000f);
431 }
432
433}
434
435static void SDL_ConvertQuadToStereo(float *dst, const float *src, int num_frames)
436{
437 int i;
438
439 LOG_DEBUG_AUDIO_CONVERT("quad", "stereo");
440
441 for (i = num_frames; i; i--, src += 4, dst += 2) {
442 const float srcBL = src[2];
443 const float srcBR = src[3];
444 dst[0] /* FL */ = (src[0] * 0.421000004f) + (srcBL * 0.358999997f) + (srcBR * 0.219999999f);
445 dst[1] /* FR */ = (src[1] * 0.421000004f) + (srcBL * 0.219999999f) + (srcBR * 0.358999997f);
446 }
447
448}
449
450static void SDL_ConvertQuadTo21(float *dst, const float *src, int num_frames)
451{
452 int i;
453
454 LOG_DEBUG_AUDIO_CONVERT("quad", "2.1");
455
456 for (i = num_frames; i; i--, src += 4, dst += 3) {
457 const float srcBL = src[2];
458 const float srcBR = src[3];
459 dst[0] /* FL */ = (src[0] * 0.421000004f) + (srcBL * 0.358999997f) + (srcBR * 0.219999999f);
460 dst[1] /* FR */ = (src[1] * 0.421000004f) + (srcBL * 0.219999999f) + (srcBR * 0.358999997f);
461 dst[2] /* LFE */ = 0.0f;
462 }
463
464}
465
466static void SDL_ConvertQuadTo41(float *dst, const float *src, int num_frames)
467{
468 int i;
469
470 LOG_DEBUG_AUDIO_CONVERT("quad", "4.1");
471
472 // convert backwards, since output is growing in-place.
473 src += (num_frames-1) * 4;
474 dst += (num_frames-1) * 5;
475 for (i = num_frames; i; i--, src -= 4, dst -= 5) {
476 dst[4] /* BR */ = src[3];
477 dst[3] /* BL */ = src[2];
478 dst[2] /* LFE */ = 0.0f;
479 dst[1] /* FR */ = src[1];
480 dst[0] /* FL */ = src[0];
481 }
482
483}
484
485static void SDL_ConvertQuadTo51(float *dst, const float *src, int num_frames)
486{
487 int i;
488
489 LOG_DEBUG_AUDIO_CONVERT("quad", "5.1");
490
491 // convert backwards, since output is growing in-place.
492 src += (num_frames-1) * 4;
493 dst += (num_frames-1) * 6;
494 for (i = num_frames; i; i--, src -= 4, dst -= 6) {
495 dst[5] /* BR */ = src[3];
496 dst[4] /* BL */ = src[2];
497 dst[3] /* LFE */ = 0.0f;
498 dst[2] /* FC */ = 0.0f;
499 dst[1] /* FR */ = src[1];
500 dst[0] /* FL */ = src[0];
501 }
502
503}
504
505static void SDL_ConvertQuadTo61(float *dst, const float *src, int num_frames)
506{
507 int i;
508
509 LOG_DEBUG_AUDIO_CONVERT("quad", "6.1");
510
511 // convert backwards, since output is growing in-place.
512 src += (num_frames-1) * 4;
513 dst += (num_frames-1) * 7;
514 for (i = num_frames; i; i--, src -= 4, dst -= 7) {
515 const float srcBL = src[2];
516 const float srcBR = src[3];
517 dst[6] /* SR */ = (srcBR * 0.796000004f);
518 dst[5] /* SL */ = (srcBL * 0.796000004f);
519 dst[4] /* BC */ = (srcBR * 0.500000000f) + (srcBL * 0.500000000f);
520 dst[3] /* LFE */ = 0.0f;
521 dst[2] /* FC */ = 0.0f;
522 dst[1] /* FR */ = (src[1] * 0.939999998f);
523 dst[0] /* FL */ = (src[0] * 0.939999998f);
524 }
525
526}
527
528static void SDL_ConvertQuadTo71(float *dst, const float *src, int num_frames)
529{
530 int i;
531
532 LOG_DEBUG_AUDIO_CONVERT("quad", "7.1");
533
534 // convert backwards, since output is growing in-place.
535 src += (num_frames-1) * 4;
536 dst += (num_frames-1) * 8;
537 for (i = num_frames; i; i--, src -= 4, dst -= 8) {
538 dst[7] /* SR */ = 0.0f;
539 dst[6] /* SL */ = 0.0f;
540 dst[5] /* BR */ = src[3];
541 dst[4] /* BL */ = src[2];
542 dst[3] /* LFE */ = 0.0f;
543 dst[2] /* FC */ = 0.0f;
544 dst[1] /* FR */ = src[1];
545 dst[0] /* FL */ = src[0];
546 }
547
548}
549
550static void SDL_Convert41ToMono(float *dst, const float *src, int num_frames)
551{
552 int i;
553
554 LOG_DEBUG_AUDIO_CONVERT("4.1", "mono");
555
556 for (i = num_frames; i; i--, src += 5, dst++) {
557 dst[0] /* FC */ = (src[0] * 0.200000003f) + (src[1] * 0.200000003f) + (src[2] * 0.200000003f) + (src[3] * 0.200000003f) + (src[4] * 0.200000003f);
558 }
559
560}
561
562static void SDL_Convert41ToStereo(float *dst, const float *src, int num_frames)
563{
564 int i;
565
566 LOG_DEBUG_AUDIO_CONVERT("4.1", "stereo");
567
568 for (i = num_frames; i; i--, src += 5, dst += 2) {
569 const float srcLFE = src[2];
570 const float srcBL = src[3];
571 const float srcBR = src[4];
572 dst[0] /* FL */ = (src[0] * 0.374222219f) + (srcLFE * 0.111111112f) + (srcBL * 0.319111109f) + (srcBR * 0.195555553f);
573 dst[1] /* FR */ = (src[1] * 0.374222219f) + (srcLFE * 0.111111112f) + (srcBL * 0.195555553f) + (srcBR * 0.319111109f);
574 }
575
576}
577
578static void SDL_Convert41To21(float *dst, const float *src, int num_frames)
579{
580 int i;
581
582 LOG_DEBUG_AUDIO_CONVERT("4.1", "2.1");
583
584 for (i = num_frames; i; i--, src += 5, dst += 3) {
585 const float srcBL = src[3];
586 const float srcBR = src[4];
587 dst[0] /* FL */ = (src[0] * 0.421000004f) + (srcBL * 0.358999997f) + (srcBR * 0.219999999f);
588 dst[1] /* FR */ = (src[1] * 0.421000004f) + (srcBL * 0.219999999f) + (srcBR * 0.358999997f);
589 dst[2] /* LFE */ = src[2];
590 }
591
592}
593
594static void SDL_Convert41ToQuad(float *dst, const float *src, int num_frames)
595{
596 int i;
597
598 LOG_DEBUG_AUDIO_CONVERT("4.1", "quad");
599
600 for (i = num_frames; i; i--, src += 5, dst += 4) {
601 const float srcLFE = src[2];
602 dst[0] /* FL */ = (src[0] * 0.941176474f) + (srcLFE * 0.058823530f);
603 dst[1] /* FR */ = (src[1] * 0.941176474f) + (srcLFE * 0.058823530f);
604 dst[2] /* BL */ = (srcLFE * 0.058823530f) + (src[3] * 0.941176474f);
605 dst[3] /* BR */ = (srcLFE * 0.058823530f) + (src[4] * 0.941176474f);
606 }
607
608}
609
610static void SDL_Convert41To51(float *dst, const float *src, int num_frames)
611{
612 int i;
613
614 LOG_DEBUG_AUDIO_CONVERT("4.1", "5.1");
615
616 // convert backwards, since output is growing in-place.
617 src += (num_frames-1) * 5;
618 dst += (num_frames-1) * 6;
619 for (i = num_frames; i; i--, src -= 5, dst -= 6) {
620 dst[5] /* BR */ = src[4];
621 dst[4] /* BL */ = src[3];
622 dst[3] /* LFE */ = src[2];
623 dst[2] /* FC */ = 0.0f;
624 dst[1] /* FR */ = src[1];
625 dst[0] /* FL */ = src[0];
626 }
627
628}
629
630static void SDL_Convert41To61(float *dst, const float *src, int num_frames)
631{
632 int i;
633
634 LOG_DEBUG_AUDIO_CONVERT("4.1", "6.1");
635
636 // convert backwards, since output is growing in-place.
637 src += (num_frames-1) * 5;
638 dst += (num_frames-1) * 7;
639 for (i = num_frames; i; i--, src -= 5, dst -= 7) {
640 const float srcBL = src[3];
641 const float srcBR = src[4];
642 dst[6] /* SR */ = (srcBR * 0.796000004f);
643 dst[5] /* SL */ = (srcBL * 0.796000004f);
644 dst[4] /* BC */ = (srcBR * 0.500000000f) + (srcBL * 0.500000000f);
645 dst[3] /* LFE */ = src[2];
646 dst[2] /* FC */ = 0.0f;
647 dst[1] /* FR */ = (src[1] * 0.939999998f);
648 dst[0] /* FL */ = (src[0] * 0.939999998f);
649 }
650
651}
652
653static void SDL_Convert41To71(float *dst, const float *src, int num_frames)
654{
655 int i;
656
657 LOG_DEBUG_AUDIO_CONVERT("4.1", "7.1");
658
659 // convert backwards, since output is growing in-place.
660 src += (num_frames-1) * 5;
661 dst += (num_frames-1) * 8;
662 for (i = num_frames; i; i--, src -= 5, dst -= 8) {
663 dst[7] /* SR */ = 0.0f;
664 dst[6] /* SL */ = 0.0f;
665 dst[5] /* BR */ = src[4];
666 dst[4] /* BL */ = src[3];
667 dst[3] /* LFE */ = src[2];
668 dst[2] /* FC */ = 0.0f;
669 dst[1] /* FR */ = src[1];
670 dst[0] /* FL */ = src[0];
671 }
672
673}
674
675static void SDL_Convert51ToMono(float *dst, const float *src, int num_frames)
676{
677 int i;
678
679 LOG_DEBUG_AUDIO_CONVERT("5.1", "mono");
680
681 for (i = num_frames; i; i--, src += 6, dst++) {
682 dst[0] /* FC */ = (src[0] * 0.166666672f) + (src[1] * 0.166666672f) + (src[2] * 0.166666672f) + (src[3] * 0.166666672f) + (src[4] * 0.166666672f) + (src[5] * 0.166666672f);
683 }
684
685}
686
687static void SDL_Convert51ToStereo(float *dst, const float *src, int num_frames)
688{
689 int i;
690
691 LOG_DEBUG_AUDIO_CONVERT("5.1", "stereo");
692
693 for (i = num_frames; i; i--, src += 6, dst += 2) {
694 const float srcFC = src[2];
695 const float srcLFE = src[3];
696 const float srcBL = src[4];
697 const float srcBR = src[5];
698 dst[0] /* FL */ = (src[0] * 0.294545442f) + (srcFC * 0.208181813f) + (srcLFE * 0.090909094f) + (srcBL * 0.251818180f) + (srcBR * 0.154545456f);
699 dst[1] /* FR */ = (src[1] * 0.294545442f) + (srcFC * 0.208181813f) + (srcLFE * 0.090909094f) + (srcBL * 0.154545456f) + (srcBR * 0.251818180f);
700 }
701
702}
703
704static void SDL_Convert51To21(float *dst, const float *src, int num_frames)
705{
706 int i;
707
708 LOG_DEBUG_AUDIO_CONVERT("5.1", "2.1");
709
710 for (i = num_frames; i; i--, src += 6, dst += 3) {
711 const float srcFC = src[2];
712 const float srcBL = src[4];
713 const float srcBR = src[5];
714 dst[0] /* FL */ = (src[0] * 0.324000001f) + (srcFC * 0.229000002f) + (srcBL * 0.277000010f) + (srcBR * 0.170000002f);
715 dst[1] /* FR */ = (src[1] * 0.324000001f) + (srcFC * 0.229000002f) + (srcBL * 0.170000002f) + (srcBR * 0.277000010f);
716 dst[2] /* LFE */ = src[3];
717 }
718
719}
720
721static void SDL_Convert51ToQuad(float *dst, const float *src, int num_frames)
722{
723 int i;
724
725 LOG_DEBUG_AUDIO_CONVERT("5.1", "quad");
726
727 for (i = num_frames; i; i--, src += 6, dst += 4) {
728 const float srcFC = src[2];
729 const float srcLFE = src[3];
730 dst[0] /* FL */ = (src[0] * 0.558095276f) + (srcFC * 0.394285709f) + (srcLFE * 0.047619049f);
731 dst[1] /* FR */ = (src[1] * 0.558095276f) + (srcFC * 0.394285709f) + (srcLFE * 0.047619049f);
732 dst[2] /* BL */ = (srcLFE * 0.047619049f) + (src[4] * 0.558095276f);
733 dst[3] /* BR */ = (srcLFE * 0.047619049f) + (src[5] * 0.558095276f);
734 }
735
736}
737
738static void SDL_Convert51To41(float *dst, const float *src, int num_frames)
739{
740 int i;
741
742 LOG_DEBUG_AUDIO_CONVERT("5.1", "4.1");
743
744 for (i = num_frames; i; i--, src += 6, dst += 5) {
745 const float srcFC = src[2];
746 dst[0] /* FL */ = (src[0] * 0.586000025f) + (srcFC * 0.414000005f);
747 dst[1] /* FR */ = (src[1] * 0.586000025f) + (srcFC * 0.414000005f);
748 dst[2] /* LFE */ = src[3];
749 dst[3] /* BL */ = (src[4] * 0.586000025f);
750 dst[4] /* BR */ = (src[5] * 0.586000025f);
751 }
752
753}
754
755static void SDL_Convert51To61(float *dst, const float *src, int num_frames)
756{
757 int i;
758
759 LOG_DEBUG_AUDIO_CONVERT("5.1", "6.1");
760
761 // convert backwards, since output is growing in-place.
762 src += (num_frames-1) * 6;
763 dst += (num_frames-1) * 7;
764 for (i = num_frames; i; i--, src -= 6, dst -= 7) {
765 const float srcBL = src[4];
766 const float srcBR = src[5];
767 dst[6] /* SR */ = (srcBR * 0.796000004f);
768 dst[5] /* SL */ = (srcBL * 0.796000004f);
769 dst[4] /* BC */ = (srcBR * 0.500000000f) + (srcBL * 0.500000000f);
770 dst[3] /* LFE */ = src[3];
771 dst[2] /* FC */ = (src[2] * 0.939999998f);
772 dst[1] /* FR */ = (src[1] * 0.939999998f);
773 dst[0] /* FL */ = (src[0] * 0.939999998f);
774 }
775
776}
777
778static void SDL_Convert51To71(float *dst, const float *src, int num_frames)
779{
780 int i;
781
782 LOG_DEBUG_AUDIO_CONVERT("5.1", "7.1");
783
784 // convert backwards, since output is growing in-place.
785 src += (num_frames-1) * 6;
786 dst += (num_frames-1) * 8;
787 for (i = num_frames; i; i--, src -= 6, dst -= 8) {
788 dst[7] /* SR */ = 0.0f;
789 dst[6] /* SL */ = 0.0f;
790 dst[5] /* BR */ = src[5];
791 dst[4] /* BL */ = src[4];
792 dst[3] /* LFE */ = src[3];
793 dst[2] /* FC */ = src[2];
794 dst[1] /* FR */ = src[1];
795 dst[0] /* FL */ = src[0];
796 }
797
798}
799
800static void SDL_Convert61ToMono(float *dst, const float *src, int num_frames)
801{
802 int i;
803
804 LOG_DEBUG_AUDIO_CONVERT("6.1", "mono");
805
806 for (i = num_frames; i; i--, src += 7, dst++) {
807 dst[0] /* FC */ = (src[0] * 0.143142849f) + (src[1] * 0.143142849f) + (src[2] * 0.143142849f) + (src[3] * 0.142857149f) + (src[4] * 0.143142849f) + (src[5] * 0.143142849f) + (src[6] * 0.143142849f);
808 }
809
810}
811
812static void SDL_Convert61ToStereo(float *dst, const float *src, int num_frames)
813{
814 int i;
815
816 LOG_DEBUG_AUDIO_CONVERT("6.1", "stereo");
817
818 for (i = num_frames; i; i--, src += 7, dst += 2) {
819 const float srcFC = src[2];
820 const float srcLFE = src[3];
821 const float srcBC = src[4];
822 const float srcSL = src[5];
823 const float srcSR = src[6];
824 dst[0] /* FL */ = (src[0] * 0.247384623f) + (srcFC * 0.174461529f) + (srcLFE * 0.076923080f) + (srcBC * 0.174461529f) + (srcSL * 0.226153851f) + (srcSR * 0.100615382f);
825 dst[1] /* FR */ = (src[1] * 0.247384623f) + (srcFC * 0.174461529f) + (srcLFE * 0.076923080f) + (srcBC * 0.174461529f) + (srcSL * 0.100615382f) + (srcSR * 0.226153851f);
826 }
827
828}
829
830static void SDL_Convert61To21(float *dst, const float *src, int num_frames)
831{
832 int i;
833
834 LOG_DEBUG_AUDIO_CONVERT("6.1", "2.1");
835
836 for (i = num_frames; i; i--, src += 7, dst += 3) {
837 const float srcFC = src[2];
838 const float srcBC = src[4];
839 const float srcSL = src[5];
840 const float srcSR = src[6];
841 dst[0] /* FL */ = (src[0] * 0.268000007f) + (srcFC * 0.188999996f) + (srcBC * 0.188999996f) + (srcSL * 0.245000005f) + (srcSR * 0.108999997f);
842 dst[1] /* FR */ = (src[1] * 0.268000007f) + (srcFC * 0.188999996f) + (srcBC * 0.188999996f) + (srcSL * 0.108999997f) + (srcSR * 0.245000005f);
843 dst[2] /* LFE */ = src[3];
844 }
845
846}
847
848static void SDL_Convert61ToQuad(float *dst, const float *src, int num_frames)
849{
850 int i;
851
852 LOG_DEBUG_AUDIO_CONVERT("6.1", "quad");
853
854 for (i = num_frames; i; i--, src += 7, dst += 4) {
855 const float srcFC = src[2];
856 const float srcLFE = src[3];
857 const float srcBC = src[4];
858 const float srcSL = src[5];
859 const float srcSR = src[6];
860 dst[0] /* FL */ = (src[0] * 0.463679999f) + (srcFC * 0.327360004f) + (srcLFE * 0.040000003f) + (srcSL * 0.168960005f);
861 dst[1] /* FR */ = (src[1] * 0.463679999f) + (srcFC * 0.327360004f) + (srcLFE * 0.040000003f) + (srcSR * 0.168960005f);
862 dst[2] /* BL */ = (srcLFE * 0.040000003f) + (srcBC * 0.327360004f) + (srcSL * 0.431039989f);
863 dst[3] /* BR */ = (srcLFE * 0.040000003f) + (srcBC * 0.327360004f) + (srcSR * 0.431039989f);
864 }
865
866}
867
868static void SDL_Convert61To41(float *dst, const float *src, int num_frames)
869{
870 int i;
871
872 LOG_DEBUG_AUDIO_CONVERT("6.1", "4.1");
873
874 for (i = num_frames; i; i--, src += 7, dst += 5) {
875 const float srcFC = src[2];
876 const float srcBC = src[4];
877 const float srcSL = src[5];
878 const float srcSR = src[6];
879 dst[0] /* FL */ = (src[0] * 0.483000010f) + (srcFC * 0.340999991f) + (srcSL * 0.175999999f);
880 dst[1] /* FR */ = (src[1] * 0.483000010f) + (srcFC * 0.340999991f) + (srcSR * 0.175999999f);
881 dst[2] /* LFE */ = src[3];
882 dst[3] /* BL */ = (srcBC * 0.340999991f) + (srcSL * 0.449000001f);
883 dst[4] /* BR */ = (srcBC * 0.340999991f) + (srcSR * 0.449000001f);
884 }
885
886}
887
888static void SDL_Convert61To51(float *dst, const float *src, int num_frames)
889{
890 int i;
891
892 LOG_DEBUG_AUDIO_CONVERT("6.1", "5.1");
893
894 for (i = num_frames; i; i--, src += 7, dst += 6) {
895 const float srcBC = src[4];
896 const float srcSL = src[5];
897 const float srcSR = src[6];
898 dst[0] /* FL */ = (src[0] * 0.611000001f) + (srcSL * 0.223000005f);
899 dst[1] /* FR */ = (src[1] * 0.611000001f) + (srcSR * 0.223000005f);
900 dst[2] /* FC */ = (src[2] * 0.611000001f);
901 dst[3] /* LFE */ = src[3];
902 dst[4] /* BL */ = (srcBC * 0.432000011f) + (srcSL * 0.568000019f);
903 dst[5] /* BR */ = (srcBC * 0.432000011f) + (srcSR * 0.568000019f);
904 }
905
906}
907
908static void SDL_Convert61To71(float *dst, const float *src, int num_frames)
909{
910 int i;
911
912 LOG_DEBUG_AUDIO_CONVERT("6.1", "7.1");
913
914 // convert backwards, since output is growing in-place.
915 src += (num_frames-1) * 7;
916 dst += (num_frames-1) * 8;
917 for (i = num_frames; i; i--, src -= 7, dst -= 8) {
918 const float srcBC = src[4];
919 dst[7] /* SR */ = src[6];
920 dst[6] /* SL */ = src[5];
921 dst[5] /* BR */ = (srcBC * 0.707000017f);
922 dst[4] /* BL */ = (srcBC * 0.707000017f);
923 dst[3] /* LFE */ = src[3];
924 dst[2] /* FC */ = src[2];
925 dst[1] /* FR */ = src[1];
926 dst[0] /* FL */ = src[0];
927 }
928
929}
930
931static void SDL_Convert71ToMono(float *dst, const float *src, int num_frames)
932{
933 int i;
934
935 LOG_DEBUG_AUDIO_CONVERT("7.1", "mono");
936
937 for (i = num_frames; i; i--, src += 8, dst++) {
938 dst[0] /* FC */ = (src[0] * 0.125125006f) + (src[1] * 0.125125006f) + (src[2] * 0.125125006f) + (src[3] * 0.125000000f) + (src[4] * 0.125125006f) + (src[5] * 0.125125006f) + (src[6] * 0.125125006f) + (src[7] * 0.125125006f);
939 }
940
941}
942
943static void SDL_Convert71ToStereo(float *dst, const float *src, int num_frames)
944{
945 int i;
946
947 LOG_DEBUG_AUDIO_CONVERT("7.1", "stereo");
948
949 for (i = num_frames; i; i--, src += 8, dst += 2) {
950 const float srcFC = src[2];
951 const float srcLFE = src[3];
952 const float srcBL = src[4];
953 const float srcBR = src[5];
954 const float srcSL = src[6];
955 const float srcSR = src[7];
956 dst[0] /* FL */ = (src[0] * 0.211866662f) + (srcFC * 0.150266662f) + (srcLFE * 0.066666670f) + (srcBL * 0.181066677f) + (srcBR * 0.111066669f) + (srcSL * 0.194133341f) + (srcSR * 0.085866667f);
957 dst[1] /* FR */ = (src[1] * 0.211866662f) + (srcFC * 0.150266662f) + (srcLFE * 0.066666670f) + (srcBL * 0.111066669f) + (srcBR * 0.181066677f) + (srcSL * 0.085866667f) + (srcSR * 0.194133341f);
958 }
959
960}
961
962static void SDL_Convert71To21(float *dst, const float *src, int num_frames)
963{
964 int i;
965
966 LOG_DEBUG_AUDIO_CONVERT("7.1", "2.1");
967
968 for (i = num_frames; i; i--, src += 8, dst += 3) {
969 const float srcFC = src[2];
970 const float srcBL = src[4];
971 const float srcBR = src[5];
972 const float srcSL = src[6];
973 const float srcSR = src[7];
974 dst[0] /* FL */ = (src[0] * 0.226999998f) + (srcFC * 0.160999998f) + (srcBL * 0.194000006f) + (srcBR * 0.119000003f) + (srcSL * 0.208000004f) + (srcSR * 0.092000000f);
975 dst[1] /* FR */ = (src[1] * 0.226999998f) + (srcFC * 0.160999998f) + (srcBL * 0.119000003f) + (srcBR * 0.194000006f) + (srcSL * 0.092000000f) + (srcSR * 0.208000004f);
976 dst[2] /* LFE */ = src[3];
977 }
978
979}
980
981static void SDL_Convert71ToQuad(float *dst, const float *src, int num_frames)
982{
983 int i;
984
985 LOG_DEBUG_AUDIO_CONVERT("7.1", "quad");
986
987 for (i = num_frames; i; i--, src += 8, dst += 4) {
988 const float srcFC = src[2];
989 const float srcLFE = src[3];
990 const float srcSL = src[6];
991 const float srcSR = src[7];
992 dst[0] /* FL */ = (src[0] * 0.466344833f) + (srcFC * 0.329241365f) + (srcLFE * 0.034482758f) + (srcSL * 0.169931039f);
993 dst[1] /* FR */ = (src[1] * 0.466344833f) + (srcFC * 0.329241365f) + (srcLFE * 0.034482758f) + (srcSR * 0.169931039f);
994 dst[2] /* BL */ = (srcLFE * 0.034482758f) + (src[4] * 0.466344833f) + (srcSL * 0.433517247f);
995 dst[3] /* BR */ = (srcLFE * 0.034482758f) + (src[5] * 0.466344833f) + (srcSR * 0.433517247f);
996 }
997
998}
999
1000static void SDL_Convert71To41(float *dst, const float *src, int num_frames)
1001{
1002 int i;
1003
1004 LOG_DEBUG_AUDIO_CONVERT("7.1", "4.1");
1005
1006 for (i = num_frames; i; i--, src += 8, dst += 5) {
1007 const float srcFC = src[2];
1008 const float srcSL = src[6];
1009 const float srcSR = src[7];
1010 dst[0] /* FL */ = (src[0] * 0.483000010f) + (srcFC * 0.340999991f) + (srcSL * 0.175999999f);
1011 dst[1] /* FR */ = (src[1] * 0.483000010f) + (srcFC * 0.340999991f) + (srcSR * 0.175999999f);
1012 dst[2] /* LFE */ = src[3];
1013 dst[3] /* BL */ = (src[4] * 0.483000010f) + (srcSL * 0.449000001f);
1014 dst[4] /* BR */ = (src[5] * 0.483000010f) + (srcSR * 0.449000001f);
1015 }
1016
1017}
1018
1019static void SDL_Convert71To51(float *dst, const float *src, int num_frames)
1020{
1021 int i;
1022
1023 LOG_DEBUG_AUDIO_CONVERT("7.1", "5.1");
1024
1025 for (i = num_frames; i; i--, src += 8, dst += 6) {
1026 const float srcSL = src[6];
1027 const float srcSR = src[7];
1028 dst[0] /* FL */ = (src[0] * 0.518000007f) + (srcSL * 0.188999996f);
1029 dst[1] /* FR */ = (src[1] * 0.518000007f) + (srcSR * 0.188999996f);
1030 dst[2] /* FC */ = (src[2] * 0.518000007f);
1031 dst[3] /* LFE */ = src[3];
1032 dst[4] /* BL */ = (src[4] * 0.518000007f) + (srcSL * 0.481999993f);
1033 dst[5] /* BR */ = (src[5] * 0.518000007f) + (srcSR * 0.481999993f);
1034 }
1035
1036}
1037
1038static void SDL_Convert71To61(float *dst, const float *src, int num_frames)
1039{
1040 int i;
1041
1042 LOG_DEBUG_AUDIO_CONVERT("7.1", "6.1");
1043
1044 for (i = num_frames; i; i--, src += 8, dst += 7) {
1045 const float srcBL = src[4];
1046 const float srcBR = src[5];
1047 dst[0] /* FL */ = (src[0] * 0.541000009f);
1048 dst[1] /* FR */ = (src[1] * 0.541000009f);
1049 dst[2] /* FC */ = (src[2] * 0.541000009f);
1050 dst[3] /* LFE */ = src[3];
1051 dst[4] /* BC */ = (srcBL * 0.287999988f) + (srcBR * 0.287999988f);
1052 dst[5] /* SL */ = (srcBL * 0.458999991f) + (src[6] * 0.541000009f);
1053 dst[6] /* SR */ = (srcBR * 0.458999991f) + (src[7] * 0.541000009f);
1054 }
1055
1056}
1057
1058static const SDL_AudioChannelConverter channel_converters[8][8] = { // [from][to]
1059 { NULL, SDL_ConvertMonoToStereo, SDL_ConvertMonoTo21, SDL_ConvertMonoToQuad, SDL_ConvertMonoTo41, SDL_ConvertMonoTo51, SDL_ConvertMonoTo61, SDL_ConvertMonoTo71 },
1060 { SDL_ConvertStereoToMono, NULL, SDL_ConvertStereoTo21, SDL_ConvertStereoToQuad, SDL_ConvertStereoTo41, SDL_ConvertStereoTo51, SDL_ConvertStereoTo61, SDL_ConvertStereoTo71 },
1061 { SDL_Convert21ToMono, SDL_Convert21ToStereo, NULL, SDL_Convert21ToQuad, SDL_Convert21To41, SDL_Convert21To51, SDL_Convert21To61, SDL_Convert21To71 },
1062 { SDL_ConvertQuadToMono, SDL_ConvertQuadToStereo, SDL_ConvertQuadTo21, NULL, SDL_ConvertQuadTo41, SDL_ConvertQuadTo51, SDL_ConvertQuadTo61, SDL_ConvertQuadTo71 },
1063 { SDL_Convert41ToMono, SDL_Convert41ToStereo, SDL_Convert41To21, SDL_Convert41ToQuad, NULL, SDL_Convert41To51, SDL_Convert41To61, SDL_Convert41To71 },
1064 { SDL_Convert51ToMono, SDL_Convert51ToStereo, SDL_Convert51To21, SDL_Convert51ToQuad, SDL_Convert51To41, NULL, SDL_Convert51To61, SDL_Convert51To71 },
1065 { SDL_Convert61ToMono, SDL_Convert61ToStereo, SDL_Convert61To21, SDL_Convert61ToQuad, SDL_Convert61To41, SDL_Convert61To51, NULL, SDL_Convert61To71 },
1066 { SDL_Convert71ToMono, SDL_Convert71ToStereo, SDL_Convert71To21, SDL_Convert71ToQuad, SDL_Convert71To41, SDL_Convert71To51, SDL_Convert71To61, NULL }
1067};
1068
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audiocvt.c b/contrib/SDL-3.2.8/src/audio/SDL_audiocvt.c
new file mode 100644
index 0000000..f751b0e
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audiocvt.c
@@ -0,0 +1,1381 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_sysaudio.h"
24
25#include "SDL_audioqueue.h"
26#include "SDL_audioresample.h"
27
28#ifndef SDL_INT_MAX
29#define SDL_INT_MAX ((int)(~0u>>1))
30#endif
31
32#ifdef SDL_SSE3_INTRINSICS
33// Convert from stereo to mono. Average left and right.
34static void SDL_TARGETING("sse3") SDL_ConvertStereoToMono_SSE3(float *dst, const float *src, int num_frames)
35{
36 LOG_DEBUG_AUDIO_CONVERT("stereo", "mono (using SSE3)");
37
38 const __m128 divby2 = _mm_set1_ps(0.5f);
39 int i = num_frames;
40
41 /* Do SSE blocks as long as we have 16 bytes available.
42 Just use unaligned load/stores, if the memory at runtime is
43 aligned it'll be just as fast on modern processors */
44 while (i >= 4) { // 4 * float32
45 _mm_storeu_ps(dst, _mm_mul_ps(_mm_hadd_ps(_mm_loadu_ps(src), _mm_loadu_ps(src + 4)), divby2));
46 i -= 4;
47 src += 8;
48 dst += 4;
49 }
50
51 // Finish off any leftovers with scalar operations.
52 while (i) {
53 *dst = (src[0] + src[1]) * 0.5f;
54 dst++;
55 i--;
56 src += 2;
57 }
58}
59#endif
60
61#ifdef SDL_SSE_INTRINSICS
62// Convert from mono to stereo. Duplicate to stereo left and right.
63static void SDL_TARGETING("sse") SDL_ConvertMonoToStereo_SSE(float *dst, const float *src, int num_frames)
64{
65 LOG_DEBUG_AUDIO_CONVERT("mono", "stereo (using SSE)");
66
67 // convert backwards, since output is growing in-place.
68 src += (num_frames-4) * 1;
69 dst += (num_frames-4) * 2;
70
71 /* Do SSE blocks as long as we have 16 bytes available.
72 Just use unaligned load/stores, if the memory at runtime is
73 aligned it'll be just as fast on modern processors */
74 // convert backwards, since output is growing in-place.
75 int i = num_frames;
76 while (i >= 4) { // 4 * float32
77 const __m128 input = _mm_loadu_ps(src); // A B C D
78 _mm_storeu_ps(dst, _mm_unpacklo_ps(input, input)); // A A B B
79 _mm_storeu_ps(dst + 4, _mm_unpackhi_ps(input, input)); // C C D D
80 i -= 4;
81 src -= 4;
82 dst -= 8;
83 }
84
85 // Finish off any leftovers with scalar operations.
86 src += 3;
87 dst += 6; // adjust for smaller buffers.
88 while (i) { // convert backwards, since output is growing in-place.
89 const float srcFC = src[0];
90 dst[1] /* FR */ = srcFC;
91 dst[0] /* FL */ = srcFC;
92 i--;
93 src--;
94 dst -= 2;
95 }
96}
97#endif
98
99// Include the autogenerated channel converters...
100#include "SDL_audio_channel_converters.h"
101
102static bool SDL_IsSupportedAudioFormat(const SDL_AudioFormat fmt)
103{
104 switch (fmt) {
105 case SDL_AUDIO_U8:
106 case SDL_AUDIO_S8:
107 case SDL_AUDIO_S16LE:
108 case SDL_AUDIO_S16BE:
109 case SDL_AUDIO_S32LE:
110 case SDL_AUDIO_S32BE:
111 case SDL_AUDIO_F32LE:
112 case SDL_AUDIO_F32BE:
113 return true; // supported.
114
115 default:
116 break;
117 }
118
119 return false; // unsupported.
120}
121
122static bool SDL_IsSupportedChannelCount(const int channels)
123{
124 return ((channels >= 1) && (channels <= 8));
125}
126
127bool SDL_ChannelMapIsBogus(const int *chmap, int channels)
128{
129 if (chmap) {
130 for (int i = 0; i < channels; i++) {
131 const int mapping = chmap[i];
132 if ((mapping < -1) || (mapping >= channels)) {
133 return true;
134 }
135 }
136 }
137 return false;
138}
139
140bool SDL_ChannelMapIsDefault(const int *chmap, int channels)
141{
142 if (chmap) {
143 for (int i = 0; i < channels; i++) {
144 if (chmap[i] != i) {
145 return false;
146 }
147 }
148 }
149 return true;
150}
151
152// Swizzle audio channels. src and dst can be the same pointer. It does not change the buffer size.
153static void SwizzleAudio(const int num_frames, void *dst, const void *src, int channels, const int *map, SDL_AudioFormat fmt)
154{
155 const int bitsize = (int) SDL_AUDIO_BITSIZE(fmt);
156
157 bool has_null_mappings = false; // !!! FIXME: calculate this when setting the channel map instead.
158 for (int i = 0; i < channels; i++) {
159 if (map[i] == -1) {
160 has_null_mappings = true;
161 break;
162 }
163 }
164
165 #define CHANNEL_SWIZZLE(bits) { \
166 Uint##bits *tdst = (Uint##bits *) dst; /* treat as UintX; we only care about moving bits and not the type here. */ \
167 const Uint##bits *tsrc = (const Uint##bits *) src; \
168 if (src != dst) { /* don't need to copy to a temporary frame first. */ \
169 if (has_null_mappings) { \
170 const Uint##bits silence = (Uint##bits) SDL_GetSilenceValueForFormat(fmt); \
171 for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
172 for (int ch = 0; ch < channels; ch++) { \
173 const int m = map[ch]; \
174 tdst[ch] = (m == -1) ? silence : tsrc[m]; \
175 } \
176 } \
177 } else { \
178 for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
179 for (int ch = 0; ch < channels; ch++) { \
180 tdst[ch] = tsrc[map[ch]]; \
181 } \
182 } \
183 } \
184 } else { \
185 bool isstack; \
186 Uint##bits *tmp = (Uint##bits *) SDL_small_alloc(int, channels, &isstack); /* !!! FIXME: allocate this when setting the channel map instead. */ \
187 if (tmp) { \
188 if (has_null_mappings) { \
189 const Uint##bits silence = (Uint##bits) SDL_GetSilenceValueForFormat(fmt); \
190 for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
191 for (int ch = 0; ch < channels; ch++) { \
192 const int m = map[ch]; \
193 tmp[ch] = (m == -1) ? silence : tsrc[m]; \
194 } \
195 for (int ch = 0; ch < channels; ch++) { \
196 tdst[ch] = tmp[ch]; \
197 } \
198 } \
199 } else { \
200 for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
201 for (int ch = 0; ch < channels; ch++) { \
202 tmp[ch] = tsrc[map[ch]]; \
203 } \
204 for (int ch = 0; ch < channels; ch++) { \
205 tdst[ch] = tmp[ch]; \
206 } \
207 } \
208 } \
209 SDL_small_free(tmp, isstack); \
210 } \
211 } \
212 }
213
214 switch (bitsize) {
215 case 8: CHANNEL_SWIZZLE(8); break;
216 case 16: CHANNEL_SWIZZLE(16); break;
217 case 32: CHANNEL_SWIZZLE(32); break;
218 // we don't currently have int64 or double audio datatypes, so no `case 64` for now.
219 default: SDL_assert(!"Unsupported audio datatype size"); break;
220 }
221
222 #undef CHANNEL_SWIZZLE
223}
224
225
226// This does type and channel conversions _but not resampling_ (resampling happens in SDL_AudioStream).
227// This does not check parameter validity, (beyond asserts), it expects you did that already!
228// All of this has to function as if src==dst==scratch (conversion in-place), but as a convenience
229// if you're just going to copy the final output elsewhere, you can specify a different output pointer.
230//
231// The scratch buffer must be able to store `num_frames * CalculateMaxSampleFrameSize(src_format, src_channels, dst_format, dst_channels)` bytes.
232// If the scratch buffer is NULL, this restriction applies to the output buffer instead.
233//
234// Since this is a convenient point that audio goes through even if it doesn't need format conversion,
235// we also handle gain adjustment here, so we don't have to make another pass over the data later.
236// Strictly speaking, this is also a "conversion". :)
237void ConvertAudio(int num_frames,
238 const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
239 void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
240 void *scratch, float gain)
241{
242 SDL_assert(src != NULL);
243 SDL_assert(dst != NULL);
244 SDL_assert(SDL_IsSupportedAudioFormat(src_format));
245 SDL_assert(SDL_IsSupportedAudioFormat(dst_format));
246 SDL_assert(SDL_IsSupportedChannelCount(src_channels));
247 SDL_assert(SDL_IsSupportedChannelCount(dst_channels));
248
249 if (!num_frames) {
250 return; // no data to convert, quit.
251 }
252
253#if DEBUG_AUDIO_CONVERT
254 SDL_Log("SDL_AUDIO_CONVERT: Convert format %04x->%04x, channels %u->%u", src_format, dst_format, src_channels, dst_channels);
255#endif
256
257 const int dst_bitsize = (int) SDL_AUDIO_BITSIZE(dst_format);
258 const int dst_sample_frame_size = (dst_bitsize / 8) * dst_channels;
259
260 const bool chmaps_match = (src_channels == dst_channels) && SDL_AudioChannelMapsEqual(src_channels, src_map, dst_map);
261 if (chmaps_match) {
262 src_map = dst_map = NULL; // NULL both these out so we don't do any unnecessary swizzling.
263 }
264
265 /* Type conversion goes like this now:
266 - swizzle through source channel map to "standard" layout.
267 - byteswap to CPU native format first if necessary.
268 - convert to native Float32 if necessary.
269 - change channel count if necessary.
270 - convert to final data format.
271 - byteswap back to foreign format if necessary.
272 - swizzle through dest channel map from "standard" layout.
273
274 The expectation is we can process data faster in float32
275 (possibly with SIMD), and making several passes over the same
276 buffer is likely to be CPU cache-friendly, avoiding the
277 biggest performance hit in modern times. Previously we had
278 (script-generated) custom converters for every data type and
279 it was a bloat on SDL compile times and final library size. */
280
281 // swizzle input to "standard" format if necessary.
282 if (src_map) {
283 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.
284 SwizzleAudio(num_frames, buf, src, src_channels, src_map, src_format);
285 src = buf;
286 }
287
288 // see if we can skip float conversion entirely.
289 if ((src_channels == dst_channels) && (gain == 1.0f)) {
290 if (src_format == dst_format) {
291 // nothing to do, we're already in the right format, just copy it over if necessary.
292 if (dst_map) {
293 SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_format);
294 } else if (src != dst) {
295 SDL_memcpy(dst, src, num_frames * dst_sample_frame_size);
296 }
297 return;
298 }
299
300 // just a byteswap needed?
301 if ((src_format ^ dst_format) == SDL_AUDIO_MASK_BIG_ENDIAN) {
302 if (dst_map) { // do this first, in case we duplicate channels, we can avoid an extra copy if src != dst.
303 SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_format);
304 src = dst;
305 }
306 ConvertAudioSwapEndian(dst, src, num_frames * dst_channels, dst_bitsize);
307 return; // all done.
308 }
309 }
310
311 if (!scratch) {
312 scratch = dst;
313 }
314
315 const bool srcconvert = src_format != SDL_AUDIO_F32;
316 const bool channelconvert = src_channels != dst_channels;
317 const bool dstconvert = dst_format != SDL_AUDIO_F32;
318
319 // get us to float format.
320 if (srcconvert) {
321 void* buf = (channelconvert || dstconvert) ? scratch : dst;
322 ConvertAudioToFloat((float *) buf, src, num_frames * src_channels, src_format);
323 src = buf;
324 }
325
326 // Gain adjustment
327 if (gain != 1.0f) {
328 float *buf = (float *)((channelconvert || dstconvert) ? scratch : dst);
329 const int total_samples = num_frames * src_channels;
330 if (src == buf) {
331 for (int i = 0; i < total_samples; i++) {
332 buf[i] *= gain;
333 }
334 } else {
335 float *fsrc = (float *)src;
336 for (int i = 0; i < total_samples; i++) {
337 buf[i] = fsrc[i] * gain;
338 }
339 }
340 src = buf;
341 }
342
343 // Channel conversion
344
345 if (channelconvert) {
346 SDL_AudioChannelConverter channel_converter;
347 SDL_AudioChannelConverter override = NULL;
348
349 // SDL_IsSupportedChannelCount should have caught these asserts, or we added a new format and forgot to update the table.
350 SDL_assert(src_channels <= SDL_arraysize(channel_converters));
351 SDL_assert(dst_channels <= SDL_arraysize(channel_converters[0]));
352
353 channel_converter = channel_converters[src_channels - 1][dst_channels - 1];
354 SDL_assert(channel_converter != NULL);
355
356 // swap in some SIMD versions for a few of these.
357 if (channel_converter == SDL_ConvertStereoToMono) {
358 #ifdef SDL_SSE3_INTRINSICS
359 if (!override && SDL_HasSSE3()) { override = SDL_ConvertStereoToMono_SSE3; }
360 #endif
361 } else if (channel_converter == SDL_ConvertMonoToStereo) {
362 #ifdef SDL_SSE_INTRINSICS
363 if (!override && SDL_HasSSE()) { override = SDL_ConvertMonoToStereo_SSE; }
364 #endif
365 }
366
367 if (override) {
368 channel_converter = override;
369 }
370
371 void* buf = dstconvert ? scratch : dst;
372 channel_converter((float *) buf, (const float *) src, num_frames);
373 src = buf;
374 }
375
376 // Resampling is not done in here. SDL_AudioStream handles that.
377
378 // Move to final data type.
379 if (dstconvert) {
380 ConvertAudioFromFloat(dst, (const float *) src, num_frames * dst_channels, dst_format);
381 src = dst;
382 }
383
384 SDL_assert(src == dst); // if we got here, we _had_ to have done _something_. Otherwise, we should have memcpy'd!
385
386 if (dst_map) {
387 SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_format);
388 }
389}
390
391// Calculate the largest frame size needed to convert between the two formats.
392static int CalculateMaxFrameSize(SDL_AudioFormat src_format, int src_channels, SDL_AudioFormat dst_format, int dst_channels)
393{
394 const int src_format_size = SDL_AUDIO_BYTESIZE(src_format);
395 const int dst_format_size = SDL_AUDIO_BYTESIZE(dst_format);
396 const int max_app_format_size = SDL_max(src_format_size, dst_format_size);
397 const int max_format_size = SDL_max(max_app_format_size, sizeof (float)); // ConvertAudio and ResampleAudio use floats.
398 const int max_channels = SDL_max(src_channels, dst_channels);
399 return max_format_size * max_channels;
400}
401
402static Sint64 GetAudioStreamResampleRate(SDL_AudioStream* stream, int src_freq, Sint64 resample_offset)
403{
404 src_freq = (int)((float)src_freq * stream->freq_ratio);
405
406 Sint64 resample_rate = SDL_GetResampleRate(src_freq, stream->dst_spec.freq);
407
408 // If src_freq == dst_freq, and we aren't between frames, don't resample
409 if ((resample_rate == 0x100000000) && (resample_offset == 0)) {
410 resample_rate = 0;
411 }
412
413 return resample_rate;
414}
415
416static bool UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSpec *spec, const int *chmap)
417{
418 if (SDL_AudioSpecsEqual(&stream->input_spec, spec, stream->input_chmap, chmap)) {
419 return true;
420 }
421
422 if (!SDL_ResetAudioQueueHistory(stream->queue, SDL_GetResamplerHistoryFrames())) {
423 return false;
424 }
425
426 if (!chmap) {
427 stream->input_chmap = NULL;
428 } else {
429 const size_t chmaplen = sizeof (*chmap) * spec->channels;
430 stream->input_chmap = stream->input_chmap_storage;
431 SDL_memcpy(stream->input_chmap, chmap, chmaplen);
432 }
433
434 SDL_copyp(&stream->input_spec, spec);
435
436 return true;
437}
438
439SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)
440{
441 SDL_ChooseAudioConverters();
442 SDL_SetupAudioResampler();
443
444 SDL_AudioStream *result = (SDL_AudioStream *)SDL_calloc(1, sizeof(SDL_AudioStream));
445 if (!result) {
446 return NULL;
447 }
448
449 result->freq_ratio = 1.0f;
450 result->gain = 1.0f;
451 result->queue = SDL_CreateAudioQueue(8192);
452
453 if (!result->queue) {
454 SDL_free(result);
455 return NULL;
456 }
457
458 result->lock = SDL_CreateMutex();
459 if (!result->lock) {
460 SDL_free(result->queue);
461 SDL_free(result);
462 return NULL;
463 }
464
465 OnAudioStreamCreated(result);
466
467 if (!SDL_SetAudioStreamFormat(result, src_spec, dst_spec)) {
468 SDL_DestroyAudioStream(result);
469 return NULL;
470 }
471
472 return result;
473}
474
475SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream)
476{
477 if (!stream) {
478 SDL_InvalidParamError("stream");
479 return 0;
480 }
481 SDL_LockMutex(stream->lock);
482 if (stream->props == 0) {
483 stream->props = SDL_CreateProperties();
484 }
485 SDL_UnlockMutex(stream->lock);
486 return stream->props;
487}
488
489bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)
490{
491 if (!stream) {
492 return SDL_InvalidParamError("stream");
493 }
494 SDL_LockMutex(stream->lock);
495 stream->get_callback = callback;
496 stream->get_callback_userdata = userdata;
497 SDL_UnlockMutex(stream->lock);
498 return true;
499}
500
501bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)
502{
503 if (!stream) {
504 return SDL_InvalidParamError("stream");
505 }
506 SDL_LockMutex(stream->lock);
507 stream->put_callback = callback;
508 stream->put_callback_userdata = userdata;
509 SDL_UnlockMutex(stream->lock);
510 return true;
511}
512
513bool SDL_LockAudioStream(SDL_AudioStream *stream)
514{
515 if (!stream) {
516 return SDL_InvalidParamError("stream");
517 }
518 SDL_LockMutex(stream->lock);
519 return true;
520}
521
522bool SDL_UnlockAudioStream(SDL_AudioStream *stream)
523{
524 if (!stream) {
525 return SDL_InvalidParamError("stream");
526 }
527 SDL_UnlockMutex(stream->lock);
528 return true;
529}
530
531bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec)
532{
533 if (!stream) {
534 return SDL_InvalidParamError("stream");
535 }
536
537 SDL_LockMutex(stream->lock);
538 if (src_spec) {
539 SDL_copyp(src_spec, &stream->src_spec);
540 }
541 if (dst_spec) {
542 SDL_copyp(dst_spec, &stream->dst_spec);
543 }
544 SDL_UnlockMutex(stream->lock);
545
546 if (src_spec && src_spec->format == 0) {
547 return SDL_SetError("Stream has no source format");
548 } else if (dst_spec && dst_spec->format == 0) {
549 return SDL_SetError("Stream has no destination format");
550 }
551
552 return true;
553}
554
555bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)
556{
557 if (!stream) {
558 return SDL_InvalidParamError("stream");
559 }
560
561 // note that while we've removed the maximum frequency checks, SDL _will_
562 // fail to resample to extremely high sample rates correctly. Really high,
563 // like 196608000Hz. File a bug. :P
564
565 if (src_spec) {
566 if (!SDL_IsSupportedAudioFormat(src_spec->format)) {
567 return SDL_InvalidParamError("src_spec->format");
568 } else if (!SDL_IsSupportedChannelCount(src_spec->channels)) {
569 return SDL_InvalidParamError("src_spec->channels");
570 } else if (src_spec->freq <= 0) {
571 return SDL_InvalidParamError("src_spec->freq");
572 }
573 }
574
575 if (dst_spec) {
576 if (!SDL_IsSupportedAudioFormat(dst_spec->format)) {
577 return SDL_InvalidParamError("dst_spec->format");
578 } else if (!SDL_IsSupportedChannelCount(dst_spec->channels)) {
579 return SDL_InvalidParamError("dst_spec->channels");
580 } else if (dst_spec->freq <= 0) {
581 return SDL_InvalidParamError("dst_spec->freq");
582 }
583 }
584
585 SDL_LockMutex(stream->lock);
586
587 // quietly refuse to change the format of the end currently bound to a device.
588 if (stream->bound_device) {
589 if (stream->bound_device->physical_device->recording) {
590 src_spec = NULL;
591 } else {
592 dst_spec = NULL;
593 }
594 }
595
596 if (src_spec) {
597 if (src_spec->channels != stream->src_spec.channels) {
598 SDL_free(stream->src_chmap);
599 stream->src_chmap = NULL;
600 }
601 SDL_copyp(&stream->src_spec, src_spec);
602 }
603
604 if (dst_spec) {
605 if (dst_spec->channels != stream->dst_spec.channels) {
606 SDL_free(stream->dst_chmap);
607 stream->dst_chmap = NULL;
608 }
609 SDL_copyp(&stream->dst_spec, dst_spec);
610 }
611
612 SDL_UnlockMutex(stream->lock);
613
614 return true;
615}
616
617bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput)
618{
619 if (!stream) {
620 return SDL_InvalidParamError("stream");
621 }
622
623 bool result = true;
624
625 SDL_LockMutex(stream->lock);
626
627 if (channels != spec->channels) {
628 result = SDL_SetError("Wrong number of channels");
629 } else if (!*stream_chmap && !chmap) {
630 // already at default, we're good.
631 } else if (*stream_chmap && chmap && (SDL_memcmp(*stream_chmap, chmap, sizeof (*chmap) * channels) == 0)) {
632 // already have this map, don't allocate/copy it again.
633 } else if (SDL_ChannelMapIsBogus(chmap, channels)) {
634 result = SDL_SetError("Invalid channel mapping");
635 } else {
636 if (SDL_ChannelMapIsDefault(chmap, channels)) {
637 chmap = NULL; // just apply a default mapping.
638 }
639 if (chmap) {
640 int *dupmap = SDL_ChannelMapDup(chmap, channels);
641 if (!dupmap) {
642 result = SDL_SetError("Invalid channel mapping");
643 } else {
644 SDL_free(*stream_chmap);
645 *stream_chmap = dupmap;
646 }
647 } else {
648 SDL_free(*stream_chmap);
649 *stream_chmap = NULL;
650 }
651 }
652
653 SDL_UnlockMutex(stream->lock);
654 return result;
655}
656
657bool SDL_SetAudioStreamInputChannelMap(SDL_AudioStream *stream, const int *chmap, int channels)
658{
659 return SetAudioStreamChannelMap(stream, &stream->src_spec, &stream->src_chmap, chmap, channels, 1);
660}
661
662bool SDL_SetAudioStreamOutputChannelMap(SDL_AudioStream *stream, const int *chmap, int channels)
663{
664 return SetAudioStreamChannelMap(stream, &stream->dst_spec, &stream->dst_chmap, chmap, channels, 0);
665}
666
667int *SDL_GetAudioStreamInputChannelMap(SDL_AudioStream *stream, int *count)
668{
669 int *result = NULL;
670 int channels = 0;
671 if (stream) {
672 SDL_LockMutex(stream->lock);
673 channels = stream->src_spec.channels;
674 result = SDL_ChannelMapDup(stream->src_chmap, channels);
675 SDL_UnlockMutex(stream->lock);
676 }
677
678 if (count) {
679 *count = channels;
680 }
681
682 return result;
683}
684
685int *SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count)
686{
687 int *result = NULL;
688 int channels = 0;
689 if (stream) {
690 SDL_LockMutex(stream->lock);
691 channels = stream->dst_spec.channels;
692 result = SDL_ChannelMapDup(stream->dst_chmap, channels);
693 SDL_UnlockMutex(stream->lock);
694 }
695
696 if (count) {
697 *count = channels;
698 }
699
700 return result;
701}
702
703float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream)
704{
705 if (!stream) {
706 SDL_InvalidParamError("stream");
707 return 0.0f;
708 }
709
710 SDL_LockMutex(stream->lock);
711 const float freq_ratio = stream->freq_ratio;
712 SDL_UnlockMutex(stream->lock);
713
714 return freq_ratio;
715}
716
717bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio)
718{
719 if (!stream) {
720 return SDL_InvalidParamError("stream");
721 }
722
723 // Picked mostly arbitrarily.
724 const float min_freq_ratio = 0.01f;
725 const float max_freq_ratio = 100.0f;
726
727 if (freq_ratio < min_freq_ratio) {
728 return SDL_SetError("Frequency ratio is too low");
729 } else if (freq_ratio > max_freq_ratio) {
730 return SDL_SetError("Frequency ratio is too high");
731 }
732
733 SDL_LockMutex(stream->lock);
734 stream->freq_ratio = freq_ratio;
735 SDL_UnlockMutex(stream->lock);
736
737 return true;
738}
739
740float SDL_GetAudioStreamGain(SDL_AudioStream *stream)
741{
742 if (!stream) {
743 SDL_InvalidParamError("stream");
744 return -1.0f;
745 }
746
747 SDL_LockMutex(stream->lock);
748 const float gain = stream->gain;
749 SDL_UnlockMutex(stream->lock);
750
751 return gain;
752}
753
754bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain)
755{
756 if (!stream) {
757 return SDL_InvalidParamError("stream");
758 } else if (gain < 0.0f) {
759 return SDL_InvalidParamError("gain");
760 }
761
762 SDL_LockMutex(stream->lock);
763 stream->gain = gain;
764 SDL_UnlockMutex(stream->lock);
765
766 return true;
767}
768
769static bool CheckAudioStreamIsFullySetup(SDL_AudioStream *stream)
770{
771 if (stream->src_spec.format == 0) {
772 return SDL_SetError("Stream has no source format");
773 } else if (stream->dst_spec.format == 0) {
774 return SDL_SetError("Stream has no destination format");
775 }
776
777 return true;
778}
779
780static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata)
781{
782#if DEBUG_AUDIOSTREAM
783 SDL_Log("AUDIOSTREAM: wants to put %d bytes", len);
784#endif
785
786 SDL_LockMutex(stream->lock);
787
788 if (!CheckAudioStreamIsFullySetup(stream)) {
789 SDL_UnlockMutex(stream->lock);
790 return false;
791 }
792
793 if ((len % SDL_AUDIO_FRAMESIZE(stream->src_spec)) != 0) {
794 SDL_UnlockMutex(stream->lock);
795 return SDL_SetError("Can't add partial sample frames");
796 }
797
798 SDL_AudioTrack* track = NULL;
799
800 if (callback) {
801 track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, stream->src_chmap, (Uint8 *)buf, len, len, callback, userdata);
802
803 if (!track) {
804 SDL_UnlockMutex(stream->lock);
805 return false;
806 }
807 }
808
809 const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
810
811 bool result = true;
812
813 if (track) {
814 SDL_AddTrackToAudioQueue(stream->queue, track);
815 } else {
816 result = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, stream->src_chmap, (const Uint8 *)buf, len);
817 }
818
819 if (result) {
820 if (stream->put_callback) {
821 const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
822 stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail);
823 }
824 }
825
826 SDL_UnlockMutex(stream->lock);
827
828 return result;
829}
830
831static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len)
832{
833 SDL_free((void*) buf);
834}
835
836bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
837{
838 if (!stream) {
839 return SDL_InvalidParamError("stream");
840 } else if (!buf) {
841 return SDL_InvalidParamError("buf");
842 } else if (len < 0) {
843 return SDL_InvalidParamError("len");
844 } else if (len == 0) {
845 return true; // nothing to do.
846 }
847
848 // When copying in large amounts of data, try and do as much work as possible
849 // outside of the stream lock, otherwise the output device is likely to be starved.
850 const int large_input_thresh = 64 * 1024;
851
852 if (len >= large_input_thresh) {
853 void *data = SDL_malloc(len);
854
855 if (!data) {
856 return false;
857 }
858
859 SDL_memcpy(data, buf, len);
860 buf = data;
861
862 bool ret = PutAudioStreamBuffer(stream, buf, len, FreeAllocatedAudioBuffer, NULL);
863 if (!ret) {
864 SDL_free(data);
865 }
866 return ret;
867 }
868
869 return PutAudioStreamBuffer(stream, buf, len, NULL, NULL);
870}
871
872bool SDL_FlushAudioStream(SDL_AudioStream *stream)
873{
874 if (!stream) {
875 return SDL_InvalidParamError("stream");
876 }
877
878 SDL_LockMutex(stream->lock);
879 SDL_FlushAudioQueue(stream->queue);
880 SDL_UnlockMutex(stream->lock);
881
882 return true;
883}
884
885/* this does not save the previous contents of stream->work_buffer. It's a work buffer!!
886 The returned buffer is aligned/padded for use with SIMD instructions. */
887static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen)
888{
889 if (stream->work_buffer_allocation >= newlen) {
890 return stream->work_buffer;
891 }
892
893 Uint8 *ptr = (Uint8 *) SDL_aligned_alloc(SDL_GetSIMDAlignment(), newlen);
894 if (!ptr) {
895 return NULL; // previous work buffer is still valid!
896 }
897
898 SDL_aligned_free(stream->work_buffer);
899 stream->work_buffer = ptr;
900 stream->work_buffer_allocation = newlen;
901 return ptr;
902}
903
904static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter,
905 Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed)
906{
907 SDL_AudioSpec spec;
908 bool flushed;
909 int *chmap;
910 size_t queued_bytes = SDL_NextAudioQueueIter(stream->queue, inout_iter, &spec, &chmap, &flushed);
911
912 if (out_spec) {
913 SDL_copyp(out_spec, &spec);
914 }
915
916 if (out_chmap) {
917 *out_chmap = chmap;
918 }
919
920 // There is infinite audio available, whether or not we are resampling
921 if (queued_bytes == SDL_SIZE_MAX) {
922 *inout_resample_offset = 0;
923
924 if (out_flushed) {
925 *out_flushed = false;
926 }
927
928 return SDL_MAX_SINT32;
929 }
930
931 Sint64 resample_offset = *inout_resample_offset;
932 Sint64 resample_rate = GetAudioStreamResampleRate(stream, spec.freq, resample_offset);
933 Sint64 output_frames = (Sint64)(queued_bytes / SDL_AUDIO_FRAMESIZE(spec));
934
935 if (resample_rate) {
936 // Resampling requires padding frames to the left and right of the current position.
937 // Past the end of the track, the right padding is filled with silence.
938 // But we only want to do that if the track is actually finished (flushed).
939 if (!flushed) {
940 output_frames -= SDL_GetResamplerPaddingFrames(resample_rate);
941 }
942
943 output_frames = SDL_GetResamplerOutputFrames(output_frames, resample_rate, &resample_offset);
944 }
945
946 if (flushed) {
947 resample_offset = 0;
948 }
949
950 *inout_resample_offset = resample_offset;
951
952 if (out_flushed) {
953 *out_flushed = flushed;
954 }
955
956 return output_frames;
957}
958
959static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out_resample_offset)
960{
961 void* iter = SDL_BeginAudioQueueIter(stream->queue);
962
963 Sint64 resample_offset = stream->resample_offset;
964 Sint64 output_frames = 0;
965
966 while (iter) {
967 output_frames += NextAudioStreamIter(stream, &iter, &resample_offset, NULL, NULL, NULL);
968
969 // Already got loads of frames. Just clamp it to something reasonable
970 if (output_frames >= SDL_MAX_SINT32) {
971 output_frames = SDL_MAX_SINT32;
972 break;
973 }
974 }
975
976 if (out_resample_offset) {
977 *out_resample_offset = resample_offset;
978 }
979
980 return output_frames;
981}
982
983static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed)
984{
985 void* iter = SDL_BeginAudioQueueIter(stream->queue);
986
987 if (!iter) {
988 SDL_zerop(out_spec);
989 *out_flushed = false;
990 return 0;
991 }
992
993 Sint64 resample_offset = stream->resample_offset;
994 return NextAudioStreamIter(stream, &iter, &resample_offset, out_spec, out_chmap, out_flushed);
995}
996
997// You must hold stream->lock and validate your parameters before calling this!
998// Enough input data MUST be available!
999static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int output_frames, float gain)
1000{
1001 const SDL_AudioSpec* src_spec = &stream->input_spec;
1002 const SDL_AudioSpec* dst_spec = &stream->dst_spec;
1003
1004 const SDL_AudioFormat src_format = src_spec->format;
1005 const int src_channels = src_spec->channels;
1006
1007 const SDL_AudioFormat dst_format = dst_spec->format;
1008 const int dst_channels = dst_spec->channels;
1009 const int *dst_map = stream->dst_chmap;
1010
1011 const int max_frame_size = CalculateMaxFrameSize(src_format, src_channels, dst_format, dst_channels);
1012 const Sint64 resample_rate = GetAudioStreamResampleRate(stream, src_spec->freq, stream->resample_offset);
1013
1014#if DEBUG_AUDIOSTREAM
1015 SDL_Log("AUDIOSTREAM: asking for %d frames.", output_frames);
1016#endif
1017
1018 SDL_assert(output_frames > 0);
1019
1020 // Not resampling? It's an easy conversion (and maybe not even that!)
1021 if (resample_rate == 0) {
1022 Uint8* work_buffer = NULL;
1023
1024 // Ensure we have enough scratch space for any conversions
1025 if ((src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f)) {
1026 work_buffer = EnsureAudioStreamWorkBufferSize(stream, output_frames * max_frame_size);
1027
1028 if (!work_buffer) {
1029 return false;
1030 }
1031 }
1032
1033 if (SDL_ReadFromAudioQueue(stream->queue, (Uint8 *)buf, dst_format, dst_channels, dst_map, 0, output_frames, 0, work_buffer, gain) != buf) {
1034 return SDL_SetError("Not enough data in queue");
1035 }
1036
1037 return true;
1038 }
1039
1040 // Time to do some resampling!
1041 // Calculate the number of input frames necessary for this request.
1042 // Because resampling happens "between" frames, The same number of output_frames
1043 // can require a different number of input_frames, depending on the resample_offset.
1044 // In fact, input_frames can sometimes even be zero when upsampling.
1045 const int input_frames = (int) SDL_GetResamplerInputFrames(output_frames, resample_rate, stream->resample_offset);
1046
1047 const int padding_frames = SDL_GetResamplerPaddingFrames(resample_rate);
1048
1049 const SDL_AudioFormat resample_format = SDL_AUDIO_F32;
1050
1051 // If increasing channels, do it after resampling, since we'd just
1052 // do more work to resample duplicate channels. If we're decreasing, do
1053 // it first so we resample the interpolated data instead of interpolating
1054 // the resampled data.
1055 const int resample_channels = SDL_min(src_channels, dst_channels);
1056
1057 // The size of the frame used when resampling
1058 const int resample_frame_size = SDL_AUDIO_BYTESIZE(resample_format) * resample_channels;
1059
1060 // The main portion of the work_buffer can be used to store 3 things:
1061 // src_sample_frame_size * (left_padding+input_buffer+right_padding)
1062 // resample_frame_size * (left_padding+input_buffer+right_padding)
1063 // dst_sample_frame_size * output_frames
1064 //
1065 // ResampleAudio also requires an additional buffer if it can't write straight to the output:
1066 // resample_frame_size * output_frames
1067 //
1068 // Note, ConvertAudio requires (num_frames * max_sample_frame_size) of scratch space
1069 const int work_buffer_frames = input_frames + (padding_frames * 2);
1070 int work_buffer_capacity = work_buffer_frames * max_frame_size;
1071 int resample_buffer_offset = -1;
1072
1073 // Check if we can resample directly into the output buffer.
1074 // Note, this is just to avoid extra copies.
1075 // Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer.
1076 if ((dst_format != resample_format) || (dst_channels != resample_channels)) {
1077 // Allocate space for converting the resampled output to the destination format
1078 int resample_convert_bytes = output_frames * max_frame_size;
1079 work_buffer_capacity = SDL_max(work_buffer_capacity, resample_convert_bytes);
1080
1081 // SIMD-align the buffer
1082 int simd_alignment = (int) SDL_GetSIMDAlignment();
1083 work_buffer_capacity += simd_alignment - 1;
1084 work_buffer_capacity -= work_buffer_capacity % simd_alignment;
1085
1086 // Allocate space for the resampled output
1087 int resample_bytes = output_frames * resample_frame_size;
1088 resample_buffer_offset = work_buffer_capacity;
1089 work_buffer_capacity += resample_bytes;
1090 }
1091
1092 Uint8* work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity);
1093
1094 if (!work_buffer) {
1095 return false;
1096 }
1097
1098 // adjust gain either before resampling or after, depending on which point has less
1099 // samples to process.
1100 const float preresample_gain = (input_frames > output_frames) ? 1.0f : gain;
1101 const float postresample_gain = (input_frames > output_frames) ? gain : 1.0f;
1102
1103 // (dst channel map is NULL because we'll do the final swizzle on ConvertAudio after resample.)
1104 const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue,
1105 NULL, resample_format, resample_channels, NULL,
1106 padding_frames, input_frames, padding_frames, work_buffer, preresample_gain);
1107
1108 if (!input_buffer) {
1109 return SDL_SetError("Not enough data in queue (resample)");
1110 }
1111
1112 input_buffer += padding_frames * resample_frame_size;
1113
1114 // Decide where the resampled output goes
1115 void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf;
1116
1117 SDL_ResampleAudio(resample_channels,
1118 (const float *) input_buffer, input_frames,
1119 (float*) resample_buffer, output_frames,
1120 resample_rate, &stream->resample_offset);
1121
1122 // Convert to the final format, if necessary (src channel map is NULL because SDL_ReadFromAudioQueue already handled this).
1123 ConvertAudio(output_frames, resample_buffer, resample_format, resample_channels, NULL, buf, dst_format, dst_channels, dst_map, work_buffer, postresample_gain);
1124
1125 return true;
1126}
1127
1128// get converted/resampled data from the stream
1129int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int len, float extra_gain)
1130{
1131 Uint8 *buf = (Uint8 *) voidbuf;
1132
1133#if DEBUG_AUDIOSTREAM
1134 SDL_Log("AUDIOSTREAM: want to get %d converted bytes", len);
1135#endif
1136
1137 if (!stream) {
1138 SDL_InvalidParamError("stream");
1139 return -1;
1140 } else if (!buf) {
1141 SDL_InvalidParamError("buf");
1142 return -1;
1143 } else if (len < 0) {
1144 SDL_InvalidParamError("len");
1145 return -1;
1146 } else if (len == 0) {
1147 return 0; // nothing to do.
1148 }
1149
1150 SDL_LockMutex(stream->lock);
1151
1152 if (!CheckAudioStreamIsFullySetup(stream)) {
1153 SDL_UnlockMutex(stream->lock);
1154 return -1;
1155 }
1156
1157 const float gain = stream->gain * extra_gain;
1158 const int dst_frame_size = SDL_AUDIO_FRAMESIZE(stream->dst_spec);
1159
1160 len -= len % dst_frame_size; // chop off any fractional sample frame.
1161
1162 // give the callback a chance to fill in more stream data if it wants.
1163 if (stream->get_callback) {
1164 Sint64 total_request = len / dst_frame_size; // start with sample frames desired
1165 Sint64 additional_request = total_request;
1166
1167 Sint64 resample_offset = 0;
1168 Sint64 available_frames = GetAudioStreamAvailableFrames(stream, &resample_offset);
1169
1170 additional_request -= SDL_min(additional_request, available_frames);
1171
1172 Sint64 resample_rate = GetAudioStreamResampleRate(stream, stream->src_spec.freq, resample_offset);
1173
1174 if (resample_rate) {
1175 total_request = SDL_GetResamplerInputFrames(total_request, resample_rate, resample_offset);
1176 additional_request = SDL_GetResamplerInputFrames(additional_request, resample_rate, resample_offset);
1177 }
1178
1179 total_request *= SDL_AUDIO_FRAMESIZE(stream->src_spec); // convert sample frames to bytes.
1180 additional_request *= SDL_AUDIO_FRAMESIZE(stream->src_spec); // convert sample frames to bytes.
1181 stream->get_callback(stream->get_callback_userdata, stream, (int) SDL_min(additional_request, SDL_INT_MAX), (int) SDL_min(total_request, SDL_INT_MAX));
1182 }
1183
1184 // Process the data in chunks to avoid allocating too much memory (and potential integer overflows)
1185 const int chunk_size = 4096;
1186
1187 int total = 0;
1188
1189 while (total < len) {
1190 // Audio is processed a track at a time.
1191 SDL_AudioSpec input_spec;
1192 int *input_chmap;
1193 bool flushed;
1194 const Sint64 available_frames = GetAudioStreamHead(stream, &input_spec, &input_chmap, &flushed);
1195
1196 if (available_frames == 0) {
1197 if (flushed) {
1198 SDL_PopAudioQueueHead(stream->queue);
1199 SDL_zero(stream->input_spec);
1200 stream->resample_offset = 0;
1201 stream->input_chmap = NULL;
1202 continue;
1203 }
1204 // There are no frames available, but the track hasn't been flushed, so more might be added later.
1205 break;
1206 }
1207
1208 if (!UpdateAudioStreamInputSpec(stream, &input_spec, input_chmap)) {
1209 total = total ? total : -1;
1210 break;
1211 }
1212
1213 // Clamp the output length to the maximum currently available.
1214 // GetAudioStreamDataInternal requires enough input data is available.
1215 int output_frames = (len - total) / dst_frame_size;
1216 output_frames = SDL_min(output_frames, chunk_size);
1217 output_frames = (int) SDL_min(output_frames, available_frames);
1218
1219 if (!GetAudioStreamDataInternal(stream, &buf[total], output_frames, gain)) {
1220 total = total ? total : -1;
1221 break;
1222 }
1223
1224 total += output_frames * dst_frame_size;
1225 }
1226
1227 SDL_UnlockMutex(stream->lock);
1228
1229#if DEBUG_AUDIOSTREAM
1230 SDL_Log("AUDIOSTREAM: Final result was %d", total);
1231#endif
1232
1233 return total;
1234}
1235
1236int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len)
1237{
1238 return SDL_GetAudioStreamDataAdjustGain(stream, voidbuf, len, 1.0f);
1239}
1240
1241// number of converted/resampled bytes available for output
1242int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
1243{
1244 if (!stream) {
1245 SDL_InvalidParamError("stream");
1246 return -1;
1247 }
1248
1249 SDL_LockMutex(stream->lock);
1250
1251 if (!CheckAudioStreamIsFullySetup(stream)) {
1252 SDL_UnlockMutex(stream->lock);
1253 return 0;
1254 }
1255
1256 Sint64 count = GetAudioStreamAvailableFrames(stream, NULL);
1257
1258 // convert from sample frames to bytes in destination format.
1259 count *= SDL_AUDIO_FRAMESIZE(stream->dst_spec);
1260
1261 SDL_UnlockMutex(stream->lock);
1262
1263 // if this overflows an int, just clamp it to a maximum.
1264 return (int) SDL_min(count, SDL_INT_MAX);
1265}
1266
1267// number of sample frames that are currently queued as input.
1268int SDL_GetAudioStreamQueued(SDL_AudioStream *stream)
1269{
1270 if (!stream) {
1271 SDL_InvalidParamError("stream");
1272 return -1;
1273 }
1274
1275 SDL_LockMutex(stream->lock);
1276
1277 size_t total = SDL_GetAudioQueueQueued(stream->queue);
1278
1279 SDL_UnlockMutex(stream->lock);
1280
1281 // if this overflows an int, just clamp it to a maximum.
1282 return (int) SDL_min(total, SDL_INT_MAX);
1283}
1284
1285bool SDL_ClearAudioStream(SDL_AudioStream *stream)
1286{
1287 if (!stream) {
1288 return SDL_InvalidParamError("stream");
1289 }
1290
1291 SDL_LockMutex(stream->lock);
1292
1293 SDL_ClearAudioQueue(stream->queue);
1294 SDL_zero(stream->input_spec);
1295 stream->input_chmap = NULL;
1296 stream->resample_offset = 0;
1297
1298 SDL_UnlockMutex(stream->lock);
1299 return true;
1300}
1301
1302void SDL_DestroyAudioStream(SDL_AudioStream *stream)
1303{
1304 if (!stream) {
1305 return;
1306 }
1307
1308 SDL_DestroyProperties(stream->props);
1309
1310 OnAudioStreamDestroy(stream);
1311
1312 const bool simplified = stream->simplified;
1313 if (simplified) {
1314 if (stream->bound_device) {
1315 SDL_assert(stream->bound_device->simplified);
1316 SDL_CloseAudioDevice(stream->bound_device->instance_id); // this will unbind the stream.
1317 }
1318 } else {
1319 SDL_UnbindAudioStream(stream);
1320 }
1321
1322 SDL_aligned_free(stream->work_buffer);
1323 SDL_DestroyAudioQueue(stream->queue);
1324 SDL_DestroyMutex(stream->lock);
1325
1326 SDL_free(stream);
1327}
1328
1329static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len)
1330{
1331 // We don't own the buffer, but know it will outlive the stream
1332}
1333
1334bool 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)
1335{
1336 if (dst_data) {
1337 *dst_data = NULL;
1338 }
1339
1340 if (dst_len) {
1341 *dst_len = 0;
1342 }
1343
1344 if (!src_data) {
1345 return SDL_InvalidParamError("src_data");
1346 } else if (src_len < 0) {
1347 return SDL_InvalidParamError("src_len");
1348 } else if (!dst_data) {
1349 return SDL_InvalidParamError("dst_data");
1350 } else if (!dst_len) {
1351 return SDL_InvalidParamError("dst_len");
1352 }
1353
1354 bool result = false;
1355 Uint8 *dst = NULL;
1356 int dstlen = 0;
1357
1358 SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec);
1359 if (stream) {
1360 if (PutAudioStreamBuffer(stream, src_data, src_len, DontFreeThisAudioBuffer, NULL) &&
1361 SDL_FlushAudioStream(stream)) {
1362 dstlen = SDL_GetAudioStreamAvailable(stream);
1363 if (dstlen >= 0) {
1364 dst = (Uint8 *)SDL_malloc(dstlen);
1365 if (dst) {
1366 result = (SDL_GetAudioStreamData(stream, dst, dstlen) == dstlen);
1367 }
1368 }
1369 }
1370 }
1371
1372 if (result) {
1373 *dst_data = dst;
1374 *dst_len = dstlen;
1375 } else {
1376 SDL_free(dst);
1377 }
1378
1379 SDL_DestroyAudioStream(stream);
1380 return result;
1381}
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audiodev.c b/contrib/SDL-3.2.8/src/audio/SDL_audiodev.c
new file mode 100644
index 0000000..623a380
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audiodev.c
@@ -0,0 +1,124 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// Get the name of the audio device we use for output
24
25#if defined(SDL_AUDIO_DRIVER_NETBSD) || defined(SDL_AUDIO_DRIVER_OSS)
26
27#include <fcntl.h>
28#include <sys/types.h>
29#include <sys/stat.h>
30#include <unistd.h> // For close()
31
32#include "SDL_audiodev_c.h"
33
34#ifndef SDL_PATH_DEV_DSP
35#if defined(SDL_PLATFORM_NETBSD) || defined(SDL_PLATFORM_OPENBSD)
36#define SDL_PATH_DEV_DSP "/dev/audio"
37#else
38#define SDL_PATH_DEV_DSP "/dev/dsp"
39#endif
40#endif
41#ifndef SDL_PATH_DEV_DSP24
42#define SDL_PATH_DEV_DSP24 "/dev/sound/dsp"
43#endif
44#ifndef SDL_PATH_DEV_AUDIO
45#define SDL_PATH_DEV_AUDIO "/dev/audio"
46#endif
47
48static void test_device(const bool recording, const char *fname, int flags, bool (*test)(int fd))
49{
50 struct stat sb;
51 const int audio_fd = open(fname, flags | O_CLOEXEC, 0);
52 if (audio_fd >= 0) {
53 if ((fstat(audio_fd, &sb) == 0) && (S_ISCHR(sb.st_mode))) {
54 const bool okay = test(audio_fd);
55 close(audio_fd);
56 if (okay) {
57 static size_t dummyhandle = 0;
58 dummyhandle++;
59 SDL_assert(dummyhandle != 0);
60
61 /* Note that spec is NULL; while we are opening the device
62 * endpoint here, the endpoint does not provide any mix format
63 * information, making this information inaccessible at
64 * enumeration time
65 */
66 SDL_AddAudioDevice(recording, fname, NULL, (void *)(uintptr_t)dummyhandle);
67 }
68 } else {
69 close(audio_fd);
70 }
71 }
72}
73
74static bool test_stub(int fd)
75{
76 return true;
77}
78
79static void SDL_EnumUnixAudioDevices_Internal(const bool recording, const bool classic, bool (*test)(int))
80{
81 const int flags = recording ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT;
82 const char *audiodev;
83 char audiopath[1024];
84
85 if (!test) {
86 test = test_stub;
87 }
88
89 // Figure out what our audio device is
90 audiodev = SDL_getenv("AUDIODEV");
91 if (!audiodev) {
92 if (classic) {
93 audiodev = SDL_PATH_DEV_AUDIO;
94 } else {
95 struct stat sb;
96
97 // Added support for /dev/sound/\* in Linux 2.4
98 if (((stat("/dev/sound", &sb) == 0) && S_ISDIR(sb.st_mode)) && ((stat(SDL_PATH_DEV_DSP24, &sb) == 0) && S_ISCHR(sb.st_mode))) {
99 audiodev = SDL_PATH_DEV_DSP24;
100 } else {
101 audiodev = SDL_PATH_DEV_DSP;
102 }
103 }
104 }
105 test_device(recording, audiodev, flags, test);
106
107 if (SDL_strlen(audiodev) < (sizeof(audiopath) - 3)) {
108 int instance = 0;
109 while (instance <= 64) {
110 (void)SDL_snprintf(audiopath, SDL_arraysize(audiopath),
111 "%s%d", audiodev, instance);
112 instance++;
113 test_device(recording, audiopath, flags, test);
114 }
115 }
116}
117
118void SDL_EnumUnixAudioDevices(const bool classic, bool (*test)(int))
119{
120 SDL_EnumUnixAudioDevices_Internal(true, classic, test);
121 SDL_EnumUnixAudioDevices_Internal(false, classic, test);
122}
123
124#endif // Audio device selection
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audiodev_c.h b/contrib/SDL-3.2.8/src/audio/SDL_audiodev_c.h
new file mode 100644
index 0000000..ded13fc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audiodev_c.h
@@ -0,0 +1,41 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_audiodev_c_h_
23#define SDL_audiodev_c_h_
24
25#include "SDL_internal.h"
26#include "SDL_sysaudio.h"
27
28// Open the audio device for playback, and don't block if busy
29//#define USE_BLOCKING_WRITES
30
31#ifdef USE_BLOCKING_WRITES
32#define OPEN_FLAGS_OUTPUT O_WRONLY
33#define OPEN_FLAGS_INPUT O_RDONLY
34#else
35#define OPEN_FLAGS_OUTPUT (O_WRONLY | O_NONBLOCK)
36#define OPEN_FLAGS_INPUT (O_RDONLY | O_NONBLOCK)
37#endif
38
39extern void SDL_EnumUnixAudioDevices(const bool classic, bool (*test)(int));
40
41#endif // SDL_audiodev_c_h_
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audioqueue.c b/contrib/SDL-3.2.8/src/audio/SDL_audioqueue.c
new file mode 100644
index 0000000..df7cac8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audioqueue.c
@@ -0,0 +1,652 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_audioqueue.h"
24#include "SDL_sysaudio.h"
25
26typedef struct SDL_MemoryPool SDL_MemoryPool;
27
28struct SDL_MemoryPool
29{
30 void *free_blocks;
31 size_t block_size;
32 size_t num_free;
33 size_t max_free;
34};
35
36struct SDL_AudioTrack
37{
38 SDL_AudioSpec spec;
39 int *chmap;
40 bool flushed;
41 SDL_AudioTrack *next;
42
43 void *userdata;
44 SDL_ReleaseAudioBufferCallback callback;
45
46 Uint8 *data;
47 size_t head;
48 size_t tail;
49 size_t capacity;
50
51 int chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
52};
53
54struct SDL_AudioQueue
55{
56 SDL_AudioTrack *head;
57 SDL_AudioTrack *tail;
58
59 Uint8 *history_buffer;
60 size_t history_length;
61 size_t history_capacity;
62
63 SDL_MemoryPool track_pool;
64 SDL_MemoryPool chunk_pool;
65};
66
67// Allocate a new block, avoiding checking for ones already in the pool
68static void *AllocNewMemoryPoolBlock(const SDL_MemoryPool *pool)
69{
70 return SDL_malloc(pool->block_size);
71}
72
73// Allocate a new block, first checking if there are any in the pool
74static void *AllocMemoryPoolBlock(SDL_MemoryPool *pool)
75{
76 if (pool->num_free == 0) {
77 return AllocNewMemoryPoolBlock(pool);
78 }
79
80 void *block = pool->free_blocks;
81 pool->free_blocks = *(void **)block;
82 --pool->num_free;
83 return block;
84}
85
86// Free a block, or add it to the pool if there's room
87static void FreeMemoryPoolBlock(SDL_MemoryPool *pool, void *block)
88{
89 if (pool->num_free < pool->max_free) {
90 *(void **)block = pool->free_blocks;
91 pool->free_blocks = block;
92 ++pool->num_free;
93 } else {
94 SDL_free(block);
95 }
96}
97
98// Destroy a pool and all of its blocks
99static void DestroyMemoryPool(SDL_MemoryPool *pool)
100{
101 void *block = pool->free_blocks;
102 pool->free_blocks = NULL;
103 pool->num_free = 0;
104
105 while (block) {
106 void *next = *(void **)block;
107 SDL_free(block);
108 block = next;
109 }
110}
111
112// Keeping a list of free chunks reduces memory allocations,
113// But also increases the amount of work to perform when freeing the track.
114static void InitMemoryPool(SDL_MemoryPool *pool, size_t block_size, size_t max_free)
115{
116 SDL_zerop(pool);
117
118 SDL_assert(block_size >= sizeof(void *));
119 pool->block_size = block_size;
120 pool->max_free = max_free;
121}
122
123// Allocates a number of blocks and adds them to the pool
124static bool ReserveMemoryPoolBlocks(SDL_MemoryPool *pool, size_t num_blocks)
125{
126 for (; num_blocks; --num_blocks) {
127 void *block = AllocNewMemoryPoolBlock(pool);
128
129 if (block == NULL) {
130 return false;
131 }
132
133 *(void **)block = pool->free_blocks;
134 pool->free_blocks = block;
135 ++pool->num_free;
136 }
137
138 return true;
139}
140
141void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
142{
143 SDL_ClearAudioQueue(queue);
144
145 DestroyMemoryPool(&queue->track_pool);
146 DestroyMemoryPool(&queue->chunk_pool);
147 SDL_aligned_free(queue->history_buffer);
148
149 SDL_free(queue);
150}
151
152SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
153{
154 SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
155
156 if (!queue) {
157 return NULL;
158 }
159
160 InitMemoryPool(&queue->track_pool, sizeof(SDL_AudioTrack), 8);
161 InitMemoryPool(&queue->chunk_pool, chunk_size, 4);
162
163 if (!ReserveMemoryPoolBlocks(&queue->track_pool, 2)) {
164 SDL_DestroyAudioQueue(queue);
165 return NULL;
166 }
167
168 return queue;
169}
170
171static void DestroyAudioTrack(SDL_AudioQueue *queue, SDL_AudioTrack *track)
172{
173 track->callback(track->userdata, track->data, (int)track->capacity);
174
175 FreeMemoryPoolBlock(&queue->track_pool, track);
176}
177
178void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
179{
180 SDL_AudioTrack *track = queue->head;
181
182 queue->head = NULL;
183 queue->tail = NULL;
184 queue->history_length = 0;
185
186 while (track) {
187 SDL_AudioTrack *next = track->next;
188 DestroyAudioTrack(queue, track);
189 track = next;
190 }
191}
192
193static void FlushAudioTrack(SDL_AudioTrack *track)
194{
195 track->flushed = true;
196}
197
198void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
199{
200 SDL_AudioTrack *track = queue->tail;
201
202 if (track) {
203 FlushAudioTrack(track);
204 }
205}
206
207void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
208{
209 SDL_AudioTrack *track = queue->head;
210
211 for (;;) {
212 bool flushed = track->flushed;
213
214 SDL_AudioTrack *next = track->next;
215 DestroyAudioTrack(queue, track);
216 track = next;
217
218 if (flushed) {
219 break;
220 }
221 }
222
223 queue->head = track;
224 queue->history_length = 0;
225
226 if (!track) {
227 queue->tail = NULL;
228 }
229}
230
231SDL_AudioTrack *SDL_CreateAudioTrack(
232 SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap,
233 Uint8 *data, size_t len, size_t capacity,
234 SDL_ReleaseAudioBufferCallback callback, void *userdata)
235{
236 SDL_AudioTrack *track = (SDL_AudioTrack *)AllocMemoryPoolBlock(&queue->track_pool);
237
238 if (!track) {
239 return NULL;
240 }
241
242 SDL_zerop(track);
243
244 if (chmap) {
245 SDL_assert(SDL_arraysize(track->chmap_storage) >= spec->channels);
246 SDL_memcpy(track->chmap_storage, chmap, sizeof (*chmap) * spec->channels);
247 track->chmap = track->chmap_storage;
248 }
249
250 SDL_copyp(&track->spec, spec);
251
252 track->userdata = userdata;
253 track->callback = callback;
254 track->data = data;
255 track->head = 0;
256 track->tail = len;
257 track->capacity = capacity;
258
259 return track;
260}
261
262static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int len)
263{
264 SDL_AudioQueue *queue = (SDL_AudioQueue *)userdata;
265
266 FreeMemoryPoolBlock(&queue->chunk_pool, (void *)buf);
267}
268
269static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap)
270{
271 Uint8 *chunk = (Uint8 *)AllocMemoryPoolBlock(&queue->chunk_pool);
272
273 if (!chunk) {
274 return NULL;
275 }
276
277 size_t capacity = queue->chunk_pool.block_size;
278 capacity -= capacity % SDL_AUDIO_FRAMESIZE(*spec);
279
280 SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chmap, chunk, 0, capacity, FreeChunkedAudioBuffer, queue);
281
282 if (!track) {
283 FreeMemoryPoolBlock(&queue->chunk_pool, chunk);
284 return NULL;
285 }
286
287 return track;
288}
289
290void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
291{
292 SDL_AudioTrack *tail = queue->tail;
293
294 if (tail) {
295 // If the spec has changed, make sure to flush the previous track
296 if (!SDL_AudioSpecsEqual(&tail->spec, &track->spec, tail->chmap, track->chmap)) {
297 FlushAudioTrack(tail);
298 }
299
300 tail->next = track;
301 } else {
302 queue->head = track;
303 }
304
305 queue->tail = track;
306}
307
308static size_t WriteToAudioTrack(SDL_AudioTrack *track, const Uint8 *data, size_t len)
309{
310 if (track->flushed || track->tail >= track->capacity) {
311 return 0;
312 }
313
314 len = SDL_min(len, track->capacity - track->tail);
315 SDL_memcpy(&track->data[track->tail], data, len);
316 track->tail += len;
317
318 return len;
319}
320
321bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len)
322{
323 if (len == 0) {
324 return true;
325 }
326
327 SDL_AudioTrack *track = queue->tail;
328
329 if (track) {
330 if (!SDL_AudioSpecsEqual(&track->spec, spec, track->chmap, chmap)) {
331 FlushAudioTrack(track);
332 }
333 } else {
334 SDL_assert(!queue->head);
335 track = CreateChunkedAudioTrack(queue, spec, chmap);
336
337 if (!track) {
338 return false;
339 }
340
341 queue->head = track;
342 queue->tail = track;
343 }
344
345 for (;;) {
346 const size_t written = WriteToAudioTrack(track, data, len);
347 data += written;
348 len -= written;
349
350 if (len == 0) {
351 break;
352 }
353
354 SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec, chmap);
355
356 if (!new_track) {
357 return false;
358 }
359
360 track->next = new_track;
361 queue->tail = new_track;
362 track = new_track;
363 }
364
365 return true;
366}
367
368void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
369{
370 return queue->head;
371}
372
373size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed)
374{
375 SDL_AudioTrack *iter = (SDL_AudioTrack *)(*inout_iter);
376 SDL_assert(iter != NULL);
377
378 SDL_copyp(out_spec, &iter->spec);
379 *out_chmap = iter->chmap;
380
381 bool flushed = false;
382 size_t queued_bytes = 0;
383
384 while (iter) {
385 SDL_AudioTrack *track = iter;
386 iter = iter->next;
387
388 size_t avail = track->tail - track->head;
389
390 if (avail >= SDL_SIZE_MAX - queued_bytes) {
391 queued_bytes = SDL_SIZE_MAX;
392 flushed = false;
393 break;
394 }
395
396 queued_bytes += avail;
397 flushed = track->flushed;
398
399 if (flushed) {
400 break;
401 }
402 }
403
404 *inout_iter = iter;
405 *out_flushed = flushed;
406
407 return queued_bytes;
408}
409
410static const Uint8 *PeekIntoAudioQueuePast(SDL_AudioQueue *queue, Uint8 *data, size_t len)
411{
412 SDL_AudioTrack *track = queue->head;
413
414 if (track->head >= len) {
415 return &track->data[track->head - len];
416 }
417
418 size_t past = len - track->head;
419
420 if (past > queue->history_length) {
421 return NULL;
422 }
423
424 SDL_memcpy(data, &queue->history_buffer[queue->history_length - past], past);
425 SDL_memcpy(&data[past], track->data, track->head);
426
427 return data;
428}
429
430static void UpdateAudioQueueHistory(SDL_AudioQueue *queue,
431 const Uint8 *data, size_t len)
432{
433 Uint8 *history_buffer = queue->history_buffer;
434 size_t history_bytes = queue->history_length;
435
436 if (len >= history_bytes) {
437 SDL_memcpy(history_buffer, &data[len - history_bytes], history_bytes);
438 } else {
439 size_t preserve = history_bytes - len;
440 SDL_memmove(history_buffer, &history_buffer[len], preserve);
441 SDL_memcpy(&history_buffer[preserve], data, len);
442 }
443}
444
445static const Uint8 *ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
446{
447 SDL_AudioTrack *track = queue->head;
448
449 if (track->tail - track->head >= len) {
450 const Uint8 *ptr = &track->data[track->head];
451 track->head += len;
452 return ptr;
453 }
454
455 size_t total = 0;
456
457 for (;;) {
458 size_t avail = SDL_min(len - total, track->tail - track->head);
459 SDL_memcpy(&data[total], &track->data[track->head], avail);
460 track->head += avail;
461 total += avail;
462
463 if (total == len) {
464 break;
465 }
466
467 if (track->flushed) {
468 SDL_SetError("Reading past end of flushed track");
469 return NULL;
470 }
471
472 SDL_AudioTrack *next = track->next;
473
474 if (!next) {
475 SDL_SetError("Reading past end of incomplete track");
476 return NULL;
477 }
478
479 UpdateAudioQueueHistory(queue, track->data, track->tail);
480
481 queue->head = next;
482 DestroyAudioTrack(queue, track);
483 track = next;
484 }
485
486 return data;
487}
488
489static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data, size_t len)
490{
491 SDL_AudioTrack *track = queue->head;
492
493 if (track->tail - track->head >= len) {
494 return &track->data[track->head];
495 }
496
497 size_t total = 0;
498
499 for (;;) {
500 size_t avail = SDL_min(len - total, track->tail - track->head);
501 SDL_memcpy(&data[total], &track->data[track->head], avail);
502 total += avail;
503
504 if (total == len) {
505 break;
506 }
507
508 if (track->flushed) {
509 // If we have run out of data, fill the rest with silence.
510 SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total);
511 break;
512 }
513
514 track = track->next;
515
516 if (!track) {
517 SDL_SetError("Peeking past end of incomplete track");
518 return NULL;
519 }
520 }
521
522 return data;
523}
524
525const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
526 Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
527 int past_frames, int present_frames, int future_frames,
528 Uint8 *scratch, float gain)
529{
530 SDL_AudioTrack *track = queue->head;
531
532 if (!track) {
533 return NULL;
534 }
535
536 SDL_AudioFormat src_format = track->spec.format;
537 int src_channels = track->spec.channels;
538 const int *src_map = track->chmap;
539
540 size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels;
541 size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels;
542
543 size_t src_past_bytes = past_frames * src_frame_size;
544 size_t src_present_bytes = present_frames * src_frame_size;
545 size_t src_future_bytes = future_frames * src_frame_size;
546
547 size_t dst_past_bytes = past_frames * dst_frame_size;
548 size_t dst_present_bytes = present_frames * dst_frame_size;
549 size_t dst_future_bytes = future_frames * dst_frame_size;
550
551 const bool convert = (src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f);
552
553 if (convert && !dst) {
554 // The user didn't ask for the data to be copied, but we need to convert it, so store it in the scratch buffer
555 dst = scratch;
556 }
557
558 // Can we get all of the data straight from this track?
559 if ((track->head >= src_past_bytes) && ((track->tail - track->head) >= (src_present_bytes + src_future_bytes))) {
560 const Uint8 *ptr = &track->data[track->head - src_past_bytes];
561 track->head += src_present_bytes;
562
563 // Do we still need to copy/convert the data?
564 if (dst) {
565 ConvertAudio(past_frames + present_frames + future_frames, ptr,
566 src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
567 ptr = dst;
568 }
569
570 return ptr;
571 }
572
573 if (!dst) {
574 // The user didn't ask for the data to be copied, but we need to, so store it in the scratch buffer
575 dst = scratch;
576 } else if (!convert) {
577 // We are only copying, not converting, so copy straight into the dst buffer
578 scratch = dst;
579 }
580
581 Uint8 *ptr = dst;
582
583 if (src_past_bytes) {
584 ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
585 dst += dst_past_bytes;
586 scratch += dst_past_bytes;
587 }
588
589 if (src_present_bytes) {
590 ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
591 dst += dst_present_bytes;
592 scratch += dst_present_bytes;
593 }
594
595 if (src_future_bytes) {
596 ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
597 dst += dst_future_bytes;
598 scratch += dst_future_bytes;
599 }
600
601 return ptr;
602}
603
604size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue)
605{
606 size_t total = 0;
607 void *iter = SDL_BeginAudioQueueIter(queue);
608
609 while (iter) {
610 SDL_AudioSpec src_spec;
611 int *src_chmap;
612 bool flushed;
613
614 size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &src_chmap, &flushed);
615
616 if (avail >= SDL_SIZE_MAX - total) {
617 total = SDL_SIZE_MAX;
618 break;
619 }
620
621 total += avail;
622 }
623
624 return total;
625}
626
627bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames)
628{
629 SDL_AudioTrack *track = queue->head;
630
631 if (!track) {
632 return false;
633 }
634
635 size_t length = num_frames * SDL_AUDIO_FRAMESIZE(track->spec);
636 Uint8 *history_buffer = queue->history_buffer;
637
638 if (queue->history_capacity < length) {
639 history_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), length);
640 if (!history_buffer) {
641 return false;
642 }
643 SDL_aligned_free(queue->history_buffer);
644 queue->history_buffer = history_buffer;
645 queue->history_capacity = length;
646 }
647
648 queue->history_length = length;
649 SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(track->spec.format), length);
650
651 return true;
652}
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audioqueue.h b/contrib/SDL-3.2.8/src/audio/SDL_audioqueue.h
new file mode 100644
index 0000000..4662946
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audioqueue.h
@@ -0,0 +1,79 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_audioqueue_h_
24#define SDL_audioqueue_h_
25
26// Internal functions used by SDL_AudioStream for queueing audio.
27
28typedef void (SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen);
29
30typedef struct SDL_AudioQueue SDL_AudioQueue;
31typedef struct SDL_AudioTrack SDL_AudioTrack;
32
33// Create a new audio queue
34extern SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size);
35
36// Destroy an audio queue
37extern void SDL_DestroyAudioQueue(SDL_AudioQueue *queue);
38
39// Completely clear the queue
40extern void SDL_ClearAudioQueue(SDL_AudioQueue *queue);
41
42// Mark the last track as flushed
43extern void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
44
45// Pop the current head track
46// REQUIRES: The head track must exist, and must have been flushed
47extern void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
48
49// Write data to the end of queue
50// REQUIRES: If the spec has changed, the last track must have been flushed
51extern bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len);
52
53// Create a track where the input data is owned by the caller
54extern SDL_AudioTrack *SDL_CreateAudioTrack(SDL_AudioQueue *queue,
55 const SDL_AudioSpec *spec, const int *chmap, Uint8 *data, size_t len, size_t capacity,
56 SDL_ReleaseAudioBufferCallback callback, void *userdata);
57
58// Add a track to the end of the queue
59// REQUIRES: `track != NULL`
60extern void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track);
61
62// Iterate over the tracks in the queue
63extern void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
64
65// Query and update the track iterator
66// REQUIRES: `*inout_iter != NULL` (a valid iterator)
67extern size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed);
68
69extern const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
70 Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
71 int past_frames, int present_frames, int future_frames,
72 Uint8 *scratch, float gain);
73
74// Get the total number of bytes currently queued
75extern size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue);
76
77extern bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames);
78
79#endif // SDL_audioqueue_h_
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audioresample.c b/contrib/SDL-3.2.8/src/audio/SDL_audioresample.c
new file mode 100644
index 0000000..371002e
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audioresample.c
@@ -0,0 +1,706 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_sysaudio.h"
24
25#include "SDL_audioresample.h"
26
27// SDL's resampler uses a "bandlimited interpolation" algorithm:
28// https://ccrma.stanford.edu/~jos/resample/
29
30// TODO: Support changing this at runtime?
31#if defined(SDL_SSE_INTRINSICS) || defined(SDL_NEON_INTRINSICS)
32// In <current year>, SSE is basically mandatory anyway
33// We want RESAMPLER_SAMPLES_PER_FRAME to be a multiple of 4, to make SIMD easier
34#define RESAMPLER_ZERO_CROSSINGS 6
35#else
36#define RESAMPLER_ZERO_CROSSINGS 5
37#endif
38
39#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
40
41// For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`.
42// Note, when upsampling, it is also possible to start sampling from `srcpos = -1`.
43#define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1)
44
45// More bits gives more precision, at the cost of a larger table.
46#define RESAMPLER_BITS_PER_ZERO_CROSSING 3
47#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
48#define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
49#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
50
51// ResampleFrame is just a vector/matrix/matrix multiplication.
52// It performs cubic interpolation of the filter, then multiplies that with the input.
53// dst = [1, frac, frac^2, frac^3] * filter * src
54
55// Cubic Polynomial
56typedef union Cubic
57{
58 float v[4];
59
60#ifdef SDL_SSE_INTRINSICS
61 // Aligned loads can be used directly as memory operands for mul/add
62 __m128 v128;
63#endif
64
65#ifdef SDL_NEON_INTRINSICS
66 float32x4_t v128;
67#endif
68
69} Cubic;
70
71static void ResampleFrame_Generic(const float *src, float *dst, const Cubic *filter, float frac, int chans)
72{
73 const float frac2 = frac * frac;
74 const float frac3 = frac * frac2;
75
76 int i, chan;
77 float scales[RESAMPLER_SAMPLES_PER_FRAME];
78
79 for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
80 scales[i] = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
81 }
82
83 for (chan = 0; chan < chans; ++chan) {
84 float out = 0.0f;
85
86 for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i) {
87 out += src[i * chans + chan] * scales[i];
88 }
89
90 dst[chan] = out;
91 }
92}
93
94static void ResampleFrame_Mono(const float *src, float *dst, const Cubic *filter, float frac, int chans)
95{
96 const float frac2 = frac * frac;
97 const float frac3 = frac * frac2;
98
99 int i;
100 float out = 0.0f;
101
102 for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
103 // Interpolate between the nearest two filters
104 const float scale = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
105
106 out += src[i] * scale;
107 }
108
109 dst[0] = out;
110}
111
112static void ResampleFrame_Stereo(const float *src, float *dst, const Cubic *filter, float frac, int chans)
113{
114 const float frac2 = frac * frac;
115 const float frac3 = frac * frac2;
116
117 int i;
118 float out0 = 0.0f;
119 float out1 = 0.0f;
120
121 for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
122 // Interpolate between the nearest two filters
123 const float scale = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
124
125 out0 += src[i * 2 + 0] * scale;
126 out1 += src[i * 2 + 1] * scale;
127 }
128
129 dst[0] = out0;
130 dst[1] = out1;
131}
132
133#ifdef SDL_SSE_INTRINSICS
134#define sdl_madd_ps(a, b, c) _mm_add_ps(a, _mm_mul_ps(b, c)) // Not-so-fused multiply-add
135
136static void SDL_TARGETING("sse") ResampleFrame_Generic_SSE(const float *src, float *dst, const Cubic *filter, float frac, int chans)
137{
138#if RESAMPLER_SAMPLES_PER_FRAME != 12
139#error Invalid samples per frame
140#endif
141
142 __m128 f0, f1, f2;
143
144 {
145 const __m128 frac1 = _mm_set1_ps(frac);
146 const __m128 frac2 = _mm_mul_ps(frac1, frac1);
147 const __m128 frac3 = _mm_mul_ps(frac1, frac2);
148
149// Transposed in SetupAudioResampler
150// Explicitly use _mm_load_ps to workaround ICE in GCC 4.9.4 accessing Cubic.v128
151#define X(out) \
152 out = _mm_load_ps(filter[0].v); \
153 out = sdl_madd_ps(out, frac1, _mm_load_ps(filter[1].v)); \
154 out = sdl_madd_ps(out, frac2, _mm_load_ps(filter[2].v)); \
155 out = sdl_madd_ps(out, frac3, _mm_load_ps(filter[3].v)); \
156 filter += 4
157
158 X(f0);
159 X(f1);
160 X(f2);
161
162#undef X
163 }
164
165 if (chans == 2) {
166 // Duplicate each of the filter elements and multiply by the input
167 // Use two accumulators to improve throughput
168 __m128 out0 = _mm_mul_ps(_mm_loadu_ps(src + 0), _mm_unpacklo_ps(f0, f0));
169 __m128 out1 = _mm_mul_ps(_mm_loadu_ps(src + 4), _mm_unpackhi_ps(f0, f0));
170 out0 = sdl_madd_ps(out0, _mm_loadu_ps(src + 8), _mm_unpacklo_ps(f1, f1));
171 out1 = sdl_madd_ps(out1, _mm_loadu_ps(src + 12), _mm_unpackhi_ps(f1, f1));
172 out0 = sdl_madd_ps(out0, _mm_loadu_ps(src + 16), _mm_unpacklo_ps(f2, f2));
173 out1 = sdl_madd_ps(out1, _mm_loadu_ps(src + 20), _mm_unpackhi_ps(f2, f2));
174
175 // Add the accumulators together
176 __m128 out = _mm_add_ps(out0, out1);
177
178 // Add the lower and upper pairs together
179 out = _mm_add_ps(out, _mm_movehl_ps(out, out));
180
181 // Store the result
182 _mm_storel_pi((__m64 *)dst, out);
183 return;
184 }
185
186 if (chans == 1) {
187 // Multiply the filter by the input
188 __m128 out = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
189 out = sdl_madd_ps(out, f1, _mm_loadu_ps(src + 4));
190 out = sdl_madd_ps(out, f2, _mm_loadu_ps(src + 8));
191
192 // Horizontal sum
193 __m128 shuf = _mm_shuffle_ps(out, out, _MM_SHUFFLE(2, 3, 0, 1));
194 out = _mm_add_ps(out, shuf);
195 out = _mm_add_ss(out, _mm_movehl_ps(shuf, out));
196
197 _mm_store_ss(dst, out);
198 return;
199 }
200
201 int chan = 0;
202
203 // Process 4 channels at once
204 for (; chan + 4 <= chans; chan += 4) {
205 const float *in = &src[chan];
206 __m128 out0 = _mm_setzero_ps();
207 __m128 out1 = _mm_setzero_ps();
208
209#define X(a, b, out) \
210 out = sdl_madd_ps(out, _mm_loadu_ps(in), _mm_shuffle_ps(a, a, _MM_SHUFFLE(b, b, b, b))); \
211 in += chans
212
213#define Y(a) \
214 X(a, 0, out0); \
215 X(a, 1, out1); \
216 X(a, 2, out0); \
217 X(a, 3, out1)
218
219 Y(f0);
220 Y(f1);
221 Y(f2);
222
223#undef X
224#undef Y
225
226 // Add the accumulators together
227 __m128 out = _mm_add_ps(out0, out1);
228
229 _mm_storeu_ps(&dst[chan], out);
230 }
231
232 // Process the remaining channels one at a time.
233 // Channel counts 1,2,4,8 are already handled above, leaving 3,5,6,7 to deal with (looping 3,1,2,3 times).
234 // Without vgatherdps (AVX2), this gets quite messy.
235 for (; chan < chans; ++chan) {
236 const float *in = &src[chan];
237 __m128 v0, v1, v2;
238
239#define X(x) \
240 x = _mm_unpacklo_ps(_mm_load_ss(in), _mm_load_ss(in + chans)); \
241 in += chans + chans; \
242 x = _mm_movelh_ps(x, _mm_unpacklo_ps(_mm_load_ss(in), _mm_load_ss(in + chans))); \
243 in += chans + chans
244
245 X(v0);
246 X(v1);
247 X(v2);
248
249#undef X
250
251 __m128 out = _mm_mul_ps(f0, v0);
252 out = sdl_madd_ps(out, f1, v1);
253 out = sdl_madd_ps(out, f2, v2);
254
255 // Horizontal sum
256 __m128 shuf = _mm_shuffle_ps(out, out, _MM_SHUFFLE(2, 3, 0, 1));
257 out = _mm_add_ps(out, shuf);
258 out = _mm_add_ss(out, _mm_movehl_ps(shuf, out));
259
260 _mm_store_ss(&dst[chan], out);
261 }
262}
263
264#undef sdl_madd_ps
265#endif
266
267#ifdef SDL_NEON_INTRINSICS
268static void ResampleFrame_Generic_NEON(const float *src, float *dst, const Cubic *filter, float frac, int chans)
269{
270#if RESAMPLER_SAMPLES_PER_FRAME != 12
271#error Invalid samples per frame
272#endif
273
274 float32x4_t f0, f1, f2;
275
276 {
277 const float32x4_t frac1 = vdupq_n_f32(frac);
278 const float32x4_t frac2 = vmulq_f32(frac1, frac1);
279 const float32x4_t frac3 = vmulq_f32(frac1, frac2);
280
281// Transposed in SetupAudioResampler
282#define X(out) \
283 out = vmlaq_f32(vmlaq_f32(vmlaq_f32(filter[0].v128, filter[1].v128, frac1), filter[2].v128, frac2), filter[3].v128, frac3); \
284 filter += 4
285
286 X(f0);
287 X(f1);
288 X(f2);
289
290#undef X
291 }
292
293 if (chans == 2) {
294 float32x4x2_t g0 = vzipq_f32(f0, f0);
295 float32x4x2_t g1 = vzipq_f32(f1, f1);
296 float32x4x2_t g2 = vzipq_f32(f2, f2);
297
298 // Duplicate each of the filter elements and multiply by the input
299 // Use two accumulators to improve throughput
300 float32x4_t out0 = vmulq_f32(vld1q_f32(src + 0), g0.val[0]);
301 float32x4_t out1 = vmulq_f32(vld1q_f32(src + 4), g0.val[1]);
302 out0 = vmlaq_f32(out0, vld1q_f32(src + 8), g1.val[0]);
303 out1 = vmlaq_f32(out1, vld1q_f32(src + 12), g1.val[1]);
304 out0 = vmlaq_f32(out0, vld1q_f32(src + 16), g2.val[0]);
305 out1 = vmlaq_f32(out1, vld1q_f32(src + 20), g2.val[1]);
306
307 // Add the accumulators together
308 out0 = vaddq_f32(out0, out1);
309
310 // Add the lower and upper pairs together
311 float32x2_t out = vadd_f32(vget_low_f32(out0), vget_high_f32(out0));
312
313 // Store the result
314 vst1_f32(dst, out);
315 return;
316 }
317
318 if (chans == 1) {
319 // Multiply the filter by the input
320 float32x4_t out = vmulq_f32(f0, vld1q_f32(src + 0));
321 out = vmlaq_f32(out, f1, vld1q_f32(src + 4));
322 out = vmlaq_f32(out, f2, vld1q_f32(src + 8));
323
324 // Horizontal sum
325 float32x2_t sum = vadd_f32(vget_low_f32(out), vget_high_f32(out));
326 sum = vpadd_f32(sum, sum);
327
328 vst1_lane_f32(dst, sum, 0);
329 return;
330 }
331
332 int chan = 0;
333
334 // Process 4 channels at once
335 for (; chan + 4 <= chans; chan += 4) {
336 const float *in = &src[chan];
337 float32x4_t out0 = vdupq_n_f32(0);
338 float32x4_t out1 = vdupq_n_f32(0);
339
340#define X(a, b, out) \
341 out = vmlaq_f32(out, vld1q_f32(in), vdupq_lane_f32(a, b)); \
342 in += chans
343
344#define Y(a) \
345 X(vget_low_f32(a), 0, out0); \
346 X(vget_low_f32(a), 1, out1); \
347 X(vget_high_f32(a), 0, out0); \
348 X(vget_high_f32(a), 1, out1)
349
350 Y(f0);
351 Y(f1);
352 Y(f2);
353
354#undef X
355#undef Y
356
357 // Add the accumulators together
358 float32x4_t out = vaddq_f32(out0, out1);
359
360 vst1q_f32(&dst[chan], out);
361 }
362
363 // Process the remaining channels one at a time.
364 // Channel counts 1,2,4,8 are already handled above, leaving 3,5,6,7 to deal with (looping 3,1,2,3 times).
365 for (; chan < chans; ++chan) {
366 const float *in = &src[chan];
367 float32x4_t v0, v1, v2;
368
369#define X(x) \
370 x = vld1q_dup_f32(in); \
371 in += chans; \
372 x = vld1q_lane_f32(in, x, 1); \
373 in += chans; \
374 x = vld1q_lane_f32(in, x, 2); \
375 in += chans; \
376 x = vld1q_lane_f32(in, x, 3); \
377 in += chans
378
379 X(v0);
380 X(v1);
381 X(v2);
382
383#undef X
384
385 float32x4_t out = vmulq_f32(f0, v0);
386 out = vmlaq_f32(out, f1, v1);
387 out = vmlaq_f32(out, f2, v2);
388
389 // Horizontal sum
390 float32x2_t sum = vadd_f32(vget_low_f32(out), vget_high_f32(out));
391 sum = vpadd_f32(sum, sum);
392
393 vst1_lane_f32(&dst[chan], sum, 0);
394 }
395}
396#endif
397
398// Calculate the cubic equation which passes through all four points.
399// https://en.wikipedia.org/wiki/Ordinary_least_squares
400// https://en.wikipedia.org/wiki/Polynomial_regression
401static void CubicLeastSquares(Cubic *coeffs, float y0, float y1, float y2, float y3)
402{
403 // Least squares matrix for xs = [0, 1/3, 2/3, 1]
404 // [ 1.0 0.0 0.0 0.0 ]
405 // [ -5.5 9.0 -4.5 1.0 ]
406 // [ 9.0 -22.5 18.0 -4.5 ]
407 // [ -4.5 13.5 -13.5 4.5 ]
408
409 coeffs->v[0] = y0;
410 coeffs->v[1] = -5.5f * y0 + 9.0f * y1 - 4.5f * y2 + y3;
411 coeffs->v[2] = 9.0f * y0 - 22.5f * y1 + 18.0f * y2 - 4.5f * y3;
412 coeffs->v[3] = -4.5f * y0 + 13.5f * y1 - 13.5f * y2 + 4.5f * y3;
413}
414
415// Zeroth-order modified Bessel function of the first kind
416// https://mathworld.wolfram.com/ModifiedBesselFunctionoftheFirstKind.html
417static float BesselI0(float x)
418{
419 float sum = 0.0f;
420 float i = 1.0f;
421 float t = 1.0f;
422 x *= x * 0.25f;
423
424 while (t >= sum * SDL_FLT_EPSILON) {
425 sum += t;
426 t *= x / (i * i);
427 ++i;
428 }
429
430 return sum;
431}
432
433// Pre-calculate 180 degrees of sin(pi * x) / pi
434// The speedup from this isn't huge, but it also avoids precision issues.
435// If sinf isn't available, SDL_sinf just calls SDL_sin.
436// Know what SDL_sin(SDL_PI_F) equals? Not quite zero.
437static void SincTable(float *table, int len)
438{
439 int i;
440
441 for (i = 0; i < len; ++i) {
442 table[i] = SDL_sinf(i * (SDL_PI_F / len)) / SDL_PI_F;
443 }
444}
445
446// Calculate Sinc(x/y), using a lookup table
447static float Sinc(const float *table, int x, int y)
448{
449 float s = table[x % y];
450 s = ((x / y) & 1) ? -s : s;
451 return (s * y) / x;
452}
453
454static Cubic ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING][RESAMPLER_SAMPLES_PER_FRAME];
455
456static void GenerateResamplerFilter(void)
457{
458 enum
459 {
460 // Generate samples at 3x the target resolution, so that we have samples at [0, 1/3, 2/3, 1] of each position
461 TABLE_SAMPLES_PER_ZERO_CROSSING = RESAMPLER_SAMPLES_PER_ZERO_CROSSING * 3,
462 TABLE_SIZE = RESAMPLER_ZERO_CROSSINGS * TABLE_SAMPLES_PER_ZERO_CROSSING,
463 };
464
465 // if dB > 50, beta=(0.1102 * (dB - 8.7)), according to Matlab.
466 const float dB = 80.0f;
467 const float beta = 0.1102f * (dB - 8.7f);
468 const float bessel_beta = BesselI0(beta);
469 const float lensqr = TABLE_SIZE * TABLE_SIZE;
470
471 int i, j;
472
473 float sinc[TABLE_SAMPLES_PER_ZERO_CROSSING];
474 SincTable(sinc, TABLE_SAMPLES_PER_ZERO_CROSSING);
475
476 // Generate one wing of the filter
477 // https://en.wikipedia.org/wiki/Kaiser_window
478 // https://en.wikipedia.org/wiki/Whittaker%E2%80%93Shannon_interpolation_formula
479 float filter[TABLE_SIZE + 1];
480 filter[0] = 1.0f;
481
482 for (i = 1; i <= TABLE_SIZE; ++i) {
483 float b = BesselI0(beta * SDL_sqrtf((lensqr - (i * i)) / lensqr)) / bessel_beta;
484 float s = Sinc(sinc, i, TABLE_SAMPLES_PER_ZERO_CROSSING);
485 filter[i] = b * s;
486 }
487
488 // Generate the coefficients for each point
489 // When interpolating, the fraction represents how far we are between input samples,
490 // so we need to align the filter by "moving" it to the right.
491 //
492 // For the left wing, this means interpolating "forwards" (away from the center)
493 // For the right wing, this means interpolating "backwards" (towards the center)
494 //
495 // The center of the filter is at the end of the left wing (RESAMPLER_ZERO_CROSSINGS - 1)
496 // The left wing is the filter, but reversed
497 // The right wing is the filter, but offset by 1
498 //
499 // Since the right wing is offset by 1, this just means we interpolate backwards
500 // between the same points, instead of forwards
501 // interp(p[n], p[n+1], t) = interp(p[n+1], p[n+1-1], 1 - t) = interp(p[n+1], p[n], 1 - t)
502 for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
503 for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; ++j) {
504 const float *ys = &filter[((j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING) + i) * 3];
505
506 Cubic *fwd = &ResamplerFilter[i][RESAMPLER_ZERO_CROSSINGS - j - 1];
507 Cubic *rev = &ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING - i - 1][RESAMPLER_ZERO_CROSSINGS + j];
508
509 // Calculate the cubic equation of the 4 points
510 CubicLeastSquares(fwd, ys[0], ys[1], ys[2], ys[3]);
511 CubicLeastSquares(rev, ys[3], ys[2], ys[1], ys[0]);
512 }
513 }
514}
515
516typedef void (*ResampleFrameFunc)(const float *src, float *dst, const Cubic *filter, float frac, int chans);
517static ResampleFrameFunc ResampleFrame[8];
518
519// Transpose 4x4 floats
520static void Transpose4x4(Cubic *data)
521{
522 int i, j;
523
524 Cubic temp[4] = { data[0], data[1], data[2], data[3] };
525
526 for (i = 0; i < 4; ++i) {
527 for (j = 0; j < 4; ++j) {
528 data[i].v[j] = temp[j].v[i];
529 }
530 }
531}
532
533static void SetupAudioResampler(void)
534{
535 int i, j;
536 bool transpose = false;
537
538 GenerateResamplerFilter();
539
540#ifdef SDL_SSE_INTRINSICS
541 if (SDL_HasSSE()) {
542 for (i = 0; i < 8; ++i) {
543 ResampleFrame[i] = ResampleFrame_Generic_SSE;
544 }
545 transpose = true;
546 } else
547#endif
548#ifdef SDL_NEON_INTRINSICS
549 if (SDL_HasNEON()) {
550 for (i = 0; i < 8; ++i) {
551 ResampleFrame[i] = ResampleFrame_Generic_NEON;
552 }
553 transpose = true;
554 } else
555#endif
556 {
557 for (i = 0; i < 8; ++i) {
558 ResampleFrame[i] = ResampleFrame_Generic;
559 }
560
561 ResampleFrame[0] = ResampleFrame_Mono;
562 ResampleFrame[1] = ResampleFrame_Stereo;
563 }
564
565 if (transpose) {
566 // Transpose each set of 4 coefficients, to reduce work when resampling
567 for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
568 for (j = 0; j + 4 <= RESAMPLER_SAMPLES_PER_FRAME; j += 4) {
569 Transpose4x4(&ResamplerFilter[i][j]);
570 }
571 }
572 }
573}
574
575void SDL_SetupAudioResampler(void)
576{
577 static SDL_InitState init;
578
579 if (SDL_ShouldInit(&init)) {
580 SetupAudioResampler();
581 SDL_SetInitialized(&init, true);
582 }
583}
584
585Sint64 SDL_GetResampleRate(int src_rate, int dst_rate)
586{
587 SDL_assert(src_rate > 0);
588 SDL_assert(dst_rate > 0);
589
590 Sint64 numerator = (Sint64)src_rate << 32;
591 Sint64 denominator = (Sint64)dst_rate;
592
593 // Generally it's expected that `dst_frames = (src_frames * dst_rate) / src_rate`
594 // To match this as closely as possible without infinite precision, always round up the resample rate.
595 // For example, without rounding up, a sample ratio of 2:3 would have `sample_rate = 0xAAAAAAAA`
596 // After 3 frames, the position would be 0x1.FFFFFFFE, meaning we haven't fully consumed the second input frame.
597 // By rounding up to 0xAAAAAAAB, we would instead reach 0x2.00000001, fulling consuming the second frame.
598 // Technically you could say this is kicking the can 0x100000000 steps down the road, but I'm fine with that :)
599 // sample_rate = div_ceil(numerator, denominator)
600 Sint64 sample_rate = ((numerator - 1) / denominator) + 1;
601
602 SDL_assert(sample_rate > 0);
603
604 return sample_rate;
605}
606
607int SDL_GetResamplerHistoryFrames(void)
608{
609 // Even if we aren't currently resampling, make sure to keep enough history in case we need to later.
610
611 return RESAMPLER_MAX_PADDING_FRAMES;
612}
613
614int SDL_GetResamplerPaddingFrames(Sint64 resample_rate)
615{
616 // This must always be <= SDL_GetResamplerHistoryFrames()
617
618 return resample_rate ? RESAMPLER_MAX_PADDING_FRAMES : 0;
619}
620
621// These are not general purpose. They do not check for all possible underflow/overflow
622SDL_FORCE_INLINE bool ResamplerAdd(Sint64 a, Sint64 b, Sint64 *ret)
623{
624 if ((b > 0) && (a > SDL_MAX_SINT64 - b)) {
625 return false;
626 }
627
628 *ret = a + b;
629 return true;
630}
631
632SDL_FORCE_INLINE bool ResamplerMul(Sint64 a, Sint64 b, Sint64 *ret)
633{
634 if ((b > 0) && (a > SDL_MAX_SINT64 / b)) {
635 return false;
636 }
637
638 *ret = a * b;
639 return true;
640}
641
642Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset)
643{
644 // Calculate the index of the last input frame, then add 1.
645 // ((((output_frames - 1) * resample_rate) + resample_offset) >> 32) + 1
646
647 Sint64 output_offset;
648 if (!ResamplerMul(output_frames, resample_rate, &output_offset) ||
649 !ResamplerAdd(output_offset, -resample_rate + resample_offset + 0x100000000, &output_offset)) {
650 output_offset = SDL_MAX_SINT64;
651 }
652
653 Sint64 input_frames = (Sint64)(Sint32)(output_offset >> 32);
654 input_frames = SDL_max(input_frames, 0);
655
656 return input_frames;
657}
658
659Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset)
660{
661 Sint64 resample_offset = *inout_resample_offset;
662
663 // input_offset = (input_frames << 32) - resample_offset;
664 Sint64 input_offset;
665 if (!ResamplerMul(input_frames, 0x100000000, &input_offset) ||
666 !ResamplerAdd(input_offset, -resample_offset, &input_offset)) {
667 input_offset = SDL_MAX_SINT64;
668 }
669
670 // output_frames = div_ceil(input_offset, resample_rate)
671 Sint64 output_frames = (input_offset > 0) ? ((input_offset - 1) / resample_rate) + 1 : 0;
672
673 *inout_resample_offset = (output_frames * resample_rate) - input_offset;
674
675 return output_frames;
676}
677
678void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
679 Sint64 resample_rate, Sint64 *inout_resample_offset)
680{
681 int i;
682 Sint64 srcpos = *inout_resample_offset;
683 ResampleFrameFunc resample_frame = ResampleFrame[chans - 1];
684
685 SDL_assert(resample_rate > 0);
686
687 src -= (RESAMPLER_ZERO_CROSSINGS - 1) * chans;
688
689 for (i = 0; i < outframes; ++i) {
690 int srcindex = (int)(Sint32)(srcpos >> 32);
691 Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
692 srcpos += resample_rate;
693
694 SDL_assert(srcindex >= -1 && srcindex < inframes);
695
696 const Cubic *filter = ResamplerFilter[srcfraction >> RESAMPLER_FILTER_INTERP_BITS];
697 const float frac = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
698
699 const float *frame = &src[srcindex * chans];
700 resample_frame(frame, dst, filter, frac, chans);
701
702 dst += chans;
703 }
704
705 *inout_resample_offset = srcpos - ((Sint64)inframes << 32);
706}
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audioresample.h b/contrib/SDL-3.2.8/src/audio/SDL_audioresample.h
new file mode 100644
index 0000000..6620073
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audioresample.h
@@ -0,0 +1,43 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_audioresample_h_
24#define SDL_audioresample_h_
25
26// Internal functions used by SDL_AudioStream for resampling audio.
27// The resampler uses 32:32 fixed-point arithmetic to track its position.
28
29Sint64 SDL_GetResampleRate(int src_rate, int dst_rate);
30
31int SDL_GetResamplerHistoryFrames(void);
32int SDL_GetResamplerPaddingFrames(Sint64 resample_rate);
33
34Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset);
35Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset);
36
37// Resample some audio.
38// REQUIRES: `inframes >= SDL_GetResamplerInputFrames(outframes)`
39// REQUIRES: At least `SDL_GetResamplerPaddingFrames(...)` extra frames to the left of src, and right of src+inframes
40void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
41 Sint64 resample_rate, Sint64 *inout_resample_offset);
42
43#endif // SDL_audioresample_h_
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_audiotypecvt.c b/contrib/SDL-3.2.8/src/audio/SDL_audiotypecvt.c
new file mode 100644
index 0000000..d80a831
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_audiotypecvt.c
@@ -0,0 +1,925 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_sysaudio.h"
24
25#define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f
26
27// start fallback scalar converters
28
29// This code requires that floats are in the IEEE-754 binary32 format
30SDL_COMPILE_TIME_ASSERT(float_bits, sizeof(float) == sizeof(Uint32));
31
32union float_bits {
33 Uint32 u32;
34 float f32;
35};
36
37static void SDL_Convert_S8_to_F32_Scalar(float *dst, const Sint8 *src, int num_samples)
38{
39 int i;
40
41 LOG_DEBUG_AUDIO_CONVERT("S8", "F32");
42
43 for (i = num_samples - 1; i >= 0; --i) {
44 /* 1) Construct a float in the range [65536.0, 65538.0)
45 * 2) Shift the float range to [-1.0, 1.0) */
46 union float_bits x;
47 x.u32 = (Uint8)src[i] ^ 0x47800080u;
48 dst[i] = x.f32 - 65537.0f;
49 }
50}
51
52static void SDL_Convert_U8_to_F32_Scalar(float *dst, const Uint8 *src, int num_samples)
53{
54 int i;
55
56 LOG_DEBUG_AUDIO_CONVERT("U8", "F32");
57
58 for (i = num_samples - 1; i >= 0; --i) {
59 /* 1) Construct a float in the range [65536.0, 65538.0)
60 * 2) Shift the float range to [-1.0, 1.0) */
61 union float_bits x;
62 x.u32 = src[i] ^ 0x47800000u;
63 dst[i] = x.f32 - 65537.0f;
64 }
65}
66
67static void SDL_Convert_S16_to_F32_Scalar(float *dst, const Sint16 *src, int num_samples)
68{
69 int i;
70
71 LOG_DEBUG_AUDIO_CONVERT("S16", "F32");
72
73 for (i = num_samples - 1; i >= 0; --i) {
74 /* 1) Construct a float in the range [256.0, 258.0)
75 * 2) Shift the float range to [-1.0, 1.0) */
76 union float_bits x;
77 x.u32 = (Uint16)src[i] ^ 0x43808000u;
78 dst[i] = x.f32 - 257.0f;
79 }
80}
81
82static void SDL_Convert_S32_to_F32_Scalar(float *dst, const Sint32 *src, int num_samples)
83{
84 int i;
85
86 LOG_DEBUG_AUDIO_CONVERT("S32", "F32");
87
88 for (i = num_samples - 1; i >= 0; --i) {
89 dst[i] = (float)src[i] * DIVBY2147483648;
90 }
91}
92
93// Create a bit-mask based on the sign-bit. Should optimize to a single arithmetic-shift-right
94#define SIGNMASK(x) (Uint32)(0u - ((Uint32)(x) >> 31))
95
96static void SDL_Convert_F32_to_S8_Scalar(Sint8 *dst, const float *src, int num_samples)
97{
98 int i;
99
100 LOG_DEBUG_AUDIO_CONVERT("F32", "S8");
101
102 for (i = 0; i < num_samples; ++i) {
103 /* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
104 * 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
105 * 3) Clamp the value to [-128, 127] */
106 union float_bits x;
107 x.f32 = src[i] + 98304.0f;
108
109 Uint32 y = x.u32 - 0x47C00000u;
110 Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
111 y = y ^ (z & SIGNMASK(z));
112
113 dst[i] = (Sint8)(y & 0xFF);
114 }
115}
116
117static void SDL_Convert_F32_to_U8_Scalar(Uint8 *dst, const float *src, int num_samples)
118{
119 int i;
120
121 LOG_DEBUG_AUDIO_CONVERT("F32", "U8");
122
123 for (i = 0; i < num_samples; ++i) {
124 /* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
125 * 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
126 * 3) Clamp the value to [-128, 127]
127 * 4) Shift the integer range from [-128, 127] to [0, 255] */
128 union float_bits x;
129 x.f32 = src[i] + 98304.0f;
130
131 Uint32 y = x.u32 - 0x47C00000u;
132 Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
133 y = (y ^ 0x80u) ^ (z & SIGNMASK(z));
134
135 dst[i] = (Uint8)(y & 0xFF);
136 }
137}
138
139static void SDL_Convert_F32_to_S16_Scalar(Sint16 *dst, const float *src, int num_samples)
140{
141 int i;
142
143 LOG_DEBUG_AUDIO_CONVERT("F32", "S16");
144
145 for (i = 0; i < num_samples; ++i) {
146 /* 1) Shift the float range from [-1.0, 1.0] to [383.0, 385.0]
147 * 2) Shift the integer range from [0x43BF8000, 0x43C08000] to [-32768, 32768]
148 * 3) Clamp values outside the [-32768, 32767] range */
149 union float_bits x;
150 x.f32 = src[i] + 384.0f;
151
152 Uint32 y = x.u32 - 0x43C00000u;
153 Uint32 z = 0x7FFFu - (y ^ SIGNMASK(y));
154 y = y ^ (z & SIGNMASK(z));
155
156 dst[i] = (Sint16)(y & 0xFFFF);
157 }
158}
159
160static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num_samples)
161{
162 int i;
163
164 LOG_DEBUG_AUDIO_CONVERT("F32", "S32");
165
166 for (i = 0; i < num_samples; ++i) {
167 /* 1) Shift the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
168 * 2) Set values outside the [-2147483648.0, 2147483647.0] range to -2147483648.0
169 * 3) Convert the float to an integer, and fixup values outside the valid range */
170 union float_bits x;
171 x.f32 = src[i];
172
173 Uint32 y = x.u32 + 0x0F800000u;
174 Uint32 z = y - 0xCF000000u;
175 z &= SIGNMASK(y ^ z);
176 x.u32 = y - z;
177
178 dst[i] = (Sint32)x.f32 ^ (Sint32)SIGNMASK(z);
179 }
180}
181
182#undef SIGNMASK
183
184static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_samples)
185{
186 int i;
187
188 for (i = 0; i < num_samples; ++i) {
189 dst[i] = SDL_Swap16(src[i]);
190 }
191}
192
193static void SDL_Convert_Swap32_Scalar(Uint32* dst, const Uint32* src, int num_samples)
194{
195 int i;
196
197 for (i = 0; i < num_samples; ++i) {
198 dst[i] = SDL_Swap32(src[i]);
199 }
200}
201
202// end fallback scalar converters
203
204// Convert forwards, when sizeof(*src) >= sizeof(*dst)
205#define CONVERT_16_FWD(CVT1, CVT16) \
206 int i = 0; \
207 if (num_samples >= 16) { \
208 while ((uintptr_t)(&dst[i]) & 15) { CVT1 ++i; } \
209 while ((i + 16) <= num_samples) { CVT16 i += 16; } \
210 } \
211 while (i < num_samples) { CVT1 ++i; }
212
213// Convert backwards, when sizeof(*src) <= sizeof(*dst)
214#define CONVERT_16_REV(CVT1, CVT16) \
215 int i = num_samples; \
216 if (i >= 16) { \
217 while ((uintptr_t)(&dst[i]) & 15) { --i; CVT1 } \
218 while (i >= 16) { i -= 16; CVT16 } \
219 } \
220 while (i > 0) { --i; CVT1 }
221
222#ifdef SDL_SSE2_INTRINSICS
223static void SDL_TARGETING("sse2") SDL_Convert_S8_to_F32_SSE2(float *dst, const Sint8 *src, int num_samples)
224{
225 /* 1) Flip the sign bit to convert from S8 to U8 format
226 * 2) Construct a float in the range [65536.0, 65538.0)
227 * 3) Shift the float range to [-1.0, 1.0)
228 * dst[i] = i2f((src[i] ^ 0x80) | 0x47800000) - 65537.0 */
229 const __m128i zero = _mm_setzero_si128();
230 const __m128i flipper = _mm_set1_epi8(-0x80);
231 const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
232 const __m128 offset = _mm_set1_ps(-65537.0);
233
234 LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using SSE2)");
235
236 CONVERT_16_REV({
237 _mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800080u)), offset));
238 }, {
239 const __m128i bytes = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
240
241 const __m128i shorts0 = _mm_unpacklo_epi8(bytes, zero);
242 const __m128i shorts1 = _mm_unpackhi_epi8(bytes, zero);
243
244 const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
245 const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
246 const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
247 const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
248
249 _mm_store_ps(&dst[i], floats0);
250 _mm_store_ps(&dst[i + 4], floats1);
251 _mm_store_ps(&dst[i + 8], floats2);
252 _mm_store_ps(&dst[i + 12], floats3);
253 })
254}
255
256static void SDL_TARGETING("sse2") SDL_Convert_U8_to_F32_SSE2(float *dst, const Uint8 *src, int num_samples)
257{
258 /* 1) Construct a float in the range [65536.0, 65538.0)
259 * 2) Shift the float range to [-1.0, 1.0)
260 * dst[i] = i2f(src[i] | 0x47800000) - 65537.0 */
261 const __m128i zero = _mm_setzero_si128();
262 const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
263 const __m128 offset = _mm_set1_ps(-65537.0);
264
265 LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using SSE2)");
266
267 CONVERT_16_REV({
268 _mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800000u)), offset));
269 }, {
270 const __m128i bytes = _mm_loadu_si128((const __m128i *)&src[i]);
271
272 const __m128i shorts0 = _mm_unpacklo_epi8(bytes, zero);
273 const __m128i shorts1 = _mm_unpackhi_epi8(bytes, zero);
274
275 const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
276 const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
277 const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
278 const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
279
280 _mm_store_ps(&dst[i], floats0);
281 _mm_store_ps(&dst[i + 4], floats1);
282 _mm_store_ps(&dst[i + 8], floats2);
283 _mm_store_ps(&dst[i + 12], floats3);
284 })
285}
286
287static void SDL_TARGETING("sse2") SDL_Convert_S16_to_F32_SSE2(float *dst, const Sint16 *src, int num_samples)
288{
289 /* 1) Flip the sign bit to convert from S16 to U16 format
290 * 2) Construct a float in the range [256.0, 258.0)
291 * 3) Shift the float range to [-1.0, 1.0)
292 * dst[i] = i2f((src[i] ^ 0x8000) | 0x43800000) - 257.0 */
293 const __m128i flipper = _mm_set1_epi16(-0x8000);
294 const __m128i caster = _mm_set1_epi16(0x4380 /* 0x43800000 = f2i(256.0) */);
295 const __m128 offset = _mm_set1_ps(-257.0f);
296
297 LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using SSE2)");
298
299 CONVERT_16_REV({
300 _mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint16)src[i] ^ 0x43808000u)), offset));
301 }, {
302 const __m128i shorts0 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
303 const __m128i shorts1 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i + 8]), flipper);
304
305 const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
306 const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
307 const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
308 const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
309
310 _mm_store_ps(&dst[i], floats0);
311 _mm_store_ps(&dst[i + 4], floats1);
312 _mm_store_ps(&dst[i + 8], floats2);
313 _mm_store_ps(&dst[i + 12], floats3);
314 })
315}
316
317static void SDL_TARGETING("sse2") SDL_Convert_S32_to_F32_SSE2(float *dst, const Sint32 *src, int num_samples)
318{
319 // dst[i] = f32(src[i]) / f32(0x80000000)
320 const __m128 scaler = _mm_set1_ps(DIVBY2147483648);
321
322 LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using SSE2)");
323
324 CONVERT_16_FWD({
325 _mm_store_ss(&dst[i], _mm_mul_ss(_mm_cvt_si2ss(_mm_setzero_ps(), src[i]), scaler));
326 }, {
327 const __m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]);
328 const __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 4]);
329 const __m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
330 const __m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 12]);
331
332 const __m128 floats0 = _mm_mul_ps(_mm_cvtepi32_ps(ints0), scaler);
333 const __m128 floats1 = _mm_mul_ps(_mm_cvtepi32_ps(ints1), scaler);
334 const __m128 floats2 = _mm_mul_ps(_mm_cvtepi32_ps(ints2), scaler);
335 const __m128 floats3 = _mm_mul_ps(_mm_cvtepi32_ps(ints3), scaler);
336
337 _mm_store_ps(&dst[i], floats0);
338 _mm_store_ps(&dst[i + 4], floats1);
339 _mm_store_ps(&dst[i + 8], floats2);
340 _mm_store_ps(&dst[i + 12], floats3);
341 })
342}
343
344static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const float *src, int num_samples)
345{
346 /* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
347 * 2) Extract the lowest 16 bits and clamp to [-128, 127]
348 * Overflow is correctly handled for inputs between roughly [-255.0, 255.0]
349 * dst[i] = clamp(i16(f2i(src[i] + 98304.0) & 0xFFFF), -128, 127) */
350 const __m128 offset = _mm_set1_ps(98304.0f);
351 const __m128i mask = _mm_set1_epi16(0xFF);
352
353 LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using SSE2)");
354
355 CONVERT_16_FWD({
356 const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset));
357 dst[i] = (Sint8)(_mm_cvtsi128_si32(_mm_packs_epi16(ints, ints)) & 0xFF);
358 }, {
359 const __m128 floats0 = _mm_loadu_ps(&src[i]);
360 const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
361 const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
362 const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
363
364 const __m128i ints0 = _mm_castps_si128(_mm_add_ps(floats0, offset));
365 const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
366 const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
367 const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
368
369 const __m128i shorts0 = _mm_and_si128(_mm_packs_epi16(ints0, ints1), mask);
370 const __m128i shorts1 = _mm_and_si128(_mm_packs_epi16(ints2, ints3), mask);
371
372 const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
373
374 _mm_store_si128((__m128i*)&dst[i], bytes);
375 })
376}
377
378static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const float *src, int num_samples)
379{
380 /* 1) Shift the float range from [-1.0, 1.0] to [98304.0, 98306.0]
381 * 2) Extract the lowest 16 bits and clamp to [0, 255]
382 * Overflow is correctly handled for inputs between roughly [-254.0, 254.0]
383 * dst[i] = clamp(i16(f2i(src[i] + 98305.0) & 0xFFFF), 0, 255) */
384 const __m128 offset = _mm_set1_ps(98305.0f);
385 const __m128i mask = _mm_set1_epi16(0xFF);
386
387 LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using SSE2)");
388
389 CONVERT_16_FWD({
390 const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset));
391 dst[i] = (Uint8)(_mm_cvtsi128_si32(_mm_packus_epi16(ints, ints)) & 0xFF);
392 }, {
393 const __m128 floats0 = _mm_loadu_ps(&src[i]);
394 const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
395 const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
396 const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
397
398 const __m128i ints0 = _mm_castps_si128(_mm_add_ps(floats0, offset));
399 const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
400 const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
401 const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
402
403 const __m128i shorts0 = _mm_and_si128(_mm_packus_epi16(ints0, ints1), mask);
404 const __m128i shorts1 = _mm_and_si128(_mm_packus_epi16(ints2, ints3), mask);
405
406 const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
407
408 _mm_store_si128((__m128i*)&dst[i], bytes);
409 })
410}
411
412static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const float *src, int num_samples)
413{
414 /* 1) Shift the float range from [-1.0, 1.0] to [256.0, 258.0]
415 * 2) Shift the int range from [0x43800000, 0x43810000] to [-32768,32768]
416 * 3) Clamp to range [-32768,32767]
417 * Overflow is correctly handled for inputs between roughly [-257.0, +inf)
418 * dst[i] = clamp(f2i(src[i] + 257.0) - 0x43808000, -32768, 32767) */
419 const __m128 offset = _mm_set1_ps(257.0f);
420
421 LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using SSE2)");
422
423 CONVERT_16_FWD({
424 const __m128i ints = _mm_sub_epi32(_mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset)), _mm_castps_si128(offset));
425 dst[i] = (Sint16)(_mm_cvtsi128_si32(_mm_packs_epi32(ints, ints)) & 0xFFFF);
426 }, {
427 const __m128 floats0 = _mm_loadu_ps(&src[i]);
428 const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
429 const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
430 const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
431
432 const __m128i ints0 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats0, offset)), _mm_castps_si128(offset));
433 const __m128i ints1 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats1, offset)), _mm_castps_si128(offset));
434 const __m128i ints2 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats2, offset)), _mm_castps_si128(offset));
435 const __m128i ints3 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats3, offset)), _mm_castps_si128(offset));
436
437 const __m128i shorts0 = _mm_packs_epi32(ints0, ints1);
438 const __m128i shorts1 = _mm_packs_epi32(ints2, ints3);
439
440 _mm_store_si128((__m128i*)&dst[i], shorts0);
441 _mm_store_si128((__m128i*)&dst[i + 8], shorts1);
442 })
443}
444
445static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const float *src, int num_samples)
446{
447 /* 1) Scale the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
448 * 2) Convert to integer (values too small/large become 0x80000000 = -2147483648)
449 * 3) Fixup values which were too large (0x80000000 ^ 0xFFFFFFFF = 2147483647)
450 * dst[i] = i32(src[i] * 2147483648.0) ^ ((src[i] >= 2147483648.0) ? 0xFFFFFFFF : 0x00000000) */
451 const __m128 limit = _mm_set1_ps(2147483648.0f);
452
453 LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using SSE2)");
454
455 CONVERT_16_FWD({
456 const __m128 floats = _mm_load_ss(&src[i]);
457 const __m128 values = _mm_mul_ss(floats, limit);
458 const __m128i ints = _mm_xor_si128(_mm_cvttps_epi32(values), _mm_castps_si128(_mm_cmpge_ss(values, limit)));
459 dst[i] = (Sint32)_mm_cvtsi128_si32(ints);
460 }, {
461 const __m128 floats0 = _mm_loadu_ps(&src[i]);
462 const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
463 const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
464 const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
465
466 const __m128 values1 = _mm_mul_ps(floats0, limit);
467 const __m128 values2 = _mm_mul_ps(floats1, limit);
468 const __m128 values3 = _mm_mul_ps(floats2, limit);
469 const __m128 values4 = _mm_mul_ps(floats3, limit);
470
471 const __m128i ints0 = _mm_xor_si128(_mm_cvttps_epi32(values1), _mm_castps_si128(_mm_cmpge_ps(values1, limit)));
472 const __m128i ints1 = _mm_xor_si128(_mm_cvttps_epi32(values2), _mm_castps_si128(_mm_cmpge_ps(values2, limit)));
473 const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit)));
474 const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit)));
475
476 _mm_store_si128((__m128i*)&dst[i], ints0);
477 _mm_store_si128((__m128i*)&dst[i + 4], ints1);
478 _mm_store_si128((__m128i*)&dst[i + 8], ints2);
479 _mm_store_si128((__m128i*)&dst[i + 12], ints3);
480 })
481}
482#endif
483
484// FIXME: SDL doesn't have SSSE3 detection, so use the next one up
485#ifdef SDL_SSE4_1_INTRINSICS
486static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16* dst, const Uint16* src, int num_samples)
487{
488 const __m128i shuffle = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
489
490 CONVERT_16_FWD({
491 dst[i] = SDL_Swap16(src[i]);
492 }, {
493 __m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]);
494 __m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 8]);
495
496 ints0 = _mm_shuffle_epi8(ints0, shuffle);
497 ints1 = _mm_shuffle_epi8(ints1, shuffle);
498
499 _mm_store_si128((__m128i*)&dst[i], ints0);
500 _mm_store_si128((__m128i*)&dst[i + 8], ints1);
501 })
502}
503
504static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32* dst, const Uint32* src, int num_samples)
505{
506 const __m128i shuffle = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
507
508 CONVERT_16_FWD({
509 dst[i] = SDL_Swap32(src[i]);
510 }, {
511 __m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]);
512 __m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 4]);
513 __m128i ints2 = _mm_loadu_si128((const __m128i*)&src[i + 8]);
514 __m128i ints3 = _mm_loadu_si128((const __m128i*)&src[i + 12]);
515
516 ints0 = _mm_shuffle_epi8(ints0, shuffle);
517 ints1 = _mm_shuffle_epi8(ints1, shuffle);
518 ints2 = _mm_shuffle_epi8(ints2, shuffle);
519 ints3 = _mm_shuffle_epi8(ints3, shuffle);
520
521 _mm_store_si128((__m128i*)&dst[i], ints0);
522 _mm_store_si128((__m128i*)&dst[i + 4], ints1);
523 _mm_store_si128((__m128i*)&dst[i + 8], ints2);
524 _mm_store_si128((__m128i*)&dst[i + 12], ints3);
525 })
526}
527#endif
528
529#ifdef SDL_NEON_INTRINSICS
530static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples)
531{
532 LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)");
533
534 CONVERT_16_REV({
535 vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 7), 0);
536 }, {
537 int8x16_t bytes = vld1q_s8(&src[i]);
538
539 int16x8_t shorts0 = vmovl_s8(vget_low_s8(bytes));
540 int16x8_t shorts1 = vmovl_s8(vget_high_s8(bytes));
541
542 float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 7);
543 float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 7);
544 float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 7);
545 float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 7);
546
547 vst1q_f32(&dst[i], floats0);
548 vst1q_f32(&dst[i + 4], floats1);
549 vst1q_f32(&dst[i + 8], floats2);
550 vst1q_f32(&dst[i + 12], floats3);
551 })
552}
553
554static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples)
555{
556 LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)");
557
558 uint8x16_t flipper = vdupq_n_u8(0x80);
559
560 CONVERT_16_REV({
561 vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32((Sint8)(src[i] ^ 0x80)), 7), 0);
562 }, {
563 int8x16_t bytes = vreinterpretq_s8_u8(veorq_u8(vld1q_u8(&src[i]), flipper));
564
565 int16x8_t shorts0 = vmovl_s8(vget_low_s8(bytes));
566 int16x8_t shorts1 = vmovl_s8(vget_high_s8(bytes));
567
568 float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 7);
569 float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 7);
570 float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 7);
571 float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 7);
572
573 vst1q_f32(&dst[i], floats0);
574 vst1q_f32(&dst[i + 4], floats1);
575 vst1q_f32(&dst[i + 8], floats2);
576 vst1q_f32(&dst[i + 12], floats3);
577 })
578}
579
580static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples)
581{
582 LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)");
583
584 CONVERT_16_REV({
585 vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 15), 0);
586 }, {
587 int16x8_t shorts0 = vld1q_s16(&src[i]);
588 int16x8_t shorts1 = vld1q_s16(&src[i + 8]);
589
590 float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 15);
591 float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 15);
592 float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 15);
593 float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 15);
594
595 vst1q_f32(&dst[i], floats0);
596 vst1q_f32(&dst[i + 4], floats1);
597 vst1q_f32(&dst[i + 8], floats2);
598 vst1q_f32(&dst[i + 12], floats3);
599 })
600}
601
602static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples)
603{
604 LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)");
605
606 CONVERT_16_FWD({
607 vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vld1_dup_s32(&src[i]), 31), 0);
608 }, {
609 int32x4_t ints0 = vld1q_s32(&src[i]);
610 int32x4_t ints1 = vld1q_s32(&src[i + 4]);
611 int32x4_t ints2 = vld1q_s32(&src[i + 8]);
612 int32x4_t ints3 = vld1q_s32(&src[i + 12]);
613
614 float32x4_t floats0 = vcvtq_n_f32_s32(ints0, 31);
615 float32x4_t floats1 = vcvtq_n_f32_s32(ints1, 31);
616 float32x4_t floats2 = vcvtq_n_f32_s32(ints2, 31);
617 float32x4_t floats3 = vcvtq_n_f32_s32(ints3, 31);
618
619 vst1q_f32(&dst[i], floats0);
620 vst1q_f32(&dst[i + 4], floats1);
621 vst1q_f32(&dst[i + 8], floats2);
622 vst1q_f32(&dst[i + 12], floats3);
623 })
624}
625
626static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples)
627{
628 LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)");
629
630 CONVERT_16_FWD({
631 vst1_lane_s8(&dst[i], vreinterpret_s8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 3);
632 }, {
633 float32x4_t floats0 = vld1q_f32(&src[i]);
634 float32x4_t floats1 = vld1q_f32(&src[i + 4]);
635 float32x4_t floats2 = vld1q_f32(&src[i + 8]);
636 float32x4_t floats3 = vld1q_f32(&src[i + 12]);
637
638 int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
639 int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
640 int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
641 int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
642
643 int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
644 int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
645
646 int8x16_t bytes = vcombine_s8(vshrn_n_s16(shorts0, 8), vshrn_n_s16(shorts1, 8));
647
648 vst1q_s8(&dst[i], bytes);
649 })
650}
651
652static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples)
653{
654 LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)");
655
656 uint8x16_t flipper = vdupq_n_u8(0x80);
657
658 CONVERT_16_FWD({
659 vst1_lane_u8(&dst[i],
660 veor_u8(vreinterpret_u8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)),
661 vget_low_u8(flipper)), 3);
662 }, {
663 float32x4_t floats0 = vld1q_f32(&src[i]);
664 float32x4_t floats1 = vld1q_f32(&src[i + 4]);
665 float32x4_t floats2 = vld1q_f32(&src[i + 8]);
666 float32x4_t floats3 = vld1q_f32(&src[i + 12]);
667
668 int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
669 int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
670 int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
671 int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
672
673 int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
674 int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
675
676 uint8x16_t bytes = veorq_u8(vreinterpretq_u8_s8(
677 vcombine_s8(vshrn_n_s16(shorts0, 8), vshrn_n_s16(shorts1, 8))),
678 flipper);
679
680 vst1q_u8(&dst[i], bytes);
681 })
682}
683
684static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples)
685{
686 LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)");
687
688 CONVERT_16_FWD({
689 vst1_lane_s16(&dst[i], vreinterpret_s16_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 1);
690 }, {
691 float32x4_t floats0 = vld1q_f32(&src[i]);
692 float32x4_t floats1 = vld1q_f32(&src[i + 4]);
693 float32x4_t floats2 = vld1q_f32(&src[i + 8]);
694 float32x4_t floats3 = vld1q_f32(&src[i + 12]);
695
696 int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
697 int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
698 int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
699 int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
700
701 int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
702 int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
703
704 vst1q_s16(&dst[i], shorts0);
705 vst1q_s16(&dst[i + 8], shorts1);
706 })
707}
708
709static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples)
710{
711 LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)");
712
713 CONVERT_16_FWD({
714 vst1_lane_s32(&dst[i], vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31), 0);
715 }, {
716 float32x4_t floats0 = vld1q_f32(&src[i]);
717 float32x4_t floats1 = vld1q_f32(&src[i + 4]);
718 float32x4_t floats2 = vld1q_f32(&src[i + 8]);
719 float32x4_t floats3 = vld1q_f32(&src[i + 12]);
720
721 int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
722 int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
723 int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
724 int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
725
726 vst1q_s32(&dst[i], ints0);
727 vst1q_s32(&dst[i + 4], ints1);
728 vst1q_s32(&dst[i + 8], ints2);
729 vst1q_s32(&dst[i + 12], ints3);
730 })
731}
732
733static void SDL_Convert_Swap16_NEON(Uint16* dst, const Uint16* src, int num_samples)
734{
735 CONVERT_16_FWD({
736 dst[i] = SDL_Swap16(src[i]);
737 }, {
738 uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]);
739 uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 8]);
740
741 ints0 = vrev16q_u8(ints0);
742 ints1 = vrev16q_u8(ints1);
743
744 vst1q_u8((Uint8*)&dst[i], ints0);
745 vst1q_u8((Uint8*)&dst[i + 8], ints1);
746 })
747}
748
749static void SDL_Convert_Swap32_NEON(Uint32* dst, const Uint32* src, int num_samples)
750{
751 CONVERT_16_FWD({
752 dst[i] = SDL_Swap32(src[i]);
753 }, {
754 uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]);
755 uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 4]);
756 uint8x16_t ints2 = vld1q_u8((const Uint8*)&src[i + 8]);
757 uint8x16_t ints3 = vld1q_u8((const Uint8*)&src[i + 12]);
758
759 ints0 = vrev32q_u8(ints0);
760 ints1 = vrev32q_u8(ints1);
761 ints2 = vrev32q_u8(ints2);
762 ints3 = vrev32q_u8(ints3);
763
764 vst1q_u8((Uint8*)&dst[i], ints0);
765 vst1q_u8((Uint8*)&dst[i + 4], ints1);
766 vst1q_u8((Uint8*)&dst[i + 8], ints2);
767 vst1q_u8((Uint8*)&dst[i + 12], ints3);
768 })
769}
770#endif
771
772#undef CONVERT_16_FWD
773#undef CONVERT_16_REV
774
775// Function pointers set to a CPU-specific implementation.
776static void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples) = NULL;
777static void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples) = NULL;
778static void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples) = NULL;
779static void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples) = NULL;
780static void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples) = NULL;
781static void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples) = NULL;
782static void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL;
783static void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL;
784
785static void (*SDL_Convert_Swap16)(Uint16* dst, const Uint16* src, int num_samples) = NULL;
786static void (*SDL_Convert_Swap32)(Uint32* dst, const Uint32* src, int num_samples) = NULL;
787
788void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt)
789{
790 switch (src_fmt) {
791 case SDL_AUDIO_S8:
792 SDL_Convert_S8_to_F32(dst, (const Sint8 *) src, num_samples);
793 break;
794
795 case SDL_AUDIO_U8:
796 SDL_Convert_U8_to_F32(dst, (const Uint8 *) src, num_samples);
797 break;
798
799 case SDL_AUDIO_S16:
800 SDL_Convert_S16_to_F32(dst, (const Sint16 *) src, num_samples);
801 break;
802
803 case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
804 SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples);
805 SDL_Convert_S16_to_F32(dst, (const Sint16 *) dst, num_samples);
806 break;
807
808 case SDL_AUDIO_S32:
809 SDL_Convert_S32_to_F32(dst, (const Sint32 *) src, num_samples);
810 break;
811
812 case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
813 SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
814 SDL_Convert_S32_to_F32(dst, (const Sint32 *) dst, num_samples);
815 break;
816
817 case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
818 SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
819 break;
820
821 default: SDL_assert(!"Unexpected audio format!"); break;
822 }
823}
824
825void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt)
826{
827 switch (dst_fmt) {
828 case SDL_AUDIO_S8:
829 SDL_Convert_F32_to_S8((Sint8 *) dst, src, num_samples);
830 break;
831
832 case SDL_AUDIO_U8:
833 SDL_Convert_F32_to_U8((Uint8 *) dst, src, num_samples);
834 break;
835
836 case SDL_AUDIO_S16:
837 SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples);
838 break;
839
840 case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
841 SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples);
842 SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) dst, num_samples);
843 break;
844
845 case SDL_AUDIO_S32:
846 SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
847 break;
848
849 case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
850 SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
851 SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) dst, num_samples);
852 break;
853
854 case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
855 SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
856 break;
857
858 default: SDL_assert(!"Unexpected audio format!"); break;
859 }
860}
861
862void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize)
863{
864 switch (bitsize) {
865 case 16: SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples); break;
866 case 32: SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); break;
867 default: SDL_assert(!"Unexpected audio format!"); break;
868 }
869}
870
871void SDL_ChooseAudioConverters(void)
872{
873 static bool converters_chosen = false;
874 if (converters_chosen) {
875 return;
876 }
877
878#define SET_CONVERTER_FUNCS(fntype) \
879 SDL_Convert_Swap16 = SDL_Convert_Swap16_##fntype; \
880 SDL_Convert_Swap32 = SDL_Convert_Swap32_##fntype;
881
882#ifdef SDL_SSE4_1_INTRINSICS
883 if (SDL_HasSSE41()) {
884 SET_CONVERTER_FUNCS(SSSE3);
885 } else
886#endif
887#ifdef SDL_NEON_INTRINSICS
888 if (SDL_HasNEON()) {
889 SET_CONVERTER_FUNCS(NEON);
890 } else
891#endif
892 {
893 SET_CONVERTER_FUNCS(Scalar);
894 }
895
896#undef SET_CONVERTER_FUNCS
897
898#define SET_CONVERTER_FUNCS(fntype) \
899 SDL_Convert_S8_to_F32 = SDL_Convert_S8_to_F32_##fntype; \
900 SDL_Convert_U8_to_F32 = SDL_Convert_U8_to_F32_##fntype; \
901 SDL_Convert_S16_to_F32 = SDL_Convert_S16_to_F32_##fntype; \
902 SDL_Convert_S32_to_F32 = SDL_Convert_S32_to_F32_##fntype; \
903 SDL_Convert_F32_to_S8 = SDL_Convert_F32_to_S8_##fntype; \
904 SDL_Convert_F32_to_U8 = SDL_Convert_F32_to_U8_##fntype; \
905 SDL_Convert_F32_to_S16 = SDL_Convert_F32_to_S16_##fntype; \
906 SDL_Convert_F32_to_S32 = SDL_Convert_F32_to_S32_##fntype; \
907
908#ifdef SDL_SSE2_INTRINSICS
909 if (SDL_HasSSE2()) {
910 SET_CONVERTER_FUNCS(SSE2);
911 } else
912#endif
913#ifdef SDL_NEON_INTRINSICS
914 if (SDL_HasNEON()) {
915 SET_CONVERTER_FUNCS(NEON);
916 } else
917#endif
918 {
919 SET_CONVERTER_FUNCS(Scalar);
920 }
921
922#undef SET_CONVERTER_FUNCS
923
924 converters_chosen = true;
925}
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_mixer.c b/contrib/SDL-3.2.8/src/audio/SDL_mixer.c
new file mode 100644
index 0000000..047c6fd
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_mixer.c
@@ -0,0 +1,290 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// This provides the default mixing callback for the SDL audio routines
24
25#include "SDL_sysaudio.h"
26
27/* This table is used to add two sound values together and pin
28 * the value to avoid overflow. (used with permission from ARDI)
29 */
30static const Uint8 mix8[] = {
31 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
32 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
33 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
34 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
36 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
37 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
38 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
39 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
40 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
41 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
42 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
43 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
44 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
45 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
46 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
47 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
48 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
49 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
50 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B,
51 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
52 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
53 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,
54 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
55 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92,
56 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
57 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
58 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
59 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
60 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
61 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
62 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
63 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
64 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
65 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF,
66 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
67 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
68 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
69 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
70 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
71 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
72 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
73 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
74 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
75 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
76 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
77 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
78};
79
80// The volume ranges from 0 - 128
81#define MIX_MAXVOLUME 128
82#define ADJUST_VOLUME(type, s, v) ((s) = (type)(((s) * (v)) / MIX_MAXVOLUME))
83#define ADJUST_VOLUME_U8(s, v) ((s) = (Uint8)(((((s) - 128) * (v)) / MIX_MAXVOLUME) + 128))
84
85// !!! FIXME: This needs some SIMD magic.
86// !!! FIXME: Add fast-path for volume = 1
87// !!! FIXME: Use larger scales for 16-bit/32-bit integers
88
89bool SDL_MixAudio(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, float fvolume)
90{
91 int volume = (int)SDL_roundf(fvolume * MIX_MAXVOLUME);
92
93 if (volume == 0) {
94 return true;
95 }
96
97 switch (format) {
98
99 case SDL_AUDIO_U8:
100 {
101 Uint8 src_sample;
102
103 while (len--) {
104 src_sample = *src;
105 ADJUST_VOLUME_U8(src_sample, volume);
106 *dst = mix8[*dst + src_sample];
107 ++dst;
108 ++src;
109 }
110 } break;
111
112 case SDL_AUDIO_S8:
113 {
114 Sint8 *dst8, *src8;
115 Sint8 src_sample;
116 int dst_sample;
117 const int max_audioval = SDL_MAX_SINT8;
118 const int min_audioval = SDL_MIN_SINT8;
119
120 src8 = (Sint8 *)src;
121 dst8 = (Sint8 *)dst;
122 while (len--) {
123 src_sample = *src8;
124 ADJUST_VOLUME(Sint8, src_sample, volume);
125 dst_sample = *dst8 + src_sample;
126 if (dst_sample > max_audioval) {
127 dst_sample = max_audioval;
128 } else if (dst_sample < min_audioval) {
129 dst_sample = min_audioval;
130 }
131 *dst8 = (Sint8)dst_sample;
132 ++dst8;
133 ++src8;
134 }
135 } break;
136
137 case SDL_AUDIO_S16LE:
138 {
139 Sint16 src1, src2;
140 int dst_sample;
141 const int max_audioval = SDL_MAX_SINT16;
142 const int min_audioval = SDL_MIN_SINT16;
143
144 len /= 2;
145 while (len--) {
146 src1 = SDL_Swap16LE(*(Sint16 *)src);
147 ADJUST_VOLUME(Sint16, src1, volume);
148 src2 = SDL_Swap16LE(*(Sint16 *)dst);
149 src += 2;
150 dst_sample = src1 + src2;
151 if (dst_sample > max_audioval) {
152 dst_sample = max_audioval;
153 } else if (dst_sample < min_audioval) {
154 dst_sample = min_audioval;
155 }
156 *(Sint16 *)dst = SDL_Swap16LE((Sint16)dst_sample);
157 dst += 2;
158 }
159 } break;
160
161 case SDL_AUDIO_S16BE:
162 {
163 Sint16 src1, src2;
164 int dst_sample;
165 const int max_audioval = SDL_MAX_SINT16;
166 const int min_audioval = SDL_MIN_SINT16;
167
168 len /= 2;
169 while (len--) {
170 src1 = SDL_Swap16BE(*(Sint16 *)src);
171 ADJUST_VOLUME(Sint16, src1, volume);
172 src2 = SDL_Swap16BE(*(Sint16 *)dst);
173 src += 2;
174 dst_sample = src1 + src2;
175 if (dst_sample > max_audioval) {
176 dst_sample = max_audioval;
177 } else if (dst_sample < min_audioval) {
178 dst_sample = min_audioval;
179 }
180 *(Sint16 *)dst = SDL_Swap16BE((Sint16)dst_sample);
181 dst += 2;
182 }
183 } break;
184
185 case SDL_AUDIO_S32LE:
186 {
187 const Uint32 *src32 = (Uint32 *)src;
188 Uint32 *dst32 = (Uint32 *)dst;
189 Sint64 src1, src2;
190 Sint64 dst_sample;
191 const Sint64 max_audioval = SDL_MAX_SINT32;
192 const Sint64 min_audioval = SDL_MIN_SINT32;
193
194 len /= 4;
195 while (len--) {
196 src1 = (Sint64)((Sint32)SDL_Swap32LE(*src32));
197 src32++;
198 ADJUST_VOLUME(Sint64, src1, volume);
199 src2 = (Sint64)((Sint32)SDL_Swap32LE(*dst32));
200 dst_sample = src1 + src2;
201 if (dst_sample > max_audioval) {
202 dst_sample = max_audioval;
203 } else if (dst_sample < min_audioval) {
204 dst_sample = min_audioval;
205 }
206 *(dst32++) = SDL_Swap32LE((Uint32)((Sint32)dst_sample));
207 }
208 } break;
209
210 case SDL_AUDIO_S32BE:
211 {
212 const Uint32 *src32 = (Uint32 *)src;
213 Uint32 *dst32 = (Uint32 *)dst;
214 Sint64 src1, src2;
215 Sint64 dst_sample;
216 const Sint64 max_audioval = SDL_MAX_SINT32;
217 const Sint64 min_audioval = SDL_MIN_SINT32;
218
219 len /= 4;
220 while (len--) {
221 src1 = (Sint64)((Sint32)SDL_Swap32BE(*src32));
222 src32++;
223 ADJUST_VOLUME(Sint64, src1, volume);
224 src2 = (Sint64)((Sint32)SDL_Swap32BE(*dst32));
225 dst_sample = src1 + src2;
226 if (dst_sample > max_audioval) {
227 dst_sample = max_audioval;
228 } else if (dst_sample < min_audioval) {
229 dst_sample = min_audioval;
230 }
231 *(dst32++) = SDL_Swap32BE((Uint32)((Sint32)dst_sample));
232 }
233 } break;
234
235 case SDL_AUDIO_F32LE:
236 {
237 const float *src32 = (float *)src;
238 float *dst32 = (float *)dst;
239 float src1, src2;
240 float dst_sample;
241 const float max_audioval = 1.0f;
242 const float min_audioval = -1.0f;
243
244 len /= 4;
245 while (len--) {
246 src1 = SDL_SwapFloatLE(*src32) * fvolume;
247 src2 = SDL_SwapFloatLE(*dst32);
248 src32++;
249
250 dst_sample = src1 + src2;
251 if (dst_sample > max_audioval) {
252 dst_sample = max_audioval;
253 } else if (dst_sample < min_audioval) {
254 dst_sample = min_audioval;
255 }
256 *(dst32++) = SDL_SwapFloatLE(dst_sample);
257 }
258 } break;
259
260 case SDL_AUDIO_F32BE:
261 {
262 const float *src32 = (float *)src;
263 float *dst32 = (float *)dst;
264 float src1, src2;
265 float dst_sample;
266 const float max_audioval = 1.0f;
267 const float min_audioval = -1.0f;
268
269 len /= 4;
270 while (len--) {
271 src1 = SDL_SwapFloatBE(*src32) * fvolume;
272 src2 = SDL_SwapFloatBE(*dst32);
273 src32++;
274
275 dst_sample = src1 + src2;
276 if (dst_sample > max_audioval) {
277 dst_sample = max_audioval;
278 } else if (dst_sample < min_audioval) {
279 dst_sample = min_audioval;
280 }
281 *(dst32++) = SDL_SwapFloatBE(dst_sample);
282 }
283 } break;
284
285 default: // If this happens... FIXME!
286 return SDL_SetError("SDL_MixAudio(): unknown audio format");
287 }
288
289 return true;
290}
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_sysaudio.h b/contrib/SDL-3.2.8/src/audio/SDL_sysaudio.h
new file mode 100644
index 0000000..4a88bd2
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_sysaudio.h
@@ -0,0 +1,392 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifndef SDL_sysaudio_h_
25#define SDL_sysaudio_h_
26
27#define DEBUG_AUDIOSTREAM 0
28#define DEBUG_AUDIO_CONVERT 0
29
30#if DEBUG_AUDIO_CONVERT
31#define LOG_DEBUG_AUDIO_CONVERT(from, to) SDL_Log("SDL_AUDIO_CONVERT: Converting %s to %s.", from, to);
32#else
33#define LOG_DEBUG_AUDIO_CONVERT(from, to)
34#endif
35
36// !!! FIXME: These are wordy and unlocalized...
37#define DEFAULT_PLAYBACK_DEVNAME "System audio playback device"
38#define DEFAULT_RECORDING_DEVNAME "System audio recording device"
39
40// these are used when no better specifics are known. We default to CD audio quality.
41#define DEFAULT_AUDIO_PLAYBACK_FORMAT SDL_AUDIO_S16
42#define DEFAULT_AUDIO_PLAYBACK_CHANNELS 2
43#define DEFAULT_AUDIO_PLAYBACK_FREQUENCY 44100
44
45#define DEFAULT_AUDIO_RECORDING_FORMAT SDL_AUDIO_S16
46#define DEFAULT_AUDIO_RECORDING_CHANNELS 1
47#define DEFAULT_AUDIO_RECORDING_FREQUENCY 44100
48
49#define SDL_MAX_CHANNELMAP_CHANNELS 8 // !!! FIXME: if SDL ever supports more channels, clean this out and make those parts dynamic.
50
51typedef struct SDL_AudioDevice SDL_AudioDevice;
52typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice;
53
54// Used by src/SDL.c to initialize a particular audio driver.
55extern bool SDL_InitAudio(const char *driver_name);
56
57// Used by src/SDL.c to shut down previously-initialized audio.
58extern void SDL_QuitAudio(void);
59
60// Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results.
61const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format);
62
63// Must be called at least once before using converters.
64extern void SDL_ChooseAudioConverters(void);
65extern void SDL_SetupAudioResampler(void);
66
67/* Backends should call this as devices are added to the system (such as
68 a USB headset being plugged in), and should also be called for
69 for every device found during DetectDevices(). */
70extern SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_AudioSpec *spec, void *handle);
71
72/* Backends should call this if an opened audio device is lost.
73 This can happen due to i/o errors, or a device being unplugged, etc. */
74extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
75
76// Backends should call this if the system default device changes.
77extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
78
79// Backends should call this if a device's format is changing (opened or not); SDL will update state and carry on with the new format.
80extern bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
81
82// Same as above, but assume the device is already locked.
83extern bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
84
85// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
86extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);
87
88// Find an SDL_AudioDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
89extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata);
90
91// Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct.
92extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device);
93
94// Backends can call this to get a reasonable default sample frame count for a device's sample rate.
95int SDL_GetDefaultSampleFramesFromFreq(const int freq);
96
97// Backends can call this to get a standardized name for a thread to power a specific audio device.
98extern char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen);
99
100// Backends can call these to change a device's refcount.
101extern void RefPhysicalAudioDevice(SDL_AudioDevice *device);
102extern void UnrefPhysicalAudioDevice(SDL_AudioDevice *device);
103
104// These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread.
105extern void SDL_PlaybackAudioThreadSetup(SDL_AudioDevice *device);
106extern bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device);
107extern void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device);
108extern void SDL_RecordingAudioThreadSetup(SDL_AudioDevice *device);
109extern bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device);
110extern void SDL_RecordingAudioThreadShutdown(SDL_AudioDevice *device);
111extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device);
112
113extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt);
114extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt);
115extern void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize);
116
117extern bool SDL_ChannelMapIsDefault(const int *map, int channels);
118extern bool SDL_ChannelMapIsBogus(const int *map, int channels);
119
120// this gets used from the audio device threads. It has rules, don't use this if you don't know how to use it!
121extern void ConvertAudio(int num_frames,
122 const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
123 void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
124 void* scratch, float gain);
125
126// Compare two SDL_AudioSpecs, return true if they match exactly.
127// Using SDL_memcmp directly isn't safe, since potential padding might not be initialized.
128// either channel map can be NULL for the default (and both should be if you don't care about them).
129extern bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b, const int *channel_map_a, const int *channel_map_b);
130
131// See if two channel maps match
132// either channel map can be NULL for the default (and both should be if you don't care about them).
133extern bool SDL_AudioChannelMapsEqual(int channels, const int *channel_map_a, const int *channel_map_b);
134
135// allocate+copy a channel map.
136extern int *SDL_ChannelMapDup(const int *origchmap, int channels);
137
138// Special case to let something in SDL_audiocvt.c access something in SDL_audio.c. Don't use this.
139extern void OnAudioStreamCreated(SDL_AudioStream *stream);
140extern void OnAudioStreamDestroy(SDL_AudioStream *stream);
141
142// This just lets audio playback apply logical device gain at the same time as audiostream gain, so it's one multiplication instead of thousands.
143extern int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int len, float extra_gain);
144
145// This is the bulk of `SDL_SetAudioStream*putChannelMap`'s work, but it lets you skip the check about changing the device end of a stream if isinput==-1.
146extern bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput);
147
148
149typedef struct SDL_AudioDriverImpl
150{
151 void (*DetectDevices)(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording);
152 bool (*OpenDevice)(SDL_AudioDevice *device);
153 void (*ThreadInit)(SDL_AudioDevice *device); // Called by audio thread at start
154 void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end
155 bool (*WaitDevice)(SDL_AudioDevice *device);
156 bool (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); // buffer and buflen are always from GetDeviceBuf, passed here for convenience.
157 Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
158 bool (*WaitRecordingDevice)(SDL_AudioDevice *device);
159 int (*RecordDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
160 void (*FlushRecording)(SDL_AudioDevice *device);
161 void (*CloseDevice)(SDL_AudioDevice *device);
162 void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice()
163 void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection.
164 void (*Deinitialize)(void);
165
166 // Some flags to push duplicate code into the core and reduce #ifdefs.
167 bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore.
168 bool HasRecordingSupport;
169 bool OnlyHasDefaultPlaybackDevice;
170 bool OnlyHasDefaultRecordingDevice; // !!! FIXME: is there ever a time where you'd have a default playback and not a default recording (or vice versa)?
171} SDL_AudioDriverImpl;
172
173
174typedef struct SDL_PendingAudioDeviceEvent
175{
176 Uint32 type;
177 SDL_AudioDeviceID devid;
178 struct SDL_PendingAudioDeviceEvent *next;
179} SDL_PendingAudioDeviceEvent;
180
181typedef struct SDL_AudioDriver
182{
183 const char *name; // The name of this audio driver
184 const char *desc; // The description of this audio driver
185 SDL_AudioDriverImpl impl; // the backend's interface
186 SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash`
187 SDL_HashTable *device_hash; // the collection of currently-available audio devices (recording, playback, logical and physical!)
188 SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams.
189 SDL_AudioDeviceID default_playback_device_id;
190 SDL_AudioDeviceID default_recording_device_id;
191 SDL_PendingAudioDeviceEvent pending_events;
192 SDL_PendingAudioDeviceEvent *pending_events_tail;
193
194 // !!! FIXME: most (all?) of these don't have to be atomic.
195 SDL_AtomicInt playback_device_count;
196 SDL_AtomicInt recording_device_count;
197 SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
198} SDL_AudioDriver;
199
200struct SDL_AudioQueue; // forward decl.
201
202struct SDL_AudioStream
203{
204 SDL_Mutex* lock;
205
206 SDL_PropertiesID props;
207
208 SDL_AudioStreamCallback get_callback;
209 void *get_callback_userdata;
210 SDL_AudioStreamCallback put_callback;
211 void *put_callback_userdata;
212
213 SDL_AudioSpec src_spec;
214 SDL_AudioSpec dst_spec;
215 int *src_chmap;
216 int *dst_chmap;
217 float freq_ratio;
218 float gain;
219
220 struct SDL_AudioQueue* queue;
221
222 SDL_AudioSpec input_spec; // The spec of input data currently being processed
223 int *input_chmap;
224 int input_chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
225 Sint64 resample_offset;
226
227 Uint8 *work_buffer; // used for scratch space during data conversion/resampling.
228 size_t work_buffer_allocation;
229
230 bool simplified; // true if created via SDL_OpenAudioDeviceStream
231
232 SDL_LogicalAudioDevice *bound_device;
233 SDL_AudioStream *next_binding;
234 SDL_AudioStream *prev_binding;
235
236 SDL_AudioStream *prev; // linked list of all existing streams (so we can free them on shutdown).
237 SDL_AudioStream *next; // linked list of all existing streams (so we can free them on shutdown).
238};
239
240/* Logical devices are an abstraction in SDL3; you can open the same physical
241 device multiple times, and each will result in an object with its own set
242 of bound audio streams, etc, even though internally these are all processed
243 as a group when mixing the final output for the physical device. */
244struct SDL_LogicalAudioDevice
245{
246 // the unique instance ID of this device.
247 SDL_AudioDeviceID instance_id;
248
249 // The physical device associated with this opened device.
250 SDL_AudioDevice *physical_device;
251
252 // If whole logical device is paused (process no streams bound to this device).
253 SDL_AtomicInt paused;
254
255 // Volume of the device output.
256 float gain;
257
258 // double-linked list of all audio streams currently bound to this opened device.
259 SDL_AudioStream *bound_streams;
260
261 // true if this was opened as a default device.
262 bool opened_as_default;
263
264 // true if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc).
265 bool simplified;
266
267 // If non-NULL, callback into the app that lets them access the final postmix buffer.
268 SDL_AudioPostmixCallback postmix;
269
270 // App-supplied pointer for postmix callback.
271 void *postmix_userdata;
272
273 // double-linked list of opened devices on the same physical device.
274 SDL_LogicalAudioDevice *next;
275 SDL_LogicalAudioDevice *prev;
276};
277
278struct SDL_AudioDevice
279{
280 // A mutex for locking access to this struct
281 SDL_Mutex *lock;
282
283 // A condition variable to protect device close, where we can't hold the device lock forever.
284 SDL_Condition *close_cond;
285
286 // Reference count of the device; logical devices, device threads, etc, add to this.
287 SDL_AtomicInt refcount;
288
289 // These are, initially, set from current_audio, but we might swap them out with Zombie versions on disconnect/failure.
290 bool (*WaitDevice)(SDL_AudioDevice *device);
291 bool (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);
292 Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
293 bool (*WaitRecordingDevice)(SDL_AudioDevice *device);
294 int (*RecordDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
295 void (*FlushRecording)(SDL_AudioDevice *device);
296
297 // human-readable name of the device. ("SoundBlaster Pro 16")
298 char *name;
299
300 // the unique instance ID of this device.
301 SDL_AudioDeviceID instance_id;
302
303 // a way for the backend to identify this device _when not opened_
304 void *handle;
305
306 // The device's current audio specification
307 SDL_AudioSpec spec;
308
309 // The size, in bytes, of the device's playback/recording buffer.
310 int buffer_size;
311
312 // The device's channel map, or NULL for SDL default layout.
313 int *chmap;
314
315 // The device's default audio specification
316 SDL_AudioSpec default_spec;
317
318 // Number of sample frames the devices wants per-buffer.
319 int sample_frames;
320
321 // Value to use for SDL_memset to silence a buffer in this device's format
322 int silence_value;
323
324 // non-zero if we are signaling the audio thread to end.
325 SDL_AtomicInt shutdown;
326
327 // non-zero if this was a disconnected device and we're waiting for it to be decommissioned.
328 SDL_AtomicInt zombie;
329
330 // true if this is a recording device instead of an playback device
331 bool recording;
332
333 // true if audio thread can skip silence/mix/convert stages and just do a basic memcpy.
334 bool simple_copy;
335
336 // Scratch buffers used for mixing.
337 Uint8 *work_buffer;
338 Uint8 *mix_buffer;
339 float *postmix_buffer;
340
341 // Size of work_buffer (and mix_buffer) in bytes.
342 int work_buffer_size;
343
344 // A thread to feed the audio device
345 SDL_Thread *thread;
346
347 // true if this physical device is currently opened by the backend.
348 bool currently_opened;
349
350 // Data private to this driver
351 struct SDL_PrivateAudioData *hidden;
352
353 // All logical devices associated with this physical device.
354 SDL_LogicalAudioDevice *logical_devices;
355};
356
357typedef struct AudioBootStrap
358{
359 const char *name;
360 const char *desc;
361 bool (*init)(SDL_AudioDriverImpl *impl);
362 bool demand_only; // if true: request explicitly, or it won't be available.
363 bool is_preferred;
364} AudioBootStrap;
365
366// Not all of these are available in a given build. Use #ifdefs, etc.
367extern AudioBootStrap PRIVATEAUDIO_bootstrap;
368extern AudioBootStrap PIPEWIRE_PREFERRED_bootstrap;
369extern AudioBootStrap PIPEWIRE_bootstrap;
370extern AudioBootStrap PULSEAUDIO_bootstrap;
371extern AudioBootStrap ALSA_bootstrap;
372extern AudioBootStrap JACK_bootstrap;
373extern AudioBootStrap SNDIO_bootstrap;
374extern AudioBootStrap NETBSDAUDIO_bootstrap;
375extern AudioBootStrap DSP_bootstrap;
376extern AudioBootStrap WASAPI_bootstrap;
377extern AudioBootStrap DSOUND_bootstrap;
378extern AudioBootStrap WINMM_bootstrap;
379extern AudioBootStrap HAIKUAUDIO_bootstrap;
380extern AudioBootStrap COREAUDIO_bootstrap;
381extern AudioBootStrap DISKAUDIO_bootstrap;
382extern AudioBootStrap DUMMYAUDIO_bootstrap;
383extern AudioBootStrap AAUDIO_bootstrap;
384extern AudioBootStrap OPENSLES_bootstrap;
385extern AudioBootStrap PS2AUDIO_bootstrap;
386extern AudioBootStrap PSPAUDIO_bootstrap;
387extern AudioBootStrap VITAAUD_bootstrap;
388extern AudioBootStrap N3DSAUDIO_bootstrap;
389extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
390extern AudioBootStrap QSAAUDIO_bootstrap;
391
392#endif // SDL_sysaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_wave.c b/contrib/SDL-3.2.8/src/audio/SDL_wave.c
new file mode 100644
index 0000000..1d53e79
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_wave.c
@@ -0,0 +1,2151 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef HAVE_LIMITS_H
24#include <limits.h>
25#endif
26#ifndef INT_MAX
27SDL_COMPILE_TIME_ASSERT(int_size, sizeof(int) == sizeof(Sint32));
28#define INT_MAX SDL_MAX_SINT32
29#endif
30#ifndef SIZE_MAX
31#define SIZE_MAX ((size_t)-1)
32#endif
33
34// Microsoft WAVE file loading routines
35
36#include "SDL_wave.h"
37#include "SDL_sysaudio.h"
38
39/* Reads the value stored at the location of the f1 pointer, multiplies it
40 * with the second argument and then stores the result to f1.
41 * Returns 0 on success, or -1 if the multiplication overflows, in which case f1
42 * does not get modified.
43 */
44static int SafeMult(size_t *f1, size_t f2)
45{
46 if (*f1 > 0 && SIZE_MAX / *f1 <= f2) {
47 return -1;
48 }
49 *f1 *= f2;
50 return 0;
51}
52
53typedef struct ADPCM_DecoderState
54{
55 Uint32 channels; // Number of channels.
56 size_t blocksize; // Size of an ADPCM block in bytes.
57 size_t blockheadersize; // Size of an ADPCM block header in bytes.
58 size_t samplesperblock; // Number of samples per channel in an ADPCM block.
59 size_t framesize; // Size of a sample frame (16-bit PCM) in bytes.
60 Sint64 framestotal; // Total number of sample frames.
61 Sint64 framesleft; // Number of sample frames still to be decoded.
62 void *ddata; // Decoder data from initialization.
63 void *cstate; // Decoding state for each channel.
64
65 // ADPCM data.
66 struct
67 {
68 Uint8 *data;
69 size_t size;
70 size_t pos;
71 } input;
72
73 // Current ADPCM block in the ADPCM data above.
74 struct
75 {
76 Uint8 *data;
77 size_t size;
78 size_t pos;
79 } block;
80
81 // Decoded 16-bit PCM data.
82 struct
83 {
84 Sint16 *data;
85 size_t size;
86 size_t pos;
87 } output;
88} ADPCM_DecoderState;
89
90typedef struct MS_ADPCM_CoeffData
91{
92 Uint16 coeffcount;
93 Sint16 *coeff;
94 Sint16 aligndummy; // Has to be last member.
95} MS_ADPCM_CoeffData;
96
97typedef struct MS_ADPCM_ChannelState
98{
99 Uint16 delta;
100 Sint16 coeff1;
101 Sint16 coeff2;
102} MS_ADPCM_ChannelState;
103
104#ifdef SDL_WAVE_DEBUG_LOG_FORMAT
105static void WaveDebugLogFormat(WaveFile *file)
106{
107 WaveFormat *format = &file->format;
108 const char *fmtstr = "WAVE file: %s, %u Hz, %s, %u bits, %u %s/s";
109 const char *waveformat, *wavechannel, *wavebpsunit = "B";
110 Uint32 wavebps = format->byterate;
111 char channelstr[64];
112
113 SDL_zeroa(channelstr);
114
115 switch (format->encoding) {
116 case PCM_CODE:
117 waveformat = "PCM";
118 break;
119 case IEEE_FLOAT_CODE:
120 waveformat = "IEEE Float";
121 break;
122 case ALAW_CODE:
123 waveformat = "A-law";
124 break;
125 case MULAW_CODE:
126 waveformat = "\xc2\xb5-law";
127 break;
128 case MS_ADPCM_CODE:
129 waveformat = "MS ADPCM";
130 break;
131 case IMA_ADPCM_CODE:
132 waveformat = "IMA ADPCM";
133 break;
134 default:
135 waveformat = "Unknown";
136 break;
137 }
138
139#define SDL_WAVE_DEBUG_CHANNELCFG(STR, CODE) \
140 case CODE: \
141 wavechannel = STR; \
142 break;
143#define SDL_WAVE_DEBUG_CHANNELSTR(STR, CODE) \
144 if (format->channelmask & CODE) { \
145 SDL_strlcat(channelstr, channelstr[0] ? "-" STR : STR, sizeof(channelstr)); \
146 }
147
148 if (format->formattag == EXTENSIBLE_CODE && format->channelmask > 0) {
149 switch (format->channelmask) {
150 SDL_WAVE_DEBUG_CHANNELCFG("1.0 Mono", 0x4)
151 SDL_WAVE_DEBUG_CHANNELCFG("1.1 Mono", 0xc)
152 SDL_WAVE_DEBUG_CHANNELCFG("2.0 Stereo", 0x3)
153 SDL_WAVE_DEBUG_CHANNELCFG("2.1 Stereo", 0xb)
154 SDL_WAVE_DEBUG_CHANNELCFG("3.0 Stereo", 0x7)
155 SDL_WAVE_DEBUG_CHANNELCFG("3.1 Stereo", 0xf)
156 SDL_WAVE_DEBUG_CHANNELCFG("3.0 Surround", 0x103)
157 SDL_WAVE_DEBUG_CHANNELCFG("3.1 Surround", 0x10b)
158 SDL_WAVE_DEBUG_CHANNELCFG("4.0 Quad", 0x33)
159 SDL_WAVE_DEBUG_CHANNELCFG("4.1 Quad", 0x3b)
160 SDL_WAVE_DEBUG_CHANNELCFG("4.0 Surround", 0x107)
161 SDL_WAVE_DEBUG_CHANNELCFG("4.1 Surround", 0x10f)
162 SDL_WAVE_DEBUG_CHANNELCFG("5.0", 0x37)
163 SDL_WAVE_DEBUG_CHANNELCFG("5.1", 0x3f)
164 SDL_WAVE_DEBUG_CHANNELCFG("5.0 Side", 0x607)
165 SDL_WAVE_DEBUG_CHANNELCFG("5.1 Side", 0x60f)
166 SDL_WAVE_DEBUG_CHANNELCFG("6.0", 0x137)
167 SDL_WAVE_DEBUG_CHANNELCFG("6.1", 0x13f)
168 SDL_WAVE_DEBUG_CHANNELCFG("6.0 Side", 0x707)
169 SDL_WAVE_DEBUG_CHANNELCFG("6.1 Side", 0x70f)
170 SDL_WAVE_DEBUG_CHANNELCFG("7.0", 0xf7)
171 SDL_WAVE_DEBUG_CHANNELCFG("7.1", 0xff)
172 SDL_WAVE_DEBUG_CHANNELCFG("7.0 Side", 0x6c7)
173 SDL_WAVE_DEBUG_CHANNELCFG("7.1 Side", 0x6cf)
174 SDL_WAVE_DEBUG_CHANNELCFG("7.0 Surround", 0x637)
175 SDL_WAVE_DEBUG_CHANNELCFG("7.1 Surround", 0x63f)
176 SDL_WAVE_DEBUG_CHANNELCFG("9.0 Surround", 0x5637)
177 SDL_WAVE_DEBUG_CHANNELCFG("9.1 Surround", 0x563f)
178 SDL_WAVE_DEBUG_CHANNELCFG("11.0 Surround", 0x56f7)
179 SDL_WAVE_DEBUG_CHANNELCFG("11.1 Surround", 0x56ff)
180 default:
181 SDL_WAVE_DEBUG_CHANNELSTR("FL", 0x1)
182 SDL_WAVE_DEBUG_CHANNELSTR("FR", 0x2)
183 SDL_WAVE_DEBUG_CHANNELSTR("FC", 0x4)
184 SDL_WAVE_DEBUG_CHANNELSTR("LF", 0x8)
185 SDL_WAVE_DEBUG_CHANNELSTR("BL", 0x10)
186 SDL_WAVE_DEBUG_CHANNELSTR("BR", 0x20)
187 SDL_WAVE_DEBUG_CHANNELSTR("FLC", 0x40)
188 SDL_WAVE_DEBUG_CHANNELSTR("FRC", 0x80)
189 SDL_WAVE_DEBUG_CHANNELSTR("BC", 0x100)
190 SDL_WAVE_DEBUG_CHANNELSTR("SL", 0x200)
191 SDL_WAVE_DEBUG_CHANNELSTR("SR", 0x400)
192 SDL_WAVE_DEBUG_CHANNELSTR("TC", 0x800)
193 SDL_WAVE_DEBUG_CHANNELSTR("TFL", 0x1000)
194 SDL_WAVE_DEBUG_CHANNELSTR("TFC", 0x2000)
195 SDL_WAVE_DEBUG_CHANNELSTR("TFR", 0x4000)
196 SDL_WAVE_DEBUG_CHANNELSTR("TBL", 0x8000)
197 SDL_WAVE_DEBUG_CHANNELSTR("TBC", 0x10000)
198 SDL_WAVE_DEBUG_CHANNELSTR("TBR", 0x20000)
199 break;
200 }
201 } else {
202 switch (format->channels) {
203 default:
204 if (SDL_snprintf(channelstr, sizeof(channelstr), "%u channels", format->channels) >= 0) {
205 wavechannel = channelstr;
206 break;
207 }
208 case 0:
209 wavechannel = "Unknown";
210 break;
211 case 1:
212 wavechannel = "Mono";
213 break;
214 case 2:
215 wavechannel = "Setero";
216 break;
217 }
218 }
219
220#undef SDL_WAVE_DEBUG_CHANNELCFG
221#undef SDL_WAVE_DEBUG_CHANNELSTR
222
223 if (wavebps >= 1024) {
224 wavebpsunit = "KiB";
225 wavebps = wavebps / 1024 + (wavebps & 0x3ff ? 1 : 0);
226 }
227
228 SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, fmtstr, waveformat, format->frequency, wavechannel, format->bitspersample, wavebps, wavebpsunit);
229}
230#endif
231
232#ifdef SDL_WAVE_DEBUG_DUMP_FORMAT
233static void WaveDebugDumpFormat(WaveFile *file, Uint32 rifflen, Uint32 fmtlen, Uint32 datalen)
234{
235 WaveFormat *format = &file->format;
236 const char *fmtstr1 = "WAVE chunk dump:\n"
237 "-------------------------------------------\n"
238 "RIFF %11u\n"
239 "-------------------------------------------\n"
240 " fmt %11u\n"
241 " wFormatTag 0x%04x\n"
242 " nChannels %11u\n"
243 " nSamplesPerSec %11u\n"
244 " nAvgBytesPerSec %11u\n"
245 " nBlockAlign %11u\n";
246 const char *fmtstr2 = " wBitsPerSample %11u\n";
247 const char *fmtstr3 = " cbSize %11u\n";
248 const char *fmtstr4a = " wValidBitsPerSample %11u\n";
249 const char *fmtstr4b = " wSamplesPerBlock %11u\n";
250 const char *fmtstr5 = " dwChannelMask 0x%08x\n"
251 " SubFormat\n"
252 " %08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x\n";
253 const char *fmtstr6 = "-------------------------------------------\n"
254 " fact\n"
255 " dwSampleLength %11u\n";
256 const char *fmtstr7 = "-------------------------------------------\n"
257 " data %11u\n"
258 "-------------------------------------------\n";
259 char *dumpstr;
260 size_t dumppos = 0;
261 const size_t bufsize = 1024;
262 int res;
263
264 dumpstr = SDL_malloc(bufsize);
265 if (!dumpstr) {
266 return;
267 }
268 dumpstr[0] = 0;
269
270 res = SDL_snprintf(dumpstr, bufsize, fmtstr1, rifflen, fmtlen, format->formattag, format->channels, format->frequency, format->byterate, format->blockalign);
271 dumppos += res > 0 ? res : 0;
272 if (fmtlen >= 16) {
273 res = SDL_snprintf(dumpstr + dumppos, bufsize - dumppos, fmtstr2, format->bitspersample);
274 dumppos += res > 0 ? res : 0;
275 }
276 if (fmtlen >= 18) {
277 res = SDL_snprintf(dumpstr + dumppos, bufsize - dumppos, fmtstr3, format->extsize);
278 dumppos += res > 0 ? res : 0;
279 }
280 if (format->formattag == EXTENSIBLE_CODE && fmtlen >= 40 && format->extsize >= 22) {
281 const Uint8 *g = format->subformat;
282 const Uint32 g1 = g[0] | ((Uint32)g[1] << 8) | ((Uint32)g[2] << 16) | ((Uint32)g[3] << 24);
283 const Uint32 g2 = g[4] | ((Uint32)g[5] << 8);
284 const Uint32 g3 = g[6] | ((Uint32)g[7] << 8);
285
286 switch (format->encoding) {
287 default:
288 res = SDL_snprintf(dumpstr + dumppos, bufsize - dumppos, fmtstr4a, format->validsamplebits);
289 dumppos += res > 0 ? res : 0;
290 break;
291 case MS_ADPCM_CODE:
292 case IMA_ADPCM_CODE:
293 res = SDL_snprintf(dumpstr + dumppos, bufsize - dumppos, fmtstr4b, format->samplesperblock);
294 dumppos += res > 0 ? res : 0;
295 break;
296 }
297 res = SDL_snprintf(dumpstr + dumppos, bufsize - dumppos, fmtstr5, format->channelmask, g1, g2, g3, g[8], g[9], g[10], g[11], g[12], g[13], g[14], g[15]);
298 dumppos += res > 0 ? res : 0;
299 } else {
300 switch (format->encoding) {
301 case MS_ADPCM_CODE:
302 case IMA_ADPCM_CODE:
303 if (fmtlen >= 20 && format->extsize >= 2) {
304 res = SDL_snprintf(dumpstr + dumppos, bufsize - dumppos, fmtstr4b, format->samplesperblock);
305 dumppos += res > 0 ? res : 0;
306 }
307 break;
308 }
309 }
310 if (file->fact.status >= 1) {
311 res = SDL_snprintf(dumpstr + dumppos, bufsize - dumppos, fmtstr6, file->fact.samplelength);
312 dumppos += res > 0 ? res : 0;
313 }
314 res = SDL_snprintf(dumpstr + dumppos, bufsize - dumppos, fmtstr7, datalen);
315 dumppos += res > 0 ? res : 0;
316
317 SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "%s", dumpstr);
318
319 SDL_free(dumpstr);
320}
321#endif
322
323static Sint64 WaveAdjustToFactValue(WaveFile *file, Sint64 sampleframes)
324{
325 if (file->fact.status == 2) {
326 if (file->facthint == FactStrict && sampleframes < file->fact.samplelength) {
327 SDL_SetError("Invalid number of sample frames in WAVE fact chunk (too many)");
328 return -1;
329 } else if (sampleframes > file->fact.samplelength) {
330 return file->fact.samplelength;
331 }
332 }
333
334 return sampleframes;
335}
336
337static bool MS_ADPCM_CalculateSampleFrames(WaveFile *file, size_t datalength)
338{
339 WaveFormat *format = &file->format;
340 const size_t blockheadersize = (size_t)file->format.channels * 7;
341 const size_t availableblocks = datalength / file->format.blockalign;
342 const size_t blockframebitsize = (size_t)file->format.bitspersample * file->format.channels;
343 const size_t trailingdata = datalength % file->format.blockalign;
344
345 if (file->trunchint == TruncVeryStrict || file->trunchint == TruncStrict) {
346 // The size of the data chunk must be a multiple of the block size.
347 if (datalength < blockheadersize || trailingdata > 0) {
348 return SDL_SetError("Truncated MS ADPCM block");
349 }
350 }
351
352 // Calculate number of sample frames that will be decoded.
353 file->sampleframes = (Sint64)availableblocks * format->samplesperblock;
354 if (trailingdata > 0) {
355 // The last block is truncated. Check if we can get any samples out of it.
356 if (file->trunchint == TruncDropFrame) {
357 // Drop incomplete sample frame.
358 if (trailingdata >= blockheadersize) {
359 size_t trailingsamples = 2 + (trailingdata - blockheadersize) * 8 / blockframebitsize;
360 if (trailingsamples > format->samplesperblock) {
361 trailingsamples = format->samplesperblock;
362 }
363 file->sampleframes += trailingsamples;
364 }
365 }
366 }
367
368 file->sampleframes = WaveAdjustToFactValue(file, file->sampleframes);
369 if (file->sampleframes < 0) {
370 return false;
371 }
372
373 return true;
374}
375
376static bool MS_ADPCM_Init(WaveFile *file, size_t datalength)
377{
378 WaveFormat *format = &file->format;
379 WaveChunk *chunk = &file->chunk;
380 const size_t blockheadersize = (size_t)format->channels * 7;
381 const size_t blockdatasize = (size_t)format->blockalign - blockheadersize;
382 const size_t blockframebitsize = (size_t)format->bitspersample * format->channels;
383 const size_t blockdatasamples = (blockdatasize * 8) / blockframebitsize;
384 const Sint16 presetcoeffs[14] = { 256, 0, 512, -256, 0, 0, 192, 64, 240, 0, 460, -208, 392, -232 };
385 size_t i, coeffcount;
386 MS_ADPCM_CoeffData *coeffdata;
387
388 // Sanity checks.
389
390 /* While it's clear how IMA ADPCM handles more than two channels, the nibble
391 * order of MS ADPCM makes it awkward. The Standards Update does not talk
392 * about supporting more than stereo anyway.
393 */
394 if (format->channels > 2) {
395 return SDL_SetError("Invalid number of channels");
396 }
397
398 if (format->bitspersample != 4) {
399 return SDL_SetError("Invalid MS ADPCM bits per sample of %u", (unsigned int)format->bitspersample);
400 }
401
402 // The block size must be big enough to contain the block header.
403 if (format->blockalign < blockheadersize) {
404 return SDL_SetError("Invalid MS ADPCM block size (nBlockAlign)");
405 }
406
407 if (format->formattag == EXTENSIBLE_CODE) {
408 /* Does have a GUID (like all format tags), but there's no specification
409 * for how the data is packed into the extensible header. Making
410 * assumptions here could lead to new formats nobody wants to support.
411 */
412 return SDL_SetError("MS ADPCM with the extensible header is not supported");
413 }
414
415 /* There are wSamplesPerBlock, wNumCoef, and at least 7 coefficient pairs in
416 * the extended part of the header.
417 */
418 if (chunk->size < 22) {
419 return SDL_SetError("Could not read MS ADPCM format header");
420 }
421
422 format->samplesperblock = chunk->data[18] | ((Uint16)chunk->data[19] << 8);
423 // Number of coefficient pairs. A pair has two 16-bit integers.
424 coeffcount = chunk->data[20] | ((size_t)chunk->data[21] << 8);
425 /* bPredictor, the integer offset into the coefficients array, is only
426 * 8 bits. It can only address the first 256 coefficients. Let's limit
427 * the count number here.
428 */
429 if (coeffcount > 256) {
430 coeffcount = 256;
431 }
432
433 if (chunk->size < 22 + coeffcount * 4) {
434 return SDL_SetError("Could not read custom coefficients in MS ADPCM format header");
435 } else if (format->extsize < 4 + coeffcount * 4) {
436 return SDL_SetError("Invalid MS ADPCM format header (too small)");
437 } else if (coeffcount < 7) {
438 return SDL_SetError("Missing required coefficients in MS ADPCM format header");
439 }
440
441 coeffdata = (MS_ADPCM_CoeffData *)SDL_malloc(sizeof(MS_ADPCM_CoeffData) + coeffcount * 4);
442 file->decoderdata = coeffdata; // Freed in cleanup.
443 if (!coeffdata) {
444 return false;
445 }
446 coeffdata->coeff = &coeffdata->aligndummy;
447 coeffdata->coeffcount = (Uint16)coeffcount;
448
449 // Copy the 16-bit pairs.
450 for (i = 0; i < coeffcount * 2; i++) {
451 Sint32 c = chunk->data[22 + i * 2] | ((Sint32)chunk->data[23 + i * 2] << 8);
452 if (c >= 0x8000) {
453 c -= 0x10000;
454 }
455 if (i < 14 && c != presetcoeffs[i]) {
456 return SDL_SetError("Wrong preset coefficients in MS ADPCM format header");
457 }
458 coeffdata->coeff[i] = (Sint16)c;
459 }
460
461 /* Technically, wSamplesPerBlock is required, but we have all the
462 * information in the other fields to calculate it, if it's zero.
463 */
464 if (format->samplesperblock == 0) {
465 /* Let's be nice to the encoders that didn't know how to fill this.
466 * The Standards Update calculates it this way:
467 *
468 * x = Block size (in bits) minus header size (in bits)
469 * y = Bit depth multiplied by channel count
470 * z = Number of samples per channel in block header
471 * wSamplesPerBlock = x / y + z
472 */
473 format->samplesperblock = (Uint32)blockdatasamples + 2;
474 }
475
476 /* nBlockAlign can be in conflict with wSamplesPerBlock. For example, if
477 * the number of samples doesn't fit into the block. The Standards Update
478 * also describes wSamplesPerBlock with a formula that makes it necessary to
479 * always fill the block with the maximum amount of samples, but this is not
480 * enforced here as there are no compatibility issues.
481 * A truncated block header with just one sample is not supported.
482 */
483 if (format->samplesperblock == 1 || blockdatasamples < format->samplesperblock - 2) {
484 return SDL_SetError("Invalid number of samples per MS ADPCM block (wSamplesPerBlock)");
485 }
486
487 if (!MS_ADPCM_CalculateSampleFrames(file, datalength)) {
488 return false;
489 }
490
491 return true;
492}
493
494static Sint16 MS_ADPCM_ProcessNibble(MS_ADPCM_ChannelState *cstate, Sint32 sample1, Sint32 sample2, Uint8 nybble)
495{
496 const Sint32 max_audioval = 32767;
497 const Sint32 min_audioval = -32768;
498 const Uint16 max_deltaval = 65535;
499 const Uint16 adaptive[] = {
500 230, 230, 230, 230, 307, 409, 512, 614,
501 768, 614, 512, 409, 307, 230, 230, 230
502 };
503 Sint32 new_sample;
504 Sint32 errordelta;
505 Uint32 delta = cstate->delta;
506
507 new_sample = (sample1 * cstate->coeff1 + sample2 * cstate->coeff2) / 256;
508 // The nibble is a signed 4-bit error delta.
509 errordelta = (Sint32)nybble - (nybble >= 0x08 ? 0x10 : 0);
510 new_sample += (Sint32)delta * errordelta;
511 if (new_sample < min_audioval) {
512 new_sample = min_audioval;
513 } else if (new_sample > max_audioval) {
514 new_sample = max_audioval;
515 }
516 delta = (delta * adaptive[nybble]) / 256;
517 if (delta < 16) {
518 delta = 16;
519 } else if (delta > max_deltaval) {
520 /* This issue is not described in the Standards Update and therefore
521 * undefined. It seems sensible to prevent overflows with a limit.
522 */
523 delta = max_deltaval;
524 }
525
526 cstate->delta = (Uint16)delta;
527 return (Sint16)new_sample;
528}
529
530static bool MS_ADPCM_DecodeBlockHeader(ADPCM_DecoderState *state)
531{
532 Uint8 coeffindex;
533 const Uint32 channels = state->channels;
534 Sint32 sample;
535 Uint32 c;
536 MS_ADPCM_ChannelState *cstate = (MS_ADPCM_ChannelState *)state->cstate;
537 MS_ADPCM_CoeffData *ddata = (MS_ADPCM_CoeffData *)state->ddata;
538
539 for (c = 0; c < channels; c++) {
540 size_t o = c;
541
542 // Load the coefficient pair into the channel state.
543 coeffindex = state->block.data[o];
544 if (coeffindex > ddata->coeffcount) {
545 return SDL_SetError("Invalid MS ADPCM coefficient index in block header");
546 }
547 cstate[c].coeff1 = ddata->coeff[coeffindex * 2];
548 cstate[c].coeff2 = ddata->coeff[coeffindex * 2 + 1];
549
550 // Initial delta value.
551 o = (size_t)channels + c * 2;
552 cstate[c].delta = state->block.data[o] | ((Uint16)state->block.data[o + 1] << 8);
553
554 /* Load the samples from the header. Interestingly, the sample later in
555 * the output stream comes first.
556 */
557 o = (size_t)channels * 3 + c * 2;
558 sample = state->block.data[o] | ((Sint32)state->block.data[o + 1] << 8);
559 if (sample >= 0x8000) {
560 sample -= 0x10000;
561 }
562 state->output.data[state->output.pos + channels] = (Sint16)sample;
563
564 o = (size_t)channels * 5 + c * 2;
565 sample = state->block.data[o] | ((Sint32)state->block.data[o + 1] << 8);
566 if (sample >= 0x8000) {
567 sample -= 0x10000;
568 }
569 state->output.data[state->output.pos] = (Sint16)sample;
570
571 state->output.pos++;
572 }
573
574 state->block.pos += state->blockheadersize;
575
576 // Skip second sample frame that came from the header.
577 state->output.pos += state->channels;
578
579 // Header provided two sample frames.
580 state->framesleft -= 2;
581
582 return true;
583}
584
585/* Decodes the data of the MS ADPCM block. Decoding will stop if a block is too
586 * short, returning with none or partially decoded data. The partial data
587 * will always contain full sample frames (same sample count for each channel).
588 * Incomplete sample frames are discarded.
589 */
590static bool MS_ADPCM_DecodeBlockData(ADPCM_DecoderState *state)
591{
592 Uint16 nybble = 0;
593 Sint16 sample1, sample2;
594 const Uint32 channels = state->channels;
595 Uint32 c;
596 MS_ADPCM_ChannelState *cstate = (MS_ADPCM_ChannelState *)state->cstate;
597
598 size_t blockpos = state->block.pos;
599 size_t blocksize = state->block.size;
600
601 size_t outpos = state->output.pos;
602
603 Sint64 blockframesleft = state->samplesperblock - 2;
604 if (blockframesleft > state->framesleft) {
605 blockframesleft = state->framesleft;
606 }
607
608 while (blockframesleft > 0) {
609 for (c = 0; c < channels; c++) {
610 if (nybble & 0x4000) {
611 nybble <<= 4;
612 } else if (blockpos < blocksize) {
613 nybble = state->block.data[blockpos++] | 0x4000;
614 } else {
615 // Out of input data. Drop the incomplete frame and return.
616 state->output.pos = outpos - c;
617 return false;
618 }
619
620 // Load previous samples which may come from the block header.
621 sample1 = state->output.data[outpos - channels];
622 sample2 = state->output.data[outpos - channels * 2];
623
624 sample1 = MS_ADPCM_ProcessNibble(cstate + c, sample1, sample2, (nybble >> 4) & 0x0f);
625 state->output.data[outpos++] = sample1;
626 }
627
628 state->framesleft--;
629 blockframesleft--;
630 }
631
632 state->output.pos = outpos;
633
634 return true;
635}
636
637static bool MS_ADPCM_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len)
638{
639 bool result;
640 size_t bytesleft, outputsize;
641 WaveChunk *chunk = &file->chunk;
642 ADPCM_DecoderState state;
643 MS_ADPCM_ChannelState cstate[2];
644
645 SDL_zero(state);
646 SDL_zeroa(cstate);
647
648 if (chunk->size != chunk->length) {
649 // Could not read everything. Recalculate number of sample frames.
650 if (!MS_ADPCM_CalculateSampleFrames(file, chunk->size)) {
651 return false;
652 }
653 }
654
655 // Nothing to decode, nothing to return.
656 if (file->sampleframes == 0) {
657 *audio_buf = NULL;
658 *audio_len = 0;
659 return true;
660 }
661
662 state.blocksize = file->format.blockalign;
663 state.channels = file->format.channels;
664 state.blockheadersize = (size_t)state.channels * 7;
665 state.samplesperblock = file->format.samplesperblock;
666 state.framesize = state.channels * sizeof(Sint16);
667 state.ddata = file->decoderdata;
668 state.framestotal = file->sampleframes;
669 state.framesleft = state.framestotal;
670
671 state.input.data = chunk->data;
672 state.input.size = chunk->size;
673 state.input.pos = 0;
674
675 // The output size in bytes. May get modified if data is truncated.
676 outputsize = (size_t)state.framestotal;
677 if (SafeMult(&outputsize, state.framesize)) {
678 return SDL_SetError("WAVE file too big");
679 } else if (outputsize > SDL_MAX_UINT32 || state.framestotal > SIZE_MAX) {
680 return SDL_SetError("WAVE file too big");
681 }
682
683 state.output.pos = 0;
684 state.output.size = outputsize / sizeof(Sint16);
685 state.output.data = (Sint16 *)SDL_calloc(1, outputsize);
686 if (!state.output.data) {
687 return false;
688 }
689
690 state.cstate = cstate;
691
692 // Decode block by block. A truncated block will stop the decoding.
693 bytesleft = state.input.size - state.input.pos;
694 while (state.framesleft > 0 && bytesleft >= state.blockheadersize) {
695 state.block.data = state.input.data + state.input.pos;
696 state.block.size = bytesleft < state.blocksize ? bytesleft : state.blocksize;
697 state.block.pos = 0;
698
699 if (state.output.size - state.output.pos < (Uint64)state.framesleft * state.channels) {
700 // Somehow didn't allocate enough space for the output.
701 SDL_free(state.output.data);
702 return SDL_SetError("Unexpected overflow in MS ADPCM decoder");
703 }
704
705 // Initialize decoder with the values from the block header.
706 result = MS_ADPCM_DecodeBlockHeader(&state);
707 if (!result) {
708 SDL_free(state.output.data);
709 return false;
710 }
711
712 // Decode the block data. It stores the samples directly in the output.
713 result = MS_ADPCM_DecodeBlockData(&state);
714 if (!result) {
715 // Unexpected end. Stop decoding and return partial data if necessary.
716 if (file->trunchint == TruncVeryStrict || file->trunchint == TruncStrict) {
717 SDL_free(state.output.data);
718 return SDL_SetError("Truncated data chunk");
719 } else if (file->trunchint != TruncDropFrame) {
720 state.output.pos -= state.output.pos % (state.samplesperblock * state.channels);
721 }
722 outputsize = state.output.pos * sizeof(Sint16); // Can't overflow, is always smaller.
723 break;
724 }
725
726 state.input.pos += state.block.size;
727 bytesleft = state.input.size - state.input.pos;
728 }
729
730 *audio_buf = (Uint8 *)state.output.data;
731 *audio_len = (Uint32)outputsize;
732
733 return true;
734}
735
736static bool IMA_ADPCM_CalculateSampleFrames(WaveFile *file, size_t datalength)
737{
738 WaveFormat *format = &file->format;
739 const size_t blockheadersize = (size_t)format->channels * 4;
740 const size_t subblockframesize = (size_t)format->channels * 4;
741 const size_t availableblocks = datalength / format->blockalign;
742 const size_t trailingdata = datalength % format->blockalign;
743
744 if (file->trunchint == TruncVeryStrict || file->trunchint == TruncStrict) {
745 // The size of the data chunk must be a multiple of the block size.
746 if (datalength < blockheadersize || trailingdata > 0) {
747 return SDL_SetError("Truncated IMA ADPCM block");
748 }
749 }
750
751 // Calculate number of sample frames that will be decoded.
752 file->sampleframes = (Uint64)availableblocks * format->samplesperblock;
753 if (trailingdata > 0) {
754 // The last block is truncated. Check if we can get any samples out of it.
755 if (file->trunchint == TruncDropFrame && trailingdata > blockheadersize - 2) {
756 /* The sample frame in the header of the truncated block is present.
757 * Drop incomplete sample frames.
758 */
759 size_t trailingsamples = 1;
760
761 if (trailingdata > blockheadersize) {
762 // More data following after the header.
763 const size_t trailingblockdata = trailingdata - blockheadersize;
764 const size_t trailingsubblockdata = trailingblockdata % subblockframesize;
765 trailingsamples += (trailingblockdata / subblockframesize) * 8;
766 /* Due to the interleaved sub-blocks, the last 4 bytes determine
767 * how many samples of the truncated sub-block are lost.
768 */
769 if (trailingsubblockdata > subblockframesize - 4) {
770 trailingsamples += (trailingsubblockdata % 4) * 2;
771 }
772 }
773
774 if (trailingsamples > format->samplesperblock) {
775 trailingsamples = format->samplesperblock;
776 }
777 file->sampleframes += trailingsamples;
778 }
779 }
780
781 file->sampleframes = WaveAdjustToFactValue(file, file->sampleframes);
782 if (file->sampleframes < 0) {
783 return false;
784 }
785
786 return true;
787}
788
789static bool IMA_ADPCM_Init(WaveFile *file, size_t datalength)
790{
791 WaveFormat *format = &file->format;
792 WaveChunk *chunk = &file->chunk;
793 const size_t blockheadersize = (size_t)format->channels * 4;
794 const size_t blockdatasize = (size_t)format->blockalign - blockheadersize;
795 const size_t blockframebitsize = (size_t)format->bitspersample * format->channels;
796 const size_t blockdatasamples = (blockdatasize * 8) / blockframebitsize;
797
798 // Sanity checks.
799
800 // IMA ADPCM can also have 3-bit samples, but it's not supported by SDL at this time.
801 if (format->bitspersample == 3) {
802 return SDL_SetError("3-bit IMA ADPCM currently not supported");
803 } else if (format->bitspersample != 4) {
804 return SDL_SetError("Invalid IMA ADPCM bits per sample of %u", (unsigned int)format->bitspersample);
805 }
806
807 /* The block size is required to be a multiple of 4 and it must be able to
808 * hold a block header.
809 */
810 if (format->blockalign < blockheadersize || format->blockalign % 4) {
811 return SDL_SetError("Invalid IMA ADPCM block size (nBlockAlign)");
812 }
813
814 if (format->formattag == EXTENSIBLE_CODE) {
815 /* There's no specification for this, but it's basically the same
816 * format because the extensible header has wSampePerBlocks too.
817 */
818 } else {
819 // The Standards Update says there 'should' be 2 bytes for wSamplesPerBlock.
820 if (chunk->size >= 20 && format->extsize >= 2) {
821 format->samplesperblock = chunk->data[18] | ((Uint16)chunk->data[19] << 8);
822 }
823 }
824
825 if (format->samplesperblock == 0) {
826 /* Field zero? No problem. We just assume the encoder packed the block.
827 * The specification calculates it this way:
828 *
829 * x = Block size (in bits) minus header size (in bits)
830 * y = Bit depth multiplied by channel count
831 * z = Number of samples per channel in header
832 * wSamplesPerBlock = x / y + z
833 */
834 format->samplesperblock = (Uint32)blockdatasamples + 1;
835 }
836
837 /* nBlockAlign can be in conflict with wSamplesPerBlock. For example, if
838 * the number of samples doesn't fit into the block. The Standards Update
839 * also describes wSamplesPerBlock with a formula that makes it necessary
840 * to always fill the block with the maximum amount of samples, but this is
841 * not enforced here as there are no compatibility issues.
842 */
843 if (blockdatasamples < format->samplesperblock - 1) {
844 return SDL_SetError("Invalid number of samples per IMA ADPCM block (wSamplesPerBlock)");
845 }
846
847 if (!IMA_ADPCM_CalculateSampleFrames(file, datalength)) {
848 return false;
849 }
850
851 return true;
852}
853
854static Sint16 IMA_ADPCM_ProcessNibble(Sint8 *cindex, Sint16 lastsample, Uint8 nybble)
855{
856 const Sint32 max_audioval = 32767;
857 const Sint32 min_audioval = -32768;
858 const Sint8 index_table_4b[16] = {
859 -1, -1, -1, -1,
860 2, 4, 6, 8,
861 -1, -1, -1, -1,
862 2, 4, 6, 8
863 };
864 const Uint16 step_table[89] = {
865 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31,
866 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130,
867 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408,
868 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282,
869 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327,
870 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630,
871 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350,
872 22385, 24623, 27086, 29794, 32767
873 };
874 Uint32 step;
875 Sint32 sample, delta;
876 Sint8 index = *cindex;
877
878 // Clamp index into valid range.
879 if (index > 88) {
880 index = 88;
881 } else if (index < 0) {
882 index = 0;
883 }
884
885 // explicit cast to avoid gcc warning about using 'char' as array index
886 step = step_table[(size_t)index];
887
888 // Update index value
889 *cindex = index + index_table_4b[nybble];
890
891 /* This calculation uses shifts and additions because multiplications were
892 * much slower back then. Sadly, this can't just be replaced with an actual
893 * multiplication now as the old algorithm drops some bits. The closest
894 * approximation I could find is something like this:
895 * (nybble & 0x8 ? -1 : 1) * ((nybble & 0x7) * step / 4 + step / 8)
896 */
897 delta = step >> 3;
898 if (nybble & 0x04) {
899 delta += step;
900 }
901 if (nybble & 0x02) {
902 delta += step >> 1;
903 }
904 if (nybble & 0x01) {
905 delta += step >> 2;
906 }
907 if (nybble & 0x08) {
908 delta = -delta;
909 }
910
911 sample = lastsample + delta;
912
913 // Clamp output sample
914 if (sample > max_audioval) {
915 sample = max_audioval;
916 } else if (sample < min_audioval) {
917 sample = min_audioval;
918 }
919
920 return (Sint16)sample;
921}
922
923static bool IMA_ADPCM_DecodeBlockHeader(ADPCM_DecoderState *state)
924{
925 Sint16 step;
926 Uint32 c;
927 Uint8 *cstate = (Uint8 *)state->cstate;
928
929 for (c = 0; c < state->channels; c++) {
930 size_t o = state->block.pos + c * 4;
931
932 // Extract the sample from the header.
933 Sint32 sample = state->block.data[o] | ((Sint32)state->block.data[o + 1] << 8);
934 if (sample >= 0x8000) {
935 sample -= 0x10000;
936 }
937 state->output.data[state->output.pos++] = (Sint16)sample;
938
939 // Channel step index.
940 step = (Sint16)state->block.data[o + 2];
941 cstate[c] = (Sint8)(step > 0x80 ? step - 0x100 : step);
942
943 // Reserved byte in block header, should be 0.
944 if (state->block.data[o + 3] != 0) {
945 /* Uh oh, corrupt data? Buggy code? */;
946 }
947 }
948
949 state->block.pos += state->blockheadersize;
950
951 // Header provided one sample frame.
952 state->framesleft--;
953
954 return true;
955}
956
957/* Decodes the data of the IMA ADPCM block. Decoding will stop if a block is too
958 * short, returning with none or partially decoded data. The partial data always
959 * contains full sample frames (same sample count for each channel).
960 * Incomplete sample frames are discarded.
961 */
962static bool IMA_ADPCM_DecodeBlockData(ADPCM_DecoderState *state)
963{
964 size_t i;
965 const Uint32 channels = state->channels;
966 const size_t subblockframesize = (size_t)channels * 4;
967 Uint64 bytesrequired;
968 Uint32 c;
969 bool result = true;
970
971 size_t blockpos = state->block.pos;
972 size_t blocksize = state->block.size;
973 size_t blockleft = blocksize - blockpos;
974
975 size_t outpos = state->output.pos;
976
977 Sint64 blockframesleft = state->samplesperblock - 1;
978 if (blockframesleft > state->framesleft) {
979 blockframesleft = state->framesleft;
980 }
981
982 bytesrequired = (blockframesleft + 7) / 8 * subblockframesize;
983 if (blockleft < bytesrequired) {
984 // Data truncated. Calculate how many samples we can get out if it.
985 const size_t guaranteedframes = blockleft / subblockframesize;
986 const size_t remainingbytes = blockleft % subblockframesize;
987 blockframesleft = guaranteedframes;
988 if (remainingbytes > subblockframesize - 4) {
989 blockframesleft += (Sint64)(remainingbytes % 4) * 2;
990 }
991 // Signal the truncation.
992 result = false;
993 }
994
995 /* Each channel has their nibbles packed into 32-bit blocks. These blocks
996 * are interleaved and make up the data part of the ADPCM block. This loop
997 * decodes the samples as they come from the input data and puts them at
998 * the appropriate places in the output data.
999 */
1000 while (blockframesleft > 0) {
1001 const size_t subblocksamples = blockframesleft < 8 ? (size_t)blockframesleft : 8;
1002
1003 for (c = 0; c < channels; c++) {
1004 Uint8 nybble = 0;
1005 // Load previous sample which may come from the block header.
1006 Sint16 sample = state->output.data[outpos + c - channels];
1007
1008 for (i = 0; i < subblocksamples; i++) {
1009 if (i & 1) {
1010 nybble >>= 4;
1011 } else {
1012 nybble = state->block.data[blockpos++];
1013 }
1014
1015 sample = IMA_ADPCM_ProcessNibble((Sint8 *)state->cstate + c, sample, nybble & 0x0f);
1016 state->output.data[outpos + c + i * channels] = sample;
1017 }
1018 }
1019
1020 outpos += channels * subblocksamples;
1021 state->framesleft -= subblocksamples;
1022 blockframesleft -= subblocksamples;
1023 }
1024
1025 state->block.pos = blockpos;
1026 state->output.pos = outpos;
1027
1028 return result;
1029}
1030
1031static bool IMA_ADPCM_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len)
1032{
1033 bool result;
1034 size_t bytesleft, outputsize;
1035 WaveChunk *chunk = &file->chunk;
1036 ADPCM_DecoderState state;
1037 Sint8 *cstate;
1038
1039 if (chunk->size != chunk->length) {
1040 // Could not read everything. Recalculate number of sample frames.
1041 if (!IMA_ADPCM_CalculateSampleFrames(file, chunk->size)) {
1042 return false;
1043 }
1044 }
1045
1046 // Nothing to decode, nothing to return.
1047 if (file->sampleframes == 0) {
1048 *audio_buf = NULL;
1049 *audio_len = 0;
1050 return true;
1051 }
1052
1053 SDL_zero(state);
1054 state.channels = file->format.channels;
1055 state.blocksize = file->format.blockalign;
1056 state.blockheadersize = (size_t)state.channels * 4;
1057 state.samplesperblock = file->format.samplesperblock;
1058 state.framesize = state.channels * sizeof(Sint16);
1059 state.framestotal = file->sampleframes;
1060 state.framesleft = state.framestotal;
1061
1062 state.input.data = chunk->data;
1063 state.input.size = chunk->size;
1064 state.input.pos = 0;
1065
1066 // The output size in bytes. May get modified if data is truncated.
1067 outputsize = (size_t)state.framestotal;
1068 if (SafeMult(&outputsize, state.framesize)) {
1069 return SDL_SetError("WAVE file too big");
1070 } else if (outputsize > SDL_MAX_UINT32 || state.framestotal > SIZE_MAX) {
1071 return SDL_SetError("WAVE file too big");
1072 }
1073
1074 state.output.pos = 0;
1075 state.output.size = outputsize / sizeof(Sint16);
1076 state.output.data = (Sint16 *)SDL_malloc(outputsize);
1077 if (!state.output.data) {
1078 return false;
1079 }
1080
1081 cstate = (Sint8 *)SDL_calloc(state.channels, sizeof(Sint8));
1082 if (!cstate) {
1083 SDL_free(state.output.data);
1084 return false;
1085 }
1086 state.cstate = cstate;
1087
1088 // Decode block by block. A truncated block will stop the decoding.
1089 bytesleft = state.input.size - state.input.pos;
1090 while (state.framesleft > 0 && bytesleft >= state.blockheadersize) {
1091 state.block.data = state.input.data + state.input.pos;
1092 state.block.size = bytesleft < state.blocksize ? bytesleft : state.blocksize;
1093 state.block.pos = 0;
1094
1095 if (state.output.size - state.output.pos < (Uint64)state.framesleft * state.channels) {
1096 // Somehow didn't allocate enough space for the output.
1097 SDL_free(state.output.data);
1098 SDL_free(cstate);
1099 return SDL_SetError("Unexpected overflow in IMA ADPCM decoder");
1100 }
1101
1102 // Initialize decoder with the values from the block header.
1103 result = IMA_ADPCM_DecodeBlockHeader(&state);
1104 if (result) {
1105 // Decode the block data. It stores the samples directly in the output.
1106 result = IMA_ADPCM_DecodeBlockData(&state);
1107 }
1108
1109 if (!result) {
1110 // Unexpected end. Stop decoding and return partial data if necessary.
1111 if (file->trunchint == TruncVeryStrict || file->trunchint == TruncStrict) {
1112 SDL_free(state.output.data);
1113 SDL_free(cstate);
1114 return SDL_SetError("Truncated data chunk");
1115 } else if (file->trunchint != TruncDropFrame) {
1116 state.output.pos -= state.output.pos % (state.samplesperblock * state.channels);
1117 }
1118 outputsize = state.output.pos * sizeof(Sint16); // Can't overflow, is always smaller.
1119 break;
1120 }
1121
1122 state.input.pos += state.block.size;
1123 bytesleft = state.input.size - state.input.pos;
1124 }
1125
1126 *audio_buf = (Uint8 *)state.output.data;
1127 *audio_len = (Uint32)outputsize;
1128
1129 SDL_free(cstate);
1130
1131 return true;
1132}
1133
1134static bool LAW_Init(WaveFile *file, size_t datalength)
1135{
1136 WaveFormat *format = &file->format;
1137
1138 // Standards Update requires this to be 8.
1139 if (format->bitspersample != 8) {
1140 return SDL_SetError("Invalid companded bits per sample of %u", (unsigned int)format->bitspersample);
1141 }
1142
1143 // Not going to bother with weird padding.
1144 if (format->blockalign != format->channels) {
1145 return SDL_SetError("Unsupported block alignment");
1146 }
1147
1148 if ((file->trunchint == TruncVeryStrict || file->trunchint == TruncStrict)) {
1149 if (format->blockalign > 1 && datalength % format->blockalign) {
1150 return SDL_SetError("Truncated data chunk in WAVE file");
1151 }
1152 }
1153
1154 file->sampleframes = WaveAdjustToFactValue(file, datalength / format->blockalign);
1155 if (file->sampleframes < 0) {
1156 return false;
1157 }
1158
1159 return true;
1160}
1161
1162static bool LAW_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len)
1163{
1164#ifdef SDL_WAVE_LAW_LUT
1165 const Sint16 alaw_lut[256] = {
1166 -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752,
1167 -2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016,
1168 -20992, -24064, -23040, -17920, -16896, -19968, -18944, -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, -11008,
1169 -10496, -12032, -11520, -8960, -8448, -9984, -9472, -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, -344,
1170 -328, -376, -360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88,
1171 -72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376,
1172 -1312, -1504, -1440, -1120, -1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688,
1173 -656, -752, -720, -560, -528, -624, -592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504,
1174 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, 2752,
1175 2624, 3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, 22016,
1176 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, 11008,
1177 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, 344,
1178 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88,
1179 72, 120, 104, 24, 8, 56, 40, 216, 200, 248, 232, 152, 136, 184, 168, 1376,
1180 1312, 1504, 1440, 1120, 1056, 1248, 1184, 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688,
1181 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848
1182 };
1183 const Sint16 mulaw_lut[256] = {
1184 -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, -15996,
1185 -15484, -14972, -14460, -13948, -13436, -12924, -12412, -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, -7932,
1186 -7676, -7420, -7164, -6908, -6652, -6396, -6140, -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, -3900,
1187 -3772, -3644, -3516, -3388, -3260, -3132, -3004, -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, -1884,
1188 -1820, -1756, -1692, -1628, -1564, -1500, -1436, -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, -876,
1189 -844, -812, -780, -748, -716, -684, -652, -620, -588, -556, -524, -492, -460, -428, -396, -372,
1190 -356, -340, -324, -308, -292, -276, -260, -244, -228, -212, -196, -180, -164, -148, -132, -120,
1191 -112, -104, -96, -88, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8, 0, 32124,
1192 31100, 30076, 29052, 28028, 27004, 25980, 24956, 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, 15996,
1193 15484, 14972, 14460, 13948, 13436, 12924, 12412, 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, 7932,
1194 7676, 7420, 7164, 6908, 6652, 6396, 6140, 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, 3900,
1195 3772, 3644, 3516, 3388, 3260, 3132, 3004, 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, 1884,
1196 1820, 1756, 1692, 1628, 1564, 1500, 1436, 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, 876,
1197 844, 812, 780, 748, 716, 684, 652, 620, 588, 556, 524, 492, 460, 428, 396, 372,
1198 356, 340, 324, 308, 292, 276, 260, 244, 228, 212, 196, 180, 164, 148, 132, 120,
1199 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, 8, 0
1200 };
1201#endif
1202
1203 WaveFormat *format = &file->format;
1204 WaveChunk *chunk = &file->chunk;
1205 size_t i, sample_count, expanded_len;
1206 Uint8 *src;
1207 Sint16 *dst;
1208
1209 if (chunk->length != chunk->size) {
1210 file->sampleframes = WaveAdjustToFactValue(file, chunk->size / format->blockalign);
1211 if (file->sampleframes < 0) {
1212 return false;
1213 }
1214 }
1215
1216 // Nothing to decode, nothing to return.
1217 if (file->sampleframes == 0) {
1218 *audio_buf = NULL;
1219 *audio_len = 0;
1220 return true;
1221 }
1222
1223 sample_count = (size_t)file->sampleframes;
1224 if (SafeMult(&sample_count, format->channels)) {
1225 return SDL_SetError("WAVE file too big");
1226 }
1227
1228 expanded_len = sample_count;
1229 if (SafeMult(&expanded_len, sizeof(Sint16))) {
1230 return SDL_SetError("WAVE file too big");
1231 } else if (expanded_len > SDL_MAX_UINT32 || file->sampleframes > SIZE_MAX) {
1232 return SDL_SetError("WAVE file too big");
1233 }
1234
1235 // 1 to avoid allocating zero bytes, to keep static analysis happy.
1236 src = (Uint8 *)SDL_realloc(chunk->data, expanded_len ? expanded_len : 1);
1237 if (!src) {
1238 return false;
1239 }
1240 chunk->data = NULL;
1241 chunk->size = 0;
1242
1243 dst = (Sint16 *)src;
1244
1245 /* Work backwards, since we're expanding in-place. `format` will
1246 * inform the caller about the byte order.
1247 */
1248 i = sample_count;
1249 switch (file->format.encoding) {
1250#ifdef SDL_WAVE_LAW_LUT
1251 case ALAW_CODE:
1252 while (i--) {
1253 dst[i] = alaw_lut[src[i]];
1254 }
1255 break;
1256 case MULAW_CODE:
1257 while (i--) {
1258 dst[i] = mulaw_lut[src[i]];
1259 }
1260 break;
1261#else
1262 case ALAW_CODE:
1263 while (i--) {
1264 Uint8 nibble = src[i];
1265 Uint8 exponent = (nibble & 0x7f) ^ 0x55;
1266 Sint16 mantissa = exponent & 0xf;
1267
1268 exponent >>= 4;
1269 if (exponent > 0) {
1270 mantissa |= 0x10;
1271 }
1272 mantissa = (mantissa << 4) | 0x8;
1273 if (exponent > 1) {
1274 mantissa <<= exponent - 1;
1275 }
1276
1277 dst[i] = nibble & 0x80 ? mantissa : -mantissa;
1278 }
1279 break;
1280 case MULAW_CODE:
1281 while (i--) {
1282 Uint8 nibble = ~src[i];
1283 Sint16 mantissa = nibble & 0xf;
1284 Uint8 exponent = (nibble >> 4) & 0x7;
1285 Sint16 step = 4 << (exponent + 1);
1286
1287 mantissa = (0x80 << exponent) + step * mantissa + step / 2 - 132;
1288
1289 dst[i] = nibble & 0x80 ? -mantissa : mantissa;
1290 }
1291 break;
1292#endif
1293 default:
1294 SDL_free(src);
1295 return SDL_SetError("Unknown companded encoding");
1296 }
1297
1298 *audio_buf = src;
1299 *audio_len = (Uint32)expanded_len;
1300
1301 return true;
1302}
1303
1304static bool PCM_Init(WaveFile *file, size_t datalength)
1305{
1306 WaveFormat *format = &file->format;
1307
1308 if (format->encoding == PCM_CODE) {
1309 switch (format->bitspersample) {
1310 case 8:
1311 case 16:
1312 case 24:
1313 case 32:
1314 // These are supported.
1315 break;
1316 default:
1317 return SDL_SetError("%u-bit PCM format not supported", (unsigned int)format->bitspersample);
1318 }
1319 } else if (format->encoding == IEEE_FLOAT_CODE) {
1320 if (format->bitspersample != 32) {
1321 return SDL_SetError("%u-bit IEEE floating-point format not supported", (unsigned int)format->bitspersample);
1322 }
1323 }
1324
1325 /* It wouldn't be that hard to support more exotic block sizes, but
1326 * the most common formats should do for now.
1327 */
1328 // Make sure we're a multiple of the blockalign, at least.
1329 if ((format->channels * format->bitspersample) % (format->blockalign * 8)) {
1330 return SDL_SetError("Unsupported block alignment");
1331 }
1332
1333 if ((file->trunchint == TruncVeryStrict || file->trunchint == TruncStrict)) {
1334 if (format->blockalign > 1 && datalength % format->blockalign) {
1335 return SDL_SetError("Truncated data chunk in WAVE file");
1336 }
1337 }
1338
1339 file->sampleframes = WaveAdjustToFactValue(file, datalength / format->blockalign);
1340 if (file->sampleframes < 0) {
1341 return false;
1342 }
1343
1344 return true;
1345}
1346
1347static bool PCM_ConvertSint24ToSint32(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len)
1348{
1349 WaveFormat *format = &file->format;
1350 WaveChunk *chunk = &file->chunk;
1351 size_t i, expanded_len, sample_count;
1352 Uint8 *ptr;
1353
1354 sample_count = (size_t)file->sampleframes;
1355 if (SafeMult(&sample_count, format->channels)) {
1356 return SDL_SetError("WAVE file too big");
1357 }
1358
1359 expanded_len = sample_count;
1360 if (SafeMult(&expanded_len, sizeof(Sint32))) {
1361 return SDL_SetError("WAVE file too big");
1362 } else if (expanded_len > SDL_MAX_UINT32 || file->sampleframes > SIZE_MAX) {
1363 return SDL_SetError("WAVE file too big");
1364 }
1365
1366 // 1 to avoid allocating zero bytes, to keep static analysis happy.
1367 ptr = (Uint8 *)SDL_realloc(chunk->data, expanded_len ? expanded_len : 1);
1368 if (!ptr) {
1369 return false;
1370 }
1371
1372 // This pointer is now invalid.
1373 chunk->data = NULL;
1374 chunk->size = 0;
1375
1376 *audio_buf = ptr;
1377 *audio_len = (Uint32)expanded_len;
1378
1379 // work from end to start, since we're expanding in-place.
1380 for (i = sample_count; i > 0; i--) {
1381 const size_t o = i - 1;
1382 uint8_t b[4];
1383
1384 b[0] = 0;
1385 b[1] = ptr[o * 3];
1386 b[2] = ptr[o * 3 + 1];
1387 b[3] = ptr[o * 3 + 2];
1388
1389 ptr[o * 4 + 0] = b[0];
1390 ptr[o * 4 + 1] = b[1];
1391 ptr[o * 4 + 2] = b[2];
1392 ptr[o * 4 + 3] = b[3];
1393 }
1394
1395 return true;
1396}
1397
1398static bool PCM_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len)
1399{
1400 WaveFormat *format = &file->format;
1401 WaveChunk *chunk = &file->chunk;
1402 size_t outputsize;
1403
1404 if (chunk->length != chunk->size) {
1405 file->sampleframes = WaveAdjustToFactValue(file, chunk->size / format->blockalign);
1406 if (file->sampleframes < 0) {
1407 return false;
1408 }
1409 }
1410
1411 // Nothing to decode, nothing to return.
1412 if (file->sampleframes == 0) {
1413 *audio_buf = NULL;
1414 *audio_len = 0;
1415 return true;
1416 }
1417
1418 // 24-bit samples get shifted to 32 bits.
1419 if (format->encoding == PCM_CODE && format->bitspersample == 24) {
1420 return PCM_ConvertSint24ToSint32(file, audio_buf, audio_len);
1421 }
1422
1423 outputsize = (size_t)file->sampleframes;
1424 if (SafeMult(&outputsize, format->blockalign)) {
1425 return SDL_SetError("WAVE file too big");
1426 } else if (outputsize > SDL_MAX_UINT32 || file->sampleframes > SIZE_MAX) {
1427 return SDL_SetError("WAVE file too big");
1428 }
1429
1430 *audio_buf = chunk->data;
1431 *audio_len = (Uint32)outputsize;
1432
1433 // This pointer is going to be returned to the caller. Prevent free in cleanup.
1434 chunk->data = NULL;
1435 chunk->size = 0;
1436
1437 return true;
1438}
1439
1440static WaveRiffSizeHint WaveGetRiffSizeHint(void)
1441{
1442 const char *hint = SDL_GetHint(SDL_HINT_WAVE_RIFF_CHUNK_SIZE);
1443
1444 if (hint) {
1445 if (SDL_strcmp(hint, "force") == 0) {
1446 return RiffSizeForce;
1447 } else if (SDL_strcmp(hint, "ignore") == 0) {
1448 return RiffSizeIgnore;
1449 } else if (SDL_strcmp(hint, "ignorezero") == 0) {
1450 return RiffSizeIgnoreZero;
1451 } else if (SDL_strcmp(hint, "maximum") == 0) {
1452 return RiffSizeMaximum;
1453 }
1454 }
1455
1456 return RiffSizeNoHint;
1457}
1458
1459static WaveTruncationHint WaveGetTruncationHint(void)
1460{
1461 const char *hint = SDL_GetHint(SDL_HINT_WAVE_TRUNCATION);
1462
1463 if (hint) {
1464 if (SDL_strcmp(hint, "verystrict") == 0) {
1465 return TruncVeryStrict;
1466 } else if (SDL_strcmp(hint, "strict") == 0) {
1467 return TruncStrict;
1468 } else if (SDL_strcmp(hint, "dropframe") == 0) {
1469 return TruncDropFrame;
1470 } else if (SDL_strcmp(hint, "dropblock") == 0) {
1471 return TruncDropBlock;
1472 }
1473 }
1474
1475 return TruncNoHint;
1476}
1477
1478static WaveFactChunkHint WaveGetFactChunkHint(void)
1479{
1480 const char *hint = SDL_GetHint(SDL_HINT_WAVE_FACT_CHUNK);
1481
1482 if (hint) {
1483 if (SDL_strcmp(hint, "truncate") == 0) {
1484 return FactTruncate;
1485 } else if (SDL_strcmp(hint, "strict") == 0) {
1486 return FactStrict;
1487 } else if (SDL_strcmp(hint, "ignorezero") == 0) {
1488 return FactIgnoreZero;
1489 } else if (SDL_strcmp(hint, "ignore") == 0) {
1490 return FactIgnore;
1491 }
1492 }
1493
1494 return FactNoHint;
1495}
1496
1497static void WaveFreeChunkData(WaveChunk *chunk)
1498{
1499 if (chunk->data) {
1500 SDL_free(chunk->data);
1501 chunk->data = NULL;
1502 }
1503 chunk->size = 0;
1504}
1505
1506static int WaveNextChunk(SDL_IOStream *src, WaveChunk *chunk)
1507{
1508 Uint32 chunkheader[2];
1509 Sint64 nextposition = chunk->position + chunk->length;
1510
1511 // Data is no longer valid after this function returns.
1512 WaveFreeChunkData(chunk);
1513
1514 // Error on overflows.
1515 if (SDL_MAX_SINT64 - chunk->length < chunk->position || SDL_MAX_SINT64 - 8 < nextposition) {
1516 return -1;
1517 }
1518
1519 // RIFF chunks have a 2-byte alignment. Skip padding byte.
1520 if (chunk->length & 1) {
1521 nextposition++;
1522 }
1523
1524 if (SDL_SeekIO(src, nextposition, SDL_IO_SEEK_SET) != nextposition) {
1525 // Not sure how we ended up here. Just abort.
1526 return -2;
1527 } else if (SDL_ReadIO(src, chunkheader, sizeof(Uint32) * 2) != (sizeof(Uint32) * 2)) {
1528 return -1;
1529 }
1530
1531 chunk->fourcc = SDL_Swap32LE(chunkheader[0]);
1532 chunk->length = SDL_Swap32LE(chunkheader[1]);
1533 chunk->position = nextposition + 8;
1534
1535 return 0;
1536}
1537
1538static int WaveReadPartialChunkData(SDL_IOStream *src, WaveChunk *chunk, size_t length)
1539{
1540 WaveFreeChunkData(chunk);
1541
1542 if (length > chunk->length) {
1543 length = chunk->length;
1544 }
1545
1546 if (length > 0) {
1547 chunk->data = (Uint8 *)SDL_malloc(length);
1548 if (!chunk->data) {
1549 return -1;
1550 }
1551
1552 if (SDL_SeekIO(src, chunk->position, SDL_IO_SEEK_SET) != chunk->position) {
1553 // Not sure how we ended up here. Just abort.
1554 return -2;
1555 }
1556
1557 chunk->size = SDL_ReadIO(src, chunk->data, length);
1558 if (chunk->size != length) {
1559 // Expected to be handled by the caller.
1560 }
1561 }
1562
1563 return 0;
1564}
1565
1566static int WaveReadChunkData(SDL_IOStream *src, WaveChunk *chunk)
1567{
1568 return WaveReadPartialChunkData(src, chunk, chunk->length);
1569}
1570
1571typedef struct WaveExtensibleGUID
1572{
1573 Uint16 encoding;
1574 Uint8 guid[16];
1575} WaveExtensibleGUID;
1576
1577// Some of the GUIDs that are used by WAVEFORMATEXTENSIBLE.
1578#define WAVE_FORMATTAG_GUID(tag) \
1579 { \
1580 (tag) & 0xff, (tag) >> 8, 0, 0, 0, 0, 16, 0, 128, 0, 0, 170, 0, 56, 155, 113 \
1581 }
1582static WaveExtensibleGUID extensible_guids[] = {
1583 { PCM_CODE, WAVE_FORMATTAG_GUID(PCM_CODE) },
1584 { MS_ADPCM_CODE, WAVE_FORMATTAG_GUID(MS_ADPCM_CODE) },
1585 { IEEE_FLOAT_CODE, WAVE_FORMATTAG_GUID(IEEE_FLOAT_CODE) },
1586 { ALAW_CODE, WAVE_FORMATTAG_GUID(ALAW_CODE) },
1587 { MULAW_CODE, WAVE_FORMATTAG_GUID(MULAW_CODE) },
1588 { IMA_ADPCM_CODE, WAVE_FORMATTAG_GUID(IMA_ADPCM_CODE) }
1589};
1590
1591static Uint16 WaveGetFormatGUIDEncoding(WaveFormat *format)
1592{
1593 size_t i;
1594 for (i = 0; i < SDL_arraysize(extensible_guids); i++) {
1595 if (SDL_memcmp(format->subformat, extensible_guids[i].guid, 16) == 0) {
1596 return extensible_guids[i].encoding;
1597 }
1598 }
1599 return UNKNOWN_CODE;
1600}
1601
1602static bool WaveReadFormat(WaveFile *file)
1603{
1604 WaveChunk *chunk = &file->chunk;
1605 WaveFormat *format = &file->format;
1606 SDL_IOStream *fmtsrc;
1607 size_t fmtlen = chunk->size;
1608
1609 if (fmtlen > SDL_MAX_SINT32) {
1610 // Limit given by SDL_IOFromConstMem.
1611 return SDL_SetError("Data of WAVE fmt chunk too big");
1612 }
1613 fmtsrc = SDL_IOFromConstMem(chunk->data, (int)chunk->size);
1614 if (!fmtsrc) {
1615 return false;
1616 }
1617
1618 if (!SDL_ReadU16LE(fmtsrc, &format->formattag) ||
1619 !SDL_ReadU16LE(fmtsrc, &format->channels) ||
1620 !SDL_ReadU32LE(fmtsrc, &format->frequency) ||
1621 !SDL_ReadU32LE(fmtsrc, &format->byterate) ||
1622 !SDL_ReadU16LE(fmtsrc, &format->blockalign)) {
1623 return false;
1624 }
1625 format->encoding = format->formattag;
1626
1627 // This is PCM specific in the first version of the specification.
1628 if (fmtlen >= 16) {
1629 if (!SDL_ReadU16LE(fmtsrc, &format->bitspersample)) {
1630 return false;
1631 }
1632 } else if (format->encoding == PCM_CODE) {
1633 SDL_CloseIO(fmtsrc);
1634 return SDL_SetError("Missing wBitsPerSample field in WAVE fmt chunk");
1635 }
1636
1637 // The earlier versions also don't have this field.
1638 if (fmtlen >= 18) {
1639 if (!SDL_ReadU16LE(fmtsrc, &format->extsize)) {
1640 return false;
1641 }
1642 }
1643
1644 if (format->formattag == EXTENSIBLE_CODE) {
1645 /* note that this ignores channel masks, smaller valid bit counts
1646 * inside a larger container, and most subtypes. This is just enough
1647 * to get things that didn't really _need_ WAVE_FORMAT_EXTENSIBLE
1648 * to be useful working when they use this format flag.
1649 */
1650
1651 // Extensible header must be at least 22 bytes.
1652 if (fmtlen < 40 || format->extsize < 22) {
1653 SDL_CloseIO(fmtsrc);
1654 return SDL_SetError("Extensible WAVE header too small");
1655 }
1656
1657 if (!SDL_ReadU16LE(fmtsrc, &format->validsamplebits) ||
1658 !SDL_ReadU32LE(fmtsrc, &format->channelmask) ||
1659 SDL_ReadIO(fmtsrc, format->subformat, 16) != 16) {
1660 }
1661 format->samplesperblock = format->validsamplebits;
1662 format->encoding = WaveGetFormatGUIDEncoding(format);
1663 }
1664
1665 SDL_CloseIO(fmtsrc);
1666
1667 return true;
1668}
1669
1670static bool WaveCheckFormat(WaveFile *file, size_t datalength)
1671{
1672 WaveFormat *format = &file->format;
1673
1674 // Check for some obvious issues.
1675
1676 if (format->channels == 0) {
1677 return SDL_SetError("Invalid number of channels");
1678 }
1679
1680 if (format->frequency == 0) {
1681 return SDL_SetError("Invalid sample rate");
1682 } else if (format->frequency > INT_MAX) {
1683 return SDL_SetError("Sample rate exceeds limit of %d", INT_MAX);
1684 }
1685
1686 // Reject invalid fact chunks in strict mode.
1687 if (file->facthint == FactStrict && file->fact.status == -1) {
1688 return SDL_SetError("Invalid fact chunk in WAVE file");
1689 }
1690
1691 /* Check for issues common to all encodings. Some unsupported formats set
1692 * the bits per sample to zero. These fall through to the 'unsupported
1693 * format' error.
1694 */
1695 switch (format->encoding) {
1696 case IEEE_FLOAT_CODE:
1697 case ALAW_CODE:
1698 case MULAW_CODE:
1699 case MS_ADPCM_CODE:
1700 case IMA_ADPCM_CODE:
1701 // These formats require a fact chunk.
1702 if (file->facthint == FactStrict && file->fact.status <= 0) {
1703 return SDL_SetError("Missing fact chunk in WAVE file");
1704 }
1705 SDL_FALLTHROUGH;
1706 case PCM_CODE:
1707 // All supported formats require a non-zero bit depth.
1708 if (file->chunk.size < 16) {
1709 return SDL_SetError("Missing wBitsPerSample field in WAVE fmt chunk");
1710 } else if (format->bitspersample == 0) {
1711 return SDL_SetError("Invalid bits per sample");
1712 }
1713
1714 // All supported formats must have a proper block size.
1715 if (format->blockalign == 0) {
1716 format->blockalign = 1; // force it to 1 if it was unset.
1717 }
1718
1719 /* If the fact chunk is valid and the appropriate hint is set, the
1720 * decoders will use the number of sample frames from the fact chunk.
1721 */
1722 if (file->fact.status == 1) {
1723 WaveFactChunkHint hint = file->facthint;
1724 Uint32 samples = file->fact.samplelength;
1725 if (hint == FactTruncate || hint == FactStrict || (hint == FactIgnoreZero && samples > 0)) {
1726 file->fact.status = 2;
1727 }
1728 }
1729 }
1730
1731 // Check the format for encoding specific issues and initialize decoders.
1732 switch (format->encoding) {
1733 case PCM_CODE:
1734 case IEEE_FLOAT_CODE:
1735 if (!PCM_Init(file, datalength)) {
1736 return false;
1737 }
1738 break;
1739 case ALAW_CODE:
1740 case MULAW_CODE:
1741 if (!LAW_Init(file, datalength)) {
1742 return false;
1743 }
1744 break;
1745 case MS_ADPCM_CODE:
1746 if (!MS_ADPCM_Init(file, datalength)) {
1747 return false;
1748 }
1749 break;
1750 case IMA_ADPCM_CODE:
1751 if (!IMA_ADPCM_Init(file, datalength)) {
1752 return false;
1753 }
1754 break;
1755 case MPEG_CODE:
1756 case MPEGLAYER3_CODE:
1757 return SDL_SetError("MPEG formats not supported");
1758 default:
1759 if (format->formattag == EXTENSIBLE_CODE) {
1760 const char *errstr = "Unknown WAVE format GUID: %08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x";
1761 const Uint8 *g = format->subformat;
1762 const Uint32 g1 = g[0] | ((Uint32)g[1] << 8) | ((Uint32)g[2] << 16) | ((Uint32)g[3] << 24);
1763 const Uint32 g2 = g[4] | ((Uint32)g[5] << 8);
1764 const Uint32 g3 = g[6] | ((Uint32)g[7] << 8);
1765 return SDL_SetError(errstr, g1, g2, g3, g[8], g[9], g[10], g[11], g[12], g[13], g[14], g[15]);
1766 }
1767 return SDL_SetError("Unknown WAVE format tag: 0x%04x", (unsigned int)format->encoding);
1768 }
1769
1770 return true;
1771}
1772
1773static bool WaveLoad(SDL_IOStream *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
1774{
1775 int result;
1776 Uint32 chunkcount = 0;
1777 Uint32 chunkcountlimit = 10000;
1778 const char *hint;
1779 Sint64 RIFFstart, RIFFend, lastchunkpos;
1780 bool RIFFlengthknown = false;
1781 WaveFormat *format = &file->format;
1782 WaveChunk *chunk = &file->chunk;
1783 WaveChunk RIFFchunk;
1784 WaveChunk fmtchunk;
1785 WaveChunk datachunk;
1786
1787 SDL_zero(RIFFchunk);
1788 SDL_zero(fmtchunk);
1789 SDL_zero(datachunk);
1790
1791 hint = SDL_GetHint(SDL_HINT_WAVE_CHUNK_LIMIT);
1792 if (hint) {
1793 unsigned int count;
1794 if (SDL_sscanf(hint, "%u", &count) == 1) {
1795 chunkcountlimit = count <= SDL_MAX_UINT32 ? count : SDL_MAX_UINT32;
1796 }
1797 }
1798
1799 RIFFstart = SDL_TellIO(src);
1800 if (RIFFstart < 0) {
1801 return SDL_SetError("Could not seek in file");
1802 }
1803
1804 RIFFchunk.position = RIFFstart;
1805 if (WaveNextChunk(src, &RIFFchunk) < 0) {
1806 return SDL_SetError("Could not read RIFF header");
1807 }
1808
1809 // Check main WAVE file identifiers.
1810 if (RIFFchunk.fourcc == RIFF) {
1811 Uint32 formtype;
1812 // Read the form type. "WAVE" expected.
1813 if (!SDL_ReadU32LE(src, &formtype)) {
1814 return SDL_SetError("Could not read RIFF form type");
1815 } else if (formtype != WAVE) {
1816 return SDL_SetError("RIFF form type is not WAVE (not a Waveform file)");
1817 }
1818 } else if (RIFFchunk.fourcc == WAVE) {
1819 // RIFF chunk missing or skipped. Length unknown.
1820 RIFFchunk.position = 0;
1821 RIFFchunk.length = 0;
1822 } else {
1823 return SDL_SetError("Could not find RIFF or WAVE identifiers (not a Waveform file)");
1824 }
1825
1826 // The 4-byte form type is immediately followed by the first chunk.
1827 chunk->position = RIFFchunk.position + 4;
1828
1829 /* Use the RIFF chunk size to limit the search for the chunks. This is not
1830 * always reliable and the hint can be used to tune the behavior. By
1831 * default, it will never search past 4 GiB.
1832 */
1833 switch (file->riffhint) {
1834 case RiffSizeIgnore:
1835 RIFFend = RIFFchunk.position + SDL_MAX_UINT32;
1836 break;
1837 default:
1838 case RiffSizeIgnoreZero:
1839 if (RIFFchunk.length == 0) {
1840 RIFFend = RIFFchunk.position + SDL_MAX_UINT32;
1841 break;
1842 }
1843 SDL_FALLTHROUGH;
1844 case RiffSizeForce:
1845 RIFFend = RIFFchunk.position + RIFFchunk.length;
1846 RIFFlengthknown = true;
1847 break;
1848 case RiffSizeMaximum:
1849 RIFFend = SDL_MAX_SINT64;
1850 break;
1851 }
1852
1853 /* Step through all chunks and save information on the fmt, data, and fact
1854 * chunks. Ignore the chunks we don't know as per specification. This
1855 * currently also ignores cue, list, and slnt chunks.
1856 */
1857 while ((Uint64)RIFFend > (Uint64)chunk->position + chunk->length + (chunk->length & 1)) {
1858 // Abort after too many chunks or else corrupt files may waste time.
1859 if (chunkcount++ >= chunkcountlimit) {
1860 return SDL_SetError("Chunk count in WAVE file exceeds limit of %" SDL_PRIu32, chunkcountlimit);
1861 }
1862
1863 result = WaveNextChunk(src, chunk);
1864 if (result < 0) {
1865 // Unexpected EOF. Corrupt file or I/O issues.
1866 if (file->trunchint == TruncVeryStrict) {
1867 return SDL_SetError("Unexpected end of WAVE file");
1868 }
1869 // Let the checks after this loop sort this issue out.
1870 break;
1871 } else if (result == -2) {
1872 return SDL_SetError("Could not seek to WAVE chunk header");
1873 }
1874
1875 if (chunk->fourcc == FMT) {
1876 if (fmtchunk.fourcc == FMT) {
1877 // Multiple fmt chunks. Ignore or error?
1878 } else {
1879 // The fmt chunk must occur before the data chunk.
1880 if (datachunk.fourcc == DATA) {
1881 return SDL_SetError("fmt chunk after data chunk in WAVE file");
1882 }
1883 fmtchunk = *chunk;
1884 }
1885 } else if (chunk->fourcc == DATA) {
1886 /* Only use the first data chunk. Handling the wavl list madness
1887 * may require a different approach.
1888 */
1889 if (datachunk.fourcc != DATA) {
1890 datachunk = *chunk;
1891 }
1892 } else if (chunk->fourcc == FACT) {
1893 /* The fact chunk data must be at least 4 bytes for the
1894 * dwSampleLength field. Ignore all fact chunks after the first one.
1895 */
1896 if (file->fact.status == 0) {
1897 if (chunk->length < 4) {
1898 file->fact.status = -1;
1899 } else {
1900 // Let's use src directly, it's just too convenient.
1901 Sint64 position = SDL_SeekIO(src, chunk->position, SDL_IO_SEEK_SET);
1902 if (position == chunk->position && SDL_ReadU32LE(src, &file->fact.samplelength)) {
1903 file->fact.status = 1;
1904 } else {
1905 file->fact.status = -1;
1906 }
1907 }
1908 }
1909 }
1910
1911 /* Go through all chunks in verystrict mode or stop the search early if
1912 * all required chunks were found.
1913 */
1914 if (file->trunchint == TruncVeryStrict) {
1915 if ((Uint64)RIFFend < (Uint64)chunk->position + chunk->length) {
1916 return SDL_SetError("RIFF size truncates chunk");
1917 }
1918 } else if (fmtchunk.fourcc == FMT && datachunk.fourcc == DATA) {
1919 if (file->fact.status == 1 || file->facthint == FactIgnore || file->facthint == FactNoHint) {
1920 break;
1921 }
1922 }
1923 }
1924
1925 /* Save the position after the last chunk. This position will be used if the
1926 * RIFF length is unknown.
1927 */
1928 lastchunkpos = chunk->position + chunk->length;
1929
1930 // The fmt chunk is mandatory.
1931 if (fmtchunk.fourcc != FMT) {
1932 return SDL_SetError("Missing fmt chunk in WAVE file");
1933 }
1934 // A data chunk must be present.
1935 if (datachunk.fourcc != DATA) {
1936 return SDL_SetError("Missing data chunk in WAVE file");
1937 }
1938 // Check if the last chunk has all of its data in verystrict mode.
1939 if (file->trunchint == TruncVeryStrict) {
1940 // data chunk is handled later.
1941 if (chunk->fourcc != DATA && chunk->length > 0) {
1942 Uint8 tmp;
1943 Uint64 position = (Uint64)chunk->position + chunk->length - 1;
1944 if (position > SDL_MAX_SINT64 || SDL_SeekIO(src, (Sint64)position, SDL_IO_SEEK_SET) != (Sint64)position) {
1945 return SDL_SetError("Could not seek to WAVE chunk data");
1946 } else if (!SDL_ReadU8(src, &tmp)) {
1947 return SDL_SetError("RIFF size truncates chunk");
1948 }
1949 }
1950 }
1951
1952 // Process fmt chunk.
1953 *chunk = fmtchunk;
1954
1955 /* No need to read more than 1046 bytes of the fmt chunk data with the
1956 * formats that are currently supported. (1046 because of MS ADPCM coefficients)
1957 */
1958 if (WaveReadPartialChunkData(src, chunk, 1046) < 0) {
1959 return SDL_SetError("Could not read data of WAVE fmt chunk");
1960 }
1961
1962 /* The fmt chunk data must be at least 14 bytes to include all common fields.
1963 * It usually is 16 and larger depending on the header and encoding.
1964 */
1965 if (chunk->length < 14) {
1966 return SDL_SetError("Invalid WAVE fmt chunk length (too small)");
1967 } else if (chunk->size < 14) {
1968 return SDL_SetError("Could not read data of WAVE fmt chunk");
1969 } else if (!WaveReadFormat(file)) {
1970 return false;
1971 } else if (!WaveCheckFormat(file, (size_t)datachunk.length)) {
1972 return false;
1973 }
1974
1975#ifdef SDL_WAVE_DEBUG_LOG_FORMAT
1976 WaveDebugLogFormat(file);
1977#endif
1978#ifdef SDL_WAVE_DEBUG_DUMP_FORMAT
1979 WaveDebugDumpFormat(file, RIFFchunk.length, fmtchunk.length, datachunk.length);
1980#endif
1981
1982 WaveFreeChunkData(chunk);
1983
1984 // Process data chunk.
1985 *chunk = datachunk;
1986
1987 if (chunk->length > 0) {
1988 result = WaveReadChunkData(src, chunk);
1989 if (result < 0) {
1990 return false;
1991 } else if (result == -2) {
1992 return SDL_SetError("Could not seek data of WAVE data chunk");
1993 }
1994 }
1995
1996 if (chunk->length != chunk->size) {
1997 // I/O issues or corrupt file.
1998 if (file->trunchint == TruncVeryStrict || file->trunchint == TruncStrict) {
1999 return SDL_SetError("Could not read data of WAVE data chunk");
2000 }
2001 // The decoders handle this truncation.
2002 }
2003
2004 // Decode or convert the data if necessary.
2005 switch (format->encoding) {
2006 case PCM_CODE:
2007 case IEEE_FLOAT_CODE:
2008 if (!PCM_Decode(file, audio_buf, audio_len)) {
2009 return false;
2010 }
2011 break;
2012 case ALAW_CODE:
2013 case MULAW_CODE:
2014 if (!LAW_Decode(file, audio_buf, audio_len)) {
2015 return false;
2016 }
2017 break;
2018 case MS_ADPCM_CODE:
2019 if (!MS_ADPCM_Decode(file, audio_buf, audio_len)) {
2020 return false;
2021 }
2022 break;
2023 case IMA_ADPCM_CODE:
2024 if (!IMA_ADPCM_Decode(file, audio_buf, audio_len)) {
2025 return false;
2026 }
2027 break;
2028 }
2029
2030 /* Setting up the specs. All unsupported formats were filtered out
2031 * by checks earlier in this function.
2032 */
2033 spec->freq = format->frequency;
2034 spec->channels = (Uint8)format->channels;
2035 spec->format = SDL_AUDIO_UNKNOWN;
2036
2037 switch (format->encoding) {
2038 case MS_ADPCM_CODE:
2039 case IMA_ADPCM_CODE:
2040 case ALAW_CODE:
2041 case MULAW_CODE:
2042 // These can be easily stored in the byte order of the system.
2043 spec->format = SDL_AUDIO_S16;
2044 break;
2045 case IEEE_FLOAT_CODE:
2046 spec->format = SDL_AUDIO_F32LE;
2047 break;
2048 case PCM_CODE:
2049 switch (format->bitspersample) {
2050 case 8:
2051 spec->format = SDL_AUDIO_U8;
2052 break;
2053 case 16:
2054 spec->format = SDL_AUDIO_S16LE;
2055 break;
2056 case 24: // Has been shifted to 32 bits.
2057 case 32:
2058 spec->format = SDL_AUDIO_S32LE;
2059 break;
2060 default:
2061 // Just in case something unexpected happened in the checks.
2062 return SDL_SetError("Unexpected %u-bit PCM data format", (unsigned int)format->bitspersample);
2063 }
2064 break;
2065 default:
2066 return SDL_SetError("Unexpected data format");
2067 }
2068
2069 // Report the end position back to the cleanup code.
2070 if (RIFFlengthknown) {
2071 chunk->position = RIFFend;
2072 } else {
2073 chunk->position = lastchunkpos;
2074 }
2075
2076 return true;
2077}
2078
2079bool SDL_LoadWAV_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
2080{
2081 bool result = false;
2082 WaveFile file;
2083
2084 if (spec) {
2085 SDL_zerop(spec);
2086 }
2087 if (audio_buf) {
2088 *audio_buf = NULL;
2089 }
2090 if (audio_len) {
2091 *audio_len = 0;
2092 }
2093
2094 // Make sure we are passed a valid data source
2095 if (!src) {
2096 SDL_InvalidParamError("src");
2097 goto done;
2098 } else if (!spec) {
2099 SDL_InvalidParamError("spec");
2100 goto done;
2101 } else if (!audio_buf) {
2102 SDL_InvalidParamError("audio_buf");
2103 goto done;
2104 } else if (!audio_len) {
2105 SDL_InvalidParamError("audio_len");
2106 goto done;
2107 }
2108
2109 SDL_zero(file);
2110 file.riffhint = WaveGetRiffSizeHint();
2111 file.trunchint = WaveGetTruncationHint();
2112 file.facthint = WaveGetFactChunkHint();
2113
2114 result = WaveLoad(src, &file, spec, audio_buf, audio_len);
2115 if (!result) {
2116 SDL_free(*audio_buf);
2117 audio_buf = NULL;
2118 audio_len = 0;
2119 }
2120
2121 // Cleanup
2122 if (!closeio) {
2123 SDL_SeekIO(src, file.chunk.position, SDL_IO_SEEK_SET);
2124 }
2125 WaveFreeChunkData(&file.chunk);
2126 SDL_free(file.decoderdata);
2127done:
2128 if (closeio && src) {
2129 SDL_CloseIO(src);
2130 }
2131 return result;
2132}
2133
2134bool SDL_LoadWAV(const char *path, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
2135{
2136 SDL_IOStream *stream = SDL_IOFromFile(path, "rb");
2137 if (!stream) {
2138 if (spec) {
2139 SDL_zerop(spec);
2140 }
2141 if (audio_buf) {
2142 *audio_buf = NULL;
2143 }
2144 if (audio_len) {
2145 *audio_len = 0;
2146 }
2147 return false;
2148 }
2149 return SDL_LoadWAV_IO(stream, true, spec, audio_buf, audio_len);
2150}
2151
diff --git a/contrib/SDL-3.2.8/src/audio/SDL_wave.h b/contrib/SDL-3.2.8/src/audio/SDL_wave.h
new file mode 100644
index 0000000..0ec8965
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/SDL_wave.h
@@ -0,0 +1,151 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// RIFF WAVE files are little-endian
24
25/*******************************************/
26// Define values for Microsoft WAVE format
27/*******************************************/
28// FOURCC
29#define RIFF 0x46464952 // "RIFF"
30#define WAVE 0x45564157 // "WAVE"
31#define FACT 0x74636166 // "fact"
32#define LIST 0x5453494c // "LIST"
33#define BEXT 0x74786562 // "bext"
34#define JUNK 0x4B4E554A // "JUNK"
35#define FMT 0x20746D66 // "fmt "
36#define DATA 0x61746164 // "data"
37// Format tags
38#define UNKNOWN_CODE 0x0000
39#define PCM_CODE 0x0001
40#define MS_ADPCM_CODE 0x0002
41#define IEEE_FLOAT_CODE 0x0003
42#define ALAW_CODE 0x0006
43#define MULAW_CODE 0x0007
44#define IMA_ADPCM_CODE 0x0011
45#define MPEG_CODE 0x0050
46#define MPEGLAYER3_CODE 0x0055
47#define EXTENSIBLE_CODE 0xFFFE
48
49// Stores the WAVE format information.
50typedef struct WaveFormat
51{
52 Uint16 formattag; // Raw value of the first field in the fmt chunk data.
53 Uint16 encoding; // Actual encoding, possibly from the extensible header.
54 Uint16 channels; // Number of channels.
55 Uint32 frequency; // Sampling rate in Hz.
56 Uint32 byterate; // Average bytes per second.
57 Uint16 blockalign; // Bytes per block.
58 Uint16 bitspersample; // Currently supported are 8, 16, 24, 32, and 4 for ADPCM.
59
60 /* Extra information size. Number of extra bytes starting at byte 18 in the
61 * fmt chunk data. This is at least 22 for the extensible header.
62 */
63 Uint16 extsize;
64
65 // Extensible WAVE header fields
66 Uint16 validsamplebits;
67 Uint32 samplesperblock; // For compressed formats. Can be zero. Actually 16 bits in the header.
68 Uint32 channelmask;
69 Uint8 subformat[16]; // A format GUID.
70} WaveFormat;
71
72// Stores information on the fact chunk.
73typedef struct WaveFact
74{
75 /* Represents the state of the fact chunk in the WAVE file.
76 * Set to -1 if the fact chunk is invalid.
77 * Set to 0 if the fact chunk is not present
78 * Set to 1 if the fact chunk is present and valid.
79 * Set to 2 if samplelength is going to be used as the number of sample frames.
80 */
81 Sint32 status;
82
83 /* Version 1 of the RIFF specification calls the field in the fact chunk
84 * dwFileSize. The Standards Update then calls it dwSampleLength and specifies
85 * that it is 'the length of the data in samples'. WAVE files from Windows
86 * with this chunk have it set to the samples per channel (sample frames).
87 * This is useful to truncate compressed audio to a specific sample count
88 * because a compressed block is usually decoded to a fixed number of
89 * sample frames.
90 */
91 Uint32 samplelength; // Raw sample length value from the fact chunk.
92} WaveFact;
93
94// Generic struct for the chunks in the WAVE file.
95typedef struct WaveChunk
96{
97 Uint32 fourcc; // FOURCC of the chunk.
98 Uint32 length; // Size of the chunk data.
99 Sint64 position; // Position of the data in the stream.
100 Uint8 *data; // When allocated, this points to the chunk data. length is used for the memory allocation size.
101 size_t size; // Number of bytes in data that could be read from the stream. Can be smaller than length.
102} WaveChunk;
103
104// Controls how the size of the RIFF chunk affects the loading of a WAVE file.
105typedef enum WaveRiffSizeHint
106{
107 RiffSizeNoHint,
108 RiffSizeForce,
109 RiffSizeIgnoreZero,
110 RiffSizeIgnore,
111 RiffSizeMaximum
112} WaveRiffSizeHint;
113
114// Controls how a truncated WAVE file is handled.
115typedef enum WaveTruncationHint
116{
117 TruncNoHint,
118 TruncVeryStrict,
119 TruncStrict,
120 TruncDropFrame,
121 TruncDropBlock
122} WaveTruncationHint;
123
124// Controls how the fact chunk affects the loading of a WAVE file.
125typedef enum WaveFactChunkHint
126{
127 FactNoHint,
128 FactTruncate,
129 FactStrict,
130 FactIgnoreZero,
131 FactIgnore
132} WaveFactChunkHint;
133
134typedef struct WaveFile
135{
136 WaveChunk chunk;
137 WaveFormat format;
138 WaveFact fact;
139
140 /* Number of sample frames that will be decoded. Calculated either with the
141 * size of the data chunk or, if the appropriate hint is enabled, with the
142 * sample length value from the fact chunk.
143 */
144 Sint64 sampleframes;
145
146 void *decoderdata; // Some decoders require extra data for a state.
147
148 WaveRiffSizeHint riffhint;
149 WaveTruncationHint trunchint;
150 WaveFactChunkHint facthint;
151} WaveFile;
diff --git a/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c b/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c
new file mode 100644
index 0000000..3360bec
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.c
@@ -0,0 +1,551 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_AAUDIO
24
25#include "../SDL_sysaudio.h"
26#include "SDL_aaudio.h"
27
28#include "../../core/android/SDL_android.h"
29#include <aaudio/AAudio.h>
30
31#if __ANDROID_API__ < 31
32#define AAUDIO_FORMAT_PCM_I32 4
33#endif
34
35struct SDL_PrivateAudioData
36{
37 AAudioStream *stream;
38 int num_buffers;
39 Uint8 *mixbuf; // Raw mixing buffer
40 size_t mixbuf_bytes; // num_buffers * device->buffer_size
41 size_t callback_bytes;
42 size_t processed_bytes;
43 SDL_Semaphore *semaphore;
44 SDL_AtomicInt error_callback_triggered;
45};
46
47// Debug
48#if 0
49#define LOGI(...) SDL_Log(__VA_ARGS__);
50#else
51#define LOGI(...)
52#endif
53
54#define LIB_AAUDIO_SO "libaaudio.so"
55
56typedef struct AAUDIO_Data
57{
58 SDL_SharedObject *handle;
59#define SDL_PROC(ret, func, params) ret (*func) params;
60#include "SDL_aaudiofuncs.h"
61} AAUDIO_Data;
62static AAUDIO_Data ctx;
63
64static bool AAUDIO_LoadFunctions(AAUDIO_Data *data)
65{
66#define SDL_PROC(ret, func, params) \
67 do { \
68 data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
69 if (!data->func) { \
70 return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \
71 } \
72 } while (0);
73#include "SDL_aaudiofuncs.h"
74 return true;
75}
76
77
78static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error)
79{
80 LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error));
81
82 // You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here.
83 // Just flag the device so we can kill it in PlayDevice instead.
84 SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
85 SDL_SetAtomicInt(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error.
86 SDL_SignalSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
87}
88
89static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames)
90{
91 SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
92 struct SDL_PrivateAudioData *hidden = device->hidden;
93 size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec);
94 size_t callback_bytes = numFrames * framesize;
95 size_t old_buffer_index = hidden->callback_bytes / device->buffer_size;
96
97 if (device->recording) {
98 const Uint8 *input = (const Uint8 *)audioData;
99 size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes);
100 size_t size = SDL_min(available_bytes, callback_bytes);
101 size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
102 size_t end = (offset + size) % hidden->mixbuf_bytes;
103 SDL_assert(size <= hidden->mixbuf_bytes);
104
105//LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize);
106
107 if (offset <= end) {
108 SDL_memcpy(&hidden->mixbuf[offset], input, size);
109 } else {
110 size_t partial = (hidden->mixbuf_bytes - offset);
111 SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial);
112 SDL_memcpy(&hidden->mixbuf[0], &input[partial], end);
113 }
114
115 SDL_MemoryBarrierRelease();
116 hidden->callback_bytes += size;
117
118 if (size < callback_bytes) {
119 LOGI("Audio recording overflow, dropped %zu frames", (callback_bytes - size) / framesize);
120 }
121 } else {
122 Uint8 *output = (Uint8 *)audioData;
123 size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes);
124 size_t size = SDL_min(available_bytes, callback_bytes);
125 size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
126 size_t end = (offset + size) % hidden->mixbuf_bytes;
127 SDL_assert(size <= hidden->mixbuf_bytes);
128
129//LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize);
130
131 SDL_MemoryBarrierAcquire();
132 if (offset <= end) {
133 SDL_memcpy(output, &hidden->mixbuf[offset], size);
134 } else {
135 size_t partial = (hidden->mixbuf_bytes - offset);
136 SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial);
137 SDL_memcpy(&output[partial], &hidden->mixbuf[0], end);
138 }
139 hidden->callback_bytes += size;
140
141 if (size < callback_bytes) {
142 LOGI("Audio playback underflow, missed %zu frames", (callback_bytes - size) / framesize);
143 SDL_memset(&output[size], device->silence_value, (callback_bytes - size));
144 }
145 }
146
147 size_t new_buffer_index = hidden->callback_bytes / device->buffer_size;
148 while (old_buffer_index < new_buffer_index) {
149 // Trigger audio processing
150 SDL_SignalSemaphore(hidden->semaphore);
151 ++old_buffer_index;
152 }
153
154 return AAUDIO_CALLBACK_RESULT_CONTINUE;
155}
156
157static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
158{
159 struct SDL_PrivateAudioData *hidden = device->hidden;
160 size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
161 return &hidden->mixbuf[offset];
162}
163
164static bool AAUDIO_WaitDevice(SDL_AudioDevice *device)
165{
166 while (!SDL_GetAtomicInt(&device->shutdown)) {
167 // this semaphore won't fire when the app is in the background (AAUDIO_PauseDevices was called).
168 if (SDL_WaitSemaphoreTimeout(device->hidden->semaphore, 100)) {
169 return true; // semaphore was signaled, let's go!
170 }
171 // Still waiting on the semaphore (or the system), check other things then wait again.
172 }
173 return true;
174}
175
176static bool BuildAAudioStream(SDL_AudioDevice *device);
177
178static bool RecoverAAudioDevice(SDL_AudioDevice *device)
179{
180 struct SDL_PrivateAudioData *hidden = device->hidden;
181
182 // attempt to build a new stream, in case there's a new default device.
183 ctx.AAudioStream_requestStop(hidden->stream);
184 ctx.AAudioStream_close(hidden->stream);
185 hidden->stream = NULL;
186
187 SDL_aligned_free(hidden->mixbuf);
188 hidden->mixbuf = NULL;
189
190 SDL_DestroySemaphore(hidden->semaphore);
191 hidden->semaphore = NULL;
192
193 const int prev_sample_frames = device->sample_frames;
194 SDL_AudioSpec prevspec;
195 SDL_copyp(&prevspec, &device->spec);
196
197 if (!BuildAAudioStream(device)) {
198 return false; // oh well, we tried.
199 }
200
201 // we don't know the new device spec until we open the new device, so we saved off the old one and force it back
202 // so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec.
203 const int new_sample_frames = device->sample_frames;
204 SDL_AudioSpec newspec;
205 SDL_copyp(&newspec, &device->spec);
206
207 device->sample_frames = prev_sample_frames;
208 SDL_copyp(&device->spec, &prevspec);
209 if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
210 return false; // ugh
211 }
212 return true;
213}
214
215
216static bool AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
217{
218 struct SDL_PrivateAudioData *hidden = device->hidden;
219
220 // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
221 const aaudio_result_t err = (aaudio_result_t) SDL_GetAtomicInt(&hidden->error_callback_triggered);
222 if (err) {
223 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err));
224
225 if (!RecoverAAudioDevice(device)) {
226 return false; // oh well, we went down hard.
227 }
228 } else {
229 SDL_MemoryBarrierRelease();
230 hidden->processed_bytes += buflen;
231 }
232 return true;
233}
234
235static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
236{
237 struct SDL_PrivateAudioData *hidden = device->hidden;
238
239 // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
240 if (SDL_GetAtomicInt(&hidden->error_callback_triggered)) {
241 SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
242 return -1;
243 }
244
245 SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here
246 size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
247 SDL_MemoryBarrierAcquire();
248 SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen);
249 hidden->processed_bytes += buflen;
250 return buflen;
251}
252
253static void AAUDIO_CloseDevice(SDL_AudioDevice *device)
254{
255 struct SDL_PrivateAudioData *hidden = device->hidden;
256 LOGI(__func__);
257
258 if (hidden) {
259 if (hidden->stream) {
260 ctx.AAudioStream_requestStop(hidden->stream);
261 // !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)?
262 // !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again?
263 ctx.AAudioStream_close(hidden->stream);
264 }
265
266 if (hidden->semaphore) {
267 SDL_DestroySemaphore(hidden->semaphore);
268 }
269
270 SDL_aligned_free(hidden->mixbuf);
271 SDL_free(hidden);
272 device->hidden = NULL;
273 }
274}
275
276static bool BuildAAudioStream(SDL_AudioDevice *device)
277{
278 struct SDL_PrivateAudioData *hidden = device->hidden;
279 const bool recording = device->recording;
280 aaudio_result_t res;
281
282 SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
283
284 AAudioStreamBuilder *builder = NULL;
285 res = ctx.AAudio_createStreamBuilder(&builder);
286 if (res != AAUDIO_OK) {
287 LOGI("SDL Failed AAudio_createStreamBuilder %d", res);
288 return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res);
289 } else if (!builder) {
290 LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL");
291 return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL");
292 }
293
294#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
295 const int aaudio_device_id = (int) ((size_t) device->handle);
296 LOGI("Opening device id %d", aaudio_device_id);
297 ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id);
298#endif
299
300 aaudio_format_t format;
301 if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) {
302 format = AAUDIO_FORMAT_PCM_I32;
303 } else if (device->spec.format == SDL_AUDIO_F32) {
304 format = AAUDIO_FORMAT_PCM_FLOAT;
305 } else {
306 format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else.
307 }
308 ctx.AAudioStreamBuilder_setFormat(builder, format);
309 ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq);
310 ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
311
312 const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
313 ctx.AAudioStreamBuilder_setDirection(builder, direction);
314 ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device);
315 ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device);
316 // Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people
317 if (SDL_GetHintBoolean(SDL_HINT_ANDROID_LOW_LATENCY_AUDIO, true)) {
318 SDL_Log("Low latency audio enabled");
319 ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
320 } else {
321 SDL_Log("Low latency audio disabled");
322 }
323
324 LOGI("AAudio Try to open %u hz %s %u channels samples %u",
325 device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
326 device->spec.channels, device->sample_frames);
327
328 res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream);
329 if (res != AAUDIO_OK) {
330 LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
331 ctx.AAudioStreamBuilder_delete(builder);
332 return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
333 }
334 ctx.AAudioStreamBuilder_delete(builder);
335
336 device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream);
337 if (device->sample_frames == AAUDIO_UNSPECIFIED) {
338 // We'll get variable frames in the callback, make sure we have at least half a buffer available
339 device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2;
340 }
341
342 device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream);
343 device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream);
344
345 format = ctx.AAudioStream_getFormat(hidden->stream);
346 if (format == AAUDIO_FORMAT_PCM_I16) {
347 device->spec.format = SDL_AUDIO_S16;
348 } else if (format == AAUDIO_FORMAT_PCM_I32) {
349 device->spec.format = SDL_AUDIO_S32;
350 } else if (format == AAUDIO_FORMAT_PCM_FLOAT) {
351 device->spec.format = SDL_AUDIO_F32;
352 } else {
353 return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format);
354 }
355
356 SDL_UpdatedAudioDeviceFormat(device);
357
358 // Allocate a triple buffered mixing buffer
359 // Two buffers can be in the process of being filled while the third is being read
360 hidden->num_buffers = 3;
361 hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
362 hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), hidden->mixbuf_bytes);
363 if (!hidden->mixbuf) {
364 return false;
365 }
366 hidden->processed_bytes = 0;
367 hidden->callback_bytes = 0;
368
369 hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers);
370 if (!hidden->semaphore) {
371 LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording);
372 return false;
373 }
374
375 LOGI("AAudio Actually opened %u hz %s %u channels samples %u, buffers %d",
376 device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
377 device->spec.channels, device->sample_frames, hidden->num_buffers);
378
379 res = ctx.AAudioStream_requestStart(hidden->stream);
380 if (res != AAUDIO_OK) {
381 LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording);
382 return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
383 }
384
385 LOGI("SDL AAudioStream_requestStart OK");
386
387 return true;
388}
389
390// !!! FIXME: make this non-blocking!
391static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
392{
393 SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
394}
395
396static bool AAUDIO_OpenDevice(SDL_AudioDevice *device)
397{
398#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
399 SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
400#endif
401
402 LOGI(__func__);
403
404 if (device->recording) {
405 // !!! FIXME: make this non-blocking!
406 SDL_AtomicInt permission_response;
407 SDL_SetAtomicInt(&permission_response, 0);
408 if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
409 return false;
410 }
411
412 while (SDL_GetAtomicInt(&permission_response) == 0) {
413 SDL_Delay(10);
414 }
415
416 if (SDL_GetAtomicInt(&permission_response) < 0) {
417 LOGI("This app doesn't have RECORD_AUDIO permission");
418 return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
419 }
420 }
421
422 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
423 if (!device->hidden) {
424 return false;
425 }
426
427 return BuildAAudioStream(device);
428}
429
430static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
431{
432 struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
433 if (hidden) {
434 if (hidden->stream) {
435 aaudio_result_t res;
436
437 if (device->recording) {
438 // Pause() isn't implemented for recording, use Stop()
439 res = ctx.AAudioStream_requestStop(hidden->stream);
440 } else {
441 res = ctx.AAudioStream_requestPause(hidden->stream);
442 }
443
444 if (res != AAUDIO_OK) {
445 LOGI("SDL Failed AAudioStream_requestPause %d", res);
446 SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
447 }
448 }
449 }
450 return false; // keep enumerating.
451}
452
453// Pause (block) all non already paused audio devices by taking their mixer lock
454void AAUDIO_PauseDevices(void)
455{
456 if (ctx.handle) { // AAUDIO driver is used?
457 (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL);
458 }
459}
460
461// Resume (unblock) all non already paused audio devices by releasing their mixer lock
462static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
463{
464 struct SDL_PrivateAudioData *hidden = device->hidden;
465 if (hidden) {
466 if (hidden->stream) {
467 aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream);
468 if (res != AAUDIO_OK) {
469 LOGI("SDL Failed AAudioStream_requestStart %d", res);
470 SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
471 }
472 }
473 }
474 return false; // keep enumerating.
475}
476
477void AAUDIO_ResumeDevices(void)
478{
479 if (ctx.handle) { // AAUDIO driver is used?
480 (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL);
481 }
482}
483
484static void AAUDIO_Deinitialize(void)
485{
486 Android_StopAudioHotplug();
487
488 LOGI(__func__);
489 if (ctx.handle) {
490 SDL_UnloadObject(ctx.handle);
491 }
492 SDL_zero(ctx);
493 LOGI("End AAUDIO %s", SDL_GetError());
494}
495
496
497static bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
498{
499 LOGI(__func__);
500
501 /* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release,
502 * so don't use it until 8.1.
503 *
504 * See https://github.com/google/oboe/issues/40 for more information.
505 */
506 if (SDL_GetAndroidSDKVersion() < 27) {
507 return false;
508 }
509
510 SDL_zero(ctx);
511
512 ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO);
513 if (!ctx.handle) {
514 LOGI("SDL couldn't find " LIB_AAUDIO_SO);
515 return false;
516 }
517
518 if (!AAUDIO_LoadFunctions(&ctx)) {
519 SDL_UnloadObject(ctx.handle);
520 SDL_zero(ctx);
521 return false;
522 }
523
524 impl->ThreadInit = Android_AudioThreadInit;
525 impl->Deinitialize = AAUDIO_Deinitialize;
526 impl->OpenDevice = AAUDIO_OpenDevice;
527 impl->CloseDevice = AAUDIO_CloseDevice;
528 impl->WaitDevice = AAUDIO_WaitDevice;
529 impl->PlayDevice = AAUDIO_PlayDevice;
530 impl->GetDeviceBuf = AAUDIO_GetDeviceBuf;
531 impl->WaitRecordingDevice = AAUDIO_WaitDevice;
532 impl->RecordDevice = AAUDIO_RecordDevice;
533
534 impl->HasRecordingSupport = true;
535
536#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
537 impl->DetectDevices = Android_StartAudioHotplug;
538#else
539 impl->OnlyHasDefaultPlaybackDevice = true;
540 impl->OnlyHasDefaultRecordingDevice = true;
541#endif
542
543 LOGI("SDL AAUDIO_Init OK");
544 return true;
545}
546
547AudioBootStrap AAUDIO_bootstrap = {
548 "AAudio", "AAudio audio driver", AAUDIO_Init, false, false
549};
550
551#endif // SDL_AUDIO_DRIVER_AAUDIO
diff --git a/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.h b/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.h
new file mode 100644
index 0000000..5326bad
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudio.h
@@ -0,0 +1,38 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_aaudio_h_
24#define SDL_aaudio_h_
25
26#ifdef SDL_AUDIO_DRIVER_AAUDIO
27
28extern void AAUDIO_ResumeDevices(void);
29extern void AAUDIO_PauseDevices(void);
30
31#else
32
33#define AAUDIO_ResumeDevices()
34#define AAUDIO_PauseDevices()
35
36#endif
37
38#endif // SDL_aaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudiofuncs.h b/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudiofuncs.h
new file mode 100644
index 0000000..0298821
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/aaudio/SDL_aaudiofuncs.h
@@ -0,0 +1,82 @@
1/*
2 Simple DirectMedia Layer
3 Copyright , (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#define SDL_PROC_UNUSED(ret, func, params)
23
24SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
25SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
26SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder * *builder))
27SDL_PROC(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder * builder, int32_t deviceId))
28SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder * builder, int32_t sampleRate))
29SDL_PROC(void, AAudioStreamBuilder_setChannelCount, (AAudioStreamBuilder * builder, int32_t channelCount))
30SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuilder * builder, int32_t samplesPerFrame))
31SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format))
32SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode))
33SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction))
34SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames))
35SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
36SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28
37SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28
38SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28
39SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29
40SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28
41SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30
42SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData))
43SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames))
44SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData))
45SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream))
46SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder))
47SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) // API 30
48SDL_PROC(aaudio_result_t, AAudioStream_close, (AAudioStream * stream))
49SDL_PROC(aaudio_result_t, AAudioStream_requestStart, (AAudioStream * stream))
50SDL_PROC(aaudio_result_t, AAudioStream_requestPause, (AAudioStream * stream))
51SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stream))
52SDL_PROC(aaudio_result_t, AAudioStream_requestStop, (AAudioStream * stream))
53SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream * stream))
54SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream * stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds))
55SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
56SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
57SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames))
58SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream))
59SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream))
60SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream))
61SDL_PROC(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream))
62SDL_PROC_UNUSED(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream))
63SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream))
64SDL_PROC(int32_t, AAudioStream_getChannelCount, (AAudioStream * stream))
65SDL_PROC_UNUSED(int32_t, AAudioStream_getSamplesPerFrame, (AAudioStream * stream))
66SDL_PROC_UNUSED(int32_t, AAudioStream_getDeviceId, (AAudioStream * stream))
67SDL_PROC(aaudio_format_t, AAudioStream_getFormat, (AAudioStream * stream))
68SDL_PROC_UNUSED(aaudio_sharing_mode_t, AAudioStream_getSharingMode, (AAudioStream * stream))
69SDL_PROC_UNUSED(aaudio_performance_mode_t, AAudioStream_getPerformanceMode, (AAudioStream * stream))
70SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream * stream))
71SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream * stream))
72SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream * stream))
73SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) // API 28
74SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream * stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds))
75SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) // API 28
76SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) // API 28
77SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) // API 28
78SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) // API 29
79SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) // API 30
80
81#undef SDL_PROC
82#undef SDL_PROC_UNUSED
diff --git a/contrib/SDL-3.2.8/src/audio/alsa/SDL_alsa_audio.c b/contrib/SDL-3.2.8/src/audio/alsa/SDL_alsa_audio.c
new file mode 100644
index 0000000..308eb23
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/alsa/SDL_alsa_audio.c
@@ -0,0 +1,1519 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_ALSA
24
25#ifndef SDL_ALSA_NON_BLOCKING
26#define SDL_ALSA_NON_BLOCKING 0
27#endif
28
29// without the thread, you will detect devices on startup, but will not get further hotplug events. But that might be okay.
30#ifndef SDL_ALSA_HOTPLUG_THREAD
31#define SDL_ALSA_HOTPLUG_THREAD 1
32#endif
33
34// this turns off debug logging completely (but by default this goes to the bitbucket).
35#ifndef SDL_ALSA_DEBUG
36#define SDL_ALSA_DEBUG 1
37#endif
38
39#include "../SDL_sysaudio.h"
40#include "SDL_alsa_audio.h"
41
42#if SDL_ALSA_DEBUG
43#define LOGDEBUG(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "ALSA: " __VA_ARGS__)
44#else
45#define LOGDEBUG(...)
46#endif
47
48//TODO: cleanup once the code settled down
49
50static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
51static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm);
52static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm);
53static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t);
54static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t);
55static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int);
56static int (*ALSA_snd_pcm_prepare)(snd_pcm_t *);
57static int (*ALSA_snd_pcm_drain)(snd_pcm_t *);
58static const char *(*ALSA_snd_strerror)(int);
59static size_t (*ALSA_snd_pcm_hw_params_sizeof)(void);
60static size_t (*ALSA_snd_pcm_sw_params_sizeof)(void);
61static void (*ALSA_snd_pcm_hw_params_copy)(snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
62static int (*ALSA_snd_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *);
63static int (*ALSA_snd_pcm_hw_params_set_access)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
64static int (*ALSA_snd_pcm_hw_params_set_format)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
65static int (*ALSA_snd_pcm_hw_params_set_channels)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
66static int (*ALSA_snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *, unsigned int *);
67static int (*ALSA_snd_pcm_hw_params_set_rate_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
68static int (*ALSA_snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
69static int (*ALSA_snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
70static int (*ALSA_snd_pcm_hw_params_set_periods_min)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
71static int (*ALSA_snd_pcm_hw_params_set_periods_first)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
72static int (*ALSA_snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *, unsigned int *, int *);
73static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
74static int (*ALSA_snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
75static int (*ALSA_snd_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *);
76static int (*ALSA_snd_pcm_sw_params_current)(snd_pcm_t *,
77 snd_pcm_sw_params_t *);
78static int (*ALSA_snd_pcm_sw_params_set_start_threshold)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
79static int (*ALSA_snd_pcm_sw_params)(snd_pcm_t *, snd_pcm_sw_params_t *);
80static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int);
81static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
82static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
83static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
84static int (*ALSA_snd_device_name_hint)(int, const char *, void ***);
85static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *);
86static int (*ALSA_snd_device_name_free_hint)(void **);
87static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
88static size_t (*ALSA_snd_ctl_card_info_sizeof)(void);
89static size_t (*ALSA_snd_pcm_info_sizeof)(void);
90static int (*ALSA_snd_card_next)(int*);
91static int (*ALSA_snd_ctl_open)(snd_ctl_t **,const char *,int);
92static int (*ALSA_snd_ctl_close)(snd_ctl_t *);
93static int (*ALSA_snd_ctl_card_info)(snd_ctl_t *, snd_ctl_card_info_t *);
94static int (*ALSA_snd_ctl_pcm_next_device)(snd_ctl_t *, int *);
95static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
96static void (*ALSA_snd_pcm_info_set_device)(snd_pcm_info_t *, unsigned int);
97static void (*ALSA_snd_pcm_info_set_subdevice)(snd_pcm_info_t *, unsigned int);
98static void (*ALSA_snd_pcm_info_set_stream)(snd_pcm_info_t *, snd_pcm_stream_t);
99static int (*ALSA_snd_ctl_pcm_info)(snd_ctl_t *, snd_pcm_info_t *);
100static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
101static const char *(*ALSA_snd_ctl_card_info_get_id)(const snd_ctl_card_info_t *);
102static const char *(*ALSA_snd_pcm_info_get_name)(const snd_pcm_info_t *);
103static const char *(*ALSA_snd_pcm_info_get_subdevice_name)(const snd_pcm_info_t *);
104static const char *(*ALSA_snd_ctl_card_info_get_name)(const snd_ctl_card_info_t *);
105static void (*ALSA_snd_ctl_card_info_clear)(snd_ctl_card_info_t *);
106static int (*ALSA_snd_pcm_hw_free)(snd_pcm_t *);
107static int (*ALSA_snd_pcm_hw_params_set_channels_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *);
108static snd_pcm_chmap_query_t **(*ALSA_snd_pcm_query_chmaps)(snd_pcm_t *pcm);
109static void (*ALSA_snd_pcm_free_chmaps)(snd_pcm_chmap_query_t **maps);
110static int (*ALSA_snd_pcm_set_chmap)(snd_pcm_t *, const snd_pcm_chmap_t *);
111static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *, size_t, char *);
112
113#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
114#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
115#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
116
117static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
118static SDL_SharedObject *alsa_handle = NULL;
119
120static bool load_alsa_sym(const char *fn, void **addr)
121{
122 *addr = SDL_LoadFunction(alsa_handle, fn);
123 if (!*addr) {
124 // Don't call SDL_SetError(): SDL_LoadFunction already did.
125 return false;
126 }
127
128 return true;
129}
130
131// cast funcs to char* first, to please GCC's strict aliasing rules.
132#define SDL_ALSA_SYM(x) \
133 if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \
134 return false
135#else
136#define SDL_ALSA_SYM(x) ALSA_##x = x
137#endif
138
139static bool load_alsa_syms(void)
140{
141 SDL_ALSA_SYM(snd_pcm_open);
142 SDL_ALSA_SYM(snd_pcm_close);
143 SDL_ALSA_SYM(snd_pcm_start);
144 SDL_ALSA_SYM(snd_pcm_writei);
145 SDL_ALSA_SYM(snd_pcm_readi);
146 SDL_ALSA_SYM(snd_pcm_recover);
147 SDL_ALSA_SYM(snd_pcm_prepare);
148 SDL_ALSA_SYM(snd_pcm_drain);
149 SDL_ALSA_SYM(snd_strerror);
150 SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
151 SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
152 SDL_ALSA_SYM(snd_pcm_hw_params_copy);
153 SDL_ALSA_SYM(snd_pcm_hw_params_any);
154 SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
155 SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
156 SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
157 SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
158 SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
159 SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
160 SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
161 SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_min);
162 SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_first);
163 SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
164 SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
165 SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
166 SDL_ALSA_SYM(snd_pcm_hw_params);
167 SDL_ALSA_SYM(snd_pcm_sw_params_current);
168 SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
169 SDL_ALSA_SYM(snd_pcm_sw_params);
170 SDL_ALSA_SYM(snd_pcm_nonblock);
171 SDL_ALSA_SYM(snd_pcm_wait);
172 SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
173 SDL_ALSA_SYM(snd_pcm_reset);
174 SDL_ALSA_SYM(snd_device_name_hint);
175 SDL_ALSA_SYM(snd_device_name_get_hint);
176 SDL_ALSA_SYM(snd_device_name_free_hint);
177 SDL_ALSA_SYM(snd_pcm_avail);
178 SDL_ALSA_SYM(snd_ctl_card_info_sizeof);
179 SDL_ALSA_SYM(snd_pcm_info_sizeof);
180 SDL_ALSA_SYM(snd_card_next);
181 SDL_ALSA_SYM(snd_ctl_open);
182 SDL_ALSA_SYM(snd_ctl_close);
183 SDL_ALSA_SYM(snd_ctl_card_info);
184 SDL_ALSA_SYM(snd_ctl_pcm_next_device);
185 SDL_ALSA_SYM(snd_pcm_info_get_subdevices_count);
186 SDL_ALSA_SYM(snd_pcm_info_set_device);
187 SDL_ALSA_SYM(snd_pcm_info_set_subdevice);
188 SDL_ALSA_SYM(snd_pcm_info_set_stream);
189 SDL_ALSA_SYM(snd_ctl_pcm_info);
190 SDL_ALSA_SYM(snd_pcm_info_get_subdevices_count);
191 SDL_ALSA_SYM(snd_ctl_card_info_get_id);
192 SDL_ALSA_SYM(snd_pcm_info_get_name);
193 SDL_ALSA_SYM(snd_pcm_info_get_subdevice_name);
194 SDL_ALSA_SYM(snd_ctl_card_info_get_name);
195 SDL_ALSA_SYM(snd_ctl_card_info_clear);
196 SDL_ALSA_SYM(snd_pcm_hw_free);
197 SDL_ALSA_SYM(snd_pcm_hw_params_set_channels_near);
198 SDL_ALSA_SYM(snd_pcm_query_chmaps);
199 SDL_ALSA_SYM(snd_pcm_free_chmaps);
200 SDL_ALSA_SYM(snd_pcm_set_chmap);
201 SDL_ALSA_SYM(snd_pcm_chmap_print);
202
203 return true;
204}
205
206#undef SDL_ALSA_SYM
207
208#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
209
210static void UnloadALSALibrary(void)
211{
212 if (alsa_handle) {
213 SDL_UnloadObject(alsa_handle);
214 alsa_handle = NULL;
215 }
216}
217
218static bool LoadALSALibrary(void)
219{
220 bool retval = true;
221 if (!alsa_handle) {
222 alsa_handle = SDL_LoadObject(alsa_library);
223 if (!alsa_handle) {
224 retval = false;
225 // Don't call SDL_SetError(): SDL_LoadObject already did.
226 } else {
227 retval = load_alsa_syms();
228 if (!retval) {
229 UnloadALSALibrary();
230 }
231 }
232 }
233 return retval;
234}
235
236#else
237
238static void UnloadALSALibrary(void)
239{
240}
241
242static bool LoadALSALibrary(void)
243{
244 load_alsa_syms();
245 return true;
246}
247
248#endif // SDL_AUDIO_DRIVER_ALSA_DYNAMIC
249
250static const char *ALSA_device_prefix = NULL;
251static void ALSA_guess_device_prefix(void)
252{
253 if (ALSA_device_prefix) {
254 return; // already calculated.
255 }
256
257 // Apparently there are several different ways that ALSA lists
258 // actual hardware. It could be prefixed with "hw:" or "default:"
259 // or "sysdefault:" and maybe others. Go through the list and see
260 // if we can find a preferred prefix for the system.
261
262 static const char *const prefixes[] = {
263 "hw:", "sysdefault:", "default:"
264 };
265
266 void **hints = NULL;
267 if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) {
268 for (int i = 0; hints[i]; i++) {
269 char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
270 if (name) {
271 for (int j = 0; j < SDL_arraysize(prefixes); j++) {
272 const char *prefix = prefixes[j];
273 const size_t prefixlen = SDL_strlen(prefix);
274 if (SDL_strncmp(name, prefix, prefixlen) == 0) {
275 ALSA_device_prefix = prefix;
276 break;
277 }
278 }
279 free(name); // This should NOT be SDL_free()
280
281 if (ALSA_device_prefix) {
282 break;
283 }
284 }
285 }
286 }
287
288 if (!ALSA_device_prefix) {
289 ALSA_device_prefix = prefixes[0]; // oh well.
290 }
291
292 LOGDEBUG("device prefix is probably '%s'", ALSA_device_prefix);
293}
294
295typedef struct ALSA_Device
296{
297 // the unicity key is the couple (id,recording)
298 char *id; // empty means canonical default
299 char *name;
300 bool recording;
301 struct ALSA_Device *next;
302} ALSA_Device;
303
304static const ALSA_Device default_playback_handle = {
305 "",
306 "default",
307 false,
308 NULL
309};
310
311static const ALSA_Device default_recording_handle = {
312 "",
313 "default",
314 true,
315 NULL
316};
317
318// TODO: Figure out the "right"(TM) way. For the moment we presume that if a system is using a
319// software mixer for application audio sharing which is not the linux native alsa[dmix], for
320// instance jack/pulseaudio2[pipewire]/pulseaudio1/esound/etc, we expect the system integrators did
321// configure the canonical default to the right alsa PCM plugin for their software mixer.
322//
323// All the above may be completely wrong.
324static char *get_pcm_str(void *handle)
325{
326 SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3.
327 ALSA_Device *dev = (ALSA_Device *)handle;
328 char *pcm_str = NULL;
329
330 if (SDL_strlen(dev->id) == 0) {
331 // If the user does not want to go thru the default PCM or the canonical default, the
332 // the configuration space being _massive_, give the user the ability to specify
333 // its own PCMs using environment variables. It will have to fit SDL constraints though.
334 const char *devname = SDL_GetHint(dev->recording ? SDL_HINT_AUDIO_ALSA_DEFAULT_RECORDING_DEVICE : SDL_HINT_AUDIO_ALSA_DEFAULT_PLAYBACK_DEVICE);
335 if (!devname) {
336 devname = SDL_GetHint(SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE);
337 if (!devname) {
338 devname = "default";
339 }
340 }
341 pcm_str = SDL_strdup(devname);
342 } else {
343 SDL_asprintf(&pcm_str, "%sCARD=%s", ALSA_device_prefix, dev->id);
344 }
345 return pcm_str;
346}
347
348// This function waits until it is possible to write a full sound buffer
349static bool ALSA_WaitDevice(SDL_AudioDevice *device)
350{
351 const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq);
352 const int delay = SDL_max(fulldelay, 10);
353
354 while (!SDL_GetAtomicInt(&device->shutdown)) {
355 const int rc = ALSA_snd_pcm_wait(device->hidden->pcm, delay);
356 if (rc < 0 && (rc != -EAGAIN)) {
357 const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
358 if (status < 0) {
359 // Hmm, not much we can do - abort
360 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
361 return false;
362 }
363 continue;
364 }
365
366 if (rc > 0) {
367 break; // ready to go!
368 }
369
370 // Timed out! Make sure we aren't shutting down and then wait again.
371 }
372
373 return true;
374}
375
376static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
377{
378 SDL_assert(buffer == device->hidden->mixbuf);
379 Uint8 *sample_buf = (Uint8 *) buffer; // !!! FIXME: deal with this without casting away constness
380 const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
381 snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size);
382
383 while ((frames_left > 0) && !SDL_GetAtomicInt(&device->shutdown)) {
384 const int rc = ALSA_snd_pcm_writei(device->hidden->pcm, sample_buf, frames_left);
385 //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA PLAYDEVICE: WROTE %d of %d bytes", (rc >= 0) ? ((int) (rc * frame_size)) : rc, (int) (frames_left * frame_size));
386 SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space.
387 if (rc < 0) {
388 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!
389 const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
390 if (status < 0) {
391 // Hmm, not much we can do - abort
392 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc));
393 return false;
394 }
395 continue;
396 }
397
398 sample_buf += rc * frame_size;
399 frames_left -= rc;
400 }
401
402 return true;
403}
404
405static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
406{
407 snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm);
408 if (rc <= 0) {
409 // Wait a bit and try again, maybe the hardware isn't quite ready yet?
410 SDL_Delay(1);
411
412 rc = ALSA_snd_pcm_avail(device->hidden->pcm);
413 if (rc <= 0) {
414 // We'll catch it next time
415 *buffer_size = 0;
416 return NULL;
417 }
418 }
419
420 const int requested_frames = SDL_min(device->sample_frames, rc);
421 const int requested_bytes = requested_frames * SDL_AUDIO_FRAMESIZE(device->spec);
422 SDL_assert(requested_bytes <= *buffer_size);
423 //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA GETDEVICEBUF: NEED %d BYTES", requested_bytes);
424 *buffer_size = requested_bytes;
425 return device->hidden->mixbuf;
426}
427
428static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
429{
430 const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
431 SDL_assert((buflen % frame_size) == 0);
432
433 const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm);
434 const int total_frames = SDL_min(buflen / frame_size, total_available);
435
436 const int rc = ALSA_snd_pcm_readi(device->hidden->pcm, buffer, total_frames);
437
438 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!
439
440 if (rc < 0) {
441 const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
442 if (status < 0) {
443 // Hmm, not much we can do - abort
444 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc));
445 return -1;
446 }
447 return 0; // go back to WaitDevice and try again.
448 }
449
450 //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: recorded %d bytes", rc * frame_size);
451
452 return rc * frame_size;
453}
454
455static void ALSA_FlushRecording(SDL_AudioDevice *device)
456{
457 ALSA_snd_pcm_reset(device->hidden->pcm);
458}
459
460static void ALSA_CloseDevice(SDL_AudioDevice *device)
461{
462 if (device->hidden) {
463 if (device->hidden->pcm) {
464 // Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that.
465 SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2);
466 ALSA_snd_pcm_close(device->hidden->pcm);
467 }
468 SDL_free(device->hidden->mixbuf);
469 SDL_free(device->hidden);
470 }
471}
472
473
474// To make easier to track parameters during the whole alsa pcm configuration:
475struct ALSA_pcm_cfg_ctx {
476 SDL_AudioDevice *device;
477
478 snd_pcm_hw_params_t *hwparams;
479 snd_pcm_sw_params_t *swparams;
480
481 SDL_AudioFormat matched_sdl_format;
482 unsigned int chans_n;
483 unsigned int target_chans_n;
484 unsigned int rate;
485 snd_pcm_uframes_t persize; // alsa period size, SDL audio device sample_frames
486 snd_pcm_chmap_query_t **chmap_queries;
487 unsigned int sdl_chmap[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
488 unsigned int alsa_chmap_installed[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
489
490 unsigned int periods;
491};
492// The following are SDL channel maps with alsa position values, from 0 channels to 8 channels.
493// See SDL3/SDL_audio.h
494// Strictly speaking those are "parameters" of channel maps, like alsa hwparams and swparams, they
495// have to be "reduced/refined" until an exact channel map. Only the 6 channels map requires such
496// "reduction/refine".
497static enum snd_pcm_chmap_position sdl_channel_maps[SDL_AUDIO_ALSA__SDL_CHMAPS_N][SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX] = {
498 // 0 channels
499 {
500 0
501 },
502 // 1 channel
503 {
504 SND_CHMAP_MONO,
505 },
506 // 2 channels
507 {
508 SND_CHMAP_FL,
509 SND_CHMAP_FR,
510 },
511 // 3 channels
512 {
513 SND_CHMAP_FL,
514 SND_CHMAP_FR,
515 SND_CHMAP_LFE,
516 },
517 // 4 channels
518 {
519 SND_CHMAP_FL,
520 SND_CHMAP_FR,
521 SND_CHMAP_RL,
522 SND_CHMAP_RR,
523 },
524 // 5 channels
525 {
526 SND_CHMAP_FL,
527 SND_CHMAP_FR,
528 SND_CHMAP_LFE,
529 SND_CHMAP_RL,
530 SND_CHMAP_RR,
531 },
532 // 6 channels
533 // XXX: here we encode not a uniq channel map but a set of channel maps. We will reduce it each
534 // time we are going to work with an alsa 6 channels map.
535 {
536 SND_CHMAP_FL,
537 SND_CHMAP_FR,
538 SND_CHMAP_FC,
539 SND_CHMAP_LFE,
540 // The 2 following channel positions are (SND_CHMAP_SL,SND_CHMAP_SR) or
541 // (SND_CHMAP_RL,SND_CHMAP_RR)
542 SND_CHMAP_UNKNOWN,
543 SND_CHMAP_UNKNOWN,
544 },
545 // 7 channels
546 {
547 SND_CHMAP_FL,
548 SND_CHMAP_FR,
549 SND_CHMAP_FC,
550 SND_CHMAP_LFE,
551 SND_CHMAP_RC,
552 SND_CHMAP_SL,
553 SND_CHMAP_SR,
554 },
555 // 8 channels
556 {
557 SND_CHMAP_FL,
558 SND_CHMAP_FR,
559 SND_CHMAP_FC,
560 SND_CHMAP_LFE,
561 SND_CHMAP_RL,
562 SND_CHMAP_RR,
563 SND_CHMAP_SL,
564 SND_CHMAP_SR,
565 },
566};
567
568// Helper for the function right below.
569static bool has_pos(const unsigned int *chmap, unsigned int pos)
570{
571 for (unsigned int chan_idx = 0; ; chan_idx++) {
572 if (chan_idx == 6) {
573 return false;
574 }
575 if (chmap[chan_idx] == pos) {
576 return true;
577 }
578 }
579 SDL_assert(!"Shouldn't hit this code.");
580 return false;
581}
582
583// XXX: Each time we are going to work on an alsa 6 channels map, we must reduce the set of channel
584// maps which is encoded in sdl_channel_maps[6] to a uniq one.
585#define HAVE_NONE 0
586#define HAVE_REAR 1
587#define HAVE_SIDE 2
588#define HAVE_BOTH 3
589static void sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(unsigned int *sdl_6chans, const unsigned int *alsa_6chans)
590{
591 // For alsa channel maps with 6 channels and with SND_CHMAP_FL,SND_CHMAP_FR,SND_CHMAP_FC,
592 // SND_CHMAP_LFE, reduce our 6 channels maps to a uniq one.
593 if ( !has_pos(alsa_6chans, SND_CHMAP_FL) ||
594 !has_pos(alsa_6chans, SND_CHMAP_FR) ||
595 !has_pos(alsa_6chans, SND_CHMAP_FC) ||
596 !has_pos(alsa_6chans, SND_CHMAP_LFE)) {
597 sdl_6chans[4] = SND_CHMAP_UNKNOWN;
598 sdl_6chans[5] = SND_CHMAP_UNKNOWN;
599 LOGDEBUG("6channels:unsupported channel map");
600 return;
601 }
602
603 unsigned int state = HAVE_NONE;
604 for (unsigned int chan_idx = 0; chan_idx < 6; chan_idx++) {
605 if ((alsa_6chans[chan_idx] == SND_CHMAP_SL) || (alsa_6chans[chan_idx] == SND_CHMAP_SR)) {
606 if (state == HAVE_NONE) {
607 state = HAVE_SIDE;
608 } else if (state == HAVE_REAR) {
609 state = HAVE_BOTH;
610 break;
611 }
612 } else if ((alsa_6chans[chan_idx] == SND_CHMAP_RL) || (alsa_6chans[chan_idx] == SND_CHMAP_RR)) {
613 if (state == HAVE_NONE) {
614 state = HAVE_REAR;
615 } else if (state == HAVE_SIDE) {
616 state = HAVE_BOTH;
617 break;
618 }
619 }
620 }
621
622 if ((state == HAVE_BOTH) || (state == HAVE_NONE)) {
623 sdl_6chans[4] = SND_CHMAP_UNKNOWN;
624 sdl_6chans[5] = SND_CHMAP_UNKNOWN;
625 LOGDEBUG("6channels:unsupported channel map");
626 } else if (state == HAVE_REAR) {
627 sdl_6chans[4] = SND_CHMAP_RL;
628 sdl_6chans[5] = SND_CHMAP_RR;
629 LOGDEBUG("6channels:sdl map set to rear");
630 } else { // state == HAVE_SIDE
631 sdl_6chans[4] = SND_CHMAP_SL;
632 sdl_6chans[5] = SND_CHMAP_SR;
633 LOGDEBUG("6channels:sdl map set to side");
634 }
635}
636#undef HAVE_NONE
637#undef HAVE_REAR
638#undef HAVE_SIDE
639#undef HAVE_BOTH
640
641static void swizzle_map_compute_alsa_subscan(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, unsigned int sdl_pos_idx)
642{
643 swizzle_map[sdl_pos_idx] = -1;
644 for (unsigned int alsa_pos_idx = 0; ; alsa_pos_idx++) {
645 SDL_assert(alsa_pos_idx != ctx->chans_n); // no 0 channels or not found matching position should happen here (actually enforce playback/recording symmetry).
646 if (ctx->alsa_chmap_installed[alsa_pos_idx] == ctx->sdl_chmap[sdl_pos_idx]) {
647 LOGDEBUG("swizzle SDL %u <-> alsa %u", sdl_pos_idx,alsa_pos_idx);
648 swizzle_map[sdl_pos_idx] = (int) alsa_pos_idx;
649 return;
650 }
651 }
652}
653
654// XXX: this must stay playback/recording symetric.
655static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle)
656{
657 *needs_swizzle = false;
658 for (unsigned int sdl_pos_idx = 0; sdl_pos_idx != ctx->chans_n; sdl_pos_idx++) {
659 swizzle_map_compute_alsa_subscan(ctx, swizzle_map, sdl_pos_idx);
660 if (swizzle_map[sdl_pos_idx] != sdl_pos_idx) {
661 *needs_swizzle = true;
662 }
663 }
664}
665
666#define CHMAP_INSTALLED 0
667#define CHANS_N_NEXT 1
668#define CHMAP_NOT_FOUND 2
669// Should always be a queried alsa channel map unless the queried alsa channel map was of type VAR,
670// namely we can program the channel positions directly from the SDL channel map.
671static int alsa_chmap_install(struct ALSA_pcm_cfg_ctx *ctx, const unsigned int *chmap)
672{
673 bool isstack;
674 snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t*)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack);
675 if (!chmap_to_install) {
676 return -1;
677 }
678
679 chmap_to_install->channels = ctx->chans_n;
680 SDL_memcpy(chmap_to_install->pos, chmap, sizeof (unsigned int) * ctx->chans_n);
681
682 #if SDL_ALSA_DEBUG
683 char logdebug_chmap_str[128];
684 ALSA_snd_pcm_chmap_print(chmap_to_install,sizeof(logdebug_chmap_str),logdebug_chmap_str);
685 LOGDEBUG("channel map to install:%s",logdebug_chmap_str);
686 #endif
687
688 int status = ALSA_snd_pcm_set_chmap(ctx->device->hidden->pcm, chmap_to_install);
689 if (status < 0) {
690 SDL_SetError("ALSA: failed to install channel map: %s", ALSA_snd_strerror(status));
691 return -1;
692 }
693 SDL_memcpy(ctx->alsa_chmap_installed, chmap, ctx->chans_n * sizeof (unsigned int));
694
695 SDL_small_free(chmap_to_install, isstack);
696 return CHMAP_INSTALLED;
697}
698
699// We restrict the alsa channel maps because in the unordered matches we do only simple accounting.
700// In the end, this will handle mostly alsa channel maps with more than one SND_CHMAP_NA position fillers.
701static bool alsa_chmap_has_duplicate_position(const struct ALSA_pcm_cfg_ctx *ctx, const unsigned int *pos)
702{
703 if (ctx->chans_n < 2) {// we need at least 2 positions
704 LOGDEBUG("channel map:no duplicate");
705 return false;
706 }
707
708 for (unsigned int chan_idx = 1; chan_idx != ctx->chans_n; chan_idx++) {
709 for (unsigned int seen_idx = 0; seen_idx != chan_idx; seen_idx++) {
710 if (pos[seen_idx] == pos[chan_idx]) {
711 LOGDEBUG("channel map:have duplicate");
712 return true;
713 }
714 }
715 }
716
717 LOGDEBUG("channel map:no duplicate");
718 return false;
719}
720
721static int alsa_chmap_cfg_ordered_fixed_or_paired(struct ALSA_pcm_cfg_ctx *ctx)
722{
723 for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
724 if ( ((*chmap_query)->map.channels != ctx->chans_n) ||
725 (((*chmap_query)->type != SND_CHMAP_TYPE_FIXED) && ((*chmap_query)->type != SND_CHMAP_TYPE_PAIRED)) ) {
726 continue;
727 }
728
729 #if SDL_ALSA_DEBUG
730 char logdebug_chmap_str[128];
731 ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
732 LOGDEBUG("channel map:ordered:fixed|paired:%s",logdebug_chmap_str);
733 #endif
734
735 for (int i = 0; i < ctx->chans_n; i++) {
736 ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
737 }
738
739 unsigned int *alsa_chmap = (*chmap_query)->map.pos;
740 if (ctx->chans_n == 6) {
741 sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
742 }
743 if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
744 continue;
745 }
746
747 for (unsigned int chan_idx = 0; ctx->sdl_chmap[chan_idx] == alsa_chmap[chan_idx]; chan_idx++) {
748 if (chan_idx == ctx->chans_n) {
749 return alsa_chmap_install(ctx, alsa_chmap);
750 }
751 }
752 }
753 return CHMAP_NOT_FOUND;
754}
755
756// Here, the alsa channel positions can be programmed in the alsa frame (cf HDMI).
757// If the alsa channel map is VAR, we only check we have the unordered set of channel positions we
758// are looking for.
759static int alsa_chmap_cfg_ordered_var(struct ALSA_pcm_cfg_ctx *ctx)
760{
761 for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
762 if (((*chmap_query)->map.channels != ctx->chans_n) || ((*chmap_query)->type != SND_CHMAP_TYPE_VAR)) {
763 continue;
764 }
765
766 #if SDL_ALSA_DEBUG
767 char logdebug_chmap_str[128];
768 ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
769 LOGDEBUG("channel map:ordered:var:%s",logdebug_chmap_str);
770 #endif
771
772 for (int i = 0; i < ctx->chans_n; i++) {
773 ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
774 }
775
776 unsigned int *alsa_chmap = (*chmap_query)->map.pos;
777 if (ctx->chans_n == 6) {
778 sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
779 }
780 if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
781 continue;
782 }
783
784 unsigned int pos_matches_n = 0;
785 for (unsigned int chan_idx = 0; chan_idx != ctx->chans_n; chan_idx++) {
786 for (unsigned int subscan_chan_idx = 0; subscan_chan_idx != ctx->chans_n; subscan_chan_idx++) {
787 if (ctx->sdl_chmap[chan_idx] == alsa_chmap[subscan_chan_idx]) {
788 pos_matches_n++;
789 break;
790 }
791 }
792 }
793
794 if (pos_matches_n == ctx->chans_n) {
795 return alsa_chmap_install(ctx, ctx->sdl_chmap); // XXX: we program the SDL chmap here
796 }
797 }
798
799 return CHMAP_NOT_FOUND;
800}
801
802static int alsa_chmap_cfg_ordered(struct ALSA_pcm_cfg_ctx *ctx)
803{
804 const int status = alsa_chmap_cfg_ordered_fixed_or_paired(ctx);
805 return (status != CHMAP_NOT_FOUND) ? status : alsa_chmap_cfg_ordered_var(ctx);
806}
807
808// In the unordered case, we are just interested to get the same unordered set of alsa channel
809// positions than in the SDL channel map since we will swizzle (no duplicate channel position).
810static int alsa_chmap_cfg_unordered(struct ALSA_pcm_cfg_ctx *ctx)
811{
812 for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
813 if ( ((*chmap_query)->map.channels != ctx->chans_n) ||
814 (((*chmap_query)->type != SND_CHMAP_TYPE_FIXED) && ((*chmap_query)->type != SND_CHMAP_TYPE_PAIRED)) ) {
815 continue;
816 }
817
818 #if SDL_ALSA_DEBUG
819 char logdebug_chmap_str[128];
820 ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
821 LOGDEBUG("channel map:unordered:fixed|paired:%s",logdebug_chmap_str);
822 #endif
823
824 for (int i = 0; i < ctx->chans_n; i++) {
825 ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
826 }
827
828 unsigned int *alsa_chmap = (*chmap_query)->map.pos;
829 if (ctx->chans_n == 6) {
830 sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
831 }
832
833 if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
834 continue;
835 }
836
837 unsigned int pos_matches_n = 0;
838 for (unsigned int chan_idx = 0; chan_idx != ctx->chans_n; chan_idx++) {
839 for (unsigned int subscan_chan_idx = 0; subscan_chan_idx != ctx->chans_n; subscan_chan_idx++) {
840 if (ctx->sdl_chmap[chan_idx] == alsa_chmap[subscan_chan_idx]) {
841 pos_matches_n++;
842 break;
843 }
844 }
845 }
846
847 if (pos_matches_n == ctx->chans_n) {
848 return alsa_chmap_install(ctx, alsa_chmap);
849 }
850 }
851
852 return CHMAP_NOT_FOUND;
853}
854
855static int alsa_chmap_cfg(struct ALSA_pcm_cfg_ctx *ctx)
856{
857 int status;
858
859 ctx->chmap_queries = ALSA_snd_pcm_query_chmaps(ctx->device->hidden->pcm);
860 if (ctx->chmap_queries == NULL) {
861 // We couldn't query the channel map, assume no swizzle necessary
862 LOGDEBUG("couldn't query channel map, swizzling off");
863 return CHMAP_INSTALLED;
864 }
865
866 //----------------------------------------------------------------------------------------------
867 status = alsa_chmap_cfg_ordered(ctx); // we prefer first channel maps we don't need to swizzle
868 if (status == CHMAP_INSTALLED) {
869 LOGDEBUG("swizzling off");
870 return status;
871 } else if (status != CHMAP_NOT_FOUND) {
872 return status; // < 0 error code
873 }
874
875 // Fall-thru
876 //----------------------------------------------------------------------------------------------
877 status = alsa_chmap_cfg_unordered(ctx); // those we will have to swizzle
878 if (status == CHMAP_INSTALLED) {
879 LOGDEBUG("swizzling on");
880
881 bool isstack;
882 int *swizzle_map = SDL_small_alloc(int, ctx->chans_n, &isstack);
883 if (!swizzle_map) {
884 status = -1;
885 } else {
886 bool needs_swizzle;
887 swizzle_map_compute(ctx, swizzle_map, &needs_swizzle); // fine grained swizzle configuration
888 if (needs_swizzle) {
889 // let SDL's swizzler handle this one.
890 ctx->device->chmap = SDL_ChannelMapDup(swizzle_map, ctx->chans_n);
891 if (!ctx->device->chmap) {
892 status = -1;
893 }
894 }
895 SDL_small_free(swizzle_map, isstack);
896 }
897 }
898
899 if (status == CHMAP_NOT_FOUND) {
900 return CHANS_N_NEXT;
901 }
902
903 return status; // < 0 error code
904}
905
906#define CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N 0 // target more hardware pressure
907#define CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N 1 // target less hardware pressure
908#define CHANS_N_CONFIGURED 0
909#define CHANS_N_NOT_CONFIGURED 1
910static int ALSA_pcm_cfg_hw_chans_n_scan(struct ALSA_pcm_cfg_ctx *ctx, unsigned int mode)
911{
912 unsigned int target_chans_n = ctx->device->spec.channels; // we start at what was specified
913 if (mode == CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N) {
914 target_chans_n--;
915 }
916 while (true) {
917 if (mode == CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N) {
918 if (target_chans_n > SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX) {
919 return CHANS_N_NOT_CONFIGURED;
920 }
921 // else: CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
922 } else if (target_chans_n == 0) {
923 return CHANS_N_NOT_CONFIGURED;
924 }
925
926 LOGDEBUG("target chans_n is %u", target_chans_n);
927
928 int status = ALSA_snd_pcm_hw_params_any(ctx->device->hidden->pcm, ctx->hwparams);
929 if (status < 0) {
930 SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status));
931 return -1;
932 }
933 // SDL only uses interleaved sample output
934 status = ALSA_snd_pcm_hw_params_set_access(ctx->device->hidden->pcm, ctx->hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
935 if (status < 0) {
936 SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status));
937 return -1;
938 }
939 // Try for a closest match on audio format
940 snd_pcm_format_t alsa_format = 0;
941 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(ctx->device->spec.format);
942 ctx->matched_sdl_format = 0;
943 while ((ctx->matched_sdl_format = *(closefmts++)) != 0) {
944 // XXX: we are forcing the same endianness, namely we won't need byte swapping upon
945 // writing/reading to/from the SDL audio buffer.
946 switch (ctx->matched_sdl_format) {
947 case SDL_AUDIO_U8:
948 alsa_format = SND_PCM_FORMAT_U8;
949 break;
950 case SDL_AUDIO_S8:
951 alsa_format = SND_PCM_FORMAT_S8;
952 break;
953 case SDL_AUDIO_S16LE:
954 alsa_format = SND_PCM_FORMAT_S16_LE;
955 break;
956 case SDL_AUDIO_S16BE:
957 alsa_format = SND_PCM_FORMAT_S16_BE;
958 break;
959 case SDL_AUDIO_S32LE:
960 alsa_format = SND_PCM_FORMAT_S32_LE;
961 break;
962 case SDL_AUDIO_S32BE:
963 alsa_format = SND_PCM_FORMAT_S32_BE;
964 break;
965 case SDL_AUDIO_F32LE:
966 alsa_format = SND_PCM_FORMAT_FLOAT_LE;
967 break;
968 case SDL_AUDIO_F32BE:
969 alsa_format = SND_PCM_FORMAT_FLOAT_BE;
970 break;
971 default:
972 continue;
973 }
974 if (ALSA_snd_pcm_hw_params_set_format(ctx->device->hidden->pcm, ctx->hwparams, alsa_format) >= 0) {
975 break;
976 }
977 }
978 if (ctx->matched_sdl_format == 0) {
979 SDL_SetError("ALSA: Unsupported audio format: %s", ALSA_snd_strerror(status));
980 return -1;
981 }
982 // let alsa approximate the number of channels
983 ctx->chans_n = target_chans_n;
984 status = ALSA_snd_pcm_hw_params_set_channels_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->chans_n));
985 if (status < 0) {
986 SDL_SetError("ALSA: Couldn't set audio channels: %s", ALSA_snd_strerror(status));
987 return -1;
988 }
989 // let alsa approximate the audio rate
990 ctx->rate = ctx->device->spec.freq;
991 status = ALSA_snd_pcm_hw_params_set_rate_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->rate), NULL);
992 if (status < 0) {
993 SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status));
994 return -1;
995 }
996 // let approximate the period size to the requested buffer size
997 ctx->persize = ctx->device->sample_frames;
998 status = ALSA_snd_pcm_hw_params_set_period_size_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->persize), NULL);
999 if (status < 0) {
1000 SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status));
1001 return -1;
1002 }
1003 // let approximate the minimun number of periods per buffer (we target a double buffer)
1004 ctx->periods = 2;
1005 status = ALSA_snd_pcm_hw_params_set_periods_min(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL);
1006 if (status < 0) {
1007 SDL_SetError("ALSA: Couldn't set the minimum number of periods per buffer: %s", ALSA_snd_strerror(status));
1008 return -1;
1009 }
1010 // restrict the number of periods per buffer to an approximation of the approximated minimum
1011 // number of periods per buffer done right above
1012 status = ALSA_snd_pcm_hw_params_set_periods_first(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL);
1013 if (status < 0) {
1014 SDL_SetError("ALSA: Couldn't set the number of periods per buffer: %s", ALSA_snd_strerror(status));
1015 return -1;
1016 }
1017 // install the hw parameters
1018 status = ALSA_snd_pcm_hw_params(ctx->device->hidden->pcm, ctx->hwparams);
1019 if (status < 0) {
1020 SDL_SetError("ALSA: installation of hardware parameter failed: %s", ALSA_snd_strerror(status));
1021 return -1;
1022 }
1023 //==========================================================================================
1024 // Here the alsa pcm is in SND_PCM_STATE_PREPARED state, let's figure out a good fit for
1025 // SDL channel map, it may request to change the target number of channels though.
1026 status = alsa_chmap_cfg(ctx);
1027 if (status < 0) {
1028 return status; // we forward the SDL error
1029 } else if (status == CHMAP_INSTALLED) {
1030 return CHANS_N_CONFIGURED; // we are finished here
1031 }
1032
1033 // status == CHANS_N_NEXT
1034 ALSA_snd_pcm_free_chmaps(ctx->chmap_queries);
1035 ALSA_snd_pcm_hw_free(ctx->device->hidden->pcm); // uninstall those hw params
1036
1037 if (mode == CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N) {
1038 target_chans_n++;
1039 } else { // CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
1040 target_chans_n--;
1041 }
1042 }
1043
1044 SDL_assert(!"Shouldn't reach this code.");
1045 return CHANS_N_NOT_CONFIGURED;
1046}
1047#undef CHMAP_INSTALLED
1048#undef CHANS_N_NEXT
1049#undef CHMAP_NOT_FOUND
1050
1051static bool ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx)
1052{
1053 LOGDEBUG("target chans_n, equal or above requested chans_n mode");
1054 int status = ALSA_pcm_cfg_hw_chans_n_scan(ctx, CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N);
1055 if (status < 0) { // something went too wrong
1056 return false;
1057 } else if (status == CHANS_N_CONFIGURED) {
1058 return true;
1059 }
1060
1061 // Here, status == CHANS_N_NOT_CONFIGURED
1062 LOGDEBUG("target chans_n, below requested chans_n mode");
1063 status = ALSA_pcm_cfg_hw_chans_n_scan(ctx, CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N);
1064 if (status < 0) { // something went too wrong
1065 return false;
1066 } else if (status == CHANS_N_CONFIGURED) {
1067 return true;
1068 }
1069
1070 // Here, status == CHANS_N_NOT_CONFIGURED
1071 return SDL_SetError("ALSA: Coudn't configure targetting any SDL supported channel number");
1072}
1073#undef CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N
1074#undef CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
1075#undef CHANS_N_CONFIGURED
1076#undef CHANS_N_NOT_CONFIGURED
1077
1078
1079static bool ALSA_pcm_cfg_sw(struct ALSA_pcm_cfg_ctx *ctx)
1080{
1081 int status;
1082
1083 status = ALSA_snd_pcm_sw_params_current(ctx->device->hidden->pcm, ctx->swparams);
1084 if (status < 0) {
1085 return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
1086 }
1087
1088 status = ALSA_snd_pcm_sw_params_set_avail_min(ctx->device->hidden->pcm, ctx->swparams, ctx->persize); // will become device->sample_frames if the alsa pcm configuration is successful
1089 if (status < 0) {
1090 return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
1091 }
1092
1093 status = ALSA_snd_pcm_sw_params_set_start_threshold(ctx->device->hidden->pcm, ctx->swparams, 1);
1094 if (status < 0) {
1095 return SDL_SetError("ALSA: Couldn't set start threshold: %s", ALSA_snd_strerror(status));
1096 }
1097 status = ALSA_snd_pcm_sw_params(ctx->device->hidden->pcm, ctx->swparams);
1098 if (status < 0) {
1099 return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
1100 }
1101 return true;
1102}
1103
1104
1105static bool ALSA_OpenDevice(SDL_AudioDevice *device)
1106{
1107 const bool recording = device->recording;
1108 struct ALSA_pcm_cfg_ctx cfg_ctx; // used to track everything here
1109 char *pcm_str;
1110 int status = 0;
1111
1112 //device->spec.channels = 8;
1113 //SDL_SetLogPriority(SDL_LOG_CATEGORY_AUDIO, SDL_LOG_PRIORITY_VERBOSE);
1114 LOGDEBUG("channels requested %u",device->spec.channels);
1115 // XXX: We do not use the SDL internal swizzler yet.
1116 device->chmap = NULL;
1117
1118 SDL_zero(cfg_ctx);
1119 cfg_ctx.device = device;
1120
1121 // Initialize all variables that we clean on shutdown
1122 cfg_ctx.device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*cfg_ctx.device->hidden));
1123 if (!cfg_ctx.device->hidden) {
1124 return false;
1125 }
1126
1127 // Open the audio device
1128 pcm_str = get_pcm_str(cfg_ctx.device->handle);
1129 if (pcm_str == NULL) {
1130 goto err_free_device_hidden;
1131 }
1132 LOGDEBUG("PCM open '%s'", pcm_str);
1133 status = ALSA_snd_pcm_open(&cfg_ctx.device->hidden->pcm,
1134 pcm_str,
1135 recording ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
1136 SND_PCM_NONBLOCK);
1137 SDL_free(pcm_str);
1138 if (status < 0) {
1139 SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status));
1140 goto err_free_device_hidden;
1141 }
1142
1143 // Now we need to configure the opened pcm as close as possible from the requested parameters we
1144 // can reasonably deal with (and that could change)
1145 snd_pcm_hw_params_alloca(&(cfg_ctx.hwparams));
1146 snd_pcm_sw_params_alloca(&(cfg_ctx.swparams));
1147
1148 if (!ALSA_pcm_cfg_hw(&cfg_ctx)) { // alsa pcm "hardware" part of the pcm
1149 goto err_close_pcm;
1150 }
1151
1152 // from here, we get only the alsa chmap queries in cfg_ctx to explicitely clean, hwparams is
1153 // uninstalled upon pcm closing
1154
1155 // This is useful for debugging
1156 #if SDL_ALSA_DEBUG
1157 snd_pcm_uframes_t bufsize;
1158 ALSA_snd_pcm_hw_params_get_buffer_size(cfg_ctx.hwparams, &bufsize);
1159 SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
1160 "ALSA: period size = %ld, periods = %u, buffer size = %lu",
1161 cfg_ctx.persize, cfg_ctx.periods, bufsize);
1162 #endif
1163
1164 if (!ALSA_pcm_cfg_sw(&cfg_ctx)) { // alsa pcm "software" part of the pcm
1165 goto err_cleanup_ctx;
1166 }
1167
1168 // Now we can update the following parameters in the spec:
1169 cfg_ctx.device->spec.format = cfg_ctx.matched_sdl_format;
1170 cfg_ctx.device->spec.channels = cfg_ctx.chans_n;
1171 cfg_ctx.device->spec.freq = cfg_ctx.rate;
1172 cfg_ctx.device->sample_frames = cfg_ctx.persize;
1173 // Calculate the final parameters for this audio specification
1174 SDL_UpdatedAudioDeviceFormat(cfg_ctx.device);
1175
1176 // Allocate mixing buffer
1177 if (!recording) {
1178 cfg_ctx.device->hidden->mixbuf = (Uint8 *)SDL_malloc(cfg_ctx.device->buffer_size);
1179 if (cfg_ctx.device->hidden->mixbuf == NULL) {
1180 goto err_cleanup_ctx;
1181 }
1182 SDL_memset(cfg_ctx.device->hidden->mixbuf, cfg_ctx.device->silence_value, cfg_ctx.device->buffer_size);
1183 }
1184
1185#if !SDL_ALSA_NON_BLOCKING
1186 if (!recording) {
1187 ALSA_snd_pcm_nonblock(cfg_ctx.device->hidden->pcm, 0);
1188 }
1189#endif
1190 ALSA_snd_pcm_start(cfg_ctx.device->hidden->pcm);
1191 return true; // We're ready to rock and roll. :-)
1192
1193err_cleanup_ctx:
1194 ALSA_snd_pcm_free_chmaps(cfg_ctx.chmap_queries);
1195err_close_pcm:
1196 ALSA_snd_pcm_close(cfg_ctx.device->hidden->pcm);
1197err_free_device_hidden:
1198 SDL_free(cfg_ctx.device->hidden);
1199 cfg_ctx.device->hidden = NULL;
1200 return false;
1201}
1202
1203static ALSA_Device *hotplug_devices = NULL;
1204
1205static int hotplug_device_process(snd_ctl_t *ctl, snd_ctl_card_info_t *ctl_card_info, int dev_idx,
1206 snd_pcm_stream_t direction, ALSA_Device **unseen, ALSA_Device **seen)
1207{
1208 unsigned int subdevs_n = 1; // we have at least one subdevice (substream since the direction is a stream in alsa terminology)
1209 unsigned int subdev_idx = 0;
1210 const bool recording = direction == SND_PCM_STREAM_CAPTURE ? true : false; // used for the unicity of the device
1211 bool isstack;
1212 snd_pcm_info_t *pcm_info = (snd_pcm_info_t*)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack);
1213 SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof());
1214
1215 while (true) {
1216 ALSA_snd_pcm_info_set_stream(pcm_info, direction);
1217 ALSA_snd_pcm_info_set_device(pcm_info, dev_idx);
1218 ALSA_snd_pcm_info_set_subdevice(pcm_info, subdev_idx); // we have at least one subdevice (substream) of index 0
1219
1220 const int r = ALSA_snd_ctl_pcm_info(ctl, pcm_info);
1221 if (r < 0) {
1222 SDL_small_free(pcm_info, isstack);
1223 // first call to ALSA_snd_ctl_pcm_info
1224 if (subdev_idx == 0 && r == -ENOENT) { // no such direction/stream for this device
1225 return 0;
1226 }
1227 return -1;
1228 }
1229
1230 if (subdev_idx == 0) {
1231 subdevs_n = ALSA_snd_pcm_info_get_subdevices_count(pcm_info);
1232 }
1233
1234 // building the unseen list scanning the list of hotplug devices, if it is already there
1235 // using the id, move it to the seen list.
1236 ALSA_Device *unseen_prev_adev = NULL;
1237 ALSA_Device *adev;
1238 for (adev = *unseen; adev; adev = adev->next) {
1239 // the unicity key is the couple (id,recording)
1240 if ((SDL_strcmp(adev->id, ALSA_snd_ctl_card_info_get_id(ctl_card_info)) == 0) && (adev->recording == recording)) {
1241 // unchain from unseen
1242 if (*unseen == adev) { // head
1243 *unseen = adev->next;
1244 } else {
1245 unseen_prev_adev->next = adev->next;
1246 }
1247 // chain to seen
1248 adev->next = *seen;
1249 *seen = adev;
1250 break;
1251 }
1252 unseen_prev_adev = adev;
1253 }
1254
1255 if (adev == NULL) { // newly seen device
1256 adev = SDL_calloc(1, sizeof(*adev));
1257 if (adev == NULL) {
1258 SDL_small_free(pcm_info, isstack);
1259 return -1;
1260 }
1261
1262 adev->id = SDL_strdup(ALSA_snd_ctl_card_info_get_id(ctl_card_info));
1263 if (adev->id == NULL) {
1264 SDL_small_free(pcm_info, isstack);
1265 SDL_free(adev);
1266 return -1;
1267 }
1268
1269 if (SDL_asprintf(&adev->name, "%s:%s", ALSA_snd_ctl_card_info_get_name(ctl_card_info), ALSA_snd_pcm_info_get_name(pcm_info)) == -1) {
1270 SDL_small_free(pcm_info, isstack);
1271 SDL_free(adev->id);
1272 SDL_free(adev);
1273 return -1;
1274 }
1275
1276 if (direction == SND_PCM_STREAM_CAPTURE) {
1277 adev->recording = true;
1278 } else {
1279 adev->recording = false;
1280 }
1281
1282 if (SDL_AddAudioDevice(recording, adev->name, NULL, adev) == NULL) {
1283 SDL_small_free(pcm_info, isstack);
1284 SDL_free(adev->id);
1285 SDL_free(adev->name);
1286 SDL_free(adev);
1287 return -1;
1288 }
1289
1290 adev->next = *seen;
1291 *seen = adev;
1292 }
1293
1294 subdev_idx++;
1295 if (subdev_idx == subdevs_n) {
1296 SDL_small_free(pcm_info, isstack);
1297 return 0;
1298 }
1299
1300 SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof());
1301 }
1302
1303 SDL_small_free(pcm_info, isstack);
1304 SDL_assert(!"Shouldn't reach this code");
1305 return -1;
1306}
1307
1308static void ALSA_HotplugIteration(bool *has_default_output, bool *has_default_recording)
1309{
1310 if (has_default_output != NULL) {
1311 *has_default_output = true;
1312 }
1313
1314 if (has_default_recording != NULL) {
1315 *has_default_recording = true;
1316 }
1317
1318 bool isstack;
1319 snd_ctl_card_info_t *ctl_card_info = (snd_ctl_card_info_t *) SDL_small_alloc(Uint8, ALSA_snd_ctl_card_info_sizeof(), &isstack);
1320 if (!ctl_card_info) {
1321 return; // oh well.
1322 }
1323
1324 SDL_memset(ctl_card_info, 0, ALSA_snd_ctl_card_info_sizeof());
1325
1326 snd_ctl_t *ctl = NULL;
1327 ALSA_Device *unseen = hotplug_devices;
1328 ALSA_Device *seen = NULL;
1329 int card_idx = -1;
1330 while (true) {
1331 int r = ALSA_snd_card_next(&card_idx);
1332 if (r < 0) {
1333 goto failed;
1334 } else if (card_idx == -1) {
1335 break;
1336 }
1337
1338 char ctl_name[64];
1339 SDL_snprintf(ctl_name, sizeof (ctl_name), "%s%d", ALSA_device_prefix, card_idx); // card_idx >= 0
1340 LOGDEBUG("hotplug ctl_name = '%s'", ctl_name);
1341
1342 r = ALSA_snd_ctl_open(&ctl, ctl_name, 0);
1343 if (r < 0) {
1344 continue;
1345 }
1346
1347 r = ALSA_snd_ctl_card_info(ctl, ctl_card_info);
1348 if (r < 0) {
1349 goto failed;
1350 }
1351
1352 int dev_idx = -1;
1353 while (true) {
1354 r = ALSA_snd_ctl_pcm_next_device(ctl, &dev_idx);
1355 if (r < 0) {
1356 goto failed;
1357 } else if (dev_idx == -1) {
1358 break;
1359 }
1360
1361 r = hotplug_device_process(ctl, ctl_card_info, dev_idx, SND_PCM_STREAM_PLAYBACK, &unseen, &seen);
1362 if (r < 0) {
1363 goto failed;
1364 }
1365
1366 r = hotplug_device_process(ctl, ctl_card_info, dev_idx, SND_PCM_STREAM_CAPTURE, &unseen, &seen);
1367 if (r < 0) {
1368 goto failed;
1369 }
1370 }
1371 ALSA_snd_ctl_close(ctl);
1372 ALSA_snd_ctl_card_info_clear(ctl_card_info);
1373 }
1374
1375 // remove only the unseen devices
1376 while (unseen) {
1377 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(unseen));
1378 SDL_free(unseen->name);
1379 SDL_free(unseen->id);
1380 ALSA_Device *next = unseen->next;
1381 SDL_free(unseen);
1382 unseen = next;
1383 }
1384
1385 // update hotplug devices to be the seen devices
1386 hotplug_devices = seen;
1387 SDL_small_free(ctl_card_info, isstack);
1388 return;
1389
1390failed:
1391 if (ctl) {
1392 ALSA_snd_ctl_close(ctl);
1393 }
1394
1395 // remove the unseen
1396 while (unseen) {
1397 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(unseen));
1398 SDL_free(unseen->name);
1399 SDL_free(unseen->id);
1400 ALSA_Device *next = unseen->next;
1401 SDL_free(unseen);
1402 unseen = next;
1403 }
1404
1405 // remove the seen
1406 while (seen) {
1407 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(seen));
1408 SDL_free(seen->name);
1409 SDL_free(seen->id);
1410 ALSA_Device *next = seen->next;
1411 SDL_free(seen);
1412 seen = next;
1413 }
1414
1415 hotplug_devices = NULL;
1416 SDL_small_free(ctl_card_info, isstack);
1417}
1418
1419
1420#if SDL_ALSA_HOTPLUG_THREAD
1421static SDL_AtomicInt ALSA_hotplug_shutdown;
1422static SDL_Thread *ALSA_hotplug_thread;
1423
1424static int SDLCALL ALSA_HotplugThread(void *arg)
1425{
1426 SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_LOW);
1427
1428 while (!SDL_GetAtomicInt(&ALSA_hotplug_shutdown)) {
1429 // Block awhile before checking again, unless we're told to stop.
1430 const Uint64 ticks = SDL_GetTicks() + 5000;
1431 while (!SDL_GetAtomicInt(&ALSA_hotplug_shutdown) && (SDL_GetTicks() < ticks)) {
1432 SDL_Delay(100);
1433 }
1434
1435 ALSA_HotplugIteration(NULL, NULL); // run the check.
1436 }
1437
1438 return 0;
1439}
1440#endif
1441
1442static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
1443{
1444 ALSA_guess_device_prefix();
1445
1446 // ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default
1447 // device here. It's the best we can do at this level.
1448 bool has_default_playback = false, has_default_recording = false;
1449 ALSA_HotplugIteration(&has_default_playback, &has_default_recording); // run once now before a thread continues to check.
1450 if (has_default_playback) {
1451 *default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void*)&default_playback_handle);
1452 }
1453 if (has_default_recording) {
1454 *default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void*)&default_recording_handle);
1455 }
1456
1457#if SDL_ALSA_HOTPLUG_THREAD
1458 SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0);
1459 ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
1460 // if the thread doesn't spin, oh well, you just don't get further hotplug events.
1461#endif
1462}
1463
1464static void ALSA_DeinitializeStart(void)
1465{
1466 ALSA_Device *dev;
1467 ALSA_Device *next;
1468
1469#if SDL_ALSA_HOTPLUG_THREAD
1470 if (ALSA_hotplug_thread) {
1471 SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 1);
1472 SDL_WaitThread(ALSA_hotplug_thread, NULL);
1473 ALSA_hotplug_thread = NULL;
1474 }
1475#endif
1476
1477 // Shutting down! Clean up any data we've gathered.
1478 for (dev = hotplug_devices; dev; dev = next) {
1479 //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: at shutdown, removing %s device '%s'", dev->recording ? "recording" : "playback", dev->name);
1480 next = dev->next;
1481 SDL_free(dev->name);
1482 SDL_free(dev);
1483 }
1484 hotplug_devices = NULL;
1485}
1486
1487static void ALSA_Deinitialize(void)
1488{
1489 UnloadALSALibrary();
1490}
1491
1492static bool ALSA_Init(SDL_AudioDriverImpl *impl)
1493{
1494 if (!LoadALSALibrary()) {
1495 return false;
1496 }
1497
1498 impl->DetectDevices = ALSA_DetectDevices;
1499 impl->OpenDevice = ALSA_OpenDevice;
1500 impl->WaitDevice = ALSA_WaitDevice;
1501 impl->GetDeviceBuf = ALSA_GetDeviceBuf;
1502 impl->PlayDevice = ALSA_PlayDevice;
1503 impl->CloseDevice = ALSA_CloseDevice;
1504 impl->DeinitializeStart = ALSA_DeinitializeStart;
1505 impl->Deinitialize = ALSA_Deinitialize;
1506 impl->WaitRecordingDevice = ALSA_WaitDevice;
1507 impl->RecordDevice = ALSA_RecordDevice;
1508 impl->FlushRecording = ALSA_FlushRecording;
1509
1510 impl->HasRecordingSupport = true;
1511
1512 return true;
1513}
1514
1515AudioBootStrap ALSA_bootstrap = {
1516 "alsa", "ALSA PCM audio", ALSA_Init, false, false
1517};
1518
1519#endif // SDL_AUDIO_DRIVER_ALSA
diff --git a/contrib/SDL-3.2.8/src/audio/alsa/SDL_alsa_audio.h b/contrib/SDL-3.2.8/src/audio/alsa/SDL_alsa_audio.h
new file mode 100644
index 0000000..c68dda8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/alsa/SDL_alsa_audio.h
@@ -0,0 +1,41 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_ALSA_audio_h_
24#define SDL_ALSA_audio_h_
25
26#include <alsa/asoundlib.h>
27
28#include "../SDL_sysaudio.h"
29
30#define SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX 8
31#define SDL_AUDIO_ALSA__SDL_CHMAPS_N 9 // from 0 channels to 8 channels
32struct SDL_PrivateAudioData
33{
34 // The audio device handle
35 snd_pcm_t *pcm;
36
37 // Raw mixing buffer
38 Uint8 *mixbuf;
39};
40
41#endif // SDL_ALSA_audio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h
new file mode 100644
index 0000000..f117c09
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h
@@ -0,0 +1,68 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_coreaudio_h_
24#define SDL_coreaudio_h_
25
26#include "../SDL_sysaudio.h"
27
28#ifndef SDL_PLATFORM_IOS
29#define MACOSX_COREAUDIO
30#endif
31
32#ifdef MACOSX_COREAUDIO
33#include <CoreAudio/CoreAudio.h>
34#else
35#import <AVFoundation/AVFoundation.h>
36#import <UIKit/UIApplication.h>
37#endif
38
39#include <AudioToolbox/AudioToolbox.h>
40#include <AudioUnit/AudioUnit.h>
41
42// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
43#ifdef MACOSX_COREAUDIO
44#include <AvailabilityMacros.h>
45#ifndef MAC_OS_VERSION_12_0
46#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
47#endif
48#endif
49
50struct SDL_PrivateAudioData
51{
52 SDL_Thread *thread;
53 AudioQueueRef audioQueue;
54 int numAudioBuffers;
55 AudioQueueBufferRef *audioBuffer;
56 AudioQueueBufferRef current_buffer;
57 AudioStreamBasicDescription strdesc;
58 SDL_Semaphore *ready_semaphore;
59 char *thread_error;
60#ifdef MACOSX_COREAUDIO
61 AudioDeviceID deviceID;
62#else
63 bool interrupted;
64 CFTypeRef interruption_listener;
65#endif
66};
67
68#endif // SDL_coreaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m
new file mode 100644
index 0000000..57b19c7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m
@@ -0,0 +1,1040 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_COREAUDIO
24
25#include "../SDL_sysaudio.h"
26#include "SDL_coreaudio.h"
27#include "../../thread/SDL_systhread.h"
28
29#define DEBUG_COREAUDIO 0
30
31#if DEBUG_COREAUDIO
32 #define CHECK_RESULT(msg) \
33 if (result != noErr) { \
34 SDL_Log("COREAUDIO: Got error %d from '%s'!", (int)result, msg); \
35 return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \
36 }
37#else
38 #define CHECK_RESULT(msg) \
39 if (result != noErr) { \
40 return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \
41 }
42#endif
43
44#ifdef MACOSX_COREAUDIO
45// Apparently AudioDeviceID values might not be unique, so we wrap it in an SDL_malloc()'d pointer
46// to make it so. Use FindCoreAudioDeviceByHandle to deal with this redirection, if you need to
47// map from an AudioDeviceID to a SDL handle.
48typedef struct SDLCoreAudioHandle
49{
50 AudioDeviceID devid;
51 bool recording;
52} SDLCoreAudioHandle;
53
54static bool TestCoreAudioDeviceHandleCallback(SDL_AudioDevice *device, void *handle)
55{
56 const SDLCoreAudioHandle *a = (const SDLCoreAudioHandle *) device->handle;
57 const SDLCoreAudioHandle *b = (const SDLCoreAudioHandle *) handle;
58 return (a->devid == b->devid) && (!!a->recording == !!b->recording);
59}
60
61static SDL_AudioDevice *FindCoreAudioDeviceByHandle(const AudioDeviceID devid, const bool recording)
62{
63 SDLCoreAudioHandle handle = { devid, recording };
64 return SDL_FindPhysicalAudioDeviceByCallback(TestCoreAudioDeviceHandleCallback, &handle);
65}
66
67static const AudioObjectPropertyAddress devlist_address = {
68 kAudioHardwarePropertyDevices,
69 kAudioObjectPropertyScopeGlobal,
70 kAudioObjectPropertyElementMain
71};
72
73static const AudioObjectPropertyAddress default_playback_device_address = {
74 kAudioHardwarePropertyDefaultOutputDevice,
75 kAudioObjectPropertyScopeGlobal,
76 kAudioObjectPropertyElementMain
77};
78
79static const AudioObjectPropertyAddress default_recording_device_address = {
80 kAudioHardwarePropertyDefaultInputDevice,
81 kAudioObjectPropertyScopeGlobal,
82 kAudioObjectPropertyElementMain
83};
84
85static const AudioObjectPropertyAddress alive_address = {
86 kAudioDevicePropertyDeviceIsAlive,
87 kAudioObjectPropertyScopeGlobal,
88 kAudioObjectPropertyElementMain
89};
90
91
92static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
93{
94 SDL_AudioDevice *device = (SDL_AudioDevice *)data;
95 SDL_assert(((const SDLCoreAudioHandle *) device->handle)->devid == devid);
96
97 UInt32 alive = 1;
98 UInt32 size = sizeof(alive);
99 const OSStatus error = AudioObjectGetPropertyData(devid, addrs, 0, NULL, &size, &alive);
100
101 bool dead = false;
102 if (error == kAudioHardwareBadDeviceError) {
103 dead = true; // device was unplugged.
104 } else if ((error == kAudioHardwareNoError) && (!alive)) {
105 dead = true; // device died in some other way.
106 }
107
108 if (dead) {
109 #if DEBUG_COREAUDIO
110 SDL_Log("COREAUDIO: device '%s' is lost!", device->name);
111 #endif
112 SDL_AudioDeviceDisconnected(device);
113 }
114
115 return noErr;
116}
117
118static void COREAUDIO_FreeDeviceHandle(SDL_AudioDevice *device)
119{
120 SDLCoreAudioHandle *handle = (SDLCoreAudioHandle *) device->handle;
121 AudioObjectRemovePropertyListener(handle->devid, &alive_address, DeviceAliveNotification, device);
122 SDL_free(handle);
123}
124
125// This only _adds_ new devices. Removal is handled by devices triggering kAudioDevicePropertyDeviceIsAlive property changes.
126static void RefreshPhysicalDevices(void)
127{
128 UInt32 size = 0;
129 AudioDeviceID *devs = NULL;
130 bool isstack;
131
132 if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size) != kAudioHardwareNoError) {
133 return;
134 } else if ((devs = (AudioDeviceID *) SDL_small_alloc(Uint8, size, &isstack)) == NULL) {
135 return;
136 } else if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size, devs) != kAudioHardwareNoError) {
137 SDL_small_free(devs, isstack);
138 return;
139 }
140
141 const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID));
142 for (UInt32 i = 0; i < total_devices; i++) {
143 if (FindCoreAudioDeviceByHandle(devs[i], true) || FindCoreAudioDeviceByHandle(devs[i], false)) {
144 devs[i] = 0; // The system and SDL both agree it's already here, don't check it again.
145 }
146 }
147
148 // any non-zero items remaining in `devs` are new devices to be added.
149 for (int recording = 0; recording < 2; recording++) {
150 const AudioObjectPropertyAddress addr = {
151 kAudioDevicePropertyStreamConfiguration,
152 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
153 kAudioObjectPropertyElementMain
154 };
155 const AudioObjectPropertyAddress nameaddr = {
156 kAudioObjectPropertyName,
157 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
158 kAudioObjectPropertyElementMain
159 };
160 const AudioObjectPropertyAddress freqaddr = {
161 kAudioDevicePropertyNominalSampleRate,
162 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
163 kAudioObjectPropertyElementMain
164 };
165
166 for (UInt32 i = 0; i < total_devices; i++) {
167 const AudioDeviceID dev = devs[i];
168 if (!dev) {
169 continue; // already added.
170 }
171
172 AudioBufferList *buflist = NULL;
173 double sampleRate = 0;
174
175 if (AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size) != noErr) {
176 continue;
177 } else if ((buflist = (AudioBufferList *)SDL_malloc(size)) == NULL) {
178 continue;
179 }
180
181 OSStatus result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist);
182
183 SDL_AudioSpec spec;
184 SDL_zero(spec);
185 if (result == noErr) {
186 for (Uint32 j = 0; j < buflist->mNumberBuffers; j++) {
187 spec.channels += buflist->mBuffers[j].mNumberChannels;
188 }
189 }
190
191 SDL_free(buflist);
192
193 if (spec.channels == 0) {
194 continue;
195 }
196
197 size = sizeof(sampleRate);
198 if (AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate) == noErr) {
199 spec.freq = (int)sampleRate;
200 }
201
202 CFStringRef cfstr = NULL;
203 size = sizeof(CFStringRef);
204 if (AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr) != kAudioHardwareNoError) {
205 continue;
206 }
207
208 CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8);
209 char *name = (char *)SDL_malloc(len + 1);
210 bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8)));
211
212 CFRelease(cfstr);
213
214 if (usable) {
215 // Some devices have whitespace at the end...trim it.
216 len = (CFIndex) SDL_strlen(name);
217 while ((len > 0) && (name[len - 1] == ' ')) {
218 len--;
219 }
220 usable = (len > 0);
221 }
222
223 if (usable) {
224 name[len] = '\0';
225
226 #if DEBUG_COREAUDIO
227 SDL_Log("COREAUDIO: Found %s device #%d: '%s' (devid %d)", ((recording) ? "recording" : "playback"), (int)i, name, (int)dev);
228 #endif
229 SDLCoreAudioHandle *newhandle = (SDLCoreAudioHandle *) SDL_calloc(1, sizeof (*newhandle));
230 if (newhandle) {
231 newhandle->devid = dev;
232 newhandle->recording = recording ? true : false;
233 SDL_AudioDevice *device = SDL_AddAudioDevice(newhandle->recording, name, &spec, newhandle);
234 if (device) {
235 AudioObjectAddPropertyListener(dev, &alive_address, DeviceAliveNotification, device);
236 } else {
237 SDL_free(newhandle);
238 }
239 }
240 }
241 SDL_free(name); // SDL_AddAudioDevice() would have copied the string.
242 }
243 }
244
245 SDL_small_free(devs, isstack);
246}
247
248// this is called when the system's list of available audio devices changes.
249static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
250{
251 RefreshPhysicalDevices();
252 return noErr;
253}
254
255static OSStatus DefaultAudioDeviceChangedNotification(const bool recording, AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr)
256{
257 AudioDeviceID devid;
258 UInt32 size = sizeof(devid);
259 if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) {
260 SDL_DefaultAudioDeviceChanged(FindCoreAudioDeviceByHandle(devid, recording));
261 }
262 return noErr;
263}
264
265static OSStatus DefaultPlaybackDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
266{
267 #if DEBUG_COREAUDIO
268 SDL_Log("COREAUDIO: default playback device changed!");
269 #endif
270 SDL_assert(inNumberAddresses == 1);
271 return DefaultAudioDeviceChangedNotification(false, inObjectID, inAddresses);
272}
273
274static OSStatus DefaultRecordingDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
275{
276 #if DEBUG_COREAUDIO
277 SDL_Log("COREAUDIO: default recording device changed!");
278 #endif
279 SDL_assert(inNumberAddresses == 1);
280 return DefaultAudioDeviceChangedNotification(true, inObjectID, inAddresses);
281}
282
283static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
284{
285 RefreshPhysicalDevices();
286
287 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL);
288
289 // Get the Device ID
290 UInt32 size;
291 AudioDeviceID devid;
292
293 size = sizeof(AudioDeviceID);
294 if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_playback_device_address, 0, NULL, &size, &devid) == noErr) {
295 SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, false);
296 if (device) {
297 *default_playback = device;
298 }
299 }
300 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_playback_device_address, DefaultPlaybackDeviceChangedNotification, NULL);
301
302 size = sizeof(AudioDeviceID);
303 if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_recording_device_address, 0, NULL, &size, &devid) == noErr) {
304 SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, true);
305 if (device) {
306 *default_recording = device;
307 }
308 }
309 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_recording_device_address, DefaultRecordingDeviceChangedNotification, NULL);
310}
311
312#else // iOS-specific section follows.
313
314static bool session_active = false;
315
316static bool PauseOneAudioDevice(SDL_AudioDevice *device, void *userdata)
317{
318 if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) {
319 AudioQueuePause(device->hidden->audioQueue);
320 }
321 return false; // keep enumerating devices until we've paused them all.
322}
323
324static void PauseAudioDevices(void)
325{
326 (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneAudioDevice, NULL);
327}
328
329static bool ResumeOneAudioDevice(SDL_AudioDevice *device, void *userdata)
330{
331 if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) {
332 AudioQueueStart(device->hidden->audioQueue, NULL);
333 }
334 return false; // keep enumerating devices until we've resumed them all.
335}
336
337static void ResumeAudioDevices(void)
338{
339 (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneAudioDevice, NULL);
340}
341
342static void InterruptionBegin(SDL_AudioDevice *device)
343{
344 if (device != NULL && device->hidden->audioQueue != NULL) {
345 device->hidden->interrupted = true;
346 AudioQueuePause(device->hidden->audioQueue);
347 }
348}
349
350static void InterruptionEnd(SDL_AudioDevice *device)
351{
352 if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL && device->hidden->interrupted && AudioQueueStart(device->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) {
353 device->hidden->interrupted = false;
354 }
355}
356
357@interface SDLInterruptionListener : NSObject
358
359@property(nonatomic, assign) SDL_AudioDevice *device;
360
361@end
362
363@implementation SDLInterruptionListener
364
365- (void)audioSessionInterruption:(NSNotification *)note
366{
367 @synchronized(self) {
368 NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
369 if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
370 InterruptionBegin(self.device);
371 } else {
372 InterruptionEnd(self.device);
373 }
374 }
375}
376
377- (void)applicationBecameActive:(NSNotification *)note
378{
379 @synchronized(self) {
380 InterruptionEnd(self.device);
381 }
382}
383
384@end
385
386typedef struct
387{
388 int playback;
389 int recording;
390} CountOpenAudioDevicesData;
391
392static bool CountOpenAudioDevices(SDL_AudioDevice *device, void *userdata)
393{
394 CountOpenAudioDevicesData *data = (CountOpenAudioDevicesData *) userdata;
395 if (device->hidden != NULL) { // assume it's open if hidden != NULL
396 if (device->recording) {
397 data->recording++;
398 } else {
399 data->playback++;
400 }
401 }
402 return false; // keep enumerating until all devices have been checked.
403}
404
405static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_playandrecord)
406{
407 @autoreleasepool {
408 AVAudioSession *session = [AVAudioSession sharedInstance];
409 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
410
411 NSString *category = AVAudioSessionCategoryPlayback;
412 NSString *mode = AVAudioSessionModeDefault;
413 NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers;
414 NSError *err = nil;
415 const char *hint;
416
417 CountOpenAudioDevicesData data;
418 SDL_zero(data);
419 (void) SDL_FindPhysicalAudioDeviceByCallback(CountOpenAudioDevices, &data);
420
421 hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY);
422 if (hint) {
423 if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) {
424 category = AVAudioSessionCategoryAmbient;
425 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) {
426 category = AVAudioSessionCategorySoloAmbient;
427 options &= ~AVAudioSessionCategoryOptionMixWithOthers;
428 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 ||
429 SDL_strcasecmp(hint, "playback") == 0) {
430 category = AVAudioSessionCategoryPlayback;
431 options &= ~AVAudioSessionCategoryOptionMixWithOthers;
432 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayAndRecord") == 0 ||
433 SDL_strcasecmp(hint, "playandrecord") == 0) {
434 if (allow_playandrecord) {
435 category = AVAudioSessionCategoryPlayAndRecord;
436 }
437 }
438 } else if (data.playback && data.recording) {
439 if (allow_playandrecord) {
440 category = AVAudioSessionCategoryPlayAndRecord;
441 } else {
442 // We already failed play and record with AVAudioSessionErrorCodeResourceNotAvailable
443 return false;
444 }
445 } else if (data.recording) {
446 category = AVAudioSessionCategoryRecord;
447 }
448
449 #ifndef SDL_PLATFORM_TVOS
450 if (category == AVAudioSessionCategoryPlayAndRecord) {
451 options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
452 }
453 #endif
454 if (category == AVAudioSessionCategoryRecord ||
455 category == AVAudioSessionCategoryPlayAndRecord) {
456 /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for
457 Apple TV but is still needed in order to output to Bluetooth devices.
458 */
459 options |= 0x4; // AVAudioSessionCategoryOptionAllowBluetooth;
460 }
461 if (category == AVAudioSessionCategoryPlayAndRecord) {
462 options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP |
463 AVAudioSessionCategoryOptionAllowAirPlay;
464 }
465 if (category == AVAudioSessionCategoryPlayback ||
466 category == AVAudioSessionCategoryPlayAndRecord) {
467 options |= AVAudioSessionCategoryOptionDuckOthers;
468 }
469
470 if (![session.category isEqualToString:category] || session.categoryOptions != options) {
471 // Stop the current session so we don't interrupt other application audio
472 PauseAudioDevices();
473 [session setActive:NO error:nil];
474 session_active = false;
475
476 if (![session setCategory:category mode:mode options:options error:&err]) {
477 NSString *desc = err.description;
478 SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
479 return false;
480 }
481 }
482
483 if ((data.playback || data.recording) && !session_active) {
484 if (![session setActive:YES error:&err]) {
485 if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable &&
486 category == AVAudioSessionCategoryPlayAndRecord) {
487 if (UpdateAudioSession(device, open, false)) {
488 return true;
489 } else {
490 return SDL_SetError("Could not activate Audio Session: Resource not available");
491 }
492 }
493
494 NSString *desc = err.description;
495 return SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String);
496 }
497 session_active = true;
498 ResumeAudioDevices();
499 } else if (!data.playback && !data.recording && session_active) {
500 PauseAudioDevices();
501 [session setActive:NO error:nil];
502 session_active = false;
503 }
504
505 if (open) {
506 SDLInterruptionListener *listener = [SDLInterruptionListener new];
507 listener.device = device;
508
509 [center addObserver:listener
510 selector:@selector(audioSessionInterruption:)
511 name:AVAudioSessionInterruptionNotification
512 object:session];
513
514 /* An interruption end notification is not guaranteed to be sent if
515 we were previously interrupted... resuming if needed when the app
516 becomes active seems to be the way to go. */
517 // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications.
518 [center addObserver:listener
519 selector:@selector(applicationBecameActive:)
520 name:UIApplicationDidBecomeActiveNotification
521 object:nil];
522
523 [center addObserver:listener
524 selector:@selector(applicationBecameActive:)
525 name:UIApplicationWillEnterForegroundNotification
526 object:nil];
527
528 device->hidden->interruption_listener = CFBridgingRetain(listener);
529 } else {
530 SDLInterruptionListener *listener = nil;
531 listener = (SDLInterruptionListener *)CFBridgingRelease(device->hidden->interruption_listener);
532 [center removeObserver:listener];
533 @synchronized(listener) {
534 listener.device = NULL;
535 }
536 }
537 }
538
539 return true;
540}
541#endif
542
543
544static bool COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
545{
546 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
547 SDL_assert(current_buffer != NULL); // should have been called from PlaybackBufferReadyCallback
548 SDL_assert(buffer == (Uint8 *) current_buffer->mAudioData);
549 current_buffer->mAudioDataByteSize = current_buffer->mAudioDataBytesCapacity;
550 device->hidden->current_buffer = NULL;
551 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
552 return true;
553}
554
555static Uint8 *COREAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
556{
557 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
558 SDL_assert(current_buffer != NULL); // should have been called from PlaybackBufferReadyCallback
559 SDL_assert(current_buffer->mAudioData != NULL);
560 *buffer_size = (int) current_buffer->mAudioDataBytesCapacity;
561 return (Uint8 *) current_buffer->mAudioData;
562}
563
564static void PlaybackBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
565{
566 SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData;
567 SDL_assert(inBuffer != NULL); // ...right?
568 SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending
569 device->hidden->current_buffer = inBuffer;
570 const bool okay = SDL_PlaybackAudioThreadIterate(device);
571 SDL_assert((device->hidden->current_buffer == NULL) || !okay); // PlayDevice should have enqueued and cleaned it out, unless we failed or shutdown.
572
573 // buffer is unexpectedly here? We're probably dying, but try to requeue this buffer with silence.
574 if (device->hidden->current_buffer) {
575 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
576 device->hidden->current_buffer = NULL;
577 SDL_memset(current_buffer->mAudioData, device->silence_value, (size_t) current_buffer->mAudioDataBytesCapacity);
578 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
579 }
580}
581
582static int COREAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
583{
584 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
585 SDL_assert(current_buffer != NULL); // should have been called from RecordingBufferReadyCallback
586 SDL_assert(current_buffer->mAudioData != NULL);
587 SDL_assert(buflen >= (int) current_buffer->mAudioDataByteSize); // `cpy` makes sure this won't overflow a buffer, but we _will_ drop samples if this assertion fails!
588 const int cpy = SDL_min(buflen, (int) current_buffer->mAudioDataByteSize);
589 SDL_memcpy(buffer, current_buffer->mAudioData, cpy);
590 device->hidden->current_buffer = NULL;
591 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); // requeue for capturing more data later.
592 return cpy;
593}
594
595static void COREAUDIO_FlushRecording(SDL_AudioDevice *device)
596{
597 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
598 if (current_buffer != NULL) { // also gets called at shutdown, when no buffer is available.
599 // just requeue the current buffer without reading from it, so it can be refilled with new data later.
600 device->hidden->current_buffer = NULL;
601 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
602 }
603}
604
605static void RecordingBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
606 const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
607 const AudioStreamPacketDescription *inPacketDescs)
608{
609 SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData;
610 SDL_assert(inAQ == device->hidden->audioQueue);
611 SDL_assert(inBuffer != NULL); // ...right?
612 SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending
613 device->hidden->current_buffer = inBuffer;
614 SDL_RecordingAudioThreadIterate(device);
615
616 // buffer is unexpectedly here? We're probably dying, but try to requeue this buffer anyhow.
617 if (device->hidden->current_buffer != NULL) {
618 SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0);
619 COREAUDIO_FlushRecording(device); // just flush it manually, which will requeue it.
620 }
621}
622
623static void COREAUDIO_CloseDevice(SDL_AudioDevice *device)
624{
625 if (!device->hidden) {
626 return;
627 }
628
629 // dispose of the audio queue before waiting on the thread, or it might stall for a long time!
630 if (device->hidden->audioQueue) {
631 AudioQueueFlush(device->hidden->audioQueue);
632 AudioQueueStop(device->hidden->audioQueue, 0);
633 AudioQueueDispose(device->hidden->audioQueue, 0);
634 }
635
636 if (device->hidden->thread) {
637 SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0); // should have been set by SDL_audio.c
638 SDL_WaitThread(device->hidden->thread, NULL);
639 }
640
641 #ifndef MACOSX_COREAUDIO
642 UpdateAudioSession(device, false, true);
643 #endif
644
645 if (device->hidden->ready_semaphore) {
646 SDL_DestroySemaphore(device->hidden->ready_semaphore);
647 }
648
649 // AudioQueueDispose() frees the actual buffer objects.
650 SDL_free(device->hidden->audioBuffer);
651 SDL_free(device->hidden->thread_error);
652 SDL_free(device->hidden);
653}
654
655#ifdef MACOSX_COREAUDIO
656static bool PrepareDevice(SDL_AudioDevice *device)
657{
658 SDL_assert(device->handle != NULL); // this meant "system default" in SDL2, but doesn't anymore
659
660 const SDLCoreAudioHandle *handle = (const SDLCoreAudioHandle *) device->handle;
661 const AudioDeviceID devid = handle->devid;
662 OSStatus result = noErr;
663 UInt32 size = 0;
664
665 AudioObjectPropertyAddress addr = {
666 0,
667 kAudioObjectPropertyScopeGlobal,
668 kAudioObjectPropertyElementMain
669 };
670
671 UInt32 alive = 0;
672 size = sizeof(alive);
673 addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
674 addr.mScope = device->recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
675 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
676 CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
677 if (!alive) {
678 return SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
679 }
680
681 // some devices don't support this property, so errors are fine here.
682 pid_t pid = 0;
683 size = sizeof(pid);
684 addr.mSelector = kAudioDevicePropertyHogMode;
685 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
686 if ((result == noErr) && (pid != -1)) {
687 return SDL_SetError("CoreAudio: requested device is being hogged.");
688 }
689
690 device->hidden->deviceID = devid;
691
692 return true;
693}
694
695static bool AssignDeviceToAudioQueue(SDL_AudioDevice *device)
696{
697 const AudioObjectPropertyAddress prop = {
698 kAudioDevicePropertyDeviceUID,
699 device->recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
700 kAudioObjectPropertyElementMain
701 };
702
703 OSStatus result;
704 CFStringRef devuid;
705 UInt32 devuidsize = sizeof(devuid);
706 result = AudioObjectGetPropertyData(device->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid);
707 CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
708 result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
709 CFRelease(devuid); // Release devuid; we're done with it and AudioQueueSetProperty should have retained if it wants to keep it.
710 CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
711 return true;
712}
713#endif
714
715static bool PrepareAudioQueue(SDL_AudioDevice *device)
716{
717 const AudioStreamBasicDescription *strdesc = &device->hidden->strdesc;
718 const bool recording = device->recording;
719 OSStatus result;
720
721 SDL_assert(CFRunLoopGetCurrent() != NULL);
722
723 if (recording) {
724 result = AudioQueueNewInput(strdesc, RecordingBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue);
725 CHECK_RESULT("AudioQueueNewInput");
726 } else {
727 result = AudioQueueNewOutput(strdesc, PlaybackBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue);
728 CHECK_RESULT("AudioQueueNewOutput");
729 }
730
731 #ifdef MACOSX_COREAUDIO
732 if (!AssignDeviceToAudioQueue(device)) {
733 return false;
734 }
735 #endif
736
737 SDL_UpdatedAudioDeviceFormat(device); // make sure this is correct.
738
739 // Set the channel layout for the audio queue
740 AudioChannelLayout layout;
741 SDL_zero(layout);
742 switch (device->spec.channels) {
743 case 1:
744 // a standard mono stream
745 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
746 break;
747 case 2:
748 // a standard stereo stream (L R) - implied playback
749 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
750 break;
751 case 3:
752 // L R LFE
753 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4;
754 break;
755 case 4:
756 // front left, front right, back left, back right
757 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic;
758 break;
759 case 5:
760 // L R LFE Ls Rs
761 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_6;
762 break;
763 case 6:
764 // L R C LFE Ls Rs
765 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_12;
766 break;
767 case 7:
768 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
769 // L R C LFE Cs Ls Rs
770 layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_6_1;
771 } else {
772 // L R C LFE Ls Rs Cs
773 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A;
774
775 // Convert from SDL channel layout to kAudioChannelLayoutTag_MPEG_6_1_A
776 static const int swizzle_map[7] = {
777 0, 1, 2, 3, 6, 4, 5
778 };
779 device->chmap = SDL_ChannelMapDup(swizzle_map, SDL_arraysize(swizzle_map));
780 if (!device->chmap) {
781 return false;
782 }
783 }
784 break;
785 case 8:
786 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
787 // L R C LFE Rls Rrs Ls Rs
788 layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_7_1;
789 } else {
790 // L R C LFE Ls Rs Rls Rrs
791 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_C;
792
793 // Convert from SDL channel layout to kAudioChannelLayoutTag_MPEG_7_1_C
794 static const int swizzle_map[8] = {
795 0, 1, 2, 3, 6, 7, 4, 5
796 };
797 device->chmap = SDL_ChannelMapDup(swizzle_map, SDL_arraysize(swizzle_map));
798 if (!device->chmap) {
799 return false;
800 }
801 }
802 break;
803 default:
804 return SDL_SetError("Unsupported audio channels");
805 }
806 if (layout.mChannelLayoutTag != 0) {
807 result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout));
808 CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)");
809 }
810
811 // Make sure we can feed the device a minimum amount of time
812 double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
813 #ifdef SDL_PLATFORM_IOS
814 if (SDL_floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
815 // Older iOS hardware, use 40 ms as a minimum time
816 MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
817 }
818 #endif
819
820 // we use THREE audio buffers by default, unlike most things that would
821 // choose two alternating buffers, because it helps with issues on
822 // Bluetooth headsets when recording and playing at the same time.
823 // See conversation in #8192 for details.
824 int numAudioBuffers = 3;
825 const double msecs = (device->sample_frames / ((double)device->spec.freq)) * 1000.0;
826 if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { // use more buffers if we have a VERY small sample set.
827 numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
828 }
829
830 device->hidden->numAudioBuffers = numAudioBuffers;
831 device->hidden->audioBuffer = SDL_calloc(numAudioBuffers, sizeof(AudioQueueBufferRef));
832 if (device->hidden->audioBuffer == NULL) {
833 return false;
834 }
835
836 #if DEBUG_COREAUDIO
837 SDL_Log("COREAUDIO: numAudioBuffers == %d", numAudioBuffers);
838 #endif
839
840 for (int i = 0; i < numAudioBuffers; i++) {
841 result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->buffer_size, &device->hidden->audioBuffer[i]);
842 CHECK_RESULT("AudioQueueAllocateBuffer");
843 SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->silence_value, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
844 device->hidden->audioBuffer[i]->mAudioDataByteSize = device->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
845 // !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data?
846 result = AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL);
847 CHECK_RESULT("AudioQueueEnqueueBuffer");
848 }
849
850 result = AudioQueueStart(device->hidden->audioQueue, NULL);
851 CHECK_RESULT("AudioQueueStart");
852
853 return true; // We're running!
854}
855
856static int AudioQueueThreadEntry(void *arg)
857{
858 SDL_AudioDevice *device = (SDL_AudioDevice *)arg;
859
860 if (device->recording) {
861 SDL_RecordingAudioThreadSetup(device);
862 } else {
863 SDL_PlaybackAudioThreadSetup(device);
864 }
865
866 if (!PrepareAudioQueue(device)) {
867 device->hidden->thread_error = SDL_strdup(SDL_GetError());
868 SDL_SignalSemaphore(device->hidden->ready_semaphore);
869 return 0;
870 }
871
872 // init was successful, alert parent thread and start running...
873 SDL_SignalSemaphore(device->hidden->ready_semaphore);
874
875 // This would be WaitDevice/WaitRecordingDevice in the normal SDL audio thread, but we get *BufferReadyCallback calls here to know when to iterate.
876 while (!SDL_GetAtomicInt(&device->shutdown)) {
877 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
878 }
879
880 if (device->recording) {
881 SDL_RecordingAudioThreadShutdown(device);
882 } else {
883 // Drain off any pending playback.
884 const CFTimeInterval secs = (((CFTimeInterval)device->sample_frames) / ((CFTimeInterval)device->spec.freq)) * 2.0;
885 CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
886 SDL_PlaybackAudioThreadShutdown(device);
887 }
888
889 return 0;
890}
891
892static bool COREAUDIO_OpenDevice(SDL_AudioDevice *device)
893{
894 // Initialize all variables that we clean on shutdown
895 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
896 if (device->hidden == NULL) {
897 return false;
898 }
899
900 #ifndef MACOSX_COREAUDIO
901 if (!UpdateAudioSession(device, true, true)) {
902 return false;
903 }
904
905 // Stop CoreAudio from doing expensive audio rate conversion
906 @autoreleasepool {
907 AVAudioSession *session = [AVAudioSession sharedInstance];
908 [session setPreferredSampleRate:device->spec.freq error:nil];
909 device->spec.freq = (int)session.sampleRate;
910 #ifdef SDL_PLATFORM_TVOS
911 if (device->recording) {
912 [session setPreferredInputNumberOfChannels:device->spec.channels error:nil];
913 device->spec.channels = (int)session.preferredInputNumberOfChannels;
914 } else {
915 [session setPreferredOutputNumberOfChannels:device->spec.channels error:nil];
916 device->spec.channels = (int)session.preferredOutputNumberOfChannels;
917 }
918 #else
919 // Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS
920 #endif // SDL_PLATFORM_TVOS
921 }
922 #endif
923
924 // Setup a AudioStreamBasicDescription with the requested format
925 AudioStreamBasicDescription *strdesc = &device->hidden->strdesc;
926 strdesc->mFormatID = kAudioFormatLinearPCM;
927 strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
928 strdesc->mChannelsPerFrame = device->spec.channels;
929 strdesc->mSampleRate = device->spec.freq;
930 strdesc->mFramesPerPacket = 1;
931
932 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
933 SDL_AudioFormat test_format;
934 while ((test_format = *(closefmts++)) != 0) {
935 // CoreAudio handles most of SDL's formats natively.
936 switch (test_format) {
937 case SDL_AUDIO_U8:
938 case SDL_AUDIO_S8:
939 case SDL_AUDIO_S16LE:
940 case SDL_AUDIO_S16BE:
941 case SDL_AUDIO_S32LE:
942 case SDL_AUDIO_S32BE:
943 case SDL_AUDIO_F32LE:
944 case SDL_AUDIO_F32BE:
945 break;
946
947 default:
948 continue;
949 }
950 break;
951 }
952
953 if (!test_format) { // shouldn't happen, but just in case...
954 return SDL_SetError("%s: Unsupported audio format", "coreaudio");
955 }
956 device->spec.format = test_format;
957 strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(test_format);
958 if (SDL_AUDIO_ISBIGENDIAN(test_format)) {
959 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
960 }
961
962 if (SDL_AUDIO_ISFLOAT(test_format)) {
963 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
964 } else if (SDL_AUDIO_ISSIGNED(test_format)) {
965 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
966 }
967
968 strdesc->mBytesPerFrame = strdesc->mChannelsPerFrame * strdesc->mBitsPerChannel / 8;
969 strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;
970
971#ifdef MACOSX_COREAUDIO
972 if (!PrepareDevice(device)) {
973 return false;
974 }
975#endif
976
977 // This has to init in a new thread so it can get its own CFRunLoop. :/
978 device->hidden->ready_semaphore = SDL_CreateSemaphore(0);
979 if (!device->hidden->ready_semaphore) {
980 return false; // oh well.
981 }
982
983 char threadname[64];
984 SDL_GetAudioThreadName(device, threadname, sizeof(threadname));
985 device->hidden->thread = SDL_CreateThread(AudioQueueThreadEntry, threadname, device);
986 if (!device->hidden->thread) {
987 return false;
988 }
989
990 SDL_WaitSemaphore(device->hidden->ready_semaphore);
991 SDL_DestroySemaphore(device->hidden->ready_semaphore);
992 device->hidden->ready_semaphore = NULL;
993
994 if ((device->hidden->thread != NULL) && (device->hidden->thread_error != NULL)) {
995 SDL_WaitThread(device->hidden->thread, NULL);
996 device->hidden->thread = NULL;
997 return SDL_SetError("%s", device->hidden->thread_error);
998 }
999
1000 return (device->hidden->thread != NULL);
1001}
1002
1003static void COREAUDIO_DeinitializeStart(void)
1004{
1005#ifdef MACOSX_COREAUDIO
1006 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL);
1007 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_playback_device_address, DefaultPlaybackDeviceChangedNotification, NULL);
1008 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_recording_device_address, DefaultRecordingDeviceChangedNotification, NULL);
1009#endif
1010}
1011
1012static bool COREAUDIO_Init(SDL_AudioDriverImpl *impl)
1013{
1014 impl->OpenDevice = COREAUDIO_OpenDevice;
1015 impl->PlayDevice = COREAUDIO_PlayDevice;
1016 impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf;
1017 impl->RecordDevice = COREAUDIO_RecordDevice;
1018 impl->FlushRecording = COREAUDIO_FlushRecording;
1019 impl->CloseDevice = COREAUDIO_CloseDevice;
1020 impl->DeinitializeStart = COREAUDIO_DeinitializeStart;
1021
1022#ifdef MACOSX_COREAUDIO
1023 impl->DetectDevices = COREAUDIO_DetectDevices;
1024 impl->FreeDeviceHandle = COREAUDIO_FreeDeviceHandle;
1025#else
1026 impl->OnlyHasDefaultPlaybackDevice = true;
1027 impl->OnlyHasDefaultRecordingDevice = true;
1028#endif
1029
1030 impl->ProvidesOwnCallbackThread = true;
1031 impl->HasRecordingSupport = true;
1032
1033 return true;
1034}
1035
1036AudioBootStrap COREAUDIO_bootstrap = {
1037 "coreaudio", "CoreAudio", COREAUDIO_Init, false, false
1038};
1039
1040#endif // SDL_AUDIO_DRIVER_COREAUDIO
diff --git a/contrib/SDL-3.2.8/src/audio/directsound/SDL_directsound.c b/contrib/SDL-3.2.8/src/audio/directsound/SDL_directsound.c
new file mode 100644
index 0000000..7b5cb11
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/directsound/SDL_directsound.c
@@ -0,0 +1,680 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_DSOUND
24
25#include "../SDL_sysaudio.h"
26#include "SDL_directsound.h"
27#include <mmreg.h>
28#ifdef HAVE_MMDEVICEAPI_H
29#include "../../core/windows/SDL_immdevice.h"
30#endif
31
32#ifndef WAVE_FORMAT_IEEE_FLOAT
33#define WAVE_FORMAT_IEEE_FLOAT 0x0003
34#endif
35
36// For Vista+, we can enumerate DSound devices with IMMDevice
37#ifdef HAVE_MMDEVICEAPI_H
38static bool SupportsIMMDevice = false;
39#endif
40
41// DirectX function pointers for audio
42static SDL_SharedObject *DSoundDLL = NULL;
43typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
44typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
45typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN);
46typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
47typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID);
48static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
49static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
50static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
51static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
52static fnGetDeviceID pGetDeviceID = NULL;
53
54#include <initguid.h>
55DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
56DEFINE_GUID(SDL_DSDEVID_DefaultCapture, 0xdef00001, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
57
58static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
59static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
60
61static void DSOUND_Unload(void)
62{
63 pDirectSoundCreate8 = NULL;
64 pDirectSoundEnumerateW = NULL;
65 pDirectSoundCaptureCreate8 = NULL;
66 pDirectSoundCaptureEnumerateW = NULL;
67 pGetDeviceID = NULL;
68
69 if (DSoundDLL) {
70 SDL_UnloadObject(DSoundDLL);
71 DSoundDLL = NULL;
72 }
73}
74
75static bool DSOUND_Load(void)
76{
77 bool loaded = false;
78
79 DSOUND_Unload();
80
81 DSoundDLL = SDL_LoadObject("DSOUND.DLL");
82 if (!DSoundDLL) {
83 SDL_SetError("DirectSound: failed to load DSOUND.DLL");
84 } else {
85// Now make sure we have DirectX 8 or better...
86#define DSOUNDLOAD(f) \
87 { \
88 p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \
89 if (!p##f) \
90 loaded = false; \
91 }
92 loaded = true; // will reset if necessary.
93 DSOUNDLOAD(DirectSoundCreate8);
94 DSOUNDLOAD(DirectSoundEnumerateW);
95 DSOUNDLOAD(DirectSoundCaptureCreate8);
96 DSOUNDLOAD(DirectSoundCaptureEnumerateW);
97 DSOUNDLOAD(GetDeviceID);
98#undef DSOUNDLOAD
99
100 if (!loaded) {
101 SDL_SetError("DirectSound: System doesn't appear to have DX8.");
102 }
103 }
104
105 if (!loaded) {
106 DSOUND_Unload();
107 }
108
109 return loaded;
110}
111
112static bool SetDSerror(const char *function, int code)
113{
114 const char *error;
115
116 switch (code) {
117 case E_NOINTERFACE:
118 error = "Unsupported interface -- Is DirectX 8.0 or later installed?";
119 break;
120 case DSERR_ALLOCATED:
121 error = "Audio device in use";
122 break;
123 case DSERR_BADFORMAT:
124 error = "Unsupported audio format";
125 break;
126 case DSERR_BUFFERLOST:
127 error = "Mixing buffer was lost";
128 break;
129 case DSERR_CONTROLUNAVAIL:
130 error = "Control requested is not available";
131 break;
132 case DSERR_INVALIDCALL:
133 error = "Invalid call for the current state";
134 break;
135 case DSERR_INVALIDPARAM:
136 error = "Invalid parameter";
137 break;
138 case DSERR_NODRIVER:
139 error = "No audio device found";
140 break;
141 case DSERR_OUTOFMEMORY:
142 error = "Out of memory";
143 break;
144 case DSERR_PRIOLEVELNEEDED:
145 error = "Caller doesn't have priority";
146 break;
147 case DSERR_UNSUPPORTED:
148 error = "Function not supported";
149 break;
150 default:
151 error = "Unknown DirectSound error";
152 break;
153 }
154
155 return SDL_SetError("%s: %s (0x%x)", function, error, code);
156}
157
158static void DSOUND_FreeDeviceHandle(SDL_AudioDevice *device)
159{
160#ifdef HAVE_MMDEVICEAPI_H
161 if (SupportsIMMDevice) {
162 SDL_IMMDevice_FreeDeviceHandle(device);
163 } else
164#endif
165 {
166 SDL_free(device->handle);
167 }
168}
169
170// FindAllDevs is presumably only used on WinXP; Vista and later can use IMMDevice for better results.
171typedef struct FindAllDevsData
172{
173 bool recording;
174 SDL_AudioDevice **default_device;
175 LPCGUID default_device_guid;
176} FindAllDevsData;
177
178static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID userdata)
179{
180 FindAllDevsData *data = (FindAllDevsData *) userdata;
181 if (guid != NULL) { // skip default device
182 char *str = WIN_LookupAudioDeviceName(desc, guid);
183 if (str) {
184 LPGUID cpyguid = (LPGUID)SDL_malloc(sizeof(GUID));
185 if (cpyguid) {
186 SDL_copyp(cpyguid, guid);
187
188 /* Note that spec is NULL, because we are required to connect to the
189 * device before getting the channel mask and output format, making
190 * this information inaccessible at enumeration time
191 */
192 SDL_AudioDevice *device = SDL_AddAudioDevice(data->recording, str, NULL, cpyguid);
193 if (device && data->default_device && data->default_device_guid) {
194 if (SDL_memcmp(cpyguid, data->default_device_guid, sizeof (GUID)) == 0) {
195 *data->default_device = device;
196 }
197 }
198 }
199 SDL_free(str); // SDL_AddAudioDevice() makes a copy of this string.
200 }
201 }
202 return TRUE; // keep enumerating.
203}
204
205static void DSOUND_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
206{
207#ifdef HAVE_MMDEVICEAPI_H
208 if (SupportsIMMDevice) {
209 SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording);
210 } else
211#endif
212 {
213 // Without IMMDevice, you can enumerate devices and figure out the default devices,
214 // but you won't get device hotplug or default device change notifications. But this is
215 // only for WinXP; Windows Vista and later should be using IMMDevice.
216 FindAllDevsData data;
217 GUID guid;
218
219 data.recording = true;
220 data.default_device = default_recording;
221 data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultCapture, &guid) == DS_OK) ? &guid : NULL;
222 pDirectSoundCaptureEnumerateW(FindAllDevs, &data);
223
224 data.recording = false;
225 data.default_device = default_playback;
226 data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultPlayback, &guid) == DS_OK) ? &guid : NULL;
227 pDirectSoundEnumerateW(FindAllDevs, &data);
228 }
229
230}
231
232static bool DSOUND_WaitDevice(SDL_AudioDevice *device)
233{
234 /* Semi-busy wait, since we have no way of getting play notification
235 on a primary mixing buffer located in hardware (DirectX 5.0)
236 */
237 while (!SDL_GetAtomicInt(&device->shutdown)) {
238 DWORD status = 0;
239 DWORD cursor = 0;
240 DWORD junk = 0;
241 HRESULT result = DS_OK;
242
243 // Try to restore a lost sound buffer
244 IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status);
245 if (status & DSBSTATUS_BUFFERLOST) {
246 IDirectSoundBuffer_Restore(device->hidden->mixbuf);
247 } else if (!(status & DSBSTATUS_PLAYING)) {
248 result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, DSBPLAY_LOOPING);
249 } else {
250 // Find out where we are playing
251 result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor);
252 if ((result == DS_OK) && ((cursor / device->buffer_size) != device->hidden->lastchunk)) {
253 break; // ready for next chunk!
254 }
255 }
256
257 if ((result != DS_OK) && (result != DSERR_BUFFERLOST)) {
258 return false;
259 }
260
261 SDL_Delay(1); // not ready yet; sleep a bit.
262 }
263
264 return true;
265}
266
267static bool DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
268{
269 // Unlock the buffer, allowing it to play
270 SDL_assert(buflen == device->buffer_size);
271 if (IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0) != DS_OK) {
272 return false;
273 }
274 return true;
275}
276
277static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
278{
279 DWORD cursor = 0;
280 DWORD junk = 0;
281 HRESULT result = DS_OK;
282
283 SDL_assert(*buffer_size == device->buffer_size);
284
285 // Figure out which blocks to fill next
286 device->hidden->locked_buf = NULL;
287 result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
288 &junk, &cursor);
289 if (result == DSERR_BUFFERLOST) {
290 IDirectSoundBuffer_Restore(device->hidden->mixbuf);
291 result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
292 &junk, &cursor);
293 }
294 if (result != DS_OK) {
295 SetDSerror("DirectSound GetCurrentPosition", result);
296 return NULL;
297 }
298 cursor /= device->buffer_size;
299#ifdef DEBUG_SOUND
300 // Detect audio dropouts
301 {
302 DWORD spot = cursor;
303 if (spot < device->hidden->lastchunk) {
304 spot += device->hidden->num_buffers;
305 }
306 if (spot > device->hidden->lastchunk + 1) {
307 fprintf(stderr, "Audio dropout, missed %d fragments\n",
308 (spot - (device->hidden->lastchunk + 1)));
309 }
310 }
311#endif
312 device->hidden->lastchunk = cursor;
313 cursor = (cursor + 1) % device->hidden->num_buffers;
314 cursor *= device->buffer_size;
315
316 // Lock the audio buffer
317 DWORD rawlen = 0;
318 result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
319 device->buffer_size,
320 (LPVOID *)&device->hidden->locked_buf,
321 &rawlen, NULL, &junk, 0);
322 if (result == DSERR_BUFFERLOST) {
323 IDirectSoundBuffer_Restore(device->hidden->mixbuf);
324 result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
325 device->buffer_size,
326 (LPVOID *)&device->hidden->locked_buf, &rawlen, NULL,
327 &junk, 0);
328 }
329 if (result != DS_OK) {
330 SetDSerror("DirectSound Lock", result);
331 return NULL;
332 }
333 return device->hidden->locked_buf;
334}
335
336static bool DSOUND_WaitRecordingDevice(SDL_AudioDevice *device)
337{
338 struct SDL_PrivateAudioData *h = device->hidden;
339 while (!SDL_GetAtomicInt(&device->shutdown)) {
340 DWORD junk, cursor;
341 if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
342 return false;
343 } else if ((cursor / device->buffer_size) != h->lastchunk) {
344 break;
345 }
346 SDL_Delay(1);
347 }
348
349 return true;
350}
351
352static int DSOUND_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
353{
354 struct SDL_PrivateAudioData *h = device->hidden;
355 DWORD ptr1len, ptr2len;
356 VOID *ptr1, *ptr2;
357
358 SDL_assert(buflen == device->buffer_size);
359
360 if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * buflen, buflen, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) {
361 return -1;
362 }
363
364 SDL_assert(ptr1len == (DWORD)buflen);
365 SDL_assert(ptr2 == NULL);
366 SDL_assert(ptr2len == 0);
367
368 SDL_memcpy(buffer, ptr1, ptr1len);
369
370 if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) {
371 return -1;
372 }
373
374 h->lastchunk = (h->lastchunk + 1) % h->num_buffers;
375
376 return (int) ptr1len;
377}
378
379static void DSOUND_FlushRecording(SDL_AudioDevice *device)
380{
381 struct SDL_PrivateAudioData *h = device->hidden;
382 DWORD junk, cursor;
383 if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) {
384 h->lastchunk = cursor / device->buffer_size;
385 }
386}
387
388static void DSOUND_CloseDevice(SDL_AudioDevice *device)
389{
390 if (device->hidden) {
391 if (device->hidden->mixbuf) {
392 IDirectSoundBuffer_Stop(device->hidden->mixbuf);
393 IDirectSoundBuffer_Release(device->hidden->mixbuf);
394 }
395 if (device->hidden->sound) {
396 IDirectSound_Release(device->hidden->sound);
397 }
398 if (device->hidden->capturebuf) {
399 IDirectSoundCaptureBuffer_Stop(device->hidden->capturebuf);
400 IDirectSoundCaptureBuffer_Release(device->hidden->capturebuf);
401 }
402 if (device->hidden->capture) {
403 IDirectSoundCapture_Release(device->hidden->capture);
404 }
405 SDL_free(device->hidden);
406 device->hidden = NULL;
407 }
408}
409
410/* This function tries to create a secondary audio buffer, and returns the
411 number of audio chunks available in the created buffer. This is for
412 playback devices, not recording.
413*/
414static bool CreateSecondary(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
415{
416 LPDIRECTSOUND sndObj = device->hidden->sound;
417 LPDIRECTSOUNDBUFFER *sndbuf = &device->hidden->mixbuf;
418 HRESULT result = DS_OK;
419 DSBUFFERDESC format;
420 LPVOID pvAudioPtr1, pvAudioPtr2;
421 DWORD dwAudioBytes1, dwAudioBytes2;
422
423 // Try to create the secondary buffer
424 SDL_zero(format);
425 format.dwSize = sizeof(format);
426 format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
427 format.dwFlags |= DSBCAPS_GLOBALFOCUS;
428 format.dwBufferBytes = bufsize;
429 format.lpwfxFormat = wfmt;
430 result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
431 if (result != DS_OK) {
432 return SetDSerror("DirectSound CreateSoundBuffer", result);
433 }
434 IDirectSoundBuffer_SetFormat(*sndbuf, wfmt);
435
436 // Silence the initial audio buffer
437 result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
438 (LPVOID *)&pvAudioPtr1, &dwAudioBytes1,
439 (LPVOID *)&pvAudioPtr2, &dwAudioBytes2,
440 DSBLOCK_ENTIREBUFFER);
441 if (result == DS_OK) {
442 SDL_memset(pvAudioPtr1, device->silence_value, dwAudioBytes1);
443 IDirectSoundBuffer_Unlock(*sndbuf,
444 (LPVOID)pvAudioPtr1, dwAudioBytes1,
445 (LPVOID)pvAudioPtr2, dwAudioBytes2);
446 }
447
448 return true; // We're ready to go
449}
450
451/* This function tries to create a capture buffer, and returns the
452 number of audio chunks available in the created buffer. This is for
453 recording devices, not playback.
454*/
455static bool CreateCaptureBuffer(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
456{
457 LPDIRECTSOUNDCAPTURE capture = device->hidden->capture;
458 LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &device->hidden->capturebuf;
459 DSCBUFFERDESC format;
460 HRESULT result;
461
462 SDL_zero(format);
463 format.dwSize = sizeof(format);
464 format.dwFlags = DSCBCAPS_WAVEMAPPED;
465 format.dwBufferBytes = bufsize;
466 format.lpwfxFormat = wfmt;
467
468 result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
469 if (result != DS_OK) {
470 return SetDSerror("DirectSound CreateCaptureBuffer", result);
471 }
472
473 result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
474 if (result != DS_OK) {
475 IDirectSoundCaptureBuffer_Release(*capturebuf);
476 return SetDSerror("DirectSound Start", result);
477 }
478
479#if 0
480 // presumably this starts at zero, but just in case...
481 result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
482 if (result != DS_OK) {
483 IDirectSoundCaptureBuffer_Stop(*capturebuf);
484 IDirectSoundCaptureBuffer_Release(*capturebuf);
485 return SetDSerror("DirectSound GetCurrentPosition", result);
486 }
487
488 device->hidden->lastchunk = cursor / device->buffer_size;
489#endif
490
491 return true;
492}
493
494static bool DSOUND_OpenDevice(SDL_AudioDevice *device)
495{
496 // Initialize all variables that we clean on shutdown
497 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
498 if (!device->hidden) {
499 return false;
500 }
501
502 // Open the audio device
503 LPGUID guid;
504#ifdef HAVE_MMDEVICEAPI_H
505 if (SupportsIMMDevice) {
506 guid = SDL_IMMDevice_GetDirectSoundGUID(device);
507 } else
508#endif
509 {
510 guid = (LPGUID) device->handle;
511 }
512
513 SDL_assert(guid != NULL);
514
515 HRESULT result;
516 if (device->recording) {
517 result = pDirectSoundCaptureCreate8(guid, &device->hidden->capture, NULL);
518 if (result != DS_OK) {
519 return SetDSerror("DirectSoundCaptureCreate8", result);
520 }
521 } else {
522 result = pDirectSoundCreate8(guid, &device->hidden->sound, NULL);
523 if (result != DS_OK) {
524 return SetDSerror("DirectSoundCreate8", result);
525 }
526 result = IDirectSound_SetCooperativeLevel(device->hidden->sound,
527 GetDesktopWindow(),
528 DSSCL_NORMAL);
529 if (result != DS_OK) {
530 return SetDSerror("DirectSound SetCooperativeLevel", result);
531 }
532 }
533
534 const DWORD numchunks = 8;
535 DWORD bufsize;
536 bool tried_format = false;
537 SDL_AudioFormat test_format;
538 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
539 while ((test_format = *(closefmts++)) != 0) {
540 switch (test_format) {
541 case SDL_AUDIO_U8:
542 case SDL_AUDIO_S16:
543 case SDL_AUDIO_S32:
544 case SDL_AUDIO_F32:
545 tried_format = true;
546
547 device->spec.format = test_format;
548
549 // Update the fragment size as size in bytes
550 SDL_UpdatedAudioDeviceFormat(device);
551
552 bufsize = numchunks * device->buffer_size;
553 if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) {
554 SDL_SetError("Sound buffer size must be between %d and %d",
555 (int)((DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks),
556 (int)(DSBSIZE_MAX / numchunks));
557 } else {
558 WAVEFORMATEXTENSIBLE wfmt;
559 SDL_zero(wfmt);
560 if (device->spec.channels > 2) {
561 wfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
562 wfmt.Format.cbSize = sizeof(wfmt) - sizeof(WAVEFORMATEX);
563
564 if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
565 SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID));
566 } else {
567 SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID));
568 }
569 wfmt.Samples.wValidBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
570
571 switch (device->spec.channels) {
572 case 3: // 3.0 (or 2.1)
573 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER;
574 break;
575 case 4: // 4.0
576 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
577 break;
578 case 5: // 5.0 (or 4.1)
579 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
580 break;
581 case 6: // 5.1
582 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
583 break;
584 case 7: // 6.1
585 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_BACK_CENTER;
586 break;
587 case 8: // 7.1
588 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
589 break;
590 default:
591 SDL_assert(!"Unsupported channel count!");
592 break;
593 }
594 } else if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
595 wfmt.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
596 } else {
597 wfmt.Format.wFormatTag = WAVE_FORMAT_PCM;
598 }
599
600 wfmt.Format.wBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
601 wfmt.Format.nChannels = (WORD)device->spec.channels;
602 wfmt.Format.nSamplesPerSec = device->spec.freq;
603 wfmt.Format.nBlockAlign = wfmt.Format.nChannels * (wfmt.Format.wBitsPerSample / 8);
604 wfmt.Format.nAvgBytesPerSec = wfmt.Format.nSamplesPerSec * wfmt.Format.nBlockAlign;
605
606 const bool rc = device->recording ? CreateCaptureBuffer(device, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(device, bufsize, (WAVEFORMATEX *)&wfmt);
607 if (rc) {
608 device->hidden->num_buffers = numchunks;
609 break;
610 }
611 }
612 continue;
613 default:
614 continue;
615 }
616 break;
617 }
618
619 if (!test_format) {
620 if (tried_format) {
621 return false; // CreateSecondary() should have called SDL_SetError().
622 }
623 return SDL_SetError("%s: Unsupported audio format", "directsound");
624 }
625
626 // Playback buffers will auto-start playing in DSOUND_WaitDevice()
627
628 return true; // good to go.
629}
630
631static void DSOUND_DeinitializeStart(void)
632{
633#ifdef HAVE_MMDEVICEAPI_H
634 if (SupportsIMMDevice) {
635 SDL_IMMDevice_Quit();
636 }
637#endif
638}
639
640static void DSOUND_Deinitialize(void)
641{
642 DSOUND_Unload();
643#ifdef HAVE_MMDEVICEAPI_H
644 SupportsIMMDevice = false;
645#endif
646}
647
648static bool DSOUND_Init(SDL_AudioDriverImpl *impl)
649{
650 if (!DSOUND_Load()) {
651 return false;
652 }
653
654#ifdef HAVE_MMDEVICEAPI_H
655 SupportsIMMDevice = SDL_IMMDevice_Init(NULL);
656#endif
657
658 impl->DetectDevices = DSOUND_DetectDevices;
659 impl->OpenDevice = DSOUND_OpenDevice;
660 impl->PlayDevice = DSOUND_PlayDevice;
661 impl->WaitDevice = DSOUND_WaitDevice;
662 impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
663 impl->WaitRecordingDevice = DSOUND_WaitRecordingDevice;
664 impl->RecordDevice = DSOUND_RecordDevice;
665 impl->FlushRecording = DSOUND_FlushRecording;
666 impl->CloseDevice = DSOUND_CloseDevice;
667 impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
668 impl->DeinitializeStart = DSOUND_DeinitializeStart;
669 impl->Deinitialize = DSOUND_Deinitialize;
670
671 impl->HasRecordingSupport = true;
672
673 return true;
674}
675
676AudioBootStrap DSOUND_bootstrap = {
677 "directsound", "DirectSound", DSOUND_Init, false, false
678};
679
680#endif // SDL_AUDIO_DRIVER_DSOUND
diff --git a/contrib/SDL-3.2.8/src/audio/directsound/SDL_directsound.h b/contrib/SDL-3.2.8/src/audio/directsound/SDL_directsound.h
new file mode 100644
index 0000000..a4fa2fa
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/directsound/SDL_directsound.h
@@ -0,0 +1,43 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_directsound_h_
24#define SDL_directsound_h_
25
26#include "../../core/windows/SDL_directx.h"
27
28#include "../SDL_sysaudio.h"
29
30// The DirectSound objects
31struct SDL_PrivateAudioData
32{
33 // !!! FIXME: make this a union with capture/playback sections?
34 LPDIRECTSOUND sound;
35 LPDIRECTSOUNDBUFFER mixbuf;
36 LPDIRECTSOUNDCAPTURE capture;
37 LPDIRECTSOUNDCAPTUREBUFFER capturebuf;
38 int num_buffers;
39 DWORD lastchunk;
40 Uint8 *locked_buf;
41};
42
43#endif // SDL_directsound_h_
diff --git a/contrib/SDL-3.2.8/src/audio/disk/SDL_diskaudio.c b/contrib/SDL-3.2.8/src/audio/disk/SDL_diskaudio.c
new file mode 100644
index 0000000..9e05478
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/disk/SDL_diskaudio.c
@@ -0,0 +1,171 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_DISK
24
25// Output raw audio data to a file.
26
27#include "../SDL_sysaudio.h"
28#include "SDL_diskaudio.h"
29
30#define DISKDEFAULT_OUTFILE "sdlaudio.raw"
31#define DISKDEFAULT_INFILE "sdlaudio-in.raw"
32
33static bool DISKAUDIO_WaitDevice(SDL_AudioDevice *device)
34{
35 SDL_Delay(device->hidden->io_delay);
36 return true;
37}
38
39static bool DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
40{
41 const int written = (int)SDL_WriteIO(device->hidden->io, buffer, (size_t)buffer_size);
42 if (written != buffer_size) { // If we couldn't write, assume fatal error for now
43 return false;
44 }
45#ifdef DEBUG_AUDIO
46 SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written);
47#endif
48 return true;
49}
50
51static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
52{
53 return device->hidden->mixbuf;
54}
55
56static int DISKAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
57{
58 struct SDL_PrivateAudioData *h = device->hidden;
59 const int origbuflen = buflen;
60
61 if (h->io) {
62 const int br = (int)SDL_ReadIO(h->io, buffer, (size_t)buflen);
63 buflen -= br;
64 buffer = ((Uint8 *)buffer) + br;
65 if (buflen > 0) { // EOF (or error, but whatever).
66 SDL_CloseIO(h->io);
67 h->io = NULL;
68 }
69 }
70
71 // if we ran out of file, just write silence.
72 SDL_memset(buffer, device->silence_value, buflen);
73
74 return origbuflen;
75}
76
77static void DISKAUDIO_FlushRecording(SDL_AudioDevice *device)
78{
79 // no op...we don't advance the file pointer or anything.
80}
81
82static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device)
83{
84 if (device->hidden) {
85 if (device->hidden->io) {
86 SDL_CloseIO(device->hidden->io);
87 }
88 SDL_free(device->hidden->mixbuf);
89 SDL_free(device->hidden);
90 device->hidden = NULL;
91 }
92}
93
94static const char *get_filename(const bool recording)
95{
96 const char *devname = SDL_GetHint(recording ? SDL_HINT_AUDIO_DISK_INPUT_FILE : SDL_HINT_AUDIO_DISK_OUTPUT_FILE);
97 if (!devname) {
98 devname = recording ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE;
99 }
100 return devname;
101}
102
103static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
104{
105 bool recording = device->recording;
106 const char *fname = get_filename(recording);
107
108 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
109 if (!device->hidden) {
110 return false;
111 }
112
113 device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
114
115 const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DISK_TIMESCALE);
116 if (hint) {
117 double scale = SDL_atof(hint);
118 if (scale >= 0.0) {
119 device->hidden->io_delay = (Uint32)SDL_round(device->hidden->io_delay * scale);
120 }
121 }
122
123 // Open the "audio device"
124 device->hidden->io = SDL_IOFromFile(fname, recording ? "rb" : "wb");
125 if (!device->hidden->io) {
126 return false;
127 }
128
129 // Allocate mixing buffer
130 if (!recording) {
131 device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
132 if (!device->hidden->mixbuf) {
133 return false;
134 }
135 SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
136 }
137
138 SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!");
139 SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].", recording ? "Reading from" : "Writing to", fname);
140
141 return true; // We're ready to rock and roll. :-)
142}
143
144static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
145{
146 *default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
147 *default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
148}
149
150static bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)
151{
152 impl->OpenDevice = DISKAUDIO_OpenDevice;
153 impl->WaitDevice = DISKAUDIO_WaitDevice;
154 impl->WaitRecordingDevice = DISKAUDIO_WaitDevice;
155 impl->PlayDevice = DISKAUDIO_PlayDevice;
156 impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf;
157 impl->RecordDevice = DISKAUDIO_RecordDevice;
158 impl->FlushRecording = DISKAUDIO_FlushRecording;
159 impl->CloseDevice = DISKAUDIO_CloseDevice;
160 impl->DetectDevices = DISKAUDIO_DetectDevices;
161
162 impl->HasRecordingSupport = true;
163
164 return true;
165}
166
167AudioBootStrap DISKAUDIO_bootstrap = {
168 "disk", "direct-to-disk audio", DISKAUDIO_Init, true, false
169};
170
171#endif // SDL_AUDIO_DRIVER_DISK
diff --git a/contrib/SDL-3.2.8/src/audio/disk/SDL_diskaudio.h b/contrib/SDL-3.2.8/src/audio/disk/SDL_diskaudio.h
new file mode 100644
index 0000000..d8d9980
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/disk/SDL_diskaudio.h
@@ -0,0 +1,36 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_diskaudio_h_
24#define SDL_diskaudio_h_
25
26#include "../SDL_sysaudio.h"
27
28struct SDL_PrivateAudioData
29{
30 // The file descriptor for the audio device
31 SDL_IOStream *io;
32 Uint32 io_delay;
33 Uint8 *mixbuf;
34};
35
36#endif // SDL_diskaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/dsp/SDL_dspaudio.c b/contrib/SDL-3.2.8/src/audio/dsp/SDL_dspaudio.c
new file mode 100644
index 0000000..62b8990
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/dsp/SDL_dspaudio.c
@@ -0,0 +1,303 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// !!! FIXME: clean out perror and fprintf calls in here.
24
25#ifdef SDL_AUDIO_DRIVER_OSS
26
27#include <stdio.h> // For perror()
28#include <string.h> // For strerror()
29#include <errno.h>
30#include <unistd.h>
31#include <fcntl.h>
32#include <signal.h>
33#include <sys/time.h>
34#include <sys/ioctl.h>
35#include <sys/stat.h>
36
37#include <sys/soundcard.h>
38
39#include "../SDL_audiodev_c.h"
40#include "SDL_dspaudio.h"
41
42static void DSP_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
43{
44 SDL_EnumUnixAudioDevices(false, NULL);
45}
46
47static void DSP_CloseDevice(SDL_AudioDevice *device)
48{
49 if (device->hidden) {
50 if (device->hidden->audio_fd >= 0) {
51 close(device->hidden->audio_fd);
52 }
53 SDL_free(device->hidden->mixbuf);
54 SDL_free(device->hidden);
55 }
56}
57
58static bool DSP_OpenDevice(SDL_AudioDevice *device)
59{
60 // Make sure fragment size stays a power of 2, or OSS fails.
61 // (I don't know which of these are actually legal values, though...)
62 if (device->spec.channels > 8) {
63 device->spec.channels = 8;
64 } else if (device->spec.channels > 4) {
65 device->spec.channels = 4;
66 } else if (device->spec.channels > 2) {
67 device->spec.channels = 2;
68 }
69
70 // Initialize all variables that we clean on shutdown
71 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
72 if (!device->hidden) {
73 return false;
74 }
75
76 // Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
77 const int flags = ((device->recording) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
78 device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC, 0);
79 if (device->hidden->audio_fd < 0) {
80 return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
81 }
82
83 // Make the file descriptor use blocking i/o with fcntl()
84 {
85 const long ctlflags = fcntl(device->hidden->audio_fd, F_GETFL) & ~O_NONBLOCK;
86 if (fcntl(device->hidden->audio_fd, F_SETFL, ctlflags) < 0) {
87 return SDL_SetError("Couldn't set audio blocking mode");
88 }
89 }
90
91 // Get a list of supported hardware formats
92 int value;
93 if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) {
94 perror("SNDCTL_DSP_GETFMTS");
95 return SDL_SetError("Couldn't get audio format list");
96 }
97
98 // Try for a closest match on audio format
99 int format = 0;
100 SDL_AudioFormat test_format;
101 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
102 while ((test_format = *(closefmts++)) != 0) {
103#ifdef DEBUG_AUDIO
104 fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
105#endif
106 switch (test_format) {
107 case SDL_AUDIO_U8:
108 if (value & AFMT_U8) {
109 format = AFMT_U8;
110 }
111 break;
112 case SDL_AUDIO_S16LE:
113 if (value & AFMT_S16_LE) {
114 format = AFMT_S16_LE;
115 }
116 break;
117 case SDL_AUDIO_S16BE:
118 if (value & AFMT_S16_BE) {
119 format = AFMT_S16_BE;
120 }
121 break;
122
123 default:
124 continue;
125 }
126 break;
127 }
128 if (format == 0) {
129 return SDL_SetError("Couldn't find any hardware audio formats");
130 }
131 device->spec.format = test_format;
132
133 // Set the audio format
134 value = format;
135 if ((ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) ||
136 (value != format)) {
137 perror("SNDCTL_DSP_SETFMT");
138 return SDL_SetError("Couldn't set audio format");
139 }
140
141 // Set the number of channels of output
142 value = device->spec.channels;
143 if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) {
144 perror("SNDCTL_DSP_CHANNELS");
145 return SDL_SetError("Cannot set the number of channels");
146 }
147 device->spec.channels = value;
148
149 // Set the DSP frequency
150 value = device->spec.freq;
151 if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) {
152 perror("SNDCTL_DSP_SPEED");
153 return SDL_SetError("Couldn't set audio frequency");
154 }
155 device->spec.freq = value;
156
157 // Calculate the final parameters for this audio specification
158 SDL_UpdatedAudioDeviceFormat(device);
159
160 /* Determine the power of two of the fragment size
161 Since apps don't control this in SDL3, and this driver only accepts 8, 16
162 bit formats and 1, 2, 4, 8 channels, this should always be a power of 2 already. */
163 SDL_assert(SDL_powerof2(device->buffer_size) == device->buffer_size);
164
165 int frag_spec = 0;
166 while ((0x01U << frag_spec) < device->buffer_size) {
167 frag_spec++;
168 }
169 frag_spec |= 0x00020000; // two fragments, for low latency
170
171 // Set the audio buffering parameters
172#ifdef DEBUG_AUDIO
173 fprintf(stderr, "Requesting %d fragments of size %d\n",
174 (frag_spec >> 16), 1 << (frag_spec & 0xFFFF));
175#endif
176 if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) {
177 perror("SNDCTL_DSP_SETFRAGMENT");
178 }
179#ifdef DEBUG_AUDIO
180 {
181 audio_buf_info info;
182 ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info);
183 fprintf(stderr, "fragments = %d\n", info.fragments);
184 fprintf(stderr, "fragstotal = %d\n", info.fragstotal);
185 fprintf(stderr, "fragsize = %d\n", info.fragsize);
186 fprintf(stderr, "bytes = %d\n", info.bytes);
187 }
188#endif
189
190 // Allocate mixing buffer
191 if (!device->recording) {
192 device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
193 if (!device->hidden->mixbuf) {
194 return false;
195 }
196 SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
197 }
198
199 return true; // We're ready to rock and roll. :-)
200}
201
202static bool DSP_WaitDevice(SDL_AudioDevice *device)
203{
204 const unsigned long ioctlreq = device->recording ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE;
205 struct SDL_PrivateAudioData *h = device->hidden;
206
207 while (!SDL_GetAtomicInt(&device->shutdown)) {
208 audio_buf_info info;
209 const int rc = ioctl(h->audio_fd, ioctlreq, &info);
210 if (rc < 0) {
211 if (errno == EAGAIN) {
212 continue;
213 }
214 // Hmm, not much we can do - abort
215 fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
216 return false;
217 } else if (info.bytes < device->buffer_size) {
218 SDL_Delay(10);
219 } else {
220 break; // ready to go!
221 }
222 }
223
224 return true;
225}
226
227static bool DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
228{
229 struct SDL_PrivateAudioData *h = device->hidden;
230 if (write(h->audio_fd, buffer, buflen) == -1) {
231 perror("Audio write");
232 return false;
233 }
234#ifdef DEBUG_AUDIO
235 fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
236#endif
237 return true;
238}
239
240static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
241{
242 return device->hidden->mixbuf;
243}
244
245static int DSP_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
246{
247 return (int)read(device->hidden->audio_fd, buffer, buflen);
248}
249
250static void DSP_FlushRecording(SDL_AudioDevice *device)
251{
252 struct SDL_PrivateAudioData *h = device->hidden;
253 audio_buf_info info;
254 if (ioctl(h->audio_fd, SNDCTL_DSP_GETISPACE, &info) == 0) {
255 while (info.bytes > 0) {
256 char buf[512];
257 const size_t len = SDL_min(sizeof(buf), info.bytes);
258 const ssize_t br = read(h->audio_fd, buf, len);
259 if (br <= 0) {
260 break;
261 }
262 info.bytes -= br;
263 }
264 }
265}
266
267static bool InitTimeDevicesExist = false;
268static bool look_for_devices_test(int fd)
269{
270 InitTimeDevicesExist = true; // note that _something_ exists.
271 // Don't add to the device list, we're just seeing if any devices exist.
272 return false;
273}
274
275static bool DSP_Init(SDL_AudioDriverImpl *impl)
276{
277 InitTimeDevicesExist = false;
278 SDL_EnumUnixAudioDevices(false, look_for_devices_test);
279 if (!InitTimeDevicesExist) {
280 SDL_SetError("dsp: No such audio device");
281 return false; // maybe try a different backend.
282 }
283
284 impl->DetectDevices = DSP_DetectDevices;
285 impl->OpenDevice = DSP_OpenDevice;
286 impl->WaitDevice = DSP_WaitDevice;
287 impl->PlayDevice = DSP_PlayDevice;
288 impl->GetDeviceBuf = DSP_GetDeviceBuf;
289 impl->CloseDevice = DSP_CloseDevice;
290 impl->WaitRecordingDevice = DSP_WaitDevice;
291 impl->RecordDevice = DSP_RecordDevice;
292 impl->FlushRecording = DSP_FlushRecording;
293
294 impl->HasRecordingSupport = true;
295
296 return true;
297}
298
299AudioBootStrap DSP_bootstrap = {
300 "dsp", "Open Sound System (/dev/dsp)", DSP_Init, false, false
301};
302
303#endif // SDL_AUDIO_DRIVER_OSS
diff --git a/contrib/SDL-3.2.8/src/audio/dsp/SDL_dspaudio.h b/contrib/SDL-3.2.8/src/audio/dsp/SDL_dspaudio.h
new file mode 100644
index 0000000..65dea60
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/dsp/SDL_dspaudio.h
@@ -0,0 +1,37 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_dspaudio_h_
24#define SDL_dspaudio_h_
25
26#include "../SDL_sysaudio.h"
27
28struct SDL_PrivateAudioData
29{
30 // The file descriptor for the audio device
31 int audio_fd;
32
33 // Raw mixing buffer
34 Uint8 *mixbuf;
35};
36
37#endif // SDL_dspaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/dummy/SDL_dummyaudio.c b/contrib/SDL-3.2.8/src/audio/dummy/SDL_dummyaudio.c
new file mode 100644
index 0000000..d0f1a1a
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/dummy/SDL_dummyaudio.c
@@ -0,0 +1,135 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// Output audio to nowhere...
24
25#include "../SDL_sysaudio.h"
26#include "SDL_dummyaudio.h"
27
28#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
29#include <emscripten/emscripten.h>
30#endif
31
32static bool DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device)
33{
34 SDL_Delay(device->hidden->io_delay);
35 return true;
36}
37
38static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
39{
40 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
41 if (!device->hidden) {
42 return false;
43 }
44
45 if (!device->recording) {
46 device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size);
47 if (!device->hidden->mixbuf) {
48 return false;
49 }
50 }
51
52 device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
53
54 const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DUMMY_TIMESCALE);
55 if (hint) {
56 double scale = SDL_atof(hint);
57 if (scale >= 0.0) {
58 device->hidden->io_delay = (Uint32)SDL_round(device->hidden->io_delay * scale);
59 }
60 }
61
62 // on Emscripten without threads, we just fire a repeating timer to consume audio.
63 #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
64 MAIN_THREAD_EM_ASM({
65 var a = Module['SDL3'].dummy_audio;
66 if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
67 a.timers[$0] = setInterval(function() { dynCall('vi', $3, [$4]); }, ($1 / $2) * 1000);
68 }, device->recording ? 1 : 0, device->sample_frames, device->spec.freq, device->recording ? SDL_RecordingAudioThreadIterate : SDL_PlaybackAudioThreadIterate, device);
69 #endif
70
71 return true; // we're good; don't change reported device format.
72}
73
74static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device)
75{
76 if (device->hidden) {
77 // on Emscripten without threads, we just fire a repeating timer to consume audio.
78 #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
79 MAIN_THREAD_EM_ASM({
80 var a = Module['SDL3'].dummy_audio;
81 if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
82 a.timers[$0] = undefined;
83 }, device->recording ? 1 : 0);
84 #endif
85 SDL_free(device->hidden->mixbuf);
86 SDL_free(device->hidden);
87 device->hidden = NULL;
88 }
89}
90
91static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
92{
93 return device->hidden->mixbuf;
94}
95
96static int DUMMYAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
97{
98 // always return a full buffer of silence.
99 SDL_memset(buffer, device->silence_value, buflen);
100 return buflen;
101}
102
103static bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl)
104{
105 impl->OpenDevice = DUMMYAUDIO_OpenDevice;
106 impl->CloseDevice = DUMMYAUDIO_CloseDevice;
107 impl->WaitDevice = DUMMYAUDIO_WaitDevice;
108 impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf;
109 impl->WaitRecordingDevice = DUMMYAUDIO_WaitDevice;
110 impl->RecordDevice = DUMMYAUDIO_RecordDevice;
111
112 impl->OnlyHasDefaultPlaybackDevice = true;
113 impl->OnlyHasDefaultRecordingDevice = true;
114 impl->HasRecordingSupport = true;
115
116 // on Emscripten without threads, we just fire a repeating timer to consume audio.
117 #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
118 MAIN_THREAD_EM_ASM({
119 if (typeof(Module['SDL3']) === 'undefined') {
120 Module['SDL3'] = {};
121 }
122 Module['SDL3'].dummy_audio = {};
123 Module['SDL3'].dummy_audio.timers = [];
124 Module['SDL3'].dummy_audio.timers[0] = undefined;
125 Module['SDL3'].dummy_audio.timers[1] = undefined;
126 });
127 impl->ProvidesOwnCallbackThread = true;
128 #endif
129
130 return true;
131}
132
133AudioBootStrap DUMMYAUDIO_bootstrap = {
134 "dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, true, false
135};
diff --git a/contrib/SDL-3.2.8/src/audio/dummy/SDL_dummyaudio.h b/contrib/SDL-3.2.8/src/audio/dummy/SDL_dummyaudio.h
new file mode 100644
index 0000000..d5e75c7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/dummy/SDL_dummyaudio.h
@@ -0,0 +1,34 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_dummyaudio_h_
24#define SDL_dummyaudio_h_
25
26#include "../SDL_sysaudio.h"
27
28struct SDL_PrivateAudioData
29{
30 Uint8 *mixbuf; // The file descriptor for the audio device
31 Uint32 io_delay; // milliseconds to sleep in WaitDevice.
32};
33
34#endif // SDL_dummyaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/emscripten/SDL_emscriptenaudio.c b/contrib/SDL-3.2.8/src/audio/emscripten/SDL_emscriptenaudio.c
new file mode 100644
index 0000000..55fb5b4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/emscripten/SDL_emscriptenaudio.c
@@ -0,0 +1,359 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
24
25#include "../SDL_sysaudio.h"
26#include "SDL_emscriptenaudio.h"
27
28#include <emscripten/emscripten.h>
29
30// just turn off clang-format for this whole file, this INDENT_OFF stuff on
31// each EM_ASM section is ugly.
32/* *INDENT-OFF* */ // clang-format off
33
34static Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
35{
36 return device->hidden->mixbuf;
37}
38
39static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
40{
41 const int framelen = SDL_AUDIO_FRAMESIZE(device->spec);
42 MAIN_THREAD_EM_ASM({
43 /* Convert incoming buf pointer to a HEAPF32 offset. */
44 #ifdef __wasm64__
45 var buf = $0 / 4;
46 #else
47 var buf = $0 >>> 2;
48 #endif
49
50 var SDL3 = Module['SDL3'];
51 var numChannels = SDL3.audio_playback.currentPlaybackBuffer['numberOfChannels'];
52 for (var c = 0; c < numChannels; ++c) {
53 var channelData = SDL3.audio_playback.currentPlaybackBuffer['getChannelData'](c);
54 if (channelData.length != $1) {
55 throw 'Web Audio playback buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
56 }
57
58 for (var j = 0; j < $1; ++j) {
59 channelData[j] = HEAPF32[buf + (j*numChannels + c)];
60 }
61 }
62 }, buffer, buffer_size / framelen);
63 return true;
64}
65
66
67static void EMSCRIPTENAUDIO_FlushRecording(SDL_AudioDevice *device)
68{
69 // Do nothing, the new data will just be dropped.
70}
71
72static int EMSCRIPTENAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
73{
74 MAIN_THREAD_EM_ASM({
75 var SDL3 = Module['SDL3'];
76 var numChannels = SDL3.audio_recording.currentRecordingBuffer.numberOfChannels;
77 for (var c = 0; c < numChannels; ++c) {
78 var channelData = SDL3.audio_recording.currentRecordingBuffer.getChannelData(c);
79 if (channelData.length != $1) {
80 throw 'Web Audio recording buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
81 }
82
83 if (numChannels == 1) { // fastpath this a little for the common (mono) case.
84 for (var j = 0; j < $1; ++j) {
85 setValue($0 + (j * 4), channelData[j], 'float');
86 }
87 } else {
88 for (var j = 0; j < $1; ++j) {
89 setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
90 }
91 }
92 }
93 }, buffer, (buflen / sizeof(float)) / device->spec.channels);
94
95 return buflen;
96}
97
98static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device)
99{
100 if (!device->hidden) {
101 return;
102 }
103
104 MAIN_THREAD_EM_ASM({
105 var SDL3 = Module['SDL3'];
106 if ($0) {
107 if (SDL3.audio_recording.silenceTimer !== undefined) {
108 clearInterval(SDL3.audio_recording.silenceTimer);
109 }
110 if (SDL3.audio_recording.stream !== undefined) {
111 var tracks = SDL3.audio_recording.stream.getAudioTracks();
112 for (var i = 0; i < tracks.length; i++) {
113 SDL3.audio_recording.stream.removeTrack(tracks[i]);
114 }
115 }
116 if (SDL3.audio_recording.scriptProcessorNode !== undefined) {
117 SDL3.audio_recording.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
118 SDL3.audio_recording.scriptProcessorNode.disconnect();
119 }
120 if (SDL3.audio_recording.mediaStreamNode !== undefined) {
121 SDL3.audio_recording.mediaStreamNode.disconnect();
122 }
123 SDL3.audio_recording = undefined;
124 } else {
125 if (SDL3.audio_playback.scriptProcessorNode != undefined) {
126 SDL3.audio_playback.scriptProcessorNode.disconnect();
127 }
128 if (SDL3.audio_playback.silenceTimer !== undefined) {
129 clearInterval(SDL3.audio_playback.silenceTimer);
130 }
131 SDL3.audio_playback = undefined;
132 }
133 if ((SDL3.audioContext !== undefined) && (SDL3.audio_playback === undefined) && (SDL3.audio_recording === undefined)) {
134 SDL3.audioContext.close();
135 SDL3.audioContext = undefined;
136 }
137 }, device->recording);
138
139 SDL_free(device->hidden->mixbuf);
140 SDL_free(device->hidden);
141 device->hidden = NULL;
142
143 SDL_AudioThreadFinalize(device);
144}
145
146EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall");
147
148static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
149{
150 // based on parts of library_sdl.js
151
152 // create context
153 const bool result = MAIN_THREAD_EM_ASM_INT({
154 if (typeof(Module['SDL3']) === 'undefined') {
155 Module['SDL3'] = {};
156 }
157 var SDL3 = Module['SDL3'];
158 if (!$0) {
159 SDL3.audio_playback = {};
160 } else {
161 SDL3.audio_recording = {};
162 }
163
164 if (!SDL3.audioContext) {
165 if (typeof(AudioContext) !== 'undefined') {
166 SDL3.audioContext = new AudioContext();
167 } else if (typeof(webkitAudioContext) !== 'undefined') {
168 SDL3.audioContext = new webkitAudioContext();
169 }
170 if (SDL3.audioContext) {
171 if ((typeof navigator.userActivation) === 'undefined') {
172 autoResumeAudioContext(SDL3.audioContext);
173 }
174 }
175 }
176 return (SDL3.audioContext !== undefined);
177 }, device->recording);
178
179 if (!result) {
180 return SDL_SetError("Web Audio API is not available!");
181 }
182
183 device->spec.format = SDL_AUDIO_F32; // web audio only supports floats
184
185 // Initialize all variables that we clean on shutdown
186 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
187 if (!device->hidden) {
188 return false;
189 }
190
191 // limit to native freq
192 device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; });
193 device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq) * 2; // double the buffer size, some browsers need more, and we'll just have to live with the latency.
194
195 SDL_UpdatedAudioDeviceFormat(device);
196
197 if (!device->recording) {
198 device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
199 if (!device->hidden->mixbuf) {
200 return false;
201 }
202 SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
203 }
204
205 if (device->recording) {
206 /* The idea is to take the recording media stream, hook it up to an
207 audio graph where we can pass it through a ScriptProcessorNode
208 to access the raw PCM samples and push them to the SDL app's
209 callback. From there, we "process" the audio data into silence
210 and forget about it.
211
212 This should, strictly speaking, use MediaRecorder for recording, but
213 this API is cleaner to use and better supported, and fires a
214 callback whenever there's enough data to fire down into the app.
215 The downside is that we are spending CPU time silencing a buffer
216 that the audiocontext uselessly mixes into any playback. On the
217 upside, both of those things are not only run in native code in
218 the browser, they're probably SIMD code, too. MediaRecorder
219 feels like it's a pretty inefficient tapdance in similar ways,
220 to be honest. */
221
222 MAIN_THREAD_EM_ASM({
223 var SDL3 = Module['SDL3'];
224 var have_microphone = function(stream) {
225 //console.log('SDL audio recording: we have a microphone! Replacing silence callback.');
226 if (SDL3.audio_recording.silenceTimer !== undefined) {
227 clearInterval(SDL3.audio_recording.silenceTimer);
228 SDL3.audio_recording.silenceTimer = undefined;
229 SDL3.audio_recording.silenceBuffer = undefined
230 }
231 SDL3.audio_recording.mediaStreamNode = SDL3.audioContext.createMediaStreamSource(stream);
232 SDL3.audio_recording.scriptProcessorNode = SDL3.audioContext.createScriptProcessor($1, $0, 1);
233 SDL3.audio_recording.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
234 if ((SDL3 === undefined) || (SDL3.audio_recording === undefined)) { return; }
235 audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
236 SDL3.audio_recording.currentRecordingBuffer = audioProcessingEvent.inputBuffer;
237 dynCall('ip', $2, [$3]);
238 };
239 SDL3.audio_recording.mediaStreamNode.connect(SDL3.audio_recording.scriptProcessorNode);
240 SDL3.audio_recording.scriptProcessorNode.connect(SDL3.audioContext.destination);
241 SDL3.audio_recording.stream = stream;
242 };
243
244 var no_microphone = function(error) {
245 //console.log('SDL audio recording: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
246 };
247
248 // we write silence to the audio callback until the microphone is available (user approves use, etc).
249 SDL3.audio_recording.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
250 SDL3.audio_recording.silenceBuffer.getChannelData(0).fill(0.0);
251 var silence_callback = function() {
252 SDL3.audio_recording.currentRecordingBuffer = SDL3.audio_recording.silenceBuffer;
253 dynCall('ip', $2, [$3]);
254 };
255
256 SDL3.audio_recording.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
257
258 if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
259 navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
260 } else if (navigator.webkitGetUserMedia !== undefined) {
261 navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
262 }
263 }, device->spec.channels, device->sample_frames, SDL_RecordingAudioThreadIterate, device);
264 } else {
265 // setup a ScriptProcessorNode
266 MAIN_THREAD_EM_ASM({
267 var SDL3 = Module['SDL3'];
268 SDL3.audio_playback.scriptProcessorNode = SDL3.audioContext['createScriptProcessor']($1, 0, $0);
269 SDL3.audio_playback.scriptProcessorNode['onaudioprocess'] = function (e) {
270 if ((SDL3 === undefined) || (SDL3.audio_playback === undefined)) { return; }
271 // if we're actually running the node, we don't need the fake callback anymore, so kill it.
272 if (SDL3.audio_playback.silenceTimer !== undefined) {
273 clearInterval(SDL3.audio_playback.silenceTimer);
274 SDL3.audio_playback.silenceTimer = undefined;
275 SDL3.audio_playback.silenceBuffer = undefined;
276 }
277 SDL3.audio_playback.currentPlaybackBuffer = e['outputBuffer'];
278 dynCall('ip', $2, [$3]);
279 };
280
281 SDL3.audio_playback.scriptProcessorNode['connect'](SDL3.audioContext['destination']);
282
283 if (SDL3.audioContext.state === 'suspended') { // uhoh, autoplay is blocked.
284 SDL3.audio_playback.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
285 SDL3.audio_playback.silenceBuffer.getChannelData(0).fill(0.0);
286 var silence_callback = function() {
287 if ((typeof navigator.userActivation) !== 'undefined') {
288 if (navigator.userActivation.hasBeenActive) {
289 SDL3.audioContext.resume();
290 }
291 }
292
293 // the buffer that gets filled here just gets ignored, so the app can make progress
294 // and/or avoid flooding audio queues until we can actually play audio.
295 SDL3.audio_playback.currentPlaybackBuffer = SDL3.audio_playback.silenceBuffer;
296 dynCall('ip', $2, [$3]);
297 SDL3.audio_playback.currentPlaybackBuffer = undefined;
298 };
299
300 SDL3.audio_playback.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
301 }
302 }, device->spec.channels, device->sample_frames, SDL_PlaybackAudioThreadIterate, device);
303 }
304
305 return true;
306}
307
308static bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl)
309{
310 bool available, recording_available;
311
312 impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
313 impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
314 impl->GetDeviceBuf = EMSCRIPTENAUDIO_GetDeviceBuf;
315 impl->PlayDevice = EMSCRIPTENAUDIO_PlayDevice;
316 impl->FlushRecording = EMSCRIPTENAUDIO_FlushRecording;
317 impl->RecordDevice = EMSCRIPTENAUDIO_RecordDevice;
318
319 impl->OnlyHasDefaultPlaybackDevice = true;
320
321 // technically, this is just runs in idle time in the main thread, but it's close enough to a "thread" for our purposes.
322 impl->ProvidesOwnCallbackThread = true;
323
324 // check availability
325 available = MAIN_THREAD_EM_ASM_INT({
326 if (typeof(AudioContext) !== 'undefined') {
327 return true;
328 } else if (typeof(webkitAudioContext) !== 'undefined') {
329 return true;
330 }
331 return false;
332 });
333
334 if (!available) {
335 SDL_SetError("No audio context available");
336 }
337
338 recording_available = available && MAIN_THREAD_EM_ASM_INT({
339 if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
340 return true;
341 } else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
342 return true;
343 }
344 return false;
345 });
346
347 impl->HasRecordingSupport = recording_available;
348 impl->OnlyHasDefaultRecordingDevice = recording_available;
349
350 return available;
351}
352
353AudioBootStrap EMSCRIPTENAUDIO_bootstrap = {
354 "emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, false, false
355};
356
357/* *INDENT-ON* */ // clang-format on
358
359#endif // SDL_AUDIO_DRIVER_EMSCRIPTEN
diff --git a/contrib/SDL-3.2.8/src/audio/emscripten/SDL_emscriptenaudio.h b/contrib/SDL-3.2.8/src/audio/emscripten/SDL_emscriptenaudio.h
new file mode 100644
index 0000000..aaf761b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/emscripten/SDL_emscriptenaudio.h
@@ -0,0 +1,33 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_emscriptenaudio_h_
24#define SDL_emscriptenaudio_h_
25
26#include "../SDL_sysaudio.h"
27
28struct SDL_PrivateAudioData
29{
30 Uint8 *mixbuf;
31};
32
33#endif // SDL_emscriptenaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/haiku/SDL_haikuaudio.cc b/contrib/SDL-3.2.8/src/audio/haiku/SDL_haikuaudio.cc
new file mode 100644
index 0000000..730b107
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/haiku/SDL_haikuaudio.cc
@@ -0,0 +1,222 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_HAIKU
24
25// Allow access to the audio stream on Haiku
26
27#include <SoundPlayer.h>
28#include <signal.h>
29
30#include "../../core/haiku/SDL_BeApp.h"
31
32extern "C"
33{
34
35#include "../SDL_sysaudio.h"
36#include "SDL_haikuaudio.h"
37
38}
39
40static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
41{
42 SDL_assert(device->hidden->current_buffer != NULL);
43 SDL_assert(device->hidden->current_buffer_len > 0);
44 *buffer_size = device->hidden->current_buffer_len;
45 return device->hidden->current_buffer;
46}
47
48static bool HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
49{
50 // We already wrote our output right into the BSoundPlayer's callback's stream. Just clean up our stuff.
51 SDL_assert(device->hidden->current_buffer != NULL);
52 SDL_assert(device->hidden->current_buffer_len > 0);
53 device->hidden->current_buffer = NULL;
54 device->hidden->current_buffer_len = 0;
55 return true;
56}
57
58// The Haiku callback for handling the audio buffer
59static void FillSound(void *data, void *stream, size_t len, const media_raw_audio_format & format)
60{
61 SDL_AudioDevice *device = (SDL_AudioDevice *)data;
62 SDL_assert(device->hidden->current_buffer == NULL);
63 SDL_assert(device->hidden->current_buffer_len == 0);
64 device->hidden->current_buffer = (Uint8 *) stream;
65 device->hidden->current_buffer_len = (int) len;
66 SDL_PlaybackAudioThreadIterate(device);
67}
68
69static void HAIKUAUDIO_CloseDevice(SDL_AudioDevice *device)
70{
71 if (device->hidden) {
72 if (device->hidden->audio_obj) {
73 device->hidden->audio_obj->Stop();
74 delete device->hidden->audio_obj;
75 }
76 delete device->hidden;
77 device->hidden = NULL;
78 SDL_AudioThreadFinalize(device);
79 }
80}
81
82
83static const int sig_list[] = {
84 SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGWINCH, 0
85};
86
87static inline void MaskSignals(sigset_t * omask)
88{
89 sigset_t mask;
90 int i;
91
92 sigemptyset(&mask);
93 for (i = 0; sig_list[i]; ++i) {
94 sigaddset(&mask, sig_list[i]);
95 }
96 sigprocmask(SIG_BLOCK, &mask, omask);
97}
98
99static inline void UnmaskSignals(sigset_t * omask)
100{
101 sigprocmask(SIG_SETMASK, omask, NULL);
102}
103
104
105static bool HAIKUAUDIO_OpenDevice(SDL_AudioDevice *device)
106{
107 // Initialize all variables that we clean on shutdown
108 device->hidden = new SDL_PrivateAudioData;
109 if (!device->hidden) {
110 return false;
111 }
112 SDL_zerop(device->hidden);
113
114 // Parse the audio format and fill the Be raw audio format
115 media_raw_audio_format format;
116 SDL_zero(format);
117 format.byte_order = B_MEDIA_LITTLE_ENDIAN;
118 format.frame_rate = (float) device->spec.freq;
119 format.channel_count = device->spec.channels; // !!! FIXME: support > 2?
120
121 SDL_AudioFormat test_format;
122 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
123 while ((test_format = *(closefmts++)) != 0) {
124 switch (test_format) {
125 case SDL_AUDIO_S8:
126 format.format = media_raw_audio_format::B_AUDIO_CHAR;
127 break;
128
129 case SDL_AUDIO_U8:
130 format.format = media_raw_audio_format::B_AUDIO_UCHAR;
131 break;
132
133 case SDL_AUDIO_S16LE:
134 format.format = media_raw_audio_format::B_AUDIO_SHORT;
135 break;
136
137 case SDL_AUDIO_S16BE:
138 format.format = media_raw_audio_format::B_AUDIO_SHORT;
139 format.byte_order = B_MEDIA_BIG_ENDIAN;
140 break;
141
142 case SDL_AUDIO_S32LE:
143 format.format = media_raw_audio_format::B_AUDIO_INT;
144 break;
145
146 case SDL_AUDIO_S32BE:
147 format.format = media_raw_audio_format::B_AUDIO_INT;
148 format.byte_order = B_MEDIA_BIG_ENDIAN;
149 break;
150
151 case SDL_AUDIO_F32LE:
152 format.format = media_raw_audio_format::B_AUDIO_FLOAT;
153 break;
154
155 case SDL_AUDIO_F32BE:
156 format.format = media_raw_audio_format::B_AUDIO_FLOAT;
157 format.byte_order = B_MEDIA_BIG_ENDIAN;
158 break;
159
160 default:
161 continue;
162 }
163 break;
164 }
165
166 if (!test_format) { // shouldn't happen, but just in case...
167 return SDL_SetError("HAIKU: Unsupported audio format");
168 }
169 device->spec.format = test_format;
170
171 // Calculate the final parameters for this audio specification
172 SDL_UpdatedAudioDeviceFormat(device);
173
174 format.buffer_size = device->buffer_size;
175
176 // Subscribe to the audio stream (creates a new thread)
177 sigset_t omask;
178 MaskSignals(&omask);
179 device->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio",
180 FillSound, NULL, device);
181 UnmaskSignals(&omask);
182
183 if (device->hidden->audio_obj->Start() == B_NO_ERROR) {
184 device->hidden->audio_obj->SetHasData(true);
185 } else {
186 return SDL_SetError("Unable to start Haiku audio");
187 }
188
189 return true; // We're running!
190}
191
192static void HAIKUAUDIO_Deinitialize(void)
193{
194 SDL_QuitBeApp();
195}
196
197static bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl)
198{
199 if (!SDL_InitBeApp()) {
200 return false;
201 }
202
203 // Set the function pointers
204 impl->OpenDevice = HAIKUAUDIO_OpenDevice;
205 impl->GetDeviceBuf = HAIKUAUDIO_GetDeviceBuf;
206 impl->PlayDevice = HAIKUAUDIO_PlayDevice;
207 impl->CloseDevice = HAIKUAUDIO_CloseDevice;
208 impl->Deinitialize = HAIKUAUDIO_Deinitialize;
209 impl->ProvidesOwnCallbackThread = true;
210 impl->OnlyHasDefaultPlaybackDevice = true;
211
212 return true;
213}
214
215
216extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; }
217
218AudioBootStrap HAIKUAUDIO_bootstrap = {
219 "haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, false, false
220};
221
222#endif // SDL_AUDIO_DRIVER_HAIKU
diff --git a/contrib/SDL-3.2.8/src/audio/haiku/SDL_haikuaudio.h b/contrib/SDL-3.2.8/src/audio/haiku/SDL_haikuaudio.h
new file mode 100644
index 0000000..9547546
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/haiku/SDL_haikuaudio.h
@@ -0,0 +1,35 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_haikuaudio_h_
24#define SDL_haikuaudio_h_
25
26#include "../SDL_sysaudio.h"
27
28struct SDL_PrivateAudioData
29{
30 BSoundPlayer *audio_obj;
31 Uint8 *current_buffer;
32 int current_buffer_len;
33};
34
35#endif // SDL_haikuaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/jack/SDL_jackaudio.c b/contrib/SDL-3.2.8/src/audio/jack/SDL_jackaudio.c
new file mode 100644
index 0000000..3ae5137
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/jack/SDL_jackaudio.c
@@ -0,0 +1,435 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_JACK
24
25#include "../SDL_sysaudio.h"
26#include "SDL_jackaudio.h"
27#include "../../thread/SDL_systhread.h"
28
29static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...);
30static int (*JACK_jack_client_close)(jack_client_t *);
31static void (*JACK_jack_on_shutdown)(jack_client_t *, JackShutdownCallback, void *);
32static int (*JACK_jack_activate)(jack_client_t *);
33static int (*JACK_jack_deactivate)(jack_client_t *);
34static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t);
35static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *);
36static void (*JACK_jack_free)(void *);
37static const char **(*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long);
38static jack_nframes_t (*JACK_jack_get_sample_rate)(jack_client_t *);
39static jack_nframes_t (*JACK_jack_get_buffer_size)(jack_client_t *);
40static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long);
41static jack_port_t *(*JACK_jack_port_by_name)(jack_client_t *, const char *);
42static const char *(*JACK_jack_port_name)(const jack_port_t *);
43static const char *(*JACK_jack_port_type)(const jack_port_t *);
44static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *);
45static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *);
46static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *);
47static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *);
48
49static bool load_jack_syms(void);
50
51#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
52
53static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
54static SDL_SharedObject *jack_handle = NULL;
55
56// !!! FIXME: this is copy/pasted in several places now
57static bool load_jack_sym(const char *fn, void **addr)
58{
59 *addr = SDL_LoadFunction(jack_handle, fn);
60 if (!*addr) {
61 // Don't call SDL_SetError(): SDL_LoadFunction already did.
62 return false;
63 }
64
65 return true;
66}
67
68// cast funcs to char* first, to please GCC's strict aliasing rules.
69#define SDL_JACK_SYM(x) \
70 if (!load_jack_sym(#x, (void **)(char *)&JACK_##x)) \
71 return false
72
73static void UnloadJackLibrary(void)
74{
75 if (jack_handle) {
76 SDL_UnloadObject(jack_handle);
77 jack_handle = NULL;
78 }
79}
80
81static bool LoadJackLibrary(void)
82{
83 bool result = true;
84 if (!jack_handle) {
85 jack_handle = SDL_LoadObject(jack_library);
86 if (!jack_handle) {
87 result = false;
88 // Don't call SDL_SetError(): SDL_LoadObject already did.
89 } else {
90 result = load_jack_syms();
91 if (!result) {
92 UnloadJackLibrary();
93 }
94 }
95 }
96 return result;
97}
98
99#else
100
101#define SDL_JACK_SYM(x) JACK_##x = x
102
103static void UnloadJackLibrary(void)
104{
105}
106
107static bool LoadJackLibrary(void)
108{
109 load_jack_syms();
110 return true;
111}
112
113#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC
114
115static bool load_jack_syms(void)
116{
117 SDL_JACK_SYM(jack_client_open);
118 SDL_JACK_SYM(jack_client_close);
119 SDL_JACK_SYM(jack_on_shutdown);
120 SDL_JACK_SYM(jack_activate);
121 SDL_JACK_SYM(jack_deactivate);
122 SDL_JACK_SYM(jack_port_get_buffer);
123 SDL_JACK_SYM(jack_port_unregister);
124 SDL_JACK_SYM(jack_free);
125 SDL_JACK_SYM(jack_get_ports);
126 SDL_JACK_SYM(jack_get_sample_rate);
127 SDL_JACK_SYM(jack_get_buffer_size);
128 SDL_JACK_SYM(jack_port_register);
129 SDL_JACK_SYM(jack_port_by_name);
130 SDL_JACK_SYM(jack_port_name);
131 SDL_JACK_SYM(jack_port_type);
132 SDL_JACK_SYM(jack_connect);
133 SDL_JACK_SYM(jack_set_process_callback);
134 SDL_JACK_SYM(jack_set_sample_rate_callback);
135 SDL_JACK_SYM(jack_set_buffer_size_callback);
136
137 return true;
138}
139
140static void jackShutdownCallback(void *arg) // JACK went away; device is lost.
141{
142 SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg);
143}
144
145static int jackSampleRateCallback(jack_nframes_t nframes, void *arg)
146{
147 //SDL_Log("JACK Sample Rate Callback! %d", (int) nframes);
148 SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
149 SDL_AudioSpec newspec;
150 SDL_copyp(&newspec, &device->spec);
151 newspec.freq = (int) nframes;
152 if (!SDL_AudioDeviceFormatChanged(device, &newspec, device->sample_frames)) {
153 SDL_AudioDeviceDisconnected(device);
154 }
155 return 0;
156}
157
158static int jackBufferSizeCallback(jack_nframes_t nframes, void *arg)
159{
160 //SDL_Log("JACK Buffer Size Callback! %d", (int) nframes);
161 SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
162 SDL_AudioSpec newspec;
163 SDL_copyp(&newspec, &device->spec);
164 if (!SDL_AudioDeviceFormatChanged(device, &newspec, (int) nframes)) {
165 SDL_AudioDeviceDisconnected(device);
166 }
167 return 0;
168}
169
170static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
171{
172 SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
173 SDL_PlaybackAudioThreadIterate((SDL_AudioDevice *)arg);
174 return 0;
175}
176
177static bool JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen)
178{
179 const float *buffer = (float *) ui8buffer;
180 jack_port_t **ports = device->hidden->sdlports;
181 const int total_channels = device->spec.channels;
182 const int total_frames = device->sample_frames;
183 const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
184
185 for (int channelsi = 0; channelsi < total_channels; channelsi++) {
186 float *dst = (float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
187 if (dst) {
188 const float *src = buffer + channelsi;
189 for (int framesi = 0; framesi < total_frames; framesi++) {
190 *(dst++) = *src;
191 src += total_channels;
192 }
193 }
194 }
195
196 return true;
197}
198
199static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
200{
201 return (Uint8 *)device->hidden->iobuffer;
202}
203
204static int jackProcessRecordingCallback(jack_nframes_t nframes, void *arg)
205{
206 SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
207 SDL_RecordingAudioThreadIterate((SDL_AudioDevice *)arg);
208 return 0;
209}
210
211static int JACK_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
212{
213 float *buffer = (float *) vbuffer;
214 jack_port_t **ports = device->hidden->sdlports;
215 const int total_channels = device->spec.channels;
216 const int total_frames = device->sample_frames;
217 const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
218
219 for (int channelsi = 0; channelsi < total_channels; channelsi++) {
220 const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
221 if (src) {
222 float *dst = buffer + channelsi;
223 for (int framesi = 0; framesi < total_frames; framesi++) {
224 *dst = *(src++);
225 dst += total_channels;
226 }
227 }
228 }
229
230 return buflen;
231}
232
233static void JACK_FlushRecording(SDL_AudioDevice *device)
234{
235 // do nothing, the data will just be replaced next callback.
236}
237
238static void JACK_CloseDevice(SDL_AudioDevice *device)
239{
240 if (device->hidden) {
241 if (device->hidden->client) {
242 JACK_jack_deactivate(device->hidden->client);
243
244 if (device->hidden->sdlports) {
245 const int channels = device->spec.channels;
246 int i;
247 for (i = 0; i < channels; i++) {
248 JACK_jack_port_unregister(device->hidden->client, device->hidden->sdlports[i]);
249 }
250 SDL_free(device->hidden->sdlports);
251 }
252
253 JACK_jack_client_close(device->hidden->client);
254 }
255
256 SDL_free(device->hidden->iobuffer);
257 SDL_free(device->hidden);
258 device->hidden = NULL;
259
260 SDL_AudioThreadFinalize(device);
261 }
262}
263
264// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc)
265static const char *GetJackAppName(void)
266{
267 return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
268}
269
270static bool JACK_OpenDevice(SDL_AudioDevice *device)
271{
272 /* Note that JACK uses "output" for recording devices (they output audio
273 data to us) and "input" for playback (we input audio data to them).
274 Likewise, SDL's playback port will be "output" (we write data out)
275 and recording will be "input" (we read data in). */
276 const bool recording = device->recording;
277 const unsigned long sysportflags = recording ? JackPortIsOutput : JackPortIsInput;
278 const unsigned long sdlportflags = recording ? JackPortIsInput : JackPortIsOutput;
279 const JackProcessCallback callback = recording ? jackProcessRecordingCallback : jackProcessPlaybackCallback;
280 const char *sdlportstr = recording ? "input" : "output";
281 const char **devports = NULL;
282 int *audio_ports;
283 jack_client_t *client = NULL;
284 jack_status_t status;
285 int channels = 0;
286 int ports = 0;
287 int i;
288
289 // Initialize all variables that we clean on shutdown
290 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
291 if (!device->hidden) {
292 return false;
293 }
294
295 client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL);
296 device->hidden->client = client;
297 if (!client) {
298 return SDL_SetError("Can't open JACK client");
299 }
300
301 devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
302 if (!devports || !devports[0]) {
303 return SDL_SetError("No physical JACK ports available");
304 }
305
306 while (devports[++ports]) {
307 // spin to count devports
308 }
309
310 // Filter out non-audio ports
311 audio_ports = SDL_calloc(ports, sizeof(*audio_ports));
312 for (i = 0; i < ports; i++) {
313 const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]);
314 const char *type = JACK_jack_port_type(dport);
315 const int len = SDL_strlen(type);
316 // See if type ends with "audio"
317 if (len >= 5 && !SDL_memcmp(type + len - 5, "audio", 5)) {
318 audio_ports[channels++] = i;
319 }
320 }
321 if (channels == 0) {
322 SDL_free(audio_ports);
323 return SDL_SetError("No physical JACK ports available");
324 }
325
326 // Jack pretty much demands what it wants.
327 device->spec.format = SDL_AUDIO_F32;
328 device->spec.freq = JACK_jack_get_sample_rate(client);
329 device->spec.channels = channels;
330 device->sample_frames = JACK_jack_get_buffer_size(client);
331
332 SDL_UpdatedAudioDeviceFormat(device);
333
334 if (!recording) {
335 device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size);
336 if (!device->hidden->iobuffer) {
337 SDL_free(audio_ports);
338 return false;
339 }
340 }
341
342 // Build SDL's ports, which we will connect to the device ports.
343 device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *));
344 if (!device->hidden->sdlports) {
345 SDL_free(audio_ports);
346 return false;
347 }
348
349 for (i = 0; i < channels; i++) {
350 char portname[32];
351 (void)SDL_snprintf(portname, sizeof(portname), "sdl_jack_%s_%d", sdlportstr, i);
352 device->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0);
353 if (device->hidden->sdlports[i] == NULL) {
354 SDL_free(audio_ports);
355 return SDL_SetError("jack_port_register failed");
356 }
357 }
358
359 if (JACK_jack_set_buffer_size_callback(client, jackBufferSizeCallback, device) != 0) {
360 SDL_free(audio_ports);
361 return SDL_SetError("JACK: Couldn't set buffer size callback");
362 } else if (JACK_jack_set_sample_rate_callback(client, jackSampleRateCallback, device) != 0) {
363 SDL_free(audio_ports);
364 return SDL_SetError("JACK: Couldn't set sample rate callback");
365 } else if (JACK_jack_set_process_callback(client, callback, device) != 0) {
366 SDL_free(audio_ports);
367 return SDL_SetError("JACK: Couldn't set process callback");
368 }
369
370 JACK_jack_on_shutdown(client, jackShutdownCallback, device);
371
372 if (JACK_jack_activate(client) != 0) {
373 SDL_free(audio_ports);
374 return SDL_SetError("Failed to activate JACK client");
375 }
376
377 // once activated, we can connect all the ports.
378 for (i = 0; i < channels; i++) {
379 const char *sdlport = JACK_jack_port_name(device->hidden->sdlports[i]);
380 const char *srcport = recording ? devports[audio_ports[i]] : sdlport;
381 const char *dstport = recording ? sdlport : devports[audio_ports[i]];
382 if (JACK_jack_connect(client, srcport, dstport) != 0) {
383 SDL_free(audio_ports);
384 return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
385 }
386 }
387
388 // don't need these anymore.
389 JACK_jack_free(devports);
390 SDL_free(audio_ports);
391
392 // We're ready to rock and roll. :-)
393 return true;
394}
395
396static void JACK_Deinitialize(void)
397{
398 UnloadJackLibrary();
399}
400
401static bool JACK_Init(SDL_AudioDriverImpl *impl)
402{
403 if (!LoadJackLibrary()) {
404 return false;
405 } else {
406 // Make sure a JACK server is running and available.
407 jack_status_t status;
408 jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
409 if (!client) {
410 UnloadJackLibrary();
411 return SDL_SetError("Can't open JACK client");
412 }
413 JACK_jack_client_close(client);
414 }
415
416 impl->OpenDevice = JACK_OpenDevice;
417 impl->GetDeviceBuf = JACK_GetDeviceBuf;
418 impl->PlayDevice = JACK_PlayDevice;
419 impl->CloseDevice = JACK_CloseDevice;
420 impl->Deinitialize = JACK_Deinitialize;
421 impl->RecordDevice = JACK_RecordDevice;
422 impl->FlushRecording = JACK_FlushRecording;
423 impl->OnlyHasDefaultPlaybackDevice = true;
424 impl->OnlyHasDefaultRecordingDevice = true;
425 impl->HasRecordingSupport = true;
426 impl->ProvidesOwnCallbackThread = true;
427
428 return true;
429}
430
431AudioBootStrap JACK_bootstrap = {
432 "jack", "JACK Audio Connection Kit", JACK_Init, false, false
433};
434
435#endif // SDL_AUDIO_DRIVER_JACK
diff --git a/contrib/SDL-3.2.8/src/audio/jack/SDL_jackaudio.h b/contrib/SDL-3.2.8/src/audio/jack/SDL_jackaudio.h
new file mode 100644
index 0000000..4f972d5
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/jack/SDL_jackaudio.h
@@ -0,0 +1,35 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#ifndef SDL_jackaudio_h_
22#define SDL_jackaudio_h_
23
24#include <jack/jack.h>
25
26#include "../SDL_sysaudio.h"
27
28struct SDL_PrivateAudioData
29{
30 jack_client_t *client;
31 jack_port_t **sdlports;
32 float *iobuffer;
33};
34
35#endif // SDL_jackaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/n3ds/SDL_n3dsaudio.c b/contrib/SDL-3.2.8/src/audio/n3ds/SDL_n3dsaudio.c
new file mode 100644
index 0000000..780b06c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/n3ds/SDL_n3dsaudio.c
@@ -0,0 +1,287 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_N3DS
24
25// N3DS Audio driver
26
27#include "../SDL_sysaudio.h"
28#include "SDL_n3dsaudio.h"
29
30#define N3DSAUDIO_DRIVER_NAME "n3ds"
31
32static dspHookCookie dsp_hook;
33static SDL_AudioDevice *audio_device;
34
35// fully local functions related to the wavebufs / DSP, not the same as the `device->lock` SDL_Mutex!
36static SDL_INLINE void contextLock(SDL_AudioDevice *device)
37{
38 LightLock_Lock(&device->hidden->lock);
39}
40
41static SDL_INLINE void contextUnlock(SDL_AudioDevice *device)
42{
43 LightLock_Unlock(&device->hidden->lock);
44}
45
46static void N3DSAUD_DspHook(DSP_HookType hook)
47{
48 if (hook == DSPHOOK_ONCANCEL) {
49 contextLock(audio_device);
50 audio_device->hidden->isCancelled = true;
51 SDL_AudioDeviceDisconnected(audio_device);
52 CondVar_Broadcast(&audio_device->hidden->cv);
53 contextUnlock(audio_device);
54 }
55}
56
57static void AudioFrameFinished(void *vdevice)
58{
59 bool shouldBroadcast = false;
60 unsigned i;
61 SDL_AudioDevice *device = (SDL_AudioDevice *)vdevice;
62
63 contextLock(device);
64
65 for (i = 0; i < NUM_BUFFERS; i++) {
66 if (device->hidden->waveBuf[i].status == NDSP_WBUF_DONE) {
67 device->hidden->waveBuf[i].status = NDSP_WBUF_FREE;
68 shouldBroadcast = true;
69 }
70 }
71
72 if (shouldBroadcast) {
73 CondVar_Broadcast(&device->hidden->cv);
74 }
75
76 contextUnlock(device);
77}
78
79static bool N3DSAUDIO_OpenDevice(SDL_AudioDevice *device)
80{
81 Result ndsp_init_res;
82 Uint8 *data_vaddr;
83 float mix[12];
84
85 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
86 if (!device->hidden) {
87 return false;
88 }
89
90 // Initialise the DSP service
91 ndsp_init_res = ndspInit();
92 if (R_FAILED(ndsp_init_res)) {
93 if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) {
94 return SDL_SetError("DSP init failed: dspfirm.cdc missing!");
95 } else {
96 return SDL_SetError("DSP init failed. Error code: 0x%lX", ndsp_init_res);
97 }
98 }
99
100 // Initialise internal state
101 LightLock_Init(&device->hidden->lock);
102 CondVar_Init(&device->hidden->cv);
103
104 if (device->spec.channels > 2) {
105 device->spec.channels = 2;
106 }
107
108 Uint32 format = 0;
109 SDL_AudioFormat test_format;
110 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
111 while ((test_format = *(closefmts++)) != 0) {
112 if (test_format == SDL_AUDIO_S8) { // Signed 8-bit audio supported
113 format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM8 : NDSP_FORMAT_MONO_PCM8;
114 break;
115 } else if (test_format == SDL_AUDIO_S16) { // Signed 16-bit audio supported
116 format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16;
117 break;
118 }
119 }
120
121 if (!test_format) { // shouldn't happen, but just in case...
122 return SDL_SetError("No supported audio format found.");
123 }
124
125 device->spec.format = test_format;
126
127 // Update the fragment size as size in bytes
128 SDL_UpdatedAudioDeviceFormat(device);
129
130 // Allocate mixing buffer
131 if (device->buffer_size >= SDL_MAX_UINT32 / 2) {
132 return SDL_SetError("Mixing buffer is too large.");
133 }
134
135 device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
136 if (!device->hidden->mixbuf) {
137 return false;
138 }
139
140 SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
141
142 data_vaddr = (Uint8 *)linearAlloc(device->buffer_size * NUM_BUFFERS);
143 if (!data_vaddr) {
144 return SDL_OutOfMemory();
145 }
146
147 SDL_memset(data_vaddr, 0, device->buffer_size * NUM_BUFFERS);
148 DSP_FlushDataCache(data_vaddr, device->buffer_size * NUM_BUFFERS);
149
150 device->hidden->nextbuf = 0;
151
152 ndspChnReset(0);
153
154 ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
155 ndspChnSetRate(0, device->spec.freq);
156 ndspChnSetFormat(0, format);
157
158 SDL_zeroa(mix);
159 mix[0] = mix[1] = 1.0f;
160 ndspChnSetMix(0, mix);
161
162 SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
163
164 const int sample_frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
165 for (unsigned i = 0; i < NUM_BUFFERS; i++) {
166 device->hidden->waveBuf[i].data_vaddr = data_vaddr;
167 device->hidden->waveBuf[i].nsamples = device->buffer_size / sample_frame_size;
168 data_vaddr += device->buffer_size;
169 }
170
171 // Setup callback
172 audio_device = device;
173 ndspSetCallback(AudioFrameFinished, device);
174 dspHook(&dsp_hook, N3DSAUD_DspHook);
175
176 return true;
177}
178
179static bool N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
180{
181 contextLock(device);
182
183 const size_t nextbuf = device->hidden->nextbuf;
184
185 if (device->hidden->isCancelled ||
186 device->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) {
187 contextUnlock(device);
188 return true; // !!! FIXME: is this a fatal error? If so, this should return false.
189 }
190
191 device->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;
192
193 contextUnlock(device);
194
195 SDL_memcpy((void *)device->hidden->waveBuf[nextbuf].data_vaddr, buffer, buflen);
196 DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, buflen);
197
198 ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]);
199
200 return true;
201}
202
203static bool N3DSAUDIO_WaitDevice(SDL_AudioDevice *device)
204{
205 contextLock(device);
206 while (!device->hidden->isCancelled && !SDL_GetAtomicInt(&device->shutdown) &&
207 device->hidden->waveBuf[device->hidden->nextbuf].status != NDSP_WBUF_FREE) {
208 CondVar_Wait(&device->hidden->cv, &device->hidden->lock);
209 }
210 contextUnlock(device);
211 return true;
212}
213
214static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
215{
216 return device->hidden->mixbuf;
217}
218
219static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *device)
220{
221 if (!device->hidden) {
222 return;
223 }
224
225 contextLock(device);
226
227 dspUnhook(&dsp_hook);
228 ndspSetCallback(NULL, NULL);
229
230 if (!device->hidden->isCancelled) {
231 ndspChnReset(0);
232 SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
233 CondVar_Broadcast(&device->hidden->cv);
234 }
235
236 contextUnlock(device);
237
238 ndspExit();
239
240 if (device->hidden->waveBuf[0].data_vaddr) {
241 linearFree((void *)device->hidden->waveBuf[0].data_vaddr);
242 }
243
244 if (device->hidden->mixbuf) {
245 SDL_free(device->hidden->mixbuf);
246 device->hidden->mixbuf = NULL;
247 }
248
249 SDL_free(device->hidden);
250 device->hidden = NULL;
251}
252
253static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *device)
254{
255 s32 current_priority = 0x30;
256 svcGetThreadPriority(&current_priority, CUR_THREAD_HANDLE);
257 current_priority--;
258 // 0x18 is reserved for video, 0x30 is the default for main thread
259 current_priority = SDL_clamp(current_priority, 0x19, 0x2F);
260 svcSetThreadPriority(CUR_THREAD_HANDLE, current_priority);
261}
262
263static bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl)
264{
265 impl->OpenDevice = N3DSAUDIO_OpenDevice;
266 impl->PlayDevice = N3DSAUDIO_PlayDevice;
267 impl->WaitDevice = N3DSAUDIO_WaitDevice;
268 impl->GetDeviceBuf = N3DSAUDIO_GetDeviceBuf;
269 impl->CloseDevice = N3DSAUDIO_CloseDevice;
270 impl->ThreadInit = N3DSAUDIO_ThreadInit;
271 impl->OnlyHasDefaultPlaybackDevice = true;
272
273 // Should be possible, but micInit would fail
274 impl->HasRecordingSupport = false;
275
276 return true;
277}
278
279AudioBootStrap N3DSAUDIO_bootstrap = {
280 N3DSAUDIO_DRIVER_NAME,
281 "SDL N3DS audio driver",
282 N3DSAUDIO_Init,
283 false,
284 false
285};
286
287#endif // SDL_AUDIO_DRIVER_N3DS
diff --git a/contrib/SDL-3.2.8/src/audio/n3ds/SDL_n3dsaudio.h b/contrib/SDL-3.2.8/src/audio/n3ds/SDL_n3dsaudio.h
new file mode 100644
index 0000000..c9ae4f8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/n3ds/SDL_n3dsaudio.h
@@ -0,0 +1,40 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_n3dsaudio_h
23#define SDL_n3dsaudio_h
24
25#include <3ds.h>
26
27#define NUM_BUFFERS 3 // -- Minimum 2!
28
29struct SDL_PrivateAudioData
30{
31 // Speaker data
32 Uint8 *mixbuf;
33 Uint32 nextbuf;
34 ndspWaveBuf waveBuf[NUM_BUFFERS];
35 LightLock lock;
36 CondVar cv;
37 bool isCancelled;
38};
39
40#endif // SDL_n3dsaudio_h
diff --git a/contrib/SDL-3.2.8/src/audio/netbsd/SDL_netbsdaudio.c b/contrib/SDL-3.2.8/src/audio/netbsd/SDL_netbsdaudio.c
new file mode 100644
index 0000000..26060d3
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/netbsd/SDL_netbsdaudio.c
@@ -0,0 +1,328 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_NETBSD
24
25// Driver for native NetBSD audio(4).
26
27#include <errno.h>
28#include <unistd.h>
29#include <fcntl.h>
30#include <sys/time.h>
31#include <sys/ioctl.h>
32#include <sys/stat.h>
33#include <sys/types.h>
34#include <sys/audioio.h>
35
36#include "../../core/unix/SDL_poll.h"
37#include "../SDL_audiodev_c.h"
38#include "SDL_netbsdaudio.h"
39
40//#define DEBUG_AUDIO
41
42static void NETBSDAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
43{
44 SDL_EnumUnixAudioDevices(false, NULL);
45}
46
47static void NETBSDAUDIO_Status(SDL_AudioDevice *device)
48{
49#ifdef DEBUG_AUDIO
50 /* *INDENT-OFF* */ // clang-format off
51 audio_info_t info;
52 const struct audio_prinfo *prinfo;
53
54 if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
55 fprintf(stderr, "AUDIO_GETINFO failed.\n");
56 return;
57 }
58
59 prinfo = device->recording ? &info.record : &info.play;
60
61 fprintf(stderr, "\n"
62 "[%s info]\n"
63 "buffer size : %d bytes\n"
64 "sample rate : %i Hz\n"
65 "channels : %i\n"
66 "precision : %i-bit\n"
67 "encoding : 0x%x\n"
68 "seek : %i\n"
69 "sample count : %i\n"
70 "EOF count : %i\n"
71 "paused : %s\n"
72 "error occurred : %s\n"
73 "waiting : %s\n"
74 "active : %s\n"
75 "",
76 device->recording ? "record" : "play",
77 prinfo->buffer_size,
78 prinfo->sample_rate,
79 prinfo->channels,
80 prinfo->precision,
81 prinfo->encoding,
82 prinfo->seek,
83 prinfo->samples,
84 prinfo->eof,
85 prinfo->pause ? "yes" : "no",
86 prinfo->error ? "yes" : "no",
87 prinfo->waiting ? "yes" : "no",
88 prinfo->active ? "yes" : "no");
89
90 fprintf(stderr, "\n"
91 "[audio info]\n"
92 "monitor_gain : %i\n"
93 "hw block size : %d bytes\n"
94 "hi watermark : %i\n"
95 "lo watermark : %i\n"
96 "audio mode : %s\n"
97 "",
98 info.monitor_gain,
99 info.blocksize,
100 info.hiwat, info.lowat,
101 (info.mode == AUMODE_PLAY) ? "PLAY"
102 : (info.mode == AUMODE_RECORD) ? "RECORD"
103 : (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?"));
104
105 fprintf(stderr, "\n"
106 "[audio spec]\n"
107 "format : 0x%x\n"
108 "size : %u\n"
109 "",
110 device->spec.format,
111 device->buffer_size);
112 /* *INDENT-ON* */ // clang-format on
113
114#endif // DEBUG_AUDIO
115}
116
117static bool NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device)
118{
119 const bool recording = device->recording;
120 while (!SDL_GetAtomicInt(&device->shutdown)) {
121 audio_info_t info;
122 const int rc = ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info);
123 if (rc < 0) {
124 if (errno == EAGAIN) {
125 continue;
126 }
127 // Hmm, not much we can do - abort
128 fprintf(stderr, "netbsdaudio WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
129 return false;
130 }
131 const size_t remain = (size_t)((recording ? info.record.seek : info.play.seek) * SDL_AUDIO_BYTESIZE(device->spec.format));
132 if (!recording && (remain >= device->buffer_size)) {
133 SDL_Delay(10);
134 } else if (recording && (remain < device->buffer_size)) {
135 SDL_Delay(10);
136 } else {
137 break; // ready to go!
138 }
139 }
140
141 return true;
142}
143
144static bool NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
145{
146 struct SDL_PrivateAudioData *h = device->hidden;
147 const int written = write(h->audio_fd, buffer, buflen);
148 if (written != buflen) { // Treat even partial writes as fatal errors.
149 return false;
150 }
151
152#ifdef DEBUG_AUDIO
153 fprintf(stderr, "Wrote %d bytes of audio data\n", written);
154#endif
155 return true;
156}
157
158static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
159{
160 return device->hidden->mixbuf;
161}
162
163static int NETBSDAUDIO_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
164{
165 Uint8 *buffer = (Uint8 *)vbuffer;
166 const int br = read(device->hidden->audio_fd, buffer, buflen);
167 if (br == -1) {
168 // Non recoverable error has occurred. It should be reported!!!
169 perror("audio");
170 return -1;
171 }
172
173#ifdef DEBUG_AUDIO
174 fprintf(stderr, "Recorded %d bytes of audio data\n", br);
175#endif
176 return br;
177}
178
179static void NETBSDAUDIO_FlushRecording(SDL_AudioDevice *device)
180{
181 struct SDL_PrivateAudioData *h = device->hidden;
182 audio_info_t info;
183 if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) == 0) {
184 size_t remain = (size_t)(info.record.seek * SDL_AUDIO_BYTESIZE(device->spec.format));
185 while (remain > 0) {
186 char buf[512];
187 const size_t len = SDL_min(sizeof(buf), remain);
188 const ssize_t br = read(h->audio_fd, buf, len);
189 if (br <= 0) {
190 break;
191 }
192 remain -= br;
193 }
194 }
195}
196
197static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *device)
198{
199 if (device->hidden) {
200 if (device->hidden->audio_fd >= 0) {
201 close(device->hidden->audio_fd);
202 }
203 SDL_free(device->hidden->mixbuf);
204 SDL_free(device->hidden);
205 device->hidden = NULL;
206 }
207}
208
209static bool NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device)
210{
211 const bool recording = device->recording;
212 int encoding = AUDIO_ENCODING_NONE;
213 audio_info_t info, hwinfo;
214 struct audio_prinfo *prinfo = recording ? &info.record : &info.play;
215
216 // Initialize all variables that we clean on shutdown
217 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
218 if (!device->hidden) {
219 return false;
220 }
221
222 // Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
223 const int flags = ((device->recording) ? O_RDONLY : O_WRONLY);
224 device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC);
225 if (device->hidden->audio_fd < 0) {
226 return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
227 }
228
229 AUDIO_INITINFO(&info);
230
231#ifdef AUDIO_GETFORMAT // Introduced in NetBSD 9.0
232 if (ioctl(device->hidden->audio_fd, AUDIO_GETFORMAT, &hwinfo) != -1) {
233 // Use the device's native sample rate so the kernel doesn't have to resample.
234 device->spec.freq = recording ? hwinfo.record.sample_rate : hwinfo.play.sample_rate;
235 }
236#endif
237
238 prinfo->sample_rate = device->spec.freq;
239 prinfo->channels = device->spec.channels;
240
241 SDL_AudioFormat test_format;
242 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
243 while ((test_format = *(closefmts++)) != 0) {
244 switch (test_format) {
245 case SDL_AUDIO_U8:
246 encoding = AUDIO_ENCODING_ULINEAR;
247 break;
248 case SDL_AUDIO_S8:
249 encoding = AUDIO_ENCODING_SLINEAR;
250 break;
251 case SDL_AUDIO_S16LE:
252 encoding = AUDIO_ENCODING_SLINEAR_LE;
253 break;
254 case SDL_AUDIO_S16BE:
255 encoding = AUDIO_ENCODING_SLINEAR_BE;
256 break;
257 case SDL_AUDIO_S32LE:
258 encoding = AUDIO_ENCODING_SLINEAR_LE;
259 break;
260 case SDL_AUDIO_S32BE:
261 encoding = AUDIO_ENCODING_SLINEAR_BE;
262 break;
263 default:
264 continue;
265 }
266 break;
267 }
268
269 if (!test_format) {
270 return SDL_SetError("%s: Unsupported audio format", "netbsd");
271 }
272 prinfo->encoding = encoding;
273 prinfo->precision = SDL_AUDIO_BITSIZE(test_format);
274
275 info.hiwat = 5;
276 info.lowat = 3;
277 if (ioctl(device->hidden->audio_fd, AUDIO_SETINFO, &info) < 0) {
278 return SDL_SetError("AUDIO_SETINFO failed for %s: %s", device->name, strerror(errno));
279 }
280
281 if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
282 return SDL_SetError("AUDIO_GETINFO failed for %s: %s", device->name, strerror(errno));
283 }
284
285 // Final spec used for the device.
286 device->spec.format = test_format;
287 device->spec.freq = prinfo->sample_rate;
288 device->spec.channels = prinfo->channels;
289
290 SDL_UpdatedAudioDeviceFormat(device);
291
292 if (!recording) {
293 // Allocate mixing buffer
294 device->hidden->mixlen = device->buffer_size;
295 device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen);
296 if (!device->hidden->mixbuf) {
297 return false;
298 }
299 SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
300 }
301
302 NETBSDAUDIO_Status(device);
303
304 return true; // We're ready to rock and roll. :-)
305}
306
307static bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl)
308{
309 impl->DetectDevices = NETBSDAUDIO_DetectDevices;
310 impl->OpenDevice = NETBSDAUDIO_OpenDevice;
311 impl->WaitDevice = NETBSDAUDIO_WaitDevice;
312 impl->PlayDevice = NETBSDAUDIO_PlayDevice;
313 impl->GetDeviceBuf = NETBSDAUDIO_GetDeviceBuf;
314 impl->CloseDevice = NETBSDAUDIO_CloseDevice;
315 impl->WaitRecordingDevice = NETBSDAUDIO_WaitDevice;
316 impl->RecordDevice = NETBSDAUDIO_RecordDevice;
317 impl->FlushRecording = NETBSDAUDIO_FlushRecording;
318
319 impl->HasRecordingSupport = true;
320
321 return true;
322}
323
324AudioBootStrap NETBSDAUDIO_bootstrap = {
325 "netbsd", "NetBSD audio", NETBSDAUDIO_Init, false, false
326};
327
328#endif // SDL_AUDIO_DRIVER_NETBSD
diff --git a/contrib/SDL-3.2.8/src/audio/netbsd/SDL_netbsdaudio.h b/contrib/SDL-3.2.8/src/audio/netbsd/SDL_netbsdaudio.h
new file mode 100644
index 0000000..dcdc6f4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/netbsd/SDL_netbsdaudio.h
@@ -0,0 +1,44 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_netbsdaudio_h_
24#define SDL_netbsdaudio_h_
25
26#include "../SDL_sysaudio.h"
27
28struct SDL_PrivateAudioData
29{
30 // The file descriptor for the audio device
31 int audio_fd;
32
33 // Raw mixing buffer
34 Uint8 *mixbuf;
35 int mixlen;
36
37 // Support for audio timing using a timer, in addition to SDL_IOReady()
38 float frame_ticks;
39 float next_frame;
40};
41
42#define FUDGE_TICKS 10 // The scheduler overhead ticks per frame
43
44#endif // SDL_netbsdaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/openslES/SDL_openslES.c b/contrib/SDL-3.2.8/src/audio/openslES/SDL_openslES.c
new file mode 100644
index 0000000..4d5b3bd
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/openslES/SDL_openslES.c
@@ -0,0 +1,807 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_OPENSLES
24
25// For more discussion of low latency audio on Android, see this:
26// https://googlesamples.github.io/android-audio-high-performance/guides/opensl_es.html
27
28#include "../SDL_sysaudio.h"
29#include "SDL_openslES.h"
30
31#include "../../core/android/SDL_android.h"
32#include <SLES/OpenSLES.h>
33#include <SLES/OpenSLES_Android.h>
34#include <android/log.h>
35
36
37#define NUM_BUFFERS 2 // -- Don't lower this!
38
39struct SDL_PrivateAudioData
40{
41 Uint8 *mixbuff;
42 int next_buffer;
43 Uint8 *pmixbuff[NUM_BUFFERS];
44 SDL_Semaphore *playsem;
45};
46
47#if 0
48#define LOG_TAG "SDL_openslES"
49#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
50#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
51//#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
52#define LOGV(...)
53#else
54#define LOGE(...)
55#define LOGI(...)
56#define LOGV(...)
57#endif
58
59/*
60#define SL_SPEAKER_FRONT_LEFT ((SLuint32) 0x00000001)
61#define SL_SPEAKER_FRONT_RIGHT ((SLuint32) 0x00000002)
62#define SL_SPEAKER_FRONT_CENTER ((SLuint32) 0x00000004)
63#define SL_SPEAKER_LOW_FREQUENCY ((SLuint32) 0x00000008)
64#define SL_SPEAKER_BACK_LEFT ((SLuint32) 0x00000010)
65#define SL_SPEAKER_BACK_RIGHT ((SLuint32) 0x00000020)
66#define SL_SPEAKER_FRONT_LEFT_OF_CENTER ((SLuint32) 0x00000040)
67#define SL_SPEAKER_FRONT_RIGHT_OF_CENTER ((SLuint32) 0x00000080)
68#define SL_SPEAKER_BACK_CENTER ((SLuint32) 0x00000100)
69#define SL_SPEAKER_SIDE_LEFT ((SLuint32) 0x00000200)
70#define SL_SPEAKER_SIDE_RIGHT ((SLuint32) 0x00000400)
71#define SL_SPEAKER_TOP_CENTER ((SLuint32) 0x00000800)
72#define SL_SPEAKER_TOP_FRONT_LEFT ((SLuint32) 0x00001000)
73#define SL_SPEAKER_TOP_FRONT_CENTER ((SLuint32) 0x00002000)
74#define SL_SPEAKER_TOP_FRONT_RIGHT ((SLuint32) 0x00004000)
75#define SL_SPEAKER_TOP_BACK_LEFT ((SLuint32) 0x00008000)
76#define SL_SPEAKER_TOP_BACK_CENTER ((SLuint32) 0x00010000)
77#define SL_SPEAKER_TOP_BACK_RIGHT ((SLuint32) 0x00020000)
78*/
79#define SL_ANDROID_SPEAKER_STEREO (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT)
80#define SL_ANDROID_SPEAKER_QUAD (SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
81#define SL_ANDROID_SPEAKER_5DOT1 (SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY)
82#define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT)
83
84// engine interfaces
85static SLObjectItf engineObject = NULL;
86static SLEngineItf engineEngine = NULL;
87
88// output mix interfaces
89static SLObjectItf outputMixObject = NULL;
90
91// buffer queue player interfaces
92static SLObjectItf bqPlayerObject = NULL;
93static SLPlayItf bqPlayerPlay = NULL;
94static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
95#if 0
96static SLVolumeItf bqPlayerVolume;
97#endif
98
99// recorder interfaces
100static SLObjectItf recorderObject = NULL;
101static SLRecordItf recorderRecord = NULL;
102static SLAndroidSimpleBufferQueueItf recorderBufferQueue = NULL;
103
104#if 0
105static const char *sldevaudiorecorderstr = "SLES Audio Recorder";
106static const char *sldevaudioplayerstr = "SLES Audio Player";
107
108#define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr
109#define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr
110static void OPENSLES_DetectDevices( int recording )
111{
112 LOGI( "openSLES_DetectDevices()" );
113 if ( recording )
114 addfn( SLES_DEV_AUDIO_RECORDER );
115 else
116 addfn( SLES_DEV_AUDIO_PLAYER );
117}
118#endif
119
120static void OPENSLES_DestroyEngine(void)
121{
122 LOGI("OPENSLES_DestroyEngine()");
123
124 // destroy output mix object, and invalidate all associated interfaces
125 if (outputMixObject != NULL) {
126 (*outputMixObject)->Destroy(outputMixObject);
127 outputMixObject = NULL;
128 }
129
130 // destroy engine object, and invalidate all associated interfaces
131 if (engineObject != NULL) {
132 (*engineObject)->Destroy(engineObject);
133 engineObject = NULL;
134 engineEngine = NULL;
135 }
136}
137
138static bool OPENSLES_CreateEngine(void)
139{
140 const SLInterfaceID ids[1] = { SL_IID_VOLUME };
141 const SLboolean req[1] = { SL_BOOLEAN_FALSE };
142 SLresult result;
143
144 LOGI("openSLES_CreateEngine()");
145
146 // create engine
147 result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
148 if (SL_RESULT_SUCCESS != result) {
149 LOGE("slCreateEngine failed: %d", result);
150 goto error;
151 }
152 LOGI("slCreateEngine OK");
153
154 // realize the engine
155 result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
156 if (SL_RESULT_SUCCESS != result) {
157 LOGE("RealizeEngine failed: %d", result);
158 goto error;
159 }
160 LOGI("RealizeEngine OK");
161
162 // get the engine interface, which is needed in order to create other objects
163 result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
164 if (SL_RESULT_SUCCESS != result) {
165 LOGE("EngineGetInterface failed: %d", result);
166 goto error;
167 }
168 LOGI("EngineGetInterface OK");
169
170 // create output mix
171 result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
172 if (SL_RESULT_SUCCESS != result) {
173 LOGE("CreateOutputMix failed: %d", result);
174 goto error;
175 }
176 LOGI("CreateOutputMix OK");
177
178 // realize the output mix
179 result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
180 if (SL_RESULT_SUCCESS != result) {
181 LOGE("RealizeOutputMix failed: %d", result);
182 goto error;
183 }
184 return true;
185
186error:
187 OPENSLES_DestroyEngine();
188 return false;
189}
190
191// this callback handler is called every time a buffer finishes recording
192static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
193{
194 struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
195
196 LOGV("SLES: Recording Callback");
197 SDL_SignalSemaphore(audiodata->playsem);
198}
199
200static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device)
201{
202 struct SDL_PrivateAudioData *audiodata = device->hidden;
203 SLresult result;
204
205 // stop recording
206 if (recorderRecord != NULL) {
207 result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
208 if (SL_RESULT_SUCCESS != result) {
209 LOGE("SetRecordState stopped: %d", result);
210 }
211 }
212
213 // destroy audio recorder object, and invalidate all associated interfaces
214 if (recorderObject != NULL) {
215 (*recorderObject)->Destroy(recorderObject);
216 recorderObject = NULL;
217 recorderRecord = NULL;
218 recorderBufferQueue = NULL;
219 }
220
221 if (audiodata->playsem) {
222 SDL_DestroySemaphore(audiodata->playsem);
223 audiodata->playsem = NULL;
224 }
225
226 if (audiodata->mixbuff) {
227 SDL_free(audiodata->mixbuff);
228 }
229}
230
231// !!! FIXME: make this non-blocking!
232static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
233{
234 SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
235}
236
237static bool OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device)
238{
239 struct SDL_PrivateAudioData *audiodata = device->hidden;
240 SLDataFormat_PCM format_pcm;
241 SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
242 SLDataSink audioSnk;
243 SLDataLocator_IODevice loc_dev;
244 SLDataSource audioSrc;
245 const SLInterfaceID ids[1] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
246 const SLboolean req[1] = { SL_BOOLEAN_TRUE };
247 SLresult result;
248 int i;
249
250 // !!! FIXME: make this non-blocking!
251 {
252 SDL_AtomicInt permission_response;
253 SDL_SetAtomicInt(&permission_response, 0);
254 if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
255 return false;
256 }
257
258 while (SDL_GetAtomicInt(&permission_response) == 0) {
259 SDL_Delay(10);
260 }
261
262 if (SDL_GetAtomicInt(&permission_response) < 0) {
263 LOGE("This app doesn't have RECORD_AUDIO permission");
264 return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
265 }
266 }
267
268 // Just go with signed 16-bit audio as it's the most compatible
269 device->spec.format = SDL_AUDIO_S16;
270 device->spec.channels = 1;
271 //device->spec.freq = SL_SAMPLINGRATE_16 / 1000;*/
272
273 // Update the fragment size as size in bytes
274 SDL_UpdatedAudioDeviceFormat(device);
275
276 LOGI("Try to open %u hz %u bit %u channels %s samples %u",
277 device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
278 device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
279
280 // configure audio source
281 loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
282 loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
283 loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
284 loc_dev.device = NULL;
285 audioSrc.pLocator = &loc_dev;
286 audioSrc.pFormat = NULL;
287
288 // configure audio sink
289 loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
290 loc_bufq.numBuffers = NUM_BUFFERS;
291
292 format_pcm.formatType = SL_DATAFORMAT_PCM;
293 format_pcm.numChannels = device->spec.channels;
294 format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
295 format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
296 format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
297 format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
298 format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
299
300 audioSnk.pLocator = &loc_bufq;
301 audioSnk.pFormat = &format_pcm;
302
303 // create audio recorder
304 // (requires the RECORD_AUDIO permission)
305 result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, ids, req);
306 if (SL_RESULT_SUCCESS != result) {
307 LOGE("CreateAudioRecorder failed: %d", result);
308 goto failed;
309 }
310
311 // realize the recorder
312 result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
313 if (SL_RESULT_SUCCESS != result) {
314 LOGE("RealizeAudioPlayer failed: %d", result);
315 goto failed;
316 }
317
318 // get the record interface
319 result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord);
320 if (SL_RESULT_SUCCESS != result) {
321 LOGE("SL_IID_RECORD interface get failed: %d", result);
322 goto failed;
323 }
324
325 // get the buffer queue interface
326 result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue);
327 if (SL_RESULT_SUCCESS != result) {
328 LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
329 goto failed;
330 }
331
332 // register callback on the buffer queue
333 // context is '(SDL_PrivateAudioData *)device->hidden'
334 result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, device->hidden);
335 if (SL_RESULT_SUCCESS != result) {
336 LOGE("RegisterCallback failed: %d", result);
337 goto failed;
338 }
339
340 // Create the audio buffer semaphore
341 audiodata->playsem = SDL_CreateSemaphore(0);
342 if (!audiodata->playsem) {
343 LOGE("cannot create Semaphore!");
344 goto failed;
345 }
346
347 // Create the sound buffers
348 audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
349 if (!audiodata->mixbuff) {
350 LOGE("mixbuffer allocate - out of memory");
351 goto failed;
352 }
353
354 for (i = 0; i < NUM_BUFFERS; i++) {
355 audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
356 }
357
358 // in case already recording, stop recording and clear buffer queue
359 result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
360 if (SL_RESULT_SUCCESS != result) {
361 LOGE("Record set state failed: %d", result);
362 goto failed;
363 }
364
365 // enqueue empty buffers to be filled by the recorder
366 for (i = 0; i < NUM_BUFFERS; i++) {
367 result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[i], device->buffer_size);
368 if (SL_RESULT_SUCCESS != result) {
369 LOGE("Record enqueue buffers failed: %d", result);
370 goto failed;
371 }
372 }
373
374 // start recording
375 result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);
376 if (SL_RESULT_SUCCESS != result) {
377 LOGE("Record set state failed: %d", result);
378 goto failed;
379 }
380
381 return true;
382
383failed:
384 return SDL_SetError("Open device failed!");
385}
386
387// this callback handler is called every time a buffer finishes playing
388static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
389{
390 struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
391
392 LOGV("SLES: Playback Callback");
393 SDL_SignalSemaphore(audiodata->playsem);
394}
395
396static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device)
397{
398 struct SDL_PrivateAudioData *audiodata = device->hidden;
399
400 // set the player's state to 'stopped'
401 if (bqPlayerPlay != NULL) {
402 const SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
403 if (SL_RESULT_SUCCESS != result) {
404 LOGE("SetPlayState stopped failed: %d", result);
405 }
406 }
407
408 // destroy buffer queue audio player object, and invalidate all associated interfaces
409 if (bqPlayerObject != NULL) {
410 (*bqPlayerObject)->Destroy(bqPlayerObject);
411
412 bqPlayerObject = NULL;
413 bqPlayerPlay = NULL;
414 bqPlayerBufferQueue = NULL;
415 }
416
417 if (audiodata->playsem) {
418 SDL_DestroySemaphore(audiodata->playsem);
419 audiodata->playsem = NULL;
420 }
421
422 if (audiodata->mixbuff) {
423 SDL_free(audiodata->mixbuff);
424 }
425}
426
427static bool OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device)
428{
429 /* If we want to add floating point audio support (requires API level 21)
430 it can be done as described here:
431 https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point
432 */
433 if (SDL_GetAndroidSDKVersion() >= 21) {
434 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
435 SDL_AudioFormat test_format;
436 while ((test_format = *(closefmts++)) != 0) {
437 if (SDL_AUDIO_ISSIGNED(test_format)) {
438 break;
439 }
440 }
441
442 if (!test_format) {
443 // Didn't find a compatible format :
444 LOGI("No compatible audio format, using signed 16-bit audio");
445 test_format = SDL_AUDIO_S16;
446 }
447 device->spec.format = test_format;
448 } else {
449 // Just go with signed 16-bit audio as it's the most compatible
450 device->spec.format = SDL_AUDIO_S16;
451 }
452
453 // Update the fragment size as size in bytes
454 SDL_UpdatedAudioDeviceFormat(device);
455
456 LOGI("Try to open %u hz %s %u bit %u channels %s samples %u",
457 device->spec.freq, SDL_AUDIO_ISFLOAT(device->spec.format) ? "float" : "pcm", SDL_AUDIO_BITSIZE(device->spec.format),
458 device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
459
460 // configure audio source
461 SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
462 loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
463 loc_bufq.numBuffers = NUM_BUFFERS;
464
465 SLDataFormat_PCM format_pcm;
466 format_pcm.formatType = SL_DATAFORMAT_PCM;
467 format_pcm.numChannels = device->spec.channels;
468 format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
469 format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
470 format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
471
472 if (SDL_AUDIO_ISBIGENDIAN(device->spec.format)) {
473 format_pcm.endianness = SL_BYTEORDER_BIGENDIAN;
474 } else {
475 format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
476 }
477
478 switch (device->spec.channels) {
479 case 1:
480 format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT;
481 break;
482 case 2:
483 format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO;
484 break;
485 case 3:
486 format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_FRONT_CENTER;
487 break;
488 case 4:
489 format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD;
490 break;
491 case 5:
492 format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
493 break;
494 case 6:
495 format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1;
496 break;
497 case 7:
498 format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
499 break;
500 case 8:
501 format_pcm.channelMask = SL_ANDROID_SPEAKER_7DOT1;
502 break;
503 default:
504 // Unknown number of channels, fall back to stereo
505 device->spec.channels = 2;
506 format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
507 break;
508 }
509
510 SLDataSink audioSnk;
511 SLDataSource audioSrc;
512 audioSrc.pFormat = (void *)&format_pcm;
513
514 SLAndroidDataFormat_PCM_EX format_pcm_ex;
515 if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
516 // Copy all setup into PCM EX structure
517 format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
518 format_pcm_ex.endianness = format_pcm.endianness;
519 format_pcm_ex.channelMask = format_pcm.channelMask;
520 format_pcm_ex.numChannels = format_pcm.numChannels;
521 format_pcm_ex.sampleRate = format_pcm.samplesPerSec;
522 format_pcm_ex.bitsPerSample = format_pcm.bitsPerSample;
523 format_pcm_ex.containerSize = format_pcm.containerSize;
524 format_pcm_ex.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
525 audioSrc.pFormat = (void *)&format_pcm_ex;
526 }
527
528 audioSrc.pLocator = &loc_bufq;
529
530 // configure audio sink
531 SLDataLocator_OutputMix loc_outmix;
532 loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
533 loc_outmix.outputMix = outputMixObject;
534 audioSnk.pLocator = &loc_outmix;
535 audioSnk.pFormat = NULL;
536
537 // create audio player
538 const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
539 const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
540 SLresult result;
541 result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
542 if (SL_RESULT_SUCCESS != result) {
543 LOGE("CreateAudioPlayer failed: %d", result);
544 goto failed;
545 }
546
547 // realize the player
548 result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
549 if (SL_RESULT_SUCCESS != result) {
550 LOGE("RealizeAudioPlayer failed: %d", result);
551 goto failed;
552 }
553
554 // get the play interface
555 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
556 if (SL_RESULT_SUCCESS != result) {
557 LOGE("SL_IID_PLAY interface get failed: %d", result);
558 goto failed;
559 }
560
561 // get the buffer queue interface
562 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue);
563 if (SL_RESULT_SUCCESS != result) {
564 LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
565 goto failed;
566 }
567
568 // register callback on the buffer queue
569 // context is '(SDL_PrivateAudioData *)device->hidden'
570 result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, device->hidden);
571 if (SL_RESULT_SUCCESS != result) {
572 LOGE("RegisterCallback failed: %d", result);
573 goto failed;
574 }
575
576#if 0
577 // get the volume interface
578 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
579 if (SL_RESULT_SUCCESS != result) {
580 LOGE("SL_IID_VOLUME interface get failed: %d", result);
581 // goto failed;
582 }
583#endif
584
585 struct SDL_PrivateAudioData *audiodata = device->hidden;
586
587 // Create the audio buffer semaphore
588 audiodata->playsem = SDL_CreateSemaphore(NUM_BUFFERS - 1);
589 if (!audiodata->playsem) {
590 LOGE("cannot create Semaphore!");
591 goto failed;
592 }
593
594 // Create the sound buffers
595 audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
596 if (!audiodata->mixbuff) {
597 LOGE("mixbuffer allocate - out of memory");
598 goto failed;
599 }
600
601 for (int i = 0; i < NUM_BUFFERS; i++) {
602 audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
603 }
604
605 // set the player's state to playing
606 result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
607 if (SL_RESULT_SUCCESS != result) {
608 LOGE("Play set state failed: %d", result);
609 goto failed;
610 }
611
612 return true;
613
614failed:
615 return false;
616}
617
618static bool OPENSLES_OpenDevice(SDL_AudioDevice *device)
619{
620 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
621 if (!device->hidden) {
622 return false;
623 }
624
625 if (device->recording) {
626 LOGI("OPENSLES_OpenDevice() for recording");
627 return OPENSLES_CreatePCMRecorder(device);
628 } else {
629 bool ret;
630 LOGI("OPENSLES_OpenDevice() for playback");
631 ret = OPENSLES_CreatePCMPlayer(device);
632 if (!ret) {
633 // Another attempt to open the device with a lower frequency
634 if (device->spec.freq > 48000) {
635 OPENSLES_DestroyPCMPlayer(device);
636 device->spec.freq = 48000;
637 ret = OPENSLES_CreatePCMPlayer(device);
638 }
639 }
640
641 if (!ret) {
642 return SDL_SetError("Open device failed!");
643 }
644 }
645
646 return true;
647}
648
649static bool OPENSLES_WaitDevice(SDL_AudioDevice *device)
650{
651 struct SDL_PrivateAudioData *audiodata = device->hidden;
652
653 LOGV("OPENSLES_WaitDevice()");
654
655 while (!SDL_GetAtomicInt(&device->shutdown)) {
656 // this semaphore won't fire when the app is in the background (OPENSLES_PauseDevices was called).
657 if (SDL_WaitSemaphoreTimeout(audiodata->playsem, 100)) {
658 return true; // semaphore was signaled, let's go!
659 }
660 // Still waiting on the semaphore (or the system), check other things then wait again.
661 }
662 return true;
663}
664
665static bool OPENSLES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
666{
667 struct SDL_PrivateAudioData *audiodata = device->hidden;
668
669 LOGV("======OPENSLES_PlayDevice()======");
670
671 // Queue it up
672 const SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, buflen);
673
674 audiodata->next_buffer++;
675 if (audiodata->next_buffer >= NUM_BUFFERS) {
676 audiodata->next_buffer = 0;
677 }
678
679 // If Enqueue fails, callback won't be called.
680 // Post the semaphore, not to run out of buffer
681 if (SL_RESULT_SUCCESS != result) {
682 SDL_SignalSemaphore(audiodata->playsem);
683 }
684
685 return true;
686}
687
688/// n playn sem
689// getbuf 0 - 1
690// fill buff 0 - 1
691// play 0 - 0 1
692// wait 1 0 0
693// getbuf 1 0 0
694// fill buff 1 0 0
695// play 0 0 0
696// wait
697//
698// okay..
699
700static Uint8 *OPENSLES_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
701{
702 struct SDL_PrivateAudioData *audiodata = device->hidden;
703
704 LOGV("OPENSLES_GetDeviceBuf()");
705 return audiodata->pmixbuff[audiodata->next_buffer];
706}
707
708static int OPENSLES_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
709{
710 struct SDL_PrivateAudioData *audiodata = device->hidden;
711
712 // Copy it to the output buffer
713 SDL_assert(buflen == device->buffer_size);
714 SDL_memcpy(buffer, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
715
716 // Re-enqueue the buffer
717 const SLresult result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
718 if (SL_RESULT_SUCCESS != result) {
719 LOGE("Record enqueue buffers failed: %d", result);
720 return -1;
721 }
722
723 audiodata->next_buffer++;
724 if (audiodata->next_buffer >= NUM_BUFFERS) {
725 audiodata->next_buffer = 0;
726 }
727
728 return device->buffer_size;
729}
730
731static void OPENSLES_CloseDevice(SDL_AudioDevice *device)
732{
733 // struct SDL_PrivateAudioData *audiodata = device->hidden;
734 if (device->hidden) {
735 if (device->recording) {
736 LOGI("OPENSLES_CloseDevice() for recording");
737 OPENSLES_DestroyPCMRecorder(device);
738 } else {
739 LOGI("OPENSLES_CloseDevice() for playing");
740 OPENSLES_DestroyPCMPlayer(device);
741 }
742
743 SDL_free(device->hidden);
744 device->hidden = NULL;
745 }
746}
747
748static bool OPENSLES_Init(SDL_AudioDriverImpl *impl)
749{
750 LOGI("OPENSLES_Init() called");
751
752 if (!OPENSLES_CreateEngine()) {
753 return false;
754 }
755
756 LOGI("OPENSLES_Init() - set pointers");
757
758 // Set the function pointers
759 // impl->DetectDevices = OPENSLES_DetectDevices;
760 impl->ThreadInit = Android_AudioThreadInit;
761 impl->OpenDevice = OPENSLES_OpenDevice;
762 impl->WaitDevice = OPENSLES_WaitDevice;
763 impl->PlayDevice = OPENSLES_PlayDevice;
764 impl->GetDeviceBuf = OPENSLES_GetDeviceBuf;
765 impl->WaitRecordingDevice = OPENSLES_WaitDevice;
766 impl->RecordDevice = OPENSLES_RecordDevice;
767 impl->CloseDevice = OPENSLES_CloseDevice;
768 impl->Deinitialize = OPENSLES_DestroyEngine;
769
770 // and the capabilities
771 impl->HasRecordingSupport = true;
772 impl->OnlyHasDefaultPlaybackDevice = true;
773 impl->OnlyHasDefaultRecordingDevice = true;
774
775 LOGI("OPENSLES_Init() - success");
776
777 // this audio target is available.
778 return true;
779}
780
781AudioBootStrap OPENSLES_bootstrap = {
782 "openslES", "OpenSL ES audio driver", OPENSLES_Init, false, false
783};
784
785void OPENSLES_ResumeDevices(void)
786{
787 if (bqPlayerPlay != NULL) {
788 // set the player's state to 'playing'
789 SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
790 if (SL_RESULT_SUCCESS != result) {
791 LOGE("OPENSLES_ResumeDevices failed: %d", result);
792 }
793 }
794}
795
796void OPENSLES_PauseDevices(void)
797{
798 if (bqPlayerPlay != NULL) {
799 // set the player's state to 'paused'
800 SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
801 if (SL_RESULT_SUCCESS != result) {
802 LOGE("OPENSLES_PauseDevices failed: %d", result);
803 }
804 }
805}
806
807#endif // SDL_AUDIO_DRIVER_OPENSLES
diff --git a/contrib/SDL-3.2.8/src/audio/openslES/SDL_openslES.h b/contrib/SDL-3.2.8/src/audio/openslES/SDL_openslES.h
new file mode 100644
index 0000000..0ae2664
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/openslES/SDL_openslES.h
@@ -0,0 +1,38 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_openslesaudio_h_
24#define SDL_openslesaudio_h_
25
26#ifdef SDL_AUDIO_DRIVER_OPENSLES
27
28extern void OPENSLES_ResumeDevices(void);
29extern void OPENSLES_PauseDevices(void);
30
31#else
32
33#define OPENSLES_ResumeDevices()
34#define OPENSLES_PauseDevices()
35
36#endif
37
38#endif // SDL_openslesaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/pipewire/SDL_pipewire.c b/contrib/SDL-3.2.8/src/audio/pipewire/SDL_pipewire.c
new file mode 100644
index 0000000..e3f9439
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/pipewire/SDL_pipewire.c
@@ -0,0 +1,1349 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_AUDIO_DRIVER_PIPEWIRE
25
26#include "SDL_pipewire.h"
27
28#include <pipewire/extensions/metadata.h>
29#include <spa/param/audio/format-utils.h>
30#include <spa/utils/json.h>
31
32/*
33 * This seems to be a sane lower limit as Pipewire
34 * uses it in several of it's own modules.
35 */
36#define PW_MIN_SAMPLES 32 // About 0.67ms at 48kHz
37#define PW_BASE_CLOCK_RATE 48000
38
39#define PW_POD_BUFFER_LENGTH 1024
40#define PW_THREAD_NAME_BUFFER_LENGTH 128
41#define PW_MAX_IDENTIFIER_LENGTH 256
42
43enum PW_READY_FLAGS
44{
45 PW_READY_FLAG_BUFFER_ADDED = 0x1,
46 PW_READY_FLAG_STREAM_READY = 0x2,
47 PW_READY_FLAG_ALL_PREOPEN_BITS = 0x3,
48 PW_READY_FLAG_OPEN_COMPLETE = 0x4,
49 PW_READY_FLAG_ALL_BITS = 0x7
50};
51
52#define PW_ID_TO_HANDLE(x) (void *)((uintptr_t)x)
53#define PW_HANDLE_TO_ID(x) (uint32_t)((uintptr_t)x)
54
55static bool pipewire_initialized = false;
56
57// Pipewire entry points
58static const char *(*PIPEWIRE_pw_get_library_version)(void);
59static void (*PIPEWIRE_pw_init)(int *, char ***);
60static void (*PIPEWIRE_pw_deinit)(void);
61static struct pw_main_loop *(*PIPEWIRE_pw_main_loop_new)(const struct spa_dict *loop);
62static struct pw_loop *(*PIPEWIRE_pw_main_loop_get_loop)(struct pw_main_loop *loop);
63static int (*PIPEWIRE_pw_main_loop_run)(struct pw_main_loop *loop);
64static int (*PIPEWIRE_pw_main_loop_quit)(struct pw_main_loop *loop);
65static void(*PIPEWIRE_pw_main_loop_destroy)(struct pw_main_loop *loop);
66static struct pw_thread_loop *(*PIPEWIRE_pw_thread_loop_new)(const char *, const struct spa_dict *);
67static void (*PIPEWIRE_pw_thread_loop_destroy)(struct pw_thread_loop *);
68static void (*PIPEWIRE_pw_thread_loop_stop)(struct pw_thread_loop *);
69static struct pw_loop *(*PIPEWIRE_pw_thread_loop_get_loop)(struct pw_thread_loop *);
70static void (*PIPEWIRE_pw_thread_loop_lock)(struct pw_thread_loop *);
71static void (*PIPEWIRE_pw_thread_loop_unlock)(struct pw_thread_loop *);
72static void (*PIPEWIRE_pw_thread_loop_signal)(struct pw_thread_loop *, bool);
73static void (*PIPEWIRE_pw_thread_loop_wait)(struct pw_thread_loop *);
74static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *);
75static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t);
76static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *);
77static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t);
78static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *);
79static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *);
80static void (*PIPEWIRE_pw_proxy_destroy)(struct pw_proxy *);
81static int (*PIPEWIRE_pw_core_disconnect)(struct pw_core *);
82static struct pw_stream *(*PIPEWIRE_pw_stream_new_simple)(struct pw_loop *, const char *, struct pw_properties *,
83 const struct pw_stream_events *, void *);
84static void (*PIPEWIRE_pw_stream_destroy)(struct pw_stream *);
85static int (*PIPEWIRE_pw_stream_connect)(struct pw_stream *, enum pw_direction, uint32_t, enum pw_stream_flags,
86 const struct spa_pod **, uint32_t);
87static enum pw_stream_state (*PIPEWIRE_pw_stream_get_state)(struct pw_stream *stream, const char **error);
88static struct pw_buffer *(*PIPEWIRE_pw_stream_dequeue_buffer)(struct pw_stream *);
89static int (*PIPEWIRE_pw_stream_queue_buffer)(struct pw_stream *, struct pw_buffer *);
90static struct pw_properties *(*PIPEWIRE_pw_properties_new)(const char *, ...)SPA_SENTINEL;
91static int (*PIPEWIRE_pw_properties_set)(struct pw_properties *, const char *, const char *);
92static int (*PIPEWIRE_pw_properties_setf)(struct pw_properties *, const char *, const char *, ...) SPA_PRINTF_FUNC(3, 4);
93
94#ifdef SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC
95
96static const char *pipewire_library = SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC;
97static SDL_SharedObject *pipewire_handle = NULL;
98
99static bool pipewire_dlsym(const char *fn, void **addr)
100{
101 *addr = SDL_LoadFunction(pipewire_handle, fn);
102 if (!*addr) {
103 // Don't call SDL_SetError(): SDL_LoadFunction already did.
104 return false;
105 }
106
107 return true;
108}
109
110#define SDL_PIPEWIRE_SYM(x) \
111 if (!pipewire_dlsym(#x, (void **)(char *)&PIPEWIRE_##x)) \
112 return false
113
114static bool load_pipewire_library(void)
115{
116 pipewire_handle = SDL_LoadObject(pipewire_library);
117 return pipewire_handle ? true : false;
118}
119
120static void unload_pipewire_library(void)
121{
122 if (pipewire_handle) {
123 SDL_UnloadObject(pipewire_handle);
124 pipewire_handle = NULL;
125 }
126}
127
128#else
129
130#define SDL_PIPEWIRE_SYM(x) PIPEWIRE_##x = x
131
132static bool load_pipewire_library(void)
133{
134 return true;
135}
136
137static void unload_pipewire_library(void)
138{
139 // Nothing to do
140}
141
142#endif // SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC
143
144static bool load_pipewire_syms(void)
145{
146 SDL_PIPEWIRE_SYM(pw_get_library_version);
147 SDL_PIPEWIRE_SYM(pw_init);
148 SDL_PIPEWIRE_SYM(pw_deinit);
149 SDL_PIPEWIRE_SYM(pw_main_loop_new);
150 SDL_PIPEWIRE_SYM(pw_main_loop_get_loop);
151 SDL_PIPEWIRE_SYM(pw_main_loop_run);
152 SDL_PIPEWIRE_SYM(pw_main_loop_quit);
153 SDL_PIPEWIRE_SYM(pw_main_loop_destroy);
154 SDL_PIPEWIRE_SYM(pw_thread_loop_new);
155 SDL_PIPEWIRE_SYM(pw_thread_loop_destroy);
156 SDL_PIPEWIRE_SYM(pw_thread_loop_stop);
157 SDL_PIPEWIRE_SYM(pw_thread_loop_get_loop);
158 SDL_PIPEWIRE_SYM(pw_thread_loop_lock);
159 SDL_PIPEWIRE_SYM(pw_thread_loop_unlock);
160 SDL_PIPEWIRE_SYM(pw_thread_loop_signal);
161 SDL_PIPEWIRE_SYM(pw_thread_loop_wait);
162 SDL_PIPEWIRE_SYM(pw_thread_loop_start);
163 SDL_PIPEWIRE_SYM(pw_context_new);
164 SDL_PIPEWIRE_SYM(pw_context_destroy);
165 SDL_PIPEWIRE_SYM(pw_context_connect);
166 SDL_PIPEWIRE_SYM(pw_proxy_add_object_listener);
167 SDL_PIPEWIRE_SYM(pw_proxy_get_user_data);
168 SDL_PIPEWIRE_SYM(pw_proxy_destroy);
169 SDL_PIPEWIRE_SYM(pw_core_disconnect);
170 SDL_PIPEWIRE_SYM(pw_stream_new_simple);
171 SDL_PIPEWIRE_SYM(pw_stream_destroy);
172 SDL_PIPEWIRE_SYM(pw_stream_connect);
173 SDL_PIPEWIRE_SYM(pw_stream_get_state);
174 SDL_PIPEWIRE_SYM(pw_stream_dequeue_buffer);
175 SDL_PIPEWIRE_SYM(pw_stream_queue_buffer);
176 SDL_PIPEWIRE_SYM(pw_properties_new);
177 SDL_PIPEWIRE_SYM(pw_properties_set);
178 SDL_PIPEWIRE_SYM(pw_properties_setf);
179
180 return true;
181}
182
183static bool init_pipewire_library(void)
184{
185 if (load_pipewire_library()) {
186 if (load_pipewire_syms()) {
187 PIPEWIRE_pw_init(NULL, NULL);
188 return true;
189 }
190 }
191
192 return false;
193}
194
195static void deinit_pipewire_library(void)
196{
197 PIPEWIRE_pw_deinit();
198 unload_pipewire_library();
199}
200
201// A generic Pipewire node object used for enumeration.
202struct node_object
203{
204 struct spa_list link;
205
206 Uint32 id;
207 int seq;
208 bool persist;
209
210 /*
211 * NOTE: If used, this is *must* be allocated with SDL_malloc() or similar
212 * as SDL_free() will be called on it when the node_object is destroyed.
213 *
214 * If ownership of the referenced memory is transferred, this must be set
215 * to NULL or the memory will be freed when the node_object is destroyed.
216 */
217 void *userdata;
218
219 struct pw_proxy *proxy;
220 struct spa_hook node_listener;
221 struct spa_hook core_listener;
222};
223
224// A sink/source node used for stream I/O.
225struct io_node
226{
227 struct spa_list link;
228
229 Uint32 id;
230 bool recording;
231 SDL_AudioSpec spec;
232
233 const char *name; // Friendly name
234 const char *path; // OS identifier (i.e. ALSA endpoint)
235
236 char buf[]; // Buffer to hold the name and path strings.
237};
238
239// The global hotplug thread and associated objects.
240static struct pw_thread_loop *hotplug_loop;
241static struct pw_core *hotplug_core;
242static struct pw_context *hotplug_context;
243static struct pw_registry *hotplug_registry;
244static struct spa_hook hotplug_registry_listener;
245static struct spa_hook hotplug_core_listener;
246static struct spa_list hotplug_pending_list;
247static struct spa_list hotplug_io_list;
248static int hotplug_init_seq_val;
249static bool hotplug_init_complete;
250static bool hotplug_events_enabled;
251
252static int pipewire_version_major;
253static int pipewire_version_minor;
254static int pipewire_version_patch;
255static char *pipewire_default_sink_id = NULL;
256static char *pipewire_default_source_id = NULL;
257
258static bool pipewire_core_version_at_least(int major, int minor, int patch)
259{
260 return (pipewire_version_major >= major) &&
261 (pipewire_version_major > major || pipewire_version_minor >= minor) &&
262 (pipewire_version_major > major || pipewire_version_minor > minor || pipewire_version_patch >= patch);
263}
264
265// The active node list
266static bool io_list_check_add(struct io_node *node)
267{
268 struct io_node *n;
269 bool ret = true;
270
271 // See if the node is already in the list
272 spa_list_for_each (n, &hotplug_io_list, link) {
273 if (n->id == node->id) {
274 ret = false;
275 goto dup_found;
276 }
277 }
278
279 // Add to the list if the node doesn't already exist
280 spa_list_append(&hotplug_io_list, &node->link);
281
282 if (hotplug_events_enabled) {
283 SDL_AddAudioDevice(node->recording, node->name, &node->spec, PW_ID_TO_HANDLE(node->id));
284 }
285
286dup_found:
287
288 return ret;
289}
290
291static void io_list_remove(Uint32 id)
292{
293 struct io_node *n, *temp;
294
295 // Find and remove the node from the list
296 spa_list_for_each_safe (n, temp, &hotplug_io_list, link) {
297 if (n->id == id) {
298 spa_list_remove(&n->link);
299
300 if (hotplug_events_enabled) {
301 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(id)));
302 }
303
304 SDL_free(n);
305
306 break;
307 }
308 }
309}
310
311static void io_list_clear(void)
312{
313 struct io_node *n, *temp;
314
315 spa_list_for_each_safe (n, temp, &hotplug_io_list, link) {
316 spa_list_remove(&n->link);
317 SDL_free(n);
318 }
319}
320
321static struct io_node *io_list_get_by_id(Uint32 id)
322{
323 struct io_node *n, *temp;
324 spa_list_for_each_safe (n, temp, &hotplug_io_list, link) {
325 if (n->id == id) {
326 return n;
327 }
328 }
329 return NULL;
330}
331
332static void node_object_destroy(struct node_object *node)
333{
334 SDL_assert(node != NULL);
335
336 spa_list_remove(&node->link);
337 spa_hook_remove(&node->node_listener);
338 spa_hook_remove(&node->core_listener);
339 SDL_free(node->userdata);
340 PIPEWIRE_pw_proxy_destroy(node->proxy);
341}
342
343// The pending node list
344static void pending_list_add(struct node_object *node)
345{
346 SDL_assert(node != NULL);
347 spa_list_append(&hotplug_pending_list, &node->link);
348}
349
350static void pending_list_remove(Uint32 id)
351{
352 struct node_object *node, *temp;
353
354 spa_list_for_each_safe (node, temp, &hotplug_pending_list, link) {
355 if (node->id == id) {
356 node_object_destroy(node);
357 }
358 }
359}
360
361static void pending_list_clear(void)
362{
363 struct node_object *node, *temp;
364
365 spa_list_for_each_safe (node, temp, &hotplug_pending_list, link) {
366 node_object_destroy(node);
367 }
368}
369
370static void *node_object_new(Uint32 id, const char *type, Uint32 version, const void *funcs, const struct pw_core_events *core_events)
371{
372 struct pw_proxy *proxy;
373 struct node_object *node;
374
375 // Create the proxy object
376 proxy = pw_registry_bind(hotplug_registry, id, type, version, sizeof(struct node_object));
377 if (!proxy) {
378 SDL_SetError("Pipewire: Failed to create proxy object (%i)", errno);
379 return NULL;
380 }
381
382 node = PIPEWIRE_pw_proxy_get_user_data(proxy);
383 SDL_zerop(node);
384
385 node->id = id;
386 node->proxy = proxy;
387
388 // Add the callbacks
389 pw_core_add_listener(hotplug_core, &node->core_listener, core_events, node);
390 PIPEWIRE_pw_proxy_add_object_listener(node->proxy, &node->node_listener, funcs, node);
391
392 // Add the node to the active list
393 pending_list_add(node);
394
395 return node;
396}
397
398// Core sync points
399static void core_events_hotplug_init_callback(void *object, uint32_t id, int seq)
400{
401 if (id == PW_ID_CORE && seq == hotplug_init_seq_val) {
402 // This core listener is no longer needed.
403 spa_hook_remove(&hotplug_core_listener);
404
405 // Signal that the initial I/O list is populated
406 hotplug_init_complete = true;
407 PIPEWIRE_pw_thread_loop_signal(hotplug_loop, false);
408 }
409}
410
411static void core_events_hotplug_info_callback(void *data, const struct pw_core_info *info)
412{
413 if (SDL_sscanf(info->version, "%d.%d.%d", &pipewire_version_major, &pipewire_version_minor, &pipewire_version_patch) < 3) {
414 pipewire_version_major = 0;
415 pipewire_version_minor = 0;
416 pipewire_version_patch = 0;
417 }
418}
419
420static void core_events_interface_callback(void *object, uint32_t id, int seq)
421{
422 struct node_object *node = object;
423 struct io_node *io = node->userdata;
424
425 if (id == PW_ID_CORE && seq == node->seq) {
426 /*
427 * Move the I/O node to the connected list.
428 * On success, the list owns the I/O node object.
429 */
430 if (io_list_check_add(io)) {
431 node->userdata = NULL;
432 }
433
434 node_object_destroy(node);
435 }
436}
437
438static void core_events_metadata_callback(void *object, uint32_t id, int seq)
439{
440 struct node_object *node = object;
441
442 if (id == PW_ID_CORE && seq == node->seq && !node->persist) {
443 node_object_destroy(node);
444 }
445}
446
447static const struct pw_core_events hotplug_init_core_events = { PW_VERSION_CORE_EVENTS, .info = core_events_hotplug_info_callback, .done = core_events_hotplug_init_callback };
448static const struct pw_core_events interface_core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_interface_callback };
449static const struct pw_core_events metadata_core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_metadata_callback };
450
451static void hotplug_core_sync(struct node_object *node)
452{
453 /*
454 * Node sync events *must* come before the hotplug init sync events or the initial
455 * I/O list will be incomplete when the main hotplug sync point is hit.
456 */
457 if (node) {
458 node->seq = pw_core_sync(hotplug_core, PW_ID_CORE, node->seq);
459 }
460
461 if (!hotplug_init_complete) {
462 hotplug_init_seq_val = pw_core_sync(hotplug_core, PW_ID_CORE, hotplug_init_seq_val);
463 }
464}
465
466// Helpers for retrieving values from params
467static bool get_range_param(const struct spa_pod *param, Uint32 key, int *def, int *min, int *max)
468{
469 const struct spa_pod_prop *prop;
470 struct spa_pod *value;
471 Uint32 n_values, choice;
472
473 prop = spa_pod_find_prop(param, NULL, key);
474
475 if (prop && prop->value.type == SPA_TYPE_Choice) {
476 value = spa_pod_get_values(&prop->value, &n_values, &choice);
477
478 if (n_values == 3 && choice == SPA_CHOICE_Range) {
479 Uint32 *v = SPA_POD_BODY(value);
480
481 if (v) {
482 if (def) {
483 *def = (int)v[0];
484 }
485 if (min) {
486 *min = (int)v[1];
487 }
488 if (max) {
489 *max = (int)v[2];
490 }
491
492 return true;
493 }
494 }
495 }
496
497 return false;
498}
499
500static bool get_int_param(const struct spa_pod *param, Uint32 key, int *val)
501{
502 const struct spa_pod_prop *prop;
503 Sint32 v;
504
505 prop = spa_pod_find_prop(param, NULL, key);
506
507 if (prop && spa_pod_get_int(&prop->value, &v) == 0) {
508 if (val) {
509 *val = (int)v;
510 }
511
512 return true;
513 }
514
515 return false;
516}
517
518static SDL_AudioFormat SPAFormatToSDL(enum spa_audio_format spafmt)
519{
520 switch (spafmt) {
521 #define CHECKFMT(spa,sdl) case SPA_AUDIO_FORMAT_##spa: return SDL_AUDIO_##sdl
522 CHECKFMT(U8, U8);
523 CHECKFMT(S8, S8);
524 CHECKFMT(S16_LE, S16LE);
525 CHECKFMT(S16_BE, S16BE);
526 CHECKFMT(S32_LE, S32LE);
527 CHECKFMT(S32_BE, S32BE);
528 CHECKFMT(F32_LE, F32LE);
529 CHECKFMT(F32_BE, F32BE);
530 #undef CHECKFMT
531 default: break;
532 }
533
534 return SDL_AUDIO_UNKNOWN;
535}
536
537// Interface node callbacks
538static void node_event_info(void *object, const struct pw_node_info *info)
539{
540 struct node_object *node = object;
541 struct io_node *io = node->userdata;
542 const char *prop_val;
543 Uint32 i;
544
545 if (info) {
546 prop_val = spa_dict_lookup(info->props, PW_KEY_AUDIO_CHANNELS);
547 if (prop_val) {
548 io->spec.channels = (Uint8)SDL_atoi(prop_val);
549 }
550
551 // Need to parse the parameters to get the sample rate
552 for (i = 0; i < info->n_params; ++i) {
553 pw_node_enum_params((struct pw_node*)node->proxy, 0, info->params[i].id, 0, 0, NULL);
554 }
555
556 hotplug_core_sync(node);
557 }
558}
559
560static void node_event_param(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param)
561{
562 struct node_object *node = object;
563 struct io_node *io = node->userdata;
564
565 if ((id == SPA_PARAM_Format) && (io->spec.format == SDL_AUDIO_UNKNOWN)) {
566 struct spa_audio_info_raw info;
567 SDL_zero(info);
568 if (spa_format_audio_raw_parse(param, &info) == 0) {
569 //SDL_Log("Sink Format: %d, Rate: %d Hz, Channels: %d", info.format, info.rate, info.channels);
570 io->spec.format = SPAFormatToSDL(info.format);
571 }
572 }
573
574 // Get the default frequency
575 if (io->spec.freq == 0) {
576 get_range_param(param, SPA_FORMAT_AUDIO_rate, &io->spec.freq, NULL, NULL);
577 }
578
579 /*
580 * The channel count should have come from the node properties,
581 * but it is stored here as well. If one failed, try the other.
582 */
583 if (io->spec.channels == 0) {
584 int channels;
585 if (get_int_param(param, SPA_FORMAT_AUDIO_channels, &channels)) {
586 io->spec.channels = (Uint8)channels;
587 }
588 }
589}
590
591static const struct pw_node_events interface_node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info,
592 .param = node_event_param };
593
594static char *get_name_from_json(const char *json)
595{
596 struct spa_json parser[2];
597 char key[7]; // "name"
598 char value[PW_MAX_IDENTIFIER_LENGTH];
599 spa_json_init(&parser[0], json, SDL_strlen(json));
600 if (spa_json_enter_object(&parser[0], &parser[1]) <= 0) {
601 // Not actually JSON
602 return NULL;
603 }
604 if (spa_json_get_string(&parser[1], key, sizeof(key)) <= 0) {
605 // Not actually a key/value pair
606 return NULL;
607 }
608 if (spa_json_get_string(&parser[1], value, sizeof(value)) <= 0) {
609 // Somehow had a key with no value?
610 return NULL;
611 }
612 return SDL_strdup(value);
613}
614
615static void change_default_device(const char *path)
616{
617 if (hotplug_events_enabled) {
618 struct io_node *n, *temp;
619 spa_list_for_each_safe (n, temp, &hotplug_io_list, link) {
620 if (SDL_strcmp(n->path, path) == 0) {
621 SDL_DefaultAudioDeviceChanged(SDL_FindPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(n->id)));
622 return; // found it, we're done.
623 }
624 }
625 }
626}
627
628// Metadata node callback
629static int metadata_property(void *object, Uint32 subject, const char *key, const char *type, const char *value)
630{
631 struct node_object *node = object;
632
633 if (subject == PW_ID_CORE && key && value) {
634 if (!SDL_strcmp(key, "default.audio.sink")) {
635 if (pipewire_default_sink_id) {
636 SDL_free(pipewire_default_sink_id);
637 }
638 pipewire_default_sink_id = get_name_from_json(value);
639 node->persist = true;
640 change_default_device(pipewire_default_sink_id);
641 } else if (!SDL_strcmp(key, "default.audio.source")) {
642 if (pipewire_default_source_id) {
643 SDL_free(pipewire_default_source_id);
644 }
645 pipewire_default_source_id = get_name_from_json(value);
646 node->persist = true;
647 change_default_device(pipewire_default_source_id);
648 }
649 }
650
651 return 0;
652}
653
654static const struct pw_metadata_events metadata_node_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property };
655
656// Global registry callbacks
657static void registry_event_global_callback(void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version,
658 const struct spa_dict *props)
659{
660 struct node_object *node;
661
662 // We're only interested in interface and metadata nodes.
663 if (!SDL_strcmp(type, PW_TYPE_INTERFACE_Node)) {
664 const char *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
665
666 if (media_class) {
667 const char *node_desc;
668 const char *node_path;
669 struct io_node *io;
670 bool recording;
671 int desc_buffer_len;
672 int path_buffer_len;
673
674 // Just want sink and source
675 if (!SDL_strcasecmp(media_class, "Audio/Sink")) {
676 recording = false;
677 } else if (!SDL_strcasecmp(media_class, "Audio/Source")) {
678 recording = true;
679 } else {
680 return;
681 }
682
683 node_desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
684 node_path = spa_dict_lookup(props, PW_KEY_NODE_NAME);
685
686 if (node_desc && node_path) {
687 node = node_object_new(id, type, version, &interface_node_events, &interface_core_events);
688 if (!node) {
689 SDL_SetError("Pipewire: Failed to allocate interface node");
690 return;
691 }
692
693 // Allocate and initialize the I/O node information struct
694 desc_buffer_len = SDL_strlen(node_desc) + 1;
695 path_buffer_len = SDL_strlen(node_path) + 1;
696 node->userdata = io = SDL_calloc(1, sizeof(struct io_node) + desc_buffer_len + path_buffer_len);
697 if (!io) {
698 node_object_destroy(node);
699 return;
700 }
701
702 // Begin setting the node properties
703 io->id = id;
704 io->recording = recording;
705 if (io->spec.format == SDL_AUDIO_UNKNOWN) {
706 io->spec.format = SDL_AUDIO_S16; // we'll go conservative here if for some reason the format isn't known.
707 }
708 io->name = io->buf;
709 io->path = io->buf + desc_buffer_len;
710 SDL_strlcpy(io->buf, node_desc, desc_buffer_len);
711 SDL_strlcpy(io->buf + desc_buffer_len, node_path, path_buffer_len);
712
713 // Update sync points
714 hotplug_core_sync(node);
715 }
716 }
717 } else if (!SDL_strcmp(type, PW_TYPE_INTERFACE_Metadata)) {
718 node = node_object_new(id, type, version, &metadata_node_events, &metadata_core_events);
719 if (!node) {
720 SDL_SetError("Pipewire: Failed to allocate metadata node");
721 return;
722 }
723
724 // Update sync points
725 hotplug_core_sync(node);
726 }
727}
728
729static void registry_event_remove_callback(void *object, uint32_t id)
730{
731 io_list_remove(id);
732 pending_list_remove(id);
733}
734
735static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global_callback,
736 .global_remove = registry_event_remove_callback };
737
738// The hotplug thread
739static bool hotplug_loop_init(void)
740{
741 int res;
742
743 spa_list_init(&hotplug_pending_list);
744 spa_list_init(&hotplug_io_list);
745
746 hotplug_loop = PIPEWIRE_pw_thread_loop_new("SDLPwAudioPlug", NULL);
747 if (!hotplug_loop) {
748 return SDL_SetError("Pipewire: Failed to create hotplug detection loop (%i)", errno);
749 }
750
751 hotplug_context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(hotplug_loop), NULL, 0);
752 if (!hotplug_context) {
753 return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno);
754 }
755
756 hotplug_core = PIPEWIRE_pw_context_connect(hotplug_context, NULL, 0);
757 if (!hotplug_core) {
758 return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno);
759 }
760
761 hotplug_registry = pw_core_get_registry(hotplug_core, PW_VERSION_REGISTRY, 0);
762 if (!hotplug_registry) {
763 return SDL_SetError("Pipewire: Failed to acquire hotplug detection registry (%i)", errno);
764 }
765
766 spa_zero(hotplug_registry_listener);
767 pw_registry_add_listener(hotplug_registry, &hotplug_registry_listener, &registry_events, NULL);
768
769 spa_zero(hotplug_core_listener);
770 pw_core_add_listener(hotplug_core, &hotplug_core_listener, &hotplug_init_core_events, NULL);
771
772 hotplug_init_seq_val = pw_core_sync(hotplug_core, PW_ID_CORE, 0);
773
774 res = PIPEWIRE_pw_thread_loop_start(hotplug_loop);
775 if (res != 0) {
776 return SDL_SetError("Pipewire: Failed to start hotplug detection loop");
777 }
778
779 return true;
780}
781
782static void hotplug_loop_destroy(void)
783{
784 if (hotplug_loop) {
785 PIPEWIRE_pw_thread_loop_stop(hotplug_loop);
786 }
787
788 pending_list_clear();
789 io_list_clear();
790
791 hotplug_init_complete = false;
792 hotplug_events_enabled = false;
793
794 if (pipewire_default_sink_id) {
795 SDL_free(pipewire_default_sink_id);
796 pipewire_default_sink_id = NULL;
797 }
798 if (pipewire_default_source_id) {
799 SDL_free(pipewire_default_source_id);
800 pipewire_default_source_id = NULL;
801 }
802
803 if (hotplug_registry) {
804 PIPEWIRE_pw_proxy_destroy((struct pw_proxy *)hotplug_registry);
805 hotplug_registry = NULL;
806 }
807
808 if (hotplug_core) {
809 PIPEWIRE_pw_core_disconnect(hotplug_core);
810 hotplug_core = NULL;
811 }
812
813 if (hotplug_context) {
814 PIPEWIRE_pw_context_destroy(hotplug_context);
815 hotplug_context = NULL;
816 }
817
818 if (hotplug_loop) {
819 PIPEWIRE_pw_thread_loop_destroy(hotplug_loop);
820 hotplug_loop = NULL;
821 }
822}
823
824static void PIPEWIRE_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
825{
826 struct io_node *io;
827
828 PIPEWIRE_pw_thread_loop_lock(hotplug_loop);
829
830 // Wait until the initial registry enumeration is complete
831 if (!hotplug_init_complete) {
832 PIPEWIRE_pw_thread_loop_wait(hotplug_loop);
833 }
834
835 spa_list_for_each (io, &hotplug_io_list, link) {
836 SDL_AudioDevice *device = SDL_AddAudioDevice(io->recording, io->name, &io->spec, PW_ID_TO_HANDLE(io->id));
837 if (pipewire_default_sink_id && SDL_strcmp(io->path, pipewire_default_sink_id) == 0) {
838 if (!io->recording) {
839 *default_playback = device;
840 }
841 } else if (pipewire_default_source_id && SDL_strcmp(io->path, pipewire_default_source_id) == 0) {
842 if (io->recording) {
843 *default_recording = device;
844 }
845 }
846 }
847
848 hotplug_events_enabled = true;
849
850 PIPEWIRE_pw_thread_loop_unlock(hotplug_loop);
851}
852
853// Channel maps that match the order in SDL_Audio.h
854static const enum spa_audio_channel PIPEWIRE_channel_map_1[] = { SPA_AUDIO_CHANNEL_MONO };
855static const enum spa_audio_channel PIPEWIRE_channel_map_2[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR };
856static const enum spa_audio_channel PIPEWIRE_channel_map_3[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE };
857static const enum spa_audio_channel PIPEWIRE_channel_map_4[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL,
858 SPA_AUDIO_CHANNEL_RR };
859static const enum spa_audio_channel PIPEWIRE_channel_map_5[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC,
860 SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR };
861static const enum spa_audio_channel PIPEWIRE_channel_map_6[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC,
862 SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR };
863static const enum spa_audio_channel PIPEWIRE_channel_map_7[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC,
864 SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL,
865 SPA_AUDIO_CHANNEL_RR };
866static const enum spa_audio_channel PIPEWIRE_channel_map_8[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC,
867 SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR,
868 SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR };
869
870#define COPY_CHANNEL_MAP(c) SDL_memcpy(info->position, PIPEWIRE_channel_map_##c, sizeof(PIPEWIRE_channel_map_##c))
871
872static void initialize_spa_info(const SDL_AudioSpec *spec, struct spa_audio_info_raw *info)
873{
874 info->channels = spec->channels;
875 info->rate = spec->freq;
876
877 switch (spec->channels) {
878 case 1:
879 COPY_CHANNEL_MAP(1);
880 break;
881 case 2:
882 COPY_CHANNEL_MAP(2);
883 break;
884 case 3:
885 COPY_CHANNEL_MAP(3);
886 break;
887 case 4:
888 COPY_CHANNEL_MAP(4);
889 break;
890 case 5:
891 COPY_CHANNEL_MAP(5);
892 break;
893 case 6:
894 COPY_CHANNEL_MAP(6);
895 break;
896 case 7:
897 COPY_CHANNEL_MAP(7);
898 break;
899 case 8:
900 COPY_CHANNEL_MAP(8);
901 break;
902 }
903
904 // Pipewire natively supports all of SDL's sample formats
905 switch (spec->format) {
906 case SDL_AUDIO_U8:
907 info->format = SPA_AUDIO_FORMAT_U8;
908 break;
909 case SDL_AUDIO_S8:
910 info->format = SPA_AUDIO_FORMAT_S8;
911 break;
912 case SDL_AUDIO_S16LE:
913 info->format = SPA_AUDIO_FORMAT_S16_LE;
914 break;
915 case SDL_AUDIO_S16BE:
916 info->format = SPA_AUDIO_FORMAT_S16_BE;
917 break;
918 case SDL_AUDIO_S32LE:
919 info->format = SPA_AUDIO_FORMAT_S32_LE;
920 break;
921 case SDL_AUDIO_S32BE:
922 info->format = SPA_AUDIO_FORMAT_S32_BE;
923 break;
924 case SDL_AUDIO_F32LE:
925 info->format = SPA_AUDIO_FORMAT_F32_LE;
926 break;
927 case SDL_AUDIO_F32BE:
928 info->format = SPA_AUDIO_FORMAT_F32_BE;
929 break;
930 default:
931 info->format = SPA_AUDIO_FORMAT_UNKNOWN;
932 break;
933 }
934}
935
936static Uint8 *PIPEWIRE_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
937{
938 // See if a buffer is available. If this returns NULL, SDL_PlaybackAudioThreadIterate will return false, but since we own the thread, it won't kill playback.
939 // !!! FIXME: It's not clear to me if this ever returns NULL or if this was just defensive coding.
940
941 struct pw_stream *stream = device->hidden->stream;
942 struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream);
943 if (pw_buf == NULL) {
944 return NULL;
945 }
946
947 struct spa_buffer *spa_buf = pw_buf->buffer;
948 if (spa_buf->datas[0].data == NULL) {
949 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf);
950 return NULL;
951 }
952
953 device->hidden->pw_buf = pw_buf;
954 return (Uint8 *) spa_buf->datas[0].data;
955}
956
957static bool PIPEWIRE_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
958{
959 struct pw_stream *stream = device->hidden->stream;
960 struct pw_buffer *pw_buf = device->hidden->pw_buf;
961 struct spa_buffer *spa_buf = pw_buf->buffer;
962 spa_buf->datas[0].chunk->offset = 0;
963 spa_buf->datas[0].chunk->stride = device->hidden->stride;
964 spa_buf->datas[0].chunk->size = buffer_size;
965
966 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf);
967 device->hidden->pw_buf = NULL;
968
969 return true;
970}
971
972static void output_callback(void *data)
973{
974 SDL_AudioDevice *device = (SDL_AudioDevice *) data;
975
976 // this callback can fire in a background thread during OpenDevice, while we're still blocking
977 // _with the device lock_ until the stream is ready, causing a deadlock. Write silence in this case.
978 if (device->hidden->stream_init_status != PW_READY_FLAG_ALL_BITS) {
979 int bufsize = 0;
980 Uint8 *buf = PIPEWIRE_GetDeviceBuf(device, &bufsize);
981 if (buf && bufsize) {
982 SDL_memset(buf, device->silence_value, bufsize);
983 }
984 PIPEWIRE_PlayDevice(device, buf, bufsize);
985 return;
986 }
987
988 SDL_PlaybackAudioThreadIterate(device);
989}
990
991static void PIPEWIRE_FlushRecording(SDL_AudioDevice *device)
992{
993 struct pw_stream *stream = device->hidden->stream;
994 struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream);
995 if (pw_buf) { // just requeue it without any further thought.
996 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf);
997 }
998}
999
1000static int PIPEWIRE_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
1001{
1002 struct pw_stream *stream = device->hidden->stream;
1003 struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream);
1004 if (!pw_buf) {
1005 return 0;
1006 }
1007
1008 struct spa_buffer *spa_buf = pw_buf->buffer;
1009 if (!spa_buf) {
1010 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf);
1011 return 0;
1012 }
1013
1014 const Uint8 *src = (const Uint8 *)spa_buf->datas[0].data;
1015 const Uint32 offset = SPA_MIN(spa_buf->datas[0].chunk->offset, spa_buf->datas[0].maxsize);
1016 const Uint32 size = SPA_MIN(spa_buf->datas[0].chunk->size, spa_buf->datas[0].maxsize - offset);
1017 const int cpy = SDL_min(buflen, (int) size);
1018
1019 SDL_assert(size <= buflen); // We'll have to reengineer some stuff if this turns out to not be true.
1020
1021 SDL_memcpy(buffer, src + offset, cpy);
1022 PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf);
1023
1024 return cpy;
1025}
1026
1027static void input_callback(void *data)
1028{
1029 SDL_AudioDevice *device = (SDL_AudioDevice *) data;
1030
1031 // this callback can fire in a background thread during OpenDevice, while we're still blocking
1032 // _with the device lock_ until the stream is ready, causing a deadlock. Drop data in this case.
1033 if (device->hidden->stream_init_status != PW_READY_FLAG_ALL_BITS) {
1034 PIPEWIRE_FlushRecording(device);
1035 return;
1036 }
1037
1038 SDL_RecordingAudioThreadIterate(device);
1039}
1040
1041static void stream_add_buffer_callback(void *data, struct pw_buffer *buffer)
1042{
1043 SDL_AudioDevice *device = (SDL_AudioDevice *) data;
1044
1045 if (device->recording == false) {
1046 /* Clamp the output spec samples and size to the max size of the Pipewire buffer.
1047 If they exceed the maximum size of the Pipewire buffer, double buffering will be used. */
1048 if (device->buffer_size > buffer->buffer->datas[0].maxsize) {
1049 SDL_LockMutex(device->lock);
1050 device->sample_frames = buffer->buffer->datas[0].maxsize / device->hidden->stride;
1051 device->buffer_size = buffer->buffer->datas[0].maxsize;
1052 SDL_UnlockMutex(device->lock);
1053 }
1054 }
1055
1056 device->hidden->stream_init_status |= PW_READY_FLAG_BUFFER_ADDED;
1057 PIPEWIRE_pw_thread_loop_signal(device->hidden->loop, false);
1058}
1059
1060static void stream_state_changed_callback(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error)
1061{
1062 SDL_AudioDevice *device = (SDL_AudioDevice *) data;
1063
1064 if (state == PW_STREAM_STATE_STREAMING) {
1065 device->hidden->stream_init_status |= PW_READY_FLAG_STREAM_READY;
1066 }
1067
1068 if (state == PW_STREAM_STATE_STREAMING || state == PW_STREAM_STATE_ERROR) {
1069 PIPEWIRE_pw_thread_loop_signal(device->hidden->loop, false);
1070 }
1071}
1072
1073static const struct pw_stream_events stream_output_events = { PW_VERSION_STREAM_EVENTS,
1074 .state_changed = stream_state_changed_callback,
1075 .add_buffer = stream_add_buffer_callback,
1076 .process = output_callback };
1077static const struct pw_stream_events stream_input_events = { PW_VERSION_STREAM_EVENTS,
1078 .state_changed = stream_state_changed_callback,
1079 .add_buffer = stream_add_buffer_callback,
1080 .process = input_callback };
1081
1082static bool PIPEWIRE_OpenDevice(SDL_AudioDevice *device)
1083{
1084 /*
1085 * NOTE: The PW_STREAM_FLAG_RT_PROCESS flag can be set to call the stream
1086 * processing callback from the realtime thread. However, it comes with some
1087 * caveats: no file IO, allocations, locking or other blocking operations
1088 * must occur in the mixer callback. As this cannot be guaranteed when the
1089 * callback is in the calling application, this flag is omitted.
1090 */
1091 static const enum pw_stream_flags STREAM_FLAGS = PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS;
1092
1093 char thread_name[PW_THREAD_NAME_BUFFER_LENGTH];
1094 Uint8 pod_buffer[PW_POD_BUFFER_LENGTH];
1095 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(pod_buffer, sizeof(pod_buffer));
1096 struct spa_audio_info_raw spa_info = { 0 };
1097 const struct spa_pod *params = NULL;
1098 struct SDL_PrivateAudioData *priv;
1099 struct pw_properties *props;
1100 const char *app_name, *icon_name, *app_id, *stream_name, *stream_role, *error;
1101 Uint32 node_id = !device->handle ? PW_ID_ANY : PW_HANDLE_TO_ID(device->handle);
1102 const bool recording = device->recording;
1103 int res;
1104
1105 // Clamp the period size to sane values
1106 const int min_period = PW_MIN_SAMPLES * SPA_MAX(device->spec.freq / PW_BASE_CLOCK_RATE, 1);
1107
1108 // Get the hints for the application name, icon name, stream name and role
1109 app_name = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
1110
1111 icon_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME);
1112 if (!icon_name || *icon_name == '\0') {
1113 icon_name = "applications-games";
1114 }
1115
1116 // App ID. Default to NULL if not available.
1117 app_id = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING);
1118
1119 stream_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
1120 if (!stream_name || *stream_name == '\0') {
1121 stream_name = "Audio Stream";
1122 }
1123
1124 /*
1125 * 'Music' is the default used internally by Pipewire and it's modules,
1126 * but 'Game' seems more appropriate for the majority of SDL applications.
1127 */
1128 stream_role = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE);
1129 if (!stream_role || *stream_role == '\0') {
1130 stream_role = "Game";
1131 }
1132
1133 // Initialize the Pipewire stream info from the SDL audio spec
1134 initialize_spa_info(&device->spec, &spa_info);
1135 params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &spa_info);
1136 if (!params) {
1137 return SDL_SetError("Pipewire: Failed to set audio format parameters");
1138 }
1139
1140 priv = SDL_calloc(1, sizeof(struct SDL_PrivateAudioData));
1141 device->hidden = priv;
1142 if (!priv) {
1143 return false;
1144 }
1145
1146 // Size of a single audio frame in bytes
1147 priv->stride = SDL_AUDIO_FRAMESIZE(device->spec);
1148
1149 if (device->sample_frames < min_period) {
1150 device->sample_frames = min_period;
1151 }
1152
1153 SDL_UpdatedAudioDeviceFormat(device);
1154
1155 SDL_GetAudioThreadName(device, thread_name, sizeof(thread_name));
1156 priv->loop = PIPEWIRE_pw_thread_loop_new(thread_name, NULL);
1157 if (!priv->loop) {
1158 return SDL_SetError("Pipewire: Failed to create stream loop (%i)", errno);
1159 }
1160
1161 // Load the realtime module so Pipewire can set the loop thread to the appropriate priority.
1162 props = PIPEWIRE_pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", NULL);
1163 if (!props) {
1164 return SDL_SetError("Pipewire: Failed to create stream context properties (%i)", errno);
1165 }
1166
1167 priv->context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), props, 0);
1168 if (!priv->context) {
1169 return SDL_SetError("Pipewire: Failed to create stream context (%i)", errno);
1170 }
1171
1172 props = PIPEWIRE_pw_properties_new(NULL, NULL);
1173 if (!props) {
1174 return SDL_SetError("Pipewire: Failed to create stream properties (%i)", errno);
1175 }
1176
1177 PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Audio");
1178 PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, recording ? "Capture" : "Playback");
1179 PIPEWIRE_pw_properties_set(props, PW_KEY_MEDIA_ROLE, stream_role);
1180 PIPEWIRE_pw_properties_set(props, PW_KEY_APP_NAME, app_name);
1181 PIPEWIRE_pw_properties_set(props, PW_KEY_APP_ICON_NAME, icon_name);
1182 if (app_id) {
1183 PIPEWIRE_pw_properties_set(props, PW_KEY_APP_ID, app_id);
1184 }
1185 PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_NAME, stream_name);
1186 PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, stream_name);
1187 PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", device->sample_frames, device->spec.freq);
1188 PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", device->spec.freq);
1189 PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true");
1190 PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DONT_RECONNECT, "true"); // Requesting a specific device, don't migrate to new default hardware.
1191
1192 if (node_id != PW_ID_ANY) {
1193 PIPEWIRE_pw_thread_loop_lock(hotplug_loop);
1194 const struct io_node *node = io_list_get_by_id(node_id);
1195 if (node) {
1196 PIPEWIRE_pw_properties_set(props, PW_KEY_TARGET_OBJECT, node->path);
1197 }
1198 PIPEWIRE_pw_thread_loop_unlock(hotplug_loop);
1199 }
1200
1201 // Create the new stream
1202 priv->stream = PIPEWIRE_pw_stream_new_simple(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), stream_name, props,
1203 recording ? &stream_input_events : &stream_output_events, device);
1204 if (!priv->stream) {
1205 return SDL_SetError("Pipewire: Failed to create stream (%i)", errno);
1206 }
1207
1208 // The target node is passed via PW_KEY_TARGET_OBJECT; target_id is a legacy parameter and must be PW_ID_ANY.
1209 res = PIPEWIRE_pw_stream_connect(priv->stream, recording ? PW_DIRECTION_INPUT : PW_DIRECTION_OUTPUT, PW_ID_ANY, STREAM_FLAGS,
1210 &params, 1);
1211 if (res != 0) {
1212 return SDL_SetError("Pipewire: Failed to connect stream");
1213 }
1214
1215 res = PIPEWIRE_pw_thread_loop_start(priv->loop);
1216 if (res != 0) {
1217 return SDL_SetError("Pipewire: Failed to start stream loop");
1218 }
1219
1220 // Wait until all pre-open init flags are set or the stream has failed.
1221 PIPEWIRE_pw_thread_loop_lock(priv->loop);
1222 while (priv->stream_init_status != PW_READY_FLAG_ALL_PREOPEN_BITS &&
1223 PIPEWIRE_pw_stream_get_state(priv->stream, NULL) != PW_STREAM_STATE_ERROR) {
1224 PIPEWIRE_pw_thread_loop_wait(priv->loop);
1225 }
1226 priv->stream_init_status |= PW_READY_FLAG_OPEN_COMPLETE;
1227 PIPEWIRE_pw_thread_loop_unlock(priv->loop);
1228
1229 if (PIPEWIRE_pw_stream_get_state(priv->stream, &error) == PW_STREAM_STATE_ERROR) {
1230 return SDL_SetError("Pipewire: Stream error: %s", error);
1231 }
1232
1233 return true;
1234}
1235
1236static void PIPEWIRE_CloseDevice(SDL_AudioDevice *device)
1237{
1238 if (!device->hidden) {
1239 return;
1240 }
1241
1242 if (device->hidden->loop) {
1243 PIPEWIRE_pw_thread_loop_stop(device->hidden->loop);
1244 }
1245
1246 if (device->hidden->stream) {
1247 PIPEWIRE_pw_stream_destroy(device->hidden->stream);
1248 }
1249
1250 if (device->hidden->context) {
1251 PIPEWIRE_pw_context_destroy(device->hidden->context);
1252 }
1253
1254 if (device->hidden->loop) {
1255 PIPEWIRE_pw_thread_loop_destroy(device->hidden->loop);
1256 }
1257
1258 SDL_free(device->hidden);
1259 device->hidden = NULL;
1260
1261 SDL_AudioThreadFinalize(device);
1262}
1263
1264static void PIPEWIRE_DeinitializeStart(void)
1265{
1266 if (pipewire_initialized) {
1267 hotplug_loop_destroy();
1268 }
1269}
1270
1271static void PIPEWIRE_Deinitialize(void)
1272{
1273 if (pipewire_initialized) {
1274 hotplug_loop_destroy();
1275 deinit_pipewire_library();
1276 pipewire_initialized = false;
1277 }
1278}
1279
1280static bool PipewireInitialize(SDL_AudioDriverImpl *impl)
1281{
1282 if (!pipewire_initialized) {
1283 if (!init_pipewire_library()) {
1284 return false;
1285 }
1286
1287 pipewire_initialized = true;
1288
1289 if (!hotplug_loop_init()) {
1290 PIPEWIRE_Deinitialize();
1291 return false;
1292 }
1293 }
1294
1295 impl->DetectDevices = PIPEWIRE_DetectDevices;
1296 impl->OpenDevice = PIPEWIRE_OpenDevice;
1297 impl->DeinitializeStart = PIPEWIRE_DeinitializeStart;
1298 impl->Deinitialize = PIPEWIRE_Deinitialize;
1299 impl->PlayDevice = PIPEWIRE_PlayDevice;
1300 impl->GetDeviceBuf = PIPEWIRE_GetDeviceBuf;
1301 impl->RecordDevice = PIPEWIRE_RecordDevice;
1302 impl->FlushRecording = PIPEWIRE_FlushRecording;
1303 impl->CloseDevice = PIPEWIRE_CloseDevice;
1304
1305 impl->HasRecordingSupport = true;
1306 impl->ProvidesOwnCallbackThread = true;
1307
1308 return true;
1309}
1310
1311static bool PIPEWIRE_PREFERRED_Init(SDL_AudioDriverImpl *impl)
1312{
1313 if (!PipewireInitialize(impl)) {
1314 return false;
1315 }
1316
1317 // run device detection but don't add any devices to SDL; we're just waiting to see if PipeWire sees any devices. If not, fall back to the next backend.
1318 PIPEWIRE_pw_thread_loop_lock(hotplug_loop);
1319
1320 // Wait until the initial registry enumeration is complete
1321 if (!hotplug_init_complete) {
1322 PIPEWIRE_pw_thread_loop_wait(hotplug_loop);
1323 }
1324
1325 const bool no_devices = spa_list_is_empty(&hotplug_io_list);
1326
1327 PIPEWIRE_pw_thread_loop_unlock(hotplug_loop);
1328
1329 if (no_devices || !pipewire_core_version_at_least(1, 0, 0)) {
1330 PIPEWIRE_Deinitialize();
1331 return false;
1332 }
1333
1334 return true; // this will move on to PIPEWIRE_DetectDevices and reuse hotplug_io_list.
1335}
1336
1337static bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl)
1338{
1339 return PipewireInitialize(impl);
1340}
1341
1342AudioBootStrap PIPEWIRE_PREFERRED_bootstrap = {
1343 "pipewire", "Pipewire", PIPEWIRE_PREFERRED_Init, false, true
1344};
1345AudioBootStrap PIPEWIRE_bootstrap = {
1346 "pipewire", "Pipewire", PIPEWIRE_Init, false, false
1347};
1348
1349#endif // SDL_AUDIO_DRIVER_PIPEWIRE
diff --git a/contrib/SDL-3.2.8/src/audio/pipewire/SDL_pipewire.h b/contrib/SDL-3.2.8/src/audio/pipewire/SDL_pipewire.h
new file mode 100644
index 0000000..e609e25
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/pipewire/SDL_pipewire.h
@@ -0,0 +1,43 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifndef SDL_pipewire_h_
25#define SDL_pipewire_h_
26
27#include "../SDL_sysaudio.h"
28#include <pipewire/pipewire.h>
29
30struct SDL_PrivateAudioData
31{
32 struct pw_thread_loop *loop;
33 struct pw_stream *stream;
34 struct pw_context *context;
35
36 Sint32 stride; // Bytes-per-frame
37 int stream_init_status;
38
39 // Set in GetDeviceBuf, filled in AudioThreadIterate, queued in PlayDevice
40 struct pw_buffer *pw_buf;
41};
42
43#endif // SDL_pipewire_h_
diff --git a/contrib/SDL-3.2.8/src/audio/ps2/SDL_ps2audio.c b/contrib/SDL-3.2.8/src/audio/ps2/SDL_ps2audio.c
new file mode 100644
index 0000000..6579f1b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/ps2/SDL_ps2audio.c
@@ -0,0 +1,159 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_sysaudio.h"
24#include "SDL_ps2audio.h"
25
26#include <kernel.h>
27#include <audsrv.h>
28#include <ps2_audio_driver.h>
29
30static bool PS2AUDIO_OpenDevice(SDL_AudioDevice *device)
31{
32 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
33 if (!device->hidden) {
34 return false;
35 }
36
37 // These are the native supported audio PS2 configs
38 switch (device->spec.freq) {
39 case 11025:
40 case 12000:
41 case 22050:
42 case 24000:
43 case 32000:
44 case 44100:
45 case 48000:
46 break; // acceptable value, keep it
47 default:
48 device->spec.freq = 48000;
49 break;
50 }
51
52 device->sample_frames = 512;
53 device->spec.channels = device->spec.channels == 1 ? 1 : 2;
54 device->spec.format = device->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16;
55
56 struct audsrv_fmt_t format;
57 format.bits = device->spec.format == SDL_AUDIO_S8 ? 8 : 16;
58 format.freq = device->spec.freq;
59 format.channels = device->spec.channels;
60
61 device->hidden->channel = audsrv_set_format(&format);
62 audsrv_set_volume(MAX_VOLUME);
63
64 if (device->hidden->channel < 0) {
65 return SDL_SetError("Couldn't reserve hardware channel");
66 }
67
68 // Update the fragment size as size in bytes.
69 SDL_UpdatedAudioDeviceFormat(device);
70
71 /* Allocate the mixing buffer. Its size and starting address must
72 be a multiple of 64 bytes. Our sample count is already a multiple of
73 64, so spec->size should be a multiple of 64 as well. */
74 const int mixlen = device->buffer_size * NUM_BUFFERS;
75 device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
76 if (!device->hidden->rawbuf) {
77 return SDL_SetError("Couldn't allocate mixing buffer");
78 }
79
80 SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
81 for (int i = 0; i < NUM_BUFFERS; i++) {
82 device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
83 }
84
85 return true;
86}
87
88static bool PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
89{
90 // this returns number of bytes accepted or a negative error. We assume anything other than buflen is a fatal error.
91 return (audsrv_play_audio((char *)buffer, buflen) == buflen);
92}
93
94static bool PS2AUDIO_WaitDevice(SDL_AudioDevice *device)
95{
96 audsrv_wait_audio(device->buffer_size);
97 return true;
98}
99
100static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
101{
102 Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
103 device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
104 return buffer;
105}
106
107static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device)
108{
109 if (device->hidden) {
110 if (device->hidden->channel >= 0) {
111 audsrv_stop_audio();
112 device->hidden->channel = -1;
113 }
114
115 if (device->hidden->rawbuf) {
116 SDL_aligned_free(device->hidden->rawbuf);
117 device->hidden->rawbuf = NULL;
118 }
119 SDL_free(device->hidden);
120 device->hidden = NULL;
121 }
122}
123
124static void PS2AUDIO_ThreadInit(SDL_AudioDevice *device)
125{
126 /* Increase the priority of this audio thread by 1 to put it
127 ahead of other SDL threads. */
128 const int32_t thid = GetThreadId();
129 ee_thread_status_t status;
130 if (ReferThreadStatus(thid, &status) == 0) {
131 ChangeThreadPriority(thid, status.current_priority - 1);
132 }
133}
134
135static void PS2AUDIO_Deinitialize(void)
136{
137 deinit_audio_driver();
138}
139
140static bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl)
141{
142 if (init_audio_driver() < 0) {
143 return false;
144 }
145
146 impl->OpenDevice = PS2AUDIO_OpenDevice;
147 impl->PlayDevice = PS2AUDIO_PlayDevice;
148 impl->WaitDevice = PS2AUDIO_WaitDevice;
149 impl->GetDeviceBuf = PS2AUDIO_GetDeviceBuf;
150 impl->CloseDevice = PS2AUDIO_CloseDevice;
151 impl->ThreadInit = PS2AUDIO_ThreadInit;
152 impl->Deinitialize = PS2AUDIO_Deinitialize;
153 impl->OnlyHasDefaultPlaybackDevice = true;
154 return true; // this audio target is available.
155}
156
157AudioBootStrap PS2AUDIO_bootstrap = {
158 "ps2", "PS2 audio driver", PS2AUDIO_Init, false, false
159};
diff --git a/contrib/SDL-3.2.8/src/audio/ps2/SDL_ps2audio.h b/contrib/SDL-3.2.8/src/audio/ps2/SDL_ps2audio.h
new file mode 100644
index 0000000..5a0ecea
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/ps2/SDL_ps2audio.h
@@ -0,0 +1,42 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_ps2audio_h_
24#define SDL_ps2audio_h_
25
26#include "../SDL_sysaudio.h"
27
28#define NUM_BUFFERS 2
29
30struct SDL_PrivateAudioData
31{
32 // The hardware output channel.
33 int channel;
34 // The raw allocated mixing buffer.
35 Uint8 *rawbuf;
36 // Individual mixing buffers.
37 Uint8 *mixbufs[NUM_BUFFERS];
38 // Index of the next available mixing buffer.
39 int next_buffer;
40};
41
42#endif // SDL_ps2audio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/psp/SDL_pspaudio.c b/contrib/SDL-3.2.8/src/audio/psp/SDL_pspaudio.c
new file mode 100644
index 0000000..18546e8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/psp/SDL_pspaudio.c
@@ -0,0 +1,183 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_PSP
24
25#include <stdio.h>
26#include <string.h>
27#include <stdlib.h>
28
29#include "../SDL_audiodev_c.h"
30#include "../SDL_sysaudio.h"
31#include "SDL_pspaudio.h"
32
33#include <pspaudio.h>
34#include <pspthreadman.h>
35
36static bool isBasicAudioConfig(const SDL_AudioSpec *spec)
37{
38 return spec->freq == 44100;
39}
40
41static bool PSPAUDIO_OpenDevice(SDL_AudioDevice *device)
42{
43 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
44 if (!device->hidden) {
45 return false;
46 }
47
48 // device only natively supports S16LSB
49 device->spec.format = SDL_AUDIO_S16LE;
50
51 /* PSP has some limitations with the Audio. It fully supports 44.1KHz (Mono & Stereo),
52 however with frequencies different than 44.1KHz, it just supports Stereo,
53 so a resampler must be done for these scenarios */
54 if (isBasicAudioConfig(&device->spec)) {
55 // The sample count must be a multiple of 64.
56 device->sample_frames = PSP_AUDIO_SAMPLE_ALIGN(device->sample_frames);
57 // The number of channels (1 or 2).
58 device->spec.channels = device->spec.channels == 1 ? 1 : 2;
59 const int format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO;
60 device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->sample_frames, format);
61 } else {
62 // 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000
63 switch (device->spec.freq) {
64 case 8000:
65 case 11025:
66 case 12000:
67 case 16000:
68 case 22050:
69 case 24000:
70 case 32000:
71 case 44100:
72 case 48000:
73 break; // acceptable, keep it
74 default:
75 device->spec.freq = 48000;
76 break;
77 }
78 // The number of samples to output in one output call (min 17, max 4111).
79 device->sample_frames = device->sample_frames < 17 ? 17 : (device->sample_frames > 4111 ? 4111 : device->sample_frames);
80 device->spec.channels = 2; // we're forcing the hardware to stereo.
81 device->hidden->channel = sceAudioSRCChReserve(device->sample_frames, device->spec.freq, 2);
82 }
83
84 if (device->hidden->channel < 0) {
85 return SDL_SetError("Couldn't reserve hardware channel");
86 }
87
88 // Update the fragment size as size in bytes.
89 SDL_UpdatedAudioDeviceFormat(device);
90
91 /* Allocate the mixing buffer. Its size and starting address must
92 be a multiple of 64 bytes. Our sample count is already a multiple of
93 64, so spec->size should be a multiple of 64 as well. */
94 const int mixlen = device->buffer_size * NUM_BUFFERS;
95 device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
96 if (!device->hidden->rawbuf) {
97 return SDL_SetError("Couldn't allocate mixing buffer");
98 }
99
100 SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
101 for (int i = 0; i < NUM_BUFFERS; i++) {
102 device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
103 }
104
105 return true;
106}
107
108static bool PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
109{
110 int rc;
111 if (!isBasicAudioConfig(&device->spec)) {
112 SDL_assert(device->spec.channels == 2);
113 rc = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer);
114 } else {
115 rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer);
116 }
117 return (rc == 0);
118}
119
120static bool PSPAUDIO_WaitDevice(SDL_AudioDevice *device)
121{
122 return true; // Because we block when sending audio, there's no need for this function to do anything.
123}
124
125static Uint8 *PSPAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
126{
127 Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
128 device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
129 return buffer;
130}
131
132static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device)
133{
134 if (device->hidden) {
135 if (device->hidden->channel >= 0) {
136 if (!isBasicAudioConfig(&device->spec)) {
137 sceAudioSRCChRelease();
138 } else {
139 sceAudioChRelease(device->hidden->channel);
140 }
141 device->hidden->channel = -1;
142 }
143
144 if (device->hidden->rawbuf) {
145 SDL_aligned_free(device->hidden->rawbuf);
146 device->hidden->rawbuf = NULL;
147 }
148 SDL_free(device->hidden);
149 device->hidden = NULL;
150 }
151}
152
153static void PSPAUDIO_ThreadInit(SDL_AudioDevice *device)
154{
155 /* Increase the priority of this audio thread by 1 to put it
156 ahead of other SDL threads. */
157 const SceUID thid = sceKernelGetThreadId();
158 SceKernelThreadInfo status;
159 status.size = sizeof(SceKernelThreadInfo);
160 if (sceKernelReferThreadStatus(thid, &status) == 0) {
161 sceKernelChangeThreadPriority(thid, status.currentPriority - 1);
162 }
163}
164
165static bool PSPAUDIO_Init(SDL_AudioDriverImpl *impl)
166{
167 impl->OpenDevice = PSPAUDIO_OpenDevice;
168 impl->PlayDevice = PSPAUDIO_PlayDevice;
169 impl->WaitDevice = PSPAUDIO_WaitDevice;
170 impl->GetDeviceBuf = PSPAUDIO_GetDeviceBuf;
171 impl->CloseDevice = PSPAUDIO_CloseDevice;
172 impl->ThreadInit = PSPAUDIO_ThreadInit;
173 impl->OnlyHasDefaultPlaybackDevice = true;
174 //impl->HasRecordingSupport = true;
175 //impl->OnlyHasDefaultRecordingDevice = true;
176 return true;
177}
178
179AudioBootStrap PSPAUDIO_bootstrap = {
180 "psp", "PSP audio driver", PSPAUDIO_Init, false, false
181};
182
183#endif // SDL_AUDIO_DRIVER_PSP
diff --git a/contrib/SDL-3.2.8/src/audio/psp/SDL_pspaudio.h b/contrib/SDL-3.2.8/src/audio/psp/SDL_pspaudio.h
new file mode 100644
index 0000000..d7b2056
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/psp/SDL_pspaudio.h
@@ -0,0 +1,41 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_pspaudio_h_
23#define SDL_pspaudio_h_
24
25#include "../SDL_sysaudio.h"
26
27#define NUM_BUFFERS 2
28
29struct SDL_PrivateAudioData
30{
31 // The hardware output channel.
32 int channel;
33 // The raw allocated mixing buffer.
34 Uint8 *rawbuf;
35 // Individual mixing buffers.
36 Uint8 *mixbufs[NUM_BUFFERS];
37 // Index of the next available mixing buffer.
38 int next_buffer;
39};
40
41#endif // SDL_pspaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/pulseaudio/SDL_pulseaudio.c b/contrib/SDL-3.2.8/src/audio/pulseaudio/SDL_pulseaudio.c
new file mode 100644
index 0000000..049a5ec
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/pulseaudio/SDL_pulseaudio.c
@@ -0,0 +1,1037 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO
25
26// Allow access to a raw mixing buffer
27
28#ifdef HAVE_SIGNAL_H
29#include <signal.h>
30#endif
31#include <unistd.h>
32#include <sys/types.h>
33
34#include "../SDL_sysaudio.h"
35#include "SDL_pulseaudio.h"
36#include "../../thread/SDL_systhread.h"
37
38#if (PA_PROTOCOL_VERSION < 28)
39typedef void (*pa_operation_notify_cb_t) (pa_operation *o, void *userdata);
40#endif
41
42typedef struct PulseDeviceHandle
43{
44 char *device_path;
45 uint32_t device_index;
46} PulseDeviceHandle;
47
48// should we include monitors in the device list? Set at SDL_Init time
49static bool include_monitors = false;
50
51static pa_threaded_mainloop *pulseaudio_threaded_mainloop = NULL;
52static pa_context *pulseaudio_context = NULL;
53static SDL_Thread *pulseaudio_hotplug_thread = NULL;
54static SDL_AtomicInt pulseaudio_hotplug_thread_active;
55
56// These are the OS identifiers (i.e. ALSA strings)...these are allocated in a callback
57// when the default changes, and noticed by the hotplug thread when it alerts SDL
58// to the change.
59static char *default_sink_path = NULL;
60static char *default_source_path = NULL;
61static bool default_sink_changed = false;
62static bool default_source_changed = false;
63
64
65static const char *(*PULSEAUDIO_pa_get_library_version)(void);
66static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)(
67 pa_channel_map *, unsigned, pa_channel_map_def_t);
68static const char *(*PULSEAUDIO_pa_strerror)(int);
69static pa_proplist *(*PULSEAUDIO_pa_proplist_new)(void);
70static void (*PULSEAUDIO_pa_proplist_free)(pa_proplist *);
71static int (*PULSEAUDIO_pa_proplist_sets)(pa_proplist *, const char *, const char *);
72
73static pa_threaded_mainloop *(*PULSEAUDIO_pa_threaded_mainloop_new)(void);
74static void (*PULSEAUDIO_pa_threaded_mainloop_set_name)(pa_threaded_mainloop *, const char *);
75static pa_mainloop_api *(*PULSEAUDIO_pa_threaded_mainloop_get_api)(pa_threaded_mainloop *);
76static int (*PULSEAUDIO_pa_threaded_mainloop_start)(pa_threaded_mainloop *);
77static void (*PULSEAUDIO_pa_threaded_mainloop_stop)(pa_threaded_mainloop *);
78static void (*PULSEAUDIO_pa_threaded_mainloop_lock)(pa_threaded_mainloop *);
79static void (*PULSEAUDIO_pa_threaded_mainloop_unlock)(pa_threaded_mainloop *);
80static void (*PULSEAUDIO_pa_threaded_mainloop_wait)(pa_threaded_mainloop *);
81static void (*PULSEAUDIO_pa_threaded_mainloop_signal)(pa_threaded_mainloop *, int);
82static void (*PULSEAUDIO_pa_threaded_mainloop_free)(pa_threaded_mainloop *);
83
84static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state)(
85 const pa_operation *);
86static void (*PULSEAUDIO_pa_operation_set_state_callback)(pa_operation *, pa_operation_notify_cb_t, void *);
87static void (*PULSEAUDIO_pa_operation_cancel)(pa_operation *);
88static void (*PULSEAUDIO_pa_operation_unref)(pa_operation *);
89
90static pa_context *(*PULSEAUDIO_pa_context_new_with_proplist)(pa_mainloop_api *,
91 const char *,
92 const pa_proplist *);
93static void (*PULSEAUDIO_pa_context_set_state_callback)(pa_context *, pa_context_notify_cb_t, void *);
94static int (*PULSEAUDIO_pa_context_connect)(pa_context *, const char *,
95 pa_context_flags_t, const pa_spawn_api *);
96static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_list)(pa_context *, pa_sink_info_cb_t, void *);
97static pa_operation *(*PULSEAUDIO_pa_context_get_source_info_list)(pa_context *, pa_source_info_cb_t, void *);
98static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_by_index)(pa_context *, uint32_t, pa_sink_info_cb_t, void *);
99static pa_operation *(*PULSEAUDIO_pa_context_get_source_info_by_index)(pa_context *, uint32_t, pa_source_info_cb_t, void *);
100static pa_context_state_t (*PULSEAUDIO_pa_context_get_state)(const pa_context *);
101static pa_operation *(*PULSEAUDIO_pa_context_subscribe)(pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *);
102static void (*PULSEAUDIO_pa_context_set_subscribe_callback)(pa_context *, pa_context_subscribe_cb_t, void *);
103static void (*PULSEAUDIO_pa_context_disconnect)(pa_context *);
104static void (*PULSEAUDIO_pa_context_unref)(pa_context *);
105
106static pa_stream *(*PULSEAUDIO_pa_stream_new)(pa_context *, const char *,
107 const pa_sample_spec *, const pa_channel_map *);
108static void (*PULSEAUDIO_pa_stream_set_state_callback)(pa_stream *, pa_stream_notify_cb_t, void *);
109static int (*PULSEAUDIO_pa_stream_connect_playback)(pa_stream *, const char *,
110 const pa_buffer_attr *, pa_stream_flags_t, const pa_cvolume *, pa_stream *);
111static int (*PULSEAUDIO_pa_stream_connect_record)(pa_stream *, const char *,
112 const pa_buffer_attr *, pa_stream_flags_t);
113static const pa_buffer_attr *(*PULSEAUDIO_pa_stream_get_buffer_attr)(pa_stream *);
114static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state)(const pa_stream *);
115static size_t (*PULSEAUDIO_pa_stream_writable_size)(const pa_stream *);
116static size_t (*PULSEAUDIO_pa_stream_readable_size)(const pa_stream *);
117static int (*PULSEAUDIO_pa_stream_write)(pa_stream *, const void *, size_t,
118 pa_free_cb_t, int64_t, pa_seek_mode_t);
119static int (*PULSEAUDIO_pa_stream_begin_write)(pa_stream *, void **, size_t *);
120static pa_operation *(*PULSEAUDIO_pa_stream_drain)(pa_stream *,
121 pa_stream_success_cb_t, void *);
122static int (*PULSEAUDIO_pa_stream_peek)(pa_stream *, const void **, size_t *);
123static int (*PULSEAUDIO_pa_stream_drop)(pa_stream *);
124static pa_operation *(*PULSEAUDIO_pa_stream_flush)(pa_stream *,
125 pa_stream_success_cb_t, void *);
126static int (*PULSEAUDIO_pa_stream_disconnect)(pa_stream *);
127static void (*PULSEAUDIO_pa_stream_unref)(pa_stream *);
128static void (*PULSEAUDIO_pa_stream_set_write_callback)(pa_stream *, pa_stream_request_cb_t, void *);
129static void (*PULSEAUDIO_pa_stream_set_read_callback)(pa_stream *, pa_stream_request_cb_t, void *);
130static pa_operation *(*PULSEAUDIO_pa_context_get_server_info)(pa_context *, pa_server_info_cb_t, void *);
131
132static bool load_pulseaudio_syms(void);
133
134#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
135
136static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC;
137static SDL_SharedObject *pulseaudio_handle = NULL;
138
139static bool load_pulseaudio_sym(const char *fn, void **addr)
140{
141 *addr = SDL_LoadFunction(pulseaudio_handle, fn);
142 if (!*addr) {
143 // Don't call SDL_SetError(): SDL_LoadFunction already did.
144 return false;
145 }
146
147 return true;
148}
149
150// cast funcs to char* first, to please GCC's strict aliasing rules.
151#define SDL_PULSEAUDIO_SYM(x) \
152 if (!load_pulseaudio_sym(#x, (void **)(char *)&PULSEAUDIO_##x)) \
153 return false
154
155static void UnloadPulseAudioLibrary(void)
156{
157 if (pulseaudio_handle) {
158 SDL_UnloadObject(pulseaudio_handle);
159 pulseaudio_handle = NULL;
160 }
161}
162
163static bool LoadPulseAudioLibrary(void)
164{
165 bool result = true;
166 if (!pulseaudio_handle) {
167 pulseaudio_handle = SDL_LoadObject(pulseaudio_library);
168 if (!pulseaudio_handle) {
169 result = false;
170 // Don't call SDL_SetError(): SDL_LoadObject already did.
171 } else {
172 result = load_pulseaudio_syms();
173 if (!result) {
174 UnloadPulseAudioLibrary();
175 }
176 }
177 }
178 return result;
179}
180
181#else
182
183#define SDL_PULSEAUDIO_SYM(x) PULSEAUDIO_##x = x
184
185static void UnloadPulseAudioLibrary(void)
186{
187}
188
189static bool LoadPulseAudioLibrary(void)
190{
191 load_pulseaudio_syms();
192 return true;
193}
194
195#endif // SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
196
197static bool load_pulseaudio_syms(void)
198{
199 SDL_PULSEAUDIO_SYM(pa_get_library_version);
200 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_new);
201 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_get_api);
202 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_start);
203 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_stop);
204 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_lock);
205 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_unlock);
206 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_wait);
207 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_signal);
208 SDL_PULSEAUDIO_SYM(pa_threaded_mainloop_free);
209 SDL_PULSEAUDIO_SYM(pa_operation_get_state);
210 SDL_PULSEAUDIO_SYM(pa_operation_cancel);
211 SDL_PULSEAUDIO_SYM(pa_operation_unref);
212 SDL_PULSEAUDIO_SYM(pa_context_new_with_proplist);
213 SDL_PULSEAUDIO_SYM(pa_context_set_state_callback);
214 SDL_PULSEAUDIO_SYM(pa_context_connect);
215 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
216 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_list);
217 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_by_index);
218 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_by_index);
219 SDL_PULSEAUDIO_SYM(pa_context_get_state);
220 SDL_PULSEAUDIO_SYM(pa_context_subscribe);
221 SDL_PULSEAUDIO_SYM(pa_context_set_subscribe_callback);
222 SDL_PULSEAUDIO_SYM(pa_context_disconnect);
223 SDL_PULSEAUDIO_SYM(pa_context_unref);
224 SDL_PULSEAUDIO_SYM(pa_stream_new);
225 SDL_PULSEAUDIO_SYM(pa_stream_set_state_callback);
226 SDL_PULSEAUDIO_SYM(pa_stream_connect_playback);
227 SDL_PULSEAUDIO_SYM(pa_stream_connect_record);
228 SDL_PULSEAUDIO_SYM(pa_stream_get_buffer_attr);
229 SDL_PULSEAUDIO_SYM(pa_stream_get_state);
230 SDL_PULSEAUDIO_SYM(pa_stream_writable_size);
231 SDL_PULSEAUDIO_SYM(pa_stream_readable_size);
232 SDL_PULSEAUDIO_SYM(pa_stream_begin_write);
233 SDL_PULSEAUDIO_SYM(pa_stream_write);
234 SDL_PULSEAUDIO_SYM(pa_stream_drain);
235 SDL_PULSEAUDIO_SYM(pa_stream_disconnect);
236 SDL_PULSEAUDIO_SYM(pa_stream_peek);
237 SDL_PULSEAUDIO_SYM(pa_stream_drop);
238 SDL_PULSEAUDIO_SYM(pa_stream_flush);
239 SDL_PULSEAUDIO_SYM(pa_stream_unref);
240 SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto);
241 SDL_PULSEAUDIO_SYM(pa_strerror);
242 SDL_PULSEAUDIO_SYM(pa_stream_set_write_callback);
243 SDL_PULSEAUDIO_SYM(pa_stream_set_read_callback);
244 SDL_PULSEAUDIO_SYM(pa_context_get_server_info);
245 SDL_PULSEAUDIO_SYM(pa_proplist_new);
246 SDL_PULSEAUDIO_SYM(pa_proplist_free);
247 SDL_PULSEAUDIO_SYM(pa_proplist_sets);
248
249 // optional
250#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
251 load_pulseaudio_sym("pa_operation_set_state_callback", (void **)(char *)&PULSEAUDIO_pa_operation_set_state_callback); // needs pulseaudio 4.0
252 load_pulseaudio_sym("pa_threaded_mainloop_set_name", (void **)(char *)&PULSEAUDIO_pa_threaded_mainloop_set_name); // needs pulseaudio 5.0
253#elif (PA_PROTOCOL_VERSION >= 29)
254 PULSEAUDIO_pa_operation_set_state_callback = pa_operation_set_state_callback;
255 PULSEAUDIO_pa_threaded_mainloop_set_name = pa_threaded_mainloop_set_name;
256#elif (PA_PROTOCOL_VERSION >= 28)
257 PULSEAUDIO_pa_operation_set_state_callback = pa_operation_set_state_callback;
258 PULSEAUDIO_pa_threaded_mainloop_set_name = NULL;
259#else
260 PULSEAUDIO_pa_operation_set_state_callback = NULL;
261 PULSEAUDIO_pa_threaded_mainloop_set_name = NULL;
262#endif
263
264 return true;
265}
266
267static const char *getAppName(void)
268{
269 return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
270}
271
272static void OperationStateChangeCallback(pa_operation *o, void *userdata)
273{
274 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details.
275}
276
277/* This function assume you are holding `mainloop`'s lock. The operation is unref'd in here, assuming
278 you did the work in the callback and just want to know it's done, though. */
279static void WaitForPulseOperation(pa_operation *o)
280{
281 // This checks for NO errors currently. Either fix that, check results elsewhere, or do things you don't care about.
282 SDL_assert(pulseaudio_threaded_mainloop != NULL);
283 if (o) {
284 // note that if PULSEAUDIO_pa_operation_set_state_callback == NULL, then `o` must have a callback that will signal pulseaudio_threaded_mainloop.
285 // If not, on really old (earlier PulseAudio 4.0, from the year 2013!) installs, this call will block forever.
286 // On more modern installs, we won't ever block forever, and maybe be more efficient, thanks to pa_operation_set_state_callback.
287 // WARNING: at the time of this writing: the Steam Runtime is still on PulseAudio 1.1!
288 if (PULSEAUDIO_pa_operation_set_state_callback) {
289 PULSEAUDIO_pa_operation_set_state_callback(o, OperationStateChangeCallback, NULL);
290 }
291 while (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING) {
292 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); // this releases the lock and blocks on an internal condition variable.
293 }
294 PULSEAUDIO_pa_operation_unref(o);
295 }
296}
297
298static void DisconnectFromPulseServer(void)
299{
300 if (pulseaudio_threaded_mainloop) {
301 PULSEAUDIO_pa_threaded_mainloop_stop(pulseaudio_threaded_mainloop);
302 }
303 if (pulseaudio_context) {
304 PULSEAUDIO_pa_context_disconnect(pulseaudio_context);
305 PULSEAUDIO_pa_context_unref(pulseaudio_context);
306 pulseaudio_context = NULL;
307 }
308 if (pulseaudio_threaded_mainloop) {
309 PULSEAUDIO_pa_threaded_mainloop_free(pulseaudio_threaded_mainloop);
310 pulseaudio_threaded_mainloop = NULL;
311 }
312}
313
314static void PulseContextStateChangeCallback(pa_context *context, void *userdata)
315{
316 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details.
317}
318
319static bool ConnectToPulseServer(void)
320{
321 pa_mainloop_api *mainloop_api = NULL;
322 pa_proplist *proplist = NULL;
323 const char *icon_name;
324 int state = 0;
325
326 SDL_assert(pulseaudio_threaded_mainloop == NULL);
327 SDL_assert(pulseaudio_context == NULL);
328
329 // Set up a new main loop
330 pulseaudio_threaded_mainloop = PULSEAUDIO_pa_threaded_mainloop_new();
331 if (!pulseaudio_threaded_mainloop) {
332 return SDL_SetError("pa_threaded_mainloop_new() failed");
333 }
334
335 if (PULSEAUDIO_pa_threaded_mainloop_set_name) {
336 PULSEAUDIO_pa_threaded_mainloop_set_name(pulseaudio_threaded_mainloop, "PulseMainloop");
337 }
338
339 if (PULSEAUDIO_pa_threaded_mainloop_start(pulseaudio_threaded_mainloop) < 0) {
340 PULSEAUDIO_pa_threaded_mainloop_free(pulseaudio_threaded_mainloop);
341 pulseaudio_threaded_mainloop = NULL;
342 return SDL_SetError("pa_threaded_mainloop_start() failed");
343 }
344
345 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
346
347 mainloop_api = PULSEAUDIO_pa_threaded_mainloop_get_api(pulseaudio_threaded_mainloop);
348 SDL_assert(mainloop_api != NULL); // this never fails, right?
349
350 proplist = PULSEAUDIO_pa_proplist_new();
351 if (!proplist) {
352 SDL_SetError("pa_proplist_new() failed");
353 goto failed;
354 }
355
356 icon_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME);
357 if (!icon_name || *icon_name == '\0') {
358 icon_name = "applications-games";
359 }
360 PULSEAUDIO_pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, icon_name);
361
362 pulseaudio_context = PULSEAUDIO_pa_context_new_with_proplist(mainloop_api, getAppName(), proplist);
363 if (!pulseaudio_context) {
364 SDL_SetError("pa_context_new_with_proplist() failed");
365 goto failed;
366 }
367 PULSEAUDIO_pa_proplist_free(proplist);
368
369 PULSEAUDIO_pa_context_set_state_callback(pulseaudio_context, PulseContextStateChangeCallback, NULL);
370
371 // Connect to the PulseAudio server
372 if (PULSEAUDIO_pa_context_connect(pulseaudio_context, NULL, 0, NULL) < 0) {
373 SDL_SetError("Could not setup connection to PulseAudio");
374 goto failed;
375 }
376
377 state = PULSEAUDIO_pa_context_get_state(pulseaudio_context);
378 while (PA_CONTEXT_IS_GOOD(state) && (state != PA_CONTEXT_READY)) {
379 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
380 state = PULSEAUDIO_pa_context_get_state(pulseaudio_context);
381 }
382
383 if (state != PA_CONTEXT_READY) {
384 SDL_SetError("Could not connect to PulseAudio");
385 goto failed;
386 }
387
388 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
389
390 return true; // connected and ready!
391
392failed:
393 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
394 DisconnectFromPulseServer();
395 return false;
396}
397
398static void WriteCallback(pa_stream *p, size_t nbytes, void *userdata)
399{
400 struct SDL_PrivateAudioData *h = (struct SDL_PrivateAudioData *)userdata;
401 //SDL_Log("PULSEAUDIO WRITE CALLBACK! nbytes=%u", (unsigned int) nbytes);
402 h->bytes_requested += nbytes;
403 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
404}
405
406// This function waits until it is possible to write a full sound buffer
407static bool PULSEAUDIO_WaitDevice(SDL_AudioDevice *device)
408{
409 struct SDL_PrivateAudioData *h = device->hidden;
410 bool result = true;
411
412 //SDL_Log("PULSEAUDIO PLAYDEVICE START! mixlen=%d", available);
413
414 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
415
416 while (!SDL_GetAtomicInt(&device->shutdown) && (h->bytes_requested == 0)) {
417 //SDL_Log("PULSEAUDIO WAIT IN WAITDEVICE!");
418 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
419
420 if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
421 //SDL_Log("PULSEAUDIO DEVICE FAILURE IN WAITDEVICE!");
422 result = false;
423 break;
424 }
425 }
426
427 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
428
429 return result;
430}
431
432static bool PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
433{
434 struct SDL_PrivateAudioData *h = device->hidden;
435
436 //SDL_Log("PULSEAUDIO PLAYDEVICE START! mixlen=%d", available);
437
438 SDL_assert(h->bytes_requested >= buffer_size);
439
440 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
441 const int rc = PULSEAUDIO_pa_stream_write(h->stream, buffer, buffer_size, NULL, 0LL, PA_SEEK_RELATIVE);
442 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
443
444 if (rc < 0) {
445 return false;
446 }
447
448 //SDL_Log("PULSEAUDIO FEED! nbytes=%d", buffer_size);
449 h->bytes_requested -= buffer_size;
450
451 //SDL_Log("PULSEAUDIO PLAYDEVICE END! written=%d", written);
452 return true;
453}
454
455static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
456{
457 struct SDL_PrivateAudioData *h = device->hidden;
458 const size_t reqsize = (size_t) SDL_min(*buffer_size, h->bytes_requested);
459 size_t nbytes = reqsize;
460 void *data = NULL;
461 if (PULSEAUDIO_pa_stream_begin_write(h->stream, &data, &nbytes) == 0) {
462 *buffer_size = (int) nbytes;
463 return (Uint8 *) data;
464 }
465
466 // don't know why this would fail, but we'll fall back just in case.
467 *buffer_size = (int) reqsize;
468 return device->hidden->mixbuf;
469}
470
471static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata)
472{
473 //SDL_Log("PULSEAUDIO READ CALLBACK! nbytes=%u", (unsigned int) nbytes);
474 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // the recording code queries what it needs, we just need to signal to end any wait
475}
476
477static bool PULSEAUDIO_WaitRecordingDevice(SDL_AudioDevice *device)
478{
479 struct SDL_PrivateAudioData *h = device->hidden;
480
481 if (h->recordingbuf) {
482 return true; // there's still data available to read.
483 }
484
485 bool result = true;
486
487 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
488
489 while (!SDL_GetAtomicInt(&device->shutdown)) {
490 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
491 if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
492 //SDL_Log("PULSEAUDIO DEVICE FAILURE IN WAITRECORDINGDEVICE!");
493 result = false;
494 break;
495 } else if (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0) {
496 // a new fragment is available!
497 const void *data = NULL;
498 size_t nbytes = 0;
499 PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
500 SDL_assert(nbytes > 0);
501 if (!data) { // If NULL, then the buffer had a hole, ignore that
502 PULSEAUDIO_pa_stream_drop(h->stream); // drop this fragment.
503 } else {
504 // store this fragment's data for use with RecordDevice
505 //SDL_Log("PULSEAUDIO: recorded %d new bytes", (int) nbytes);
506 h->recordingbuf = (const Uint8 *)data;
507 h->recordinglen = nbytes;
508 break;
509 }
510 }
511 }
512
513 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
514
515 return result;
516}
517
518static int PULSEAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
519{
520 struct SDL_PrivateAudioData *h = device->hidden;
521
522 if (h->recordingbuf) {
523 const int cpy = SDL_min(buflen, h->recordinglen);
524 if (cpy > 0) {
525 //SDL_Log("PULSEAUDIO: fed %d recorded bytes", cpy);
526 SDL_memcpy(buffer, h->recordingbuf, cpy);
527 h->recordingbuf += cpy;
528 h->recordinglen -= cpy;
529 }
530 if (h->recordinglen == 0) {
531 h->recordingbuf = NULL;
532 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); // don't know if you _have_ to lock for this, but just in case.
533 PULSEAUDIO_pa_stream_drop(h->stream); // done with this fragment.
534 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
535 }
536 return cpy; // new data, return it.
537 }
538
539 return 0;
540}
541
542static void PULSEAUDIO_FlushRecording(SDL_AudioDevice *device)
543{
544 struct SDL_PrivateAudioData *h = device->hidden;
545 const void *data = NULL;
546 size_t nbytes = 0, buflen = 0;
547
548 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
549
550 if (h->recordingbuf) {
551 PULSEAUDIO_pa_stream_drop(h->stream);
552 h->recordingbuf = NULL;
553 h->recordinglen = 0;
554 }
555
556 buflen = PULSEAUDIO_pa_stream_readable_size(h->stream);
557 while (!SDL_GetAtomicInt(&device->shutdown) && (buflen > 0)) {
558 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
559 if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
560 //SDL_Log("PULSEAUDIO DEVICE FAILURE IN FLUSHRECORDING!");
561 SDL_AudioDeviceDisconnected(device);
562 break;
563 }
564
565 // a fragment of audio present before FlushCapture was call is
566 // still available! Just drop it.
567 PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
568 PULSEAUDIO_pa_stream_drop(h->stream);
569 buflen -= nbytes;
570 }
571
572 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
573}
574
575static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *device)
576{
577 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
578
579 if (device->hidden->stream) {
580 if (device->hidden->recordingbuf) {
581 PULSEAUDIO_pa_stream_drop(device->hidden->stream);
582 }
583 PULSEAUDIO_pa_stream_disconnect(device->hidden->stream);
584 PULSEAUDIO_pa_stream_unref(device->hidden->stream);
585 }
586 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // in case the device thread is waiting somewhere, this will unblock it.
587 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
588
589 SDL_free(device->hidden->mixbuf);
590 SDL_free(device->hidden);
591}
592
593static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata)
594{
595 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // just signal any waiting code, it can look up the details.
596}
597
598static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device)
599{
600 const bool recording = device->recording;
601 struct SDL_PrivateAudioData *h = NULL;
602 SDL_AudioFormat test_format;
603 const SDL_AudioFormat *closefmts;
604 pa_sample_spec paspec;
605 pa_buffer_attr paattr;
606 pa_channel_map pacmap;
607 pa_stream_flags_t flags = 0;
608 int format = PA_SAMPLE_INVALID;
609 bool result = true;
610
611 SDL_assert(pulseaudio_threaded_mainloop != NULL);
612 SDL_assert(pulseaudio_context != NULL);
613
614 // Initialize all variables that we clean on shutdown
615 h = device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
616 if (!device->hidden) {
617 return false;
618 }
619
620 // Try for a closest match on audio format
621 closefmts = SDL_ClosestAudioFormats(device->spec.format);
622 while ((test_format = *(closefmts++)) != 0) {
623#ifdef DEBUG_AUDIO
624 SDL_Log("pulseaudio: Trying format 0x%4.4x", test_format);
625#endif
626 switch (test_format) {
627 case SDL_AUDIO_U8:
628 format = PA_SAMPLE_U8;
629 break;
630 case SDL_AUDIO_S16LE:
631 format = PA_SAMPLE_S16LE;
632 break;
633 case SDL_AUDIO_S16BE:
634 format = PA_SAMPLE_S16BE;
635 break;
636 case SDL_AUDIO_S32LE:
637 format = PA_SAMPLE_S32LE;
638 break;
639 case SDL_AUDIO_S32BE:
640 format = PA_SAMPLE_S32BE;
641 break;
642 case SDL_AUDIO_F32LE:
643 format = PA_SAMPLE_FLOAT32LE;
644 break;
645 case SDL_AUDIO_F32BE:
646 format = PA_SAMPLE_FLOAT32BE;
647 break;
648 default:
649 continue;
650 }
651 break;
652 }
653 if (!test_format) {
654 return SDL_SetError("pulseaudio: Unsupported audio format");
655 }
656 device->spec.format = test_format;
657 paspec.format = format;
658
659 // Calculate the final parameters for this audio specification
660 SDL_UpdatedAudioDeviceFormat(device);
661
662 // Allocate mixing buffer
663 if (!recording) {
664 h->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
665 if (!h->mixbuf) {
666 return false;
667 }
668 SDL_memset(h->mixbuf, device->silence_value, device->buffer_size);
669 }
670
671 paspec.channels = device->spec.channels;
672 paspec.rate = device->spec.freq;
673
674 // Reduced prebuffering compared to the defaults.
675 paattr.fragsize = device->buffer_size; // despite the name, this is only used for recording devices, according to PulseAudio docs!
676 paattr.tlength = device->buffer_size;
677 paattr.prebuf = -1;
678 paattr.maxlength = -1;
679 paattr.minreq = -1;
680 flags |= PA_STREAM_ADJUST_LATENCY;
681
682 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
683
684 const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME);
685 // The SDL ALSA output hints us that we use Windows' channel mapping
686 // https://bugzilla.libsdl.org/show_bug.cgi?id=110
687 PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->spec.channels, PA_CHANNEL_MAP_WAVEEX);
688
689 h->stream = PULSEAUDIO_pa_stream_new(
690 pulseaudio_context,
691 (name && *name) ? name : "Audio Stream", // stream description
692 &paspec, // sample format spec
693 &pacmap // channel map
694 );
695
696 if (!h->stream) {
697 result = SDL_SetError("Could not set up PulseAudio stream");
698 } else {
699 int rc;
700
701 PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL);
702
703 // SDL manages device moves if the default changes, so don't ever let Pulse automatically migrate this stream.
704 flags |= PA_STREAM_DONT_MOVE;
705
706 const char *device_path = ((PulseDeviceHandle *) device->handle)->device_path;
707 if (recording) {
708 PULSEAUDIO_pa_stream_set_read_callback(h->stream, ReadCallback, h);
709 rc = PULSEAUDIO_pa_stream_connect_record(h->stream, device_path, &paattr, flags);
710 } else {
711 PULSEAUDIO_pa_stream_set_write_callback(h->stream, WriteCallback, h);
712 rc = PULSEAUDIO_pa_stream_connect_playback(h->stream, device_path, &paattr, flags, NULL, NULL);
713 }
714
715 if (rc < 0) {
716 result = SDL_SetError("Could not connect PulseAudio stream");
717 } else {
718 int state = PULSEAUDIO_pa_stream_get_state(h->stream);
719 while (PA_STREAM_IS_GOOD(state) && (state != PA_STREAM_READY)) {
720 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
721 state = PULSEAUDIO_pa_stream_get_state(h->stream);
722 }
723
724 if (!PA_STREAM_IS_GOOD(state)) {
725 result = SDL_SetError("Could not connect PulseAudio stream");
726 } else {
727 const pa_buffer_attr *actual_bufattr = PULSEAUDIO_pa_stream_get_buffer_attr(h->stream);
728 if (!actual_bufattr) {
729 result = SDL_SetError("Could not determine connected PulseAudio stream's buffer attributes");
730 } else {
731 device->buffer_size = (int) recording ? actual_bufattr->tlength : actual_bufattr->fragsize;
732 device->sample_frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec);
733 }
734 }
735 }
736 }
737
738 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
739
740 // We're (hopefully) ready to rock and roll. :-)
741 return result;
742}
743
744// device handles are device index + 1, cast to void*, so we never pass a NULL.
745
746static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format)
747{
748 switch (format) {
749 case PA_SAMPLE_U8:
750 return SDL_AUDIO_U8;
751 case PA_SAMPLE_S16LE:
752 return SDL_AUDIO_S16LE;
753 case PA_SAMPLE_S16BE:
754 return SDL_AUDIO_S16BE;
755 case PA_SAMPLE_S32LE:
756 return SDL_AUDIO_S32LE;
757 case PA_SAMPLE_S32BE:
758 return SDL_AUDIO_S32BE;
759 case PA_SAMPLE_FLOAT32LE:
760 return SDL_AUDIO_F32LE;
761 case PA_SAMPLE_FLOAT32BE:
762 return SDL_AUDIO_F32BE;
763 default:
764 return 0;
765 }
766}
767
768static void AddPulseAudioDevice(const bool recording, const char *description, const char *name, const uint32_t index, const pa_sample_spec *sample_spec)
769{
770 SDL_AudioSpec spec;
771 SDL_zero(spec);
772 spec.format = PulseFormatToSDLFormat(sample_spec->format);
773 spec.channels = sample_spec->channels;
774 spec.freq = sample_spec->rate;
775 PulseDeviceHandle *handle = (PulseDeviceHandle *) SDL_malloc(sizeof (PulseDeviceHandle));
776 if (handle) {
777 handle->device_path = SDL_strdup(name);
778 if (!handle->device_path) {
779 SDL_free(handle);
780 } else {
781 handle->device_index = index;
782 SDL_AddAudioDevice(recording, description, &spec, handle);
783 }
784 }
785}
786
787// This is called when PulseAudio adds an playback ("sink") device.
788static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
789{
790 if (i) {
791 AddPulseAudioDevice(false, i->description, i->name, i->index, &i->sample_spec);
792 }
793 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
794}
795
796// This is called when PulseAudio adds a recording ("source") device.
797static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
798{
799 // Maybe skip "monitor" sources. These are just output from other sinks.
800 if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) {
801 AddPulseAudioDevice(true, i->description, i->name, i->index, &i->sample_spec);
802 }
803 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
804}
805
806static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data)
807{
808 //SDL_Log("PULSEAUDIO ServerInfoCallback!");
809
810 if (!default_sink_path || (SDL_strcmp(default_sink_path, i->default_sink_name) != 0)) {
811 char *str = SDL_strdup(i->default_sink_name);
812 if (str) {
813 SDL_free(default_sink_path);
814 default_sink_path = str;
815 default_sink_changed = true;
816 }
817 }
818
819 if (!default_source_path || (SDL_strcmp(default_source_path, i->default_source_name) != 0)) {
820 char *str = SDL_strdup(i->default_source_name);
821 if (str) {
822 SDL_free(default_source_path);
823 default_source_path = str;
824 default_source_changed = true;
825 }
826 }
827
828 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
829}
830
831static bool FindAudioDeviceByIndex(SDL_AudioDevice *device, void *userdata)
832{
833 const uint32_t idx = (uint32_t) (uintptr_t) userdata;
834 const PulseDeviceHandle *handle = (const PulseDeviceHandle *) device->handle;
835 return (handle->device_index == idx);
836}
837
838static bool FindAudioDeviceByPath(SDL_AudioDevice *device, void *userdata)
839{
840 const char *path = (const char *) userdata;
841 const PulseDeviceHandle *handle = (const PulseDeviceHandle *) device->handle;
842 return (SDL_strcmp(handle->device_path, path) == 0);
843}
844
845// This is called when PulseAudio has a device connected/removed/changed.
846static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data)
847{
848 const bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW);
849 const bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE);
850 const bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE);
851
852 if (added || removed || changed) { // we only care about add/remove events.
853 const bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK);
854 const bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
855
856 if (changed) {
857 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL));
858 }
859
860 /* adds need sink details from the PulseAudio server. Another callback...
861 (just unref all these operations right away, because we aren't going to wait on them
862 and their callbacks will handle any work, so they can free as soon as that happens.) */
863 if (added && sink) {
864 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, NULL));
865 } else if (added && source) {
866 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, NULL));
867 } else if (removed && (sink || source)) {
868 // removes we can handle just with the device index.
869 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByIndex, (void *)(uintptr_t)idx));
870 }
871 }
872 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
873}
874
875static bool CheckDefaultDevice(const bool changed, char *device_path)
876{
877 if (!changed) {
878 return false; // nothing's happening, leave the flag marked as unchanged.
879 } else if (!device_path) {
880 return true; // check again later, we don't have a device name...
881 }
882
883 SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, device_path);
884 if (device) { // if NULL, we might still be waiting for a SinkInfoCallback or something, we'll try later.
885 SDL_DefaultAudioDeviceChanged(device);
886 return false; // changing complete, set flag to unchanged for future tests.
887 }
888 return true; // couldn't find the changed device, leave it marked as changed to try again later.
889}
890
891// this runs as a thread while the Pulse target is initialized to catch hotplug events.
892static int SDLCALL HotplugThread(void *data)
893{
894 pa_operation *op;
895
896 SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_LOW);
897 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
898 PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL);
899
900 // don't WaitForPulseOperation on the subscription; when it's done we'll be able to get hotplug events, but waiting doesn't changing anything.
901 op = PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL);
902
903 SDL_SignalSemaphore((SDL_Semaphore *) data);
904
905 while (SDL_GetAtomicInt(&pulseaudio_hotplug_thread_active)) {
906 PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
907 if (op && PULSEAUDIO_pa_operation_get_state(op) != PA_OPERATION_RUNNING) {
908 PULSEAUDIO_pa_operation_unref(op);
909 op = NULL;
910 }
911
912 // Update default devices; don't hold the pulse lock during this, since it could deadlock vs a playing device that we're about to lock here.
913 bool check_default_sink = default_sink_changed;
914 bool check_default_source = default_source_changed;
915 char *current_default_sink = check_default_sink ? SDL_strdup(default_sink_path) : NULL;
916 char *current_default_source = check_default_source ? SDL_strdup(default_source_path) : NULL;
917 default_sink_changed = default_source_changed = false;
918 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
919 check_default_sink = CheckDefaultDevice(check_default_sink, current_default_sink);
920 check_default_source = CheckDefaultDevice(check_default_source, current_default_source);
921 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
922
923 // free our copies (which will be NULL if nothing changed)
924 SDL_free(current_default_sink);
925 SDL_free(current_default_source);
926
927 // set these to true if we didn't handle the change OR there was _another_ change while we were working unlocked.
928 default_sink_changed = (default_sink_changed || check_default_sink);
929 default_source_changed = (default_source_changed || check_default_source);
930 }
931
932 if (op) {
933 PULSEAUDIO_pa_operation_unref(op);
934 }
935
936 PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL);
937 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
938 return 0;
939}
940
941static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
942{
943 SDL_Semaphore *ready_sem = SDL_CreateSemaphore(0);
944
945 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
946 WaitForPulseOperation(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL));
947 WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_list(pulseaudio_context, SinkInfoCallback, NULL));
948 WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_list(pulseaudio_context, SourceInfoCallback, NULL));
949 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
950
951 if (default_sink_path) {
952 *default_playback = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, default_sink_path);
953 }
954
955 if (default_source_path) {
956 *default_recording = SDL_FindPhysicalAudioDeviceByCallback(FindAudioDeviceByPath, default_source_path);
957 }
958
959 // ok, we have a sane list, let's set up hotplug notifications now...
960 SDL_SetAtomicInt(&pulseaudio_hotplug_thread_active, 1);
961 pulseaudio_hotplug_thread = SDL_CreateThread(HotplugThread, "PulseHotplug", ready_sem);
962 if (pulseaudio_hotplug_thread) {
963 SDL_WaitSemaphore(ready_sem); // wait until the thread hits it's main loop.
964 } else {
965 SDL_SetAtomicInt(&pulseaudio_hotplug_thread_active, 0); // thread failed to start, we'll go on without hotplug.
966 }
967
968 SDL_DestroySemaphore(ready_sem);
969}
970
971static void PULSEAUDIO_FreeDeviceHandle(SDL_AudioDevice *device)
972{
973 PulseDeviceHandle *handle = (PulseDeviceHandle *) device->handle;
974 SDL_free(handle->device_path);
975 SDL_free(handle);
976}
977
978static void PULSEAUDIO_DeinitializeStart(void)
979{
980 if (pulseaudio_hotplug_thread) {
981 PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
982 SDL_SetAtomicInt(&pulseaudio_hotplug_thread_active, 0);
983 PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);
984 PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
985 SDL_WaitThread(pulseaudio_hotplug_thread, NULL);
986 pulseaudio_hotplug_thread = NULL;
987 }
988}
989
990static void PULSEAUDIO_Deinitialize(void)
991{
992 DisconnectFromPulseServer();
993
994 SDL_free(default_sink_path);
995 default_sink_path = NULL;
996 default_sink_changed = false;
997 SDL_free(default_source_path);
998 default_source_path = NULL;
999 default_source_changed = false;
1000
1001 UnloadPulseAudioLibrary();
1002}
1003
1004static bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl)
1005{
1006 if (!LoadPulseAudioLibrary()) {
1007 return false;
1008 } else if (!ConnectToPulseServer()) {
1009 UnloadPulseAudioLibrary();
1010 return false;
1011 }
1012
1013 include_monitors = SDL_GetHintBoolean(SDL_HINT_AUDIO_INCLUDE_MONITORS, false);
1014
1015 impl->DetectDevices = PULSEAUDIO_DetectDevices;
1016 impl->OpenDevice = PULSEAUDIO_OpenDevice;
1017 impl->PlayDevice = PULSEAUDIO_PlayDevice;
1018 impl->WaitDevice = PULSEAUDIO_WaitDevice;
1019 impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf;
1020 impl->CloseDevice = PULSEAUDIO_CloseDevice;
1021 impl->DeinitializeStart = PULSEAUDIO_DeinitializeStart;
1022 impl->Deinitialize = PULSEAUDIO_Deinitialize;
1023 impl->WaitRecordingDevice = PULSEAUDIO_WaitRecordingDevice;
1024 impl->RecordDevice = PULSEAUDIO_RecordDevice;
1025 impl->FlushRecording = PULSEAUDIO_FlushRecording;
1026 impl->FreeDeviceHandle = PULSEAUDIO_FreeDeviceHandle;
1027
1028 impl->HasRecordingSupport = true;
1029
1030 return true;
1031}
1032
1033AudioBootStrap PULSEAUDIO_bootstrap = {
1034 "pulseaudio", "PulseAudio", PULSEAUDIO_Init, false, false
1035};
1036
1037#endif // SDL_AUDIO_DRIVER_PULSEAUDIO
diff --git a/contrib/SDL-3.2.8/src/audio/pulseaudio/SDL_pulseaudio.h b/contrib/SDL-3.2.8/src/audio/pulseaudio/SDL_pulseaudio.h
new file mode 100644
index 0000000..9ea44c0
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/pulseaudio/SDL_pulseaudio.h
@@ -0,0 +1,44 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_pulseaudio_h_
24#define SDL_pulseaudio_h_
25
26#include <pulse/pulseaudio.h>
27
28#include "../SDL_sysaudio.h"
29
30struct SDL_PrivateAudioData
31{
32 // pulseaudio structures
33 pa_stream *stream;
34
35 // Raw mixing buffer
36 Uint8 *mixbuf;
37
38 int bytes_requested; // bytes of data the hardware wants _now_.
39
40 const Uint8 *recordingbuf;
41 int recordinglen;
42};
43
44#endif // SDL_pulseaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c b/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c
new file mode 100644
index 0000000..a31bea4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.c
@@ -0,0 +1,451 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22// !!! FIXME: can this target support hotplugging?
23
24#include "../../SDL_internal.h"
25
26#ifdef SDL_AUDIO_DRIVER_QNX
27
28#include <errno.h>
29#include <unistd.h>
30#include <fcntl.h>
31#include <signal.h>
32#include <sys/types.h>
33#include <sys/time.h>
34#include <sched.h>
35#include <sys/select.h>
36#include <sys/neutrino.h>
37#include <sys/asoundlib.h>
38
39#include "SDL3/SDL_timer.h"
40#include "SDL3/SDL_audio.h"
41#include "../../core/unix/SDL_poll.h"
42#include "../SDL_sysaudio.h"
43#include "SDL_qsa_audio.h"
44
45// default channel communication parameters
46#define DEFAULT_CPARAMS_RATE 44100
47#define DEFAULT_CPARAMS_VOICES 1
48
49#define DEFAULT_CPARAMS_FRAG_SIZE 4096
50#define DEFAULT_CPARAMS_FRAGS_MIN 1
51#define DEFAULT_CPARAMS_FRAGS_MAX 1
52
53#define QSA_MAX_NAME_LENGTH 81+16 // Hardcoded in QSA, can't be changed
54
55static bool QSA_SetError(const char *fn, int status)
56{
57 return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status));
58}
59
60// !!! FIXME: does this need to be here? Does the SDL version not work?
61static void QSA_ThreadInit(SDL_AudioDevice *device)
62{
63 // Increase default 10 priority to 25 to avoid jerky sound
64 struct sched_param param;
65 if (SchedGet(0, 0, &param) != -1) {
66 param.sched_priority = param.sched_curpriority + 15;
67 SchedSet(0, 0, SCHED_NOCHANGE, &param);
68 }
69}
70
71// PCM channel parameters initialize function
72static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars)
73{
74 SDL_zerop(cpars);
75 cpars->channel = SND_PCM_CHANNEL_PLAYBACK;
76 cpars->mode = SND_PCM_MODE_BLOCK;
77 cpars->start_mode = SND_PCM_START_DATA;
78 cpars->stop_mode = SND_PCM_STOP_STOP;
79 cpars->format.format = SND_PCM_SFMT_S16_LE;
80 cpars->format.interleave = 1;
81 cpars->format.rate = DEFAULT_CPARAMS_RATE;
82 cpars->format.voices = DEFAULT_CPARAMS_VOICES;
83 cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
84 cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
85 cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
86}
87
88// This function waits until it is possible to write a full sound buffer
89static bool QSA_WaitDevice(SDL_AudioDevice *device)
90{
91 // Setup timeout for playing one fragment equal to 2 seconds
92 // If timeout occurred than something wrong with hardware or driver
93 // For example, Vortex 8820 audio driver stucks on second DAC because
94 // it doesn't exist !
95 const int result = SDL_IOReady(device->hidden->audio_fd,
96 device->recording ? SDL_IOR_READ : SDL_IOR_WRITE,
97 2 * 1000);
98 switch (result) {
99 case -1:
100 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "QSA: SDL_IOReady() failed: %s", strerror(errno));
101 return false;
102 case 0:
103 device->hidden->timeout_on_wait = true; // !!! FIXME: Should we just disconnect the device in this case?
104 break;
105 default:
106 device->hidden->timeout_on_wait = false;
107 break;
108 }
109
110 return true;
111}
112
113static bool QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
114{
115 if (SDL_GetAtomicInt(&device->shutdown) || !device->hidden) {
116 return true;
117 }
118
119 int towrite = buflen;
120
121 // Write the audio data, checking for EAGAIN (buffer full) and underrun
122 while ((towrite > 0) && !SDL_GetAtomicInt(&device->shutdown));
123 const int bw = snd_pcm_plugin_write(device->hidden->audio_handle, buffer, towrite);
124 if (bw != towrite) {
125 // Check if samples playback got stuck somewhere in hardware or in the audio device driver
126 if ((errno == EAGAIN) && (bw == 0)) {
127 if (device->hidden->timeout_on_wait) {
128 return true; // oh well, try again next time. !!! FIXME: Should we just disconnect the device in this case?
129 }
130 }
131
132 // Check for errors or conditions
133 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
134 SDL_Delay(1); // Let a little CPU time go by and try to write again
135
136 // if we wrote some data
137 towrite -= bw;
138 buffer += bw * device->spec.channels;
139 continue;
140 } else if ((errno == EINVAL) || (errno == EIO)) {
141 snd_pcm_channel_status_t cstatus;
142 SDL_zero(cstatus);
143 cstatus.channel = device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
144
145 int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus);
146 if (status < 0) {
147 QSA_SetError("snd_pcm_plugin_status", status);
148 return false;
149 } else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) {
150 status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
151 if (status < 0) {
152 QSA_SetError("snd_pcm_plugin_prepare", status);
153 return false;
154 }
155 }
156 continue;
157 } else {
158 return false;
159 }
160 } else {
161 // we wrote all remaining data
162 towrite -= bw;
163 buffer += bw * device->spec.channels;
164 }
165 }
166
167 // If we couldn't write, assume fatal error for now
168 return (towrite == 0);
169}
170
171static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
172{
173 return device->hidden->pcm_buf;
174}
175
176static void QSA_CloseDevice(SDL_AudioDevice *device)
177{
178 if (device->hidden) {
179 if (device->hidden->audio_handle) {
180 #if _NTO_VERSION < 710
181 // Finish playing available samples or cancel unread samples during recording
182 snd_pcm_plugin_flush(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
183 #endif
184 snd_pcm_close(device->hidden->audio_handle);
185 }
186
187 SDL_free(device->hidden->pcm_buf);
188 SDL_free(device->hidden);
189 device->hidden = NULL;
190 }
191}
192
193static bool QSA_OpenDevice(SDL_AudioDevice *device)
194{
195 if (device->recording) {
196 return SDL_SetError("SDL recording support isn't available on QNX atm"); // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
197 }
198
199 SDL_assert(device->handle != NULL); // NULL used to mean "system default device" in SDL2; it does not mean that in SDL3.
200 const Uint32 sdlhandle = (Uint32) ((size_t) device->handle);
201 const uint32_t cardno = (uint32_t) (sdlhandle & 0xFFFF);
202 const uint32_t deviceno = (uint32_t) ((sdlhandle >> 16) & 0xFFFF);
203 const bool recording = device->recording;
204 int status = 0;
205
206 // Initialize all variables that we clean on shutdown
207 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData)));
208 if (device->hidden == NULL) {
209 return false;
210 }
211
212 // Initialize channel transfer parameters to default
213 snd_pcm_channel_params_t cparams;
214 QSA_InitAudioParams(&cparams);
215
216 // Open requested audio device
217 status = snd_pcm_open(&device->hidden->audio_handle, cardno, deviceno, recording ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK);
218 if (status < 0) {
219 device->hidden->audio_handle = NULL;
220 return QSA_SetError("snd_pcm_open", status);
221 }
222
223 // Try for a closest match on audio format
224 SDL_AudioFormat test_format = 0;
225 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
226 while ((test_format = *(closefmts++)) != 0) {
227 // if match found set format to equivalent QSA format
228 switch (test_format) {
229 #define CHECKFMT(sdlfmt, qsafmt) case SDL_AUDIO_##sdlfmt: cparams.format.format = SND_PCM_SFMT_##qsafmt; break
230 CHECKFMT(U8, U8);
231 CHECKFMT(S8, S8);
232 CHECKFMT(S16LSB, S16_LE);
233 CHECKFMT(S16MSB, S16_BE);
234 CHECKFMT(S32LSB, S32_LE);
235 CHECKFMT(S32MSB, S32_BE);
236 CHECKFMT(F32LSB, FLOAT_LE);
237 CHECKFMT(F32MSB, FLOAT_BE);
238 #undef CHECKFMT
239 default: continue;
240 }
241 break;
242 }
243
244 // assumes test_format not 0 on success
245 if (test_format == 0) {
246 return SDL_SetError("QSA: Couldn't find any hardware audio formats");
247 }
248
249 device->spec.format = test_format;
250
251 // Set mono/stereo/4ch/6ch/8ch audio
252 cparams.format.voices = device->spec.channels;
253
254 // Set rate
255 cparams.format.rate = device->spec.freq;
256
257 // Setup the transfer parameters according to cparams
258 status = snd_pcm_plugin_params(device->hidden->audio_handle, &cparams);
259 if (status < 0) {
260 return QSA_SetError("snd_pcm_plugin_params", status);
261 }
262
263 // Make sure channel is setup right one last time
264 snd_pcm_channel_setup_t csetup;
265 SDL_zero(csetup);
266 csetup.channel = recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
267 if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
268 return SDL_SetError("QSA: Unable to setup channel");
269 }
270
271 device->sample_frames = csetup.buf.block.frag_size;
272
273 // Calculate the final parameters for this audio specification
274 SDL_UpdatedAudioDeviceFormat(device);
275
276 device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size);
277 if (device->hidden->pcm_buf == NULL) {
278 return false;
279 }
280 SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size);
281
282 // get the file descriptor
283 device->hidden->audio_fd = snd_pcm_file_descriptor(device->hidden->audio_handle, csetup.channel);
284 if (device->hidden->audio_fd < 0) {
285 return QSA_SetError("snd_pcm_file_descriptor", device->hidden->audio_fd);
286 }
287
288 // Prepare an audio channel
289 status = snd_pcm_plugin_prepare(device->hidden->audio_handle, csetup.channel)
290 if (status < 0) {
291 return QSA_SetError("snd_pcm_plugin_prepare", status);
292 }
293
294 return true; // We're really ready to rock and roll. :-)
295}
296
297static SDL_AudioFormat QnxFormatToSDLFormat(const int32_t qnxfmt)
298{
299 switch (qnxfmt) {
300 #define CHECKFMT(sdlfmt, qsafmt) case SND_PCM_SFMT_##qsafmt: return SDL_AUDIO_##sdlfmt
301 CHECKFMT(U8, U8);
302 CHECKFMT(S8, S8);
303 CHECKFMT(S16LSB, S16_LE);
304 CHECKFMT(S16MSB, S16_BE);
305 CHECKFMT(S32LSB, S32_LE);
306 CHECKFMT(S32MSB, S32_BE);
307 CHECKFMT(F32LSB, FLOAT_LE);
308 CHECKFMT(F32MSB, FLOAT_BE);
309 #undef CHECKFMT
310 default: break;
311 }
312 return SDL_AUDIO_S16; // oh well.
313}
314
315static void QSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
316{
317 // Detect amount of available devices
318 // this value can be changed in the runtime
319 int num_cards = 0;
320 (void) snd_cards_list(NULL, 0, &alloc_num_cards);
321 bool isstack = false;
322 int *cards = SDL_small_alloc(int, num_cards, &isstack);
323 if (!cards) {
324 return; // we're in trouble.
325 }
326 int overflow_cards = 0;
327 const int total_num_cards = snd_cards_list(cards, num_cards, &overflow_cards);
328 // if overflow_cards > 0 or total_num_cards > num_cards, it changed at the last moment; oh well, we lost some.
329 num_cards = SDL_min(num_cards, total_num_cards); // ...but make sure it didn't _shrink_.
330
331 // If io-audio manager is not running we will get 0 as number of available audio devices
332 if (num_cards == 0) { // not any available audio devices?
333 SDL_small_free(cards, isstack);
334 return;
335 }
336
337 // Find requested devices by type
338 for (int it = 0; it < num_cards; it++) {
339 const int card = cards[it];
340 for (uint32_t deviceno = 0; ; deviceno++) {
341 int32_t status;
342 char name[QSA_MAX_NAME_LENGTH];
343
344 status = snd_card_get_longname(card, name, sizeof (name));
345 if (status == EOK) {
346 snd_pcm_t *handle;
347
348 // Add device number to device name
349 char fullname[QSA_MAX_NAME_LENGTH + 32];
350 SDL_snprintf(fullname, sizeof (fullname), "%s d%d", name, (int) deviceno);
351
352 // Check if this device id could play anything
353 bool recording = false;
354 status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK);
355 if (status != EOK) { // no? See if it's a recording device instead.
356 #if 0 // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
357 status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE);
358 if (status == EOK) {
359 recording = true;
360 }
361 #endif
362 }
363
364 if (status == EOK) {
365 SDL_AudioSpec spec;
366 SDL_zero(spec);
367 SDL_AudioSpec *pspec = &spec;
368 snd_pcm_channel_setup_t csetup;
369 SDL_zero(csetup);
370 csetup.channel = recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
371
372 if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
373 pspec = NULL; // go on without spec info.
374 } else {
375 spec.format = QnxFormatToSDLFormat(csetup.format.format);
376 spec.channels = csetup.format.channels;
377 spec.freq = csetup.format.rate;
378 }
379
380 status = snd_pcm_close(handle);
381 if (status == EOK) {
382 // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
383 SDL_assert(card <= 0xFFFF);
384 SDL_assert(deviceno <= 0xFFFF);
385 const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
386 SDL_AddAudioDevice(recording, fullname, pspec, (void *) ((size_t) sdlhandle));
387 }
388 } else {
389 // Check if we got end of devices list
390 if (status == -ENOENT) {
391 break;
392 }
393 }
394 } else {
395 break;
396 }
397 }
398 }
399
400 SDL_small_free(cards, isstack);
401
402 // Try to open the "preferred" devices, which will tell us the card/device pairs for the default devices.
403 snd_pcm_t handle;
404 int cardno, deviceno;
405 if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_PLAYBACK) == 0) {
406 snd_pcm_close(handle);
407 // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
408 SDL_assert(cardno <= 0xFFFF);
409 SDL_assert(deviceno <= 0xFFFF);
410 const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
411 *default_playback = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
412 }
413
414 if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_CAPTURE) == 0) {
415 snd_pcm_close(handle);
416 // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
417 SDL_assert(cardno <= 0xFFFF);
418 SDL_assert(deviceno <= 0xFFFF);
419 const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
420 *default_recording = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
421 }
422}
423
424static void QSA_Deinitialize(void)
425{
426 // nothing to do here atm.
427}
428
429static bool QSA_Init(SDL_AudioDriverImpl * impl)
430{
431 impl->DetectDevices = QSA_DetectDevices;
432 impl->OpenDevice = QSA_OpenDevice;
433 impl->ThreadInit = QSA_ThreadInit;
434 impl->WaitDevice = QSA_WaitDevice;
435 impl->PlayDevice = QSA_PlayDevice;
436 impl->GetDeviceBuf = QSA_GetDeviceBuf;
437 impl->CloseDevice = QSA_CloseDevice;
438 impl->Deinitialize = QSA_Deinitialize;
439
440 // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
441 //impl->HasRecordingSupport = true;
442
443 return true;
444}
445
446AudioBootStrap QSAAUDIO_bootstrap = {
447 "qsa", "QNX QSA Audio", QSA_Init, false, false
448};
449
450#endif // SDL_AUDIO_DRIVER_QNX
451
diff --git a/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.h b/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.h
new file mode 100644
index 0000000..902752c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/qnx/SDL_qsa_audio.h
@@ -0,0 +1,40 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "../../SDL_internal.h"
23
24#ifndef __SDL_QSA_AUDIO_H__
25#define __SDL_QSA_AUDIO_H__
26
27#include <sys/asoundlib.h>
28
29#include "../SDL_sysaudio.h"
30
31struct SDL_PrivateAudioData
32{
33 snd_pcm_t *audio_handle; // The audio device handle
34 int audio_fd; // The audio file descriptor, for selecting on
35 bool timeout_on_wait; // Select timeout status
36 Uint8 *pcm_buf; // Raw mixing buffer
37};
38
39#endif // __SDL_QSA_AUDIO_H__
40
diff --git a/contrib/SDL-3.2.8/src/audio/sndio/SDL_sndioaudio.c b/contrib/SDL-3.2.8/src/audio/sndio/SDL_sndioaudio.c
new file mode 100644
index 0000000..a0d2020
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/sndio/SDL_sndioaudio.c
@@ -0,0 +1,356 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_AUDIO_DRIVER_SNDIO
25
26// OpenBSD sndio target
27
28#ifdef HAVE_STDIO_H
29#include <stdio.h>
30#endif
31
32#ifdef HAVE_SIGNAL_H
33#include <signal.h>
34#endif
35
36#include <poll.h>
37#include <unistd.h>
38
39#include "../SDL_sysaudio.h"
40#include "SDL_sndioaudio.h"
41
42#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
43#endif
44
45#ifndef INFTIM
46#define INFTIM -1
47#endif
48
49#ifndef SIO_DEVANY
50#define SIO_DEVANY "default"
51#endif
52
53static struct sio_hdl *(*SNDIO_sio_open)(const char *, unsigned int, int);
54static void (*SNDIO_sio_close)(struct sio_hdl *);
55static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
56static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
57static int (*SNDIO_sio_start)(struct sio_hdl *);
58static int (*SNDIO_sio_stop)(struct sio_hdl *);
59static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
60static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
61static int (*SNDIO_sio_nfds)(struct sio_hdl *);
62static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
63static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
64static int (*SNDIO_sio_eof)(struct sio_hdl *);
65static void (*SNDIO_sio_initpar)(struct sio_par *);
66
67#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
68static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
69static SDL_SharedObject *sndio_handle = NULL;
70
71static bool load_sndio_sym(const char *fn, void **addr)
72{
73 *addr = SDL_LoadFunction(sndio_handle, fn);
74 if (!*addr) {
75 return false; // Don't call SDL_SetError(): SDL_LoadFunction already did.
76 }
77
78 return true;
79}
80
81// cast funcs to char* first, to please GCC's strict aliasing rules.
82#define SDL_SNDIO_SYM(x) \
83 if (!load_sndio_sym(#x, (void **)(char *)&SNDIO_##x)) \
84 return false
85#else
86#define SDL_SNDIO_SYM(x) SNDIO_##x = x
87#endif
88
89static bool load_sndio_syms(void)
90{
91 SDL_SNDIO_SYM(sio_open);
92 SDL_SNDIO_SYM(sio_close);
93 SDL_SNDIO_SYM(sio_setpar);
94 SDL_SNDIO_SYM(sio_getpar);
95 SDL_SNDIO_SYM(sio_start);
96 SDL_SNDIO_SYM(sio_stop);
97 SDL_SNDIO_SYM(sio_read);
98 SDL_SNDIO_SYM(sio_write);
99 SDL_SNDIO_SYM(sio_nfds);
100 SDL_SNDIO_SYM(sio_pollfd);
101 SDL_SNDIO_SYM(sio_revents);
102 SDL_SNDIO_SYM(sio_eof);
103 SDL_SNDIO_SYM(sio_initpar);
104 return true;
105}
106
107#undef SDL_SNDIO_SYM
108
109#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
110
111static void UnloadSNDIOLibrary(void)
112{
113 if (sndio_handle) {
114 SDL_UnloadObject(sndio_handle);
115 sndio_handle = NULL;
116 }
117}
118
119static bool LoadSNDIOLibrary(void)
120{
121 bool result = true;
122 if (!sndio_handle) {
123 sndio_handle = SDL_LoadObject(sndio_library);
124 if (!sndio_handle) {
125 result = false; // Don't call SDL_SetError(): SDL_LoadObject already did.
126 } else {
127 result = load_sndio_syms();
128 if (!result) {
129 UnloadSNDIOLibrary();
130 }
131 }
132 }
133 return result;
134}
135
136#else
137
138static void UnloadSNDIOLibrary(void)
139{
140}
141
142static bool LoadSNDIOLibrary(void)
143{
144 load_sndio_syms();
145 return true;
146}
147
148#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
149
150static bool SNDIO_WaitDevice(SDL_AudioDevice *device)
151{
152 const bool recording = device->recording;
153
154 while (!SDL_GetAtomicInt(&device->shutdown)) {
155 if (SNDIO_sio_eof(device->hidden->dev)) {
156 return false;
157 }
158
159 const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, recording ? POLLIN : POLLOUT);
160 if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) {
161 return false;
162 }
163
164 const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd);
165 if (recording && (revents & POLLIN)) {
166 break;
167 } else if (!recording && (revents & POLLOUT)) {
168 break;
169 } else if (revents & POLLHUP) {
170 return false;
171 }
172 }
173
174 return true;
175}
176
177static bool SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
178{
179 // !!! FIXME: this should be non-blocking so we can check device->shutdown.
180 // this is set to blocking, because we _have_ to send the entire buffer down, but hopefully WaitDevice took most of the delay time.
181 if (SNDIO_sio_write(device->hidden->dev, buffer, buflen) != buflen) {
182 return false; // If we couldn't write, assume fatal error for now
183 }
184#ifdef DEBUG_AUDIO
185 fprintf(stderr, "Wrote %d bytes of audio data\n", written);
186#endif
187 return true;
188}
189
190static int SNDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
191{
192 // We set recording devices non-blocking; this can safely return 0 in SDL3, but we'll check for EOF to cause a device disconnect.
193 const size_t br = SNDIO_sio_read(device->hidden->dev, buffer, buflen);
194 if ((br == 0) && SNDIO_sio_eof(device->hidden->dev)) {
195 return -1;
196 }
197 return (int) br;
198}
199
200static void SNDIO_FlushRecording(SDL_AudioDevice *device)
201{
202 char buf[512];
203 while (!SDL_GetAtomicInt(&device->shutdown) && (SNDIO_sio_read(device->hidden->dev, buf, sizeof(buf)) > 0)) {
204 // do nothing
205 }
206}
207
208static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
209{
210 return device->hidden->mixbuf;
211}
212
213static void SNDIO_CloseDevice(SDL_AudioDevice *device)
214{
215 if (device->hidden) {
216 if (device->hidden->dev) {
217 SNDIO_sio_stop(device->hidden->dev);
218 SNDIO_sio_close(device->hidden->dev);
219 }
220 SDL_free(device->hidden->pfd);
221 SDL_free(device->hidden->mixbuf);
222 SDL_free(device->hidden);
223 device->hidden = NULL;
224 }
225}
226
227static bool SNDIO_OpenDevice(SDL_AudioDevice *device)
228{
229 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
230 if (!device->hidden) {
231 return false;
232 }
233
234 // Recording devices must be non-blocking for SNDIO_FlushRecording
235 device->hidden->dev = SNDIO_sio_open(SIO_DEVANY,
236 device->recording ? SIO_REC : SIO_PLAY, device->recording);
237 if (!device->hidden->dev) {
238 return SDL_SetError("sio_open() failed");
239 }
240
241 device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev));
242 if (!device->hidden->pfd) {
243 return false;
244 }
245
246 struct sio_par par;
247 SNDIO_sio_initpar(&par);
248
249 par.rate = device->spec.freq;
250 par.pchan = device->spec.channels;
251 par.round = device->sample_frames;
252 par.appbufsz = par.round * 2;
253
254 // Try for a closest match on audio format
255 SDL_AudioFormat test_format;
256 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
257 while ((test_format = *(closefmts++)) != 0) {
258 if (!SDL_AUDIO_ISFLOAT(test_format)) {
259 par.le = SDL_AUDIO_ISLITTLEENDIAN(test_format) ? 1 : 0;
260 par.sig = SDL_AUDIO_ISSIGNED(test_format) ? 1 : 0;
261 par.bits = SDL_AUDIO_BITSIZE(test_format);
262
263 if (SNDIO_sio_setpar(device->hidden->dev, &par) == 0) {
264 continue;
265 }
266 if (SNDIO_sio_getpar(device->hidden->dev, &par) == 0) {
267 return SDL_SetError("sio_getpar() failed");
268 }
269 if (par.bps != SIO_BPS(par.bits)) {
270 continue;
271 }
272 if ((par.bits == 8 * par.bps) || (par.msb)) {
273 break;
274 }
275 }
276 }
277
278 if (!test_format) {
279 return SDL_SetError("sndio: Unsupported audio format");
280 }
281
282 if ((par.bps == 4) && (par.sig) && (par.le)) {
283 device->spec.format = SDL_AUDIO_S32LE;
284 } else if ((par.bps == 4) && (par.sig) && (!par.le)) {
285 device->spec.format = SDL_AUDIO_S32BE;
286 } else if ((par.bps == 2) && (par.sig) && (par.le)) {
287 device->spec.format = SDL_AUDIO_S16LE;
288 } else if ((par.bps == 2) && (par.sig) && (!par.le)) {
289 device->spec.format = SDL_AUDIO_S16BE;
290 } else if ((par.bps == 1) && (par.sig)) {
291 device->spec.format = SDL_AUDIO_S8;
292 } else if ((par.bps == 1) && (!par.sig)) {
293 device->spec.format = SDL_AUDIO_U8;
294 } else {
295 return SDL_SetError("sndio: Got unsupported hardware audio format.");
296 }
297
298 device->spec.freq = par.rate;
299 device->spec.channels = par.pchan;
300 device->sample_frames = par.round;
301
302 // Calculate the final parameters for this audio specification
303 SDL_UpdatedAudioDeviceFormat(device);
304
305 // Allocate mixing buffer
306 device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
307 if (!device->hidden->mixbuf) {
308 return false;
309 }
310 SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
311
312 if (!SNDIO_sio_start(device->hidden->dev)) {
313 return SDL_SetError("sio_start() failed");
314 }
315
316 return true; // We're ready to rock and roll. :-)
317}
318
319static void SNDIO_Deinitialize(void)
320{
321 UnloadSNDIOLibrary();
322}
323
324static void SNDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
325{
326 *default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
327 *default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
328}
329
330static bool SNDIO_Init(SDL_AudioDriverImpl *impl)
331{
332 if (!LoadSNDIOLibrary()) {
333 return false;
334 }
335
336 impl->OpenDevice = SNDIO_OpenDevice;
337 impl->WaitDevice = SNDIO_WaitDevice;
338 impl->PlayDevice = SNDIO_PlayDevice;
339 impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
340 impl->CloseDevice = SNDIO_CloseDevice;
341 impl->WaitRecordingDevice = SNDIO_WaitDevice;
342 impl->RecordDevice = SNDIO_RecordDevice;
343 impl->FlushRecording = SNDIO_FlushRecording;
344 impl->Deinitialize = SNDIO_Deinitialize;
345 impl->DetectDevices = SNDIO_DetectDevices;
346
347 impl->HasRecordingSupport = true;
348
349 return true;
350}
351
352AudioBootStrap SNDIO_bootstrap = {
353 "sndio", "OpenBSD sndio", SNDIO_Init, false, false
354};
355
356#endif // SDL_AUDIO_DRIVER_SNDIO
diff --git a/contrib/SDL-3.2.8/src/audio/sndio/SDL_sndioaudio.h b/contrib/SDL-3.2.8/src/audio/sndio/SDL_sndioaudio.h
new file mode 100644
index 0000000..d4ff725
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/sndio/SDL_sndioaudio.h
@@ -0,0 +1,38 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_sndioaudio_h_
24#define SDL_sndioaudio_h_
25
26#include <poll.h>
27#include <sndio.h>
28
29#include "../SDL_sysaudio.h"
30
31struct SDL_PrivateAudioData
32{
33 struct sio_hdl *dev; // The audio device handle
34 Uint8 *mixbuf; // Raw mixing buffer
35 struct pollfd *pfd; // Polling structures for non-blocking sndio devices
36};
37
38#endif // SDL_sndioaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/vita/SDL_vitaaudio.c b/contrib/SDL-3.2.8/src/audio/vita/SDL_vitaaudio.c
new file mode 100644
index 0000000..e194f21
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/vita/SDL_vitaaudio.c
@@ -0,0 +1,238 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_VITA
24
25#include <stdio.h>
26#include <string.h>
27#include <stdlib.h>
28
29#include "../SDL_audiodev_c.h"
30#include "../SDL_sysaudio.h"
31#include "SDL_vitaaudio.h"
32
33#include <psp2/kernel/threadmgr.h>
34#include <psp2/audioout.h>
35#include <psp2/audioin.h>
36
37#define SCE_AUDIO_SAMPLE_ALIGN(s) (((s) + 63) & ~63)
38#define SCE_AUDIO_MAX_VOLUME 0x8000
39
40static bool VITAAUD_OpenRecordingDevice(SDL_AudioDevice *device)
41{
42 device->spec.freq = 16000;
43 device->spec.channels = 1;
44 device->sample_frames = 512;
45
46 SDL_UpdatedAudioDeviceFormat(device);
47
48 device->hidden->port = sceAudioInOpenPort(SCE_AUDIO_IN_PORT_TYPE_VOICE, 512, 16000, SCE_AUDIO_IN_PARAM_FORMAT_S16_MONO);
49
50 if (device->hidden->port < 0) {
51 return SDL_SetError("Couldn't open audio in port: %x", device->hidden->port);
52 }
53
54 return true;
55}
56
57static bool VITAAUD_OpenDevice(SDL_AudioDevice *device)
58{
59 int format, mixlen, i, port = SCE_AUDIO_OUT_PORT_TYPE_MAIN;
60 int vols[2] = { SCE_AUDIO_MAX_VOLUME, SCE_AUDIO_MAX_VOLUME };
61 SDL_AudioFormat test_format;
62 const SDL_AudioFormat *closefmts;
63
64 device->hidden = (struct SDL_PrivateAudioData *)
65 SDL_calloc(1, sizeof(*device->hidden));
66 if (!device->hidden) {
67 return false;
68 }
69
70 closefmts = SDL_ClosestAudioFormats(device->spec.format);
71 while ((test_format = *(closefmts++)) != 0) {
72 if (test_format == SDL_AUDIO_S16LE) {
73 device->spec.format = test_format;
74 break;
75 }
76 }
77
78 if (!test_format) {
79 return SDL_SetError("Unsupported audio format");
80 }
81
82 if (device->recording) {
83 return VITAAUD_OpenRecordingDevice(device);
84 }
85
86 // The sample count must be a multiple of 64.
87 device->sample_frames = SCE_AUDIO_SAMPLE_ALIGN(device->sample_frames);
88
89 // Update the fragment size as size in bytes.
90 SDL_UpdatedAudioDeviceFormat(device);
91
92 /* Allocate the mixing buffer. Its size and starting address must
93 be a multiple of 64 bytes. Our sample count is already a multiple of
94 64, so spec->size should be a multiple of 64 as well. */
95 mixlen = device->buffer_size * NUM_BUFFERS;
96 device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
97 if (!device->hidden->rawbuf) {
98 return SDL_SetError("Couldn't allocate mixing buffer");
99 }
100
101 // Setup the hardware channel.
102 if (device->spec.channels == 1) {
103 format = SCE_AUDIO_OUT_MODE_MONO;
104 } else {
105 format = SCE_AUDIO_OUT_MODE_STEREO;
106 }
107
108 // the main port requires 48000Hz audio, so this drops to the background music port if necessary
109 if (device->spec.freq < 48000) {
110 port = SCE_AUDIO_OUT_PORT_TYPE_BGM;
111 }
112
113 device->hidden->port = sceAudioOutOpenPort(port, device->sample_frames, device->spec.freq, format);
114 if (device->hidden->port < 0) {
115 SDL_aligned_free(device->hidden->rawbuf);
116 device->hidden->rawbuf = NULL;
117 return SDL_SetError("Couldn't open audio out port: %x", device->hidden->port);
118 }
119
120 sceAudioOutSetVolume(device->hidden->port, SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH, vols);
121
122 SDL_memset(device->hidden->rawbuf, 0, mixlen);
123 for (i = 0; i < NUM_BUFFERS; i++) {
124 device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
125 }
126
127 device->hidden->next_buffer = 0;
128 return true;
129}
130
131static bool VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
132{
133 return (sceAudioOutOutput(device->hidden->port, buffer) == 0);
134}
135
136// This function waits until it is possible to write a full sound buffer
137static bool VITAAUD_WaitDevice(SDL_AudioDevice *device)
138{
139 // !!! FIXME: we might just need to sleep roughly as long as playback buffers take to process, based on sample rate, etc.
140 while (!SDL_GetAtomicInt(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) {
141 SDL_Delay(1);
142 }
143 return true;
144}
145
146static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
147{
148 Uint8 *result = device->hidden->mixbufs[device->hidden->next_buffer];
149 device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
150 return result;
151}
152
153static void VITAAUD_CloseDevice(SDL_AudioDevice *device)
154{
155 if (device->hidden) {
156 if (device->hidden->port >= 0) {
157 if (device->recording) {
158 sceAudioInReleasePort(device->hidden->port);
159 } else {
160 sceAudioOutReleasePort(device->hidden->port);
161 }
162 device->hidden->port = -1;
163 }
164
165 if (!device->recording && device->hidden->rawbuf) {
166 SDL_aligned_free(device->hidden->rawbuf); // this uses SDL_aligned_alloc(), not SDL_malloc()
167 device->hidden->rawbuf = NULL;
168 }
169 SDL_free(device->hidden);
170 device->hidden = NULL;
171 }
172}
173
174static bool VITAAUD_WaitRecordingDevice(SDL_AudioDevice *device)
175{
176 // there's only a blocking call to obtain more data, so we'll just sleep as
177 // long as a buffer would run.
178 const Uint64 endticks = SDL_GetTicks() + ((device->sample_frames * 1000) / device->spec.freq);
179 while (!SDL_GetAtomicInt(&device->shutdown) && (SDL_GetTicks() < endticks)) {
180 SDL_Delay(1);
181 }
182 return true;
183}
184
185static int VITAAUD_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
186{
187 int ret;
188 SDL_assert(buflen == device->buffer_size);
189 ret = sceAudioInInput(device->hidden->port, buffer);
190 if (ret < 0) {
191 SDL_SetError("Failed to record from device: %x", ret);
192 return -1;
193 }
194 return device->buffer_size;
195}
196
197static void VITAAUD_FlushRecording(SDL_AudioDevice *device)
198{
199 // just grab the latest and dump it.
200 sceAudioInInput(device->hidden->port, device->work_buffer);
201}
202
203static void VITAAUD_ThreadInit(SDL_AudioDevice *device)
204{
205 // Increase the priority of this audio thread by 1 to put it ahead of other SDL threads.
206 SceUID thid;
207 SceKernelThreadInfo info;
208 thid = sceKernelGetThreadId();
209 info.size = sizeof(SceKernelThreadInfo);
210 if (sceKernelGetThreadInfo(thid, &info) == 0) {
211 sceKernelChangeThreadPriority(thid, info.currentPriority - 1);
212 }
213}
214
215static bool VITAAUD_Init(SDL_AudioDriverImpl *impl)
216{
217 impl->OpenDevice = VITAAUD_OpenDevice;
218 impl->PlayDevice = VITAAUD_PlayDevice;
219 impl->WaitDevice = VITAAUD_WaitDevice;
220 impl->GetDeviceBuf = VITAAUD_GetDeviceBuf;
221 impl->CloseDevice = VITAAUD_CloseDevice;
222 impl->ThreadInit = VITAAUD_ThreadInit;
223 impl->WaitRecordingDevice = VITAAUD_WaitRecordingDevice;
224 impl->FlushRecording = VITAAUD_FlushRecording;
225 impl->RecordDevice = VITAAUD_RecordDevice;
226
227 impl->HasRecordingSupport = true;
228 impl->OnlyHasDefaultPlaybackDevice = true;
229 impl->OnlyHasDefaultRecordingDevice = true;
230
231 return true;
232}
233
234AudioBootStrap VITAAUD_bootstrap = {
235 "vita", "VITA audio driver", VITAAUD_Init, false, false
236};
237
238#endif // SDL_AUDIO_DRIVER_VITA
diff --git a/contrib/SDL-3.2.8/src/audio/vita/SDL_vitaaudio.h b/contrib/SDL-3.2.8/src/audio/vita/SDL_vitaaudio.h
new file mode 100644
index 0000000..1e97499
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/vita/SDL_vitaaudio.h
@@ -0,0 +1,41 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_vitaaudio_h
23#define SDL_vitaaudio_h
24
25#include "../SDL_sysaudio.h"
26
27#define NUM_BUFFERS 2
28
29struct SDL_PrivateAudioData
30{
31 // The hardware input/output port.
32 int port;
33 // The raw allocated mixing buffer.
34 Uint8 *rawbuf;
35 // Individual mixing buffers.
36 Uint8 *mixbufs[NUM_BUFFERS];
37 // Index of the next available mixing buffer.
38 int next_buffer;
39};
40
41#endif // SDL_vitaaudio_h
diff --git a/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.c b/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.c
new file mode 100644
index 0000000..db0974b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.c
@@ -0,0 +1,963 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_WASAPI
24
25#include "../../core/windows/SDL_windows.h"
26#include "../../core/windows/SDL_immdevice.h"
27#include "../../thread/SDL_systhread.h"
28#include "../SDL_sysaudio.h"
29
30#define COBJMACROS
31#include <audioclient.h>
32
33#include "SDL_wasapi.h"
34
35// These constants aren't available in older SDKs
36#ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
37#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
38#endif
39#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
40#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
41#endif
42#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
43#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
44#endif
45
46// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency).
47static HMODULE libavrt = NULL;
48typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
49typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
50static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
51static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
52
53// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
54static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
55static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
56static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
57#ifdef __IAudioClient3_INTERFACE_DEFINED__
58static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } };
59#endif //
60
61static bool immdevice_initialized = false;
62
63// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
64// so we proxy various stuff to a single background thread to manage.
65
66typedef struct ManagementThreadPendingTask
67{
68 ManagementThreadTask fn;
69 void *userdata;
70 bool result;
71 SDL_Semaphore *task_complete_sem;
72 char *errorstr;
73 struct ManagementThreadPendingTask *next;
74} ManagementThreadPendingTask;
75
76static SDL_Thread *ManagementThread = NULL;
77static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
78static SDL_Mutex *ManagementThreadLock = NULL;
79static SDL_Condition *ManagementThreadCondition = NULL;
80static SDL_AtomicInt ManagementThreadShutdown;
81
82static void ManagementThreadMainloop(void)
83{
84 SDL_LockMutex(ManagementThreadLock);
85 ManagementThreadPendingTask *task;
86 while (((task = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks)) != NULL) || !SDL_GetAtomicInt(&ManagementThreadShutdown)) {
87 if (!task) {
88 SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
89 } else {
90 SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, task->next); // take task off the pending list.
91 SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task.
92 task->result = task->fn(task->userdata); // run this task.
93 if (task->task_complete_sem) { // something waiting on result?
94 task->errorstr = SDL_strdup(SDL_GetError());
95 SDL_SignalSemaphore(task->task_complete_sem);
96 } else { // nothing waiting, we're done, free it.
97 SDL_free(task);
98 }
99 SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition.
100 }
101 }
102 SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
103}
104
105bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_on_result)
106{
107 // We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock.
108 if ((wait_on_result) && (SDL_GetCurrentThreadID() == SDL_GetThreadID(ManagementThread))) {
109 *wait_on_result = task(userdata);
110 return true; // completed!
111 }
112
113 if (SDL_GetAtomicInt(&ManagementThreadShutdown)) {
114 return SDL_SetError("Can't add task, we're shutting down");
115 }
116
117 ManagementThreadPendingTask *pending = (ManagementThreadPendingTask *)SDL_calloc(1, sizeof(ManagementThreadPendingTask));
118 if (!pending) {
119 return false;
120 }
121
122 pending->fn = task;
123 pending->userdata = userdata;
124
125 if (wait_on_result) {
126 pending->task_complete_sem = SDL_CreateSemaphore(0);
127 if (!pending->task_complete_sem) {
128 SDL_free(pending);
129 return false;
130 }
131 }
132
133 pending->next = NULL;
134
135 SDL_LockMutex(ManagementThreadLock);
136
137 // add to end of task list.
138 ManagementThreadPendingTask *prev = NULL;
139 for (ManagementThreadPendingTask *i = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks); i; i = i->next) {
140 prev = i;
141 }
142
143 if (prev) {
144 prev->next = pending;
145 } else {
146 SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, pending);
147 }
148
149 // task is added to the end of the pending list, let management thread rip!
150 SDL_SignalCondition(ManagementThreadCondition);
151 SDL_UnlockMutex(ManagementThreadLock);
152
153 if (wait_on_result) {
154 SDL_WaitSemaphore(pending->task_complete_sem);
155 SDL_DestroySemaphore(pending->task_complete_sem);
156 *wait_on_result = pending->result;
157 if (pending->errorstr) {
158 SDL_SetError("%s", pending->errorstr);
159 SDL_free(pending->errorstr);
160 }
161 SDL_free(pending);
162 }
163
164 return true; // successfully added (and possibly executed)!
165}
166
167static bool mgmtthrtask_AudioDeviceDisconnected(void *userdata)
168{
169 SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
170 SDL_AudioDeviceDisconnected(device);
171 UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
172 return true;
173}
174
175static void AudioDeviceDisconnected(SDL_AudioDevice *device)
176{
177 // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
178 if (device) {
179 RefPhysicalAudioDevice(device); // make sure this lives until the task completes.
180 WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL);
181 }
182}
183
184static bool mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
185{
186 SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
187 SDL_DefaultAudioDeviceChanged(device);
188 UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
189 return true;
190}
191
192static void DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
193{
194 // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
195 if (new_default_device) {
196 RefPhysicalAudioDevice(new_default_device); // make sure this lives until the task completes.
197 WASAPI_ProxyToManagementThread(mgmtthrtask_DefaultAudioDeviceChanged, new_default_device, NULL);
198 }
199}
200
201static void StopWasapiHotplug(void)
202{
203 if (immdevice_initialized) {
204 SDL_IMMDevice_Quit();
205 immdevice_initialized = false;
206 }
207}
208
209static void Deinit(void)
210{
211 if (libavrt) {
212 FreeLibrary(libavrt);
213 libavrt = NULL;
214 }
215
216 pAvSetMmThreadCharacteristicsW = NULL;
217 pAvRevertMmThreadCharacteristics = NULL;
218
219 StopWasapiHotplug();
220
221 WIN_CoUninitialize();
222}
223
224static bool ManagementThreadPrepare(void)
225{
226 const SDL_IMMDevice_callbacks callbacks = { AudioDeviceDisconnected, DefaultAudioDeviceChanged };
227 if (FAILED(WIN_CoInitialize())) {
228 return SDL_SetError("CoInitialize() failed");
229 } else if (!SDL_IMMDevice_Init(&callbacks)) {
230 return false; // Error string is set by SDL_IMMDevice_Init
231 }
232
233 immdevice_initialized = true;
234
235 libavrt = LoadLibrary(TEXT("avrt.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
236 if (libavrt) {
237 pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
238 pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics)GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
239 }
240
241 ManagementThreadLock = SDL_CreateMutex();
242 if (!ManagementThreadLock) {
243 Deinit();
244 return false;
245 }
246
247 ManagementThreadCondition = SDL_CreateCondition();
248 if (!ManagementThreadCondition) {
249 SDL_DestroyMutex(ManagementThreadLock);
250 ManagementThreadLock = NULL;
251 Deinit();
252 return false;
253 }
254
255 return true;
256}
257
258typedef struct
259{
260 char *errorstr;
261 SDL_Semaphore *ready_sem;
262} ManagementThreadEntryData;
263
264static int ManagementThreadEntry(void *userdata)
265{
266 ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
267
268 if (!ManagementThreadPrepare()) {
269 data->errorstr = SDL_strdup(SDL_GetError());
270 SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
271 return 0;
272 }
273
274 SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
275 ManagementThreadMainloop();
276
277 Deinit();
278 return 0;
279}
280
281static bool InitManagementThread(void)
282{
283 ManagementThreadEntryData mgmtdata;
284 SDL_zero(mgmtdata);
285 mgmtdata.ready_sem = SDL_CreateSemaphore(0);
286 if (!mgmtdata.ready_sem) {
287 return false;
288 }
289
290 SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, NULL);
291 SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
292 ManagementThread = SDL_CreateThreadWithStackSize(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
293 if (!ManagementThread) {
294 return false;
295 }
296
297 SDL_WaitSemaphore(mgmtdata.ready_sem);
298 SDL_DestroySemaphore(mgmtdata.ready_sem);
299
300 if (mgmtdata.errorstr) {
301 SDL_WaitThread(ManagementThread, NULL);
302 ManagementThread = NULL;
303 SDL_SetError("%s", mgmtdata.errorstr);
304 SDL_free(mgmtdata.errorstr);
305 return false;
306 }
307
308 return true;
309}
310
311static void DeinitManagementThread(void)
312{
313 if (ManagementThread) {
314 SDL_SetAtomicInt(&ManagementThreadShutdown, 1);
315 SDL_LockMutex(ManagementThreadLock);
316 SDL_SignalCondition(ManagementThreadCondition);
317 SDL_UnlockMutex(ManagementThreadLock);
318 SDL_WaitThread(ManagementThread, NULL);
319 ManagementThread = NULL;
320 }
321
322 SDL_assert(SDL_GetAtomicPointer((void **) &ManagementThreadPendingTasks) == NULL);
323
324 SDL_DestroyCondition(ManagementThreadCondition);
325 SDL_DestroyMutex(ManagementThreadLock);
326 ManagementThreadCondition = NULL;
327 ManagementThreadLock = NULL;
328 SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
329}
330
331typedef struct
332{
333 SDL_AudioDevice **default_playback;
334 SDL_AudioDevice **default_recording;
335} mgmtthrtask_DetectDevicesData;
336
337static bool mgmtthrtask_DetectDevices(void *userdata)
338{
339 mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
340 SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording);
341 return true;
342}
343
344static void WASAPI_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
345{
346 bool rc;
347 // this blocks because it needs to finish before the audio subsystem inits
348 mgmtthrtask_DetectDevicesData data;
349 data.default_playback = default_playback;
350 data.default_recording = default_recording;
351 WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
352}
353
354static bool mgmtthrtask_DisconnectDevice(void *userdata)
355{
356 SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
357 SDL_AudioDeviceDisconnected(device);
358 UnrefPhysicalAudioDevice(device);
359 return true;
360}
361
362void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
363{
364 if (SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1)) {
365 RefPhysicalAudioDevice(device); // will unref when the task ends.
366 WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL);
367 }
368}
369
370static bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
371{
372 if (err == S_OK) {
373 return false;
374 } else if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
375 device->hidden->device_lost = true;
376 } else {
377 device->hidden->device_dead = true;
378 }
379
380 return true;
381}
382
383static bool mgmtthrtask_StopAndReleaseClient(void *userdata)
384{
385 IAudioClient *client = (IAudioClient *) userdata;
386 IAudioClient_Stop(client);
387 IAudioClient_Release(client);
388 return true;
389}
390
391static bool mgmtthrtask_ReleaseCaptureClient(void *userdata)
392{
393 IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
394 return true;
395}
396
397static bool mgmtthrtask_ReleaseRenderClient(void *userdata)
398{
399 IAudioRenderClient_Release((IAudioRenderClient *)userdata);
400 return true;
401}
402
403static bool mgmtthrtask_CoTaskMemFree(void *userdata)
404{
405 CoTaskMemFree(userdata);
406 return true;
407}
408
409static bool mgmtthrtask_CloseHandle(void *userdata)
410{
411 CloseHandle((HANDLE) userdata);
412 return true;
413}
414
415static void ResetWasapiDevice(SDL_AudioDevice *device)
416{
417 if (!device || !device->hidden) {
418 return;
419 }
420
421 // just queue up all the tasks in the management thread and don't block.
422 // We don't care when any of these actually get free'd.
423
424 if (device->hidden->client) {
425 IAudioClient *client = device->hidden->client;
426 device->hidden->client = NULL;
427 WASAPI_ProxyToManagementThread(mgmtthrtask_StopAndReleaseClient, client, NULL);
428 }
429
430 if (device->hidden->render) {
431 IAudioRenderClient *render = device->hidden->render;
432 device->hidden->render = NULL;
433 WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, render, NULL);
434 }
435
436 if (device->hidden->capture) {
437 IAudioCaptureClient *capture = device->hidden->capture;
438 device->hidden->capture = NULL;
439 WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, capture, NULL);
440 }
441
442 if (device->hidden->waveformat) {
443 void *ptr = device->hidden->waveformat;
444 device->hidden->waveformat = NULL;
445 WASAPI_ProxyToManagementThread(mgmtthrtask_CoTaskMemFree, ptr, NULL);
446 }
447
448 if (device->hidden->event) {
449 HANDLE event = device->hidden->event;
450 device->hidden->event = NULL;
451 WASAPI_ProxyToManagementThread(mgmtthrtask_CloseHandle, (void *) event, NULL);
452 }
453}
454
455static bool mgmtthrtask_ActivateDevice(void *userdata)
456{
457 SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
458
459 IMMDevice *immdevice = NULL;
460 if (!SDL_IMMDevice_Get(device, &immdevice, device->recording)) {
461 device->hidden->client = NULL;
462 return false; // This is already set by SDL_IMMDevice_Get
463 }
464
465 // this is _not_ async in standard win32, yay!
466 HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
467 IMMDevice_Release(immdevice);
468
469 if (FAILED(ret)) {
470 SDL_assert(device->hidden->client == NULL);
471 return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
472 }
473
474 SDL_assert(device->hidden->client != NULL);
475 if (!WASAPI_PrepDevice(device)) { // not async, fire it right away.
476 return false;
477 }
478
479 return true; // good to go.
480}
481
482static bool ActivateWasapiDevice(SDL_AudioDevice *device)
483{
484 // this blocks because we're either being notified from a background thread or we're running during device open,
485 // both of which won't deadlock vs the device thread.
486 bool rc = false;
487 return (WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) && rc);
488}
489
490// do not call when holding the device lock!
491static bool RecoverWasapiDevice(SDL_AudioDevice *device)
492{
493 ResetWasapiDevice(device); // dump the lost device's handles.
494
495 // This handles a non-default device that simply had its format changed in the Windows Control Panel.
496 if (!ActivateWasapiDevice(device)) {
497 WASAPI_DisconnectDevice(device);
498 return false;
499 }
500
501 device->hidden->device_lost = false;
502
503 return true; // okay, carry on with new device details!
504}
505
506// do not call when holding the device lock!
507static bool RecoverWasapiIfLost(SDL_AudioDevice *device)
508{
509 if (SDL_GetAtomicInt(&device->shutdown)) {
510 return false; // closing, stop trying.
511 } else if (SDL_GetAtomicInt(&device->hidden->device_disconnecting)) {
512 return false; // failing via the WASAPI management thread, stop trying.
513 } else if (device->hidden->device_dead) { // had a fatal error elsewhere, clean up and quit
514 IAudioClient_Stop(device->hidden->client);
515 WASAPI_DisconnectDevice(device);
516 SDL_assert(SDL_GetAtomicInt(&device->shutdown)); // so we don't come back through here.
517 return false; // already failed.
518 } else if (SDL_GetAtomicInt(&device->zombie)) {
519 return false; // we're already dead, so just leave and let the Zombie implementations take over.
520 } else if (!device->hidden->client) {
521 return true; // still waiting for activation.
522 }
523
524 return device->hidden->device_lost ? RecoverWasapiDevice(device) : true;
525}
526
527static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
528{
529 // get an endpoint buffer from WASAPI.
530 BYTE *buffer = NULL;
531
532 if (device->hidden->render) {
533 const HRESULT ret = IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer);
534 if (ret == AUDCLNT_E_BUFFER_TOO_LARGE) {
535 SDL_assert(buffer == NULL);
536 *buffer_size = 0; // just go back to WaitDevice and try again after the hardware has consumed some more data.
537 } else if (WasapiFailed(device, ret)) {
538 SDL_assert(buffer == NULL);
539 if (device->hidden->device_lost) { // just use an available buffer, we won't be playing it anyhow.
540 *buffer_size = 0; // we'll recover during WaitDevice and try again.
541 }
542 }
543 }
544
545 return (Uint8 *)buffer;
546}
547
548static bool WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
549{
550 if (device->hidden->render && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) { // definitely activated?
551 // WasapiFailed() will mark the device for reacquisition or removal elsewhere.
552 WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
553 }
554 return true;
555}
556
557static bool WASAPI_WaitDevice(SDL_AudioDevice *device)
558{
559 // WaitDevice does not hold the device lock, so check for recovery/disconnect details here.
560 while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
561 if (device->recording) {
562 // Recording devices should return immediately if there is any data available
563 UINT32 padding = 0;
564 if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
565 //SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
566 if (padding > 0) {
567 break;
568 }
569 }
570
571 switch (WaitForSingleObjectEx(device->hidden->event, 200, FALSE)) {
572 case WAIT_OBJECT_0:
573 case WAIT_TIMEOUT:
574 break;
575
576 default:
577 //SDL_Log("WASAPI FAILED EVENT!");
578 IAudioClient_Stop(device->hidden->client);
579 return false;
580 }
581 } else {
582 DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
583 if (waitResult == WAIT_OBJECT_0) {
584 UINT32 padding = 0;
585 if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
586 //SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
587 if (padding <= (UINT32)device->sample_frames) {
588 break;
589 }
590 }
591 } else if (waitResult != WAIT_TIMEOUT) {
592 //SDL_Log("WASAPI FAILED EVENT!");*/
593 IAudioClient_Stop(device->hidden->client);
594 return false;
595 }
596 }
597 }
598
599 return true;
600}
601
602static int WASAPI_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
603{
604 BYTE *ptr = NULL;
605 UINT32 frames = 0;
606 DWORD flags = 0;
607
608 while (device->hidden->capture) {
609 const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
610 if (ret == AUDCLNT_S_BUFFER_EMPTY) {
611 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.
612 }
613
614 WasapiFailed(device, ret); // mark device lost/failed if necessary.
615
616 if (ret == S_OK) {
617 const int total = ((int)frames) * device->hidden->framesize;
618 const int cpy = SDL_min(buflen, total);
619 const int leftover = total - cpy;
620 const bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? true : false;
621
622 SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
623
624 if (silent) {
625 SDL_memset(buffer, device->silence_value, cpy);
626 } else {
627 SDL_memcpy(buffer, ptr, cpy);
628 }
629
630 WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames));
631
632 return cpy;
633 }
634 }
635
636 return -1; // unrecoverable error.
637}
638
639static void WASAPI_FlushRecording(SDL_AudioDevice *device)
640{
641 BYTE *ptr = NULL;
642 UINT32 frames = 0;
643 DWORD flags = 0;
644
645 // just read until we stop getting packets, throwing them away.
646 while (!SDL_GetAtomicInt(&device->shutdown) && device->hidden->capture) {
647 const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
648 if (ret == AUDCLNT_S_BUFFER_EMPTY) {
649 break; // no more buffered data; we're done.
650 } else if (WasapiFailed(device, ret)) {
651 break; // failed for some other reason, abort.
652 } else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
653 break; // something broke.
654 }
655 }
656}
657
658static void WASAPI_CloseDevice(SDL_AudioDevice *device)
659{
660 if (device->hidden) {
661 ResetWasapiDevice(device);
662 SDL_free(device->hidden->devid);
663 SDL_free(device->hidden);
664 device->hidden = NULL;
665 }
666}
667
668static bool mgmtthrtask_PrepDevice(void *userdata)
669{
670 SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
671
672 /* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
673 !!! it will write into the kernel's audio buffer directly instead of
674 !!! shared memory that a user-mode mixer then writes to the kernel with
675 !!! everything else. Doing this means any other sound using this device will
676 !!! stop playing, including the user's MP3 player and system notification
677 !!! sounds. You'd probably need to release the device when the app isn't in
678 !!! the foreground, to be a good citizen of the system. It's doable, but it's
679 !!! more work and causes some annoyances, and I don't know what the latency
680 !!! wins actually look like. Maybe add a hint to force exclusive mode at
681 !!! some point. To be sure, defaulting to shared mode is the right thing to
682 !!! do in any case. */
683 const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
684
685 IAudioClient *client = device->hidden->client;
686 SDL_assert(client != NULL);
687
688 device->hidden->event = CreateEvent(NULL, FALSE, FALSE, NULL);
689 if (!device->hidden->event) {
690 return WIN_SetError("WASAPI can't create an event handle");
691 }
692
693 HRESULT ret;
694
695 WAVEFORMATEX *waveformat = NULL;
696 ret = IAudioClient_GetMixFormat(client, &waveformat);
697 if (FAILED(ret)) {
698 return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
699 }
700 SDL_assert(waveformat != NULL);
701 device->hidden->waveformat = waveformat;
702
703 SDL_AudioSpec newspec;
704 newspec.channels = (Uint8)waveformat->nChannels;
705
706 // Make sure we have a valid format that we can convert to whatever WASAPI wants.
707 const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat);
708
709 SDL_AudioFormat test_format;
710 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
711 while ((test_format = *(closefmts++)) != 0) {
712 if (test_format == wasapi_format) {
713 newspec.format = test_format;
714 break;
715 }
716 }
717
718 if (!test_format) {
719 return SDL_SetError("%s: Unsupported audio format", "wasapi");
720 }
721
722 REFERENCE_TIME default_period = 0;
723 ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL);
724 if (FAILED(ret)) {
725 return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
726 }
727
728 DWORD streamflags = 0;
729
730 /* we've gotten reports that WASAPI's resampler introduces distortions, but in the short term
731 it fixes some other WASAPI-specific quirks we haven't quite tracked down.
732 Refer to bug #6326 for the immediate concern. */
733#if 1
734 // favor WASAPI's resampler over our own
735 if ((DWORD)device->spec.freq != waveformat->nSamplesPerSec) {
736 streamflags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
737 waveformat->nSamplesPerSec = device->spec.freq;
738 waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
739 }
740#endif
741
742 newspec.freq = waveformat->nSamplesPerSec;
743
744 streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
745
746 int new_sample_frames = 0;
747 bool iaudioclient3_initialized = false;
748
749#ifdef __IAudioClient3_INTERFACE_DEFINED__
750 // Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED
751 if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
752 IAudioClient3 *client3 = NULL;
753 ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void**)&client3);
754 if (SUCCEEDED(ret)) {
755 UINT32 default_period_in_frames = 0;
756 UINT32 fundamental_period_in_frames = 0;
757 UINT32 min_period_in_frames = 0;
758 UINT32 max_period_in_frames = 0;
759 ret = IAudioClient3_GetSharedModeEnginePeriod(client3, waveformat,
760 &default_period_in_frames, &fundamental_period_in_frames, &min_period_in_frames, &max_period_in_frames);
761 if (SUCCEEDED(ret)) {
762 // IAudioClient3_InitializeSharedAudioStream only accepts the integral multiple of fundamental_period_in_frames
763 UINT32 period_in_frames = fundamental_period_in_frames * (UINT32)SDL_round((double)device->sample_frames / fundamental_period_in_frames);
764 period_in_frames = SDL_clamp(period_in_frames, min_period_in_frames, max_period_in_frames);
765
766 ret = IAudioClient3_InitializeSharedAudioStream(client3, streamflags, period_in_frames, waveformat, NULL);
767 if (SUCCEEDED(ret)) {
768 new_sample_frames = (int)period_in_frames;
769 iaudioclient3_initialized = true;
770 }
771 }
772
773 IAudioClient3_Release(client3);
774 }
775 }
776#endif
777
778 if (!iaudioclient3_initialized)
779 ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
780
781 if (FAILED(ret)) {
782 return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
783 }
784
785 ret = IAudioClient_SetEventHandle(client, device->hidden->event);
786 if (FAILED(ret)) {
787 return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret);
788 }
789
790 UINT32 bufsize = 0; // this is in sample frames, not samples, not bytes.
791 ret = IAudioClient_GetBufferSize(client, &bufsize);
792 if (FAILED(ret)) {
793 return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
794 }
795
796 // Match the callback size to the period size to cut down on the number of
797 // interrupts waited for in each call to WaitDevice
798 if (new_sample_frames <= 0) {
799 const float period_millis = default_period / 10000.0f;
800 const float period_frames = period_millis * newspec.freq / 1000.0f;
801 new_sample_frames = (int) SDL_ceilf(period_frames);
802 }
803
804 // regardless of what we calculated for the period size, clamp it to the expected hardware buffer size.
805 if (new_sample_frames > (int) bufsize) {
806 new_sample_frames = (int) bufsize;
807 }
808
809 // Update the fragment size as size in bytes
810 if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
811 return false;
812 }
813
814 device->hidden->framesize = SDL_AUDIO_FRAMESIZE(device->spec);
815
816 if (device->recording) {
817 IAudioCaptureClient *capture = NULL;
818 ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture);
819 if (FAILED(ret)) {
820 return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
821 }
822
823 SDL_assert(capture != NULL);
824 device->hidden->capture = capture;
825 ret = IAudioClient_Start(client);
826 if (FAILED(ret)) {
827 return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
828 }
829
830 WASAPI_FlushRecording(device); // MSDN says you should flush the recording endpoint right after startup.
831 } else {
832 IAudioRenderClient *render = NULL;
833 ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render);
834 if (FAILED(ret)) {
835 return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
836 }
837
838 SDL_assert(render != NULL);
839 device->hidden->render = render;
840 ret = IAudioClient_Start(client);
841 if (FAILED(ret)) {
842 return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
843 }
844 }
845
846 return true; // good to go.
847}
848
849// This is called once a device is activated, possibly asynchronously.
850bool WASAPI_PrepDevice(SDL_AudioDevice *device)
851{
852 bool rc = true;
853 return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) && rc);
854}
855
856static bool WASAPI_OpenDevice(SDL_AudioDevice *device)
857{
858 // Initialize all variables that we clean on shutdown
859 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
860 if (!device->hidden) {
861 return false;
862 } else if (!ActivateWasapiDevice(device)) {
863 return false; // already set error.
864 }
865
866 /* Ready, but possibly waiting for async device activation.
867 Until activation is successful, we will report silence from recording
868 devices and ignore data on playback devices. Upon activation, we'll make
869 sure any bound audio streams are adjusted for the final device format. */
870
871 return true;
872}
873
874static void WASAPI_ThreadInit(SDL_AudioDevice *device)
875{
876 // this thread uses COM.
877 if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked!
878 device->hidden->coinitialized = true;
879 }
880
881 // Set this thread to very high "Pro Audio" priority.
882 if (pAvSetMmThreadCharacteristicsW) {
883 DWORD idx = 0;
884 device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
885 } else {
886 SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
887 }
888}
889
890static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
891{
892 // Set this thread back to normal priority.
893 if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
894 pAvRevertMmThreadCharacteristics(device->hidden->task);
895 device->hidden->task = NULL;
896 }
897
898 if (device->hidden->coinitialized) {
899 WIN_CoUninitialize();
900 device->hidden->coinitialized = false;
901 }
902}
903
904static bool mgmtthrtask_FreeDeviceHandle(void *userdata)
905{
906 SDL_IMMDevice_FreeDeviceHandle((SDL_AudioDevice *) userdata);
907 return true;
908}
909
910static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
911{
912 bool rc;
913 WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
914}
915
916static bool mgmtthrtask_DeinitializeStart(void *userdata)
917{
918 StopWasapiHotplug();
919 return true;
920}
921
922static void WASAPI_DeinitializeStart(void)
923{
924 bool rc;
925 WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
926}
927
928static void WASAPI_Deinitialize(void)
929{
930 DeinitManagementThread();
931}
932
933static bool WASAPI_Init(SDL_AudioDriverImpl *impl)
934{
935 if (!InitManagementThread()) {
936 return false;
937 }
938
939 impl->DetectDevices = WASAPI_DetectDevices;
940 impl->ThreadInit = WASAPI_ThreadInit;
941 impl->ThreadDeinit = WASAPI_ThreadDeinit;
942 impl->OpenDevice = WASAPI_OpenDevice;
943 impl->PlayDevice = WASAPI_PlayDevice;
944 impl->WaitDevice = WASAPI_WaitDevice;
945 impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
946 impl->WaitRecordingDevice = WASAPI_WaitDevice;
947 impl->RecordDevice = WASAPI_RecordDevice;
948 impl->FlushRecording = WASAPI_FlushRecording;
949 impl->CloseDevice = WASAPI_CloseDevice;
950 impl->DeinitializeStart = WASAPI_DeinitializeStart;
951 impl->Deinitialize = WASAPI_Deinitialize;
952 impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
953
954 impl->HasRecordingSupport = true;
955
956 return true;
957}
958
959AudioBootStrap WASAPI_bootstrap = {
960 "wasapi", "WASAPI", WASAPI_Init, false, false
961};
962
963#endif // SDL_AUDIO_DRIVER_WASAPI
diff --git a/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.h b/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.h
new file mode 100644
index 0000000..5e528dc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.h
@@ -0,0 +1,61 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_wasapi_h_
24#define SDL_wasapi_h_
25
26#ifdef __cplusplus
27extern "C" {
28#endif
29
30#include "../SDL_sysaudio.h"
31
32struct SDL_PrivateAudioData
33{
34 WCHAR *devid;
35 WAVEFORMATEX *waveformat;
36 IAudioClient *client;
37 IAudioRenderClient *render;
38 IAudioCaptureClient *capture;
39 HANDLE event;
40 HANDLE task;
41 bool coinitialized;
42 int framesize;
43 SDL_AtomicInt device_disconnecting;
44 bool device_lost;
45 bool device_dead;
46};
47
48// win32 implementation calls into these.
49bool WASAPI_PrepDevice(SDL_AudioDevice *device);
50void WASAPI_DisconnectDevice(SDL_AudioDevice *device); // don't hold the device lock when calling this!
51
52
53// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
54typedef bool (*ManagementThreadTask)(void *userdata);
55bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_until_complete);
56
57#ifdef __cplusplus
58}
59#endif
60
61#endif // SDL_wasapi_h_