/
SDL_audio.c
2327 lines (1966 loc) · 90.4 KB
/
SDL_audio.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_audio_c.h"
#include "SDL_sysaudio.h"
#include "../thread/SDL_systhread.h"
#include "../SDL_utils_c.h"
// Available audio drivers
static const AudioBootStrap *const bootstrap[] = {
#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO
#ifdef SDL_AUDIO_DRIVER_PIPEWIRE
&PIPEWIRE_PREFERRED_bootstrap,
#endif
&PULSEAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_PIPEWIRE
&PIPEWIRE_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_ALSA
&ALSA_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_SNDIO
&SNDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_NETBSD
&NETBSDAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_WASAPI
&WASAPI_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_DSOUND
&DSOUND_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_HAIKU
&HAIKUAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_COREAUDIO
&COREAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_AAUDIO
&AAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_OPENSLES
&OPENSLES_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_ANDROID
&ANDROIDAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_PS2
&PS2AUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_PSP
&PSPAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_VITA
&VITAAUD_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_N3DS
&N3DSAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
&EMSCRIPTENAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_JACK
&JACK_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_OSS
&DSP_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_QNX
&QSAAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_DISK
&DISKAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_DUMMY
&DUMMYAUDIO_bootstrap,
#endif
NULL
};
static SDL_AudioDriver current_audio;
// Deduplicated list of audio bootstrap drivers.
static const AudioBootStrap *deduped_bootstrap[SDL_arraysize(bootstrap) - 1];
int SDL_GetNumAudioDrivers(void)
{
static int num_drivers = -1;
if (num_drivers >= 0) {
return num_drivers;
}
num_drivers = 0;
// Build a list of unique audio drivers.
for (int i = 0; bootstrap[i] != NULL; ++i) {
SDL_bool duplicate = SDL_FALSE;
for (int j = 0; j < i; ++j) {
if (SDL_strcmp(bootstrap[i]->name, bootstrap[j]->name) == 0) {
duplicate = SDL_TRUE;
break;
}
}
if (!duplicate) {
deduped_bootstrap[num_drivers++] = bootstrap[i];
}
}
return num_drivers;
}
const char *SDL_GetAudioDriver(int index)
{
if (index >= 0 && index < SDL_GetNumAudioDrivers()) {
return deduped_bootstrap[index]->name;
}
return NULL;
}
const char *SDL_GetCurrentAudioDriver(void)
{
return current_audio.name;
}
static int GetDefaultSampleFramesFromFreq(const int freq)
{
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES);
if (hint) {
const int val = SDL_atoi(hint);
if (val > 0) {
return val;
}
}
if (freq <= 22050) {
return 512;
} else if (freq <= 48000) {
return 1024;
} else if (freq <= 96000) {
return 2048;
} else {
return 4096;
}
}
void OnAudioStreamCreated(SDL_AudioStream *stream)
{
SDL_assert(stream != NULL);
// NOTE that you can create an audio stream without initializing the audio subsystem,
// but it will not be automatically destroyed during a later call to SDL_Quit!
// You must explicitly destroy it yourself!
if (current_audio.device_hash_lock) {
// this isn't really part of the "device list" but it's a convenient lock to use here.
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
if (current_audio.existing_streams) {
current_audio.existing_streams->prev = stream;
}
stream->prev = NULL;
stream->next = current_audio.existing_streams;
current_audio.existing_streams = stream;
SDL_UnlockRWLock(current_audio.device_hash_lock);
}
}
void OnAudioStreamDestroy(SDL_AudioStream *stream)
{
SDL_assert(stream != NULL);
// NOTE that you can create an audio stream without initializing the audio subsystem,
// but it will not be automatically destroyed during a later call to SDL_Quit!
// You must explicitly destroy it yourself!
if (current_audio.device_hash_lock) {
// this isn't really part of the "device list" but it's a convenient lock to use here.
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
if (stream->prev) {
stream->prev->next = stream->next;
}
if (stream->next) {
stream->next->prev = stream->prev;
}
if (stream == current_audio.existing_streams) {
current_audio.existing_streams = stream->next;
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
}
}
// device should be locked when calling this.
static SDL_bool AudioDeviceCanUseSimpleCopy(SDL_AudioDevice *device)
{
SDL_assert(device != NULL);
return (
device->logical_devices && // there's a logical device
!device->logical_devices->next && // there's only _ONE_ logical device
!device->logical_devices->postmix && // there isn't a postmix callback
device->logical_devices->bound_streams && // there's a bound stream
!device->logical_devices->bound_streams->next_binding // there's only _ONE_ bound stream.
);
}
// should hold device->lock before calling.
static void UpdateAudioStreamFormatsPhysical(SDL_AudioDevice *device)
{
if (!device->iscapture) { // for capture devices, we only want to move to float32 for postmix, which we'll handle elsewhere.
const SDL_bool simple_copy = AudioDeviceCanUseSimpleCopy(device);
SDL_AudioSpec spec;
device->simple_copy = simple_copy;
SDL_copyp(&spec, &device->spec);
if (!simple_copy) {
spec.format = SDL_AUDIO_F32; // mixing and postbuf operates in float32 format.
}
for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) {
// set the proper end of the stream to the device's format.
// SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec.
SDL_LockMutex(stream->lock);
SDL_copyp(&stream->dst_spec, &spec);
SDL_UnlockMutex(stream->lock);
}
}
}
}
// Zombie device implementation...
// These get used when a device is disconnected or fails, so audiostreams don't overflow with data that isn't being
// consumed and apps relying on audio callbacks don't stop making progress.
static int ZombieWaitDevice(SDL_AudioDevice *device)
{
if (!SDL_AtomicGet(&device->shutdown)) {
const int frames = device->buffer_size / SDL_AUDIO_FRAMESIZE(device->spec);
SDL_Delay((frames * 1000) / device->spec.freq);
}
return 0;
}
static int ZombiePlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
return 0; // no-op, just throw the audio away.
}
static Uint8 *ZombieGetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->work_buffer;
}
static int ZombieCaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
// return a full buffer of silence every time.
SDL_memset(buffer, device->silence_value, buflen);
return buflen;
}
static void ZombieFlushCapture(SDL_AudioDevice *device)
{
// no-op, this is all imaginary.
}
// device management and hotplug...
/* SDL_AudioDevice, in SDL3, represents a piece of physical hardware, whether it is in use or not, so these objects exist as long as
the system-level device is available.
Physical devices get destroyed for three reasons:
- They were lost to the system (a USB cable is kicked out, etc).
- They failed for some other unlikely reason at the API level (which is _also_ probably a USB cable being kicked out).
- We are shutting down, so all allocated resources are being freed.
They are _not_ destroyed because we are done using them (when we "close" a playing device).
*/
static void ClosePhysicalAudioDevice(SDL_AudioDevice *device);
SDL_COMPILE_TIME_ASSERT(check_lowest_audio_default_value, SDL_AUDIO_DEVICE_DEFAULT_CAPTURE < SDL_AUDIO_DEVICE_DEFAULT_OUTPUT);
static SDL_AtomicInt last_device_instance_id; // increments on each device add to provide unique instance IDs
static SDL_AudioDeviceID AssignAudioDeviceInstanceId(SDL_bool iscapture, SDL_bool islogical)
{
/* Assign an instance id! Start at 2, in case there are things from the SDL2 era that still think 1 is a special value.
Also, make sure we don't assign SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, etc. */
// The bottom two bits of the instance id tells you if it's an output device (1<<0), and if it's a physical device (1<<1).
const SDL_AudioDeviceID flags = (iscapture ? 0 : (1<<0)) | (islogical ? 0 : (1<<1));
const SDL_AudioDeviceID instance_id = (((SDL_AudioDeviceID) (SDL_AtomicIncRef(&last_device_instance_id) + 1)) << 2) | flags;
SDL_assert( (instance_id >= 2) && (instance_id < SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) );
return instance_id;
}
static void ObtainPhysicalAudioDeviceObj(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXMEL SDL_ACQUIRE
{
if (device) {
RefPhysicalAudioDevice(device);
SDL_LockMutex(device->lock);
}
}
static void ReleaseAudioDevice(SDL_AudioDevice *device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_RELEASE
{
if (device) {
SDL_UnlockMutex(device->lock);
UnrefPhysicalAudioDevice(device);
}
}
// If found, this locks _the physical device_ this logical device is associated with, before returning.
static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_AudioDevice **_device) SDL_NO_THREAD_SAFETY_ANALYSIS // !!! FIXME: SDL_ACQUIRE
{
SDL_assert(_device != NULL);
if (!SDL_GetCurrentAudioDriver()) {
SDL_SetError("Audio subsystem is not initialized");
*_device = NULL;
return NULL;
}
SDL_AudioDevice *device = NULL;
SDL_LogicalAudioDevice *logdev = NULL;
// bit #1 of devid is set for physical devices and unset for logical.
const SDL_bool islogical = !(devid & (1<<1));
if (islogical) { // don't bother looking if it's not a logical device id value.
SDL_LockRWLockForReading(current_audio.device_hash_lock);
SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev);
if (logdev) {
device = logdev->physical_device;
SDL_assert(device != NULL);
RefPhysicalAudioDevice(device); // reference it, in case the logical device migrates to a new default.
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
if (logdev) {
// we have to release the device_hash_lock before we take the device lock, to avoid deadlocks, so do a loop
// to make sure the correct physical device gets locked, in case we're in a race with the default changing.
while (SDL_TRUE) {
SDL_LockMutex(device->lock);
SDL_AudioDevice *recheck_device = (SDL_AudioDevice *) SDL_AtomicGetPtr((void **) &logdev->physical_device);
if (device == recheck_device) {
break;
}
// default changed from under us! Try again!
RefPhysicalAudioDevice(recheck_device);
SDL_UnlockMutex(device->lock);
UnrefPhysicalAudioDevice(device);
device = recheck_device;
}
}
}
if (!logdev) {
SDL_SetError("Invalid audio device instance ID");
}
*_device = device;
return logdev;
}
/* this finds the physical device associated with `devid` and locks it for use.
Note that a logical device instance id will return its associated physical device! */
static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // !!! FIXME: SDL_ACQUIRE
{
SDL_AudioDevice *device = NULL;
// bit #1 of devid is set for physical devices and unset for logical.
const SDL_bool islogical = !(devid & (1<<1));
if (islogical) {
ObtainLogicalAudioDevice(devid, &device);
} else if (!SDL_GetCurrentAudioDriver()) { // (the `islogical` path, above, checks this in ObtainLogicalAudioDevice.)
SDL_SetError("Audio subsystem is not initialized");
} else {
SDL_LockRWLockForReading(current_audio.device_hash_lock);
SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device);
SDL_UnlockRWLock(current_audio.device_hash_lock);
if (!device) {
SDL_SetError("Invalid audio device instance ID");
} else {
ObtainPhysicalAudioDeviceObj(device);
}
}
return device;
}
static SDL_AudioDevice *ObtainPhysicalAudioDeviceDefaultAllowed(SDL_AudioDeviceID devid) // !!! FIXME: SDL_ACQUIRE
{
const SDL_bool wants_default = ((devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) || (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE));
if (!wants_default) {
return ObtainPhysicalAudioDevice(devid);
}
const SDL_AudioDeviceID orig_devid = devid;
while (SDL_TRUE) {
SDL_LockRWLockForReading(current_audio.device_hash_lock);
if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) {
devid = current_audio.default_output_device_id;
} else if (orig_devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) {
devid = current_audio.default_capture_device_id;
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
if (devid == 0) {
SDL_SetError("No default audio device available");
break;
}
SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid);
if (!device) {
break;
}
// make sure the default didn't change while we were waiting for the lock...
SDL_bool got_it = SDL_FALSE;
SDL_LockRWLockForReading(current_audio.device_hash_lock);
if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) && (devid == current_audio.default_output_device_id)) {
got_it = SDL_TRUE;
} else if ((orig_devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) && (devid == current_audio.default_capture_device_id)) {
got_it = SDL_TRUE;
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
if (got_it) {
return device;
}
ReleaseAudioDevice(device); // let it go and try again.
}
return NULL;
}
// this assumes you hold the _physical_ device lock for this logical device! This will not unlock the lock or close the physical device!
// It also will not unref the physical device, since we might be shutting down; SDL_CloseAudioDevice handles the unref.
static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev)
{
// Remove ourselves from the device_hash hashtable.
if (current_audio.device_hash) { // will be NULL while shutting down.
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) logdev->instance_id);
SDL_UnlockRWLock(current_audio.device_hash_lock);
}
// remove ourselves from the physical device's list of logical devices.
if (logdev->next) {
logdev->next->prev = logdev->prev;
}
if (logdev->prev) {
logdev->prev->next = logdev->next;
}
if (logdev->physical_device->logical_devices == logdev) {
logdev->physical_device->logical_devices = logdev->next;
}
// unbind any still-bound streams...
SDL_AudioStream *next;
for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = next) {
SDL_LockMutex(stream->lock);
next = stream->next_binding;
stream->next_binding = NULL;
stream->prev_binding = NULL;
stream->bound_device = NULL;
SDL_UnlockMutex(stream->lock);
}
UpdateAudioStreamFormatsPhysical(logdev->physical_device);
SDL_free(logdev);
}
// this must not be called while `device` is still in a device list, or while a device's audio thread is still running.
static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device)
{
if (!device) {
return;
}
// Destroy any logical devices that still exist...
SDL_LockMutex(device->lock); // don't use ObtainPhysicalAudioDeviceObj because we don't want to change refcounts while destroying.
while (device->logical_devices) {
DestroyLogicalAudioDevice(device->logical_devices);
}
ClosePhysicalAudioDevice(device);
current_audio.impl.FreeDeviceHandle(device);
SDL_UnlockMutex(device->lock); // don't use ReleaseAudioDevice because we don't want to change refcounts while destroying.
SDL_DestroyMutex(device->lock);
SDL_DestroyCondition(device->close_cond);
SDL_free(device->work_buffer);
SDL_free(device->name);
SDL_free(device);
}
// Don't hold the device lock when calling this, as we may destroy the device!
void UnrefPhysicalAudioDevice(SDL_AudioDevice *device)
{
if (SDL_AtomicDecRef(&device->refcount)) {
// take it out of the device list.
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
if (SDL_RemoveFromHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id)) {
SDL_AtomicAdd(device->iscapture ? ¤t_audio.capture_device_count : ¤t_audio.output_device_count, -1);
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
DestroyPhysicalAudioDevice(device); // ...and nuke it.
}
}
void RefPhysicalAudioDevice(SDL_AudioDevice *device)
{
SDL_AtomicIncRef(&device->refcount);
}
static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool iscapture, const SDL_AudioSpec *spec, void *handle, SDL_AtomicInt *device_count)
{
SDL_assert(name != NULL);
SDL_LockRWLockForReading(current_audio.device_hash_lock);
const int shutting_down = SDL_AtomicGet(¤t_audio.shutting_down);
SDL_UnlockRWLock(current_audio.device_hash_lock);
if (shutting_down) {
return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment.
}
SDL_AudioDevice *device = (SDL_AudioDevice *)SDL_calloc(1, sizeof(SDL_AudioDevice));
if (!device) {
return NULL;
}
device->name = SDL_strdup(name);
if (!device->name) {
SDL_free(device);
return NULL;
}
device->lock = SDL_CreateMutex();
if (!device->lock) {
SDL_free(device->name);
SDL_free(device);
return NULL;
}
device->close_cond = SDL_CreateCondition();
if (!device->close_cond) {
SDL_DestroyMutex(device->lock);
SDL_free(device->name);
SDL_free(device);
return NULL;
}
SDL_AtomicSet(&device->shutdown, 0);
SDL_AtomicSet(&device->zombie, 0);
device->iscapture = iscapture;
SDL_copyp(&device->spec, spec);
SDL_copyp(&device->default_spec, spec);
device->sample_frames = GetDefaultSampleFramesFromFreq(device->spec.freq);
device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format);
device->handle = handle;
device->instance_id = AssignAudioDeviceInstanceId(iscapture, /*islogical=*/SDL_FALSE);
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
if (SDL_InsertIntoHashTable(current_audio.device_hash, (const void *) (uintptr_t) device->instance_id, device)) {
SDL_AtomicAdd(device_count, 1);
} else {
SDL_DestroyCondition(device->close_cond);
SDL_DestroyMutex(device->lock);
SDL_free(device->name);
SDL_free(device);
device = NULL;
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
RefPhysicalAudioDevice(device); // unref'd on device disconnect.
return device;
}
static SDL_AudioDevice *CreateAudioCaptureDevice(const char *name, const SDL_AudioSpec *spec, void *handle)
{
SDL_assert(current_audio.impl.HasCaptureSupport);
return CreatePhysicalAudioDevice(name, SDL_TRUE, spec, handle, ¤t_audio.capture_device_count);
}
static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, const SDL_AudioSpec *spec, void *handle)
{
return CreatePhysicalAudioDevice(name, SDL_FALSE, spec, handle, ¤t_audio.output_device_count);
}
// The audio backends call this when a new device is plugged in.
SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *inspec, void *handle)
{
// device handles MUST be unique! If the target reuses the same handle for hardware with both input and output interfaces, wrap it in a pointer you SDL_malloc'd!
SDL_assert(SDL_FindPhysicalAudioDeviceByHandle(handle) == NULL);
const SDL_AudioFormat default_format = iscapture ? DEFAULT_AUDIO_CAPTURE_FORMAT : DEFAULT_AUDIO_OUTPUT_FORMAT;
const int default_channels = iscapture ? DEFAULT_AUDIO_CAPTURE_CHANNELS : DEFAULT_AUDIO_OUTPUT_CHANNELS;
const int default_freq = iscapture ? DEFAULT_AUDIO_CAPTURE_FREQUENCY : DEFAULT_AUDIO_OUTPUT_FREQUENCY;
SDL_AudioSpec spec;
if (!inspec) {
spec.format = default_format;
spec.channels = default_channels;
spec.freq = default_freq;
} else {
spec.format = (inspec->format != 0) ? inspec->format : default_format;
spec.channels = (inspec->channels != 0) ? inspec->channels : default_channels;
spec.freq = (inspec->freq != 0) ? inspec->freq : default_freq;
}
SDL_AudioDevice *device = iscapture ? CreateAudioCaptureDevice(name, &spec, handle) : CreateAudioOutputDevice(name, &spec, handle);
// 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).
if (device) {
SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent));
if (p) { // if allocation fails, you won't get an event, but we can't help that.
p->type = SDL_EVENT_AUDIO_DEVICE_ADDED;
p->devid = device->instance_id;
p->next = NULL;
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
SDL_assert(current_audio.pending_events_tail != NULL);
SDL_assert(current_audio.pending_events_tail->next == NULL);
current_audio.pending_events_tail->next = p;
current_audio.pending_events_tail = p;
SDL_UnlockRWLock(current_audio.device_hash_lock);
}
}
return device;
}
// Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread.
void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
{
if (!device) {
return;
}
// Save off removal info in a list so we can send events for each, next
// time the event queue pumps, in case something tries to close a device
// from an event filter, as this would risk deadlocks and other disasters
// if done from the device thread.
SDL_PendingAudioDeviceEvent pending;
pending.next = NULL;
SDL_PendingAudioDeviceEvent *pending_tail = &pending;
ObtainPhysicalAudioDeviceObj(device);
SDL_LockRWLockForReading(current_audio.device_hash_lock);
const SDL_AudioDeviceID devid = device->instance_id;
const SDL_bool is_default_device = ((devid == current_audio.default_output_device_id) || (devid == current_audio.default_capture_device_id));
SDL_UnlockRWLock(current_audio.device_hash_lock);
const SDL_bool first_disconnect = SDL_AtomicCompareAndSwap(&device->zombie, 0, 1);
if (first_disconnect) { // if already disconnected this device, don't do it twice.
// Swap in "Zombie" versions of the usual platform interfaces, so the device will keep
// making progress until the app closes it. Otherwise, streams might continue to
// accumulate waste data that never drains, apps that depend on audio callbacks to
// progress will freeze, etc.
device->WaitDevice = ZombieWaitDevice;
device->GetDeviceBuf = ZombieGetDeviceBuf;
device->PlayDevice = ZombiePlayDevice;
device->WaitCaptureDevice = ZombieWaitDevice;
device->CaptureFromDevice = ZombieCaptureFromDevice;
device->FlushCapture = ZombieFlushCapture;
// on default devices, dump any logical devices that explicitly opened this device. Things that opened the system default can stay.
// on non-default devices, dump everything.
// (by "dump" we mean send a REMOVED event; the zombie will keep consuming audio data for these logical devices until explicitly closed.)
for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev; logdev = logdev->next) {
if (!is_default_device || !logdev->opened_as_default) { // if opened as a default, leave it on the zombie device for later migration.
SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent));
if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
p->type = SDL_EVENT_AUDIO_DEVICE_REMOVED;
p->devid = logdev->instance_id;
p->next = NULL;
pending_tail->next = p;
pending_tail = p;
}
}
}
SDL_PendingAudioDeviceEvent *p = (SDL_PendingAudioDeviceEvent *) SDL_malloc(sizeof (SDL_PendingAudioDeviceEvent));
if (p) { // if this failed, no event for you, but you have deeper problems anyhow.
p->type = SDL_EVENT_AUDIO_DEVICE_REMOVED;
p->devid = device->instance_id;
p->next = NULL;
pending_tail->next = p;
pending_tail = p;
}
}
ReleaseAudioDevice(device);
if (first_disconnect) {
if (pending.next) { // NULL if event is disabled or disaster struck.
SDL_LockRWLockForWriting(current_audio.device_hash_lock);
SDL_assert(current_audio.pending_events_tail != NULL);
SDL_assert(current_audio.pending_events_tail->next == NULL);
current_audio.pending_events_tail->next = pending.next;
current_audio.pending_events_tail = pending_tail;
SDL_UnlockRWLock(current_audio.device_hash_lock);
}
UnrefPhysicalAudioDevice(device);
}
}
// stubs for audio drivers that don't need a specific entry point...
static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ }
static int SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { return 0; /* no-op. */ }
static int SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { return 0; /* no-op. */ }
static int SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { return 0; /* no-op. */ }
static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ }
static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
static void SDL_AudioDeinitializeStart_Default(void) { /* no-op. */ }
static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ }
static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ }
static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device)
{
SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
}
static void SDL_AudioDetectDevices_Default(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
// you have to write your own implementation if these assertions fail.
SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice);
SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport);
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)((size_t)0x1));
if (current_audio.impl.HasCaptureSupport) {
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)((size_t)0x2));
}
}
static Uint8 *SDL_AudioGetDeviceBuf_Default(SDL_AudioDevice *device, int *buffer_size)
{
*buffer_size = 0;
return NULL;
}
static int SDL_AudioCaptureFromDevice_Default(SDL_AudioDevice *device, void *buffer, int buflen)
{
return SDL_Unsupported();
}
static int SDL_AudioOpenDevice_Default(SDL_AudioDevice *device)
{
return SDL_Unsupported();
}
// Fill in stub functions for unused driver entry points. This lets us blindly call them without having to check for validity first.
static void CompleteAudioEntryPoints(void)
{
#define FILL_STUB(x) if (!current_audio.impl.x) { current_audio.impl.x = SDL_Audio##x##_Default; }
FILL_STUB(DetectDevices);
FILL_STUB(OpenDevice);
FILL_STUB(ThreadInit);
FILL_STUB(ThreadDeinit);
FILL_STUB(WaitDevice);
FILL_STUB(PlayDevice);
FILL_STUB(GetDeviceBuf);
FILL_STUB(WaitCaptureDevice);
FILL_STUB(CaptureFromDevice);
FILL_STUB(FlushCapture);
FILL_STUB(CloseDevice);
FILL_STUB(FreeDeviceHandle);
FILL_STUB(DeinitializeStart);
FILL_STUB(Deinitialize);
#undef FILL_STUB
}
static SDL_AudioDevice *GetFirstAddedAudioDevice(const SDL_bool iscapture)
{
SDL_AudioDeviceID highest = (SDL_AudioDeviceID) SDL_AUDIO_DEVICE_DEFAULT_OUTPUT; // According to AssignAudioDeviceInstanceId, nothing can have a value this large.
SDL_AudioDevice *retval = NULL;
// (Device IDs increase as new devices are added, so the first device added has the lowest SDL_AudioDeviceID value.)
SDL_LockRWLockForReading(current_audio.device_hash_lock);
const void *key;
const void *value;
void *iter = NULL;
while (SDL_IterateHashTable(current_audio.device_hash, &key, &value, &iter)) {
const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key;
// bit #0 of devid is set for output devices and unset for capture.
// bit #1 of devid is set for physical devices and unset for logical.
const SDL_bool devid_iscapture = !(devid & (1 << 0));
const SDL_bool isphysical = (devid & (1 << 1));
if (isphysical && (devid_iscapture == iscapture) && (devid < highest)) {
highest = devid;
retval = (SDL_AudioDevice *) value;
}
}
SDL_UnlockRWLock(current_audio.device_hash_lock);
return retval;
}
static Uint32 HashAudioDeviceID(const void *key, void *data)
{
// shift right 2, to dump the first two bits, since these are flags
// (capture vs playback, logical vs physical) and the rest are unique incrementing integers.
return ((Uint32) ((uintptr_t) key)) >> 2;
}
static SDL_bool MatchAudioDeviceID(const void *a, const void *b, void *data)
{
return (a == b);
}
static void NukeAudioDeviceHashItem(const void *key, const void *value, void *data)
{
// no-op, keys and values in this hashtable are treated as Plain Old Data and don't get freed here.
}
// !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match.
int SDL_InitAudio(const char *driver_name)
{
if (SDL_GetCurrentAudioDriver()) {
SDL_QuitAudio(); // shutdown driver if already running.
}
// 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.
SDL_AtomicCompareAndSwap(&last_device_instance_id, 0, 2);
SDL_ChooseAudioConverters();
SDL_SetupAudioResampler();
SDL_RWLock *device_hash_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem.
if (!device_hash_lock) {
return -1;
}
SDL_HashTable *device_hash = SDL_CreateHashTable(NULL, 8, HashAudioDeviceID, MatchAudioDeviceID, NukeAudioDeviceHashItem, SDL_FALSE);
if (!device_hash) {
SDL_DestroyRWLock(device_hash_lock);
return -1;
}
// Select the proper audio driver
if (!driver_name) {
driver_name = SDL_GetHint(SDL_HINT_AUDIO_DRIVER);
}
SDL_bool initialized = SDL_FALSE;
SDL_bool tried_to_init = SDL_FALSE;
if (driver_name && *driver_name != 0) {
char *driver_name_copy = SDL_strdup(driver_name);
const char *driver_attempt = driver_name_copy;
if (!driver_name_copy) {
SDL_DestroyRWLock(device_hash_lock);
SDL_DestroyHashTable(device_hash);
return -1;
}
while (driver_attempt && *driver_attempt != 0 && !initialized) {
char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
if (driver_attempt_end) {
*driver_attempt_end = '\0';
}
// SDL 1.2 uses the name "dsound", so we'll support both.
if (SDL_strcmp(driver_attempt, "dsound") == 0) {
driver_attempt = "directsound";
} else if (SDL_strcmp(driver_attempt, "pulse") == 0) { // likewise, "pulse" was renamed to "pulseaudio"
driver_attempt = "pulseaudio";
}
for (int i = 0; bootstrap[i]; ++i) {
if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) {
tried_to_init = SDL_TRUE;
SDL_zero(current_audio);
current_audio.pending_events_tail = ¤t_audio.pending_events;
current_audio.device_hash_lock = device_hash_lock;
current_audio.device_hash = device_hash;
if (bootstrap[i]->init(¤t_audio.impl)) {
current_audio.name = bootstrap[i]->name;
current_audio.desc = bootstrap[i]->desc;
initialized = SDL_TRUE;
break;
}
}
}
driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
}
SDL_free(driver_name_copy);
} else {
for (int i = 0; (!initialized) && (bootstrap[i]); ++i) {
if (bootstrap[i]->demand_only) {
continue;
}
tried_to_init = SDL_TRUE;
SDL_zero(current_audio);
current_audio.pending_events_tail = ¤t_audio.pending_events;
current_audio.device_hash_lock = device_hash_lock;
current_audio.device_hash = device_hash;
if (bootstrap[i]->init(¤t_audio.impl)) {
current_audio.name = bootstrap[i]->name;
current_audio.desc = bootstrap[i]->desc;
initialized = SDL_TRUE;
}
}
}
if (!initialized) {
// specific drivers will set the error message if they fail, but otherwise we do it here.
if (!tried_to_init) {
if (driver_name) {
SDL_SetError("Audio target '%s' not available", driver_name);
} else {
SDL_SetError("No available audio device");
}
}
SDL_DestroyRWLock(device_hash_lock);
SDL_DestroyHashTable(device_hash);
SDL_zero(current_audio);
return -1; // No driver was available, so fail.
}
CompleteAudioEntryPoints();
// Make sure we have a list of devices available at startup...
SDL_AudioDevice *default_output = NULL;
SDL_AudioDevice *default_capture = NULL;
current_audio.impl.DetectDevices(&default_output, &default_capture);
// If no default was _ever_ specified, just take the first device we see, if any.
if (!default_output) {
default_output = GetFirstAddedAudioDevice(/*iscapture=*/SDL_FALSE);
}
if (!default_capture) {
default_capture = GetFirstAddedAudioDevice(/*iscapture=*/SDL_TRUE);
}
if (default_output) {
current_audio.default_output_device_id = default_output->instance_id;
RefPhysicalAudioDevice(default_output); // extra ref on default devices.
}
if (default_capture) {
current_audio.default_capture_device_id = default_capture->instance_id;
RefPhysicalAudioDevice(default_capture); // extra ref on default devices.
}
return 0;
}
void SDL_QuitAudio(void)
{
if (!current_audio.name) { // not initialized?!
return;
}
current_audio.impl.DeinitializeStart();
// Destroy any audio streams that still exist...