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