1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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 #if 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 futher hotplug events. But that might be okay. */
30 #ifndef SDL_ALSA_HOTPLUG_THREAD
31 #define SDL_ALSA_HOTPLUG_THREAD 1
32 #endif
33
34 /* Allow access to a raw mixing buffer */
35
36 #include <sys/types.h>
37 #include <signal.h> /* For kill() */
38 #include <string.h>
39
40 #include "SDL_timer.h"
41 #include "SDL_audio.h"
42 #include "../SDL_audio_c.h"
43 #include "SDL_alsa_audio.h"
44
45 #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
46 #include "SDL_loadso.h"
47 #endif
48
49 static int (*ALSA_snd_pcm_open)
50 (snd_pcm_t **, const char *, snd_pcm_stream_t, int);
51 static int (*ALSA_snd_pcm_close) (snd_pcm_t * pcm);
52 static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)
53 (snd_pcm_t *, const void *, snd_pcm_uframes_t);
54 static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)
55 (snd_pcm_t *, void *, snd_pcm_uframes_t);
56 static int (*ALSA_snd_pcm_recover) (snd_pcm_t *, int, int);
57 static int (*ALSA_snd_pcm_prepare) (snd_pcm_t *);
58 static int (*ALSA_snd_pcm_drain) (snd_pcm_t *);
59 static const char *(*ALSA_snd_strerror) (int);
60 static size_t(*ALSA_snd_pcm_hw_params_sizeof) (void);
61 static size_t(*ALSA_snd_pcm_sw_params_sizeof) (void);
62 static void (*ALSA_snd_pcm_hw_params_copy)
63 (snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
64 static int (*ALSA_snd_pcm_hw_params_any) (snd_pcm_t *, snd_pcm_hw_params_t *);
65 static int (*ALSA_snd_pcm_hw_params_set_access)
66 (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
67 static int (*ALSA_snd_pcm_hw_params_set_format)
68 (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
69 static int (*ALSA_snd_pcm_hw_params_set_channels)
70 (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
71 static int (*ALSA_snd_pcm_hw_params_get_channels)
72 (const snd_pcm_hw_params_t *, unsigned int *);
73 static int (*ALSA_snd_pcm_hw_params_set_rate_near)
74 (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
75 static int (*ALSA_snd_pcm_hw_params_set_period_size_near)
76 (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
77 static int (*ALSA_snd_pcm_hw_params_get_period_size)
78 (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
79 static int (*ALSA_snd_pcm_hw_params_set_periods_min)
80 (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
81 static int (*ALSA_snd_pcm_hw_params_set_periods_first)
82 (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
83 static int (*ALSA_snd_pcm_hw_params_get_periods)
84 (const snd_pcm_hw_params_t *, unsigned int *, int *);
85 static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)
86 (snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
87 static int (*ALSA_snd_pcm_hw_params_get_buffer_size)
88 (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
89 static int (*ALSA_snd_pcm_hw_params) (snd_pcm_t *, snd_pcm_hw_params_t *);
90 static int (*ALSA_snd_pcm_sw_params_current) (snd_pcm_t *,
91 snd_pcm_sw_params_t *);
92 static int (*ALSA_snd_pcm_sw_params_set_start_threshold)
93 (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
94 static int (*ALSA_snd_pcm_sw_params) (snd_pcm_t *, snd_pcm_sw_params_t *);
95 static int (*ALSA_snd_pcm_nonblock) (snd_pcm_t *, int);
96 static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
97 static int (*ALSA_snd_pcm_sw_params_set_avail_min)
98 (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
99 static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
100 static int (*ALSA_snd_device_name_hint) (int, const char *, void ***);
101 static char* (*ALSA_snd_device_name_get_hint) (const void *, const char *);
102 static int (*ALSA_snd_device_name_free_hint) (void **);
103 static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
104 #ifdef SND_CHMAP_API_VERSION
105 static snd_pcm_chmap_t* (*ALSA_snd_pcm_get_chmap) (snd_pcm_t *);
106 static int (*ALSA_snd_pcm_chmap_print) (const snd_pcm_chmap_t *map, size_t maxlen, char *buf);
107 #endif
108
109 #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
110 #define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
111 #define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
112
113 static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
114 static void *alsa_handle = NULL;
115
116 static int
load_alsa_sym(const char * fn,void ** addr)117 load_alsa_sym(const char *fn, void **addr)
118 {
119 *addr = SDL_LoadFunction(alsa_handle, fn);
120 if (*addr == NULL) {
121 /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
122 return 0;
123 }
124
125 return 1;
126 }
127
128 /* cast funcs to char* first, to please GCC's strict aliasing rules. */
129 #define SDL_ALSA_SYM(x) \
130 if (!load_alsa_sym(#x, (void **) (char *) &ALSA_##x)) return -1
131 #else
132 #define SDL_ALSA_SYM(x) ALSA_##x = x
133 #endif
134
135 static int
load_alsa_syms(void)136 load_alsa_syms(void)
137 {
138 SDL_ALSA_SYM(snd_pcm_open);
139 SDL_ALSA_SYM(snd_pcm_close);
140 SDL_ALSA_SYM(snd_pcm_writei);
141 SDL_ALSA_SYM(snd_pcm_readi);
142 SDL_ALSA_SYM(snd_pcm_recover);
143 SDL_ALSA_SYM(snd_pcm_prepare);
144 SDL_ALSA_SYM(snd_pcm_drain);
145 SDL_ALSA_SYM(snd_strerror);
146 SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
147 SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
148 SDL_ALSA_SYM(snd_pcm_hw_params_copy);
149 SDL_ALSA_SYM(snd_pcm_hw_params_any);
150 SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
151 SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
152 SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
153 SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
154 SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
155 SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
156 SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
157 SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_min);
158 SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_first);
159 SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
160 SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
161 SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
162 SDL_ALSA_SYM(snd_pcm_hw_params);
163 SDL_ALSA_SYM(snd_pcm_sw_params_current);
164 SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
165 SDL_ALSA_SYM(snd_pcm_sw_params);
166 SDL_ALSA_SYM(snd_pcm_nonblock);
167 SDL_ALSA_SYM(snd_pcm_wait);
168 SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
169 SDL_ALSA_SYM(snd_pcm_reset);
170 SDL_ALSA_SYM(snd_device_name_hint);
171 SDL_ALSA_SYM(snd_device_name_get_hint);
172 SDL_ALSA_SYM(snd_device_name_free_hint);
173 SDL_ALSA_SYM(snd_pcm_avail);
174 #ifdef SND_CHMAP_API_VERSION
175 SDL_ALSA_SYM(snd_pcm_get_chmap);
176 SDL_ALSA_SYM(snd_pcm_chmap_print);
177 #endif
178
179 return 0;
180 }
181
182 #undef SDL_ALSA_SYM
183
184 #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
185
186 static void
UnloadALSALibrary(void)187 UnloadALSALibrary(void)
188 {
189 if (alsa_handle != NULL) {
190 SDL_UnloadObject(alsa_handle);
191 alsa_handle = NULL;
192 }
193 }
194
195 static int
LoadALSALibrary(void)196 LoadALSALibrary(void)
197 {
198 int retval = 0;
199 if (alsa_handle == NULL) {
200 alsa_handle = SDL_LoadObject(alsa_library);
201 if (alsa_handle == NULL) {
202 retval = -1;
203 /* Don't call SDL_SetError(): SDL_LoadObject already did. */
204 } else {
205 retval = load_alsa_syms();
206 if (retval < 0) {
207 UnloadALSALibrary();
208 }
209 }
210 }
211 return retval;
212 }
213
214 #else
215
216 static void
UnloadALSALibrary(void)217 UnloadALSALibrary(void)
218 {
219 }
220
221 static int
LoadALSALibrary(void)222 LoadALSALibrary(void)
223 {
224 load_alsa_syms();
225 return 0;
226 }
227
228 #endif /* SDL_AUDIO_DRIVER_ALSA_DYNAMIC */
229
230 static const char *
get_audio_device(void * handle,const int channels)231 get_audio_device(void *handle, const int channels)
232 {
233 const char *device;
234
235 if (handle != NULL) {
236 return (const char *) handle;
237 }
238
239 /* !!! FIXME: we also check "SDL_AUDIO_DEVICE_NAME" at the higher level. */
240 device = SDL_getenv("AUDIODEV"); /* Is there a standard variable name? */
241 if (device != NULL) {
242 return device;
243 }
244
245 if (channels == 6) {
246 return "plug:surround51";
247 } else if (channels == 4) {
248 return "plug:surround40";
249 }
250
251 return "default";
252 }
253
254
255 /* This function waits until it is possible to write a full sound buffer */
256 static void
ALSA_WaitDevice(_THIS)257 ALSA_WaitDevice(_THIS)
258 {
259 #if SDL_ALSA_NON_BLOCKING
260 const snd_pcm_sframes_t needed = (snd_pcm_sframes_t) this->spec.samples;
261 while (SDL_AtomicGet(&this->enabled)) {
262 const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(this->hidden->pcm_handle);
263 if ((rc < 0) && (rc != -EAGAIN)) {
264 /* Hmm, not much we can do - abort */
265 fprintf(stderr, "ALSA snd_pcm_avail failed (unrecoverable): %s\n",
266 ALSA_snd_strerror(rc));
267 SDL_OpenedAudioDeviceDisconnected(this);
268 return;
269 } else if (rc < needed) {
270 const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / this->spec.freq;
271 SDL_Delay(SDL_max(delay, 10));
272 } else {
273 break; /* ready to go! */
274 }
275 }
276 #endif
277 }
278
279
280 /* !!! FIXME: is there a channel swizzler in alsalib instead? */
281 /*
282 * https://bugzilla.libsdl.org/show_bug.cgi?id=110
283 * "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
284 * and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
285 */
286 #define SWIZ6(T) \
287 static void swizzle_alsa_channels_6_##T(void *buffer, const Uint32 bufferlen) { \
288 T *ptr = (T *) buffer; \
289 Uint32 i; \
290 for (i = 0; i < bufferlen; i++, ptr += 6) { \
291 T tmp; \
292 tmp = ptr[2]; ptr[2] = ptr[4]; ptr[4] = tmp; \
293 tmp = ptr[3]; ptr[3] = ptr[5]; ptr[5] = tmp; \
294 } \
295 }
296
297
298 /* !!! FIXME: is there a channel swizzler in alsalib instead? */
299 /* !!! FIXME: this screams for a SIMD shuffle operation. */
300 /*
301 * https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations
302 * For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR
303 * and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR"
304 */
305 #define SWIZ8(T) \
306 static void swizzle_alsa_channels_8_##T(void *buffer, const Uint32 bufferlen) { \
307 T *ptr = (T *) buffer; \
308 Uint32 i; \
309 for (i = 0; i < bufferlen; i++, ptr += 6) { \
310 const T center = ptr[2]; \
311 const T subwoofer = ptr[3]; \
312 const T side_left = ptr[4]; \
313 const T side_right = ptr[5]; \
314 const T rear_left = ptr[6]; \
315 const T rear_right = ptr[7]; \
316 ptr[2] = rear_left; \
317 ptr[3] = rear_right; \
318 ptr[4] = center; \
319 ptr[5] = subwoofer; \
320 ptr[6] = side_left; \
321 ptr[7] = side_right; \
322 } \
323 }
324
325 #define CHANNEL_SWIZZLE(x) \
326 x(Uint64) \
327 x(Uint32) \
328 x(Uint16) \
329 x(Uint8)
330
331 CHANNEL_SWIZZLE(SWIZ6)
CHANNEL_SWIZZLE(SWIZ8)332 CHANNEL_SWIZZLE(SWIZ8)
333
334 #undef CHANNEL_SWIZZLE
335 #undef SWIZ6
336 #undef SWIZ8
337
338
339 /*
340 * Called right before feeding this->hidden->mixbuf to the hardware. Swizzle
341 * channels from Windows/Mac order to the format alsalib will want.
342 */
343 static void
344 swizzle_alsa_channels(_THIS, void *buffer, Uint32 bufferlen)
345 {
346 switch (this->spec.channels) {
347 #define CHANSWIZ(chans) \
348 case chans: \
349 switch ((this->spec.format & (0xFF))) { \
350 case 8: swizzle_alsa_channels_##chans##_Uint8(buffer, bufferlen); break; \
351 case 16: swizzle_alsa_channels_##chans##_Uint16(buffer, bufferlen); break; \
352 case 32: swizzle_alsa_channels_##chans##_Uint32(buffer, bufferlen); break; \
353 case 64: swizzle_alsa_channels_##chans##_Uint64(buffer, bufferlen); break; \
354 default: SDL_assert(!"unhandled bitsize"); break; \
355 } \
356 return;
357
358 CHANSWIZ(6);
359 CHANSWIZ(8);
360 #undef CHANSWIZ
361 default: break;
362 }
363 }
364
365 #ifdef SND_CHMAP_API_VERSION
366 /* Some devices have the right channel map, no swizzling necessary */
367 static void
no_swizzle(_THIS,void * buffer,Uint32 bufferlen)368 no_swizzle(_THIS, void *buffer, Uint32 bufferlen)
369 {
370 }
371 #endif /* SND_CHMAP_API_VERSION */
372
373
374 static void
ALSA_PlayDevice(_THIS)375 ALSA_PlayDevice(_THIS)
376 {
377 const Uint8 *sample_buf = (const Uint8 *) this->hidden->mixbuf;
378 const int frame_size = ((SDL_AUDIO_BITSIZE(this->spec.format)) / 8) *
379 this->spec.channels;
380 snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t) this->spec.samples);
381
382 this->hidden->swizzle_func(this, this->hidden->mixbuf, frames_left);
383
384 while ( frames_left > 0 && SDL_AtomicGet(&this->enabled) ) {
385 int status = ALSA_snd_pcm_writei(this->hidden->pcm_handle,
386 sample_buf, frames_left);
387
388 if (status < 0) {
389 if (status == -EAGAIN) {
390 /* Apparently snd_pcm_recover() doesn't handle this case -
391 does it assume snd_pcm_wait() above? */
392 SDL_Delay(1);
393 continue;
394 }
395 status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
396 if (status < 0) {
397 /* Hmm, not much we can do - abort */
398 fprintf(stderr, "ALSA write failed (unrecoverable): %s\n",
399 ALSA_snd_strerror(status));
400 SDL_OpenedAudioDeviceDisconnected(this);
401 return;
402 }
403 continue;
404 }
405 else if (status == 0) {
406 /* No frames were written (no available space in pcm device).
407 Allow other threads to catch up. */
408 Uint32 delay = (frames_left / 2 * 1000) / this->spec.freq;
409 SDL_Delay(delay);
410 }
411
412 sample_buf += status * frame_size;
413 frames_left -= status;
414 }
415 }
416
417 static Uint8 *
ALSA_GetDeviceBuf(_THIS)418 ALSA_GetDeviceBuf(_THIS)
419 {
420 return (this->hidden->mixbuf);
421 }
422
423 static int
ALSA_CaptureFromDevice(_THIS,void * buffer,int buflen)424 ALSA_CaptureFromDevice(_THIS, void *buffer, int buflen)
425 {
426 Uint8 *sample_buf = (Uint8 *) buffer;
427 const int frame_size = ((SDL_AUDIO_BITSIZE(this->spec.format)) / 8) *
428 this->spec.channels;
429 const int total_frames = buflen / frame_size;
430 snd_pcm_uframes_t frames_left = total_frames;
431 snd_pcm_uframes_t wait_time = frame_size / 2;
432
433 SDL_assert((buflen % frame_size) == 0);
434
435 while ( frames_left > 0 && SDL_AtomicGet(&this->enabled) ) {
436 int status;
437
438 status = ALSA_snd_pcm_readi(this->hidden->pcm_handle,
439 sample_buf, frames_left);
440
441 if (status == -EAGAIN) {
442 ALSA_snd_pcm_wait(this->hidden->pcm_handle, wait_time);
443 status = 0;
444 }
445 else if (status < 0) {
446 /*printf("ALSA: capture error %d\n", status);*/
447 status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
448 if (status < 0) {
449 /* Hmm, not much we can do - abort */
450 fprintf(stderr, "ALSA read failed (unrecoverable): %s\n",
451 ALSA_snd_strerror(status));
452 return -1;
453 }
454 continue;
455 }
456
457 /*printf("ALSA: captured %d bytes\n", status * frame_size);*/
458 sample_buf += status * frame_size;
459 frames_left -= status;
460 }
461
462 this->hidden->swizzle_func(this, buffer, total_frames - frames_left);
463
464 return (total_frames - frames_left) * frame_size;
465 }
466
467 static void
ALSA_FlushCapture(_THIS)468 ALSA_FlushCapture(_THIS)
469 {
470 ALSA_snd_pcm_reset(this->hidden->pcm_handle);
471 }
472
473 static void
ALSA_CloseDevice(_THIS)474 ALSA_CloseDevice(_THIS)
475 {
476 if (this->hidden->pcm_handle) {
477 /* Wait for the submitted audio to drain
478 ALSA_snd_pcm_drop() can hang, so don't use that.
479 */
480 Uint32 delay = ((this->spec.samples * 1000) / this->spec.freq) * 2;
481 SDL_Delay(delay);
482
483 ALSA_snd_pcm_close(this->hidden->pcm_handle);
484 }
485 SDL_free(this->hidden->mixbuf);
486 SDL_free(this->hidden);
487 }
488
489 static int
ALSA_set_buffer_size(_THIS,snd_pcm_hw_params_t * params)490 ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params)
491 {
492 int status;
493 snd_pcm_hw_params_t *hwparams;
494 snd_pcm_uframes_t persize;
495 unsigned int periods;
496
497 /* Copy the hardware parameters for this setup */
498 snd_pcm_hw_params_alloca(&hwparams);
499 ALSA_snd_pcm_hw_params_copy(hwparams, params);
500
501 /* Attempt to match the period size to the requested buffer size */
502 persize = this->spec.samples;
503 status = ALSA_snd_pcm_hw_params_set_period_size_near(
504 this->hidden->pcm_handle, hwparams, &persize, NULL);
505 if ( status < 0 ) {
506 return(-1);
507 }
508
509 /* Need to at least double buffer */
510 periods = 2;
511 status = ALSA_snd_pcm_hw_params_set_periods_min(
512 this->hidden->pcm_handle, hwparams, &periods, NULL);
513 if ( status < 0 ) {
514 return(-1);
515 }
516
517 status = ALSA_snd_pcm_hw_params_set_periods_first(
518 this->hidden->pcm_handle, hwparams, &periods, NULL);
519 if ( status < 0 ) {
520 return(-1);
521 }
522
523 /* "set" the hardware with the desired parameters */
524 status = ALSA_snd_pcm_hw_params(this->hidden->pcm_handle, hwparams);
525 if ( status < 0 ) {
526 return(-1);
527 }
528
529 this->spec.samples = persize;
530
531 /* This is useful for debugging */
532 if ( SDL_getenv("SDL_AUDIO_ALSA_DEBUG") ) {
533 snd_pcm_uframes_t bufsize;
534
535 ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
536
537 fprintf(stderr,
538 "ALSA: period size = %ld, periods = %u, buffer size = %lu\n",
539 persize, periods, bufsize);
540 }
541
542 return(0);
543 }
544
545 static int
ALSA_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)546 ALSA_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
547 {
548 int status = 0;
549 snd_pcm_t *pcm_handle = NULL;
550 snd_pcm_hw_params_t *hwparams = NULL;
551 snd_pcm_sw_params_t *swparams = NULL;
552 snd_pcm_format_t format = 0;
553 SDL_AudioFormat test_format = 0;
554 unsigned int rate = 0;
555 unsigned int channels = 0;
556 #ifdef SND_CHMAP_API_VERSION
557 snd_pcm_chmap_t *chmap;
558 char chmap_str[64];
559 #endif
560
561 /* Initialize all variables that we clean on shutdown */
562 this->hidden = (struct SDL_PrivateAudioData *)
563 SDL_malloc((sizeof *this->hidden));
564 if (this->hidden == NULL) {
565 return SDL_OutOfMemory();
566 }
567 SDL_zerop(this->hidden);
568
569 /* Open the audio device */
570 /* Name of device should depend on # channels in spec */
571 status = ALSA_snd_pcm_open(&pcm_handle,
572 get_audio_device(handle, this->spec.channels),
573 iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
574 SND_PCM_NONBLOCK);
575
576 if (status < 0) {
577 return SDL_SetError("ALSA: Couldn't open audio device: %s",
578 ALSA_snd_strerror(status));
579 }
580
581 this->hidden->pcm_handle = pcm_handle;
582
583 /* Figure out what the hardware is capable of */
584 snd_pcm_hw_params_alloca(&hwparams);
585 status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
586 if (status < 0) {
587 return SDL_SetError("ALSA: Couldn't get hardware config: %s",
588 ALSA_snd_strerror(status));
589 }
590
591 /* SDL only uses interleaved sample output */
592 status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
593 SND_PCM_ACCESS_RW_INTERLEAVED);
594 if (status < 0) {
595 return SDL_SetError("ALSA: Couldn't set interleaved access: %s",
596 ALSA_snd_strerror(status));
597 }
598
599 /* Try for a closest match on audio format */
600 status = -1;
601 for (test_format = SDL_FirstAudioFormat(this->spec.format);
602 test_format && (status < 0);) {
603 status = 0; /* if we can't support a format, it'll become -1. */
604 switch (test_format) {
605 case AUDIO_U8:
606 format = SND_PCM_FORMAT_U8;
607 break;
608 case AUDIO_S8:
609 format = SND_PCM_FORMAT_S8;
610 break;
611 case AUDIO_S16LSB:
612 format = SND_PCM_FORMAT_S16_LE;
613 break;
614 case AUDIO_S16MSB:
615 format = SND_PCM_FORMAT_S16_BE;
616 break;
617 case AUDIO_U16LSB:
618 format = SND_PCM_FORMAT_U16_LE;
619 break;
620 case AUDIO_U16MSB:
621 format = SND_PCM_FORMAT_U16_BE;
622 break;
623 case AUDIO_S32LSB:
624 format = SND_PCM_FORMAT_S32_LE;
625 break;
626 case AUDIO_S32MSB:
627 format = SND_PCM_FORMAT_S32_BE;
628 break;
629 case AUDIO_F32LSB:
630 format = SND_PCM_FORMAT_FLOAT_LE;
631 break;
632 case AUDIO_F32MSB:
633 format = SND_PCM_FORMAT_FLOAT_BE;
634 break;
635 default:
636 status = -1;
637 break;
638 }
639 if (status >= 0) {
640 status = ALSA_snd_pcm_hw_params_set_format(pcm_handle,
641 hwparams, format);
642 }
643 if (status < 0) {
644 test_format = SDL_NextAudioFormat();
645 }
646 }
647 if (status < 0) {
648 return SDL_SetError("ALSA: Couldn't find any hardware audio formats");
649 }
650 this->spec.format = test_format;
651
652 /* Validate number of channels and determine if swizzling is necessary
653 * Assume original swizzling, until proven otherwise.
654 */
655 this->hidden->swizzle_func = swizzle_alsa_channels;
656 #ifdef SND_CHMAP_API_VERSION
657 chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
658 if (chmap) {
659 if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) {
660 if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 ||
661 SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) {
662 this->hidden->swizzle_func = no_swizzle;
663 }
664 }
665 free(chmap);
666 }
667 #endif /* SND_CHMAP_API_VERSION */
668
669 /* Set the number of channels */
670 status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
671 this->spec.channels);
672 channels = this->spec.channels;
673 if (status < 0) {
674 status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
675 if (status < 0) {
676 return SDL_SetError("ALSA: Couldn't set audio channels");
677 }
678 this->spec.channels = channels;
679 }
680
681 /* Set the audio rate */
682 rate = this->spec.freq;
683 status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
684 &rate, NULL);
685 if (status < 0) {
686 return SDL_SetError("ALSA: Couldn't set audio frequency: %s",
687 ALSA_snd_strerror(status));
688 }
689 this->spec.freq = rate;
690
691 /* Set the buffer size, in samples */
692 status = ALSA_set_buffer_size(this, hwparams);
693 if (status < 0) {
694 return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
695 }
696
697 /* Set the software parameters */
698 snd_pcm_sw_params_alloca(&swparams);
699 status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
700 if (status < 0) {
701 return SDL_SetError("ALSA: Couldn't get software config: %s",
702 ALSA_snd_strerror(status));
703 }
704 status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, this->spec.samples);
705 if (status < 0) {
706 return SDL_SetError("Couldn't set minimum available samples: %s",
707 ALSA_snd_strerror(status));
708 }
709 status =
710 ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
711 if (status < 0) {
712 return SDL_SetError("ALSA: Couldn't set start threshold: %s",
713 ALSA_snd_strerror(status));
714 }
715 status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
716 if (status < 0) {
717 return SDL_SetError("Couldn't set software audio parameters: %s",
718 ALSA_snd_strerror(status));
719 }
720
721 /* Calculate the final parameters for this audio specification */
722 SDL_CalculateAudioSpec(&this->spec);
723
724 /* Allocate mixing buffer */
725 if (!iscapture) {
726 this->hidden->mixlen = this->spec.size;
727 this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->hidden->mixlen);
728 if (this->hidden->mixbuf == NULL) {
729 return SDL_OutOfMemory();
730 }
731 SDL_memset(this->hidden->mixbuf, this->spec.silence, this->hidden->mixlen);
732 }
733
734 #if !SDL_ALSA_NON_BLOCKING
735 if (!iscapture) {
736 ALSA_snd_pcm_nonblock(pcm_handle, 0);
737 }
738 #endif
739
740 /* We're ready to rock and roll. :-) */
741 return 0;
742 }
743
744 typedef struct ALSA_Device
745 {
746 char *name;
747 SDL_bool iscapture;
748 struct ALSA_Device *next;
749 } ALSA_Device;
750
751 static void
add_device(const int iscapture,const char * name,void * hint,ALSA_Device ** pSeen)752 add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen)
753 {
754 ALSA_Device *dev = SDL_malloc(sizeof (ALSA_Device));
755 char *desc;
756 char *handle = NULL;
757 char *ptr;
758
759 if (!dev) {
760 return;
761 }
762
763 /* Not all alsa devices are enumerable via snd_device_name_get_hint
764 (i.e. bluetooth devices). Therefore if hint is passed in to this
765 function as NULL, assume name contains desc.
766 Make sure not to free the storage associated with desc in this case */
767 if (hint) {
768 desc = ALSA_snd_device_name_get_hint(hint, "DESC");
769 if (!desc) {
770 SDL_free(dev);
771 return;
772 }
773 } else {
774 desc = (char *) name;
775 }
776
777 SDL_assert(name != NULL);
778
779 /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
780 just chop the extra lines off, this seems to get a reasonable device
781 name without extra details. */
782 if ((ptr = SDL_strchr(desc, '\n')) != NULL) {
783 *ptr = '\0';
784 }
785
786 /*printf("ALSA: adding %s device '%s' (%s)\n", iscapture ? "capture" : "output", name, desc);*/
787
788 handle = SDL_strdup(name);
789 if (!handle) {
790 if (hint) {
791 free(desc);
792 }
793 SDL_free(dev);
794 return;
795 }
796
797 /* Note that spec is NULL, because we are required to open the device before
798 * acquiring the mix format, making this information inaccessible at
799 * enumeration time
800 */
801 SDL_AddAudioDevice(iscapture, desc, NULL, handle);
802 if (hint)
803 free(desc);
804 dev->name = handle;
805 dev->iscapture = iscapture;
806 dev->next = *pSeen;
807 *pSeen = dev;
808 }
809
810
811 static ALSA_Device *hotplug_devices = NULL;
812
813 static void
ALSA_HotplugIteration(void)814 ALSA_HotplugIteration(void)
815 {
816 void **hints = NULL;
817 ALSA_Device *dev;
818 ALSA_Device *unseen;
819 ALSA_Device *seen;
820 ALSA_Device *next;
821 ALSA_Device *prev;
822
823 if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) {
824 int i, j;
825 const char *match = NULL;
826 int bestmatch = 0xFFFF;
827 size_t match_len = 0;
828 int defaultdev = -1;
829 static const char * const prefixes[] = {
830 "hw:", "sysdefault:", "default:", NULL
831 };
832
833 unseen = hotplug_devices;
834 seen = NULL;
835
836 /* Apparently there are several different ways that ALSA lists
837 actual hardware. It could be prefixed with "hw:" or "default:"
838 or "sysdefault:" and maybe others. Go through the list and see
839 if we can find a preferred prefix for the system. */
840 for (i = 0; hints[i]; i++) {
841 char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
842 if (!name) {
843 continue;
844 }
845
846 /* full name, not a prefix */
847 if ((defaultdev == -1) && (SDL_strcmp(name, "default") == 0)) {
848 defaultdev = i;
849 }
850
851 for (j = 0; prefixes[j]; j++) {
852 const char *prefix = prefixes[j];
853 const size_t prefixlen = SDL_strlen(prefix);
854 if (SDL_strncmp(name, prefix, prefixlen) == 0) {
855 if (j < bestmatch) {
856 bestmatch = j;
857 match = prefix;
858 match_len = prefixlen;
859 }
860 }
861 }
862
863 free(name);
864 }
865
866 /* look through the list of device names to find matches */
867 for (i = 0; hints[i]; i++) {
868 char *name;
869
870 /* if we didn't find a device name prefix we like at all... */
871 if ((!match) && (defaultdev != i)) {
872 continue; /* ...skip anything that isn't the default device. */
873 }
874
875 name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
876 if (!name) {
877 continue;
878 }
879
880 /* only want physical hardware interfaces */
881 if (!match || (SDL_strncmp(name, match, match_len) == 0)) {
882 char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
883 const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0);
884 const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0);
885 SDL_bool have_output = SDL_FALSE;
886 SDL_bool have_input = SDL_FALSE;
887
888 free(ioid);
889
890 if (!isoutput && !isinput) {
891 free(name);
892 continue;
893 }
894
895 prev = NULL;
896 for (dev = unseen; dev; dev = next) {
897 next = dev->next;
898 if ( (SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture)) ) {
899 if (prev) {
900 prev->next = next;
901 } else {
902 unseen = next;
903 }
904 dev->next = seen;
905 seen = dev;
906 if (isinput) have_input = SDL_TRUE;
907 if (isoutput) have_output = SDL_TRUE;
908 } else {
909 prev = dev;
910 }
911 }
912
913 if (isinput && !have_input) {
914 add_device(SDL_TRUE, name, hints[i], &seen);
915 }
916 if (isoutput && !have_output) {
917 add_device(SDL_FALSE, name, hints[i], &seen);
918 }
919 }
920
921 free(name);
922 }
923
924 ALSA_snd_device_name_free_hint(hints);
925
926 hotplug_devices = seen; /* now we have a known-good list of attached devices. */
927
928 /* report anything still in unseen as removed. */
929 for (dev = unseen; dev; dev = next) {
930 /*printf("ALSA: removing usb %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
931 next = dev->next;
932 SDL_RemoveAudioDevice(dev->iscapture, dev->name);
933 SDL_free(dev->name);
934 SDL_free(dev);
935 }
936 }
937 }
938
939 #if SDL_ALSA_HOTPLUG_THREAD
940 static SDL_atomic_t ALSA_hotplug_shutdown;
941 static SDL_Thread *ALSA_hotplug_thread;
942
943 static int SDLCALL
ALSA_HotplugThread(void * arg)944 ALSA_HotplugThread(void *arg)
945 {
946 SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW);
947
948 while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
949 /* Block awhile before checking again, unless we're told to stop. */
950 const Uint32 ticks = SDL_GetTicks() + 5000;
951 while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && !SDL_TICKS_PASSED(SDL_GetTicks(), ticks)) {
952 SDL_Delay(100);
953 }
954
955 ALSA_HotplugIteration(); /* run the check. */
956 }
957
958 return 0;
959 }
960 #endif
961
962 static void
ALSA_DetectDevices(void)963 ALSA_DetectDevices(void)
964 {
965 ALSA_HotplugIteration(); /* run once now before a thread continues to check. */
966
967 #if SDL_ALSA_HOTPLUG_THREAD
968 SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
969 ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
970 /* if the thread doesn't spin, oh well, you just don't get further hotplug events. */
971 #endif
972 }
973
974 static void
ALSA_Deinitialize(void)975 ALSA_Deinitialize(void)
976 {
977 ALSA_Device *dev;
978 ALSA_Device *next;
979
980 #if SDL_ALSA_HOTPLUG_THREAD
981 if (ALSA_hotplug_thread != NULL) {
982 SDL_AtomicSet(&ALSA_hotplug_shutdown, 1);
983 SDL_WaitThread(ALSA_hotplug_thread, NULL);
984 ALSA_hotplug_thread = NULL;
985 }
986 #endif
987
988 /* Shutting down! Clean up any data we've gathered. */
989 for (dev = hotplug_devices; dev; dev = next) {
990 /*printf("ALSA: at shutdown, removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
991 next = dev->next;
992 SDL_free(dev->name);
993 SDL_free(dev);
994 }
995 hotplug_devices = NULL;
996
997 UnloadALSALibrary();
998 }
999
1000 static int
ALSA_Init(SDL_AudioDriverImpl * impl)1001 ALSA_Init(SDL_AudioDriverImpl * impl)
1002 {
1003 if (LoadALSALibrary() < 0) {
1004 return 0;
1005 }
1006
1007 /* Set the function pointers */
1008 impl->DetectDevices = ALSA_DetectDevices;
1009 impl->OpenDevice = ALSA_OpenDevice;
1010 impl->WaitDevice = ALSA_WaitDevice;
1011 impl->GetDeviceBuf = ALSA_GetDeviceBuf;
1012 impl->PlayDevice = ALSA_PlayDevice;
1013 impl->CloseDevice = ALSA_CloseDevice;
1014 impl->Deinitialize = ALSA_Deinitialize;
1015 impl->CaptureFromDevice = ALSA_CaptureFromDevice;
1016 impl->FlushCapture = ALSA_FlushCapture;
1017
1018 impl->HasCaptureSupport = SDL_TRUE;
1019
1020 return 1; /* this audio target is available. */
1021 }
1022
1023
1024 AudioBootStrap ALSA_bootstrap = {
1025 "alsa", "ALSA PCM audio", ALSA_Init, 0
1026 };
1027
1028 #endif /* SDL_AUDIO_DRIVER_ALSA */
1029
1030 /* vi: set ts=4 sw=4 expandtab: */
1031