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