diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/audio')
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 | ||
| 28 | static 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 | |||
| 101 | static SDL_AudioDriver current_audio; | ||
| 102 | |||
| 103 | // Deduplicated list of audio bootstrap drivers. | ||
| 104 | static const AudioBootStrap *deduped_bootstrap[SDL_arraysize(bootstrap) - 1]; | ||
| 105 | |||
| 106 | int 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 | |||
| 134 | const 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 | |||
| 143 | const char *SDL_GetCurrentAudioDriver(void) | ||
| 144 | { | ||
| 145 | return current_audio.name; | ||
| 146 | } | ||
| 147 | |||
| 148 | int 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 | |||
| 169 | int *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 | |||
| 179 | void 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 | |||
| 199 | void 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. | ||
| 223 | static 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. | ||
| 236 | static 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 | |||
| 275 | bool 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 | |||
| 285 | bool 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. | ||
| 302 | static 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 | |||
| 311 | static bool ZombiePlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) | ||
| 312 | { | ||
| 313 | return true; // no-op, just throw the audio away. | ||
| 314 | } | ||
| 315 | |||
| 316 | static Uint8 *ZombieGetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 317 | { | ||
| 318 | return device->work_buffer; | ||
| 319 | } | ||
| 320 | |||
| 321 | static 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 | |||
| 328 | static 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 | */ | ||
| 348 | static void ClosePhysicalAudioDevice(SDL_AudioDevice *device); | ||
| 349 | |||
| 350 | |||
| 351 | SDL_COMPILE_TIME_ASSERT(check_lowest_audio_default_value, SDL_AUDIO_DEVICE_DEFAULT_RECORDING < SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK); | ||
| 352 | |||
| 353 | static SDL_AtomicInt last_device_instance_id; // increments on each device add to provide unique instance IDs | ||
| 354 | static 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 | |||
| 367 | bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid) | ||
| 368 | { | ||
| 369 | return (devid & (1 << 1)) != 0; | ||
| 370 | } | ||
| 371 | |||
| 372 | bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid) | ||
| 373 | { | ||
| 374 | return (devid & (1 << 0)) != 0; | ||
| 375 | } | ||
| 376 | |||
| 377 | static 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 | |||
| 385 | static 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. | ||
| 394 | static 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! */ | ||
| 449 | static 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 | |||
| 474 | static 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. | ||
| 524 | static 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. | ||
| 560 | static 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! | ||
| 587 | void 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 ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count, -1); | ||
| 594 | } | ||
| 595 | SDL_UnlockRWLock(current_audio.device_hash_lock); | ||
| 596 | DestroyPhysicalAudioDevice(device); // ...and nuke it. | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | void RefPhysicalAudioDevice(SDL_AudioDevice *device) | ||
| 601 | { | ||
| 602 | SDL_AtomicIncRef(&device->refcount); | ||
| 603 | } | ||
| 604 | |||
| 605 | static 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(¤t_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 | |||
| 669 | static 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, ¤t_audio.recording_device_count); | ||
| 673 | } | ||
| 674 | |||
| 675 | static SDL_AudioDevice *CreateAudioPlaybackDevice(const char *name, const SDL_AudioSpec *spec, void *handle) | ||
| 676 | { | ||
| 677 | return CreatePhysicalAudioDevice(name, false, spec, handle, ¤t_audio.playback_device_count); | ||
| 678 | } | ||
| 679 | |||
| 680 | // The audio backends call this when a new device is plugged in. | ||
| 681 | SDL_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. | ||
| 724 | void 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 | |||
| 803 | static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ } | ||
| 804 | static bool SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { return true; /* no-op. */ } | ||
| 805 | static bool SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { return true; /* no-op. */ } | ||
| 806 | static bool SDL_AudioWaitRecordingDevice_Default(SDL_AudioDevice *device) { return true; /* no-op. */ } | ||
| 807 | static void SDL_AudioFlushRecording_Default(SDL_AudioDevice *device) { /* no-op. */ } | ||
| 808 | static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } | ||
| 809 | static void SDL_AudioDeinitializeStart_Default(void) { /* no-op. */ } | ||
| 810 | static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ } | ||
| 811 | static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ } | ||
| 812 | |||
| 813 | static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) | ||
| 814 | { | ||
| 815 | SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL); | ||
| 816 | } | ||
| 817 | |||
| 818 | static 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 | |||
| 830 | static Uint8 *SDL_AudioGetDeviceBuf_Default(SDL_AudioDevice *device, int *buffer_size) | ||
| 831 | { | ||
| 832 | *buffer_size = 0; | ||
| 833 | return NULL; | ||
| 834 | } | ||
| 835 | |||
| 836 | static int SDL_AudioRecordDevice_Default(SDL_AudioDevice *device, void *buffer, int buflen) | ||
| 837 | { | ||
| 838 | SDL_Unsupported(); | ||
| 839 | return -1; | ||
| 840 | } | ||
| 841 | |||
| 842 | static 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. | ||
| 848 | static 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 | |||
| 868 | typedef struct FindLowestDeviceIDData | ||
| 869 | { | ||
| 870 | const bool recording; | ||
| 871 | SDL_AudioDeviceID highest; | ||
| 872 | SDL_AudioDevice *result; | ||
| 873 | } FindLowestDeviceIDData; | ||
| 874 | |||
| 875 | static 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 | |||
| 890 | static 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 | |||
| 902 | static 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. | ||
| 910 | bool 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 = ¤t_audio.pending_events; | ||
| 969 | current_audio.device_hash_lock = device_hash_lock; | ||
| 970 | current_audio.device_hash = device_hash; | ||
| 971 | if (bootstrap[i]->init(¤t_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 = ¤t_audio.pending_events; | ||
| 993 | current_audio.device_hash_lock = device_hash_lock; | ||
| 994 | current_audio.device_hash = device_hash; | ||
| 995 | if (bootstrap[i]->init(¤t_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 | |||
| 1048 | static 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 | |||
| 1059 | void 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(¤t_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(¤t_audio.playback_device_count, 0); | ||
| 1079 | SDL_SetAtomicInt(¤t_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 | |||
| 1100 | void SDL_AudioThreadFinalize(SDL_AudioDevice *device) | ||
| 1101 | { | ||
| 1102 | } | ||
| 1103 | |||
| 1104 | static 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 | |||
| 1114 | void SDL_PlaybackAudioThreadSetup(SDL_AudioDevice *device) | ||
| 1115 | { | ||
| 1116 | SDL_assert(!device->recording); | ||
| 1117 | current_audio.impl.ThreadInit(device); | ||
| 1118 | } | ||
| 1119 | |||
| 1120 | bool 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 | |||
| 1240 | void 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 | |||
| 1252 | static 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 | |||
| 1273 | void SDL_RecordingAudioThreadSetup(SDL_AudioDevice *device) | ||
| 1274 | { | ||
| 1275 | SDL_assert(device->recording); | ||
| 1276 | current_audio.impl.ThreadInit(device); | ||
| 1277 | } | ||
| 1278 | |||
| 1279 | bool 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 | |||
| 1360 | void 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 | |||
| 1368 | static 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 | |||
| 1385 | typedef struct CountAudioDevicesData | ||
| 1386 | { | ||
| 1387 | int devs_seen; | ||
| 1388 | const int num_devices; | ||
| 1389 | SDL_AudioDeviceID *result; | ||
| 1390 | const bool recording; | ||
| 1391 | } CountAudioDevicesData; | ||
| 1392 | |||
| 1393 | static 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 | |||
| 1408 | static 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 ? ¤t_audio.recording_device_count : ¤t_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 | |||
| 1440 | SDL_AudioDeviceID *SDL_GetAudioPlaybackDevices(int *count) | ||
| 1441 | { | ||
| 1442 | return GetAudioDevices(count, false); | ||
| 1443 | } | ||
| 1444 | |||
| 1445 | SDL_AudioDeviceID *SDL_GetAudioRecordingDevices(int *count) | ||
| 1446 | { | ||
| 1447 | return GetAudioDevices(count, true); | ||
| 1448 | } | ||
| 1449 | |||
| 1450 | typedef struct FindAudioDeviceByCallbackData | ||
| 1451 | { | ||
| 1452 | bool (*callback)(SDL_AudioDevice *device, void *userdata); | ||
| 1453 | void *userdata; | ||
| 1454 | SDL_AudioDevice *retval; | ||
| 1455 | } FindAudioDeviceByCallbackData; | ||
| 1456 | |||
| 1457 | static 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. | ||
| 1474 | SDL_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 | |||
| 1493 | static bool TestDeviceHandleCallback(SDL_AudioDevice *device, void *handle) | ||
| 1494 | { | ||
| 1495 | return device->handle == handle; | ||
| 1496 | } | ||
| 1497 | |||
| 1498 | SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) | ||
| 1499 | { | ||
| 1500 | return SDL_FindPhysicalAudioDeviceByCallback(TestDeviceHandleCallback, handle); | ||
| 1501 | } | ||
| 1502 | |||
| 1503 | const 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 | |||
| 1515 | bool 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 | |||
| 1535 | int *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. | ||
| 1560 | static 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. | ||
| 1568 | static 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 | |||
| 1606 | void 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 | |||
| 1625 | static 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 | |||
| 1645 | static 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 | |||
| 1677 | void 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 | |||
| 1685 | char *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. | ||
| 1693 | static 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 | |||
| 1766 | SDL_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 | |||
| 1828 | static 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 | |||
| 1839 | bool SDL_PauseAudioDevice(SDL_AudioDeviceID devid) | ||
| 1840 | { | ||
| 1841 | return SetLogicalAudioDevicePauseState(devid, 1); | ||
| 1842 | } | ||
| 1843 | |||
| 1844 | bool SDLCALL SDL_ResumeAudioDevice(SDL_AudioDeviceID devid) | ||
| 1845 | { | ||
| 1846 | return SetLogicalAudioDevicePauseState(devid, 0); | ||
| 1847 | } | ||
| 1848 | |||
| 1849 | bool 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 | |||
| 1861 | float 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 | |||
| 1870 | bool 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 | |||
| 1888 | bool 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 | |||
| 1912 | bool 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 | |||
| 1995 | bool 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. :/ | ||
| 2001 | void 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 | |||
| 2072 | void SDL_UnbindAudioStream(SDL_AudioStream *stream) | ||
| 2073 | { | ||
| 2074 | SDL_UnbindAudioStreams(&stream, 1); | ||
| 2075 | } | ||
| 2076 | |||
| 2077 | SDL_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 | |||
| 2097 | SDL_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 | |||
| 2164 | bool 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 | |||
| 2174 | bool 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 | |||
| 2184 | bool 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. | ||
| 2204 | static 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 | |||
| 2218 | const 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 | |||
| 2228 | const 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 | |||
| 2247 | int 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. | ||
| 2253 | void 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(¤t_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(¤t_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 | |||
| 2405 | bool 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 | |||
| 2493 | bool 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.) | ||
| 2503 | void 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 = ¤t_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 | |||
| 25 | extern 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 | |||
| 25 | typedef void (*SDL_AudioChannelConverter)(float *dst, const float *src, int num_frames); | ||
| 26 | |||
| 27 | static 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 | |||
| 44 | static 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 | |||
| 62 | static 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 | |||
| 81 | static 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 | |||
| 101 | static 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 | |||
| 122 | static 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 | |||
| 144 | static 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 | |||
| 167 | static 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 | |||
| 179 | static 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 | |||
| 196 | static 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 | |||
| 214 | static 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 | |||
| 233 | static 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 | |||
| 253 | static 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 | |||
| 274 | static 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 | |||
| 296 | static 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 | |||
| 308 | static 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 | |||
| 322 | static 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 | |||
| 341 | static 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 | |||
| 360 | static 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 | |||
| 380 | static 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 | |||
| 401 | static 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 | |||
| 423 | static 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 | |||
| 435 | static 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 | |||
| 450 | static 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 | |||
| 466 | static 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 | |||
| 485 | static 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 | |||
| 505 | static 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 | |||
| 528 | static 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 | |||
| 550 | static 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 | |||
| 562 | static 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 | |||
| 578 | static 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 | |||
| 594 | static 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 | |||
| 610 | static 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 | |||
| 630 | static 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 | |||
| 653 | static 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 | |||
| 675 | static 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 | |||
| 687 | static 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 | |||
| 704 | static 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 | |||
| 721 | static 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 | |||
| 738 | static 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 | |||
| 755 | static 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 | |||
| 778 | static 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 | |||
| 800 | static 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 | |||
| 812 | static 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 | |||
| 830 | static 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 | |||
| 848 | static 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 | |||
| 868 | static 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 | |||
| 888 | static 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 | |||
| 908 | static 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 | |||
| 931 | static 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 | |||
| 943 | static 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 | |||
| 962 | static 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 | |||
| 981 | static 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 | |||
| 1000 | static 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 | |||
| 1019 | static 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 | |||
| 1038 | static 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 | |||
| 1058 | static 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. | ||
| 34 | static 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. | ||
| 63 | static 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 | |||
| 102 | static 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 | |||
| 122 | static bool SDL_IsSupportedChannelCount(const int channels) | ||
| 123 | { | ||
| 124 | return ((channels >= 1) && (channels <= 8)); | ||
| 125 | } | ||
| 126 | |||
| 127 | bool 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 | |||
| 140 | bool 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. | ||
| 153 | static 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". :) | ||
| 237 | void 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. | ||
| 392 | static 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 | |||
| 402 | static 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 | |||
| 416 | static 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 | |||
| 439 | SDL_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 | |||
| 475 | SDL_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 | |||
| 489 | bool 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 | |||
| 501 | bool 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 | |||
| 513 | bool 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 | |||
| 522 | bool 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 | |||
| 531 | bool 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 | |||
| 555 | bool 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 | |||
| 617 | bool 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 | |||
| 657 | bool 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 | |||
| 662 | bool 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 | |||
| 667 | int *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 | |||
| 685 | int *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 | |||
| 703 | float 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 | |||
| 717 | bool 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 | |||
| 740 | float 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 | |||
| 754 | bool 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 | |||
| 769 | static 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 | |||
| 780 | static 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 | |||
| 831 | static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len) | ||
| 832 | { | ||
| 833 | SDL_free((void*) buf); | ||
| 834 | } | ||
| 835 | |||
| 836 | bool 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 | |||
| 872 | bool 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. */ | ||
| 887 | static 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 | |||
| 904 | static 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 | |||
| 959 | static 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 | |||
| 983 | static 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! | ||
| 999 | static 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 | ||
| 1129 | int 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 | |||
| 1236 | int 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 | ||
| 1242 | int 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. | ||
| 1268 | int 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 | |||
| 1285 | bool 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 | |||
| 1302 | void 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 | |||
| 1329 | static 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 | |||
| 1334 | bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) | ||
| 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 | |||
| 48 | static 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 | |||
| 74 | static bool test_stub(int fd) | ||
| 75 | { | ||
| 76 | return true; | ||
| 77 | } | ||
| 78 | |||
| 79 | static 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 | |||
| 118 | void 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 | |||
| 39 | extern 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 | |||
| 26 | typedef struct SDL_MemoryPool SDL_MemoryPool; | ||
| 27 | |||
| 28 | struct SDL_MemoryPool | ||
| 29 | { | ||
| 30 | void *free_blocks; | ||
| 31 | size_t block_size; | ||
| 32 | size_t num_free; | ||
| 33 | size_t max_free; | ||
| 34 | }; | ||
| 35 | |||
| 36 | struct 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 | |||
| 54 | struct 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 | ||
| 68 | static 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 | ||
| 74 | static 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 | ||
| 87 | static 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 | ||
| 99 | static 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. | ||
| 114 | static 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 | ||
| 124 | static 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 | |||
| 141 | void 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 | |||
| 152 | SDL_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 | |||
| 171 | static 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 | |||
| 178 | void 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 | |||
| 193 | static void FlushAudioTrack(SDL_AudioTrack *track) | ||
| 194 | { | ||
| 195 | track->flushed = true; | ||
| 196 | } | ||
| 197 | |||
| 198 | void SDL_FlushAudioQueue(SDL_AudioQueue *queue) | ||
| 199 | { | ||
| 200 | SDL_AudioTrack *track = queue->tail; | ||
| 201 | |||
| 202 | if (track) { | ||
| 203 | FlushAudioTrack(track); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | void 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 | |||
| 231 | SDL_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 | |||
| 262 | static 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 | |||
| 269 | static 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 | |||
| 290 | void 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 | |||
| 308 | static 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 | |||
| 321 | bool 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 | |||
| 368 | void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue) | ||
| 369 | { | ||
| 370 | return queue->head; | ||
| 371 | } | ||
| 372 | |||
| 373 | size_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 | |||
| 410 | static 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 | |||
| 430 | static 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 | |||
| 445 | static 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 | |||
| 489 | static 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 | |||
| 525 | const 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 | |||
| 604 | size_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 | |||
| 627 | bool 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 | |||
| 28 | typedef void (SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen); | ||
| 29 | |||
| 30 | typedef struct SDL_AudioQueue SDL_AudioQueue; | ||
| 31 | typedef struct SDL_AudioTrack SDL_AudioTrack; | ||
| 32 | |||
| 33 | // Create a new audio queue | ||
| 34 | extern SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size); | ||
| 35 | |||
| 36 | // Destroy an audio queue | ||
| 37 | extern void SDL_DestroyAudioQueue(SDL_AudioQueue *queue); | ||
| 38 | |||
| 39 | // Completely clear the queue | ||
| 40 | extern void SDL_ClearAudioQueue(SDL_AudioQueue *queue); | ||
| 41 | |||
| 42 | // Mark the last track as flushed | ||
| 43 | extern 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 | ||
| 47 | extern 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 | ||
| 51 | extern 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 | ||
| 54 | extern 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` | ||
| 60 | extern void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track); | ||
| 61 | |||
| 62 | // Iterate over the tracks in the queue | ||
| 63 | extern void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue); | ||
| 64 | |||
| 65 | // Query and update the track iterator | ||
| 66 | // REQUIRES: `*inout_iter != NULL` (a valid iterator) | ||
| 67 | extern size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed); | ||
| 68 | |||
| 69 | extern 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 | ||
| 75 | extern size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue); | ||
| 76 | |||
| 77 | extern 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 | ||
| 56 | typedef 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 | |||
| 71 | static 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 | |||
| 94 | static 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 | |||
| 112 | static 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 | |||
| 136 | static 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 | ||
| 268 | static 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 | ||
| 401 | static 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 | ||
| 417 | static 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. | ||
| 437 | static 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 | ||
| 447 | static 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 | |||
| 454 | static Cubic ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING][RESAMPLER_SAMPLES_PER_FRAME]; | ||
| 455 | |||
| 456 | static 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 | |||
| 516 | typedef void (*ResampleFrameFunc)(const float *src, float *dst, const Cubic *filter, float frac, int chans); | ||
| 517 | static ResampleFrameFunc ResampleFrame[8]; | ||
| 518 | |||
| 519 | // Transpose 4x4 floats | ||
| 520 | static 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 | |||
| 533 | static 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 | |||
| 575 | void 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 | |||
| 585 | Sint64 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 | |||
| 607 | int 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 | |||
| 614 | int 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 | ||
| 622 | SDL_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 | |||
| 632 | SDL_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 | |||
| 642 | Sint64 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 | |||
| 659 | Sint64 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 | |||
| 678 | void 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 | |||
| 29 | Sint64 SDL_GetResampleRate(int src_rate, int dst_rate); | ||
| 30 | |||
| 31 | int SDL_GetResamplerHistoryFrames(void); | ||
| 32 | int SDL_GetResamplerPaddingFrames(Sint64 resample_rate); | ||
| 33 | |||
| 34 | Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset); | ||
| 35 | Sint64 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 | ||
| 40 | void 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 | ||
| 30 | SDL_COMPILE_TIME_ASSERT(float_bits, sizeof(float) == sizeof(Uint32)); | ||
| 31 | |||
| 32 | union float_bits { | ||
| 33 | Uint32 u32; | ||
| 34 | float f32; | ||
| 35 | }; | ||
| 36 | |||
| 37 | static 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 | |||
| 52 | static 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 | |||
| 67 | static 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 | |||
| 82 | static 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 | |||
| 96 | static 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 | |||
| 117 | static 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 | |||
| 139 | static 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 | |||
| 160 | static 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 | |||
| 184 | static 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 | |||
| 193 | static 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 | ||
| 223 | static 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 | |||
| 256 | static 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 | |||
| 287 | static 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 | |||
| 317 | static 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 | |||
| 344 | static 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 | |||
| 378 | static 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 | |||
| 412 | static 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 | |||
| 445 | static 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 | ||
| 486 | static 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 | |||
| 504 | static 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 | ||
| 530 | static 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 | |||
| 554 | static 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 | |||
| 580 | static 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 | |||
| 602 | static 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 | |||
| 626 | static 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 | |||
| 652 | static 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 | |||
| 684 | static 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 | |||
| 709 | static 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 | |||
| 733 | static 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 | |||
| 749 | static 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. | ||
| 776 | static void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples) = NULL; | ||
| 777 | static void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples) = NULL; | ||
| 778 | static void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples) = NULL; | ||
| 779 | static void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples) = NULL; | ||
| 780 | static void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples) = NULL; | ||
| 781 | static void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples) = NULL; | ||
| 782 | static void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL; | ||
| 783 | static void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL; | ||
| 784 | |||
| 785 | static void (*SDL_Convert_Swap16)(Uint16* dst, const Uint16* src, int num_samples) = NULL; | ||
| 786 | static void (*SDL_Convert_Swap32)(Uint32* dst, const Uint32* src, int num_samples) = NULL; | ||
| 787 | |||
| 788 | void 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 | |||
| 825 | void 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 | |||
| 862 | void 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 | |||
| 871 | void 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 | */ | ||
| 30 | static 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 | |||
| 89 | bool 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 | |||
| 51 | typedef struct SDL_AudioDevice SDL_AudioDevice; | ||
| 52 | typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice; | ||
| 53 | |||
| 54 | // Used by src/SDL.c to initialize a particular audio driver. | ||
| 55 | extern bool SDL_InitAudio(const char *driver_name); | ||
| 56 | |||
| 57 | // Used by src/SDL.c to shut down previously-initialized audio. | ||
| 58 | extern 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. | ||
| 61 | const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format); | ||
| 62 | |||
| 63 | // Must be called at least once before using converters. | ||
| 64 | extern void SDL_ChooseAudioConverters(void); | ||
| 65 | extern 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(). */ | ||
| 70 | extern 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. */ | ||
| 74 | extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device); | ||
| 75 | |||
| 76 | // Backends should call this if the system default device changes. | ||
| 77 | extern 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. | ||
| 80 | extern 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. | ||
| 83 | extern 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. | ||
| 86 | extern 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. | ||
| 89 | extern 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. | ||
| 92 | extern 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. | ||
| 95 | int 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. | ||
| 98 | extern char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen); | ||
| 99 | |||
| 100 | // Backends can call these to change a device's refcount. | ||
| 101 | extern void RefPhysicalAudioDevice(SDL_AudioDevice *device); | ||
| 102 | extern 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. | ||
| 105 | extern void SDL_PlaybackAudioThreadSetup(SDL_AudioDevice *device); | ||
| 106 | extern bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device); | ||
| 107 | extern void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device); | ||
| 108 | extern void SDL_RecordingAudioThreadSetup(SDL_AudioDevice *device); | ||
| 109 | extern bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device); | ||
| 110 | extern void SDL_RecordingAudioThreadShutdown(SDL_AudioDevice *device); | ||
| 111 | extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device); | ||
| 112 | |||
| 113 | extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt); | ||
| 114 | extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt); | ||
| 115 | extern void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize); | ||
| 116 | |||
| 117 | extern bool SDL_ChannelMapIsDefault(const int *map, int channels); | ||
| 118 | extern 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! | ||
| 121 | extern 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). | ||
| 129 | extern 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). | ||
| 133 | extern bool SDL_AudioChannelMapsEqual(int channels, const int *channel_map_a, const int *channel_map_b); | ||
| 134 | |||
| 135 | // allocate+copy a channel map. | ||
| 136 | extern 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. | ||
| 139 | extern void OnAudioStreamCreated(SDL_AudioStream *stream); | ||
| 140 | extern 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. | ||
| 143 | extern 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. | ||
| 146 | extern bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput); | ||
| 147 | |||
| 148 | |||
| 149 | typedef 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 | |||
| 174 | typedef struct SDL_PendingAudioDeviceEvent | ||
| 175 | { | ||
| 176 | Uint32 type; | ||
| 177 | SDL_AudioDeviceID devid; | ||
| 178 | struct SDL_PendingAudioDeviceEvent *next; | ||
| 179 | } SDL_PendingAudioDeviceEvent; | ||
| 180 | |||
| 181 | typedef 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 | |||
| 200 | struct SDL_AudioQueue; // forward decl. | ||
| 201 | |||
| 202 | struct 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. */ | ||
| 244 | struct 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 | |||
| 278 | struct 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 | |||
| 357 | typedef 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. | ||
| 367 | extern AudioBootStrap PRIVATEAUDIO_bootstrap; | ||
| 368 | extern AudioBootStrap PIPEWIRE_PREFERRED_bootstrap; | ||
| 369 | extern AudioBootStrap PIPEWIRE_bootstrap; | ||
| 370 | extern AudioBootStrap PULSEAUDIO_bootstrap; | ||
| 371 | extern AudioBootStrap ALSA_bootstrap; | ||
| 372 | extern AudioBootStrap JACK_bootstrap; | ||
| 373 | extern AudioBootStrap SNDIO_bootstrap; | ||
| 374 | extern AudioBootStrap NETBSDAUDIO_bootstrap; | ||
| 375 | extern AudioBootStrap DSP_bootstrap; | ||
| 376 | extern AudioBootStrap WASAPI_bootstrap; | ||
| 377 | extern AudioBootStrap DSOUND_bootstrap; | ||
| 378 | extern AudioBootStrap WINMM_bootstrap; | ||
| 379 | extern AudioBootStrap HAIKUAUDIO_bootstrap; | ||
| 380 | extern AudioBootStrap COREAUDIO_bootstrap; | ||
| 381 | extern AudioBootStrap DISKAUDIO_bootstrap; | ||
| 382 | extern AudioBootStrap DUMMYAUDIO_bootstrap; | ||
| 383 | extern AudioBootStrap AAUDIO_bootstrap; | ||
| 384 | extern AudioBootStrap OPENSLES_bootstrap; | ||
| 385 | extern AudioBootStrap PS2AUDIO_bootstrap; | ||
| 386 | extern AudioBootStrap PSPAUDIO_bootstrap; | ||
| 387 | extern AudioBootStrap VITAAUD_bootstrap; | ||
| 388 | extern AudioBootStrap N3DSAUDIO_bootstrap; | ||
| 389 | extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap; | ||
| 390 | extern 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 | ||
| 27 | SDL_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 | */ | ||
| 44 | static 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 | |||
| 53 | typedef 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 | |||
| 90 | typedef struct MS_ADPCM_CoeffData | ||
| 91 | { | ||
| 92 | Uint16 coeffcount; | ||
| 93 | Sint16 *coeff; | ||
| 94 | Sint16 aligndummy; // Has to be last member. | ||
| 95 | } MS_ADPCM_CoeffData; | ||
| 96 | |||
| 97 | typedef 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 | ||
| 105 | static 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 | ||
| 233 | static 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 | |||
| 323 | static 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 | |||
| 337 | static 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 | |||
| 376 | static 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 | |||
| 494 | static 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 | |||
| 530 | static 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 | */ | ||
| 590 | static 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 | |||
| 637 | static 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 | |||
| 736 | static 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 | |||
| 789 | static 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 | |||
| 854 | static 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 | |||
| 923 | static 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 | */ | ||
| 962 | static 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 | |||
| 1031 | static 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 | |||
| 1134 | static 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 | |||
| 1162 | static 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 | |||
| 1304 | static 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 | |||
| 1347 | static 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 | |||
| 1398 | static 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 | |||
| 1440 | static 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 | |||
| 1459 | static 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 | |||
| 1478 | static 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 | |||
| 1497 | static 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 | |||
| 1506 | static 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 | |||
| 1538 | static 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 | |||
| 1566 | static int WaveReadChunkData(SDL_IOStream *src, WaveChunk *chunk) | ||
| 1567 | { | ||
| 1568 | return WaveReadPartialChunkData(src, chunk, chunk->length); | ||
| 1569 | } | ||
| 1570 | |||
| 1571 | typedef 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 | } | ||
| 1582 | static 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 | |||
| 1591 | static 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 | |||
| 1602 | static 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 | |||
| 1670 | static 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 | |||
| 1773 | static 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 | |||
| 2079 | bool 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); | ||
| 2127 | done: | ||
| 2128 | if (closeio && src) { | ||
| 2129 | SDL_CloseIO(src); | ||
| 2130 | } | ||
| 2131 | return result; | ||
| 2132 | } | ||
| 2133 | |||
| 2134 | bool 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. | ||
| 50 | typedef 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. | ||
| 73 | typedef 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. | ||
| 95 | typedef 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. | ||
| 105 | typedef 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. | ||
| 115 | typedef 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. | ||
| 125 | typedef enum WaveFactChunkHint | ||
| 126 | { | ||
| 127 | FactNoHint, | ||
| 128 | FactTruncate, | ||
| 129 | FactStrict, | ||
| 130 | FactIgnoreZero, | ||
| 131 | FactIgnore | ||
| 132 | } WaveFactChunkHint; | ||
| 133 | |||
| 134 | typedef 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 | |||
| 35 | struct 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 | |||
| 56 | typedef 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; | ||
| 62 | static AAUDIO_Data ctx; | ||
| 63 | |||
| 64 | static 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 | |||
| 78 | static 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 | |||
| 89 | static 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 | |||
| 157 | static 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 | |||
| 164 | static 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 | |||
| 176 | static bool BuildAAudioStream(SDL_AudioDevice *device); | ||
| 177 | |||
| 178 | static 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 | |||
| 216 | static 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 | |||
| 235 | static 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 | |||
| 253 | static 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 | |||
| 276 | static 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! | ||
| 391 | static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted) | ||
| 392 | { | ||
| 393 | SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1); | ||
| 394 | } | ||
| 395 | |||
| 396 | static 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 | |||
| 430 | static 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 | ||
| 454 | void 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 | ||
| 462 | static 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 | |||
| 477 | void AAUDIO_ResumeDevices(void) | ||
| 478 | { | ||
| 479 | if (ctx.handle) { // AAUDIO driver is used? | ||
| 480 | (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL); | ||
| 481 | } | ||
| 482 | } | ||
| 483 | |||
| 484 | static 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 | |||
| 497 | static 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 | |||
| 547 | AudioBootStrap 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 | |||
| 28 | extern void AAUDIO_ResumeDevices(void); | ||
| 29 | extern 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 | |||
| 24 | SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode)) | ||
| 25 | SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state)) | ||
| 26 | SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder * *builder)) | ||
| 27 | SDL_PROC(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder * builder, int32_t deviceId)) | ||
| 28 | SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder * builder, int32_t sampleRate)) | ||
| 29 | SDL_PROC(void, AAudioStreamBuilder_setChannelCount, (AAudioStreamBuilder * builder, int32_t channelCount)) | ||
| 30 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuilder * builder, int32_t samplesPerFrame)) | ||
| 31 | SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format)) | ||
| 32 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode)) | ||
| 33 | SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction)) | ||
| 34 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) | ||
| 35 | SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode)) | ||
| 36 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28 | ||
| 37 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28 | ||
| 38 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28 | ||
| 39 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29 | ||
| 40 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28 | ||
| 41 | SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30 | ||
| 42 | SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData)) | ||
| 43 | SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames)) | ||
| 44 | SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData)) | ||
| 45 | SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream)) | ||
| 46 | SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder)) | ||
| 47 | SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) // API 30 | ||
| 48 | SDL_PROC(aaudio_result_t, AAudioStream_close, (AAudioStream * stream)) | ||
| 49 | SDL_PROC(aaudio_result_t, AAudioStream_requestStart, (AAudioStream * stream)) | ||
| 50 | SDL_PROC(aaudio_result_t, AAudioStream_requestPause, (AAudioStream * stream)) | ||
| 51 | SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stream)) | ||
| 52 | SDL_PROC(aaudio_result_t, AAudioStream_requestStop, (AAudioStream * stream)) | ||
| 53 | SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream * stream)) | ||
| 54 | SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream * stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds)) | ||
| 55 | SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)) | ||
| 56 | SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)) | ||
| 57 | SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames)) | ||
| 58 | SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream)) | ||
| 59 | SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream)) | ||
| 60 | SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream)) | ||
| 61 | SDL_PROC(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream)) | ||
| 62 | SDL_PROC_UNUSED(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream)) | ||
| 63 | SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream)) | ||
| 64 | SDL_PROC(int32_t, AAudioStream_getChannelCount, (AAudioStream * stream)) | ||
| 65 | SDL_PROC_UNUSED(int32_t, AAudioStream_getSamplesPerFrame, (AAudioStream * stream)) | ||
| 66 | SDL_PROC_UNUSED(int32_t, AAudioStream_getDeviceId, (AAudioStream * stream)) | ||
| 67 | SDL_PROC(aaudio_format_t, AAudioStream_getFormat, (AAudioStream * stream)) | ||
| 68 | SDL_PROC_UNUSED(aaudio_sharing_mode_t, AAudioStream_getSharingMode, (AAudioStream * stream)) | ||
| 69 | SDL_PROC_UNUSED(aaudio_performance_mode_t, AAudioStream_getPerformanceMode, (AAudioStream * stream)) | ||
| 70 | SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream * stream)) | ||
| 71 | SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream * stream)) | ||
| 72 | SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream * stream)) | ||
| 73 | SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) // API 28 | ||
| 74 | SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream * stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds)) | ||
| 75 | SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) // API 28 | ||
| 76 | SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) // API 28 | ||
| 77 | SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) // API 28 | ||
| 78 | SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) // API 29 | ||
| 79 | SDL_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 | |||
| 50 | static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int); | ||
| 51 | static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm); | ||
| 52 | static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm); | ||
| 53 | static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t); | ||
| 54 | static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t); | ||
| 55 | static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int); | ||
| 56 | static int (*ALSA_snd_pcm_prepare)(snd_pcm_t *); | ||
| 57 | static int (*ALSA_snd_pcm_drain)(snd_pcm_t *); | ||
| 58 | static const char *(*ALSA_snd_strerror)(int); | ||
| 59 | static size_t (*ALSA_snd_pcm_hw_params_sizeof)(void); | ||
| 60 | static size_t (*ALSA_snd_pcm_sw_params_sizeof)(void); | ||
| 61 | static void (*ALSA_snd_pcm_hw_params_copy)(snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *); | ||
| 62 | static int (*ALSA_snd_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *); | ||
| 63 | static int (*ALSA_snd_pcm_hw_params_set_access)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t); | ||
| 64 | static int (*ALSA_snd_pcm_hw_params_set_format)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t); | ||
| 65 | static int (*ALSA_snd_pcm_hw_params_set_channels)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int); | ||
| 66 | static int (*ALSA_snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *, unsigned int *); | ||
| 67 | static int (*ALSA_snd_pcm_hw_params_set_rate_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *); | ||
| 68 | static int (*ALSA_snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *); | ||
| 69 | static int (*ALSA_snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *); | ||
| 70 | static int (*ALSA_snd_pcm_hw_params_set_periods_min)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *); | ||
| 71 | static int (*ALSA_snd_pcm_hw_params_set_periods_first)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *); | ||
| 72 | static int (*ALSA_snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *, unsigned int *, int *); | ||
| 73 | static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *); | ||
| 74 | static int (*ALSA_snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *); | ||
| 75 | static int (*ALSA_snd_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *); | ||
| 76 | static int (*ALSA_snd_pcm_sw_params_current)(snd_pcm_t *, | ||
| 77 | snd_pcm_sw_params_t *); | ||
| 78 | static int (*ALSA_snd_pcm_sw_params_set_start_threshold)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t); | ||
| 79 | static int (*ALSA_snd_pcm_sw_params)(snd_pcm_t *, snd_pcm_sw_params_t *); | ||
| 80 | static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int); | ||
| 81 | static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int); | ||
| 82 | static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t); | ||
| 83 | static int (*ALSA_snd_pcm_reset)(snd_pcm_t *); | ||
| 84 | static int (*ALSA_snd_device_name_hint)(int, const char *, void ***); | ||
| 85 | static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *); | ||
| 86 | static int (*ALSA_snd_device_name_free_hint)(void **); | ||
| 87 | static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *); | ||
| 88 | static size_t (*ALSA_snd_ctl_card_info_sizeof)(void); | ||
| 89 | static size_t (*ALSA_snd_pcm_info_sizeof)(void); | ||
| 90 | static int (*ALSA_snd_card_next)(int*); | ||
| 91 | static int (*ALSA_snd_ctl_open)(snd_ctl_t **,const char *,int); | ||
| 92 | static int (*ALSA_snd_ctl_close)(snd_ctl_t *); | ||
| 93 | static int (*ALSA_snd_ctl_card_info)(snd_ctl_t *, snd_ctl_card_info_t *); | ||
| 94 | static int (*ALSA_snd_ctl_pcm_next_device)(snd_ctl_t *, int *); | ||
| 95 | static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *); | ||
| 96 | static void (*ALSA_snd_pcm_info_set_device)(snd_pcm_info_t *, unsigned int); | ||
| 97 | static void (*ALSA_snd_pcm_info_set_subdevice)(snd_pcm_info_t *, unsigned int); | ||
| 98 | static void (*ALSA_snd_pcm_info_set_stream)(snd_pcm_info_t *, snd_pcm_stream_t); | ||
| 99 | static int (*ALSA_snd_ctl_pcm_info)(snd_ctl_t *, snd_pcm_info_t *); | ||
| 100 | static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *); | ||
| 101 | static const char *(*ALSA_snd_ctl_card_info_get_id)(const snd_ctl_card_info_t *); | ||
| 102 | static const char *(*ALSA_snd_pcm_info_get_name)(const snd_pcm_info_t *); | ||
| 103 | static const char *(*ALSA_snd_pcm_info_get_subdevice_name)(const snd_pcm_info_t *); | ||
| 104 | static const char *(*ALSA_snd_ctl_card_info_get_name)(const snd_ctl_card_info_t *); | ||
| 105 | static void (*ALSA_snd_ctl_card_info_clear)(snd_ctl_card_info_t *); | ||
| 106 | static int (*ALSA_snd_pcm_hw_free)(snd_pcm_t *); | ||
| 107 | static int (*ALSA_snd_pcm_hw_params_set_channels_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *); | ||
| 108 | static snd_pcm_chmap_query_t **(*ALSA_snd_pcm_query_chmaps)(snd_pcm_t *pcm); | ||
| 109 | static void (*ALSA_snd_pcm_free_chmaps)(snd_pcm_chmap_query_t **maps); | ||
| 110 | static int (*ALSA_snd_pcm_set_chmap)(snd_pcm_t *, const snd_pcm_chmap_t *); | ||
| 111 | static 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 | |||
| 117 | static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC; | ||
| 118 | static SDL_SharedObject *alsa_handle = NULL; | ||
| 119 | |||
| 120 | static 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 | |||
| 139 | static 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 | |||
| 210 | static void UnloadALSALibrary(void) | ||
| 211 | { | ||
| 212 | if (alsa_handle) { | ||
| 213 | SDL_UnloadObject(alsa_handle); | ||
| 214 | alsa_handle = NULL; | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | static 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 | |||
| 238 | static void UnloadALSALibrary(void) | ||
| 239 | { | ||
| 240 | } | ||
| 241 | |||
| 242 | static bool LoadALSALibrary(void) | ||
| 243 | { | ||
| 244 | load_alsa_syms(); | ||
| 245 | return true; | ||
| 246 | } | ||
| 247 | |||
| 248 | #endif // SDL_AUDIO_DRIVER_ALSA_DYNAMIC | ||
| 249 | |||
| 250 | static const char *ALSA_device_prefix = NULL; | ||
| 251 | static 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 | |||
| 295 | typedef 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 | |||
| 304 | static const ALSA_Device default_playback_handle = { | ||
| 305 | "", | ||
| 306 | "default", | ||
| 307 | false, | ||
| 308 | NULL | ||
| 309 | }; | ||
| 310 | |||
| 311 | static 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. | ||
| 324 | static 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 | ||
| 349 | static 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 | |||
| 376 | static 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 | |||
| 405 | static 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 | |||
| 428 | static 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 | |||
| 455 | static void ALSA_FlushRecording(SDL_AudioDevice *device) | ||
| 456 | { | ||
| 457 | ALSA_snd_pcm_reset(device->hidden->pcm); | ||
| 458 | } | ||
| 459 | |||
| 460 | static 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: | ||
| 475 | struct 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". | ||
| 497 | static 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. | ||
| 569 | static 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 | ||
| 589 | static 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 | |||
| 641 | static 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. | ||
| 655 | static 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. | ||
| 671 | static 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. | ||
| 701 | static 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 | |||
| 721 | static 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. | ||
| 759 | static 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 | |||
| 802 | static 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). | ||
| 810 | static 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 | |||
| 855 | static 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 | ||
| 910 | static 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 | |||
| 1051 | static 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 | |||
| 1079 | static 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 | |||
| 1105 | static 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 | |||
| 1193 | err_cleanup_ctx: | ||
| 1194 | ALSA_snd_pcm_free_chmaps(cfg_ctx.chmap_queries); | ||
| 1195 | err_close_pcm: | ||
| 1196 | ALSA_snd_pcm_close(cfg_ctx.device->hidden->pcm); | ||
| 1197 | err_free_device_hidden: | ||
| 1198 | SDL_free(cfg_ctx.device->hidden); | ||
| 1199 | cfg_ctx.device->hidden = NULL; | ||
| 1200 | return false; | ||
| 1201 | } | ||
| 1202 | |||
| 1203 | static ALSA_Device *hotplug_devices = NULL; | ||
| 1204 | |||
| 1205 | static 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 | |||
| 1308 | static 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 | |||
| 1390 | failed: | ||
| 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 | ||
| 1421 | static SDL_AtomicInt ALSA_hotplug_shutdown; | ||
| 1422 | static SDL_Thread *ALSA_hotplug_thread; | ||
| 1423 | |||
| 1424 | static 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 | |||
| 1442 | static 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 | |||
| 1464 | static 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 | |||
| 1487 | static void ALSA_Deinitialize(void) | ||
| 1488 | { | ||
| 1489 | UnloadALSALibrary(); | ||
| 1490 | } | ||
| 1491 | |||
| 1492 | static 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 | |||
| 1515 | AudioBootStrap 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 | ||
| 32 | struct 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 | |||
| 50 | struct 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. | ||
| 48 | typedef struct SDLCoreAudioHandle | ||
| 49 | { | ||
| 50 | AudioDeviceID devid; | ||
| 51 | bool recording; | ||
| 52 | } SDLCoreAudioHandle; | ||
| 53 | |||
| 54 | static 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 | |||
| 61 | static SDL_AudioDevice *FindCoreAudioDeviceByHandle(const AudioDeviceID devid, const bool recording) | ||
| 62 | { | ||
| 63 | SDLCoreAudioHandle handle = { devid, recording }; | ||
| 64 | return SDL_FindPhysicalAudioDeviceByCallback(TestCoreAudioDeviceHandleCallback, &handle); | ||
| 65 | } | ||
| 66 | |||
| 67 | static const AudioObjectPropertyAddress devlist_address = { | ||
| 68 | kAudioHardwarePropertyDevices, | ||
| 69 | kAudioObjectPropertyScopeGlobal, | ||
| 70 | kAudioObjectPropertyElementMain | ||
| 71 | }; | ||
| 72 | |||
| 73 | static const AudioObjectPropertyAddress default_playback_device_address = { | ||
| 74 | kAudioHardwarePropertyDefaultOutputDevice, | ||
| 75 | kAudioObjectPropertyScopeGlobal, | ||
| 76 | kAudioObjectPropertyElementMain | ||
| 77 | }; | ||
| 78 | |||
| 79 | static const AudioObjectPropertyAddress default_recording_device_address = { | ||
| 80 | kAudioHardwarePropertyDefaultInputDevice, | ||
| 81 | kAudioObjectPropertyScopeGlobal, | ||
| 82 | kAudioObjectPropertyElementMain | ||
| 83 | }; | ||
| 84 | |||
| 85 | static const AudioObjectPropertyAddress alive_address = { | ||
| 86 | kAudioDevicePropertyDeviceIsAlive, | ||
| 87 | kAudioObjectPropertyScopeGlobal, | ||
| 88 | kAudioObjectPropertyElementMain | ||
| 89 | }; | ||
| 90 | |||
| 91 | |||
| 92 | static 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 | |||
| 118 | static 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. | ||
| 126 | static 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. | ||
| 249 | static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) | ||
| 250 | { | ||
| 251 | RefreshPhysicalDevices(); | ||
| 252 | return noErr; | ||
| 253 | } | ||
| 254 | |||
| 255 | static 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 | |||
| 265 | static 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 | |||
| 274 | static 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 | |||
| 283 | static 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 | |||
| 314 | static bool session_active = false; | ||
| 315 | |||
| 316 | static 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 | |||
| 324 | static void PauseAudioDevices(void) | ||
| 325 | { | ||
| 326 | (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneAudioDevice, NULL); | ||
| 327 | } | ||
| 328 | |||
| 329 | static 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 | |||
| 337 | static void ResumeAudioDevices(void) | ||
| 338 | { | ||
| 339 | (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneAudioDevice, NULL); | ||
| 340 | } | ||
| 341 | |||
| 342 | static 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 | |||
| 350 | static 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 | |||
| 386 | typedef struct | ||
| 387 | { | ||
| 388 | int playback; | ||
| 389 | int recording; | ||
| 390 | } CountOpenAudioDevicesData; | ||
| 391 | |||
| 392 | static 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 | |||
| 405 | static 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 | |||
| 544 | static 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 | |||
| 555 | static 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 | |||
| 564 | static 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 | |||
| 582 | static 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 | |||
| 595 | static 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 | |||
| 605 | static 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 | |||
| 623 | static 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 | ||
| 656 | static 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 | |||
| 695 | static 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 | |||
| 715 | static 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 | |||
| 856 | static 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 | |||
| 892 | static 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 | |||
| 1003 | static 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 | |||
| 1012 | static 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 | |||
| 1036 | AudioBootStrap 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 | ||
| 38 | static bool SupportsIMMDevice = false; | ||
| 39 | #endif | ||
| 40 | |||
| 41 | // DirectX function pointers for audio | ||
| 42 | static SDL_SharedObject *DSoundDLL = NULL; | ||
| 43 | typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); | ||
| 44 | typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); | ||
| 45 | typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN); | ||
| 46 | typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); | ||
| 47 | typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID); | ||
| 48 | static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL; | ||
| 49 | static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL; | ||
| 50 | static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL; | ||
| 51 | static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL; | ||
| 52 | static fnGetDeviceID pGetDeviceID = NULL; | ||
| 53 | |||
| 54 | #include <initguid.h> | ||
| 55 | DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03); | ||
| 56 | DEFINE_GUID(SDL_DSDEVID_DefaultCapture, 0xdef00001, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03); | ||
| 57 | |||
| 58 | static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | ||
| 59 | static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; | ||
| 60 | |||
| 61 | static 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 | |||
| 75 | static 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 | |||
| 112 | static 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 | |||
| 158 | static 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. | ||
| 171 | typedef struct FindAllDevsData | ||
| 172 | { | ||
| 173 | bool recording; | ||
| 174 | SDL_AudioDevice **default_device; | ||
| 175 | LPCGUID default_device_guid; | ||
| 176 | } FindAllDevsData; | ||
| 177 | |||
| 178 | static 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 | |||
| 205 | static 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 | |||
| 232 | static 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 | |||
| 267 | static 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 | |||
| 277 | static 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 | |||
| 336 | static 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 | |||
| 352 | static 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 | |||
| 379 | static 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 | |||
| 388 | static 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 | */ | ||
| 414 | static 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 | */ | ||
| 455 | static 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 | |||
| 494 | static 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 | |||
| 631 | static void DSOUND_DeinitializeStart(void) | ||
| 632 | { | ||
| 633 | #ifdef HAVE_MMDEVICEAPI_H | ||
| 634 | if (SupportsIMMDevice) { | ||
| 635 | SDL_IMMDevice_Quit(); | ||
| 636 | } | ||
| 637 | #endif | ||
| 638 | } | ||
| 639 | |||
| 640 | static void DSOUND_Deinitialize(void) | ||
| 641 | { | ||
| 642 | DSOUND_Unload(); | ||
| 643 | #ifdef HAVE_MMDEVICEAPI_H | ||
| 644 | SupportsIMMDevice = false; | ||
| 645 | #endif | ||
| 646 | } | ||
| 647 | |||
| 648 | static 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 | |||
| 676 | AudioBootStrap 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 | ||
| 31 | struct 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 | |||
| 33 | static bool DISKAUDIO_WaitDevice(SDL_AudioDevice *device) | ||
| 34 | { | ||
| 35 | SDL_Delay(device->hidden->io_delay); | ||
| 36 | return true; | ||
| 37 | } | ||
| 38 | |||
| 39 | static 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 | |||
| 51 | static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 52 | { | ||
| 53 | return device->hidden->mixbuf; | ||
| 54 | } | ||
| 55 | |||
| 56 | static 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 | |||
| 77 | static void DISKAUDIO_FlushRecording(SDL_AudioDevice *device) | ||
| 78 | { | ||
| 79 | // no op...we don't advance the file pointer or anything. | ||
| 80 | } | ||
| 81 | |||
| 82 | static 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 | |||
| 94 | static 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 | |||
| 103 | static 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 | |||
| 144 | static 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 | |||
| 150 | static 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 | |||
| 167 | AudioBootStrap 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 | |||
| 28 | struct 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 | |||
| 42 | static void DSP_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) | ||
| 43 | { | ||
| 44 | SDL_EnumUnixAudioDevices(false, NULL); | ||
| 45 | } | ||
| 46 | |||
| 47 | static 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 | |||
| 58 | static 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 | |||
| 202 | static 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 | |||
| 227 | static 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 | |||
| 240 | static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 241 | { | ||
| 242 | return device->hidden->mixbuf; | ||
| 243 | } | ||
| 244 | |||
| 245 | static int DSP_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) | ||
| 246 | { | ||
| 247 | return (int)read(device->hidden->audio_fd, buffer, buflen); | ||
| 248 | } | ||
| 249 | |||
| 250 | static 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 | |||
| 267 | static bool InitTimeDevicesExist = false; | ||
| 268 | static 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 | |||
| 275 | static 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 | |||
| 299 | AudioBootStrap 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 | |||
| 28 | struct 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 | |||
| 32 | static bool DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device) | ||
| 33 | { | ||
| 34 | SDL_Delay(device->hidden->io_delay); | ||
| 35 | return true; | ||
| 36 | } | ||
| 37 | |||
| 38 | static 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 | |||
| 74 | static 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 | |||
| 91 | static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 92 | { | ||
| 93 | return device->hidden->mixbuf; | ||
| 94 | } | ||
| 95 | |||
| 96 | static 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 | |||
| 103 | static 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 | |||
| 133 | AudioBootStrap 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 | |||
| 28 | struct 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 | |||
| 34 | static Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 35 | { | ||
| 36 | return device->hidden->mixbuf; | ||
| 37 | } | ||
| 38 | |||
| 39 | static 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 | |||
| 67 | static void EMSCRIPTENAUDIO_FlushRecording(SDL_AudioDevice *device) | ||
| 68 | { | ||
| 69 | // Do nothing, the new data will just be dropped. | ||
| 70 | } | ||
| 71 | |||
| 72 | static 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 | |||
| 98 | static 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 | |||
| 146 | EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall"); | ||
| 147 | |||
| 148 | static 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 | |||
| 308 | static 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 | |||
| 353 | AudioBootStrap 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 | |||
| 28 | struct 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 | |||
| 32 | extern "C" | ||
| 33 | { | ||
| 34 | |||
| 35 | #include "../SDL_sysaudio.h" | ||
| 36 | #include "SDL_haikuaudio.h" | ||
| 37 | |||
| 38 | } | ||
| 39 | |||
| 40 | static 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 | |||
| 48 | static 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 | ||
| 59 | static 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 | |||
| 69 | static 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 | |||
| 83 | static const int sig_list[] = { | ||
| 84 | SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGWINCH, 0 | ||
| 85 | }; | ||
| 86 | |||
| 87 | static 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 | |||
| 99 | static inline void UnmaskSignals(sigset_t * omask) | ||
| 100 | { | ||
| 101 | sigprocmask(SIG_SETMASK, omask, NULL); | ||
| 102 | } | ||
| 103 | |||
| 104 | |||
| 105 | static 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 | |||
| 192 | static void HAIKUAUDIO_Deinitialize(void) | ||
| 193 | { | ||
| 194 | SDL_QuitBeApp(); | ||
| 195 | } | ||
| 196 | |||
| 197 | static 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 | |||
| 216 | extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; } | ||
| 217 | |||
| 218 | AudioBootStrap 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 | |||
| 28 | struct 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 | |||
| 29 | static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...); | ||
| 30 | static int (*JACK_jack_client_close)(jack_client_t *); | ||
| 31 | static void (*JACK_jack_on_shutdown)(jack_client_t *, JackShutdownCallback, void *); | ||
| 32 | static int (*JACK_jack_activate)(jack_client_t *); | ||
| 33 | static int (*JACK_jack_deactivate)(jack_client_t *); | ||
| 34 | static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t); | ||
| 35 | static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *); | ||
| 36 | static void (*JACK_jack_free)(void *); | ||
| 37 | static const char **(*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long); | ||
| 38 | static jack_nframes_t (*JACK_jack_get_sample_rate)(jack_client_t *); | ||
| 39 | static jack_nframes_t (*JACK_jack_get_buffer_size)(jack_client_t *); | ||
| 40 | static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long); | ||
| 41 | static jack_port_t *(*JACK_jack_port_by_name)(jack_client_t *, const char *); | ||
| 42 | static const char *(*JACK_jack_port_name)(const jack_port_t *); | ||
| 43 | static const char *(*JACK_jack_port_type)(const jack_port_t *); | ||
| 44 | static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *); | ||
| 45 | static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *); | ||
| 46 | static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *); | ||
| 47 | static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *); | ||
| 48 | |||
| 49 | static bool load_jack_syms(void); | ||
| 50 | |||
| 51 | #ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC | ||
| 52 | |||
| 53 | static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC; | ||
| 54 | static SDL_SharedObject *jack_handle = NULL; | ||
| 55 | |||
| 56 | // !!! FIXME: this is copy/pasted in several places now | ||
| 57 | static 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 | |||
| 73 | static void UnloadJackLibrary(void) | ||
| 74 | { | ||
| 75 | if (jack_handle) { | ||
| 76 | SDL_UnloadObject(jack_handle); | ||
| 77 | jack_handle = NULL; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | static 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 | |||
| 103 | static void UnloadJackLibrary(void) | ||
| 104 | { | ||
| 105 | } | ||
| 106 | |||
| 107 | static bool LoadJackLibrary(void) | ||
| 108 | { | ||
| 109 | load_jack_syms(); | ||
| 110 | return true; | ||
| 111 | } | ||
| 112 | |||
| 113 | #endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC | ||
| 114 | |||
| 115 | static 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 | |||
| 140 | static void jackShutdownCallback(void *arg) // JACK went away; device is lost. | ||
| 141 | { | ||
| 142 | SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg); | ||
| 143 | } | ||
| 144 | |||
| 145 | static 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 | |||
| 158 | static 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 | |||
| 170 | static 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 | |||
| 177 | static 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 | |||
| 199 | static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 200 | { | ||
| 201 | return (Uint8 *)device->hidden->iobuffer; | ||
| 202 | } | ||
| 203 | |||
| 204 | static 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 | |||
| 211 | static 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 | |||
| 233 | static void JACK_FlushRecording(SDL_AudioDevice *device) | ||
| 234 | { | ||
| 235 | // do nothing, the data will just be replaced next callback. | ||
| 236 | } | ||
| 237 | |||
| 238 | static 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) | ||
| 265 | static const char *GetJackAppName(void) | ||
| 266 | { | ||
| 267 | return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING); | ||
| 268 | } | ||
| 269 | |||
| 270 | static 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 | |||
| 396 | static void JACK_Deinitialize(void) | ||
| 397 | { | ||
| 398 | UnloadJackLibrary(); | ||
| 399 | } | ||
| 400 | |||
| 401 | static 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 | |||
| 431 | AudioBootStrap 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 | |||
| 28 | struct 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 | |||
| 32 | static dspHookCookie dsp_hook; | ||
| 33 | static SDL_AudioDevice *audio_device; | ||
| 34 | |||
| 35 | // fully local functions related to the wavebufs / DSP, not the same as the `device->lock` SDL_Mutex! | ||
| 36 | static SDL_INLINE void contextLock(SDL_AudioDevice *device) | ||
| 37 | { | ||
| 38 | LightLock_Lock(&device->hidden->lock); | ||
| 39 | } | ||
| 40 | |||
| 41 | static SDL_INLINE void contextUnlock(SDL_AudioDevice *device) | ||
| 42 | { | ||
| 43 | LightLock_Unlock(&device->hidden->lock); | ||
| 44 | } | ||
| 45 | |||
| 46 | static 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 | |||
| 57 | static 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 | |||
| 79 | static 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 | |||
| 179 | static 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 | |||
| 203 | static 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 | |||
| 214 | static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 215 | { | ||
| 216 | return device->hidden->mixbuf; | ||
| 217 | } | ||
| 218 | |||
| 219 | static 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 | |||
| 253 | static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *device) | ||
| 254 | { | ||
| 255 | s32 current_priority = 0x30; | ||
| 256 | svcGetThreadPriority(¤t_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 | |||
| 263 | static 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 | |||
| 279 | AudioBootStrap 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 | |||
| 29 | struct 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 | |||
| 42 | static void NETBSDAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) | ||
| 43 | { | ||
| 44 | SDL_EnumUnixAudioDevices(false, NULL); | ||
| 45 | } | ||
| 46 | |||
| 47 | static 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 | |||
| 117 | static 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 | |||
| 144 | static 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 | |||
| 158 | static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 159 | { | ||
| 160 | return device->hidden->mixbuf; | ||
| 161 | } | ||
| 162 | |||
| 163 | static 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 | |||
| 179 | static 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 | |||
| 197 | static 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 | |||
| 209 | static 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 | |||
| 307 | static 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 | |||
| 324 | AudioBootStrap 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 | |||
| 28 | struct 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 | |||
| 39 | struct 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 | ||
| 85 | static SLObjectItf engineObject = NULL; | ||
| 86 | static SLEngineItf engineEngine = NULL; | ||
| 87 | |||
| 88 | // output mix interfaces | ||
| 89 | static SLObjectItf outputMixObject = NULL; | ||
| 90 | |||
| 91 | // buffer queue player interfaces | ||
| 92 | static SLObjectItf bqPlayerObject = NULL; | ||
| 93 | static SLPlayItf bqPlayerPlay = NULL; | ||
| 94 | static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL; | ||
| 95 | #if 0 | ||
| 96 | static SLVolumeItf bqPlayerVolume; | ||
| 97 | #endif | ||
| 98 | |||
| 99 | // recorder interfaces | ||
| 100 | static SLObjectItf recorderObject = NULL; | ||
| 101 | static SLRecordItf recorderRecord = NULL; | ||
| 102 | static SLAndroidSimpleBufferQueueItf recorderBufferQueue = NULL; | ||
| 103 | |||
| 104 | #if 0 | ||
| 105 | static const char *sldevaudiorecorderstr = "SLES Audio Recorder"; | ||
| 106 | static const char *sldevaudioplayerstr = "SLES Audio Player"; | ||
| 107 | |||
| 108 | #define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr | ||
| 109 | #define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr | ||
| 110 | static 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 | |||
| 120 | static 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 | |||
| 138 | static 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 | |||
| 186 | error: | ||
| 187 | OPENSLES_DestroyEngine(); | ||
| 188 | return false; | ||
| 189 | } | ||
| 190 | |||
| 191 | // this callback handler is called every time a buffer finishes recording | ||
| 192 | static 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 | |||
| 200 | static 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! | ||
| 232 | static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted) | ||
| 233 | { | ||
| 234 | SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1); | ||
| 235 | } | ||
| 236 | |||
| 237 | static 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 | |||
| 383 | failed: | ||
| 384 | return SDL_SetError("Open device failed!"); | ||
| 385 | } | ||
| 386 | |||
| 387 | // this callback handler is called every time a buffer finishes playing | ||
| 388 | static 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 | |||
| 396 | static 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 | |||
| 427 | static 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 | |||
| 614 | failed: | ||
| 615 | return false; | ||
| 616 | } | ||
| 617 | |||
| 618 | static 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 | |||
| 649 | static 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 | |||
| 665 | static 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 | |||
| 700 | static 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 | |||
| 708 | static 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 | |||
| 731 | static 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 | |||
| 748 | static 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 | |||
| 781 | AudioBootStrap OPENSLES_bootstrap = { | ||
| 782 | "openslES", "OpenSL ES audio driver", OPENSLES_Init, false, false | ||
| 783 | }; | ||
| 784 | |||
| 785 | void 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 | |||
| 796 | void 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 | |||
| 28 | extern void OPENSLES_ResumeDevices(void); | ||
| 29 | extern 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 | |||
| 43 | enum 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 | |||
| 55 | static bool pipewire_initialized = false; | ||
| 56 | |||
| 57 | // Pipewire entry points | ||
| 58 | static const char *(*PIPEWIRE_pw_get_library_version)(void); | ||
| 59 | static void (*PIPEWIRE_pw_init)(int *, char ***); | ||
| 60 | static void (*PIPEWIRE_pw_deinit)(void); | ||
| 61 | static struct pw_main_loop *(*PIPEWIRE_pw_main_loop_new)(const struct spa_dict *loop); | ||
| 62 | static struct pw_loop *(*PIPEWIRE_pw_main_loop_get_loop)(struct pw_main_loop *loop); | ||
| 63 | static int (*PIPEWIRE_pw_main_loop_run)(struct pw_main_loop *loop); | ||
| 64 | static int (*PIPEWIRE_pw_main_loop_quit)(struct pw_main_loop *loop); | ||
| 65 | static void(*PIPEWIRE_pw_main_loop_destroy)(struct pw_main_loop *loop); | ||
| 66 | static struct pw_thread_loop *(*PIPEWIRE_pw_thread_loop_new)(const char *, const struct spa_dict *); | ||
| 67 | static void (*PIPEWIRE_pw_thread_loop_destroy)(struct pw_thread_loop *); | ||
| 68 | static void (*PIPEWIRE_pw_thread_loop_stop)(struct pw_thread_loop *); | ||
| 69 | static struct pw_loop *(*PIPEWIRE_pw_thread_loop_get_loop)(struct pw_thread_loop *); | ||
| 70 | static void (*PIPEWIRE_pw_thread_loop_lock)(struct pw_thread_loop *); | ||
| 71 | static void (*PIPEWIRE_pw_thread_loop_unlock)(struct pw_thread_loop *); | ||
| 72 | static void (*PIPEWIRE_pw_thread_loop_signal)(struct pw_thread_loop *, bool); | ||
| 73 | static void (*PIPEWIRE_pw_thread_loop_wait)(struct pw_thread_loop *); | ||
| 74 | static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *); | ||
| 75 | static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t); | ||
| 76 | static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *); | ||
| 77 | static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t); | ||
| 78 | static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *); | ||
| 79 | static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *); | ||
| 80 | static void (*PIPEWIRE_pw_proxy_destroy)(struct pw_proxy *); | ||
| 81 | static int (*PIPEWIRE_pw_core_disconnect)(struct pw_core *); | ||
| 82 | static struct pw_stream *(*PIPEWIRE_pw_stream_new_simple)(struct pw_loop *, const char *, struct pw_properties *, | ||
| 83 | const struct pw_stream_events *, void *); | ||
| 84 | static void (*PIPEWIRE_pw_stream_destroy)(struct pw_stream *); | ||
| 85 | static int (*PIPEWIRE_pw_stream_connect)(struct pw_stream *, enum pw_direction, uint32_t, enum pw_stream_flags, | ||
| 86 | const struct spa_pod **, uint32_t); | ||
| 87 | static enum pw_stream_state (*PIPEWIRE_pw_stream_get_state)(struct pw_stream *stream, const char **error); | ||
| 88 | static struct pw_buffer *(*PIPEWIRE_pw_stream_dequeue_buffer)(struct pw_stream *); | ||
| 89 | static int (*PIPEWIRE_pw_stream_queue_buffer)(struct pw_stream *, struct pw_buffer *); | ||
| 90 | static struct pw_properties *(*PIPEWIRE_pw_properties_new)(const char *, ...)SPA_SENTINEL; | ||
| 91 | static int (*PIPEWIRE_pw_properties_set)(struct pw_properties *, const char *, const char *); | ||
| 92 | static 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 | |||
| 96 | static const char *pipewire_library = SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC; | ||
| 97 | static SDL_SharedObject *pipewire_handle = NULL; | ||
| 98 | |||
| 99 | static 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 | |||
| 114 | static bool load_pipewire_library(void) | ||
| 115 | { | ||
| 116 | pipewire_handle = SDL_LoadObject(pipewire_library); | ||
| 117 | return pipewire_handle ? true : false; | ||
| 118 | } | ||
| 119 | |||
| 120 | static 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 | |||
| 132 | static bool load_pipewire_library(void) | ||
| 133 | { | ||
| 134 | return true; | ||
| 135 | } | ||
| 136 | |||
| 137 | static void unload_pipewire_library(void) | ||
| 138 | { | ||
| 139 | // Nothing to do | ||
| 140 | } | ||
| 141 | |||
| 142 | #endif // SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC | ||
| 143 | |||
| 144 | static 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 | |||
| 183 | static 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 | |||
| 195 | static 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. | ||
| 202 | struct 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. | ||
| 225 | struct 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. | ||
| 240 | static struct pw_thread_loop *hotplug_loop; | ||
| 241 | static struct pw_core *hotplug_core; | ||
| 242 | static struct pw_context *hotplug_context; | ||
| 243 | static struct pw_registry *hotplug_registry; | ||
| 244 | static struct spa_hook hotplug_registry_listener; | ||
| 245 | static struct spa_hook hotplug_core_listener; | ||
| 246 | static struct spa_list hotplug_pending_list; | ||
| 247 | static struct spa_list hotplug_io_list; | ||
| 248 | static int hotplug_init_seq_val; | ||
| 249 | static bool hotplug_init_complete; | ||
| 250 | static bool hotplug_events_enabled; | ||
| 251 | |||
| 252 | static int pipewire_version_major; | ||
| 253 | static int pipewire_version_minor; | ||
| 254 | static int pipewire_version_patch; | ||
| 255 | static char *pipewire_default_sink_id = NULL; | ||
| 256 | static char *pipewire_default_source_id = NULL; | ||
| 257 | |||
| 258 | static 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 | ||
| 266 | static 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 | |||
| 286 | dup_found: | ||
| 287 | |||
| 288 | return ret; | ||
| 289 | } | ||
| 290 | |||
| 291 | static 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 | |||
| 311 | static 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 | |||
| 321 | static 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 | |||
| 332 | static 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 | ||
| 344 | static 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 | |||
| 350 | static 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 | |||
| 361 | static 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 | |||
| 370 | static 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 | ||
| 399 | static 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 | |||
| 411 | static 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 | |||
| 420 | static 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 | |||
| 438 | static 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 | |||
| 447 | static 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 }; | ||
| 448 | static const struct pw_core_events interface_core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_interface_callback }; | ||
| 449 | static const struct pw_core_events metadata_core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_metadata_callback }; | ||
| 450 | |||
| 451 | static 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 | ||
| 467 | static 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 | |||
| 500 | static 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 | |||
| 518 | static 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 | ||
| 538 | static 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 | |||
| 560 | static 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 | |||
| 591 | static const struct pw_node_events interface_node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info, | ||
| 592 | .param = node_event_param }; | ||
| 593 | |||
| 594 | static 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 | |||
| 615 | static 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 | ||
| 629 | static 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 | |||
| 654 | static const struct pw_metadata_events metadata_node_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property }; | ||
| 655 | |||
| 656 | // Global registry callbacks | ||
| 657 | static 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 | |||
| 729 | static void registry_event_remove_callback(void *object, uint32_t id) | ||
| 730 | { | ||
| 731 | io_list_remove(id); | ||
| 732 | pending_list_remove(id); | ||
| 733 | } | ||
| 734 | |||
| 735 | static 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 | ||
| 739 | static 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, ®istry_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 | |||
| 782 | static 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 | |||
| 824 | static 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 | ||
| 854 | static const enum spa_audio_channel PIPEWIRE_channel_map_1[] = { SPA_AUDIO_CHANNEL_MONO }; | ||
| 855 | static const enum spa_audio_channel PIPEWIRE_channel_map_2[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }; | ||
| 856 | static const enum spa_audio_channel PIPEWIRE_channel_map_3[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE }; | ||
| 857 | static 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 }; | ||
| 859 | static 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 }; | ||
| 861 | static 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 }; | ||
| 863 | static 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 }; | ||
| 866 | static 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 | |||
| 872 | static 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 | |||
| 936 | static 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 | |||
| 957 | static 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 | |||
| 972 | static 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 | |||
| 991 | static 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 | |||
| 1000 | static 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 | |||
| 1027 | static 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 | |||
| 1041 | static 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 | |||
| 1060 | static 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 | |||
| 1073 | static 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 }; | ||
| 1077 | static 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 | |||
| 1082 | static 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 | ¶ms, 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 | |||
| 1236 | static 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 | |||
| 1264 | static void PIPEWIRE_DeinitializeStart(void) | ||
| 1265 | { | ||
| 1266 | if (pipewire_initialized) { | ||
| 1267 | hotplug_loop_destroy(); | ||
| 1268 | } | ||
| 1269 | } | ||
| 1270 | |||
| 1271 | static void PIPEWIRE_Deinitialize(void) | ||
| 1272 | { | ||
| 1273 | if (pipewire_initialized) { | ||
| 1274 | hotplug_loop_destroy(); | ||
| 1275 | deinit_pipewire_library(); | ||
| 1276 | pipewire_initialized = false; | ||
| 1277 | } | ||
| 1278 | } | ||
| 1279 | |||
| 1280 | static 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 | |||
| 1311 | static 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 | |||
| 1337 | static bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) | ||
| 1338 | { | ||
| 1339 | return PipewireInitialize(impl); | ||
| 1340 | } | ||
| 1341 | |||
| 1342 | AudioBootStrap PIPEWIRE_PREFERRED_bootstrap = { | ||
| 1343 | "pipewire", "Pipewire", PIPEWIRE_PREFERRED_Init, false, true | ||
| 1344 | }; | ||
| 1345 | AudioBootStrap 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 | |||
| 30 | struct 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 | |||
| 30 | static 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 | |||
| 88 | static 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 | |||
| 94 | static bool PS2AUDIO_WaitDevice(SDL_AudioDevice *device) | ||
| 95 | { | ||
| 96 | audsrv_wait_audio(device->buffer_size); | ||
| 97 | return true; | ||
| 98 | } | ||
| 99 | |||
| 100 | static 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 | |||
| 107 | static 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 | |||
| 124 | static 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 | |||
| 135 | static void PS2AUDIO_Deinitialize(void) | ||
| 136 | { | ||
| 137 | deinit_audio_driver(); | ||
| 138 | } | ||
| 139 | |||
| 140 | static 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 | |||
| 157 | AudioBootStrap 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 | |||
| 30 | struct 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 | |||
| 36 | static bool isBasicAudioConfig(const SDL_AudioSpec *spec) | ||
| 37 | { | ||
| 38 | return spec->freq == 44100; | ||
| 39 | } | ||
| 40 | |||
| 41 | static 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 | |||
| 108 | static 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 | |||
| 120 | static 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 | |||
| 125 | static 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 | |||
| 132 | static 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 | |||
| 153 | static 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 | |||
| 165 | static 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 | |||
| 179 | AudioBootStrap 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 | |||
| 29 | struct 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) | ||
| 39 | typedef void (*pa_operation_notify_cb_t) (pa_operation *o, void *userdata); | ||
| 40 | #endif | ||
| 41 | |||
| 42 | typedef 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 | ||
| 49 | static bool include_monitors = false; | ||
| 50 | |||
| 51 | static pa_threaded_mainloop *pulseaudio_threaded_mainloop = NULL; | ||
| 52 | static pa_context *pulseaudio_context = NULL; | ||
| 53 | static SDL_Thread *pulseaudio_hotplug_thread = NULL; | ||
| 54 | static 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. | ||
| 59 | static char *default_sink_path = NULL; | ||
| 60 | static char *default_source_path = NULL; | ||
| 61 | static bool default_sink_changed = false; | ||
| 62 | static bool default_source_changed = false; | ||
| 63 | |||
| 64 | |||
| 65 | static const char *(*PULSEAUDIO_pa_get_library_version)(void); | ||
| 66 | static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)( | ||
| 67 | pa_channel_map *, unsigned, pa_channel_map_def_t); | ||
| 68 | static const char *(*PULSEAUDIO_pa_strerror)(int); | ||
| 69 | static pa_proplist *(*PULSEAUDIO_pa_proplist_new)(void); | ||
| 70 | static void (*PULSEAUDIO_pa_proplist_free)(pa_proplist *); | ||
| 71 | static int (*PULSEAUDIO_pa_proplist_sets)(pa_proplist *, const char *, const char *); | ||
| 72 | |||
| 73 | static pa_threaded_mainloop *(*PULSEAUDIO_pa_threaded_mainloop_new)(void); | ||
| 74 | static void (*PULSEAUDIO_pa_threaded_mainloop_set_name)(pa_threaded_mainloop *, const char *); | ||
| 75 | static pa_mainloop_api *(*PULSEAUDIO_pa_threaded_mainloop_get_api)(pa_threaded_mainloop *); | ||
| 76 | static int (*PULSEAUDIO_pa_threaded_mainloop_start)(pa_threaded_mainloop *); | ||
| 77 | static void (*PULSEAUDIO_pa_threaded_mainloop_stop)(pa_threaded_mainloop *); | ||
| 78 | static void (*PULSEAUDIO_pa_threaded_mainloop_lock)(pa_threaded_mainloop *); | ||
| 79 | static void (*PULSEAUDIO_pa_threaded_mainloop_unlock)(pa_threaded_mainloop *); | ||
| 80 | static void (*PULSEAUDIO_pa_threaded_mainloop_wait)(pa_threaded_mainloop *); | ||
| 81 | static void (*PULSEAUDIO_pa_threaded_mainloop_signal)(pa_threaded_mainloop *, int); | ||
| 82 | static void (*PULSEAUDIO_pa_threaded_mainloop_free)(pa_threaded_mainloop *); | ||
| 83 | |||
| 84 | static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state)( | ||
| 85 | const pa_operation *); | ||
| 86 | static void (*PULSEAUDIO_pa_operation_set_state_callback)(pa_operation *, pa_operation_notify_cb_t, void *); | ||
| 87 | static void (*PULSEAUDIO_pa_operation_cancel)(pa_operation *); | ||
| 88 | static void (*PULSEAUDIO_pa_operation_unref)(pa_operation *); | ||
| 89 | |||
| 90 | static pa_context *(*PULSEAUDIO_pa_context_new_with_proplist)(pa_mainloop_api *, | ||
| 91 | const char *, | ||
| 92 | const pa_proplist *); | ||
| 93 | static void (*PULSEAUDIO_pa_context_set_state_callback)(pa_context *, pa_context_notify_cb_t, void *); | ||
| 94 | static int (*PULSEAUDIO_pa_context_connect)(pa_context *, const char *, | ||
| 95 | pa_context_flags_t, const pa_spawn_api *); | ||
| 96 | static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_list)(pa_context *, pa_sink_info_cb_t, void *); | ||
| 97 | static pa_operation *(*PULSEAUDIO_pa_context_get_source_info_list)(pa_context *, pa_source_info_cb_t, void *); | ||
| 98 | static pa_operation *(*PULSEAUDIO_pa_context_get_sink_info_by_index)(pa_context *, uint32_t, pa_sink_info_cb_t, void *); | ||
| 99 | static pa_operation *(*PULSEAUDIO_pa_context_get_source_info_by_index)(pa_context *, uint32_t, pa_source_info_cb_t, void *); | ||
| 100 | static pa_context_state_t (*PULSEAUDIO_pa_context_get_state)(const pa_context *); | ||
| 101 | static pa_operation *(*PULSEAUDIO_pa_context_subscribe)(pa_context *, pa_subscription_mask_t, pa_context_success_cb_t, void *); | ||
| 102 | static void (*PULSEAUDIO_pa_context_set_subscribe_callback)(pa_context *, pa_context_subscribe_cb_t, void *); | ||
| 103 | static void (*PULSEAUDIO_pa_context_disconnect)(pa_context *); | ||
| 104 | static void (*PULSEAUDIO_pa_context_unref)(pa_context *); | ||
| 105 | |||
| 106 | static pa_stream *(*PULSEAUDIO_pa_stream_new)(pa_context *, const char *, | ||
| 107 | const pa_sample_spec *, const pa_channel_map *); | ||
| 108 | static void (*PULSEAUDIO_pa_stream_set_state_callback)(pa_stream *, pa_stream_notify_cb_t, void *); | ||
| 109 | static int (*PULSEAUDIO_pa_stream_connect_playback)(pa_stream *, const char *, | ||
| 110 | const pa_buffer_attr *, pa_stream_flags_t, const pa_cvolume *, pa_stream *); | ||
| 111 | static int (*PULSEAUDIO_pa_stream_connect_record)(pa_stream *, const char *, | ||
| 112 | const pa_buffer_attr *, pa_stream_flags_t); | ||
| 113 | static const pa_buffer_attr *(*PULSEAUDIO_pa_stream_get_buffer_attr)(pa_stream *); | ||
| 114 | static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state)(const pa_stream *); | ||
| 115 | static size_t (*PULSEAUDIO_pa_stream_writable_size)(const pa_stream *); | ||
| 116 | static size_t (*PULSEAUDIO_pa_stream_readable_size)(const pa_stream *); | ||
| 117 | static int (*PULSEAUDIO_pa_stream_write)(pa_stream *, const void *, size_t, | ||
| 118 | pa_free_cb_t, int64_t, pa_seek_mode_t); | ||
| 119 | static int (*PULSEAUDIO_pa_stream_begin_write)(pa_stream *, void **, size_t *); | ||
| 120 | static pa_operation *(*PULSEAUDIO_pa_stream_drain)(pa_stream *, | ||
| 121 | pa_stream_success_cb_t, void *); | ||
| 122 | static int (*PULSEAUDIO_pa_stream_peek)(pa_stream *, const void **, size_t *); | ||
| 123 | static int (*PULSEAUDIO_pa_stream_drop)(pa_stream *); | ||
| 124 | static pa_operation *(*PULSEAUDIO_pa_stream_flush)(pa_stream *, | ||
| 125 | pa_stream_success_cb_t, void *); | ||
| 126 | static int (*PULSEAUDIO_pa_stream_disconnect)(pa_stream *); | ||
| 127 | static void (*PULSEAUDIO_pa_stream_unref)(pa_stream *); | ||
| 128 | static void (*PULSEAUDIO_pa_stream_set_write_callback)(pa_stream *, pa_stream_request_cb_t, void *); | ||
| 129 | static void (*PULSEAUDIO_pa_stream_set_read_callback)(pa_stream *, pa_stream_request_cb_t, void *); | ||
| 130 | static pa_operation *(*PULSEAUDIO_pa_context_get_server_info)(pa_context *, pa_server_info_cb_t, void *); | ||
| 131 | |||
| 132 | static bool load_pulseaudio_syms(void); | ||
| 133 | |||
| 134 | #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC | ||
| 135 | |||
| 136 | static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC; | ||
| 137 | static SDL_SharedObject *pulseaudio_handle = NULL; | ||
| 138 | |||
| 139 | static 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 | |||
| 155 | static void UnloadPulseAudioLibrary(void) | ||
| 156 | { | ||
| 157 | if (pulseaudio_handle) { | ||
| 158 | SDL_UnloadObject(pulseaudio_handle); | ||
| 159 | pulseaudio_handle = NULL; | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | static 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 | |||
| 185 | static void UnloadPulseAudioLibrary(void) | ||
| 186 | { | ||
| 187 | } | ||
| 188 | |||
| 189 | static bool LoadPulseAudioLibrary(void) | ||
| 190 | { | ||
| 191 | load_pulseaudio_syms(); | ||
| 192 | return true; | ||
| 193 | } | ||
| 194 | |||
| 195 | #endif // SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC | ||
| 196 | |||
| 197 | static 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 | |||
| 267 | static const char *getAppName(void) | ||
| 268 | { | ||
| 269 | return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING); | ||
| 270 | } | ||
| 271 | |||
| 272 | static 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. */ | ||
| 279 | static 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 | |||
| 298 | static 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 | |||
| 314 | static 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 | |||
| 319 | static 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 | |||
| 392 | failed: | ||
| 393 | PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); | ||
| 394 | DisconnectFromPulseServer(); | ||
| 395 | return false; | ||
| 396 | } | ||
| 397 | |||
| 398 | static 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 | ||
| 407 | static 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 | |||
| 432 | static 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 | |||
| 455 | static 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 | |||
| 471 | static 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 | |||
| 477 | static 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 | |||
| 518 | static 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 | |||
| 542 | static 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 | |||
| 575 | static 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 | |||
| 593 | static 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 | |||
| 598 | static 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 | |||
| 746 | static 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 | |||
| 768 | static 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. | ||
| 788 | static 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. | ||
| 797 | static 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 | |||
| 806 | static 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 | |||
| 831 | static 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 | |||
| 838 | static 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. | ||
| 846 | static 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 | |||
| 875 | static 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. | ||
| 892 | static 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 | |||
| 941 | static 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 | |||
| 971 | static 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 | |||
| 978 | static 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 | |||
| 990 | static 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 | |||
| 1004 | static 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 | |||
| 1033 | AudioBootStrap 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 | |||
| 30 | struct 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 | |||
| 55 | static 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? | ||
| 61 | static 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, ¶m) != -1) { | ||
| 66 | param.sched_priority = param.sched_curpriority + 15; | ||
| 67 | SchedSet(0, 0, SCHED_NOCHANGE, ¶m); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | // PCM channel parameters initialize function | ||
| 72 | static 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 | ||
| 89 | static 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 | |||
| 113 | static 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 | |||
| 171 | static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 172 | { | ||
| 173 | return device->hidden->pcm_buf; | ||
| 174 | } | ||
| 175 | |||
| 176 | static 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 | |||
| 193 | static 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 | |||
| 297 | static 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 | |||
| 315 | static 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 | |||
| 424 | static void QSA_Deinitialize(void) | ||
| 425 | { | ||
| 426 | // nothing to do here atm. | ||
| 427 | } | ||
| 428 | |||
| 429 | static 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 | |||
| 446 | AudioBootStrap 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 | |||
| 31 | struct 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 | |||
| 53 | static struct sio_hdl *(*SNDIO_sio_open)(const char *, unsigned int, int); | ||
| 54 | static void (*SNDIO_sio_close)(struct sio_hdl *); | ||
| 55 | static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *); | ||
| 56 | static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *); | ||
| 57 | static int (*SNDIO_sio_start)(struct sio_hdl *); | ||
| 58 | static int (*SNDIO_sio_stop)(struct sio_hdl *); | ||
| 59 | static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t); | ||
| 60 | static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t); | ||
| 61 | static int (*SNDIO_sio_nfds)(struct sio_hdl *); | ||
| 62 | static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int); | ||
| 63 | static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *); | ||
| 64 | static int (*SNDIO_sio_eof)(struct sio_hdl *); | ||
| 65 | static void (*SNDIO_sio_initpar)(struct sio_par *); | ||
| 66 | |||
| 67 | #ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC | ||
| 68 | static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC; | ||
| 69 | static SDL_SharedObject *sndio_handle = NULL; | ||
| 70 | |||
| 71 | static 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 | |||
| 89 | static 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 | |||
| 111 | static void UnloadSNDIOLibrary(void) | ||
| 112 | { | ||
| 113 | if (sndio_handle) { | ||
| 114 | SDL_UnloadObject(sndio_handle); | ||
| 115 | sndio_handle = NULL; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | static 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 | |||
| 138 | static void UnloadSNDIOLibrary(void) | ||
| 139 | { | ||
| 140 | } | ||
| 141 | |||
| 142 | static bool LoadSNDIOLibrary(void) | ||
| 143 | { | ||
| 144 | load_sndio_syms(); | ||
| 145 | return true; | ||
| 146 | } | ||
| 147 | |||
| 148 | #endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC | ||
| 149 | |||
| 150 | static 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 | |||
| 177 | static 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 | |||
| 190 | static 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 | |||
| 200 | static 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 | |||
| 208 | static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) | ||
| 209 | { | ||
| 210 | return device->hidden->mixbuf; | ||
| 211 | } | ||
| 212 | |||
| 213 | static 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 | |||
| 227 | static 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 | |||
| 319 | static void SNDIO_Deinitialize(void) | ||
| 320 | { | ||
| 321 | UnloadSNDIOLibrary(); | ||
| 322 | } | ||
| 323 | |||
| 324 | static 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 | |||
| 330 | static 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 | |||
| 352 | AudioBootStrap 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 | |||
| 31 | struct 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 | |||
| 40 | static 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 | |||
| 57 | static 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 | |||
| 131 | static 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 | ||
| 137 | static 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 | |||
| 146 | static 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 | |||
| 153 | static 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 | |||
| 174 | static 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 | |||
| 185 | static 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 | |||
| 197 | static 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 | |||
| 203 | static 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 | |||
| 215 | static 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 | |||
| 234 | AudioBootStrap 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 | |||
| 29 | struct 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). | ||
| 47 | static HMODULE libavrt = NULL; | ||
| 48 | typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD); | ||
| 49 | typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE); | ||
| 50 | static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL; | ||
| 51 | static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL; | ||
| 52 | |||
| 53 | // Some GUIDs we need to know without linking to libraries that aren't available before Vista. | ||
| 54 | static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } }; | ||
| 55 | static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } }; | ||
| 56 | static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } }; | ||
| 57 | #ifdef __IAudioClient3_INTERFACE_DEFINED__ | ||
| 58 | static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } }; | ||
| 59 | #endif // | ||
| 60 | |||
| 61 | static 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 | |||
| 66 | typedef 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 | |||
| 76 | static SDL_Thread *ManagementThread = NULL; | ||
| 77 | static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL; | ||
| 78 | static SDL_Mutex *ManagementThreadLock = NULL; | ||
| 79 | static SDL_Condition *ManagementThreadCondition = NULL; | ||
| 80 | static SDL_AtomicInt ManagementThreadShutdown; | ||
| 81 | |||
| 82 | static 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 | |||
| 105 | bool 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 | |||
| 167 | static 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 | |||
| 175 | static 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 | |||
| 184 | static 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 | |||
| 192 | static 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 | |||
| 201 | static void StopWasapiHotplug(void) | ||
| 202 | { | ||
| 203 | if (immdevice_initialized) { | ||
| 204 | SDL_IMMDevice_Quit(); | ||
| 205 | immdevice_initialized = false; | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | static 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 | |||
| 224 | static 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 | |||
| 258 | typedef struct | ||
| 259 | { | ||
| 260 | char *errorstr; | ||
| 261 | SDL_Semaphore *ready_sem; | ||
| 262 | } ManagementThreadEntryData; | ||
| 263 | |||
| 264 | static 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 | |||
| 281 | static 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 | |||
| 311 | static 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 | |||
| 331 | typedef struct | ||
| 332 | { | ||
| 333 | SDL_AudioDevice **default_playback; | ||
| 334 | SDL_AudioDevice **default_recording; | ||
| 335 | } mgmtthrtask_DetectDevicesData; | ||
| 336 | |||
| 337 | static 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 | |||
| 344 | static 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 | |||
| 354 | static 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 | |||
| 362 | void 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 | |||
| 370 | static 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 | |||
| 383 | static bool mgmtthrtask_StopAndReleaseClient(void *userdata) | ||
| 384 | { | ||
| 385 | IAudioClient *client = (IAudioClient *) userdata; | ||
| 386 | IAudioClient_Stop(client); | ||
| 387 | IAudioClient_Release(client); | ||
| 388 | return true; | ||
| 389 | } | ||
| 390 | |||
| 391 | static bool mgmtthrtask_ReleaseCaptureClient(void *userdata) | ||
| 392 | { | ||
| 393 | IAudioCaptureClient_Release((IAudioCaptureClient *)userdata); | ||
| 394 | return true; | ||
| 395 | } | ||
| 396 | |||
| 397 | static bool mgmtthrtask_ReleaseRenderClient(void *userdata) | ||
| 398 | { | ||
| 399 | IAudioRenderClient_Release((IAudioRenderClient *)userdata); | ||
| 400 | return true; | ||
| 401 | } | ||
| 402 | |||
| 403 | static bool mgmtthrtask_CoTaskMemFree(void *userdata) | ||
| 404 | { | ||
| 405 | CoTaskMemFree(userdata); | ||
| 406 | return true; | ||
| 407 | } | ||
| 408 | |||
| 409 | static bool mgmtthrtask_CloseHandle(void *userdata) | ||
| 410 | { | ||
| 411 | CloseHandle((HANDLE) userdata); | ||
| 412 | return true; | ||
| 413 | } | ||
| 414 | |||
| 415 | static 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 | |||
| 455 | static 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 | |||
| 482 | static 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! | ||
| 491 | static 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! | ||
| 507 | static 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 | |||
| 527 | static 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 | |||
| 548 | static 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 | |||
| 557 | static 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 | |||
| 602 | static 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 | |||
| 639 | static 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 | |||
| 658 | static 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 | |||
| 668 | static 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. | ||
| 850 | bool WASAPI_PrepDevice(SDL_AudioDevice *device) | ||
| 851 | { | ||
| 852 | bool rc = true; | ||
| 853 | return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) && rc); | ||
| 854 | } | ||
| 855 | |||
| 856 | static 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 | |||
| 874 | static 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 | |||
| 890 | static 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 | |||
| 904 | static bool mgmtthrtask_FreeDeviceHandle(void *userdata) | ||
| 905 | { | ||
| 906 | SDL_IMMDevice_FreeDeviceHandle((SDL_AudioDevice *) userdata); | ||
| 907 | return true; | ||
| 908 | } | ||
| 909 | |||
| 910 | static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device) | ||
| 911 | { | ||
| 912 | bool rc; | ||
| 913 | WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc); | ||
| 914 | } | ||
| 915 | |||
| 916 | static bool mgmtthrtask_DeinitializeStart(void *userdata) | ||
| 917 | { | ||
| 918 | StopWasapiHotplug(); | ||
| 919 | return true; | ||
| 920 | } | ||
| 921 | |||
| 922 | static void WASAPI_DeinitializeStart(void) | ||
| 923 | { | ||
| 924 | bool rc; | ||
| 925 | WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc); | ||
| 926 | } | ||
| 927 | |||
| 928 | static void WASAPI_Deinitialize(void) | ||
| 929 | { | ||
| 930 | DeinitManagementThread(); | ||
| 931 | } | ||
| 932 | |||
| 933 | static 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 | |||
| 959 | AudioBootStrap 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 | ||
| 27 | extern "C" { | ||
| 28 | #endif | ||
| 29 | |||
| 30 | #include "../SDL_sysaudio.h" | ||
| 31 | |||
| 32 | struct 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. | ||
| 49 | bool WASAPI_PrepDevice(SDL_AudioDevice *device); | ||
| 50 | void 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. | ||
| 54 | typedef bool (*ManagementThreadTask)(void *userdata); | ||
| 55 | bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_until_complete); | ||
| 56 | |||
| 57 | #ifdef __cplusplus | ||
| 58 | } | ||
| 59 | #endif | ||
| 60 | |||
| 61 | #endif // SDL_wasapi_h_ | ||
