2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "../../SDL_internal.h"
23 #if SDL_AUDIO_DRIVER_ALSA
25 /* Allow access to a raw mixing buffer */
27 #include <sys/types.h>
28 #include <signal.h> /* For kill() */
32 #include "SDL_timer.h"
33 #include "SDL_audio.h"
34 #include "../SDL_audiomem.h"
35 #include "../SDL_audio_c.h"
36 #include "SDL_alsa_audio.h"
38 #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
39 #include "SDL_loadso.h"
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 int (*ALSA_snd_pcm_recover) (snd_pcm_t *, int, int);
48 static int (*ALSA_snd_pcm_prepare) (snd_pcm_t *);
49 static int (*ALSA_snd_pcm_drain) (snd_pcm_t *);
50 static const char *(*ALSA_snd_strerror) (int);
51 static size_t(*ALSA_snd_pcm_hw_params_sizeof) (void);
52 static size_t(*ALSA_snd_pcm_sw_params_sizeof) (void);
53 static void (*ALSA_snd_pcm_hw_params_copy)
54 (snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
55 static int (*ALSA_snd_pcm_hw_params_any) (snd_pcm_t *, snd_pcm_hw_params_t *);
56 static int (*ALSA_snd_pcm_hw_params_set_access)
57 (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
58 static int (*ALSA_snd_pcm_hw_params_set_format)
59 (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
60 static int (*ALSA_snd_pcm_hw_params_set_channels)
61 (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
62 static int (*ALSA_snd_pcm_hw_params_get_channels)
63 (const snd_pcm_hw_params_t *, unsigned int *);
64 static int (*ALSA_snd_pcm_hw_params_set_rate_near)
65 (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
66 static int (*ALSA_snd_pcm_hw_params_set_period_size_near)
67 (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
68 static int (*ALSA_snd_pcm_hw_params_get_period_size)
69 (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
70 static int (*ALSA_snd_pcm_hw_params_set_periods_near)
71 (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
72 static int (*ALSA_snd_pcm_hw_params_get_periods)
73 (const snd_pcm_hw_params_t *, unsigned int *, int *);
74 static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)
75 (snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
76 static int (*ALSA_snd_pcm_hw_params_get_buffer_size)
77 (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
78 static int (*ALSA_snd_pcm_hw_params) (snd_pcm_t *, snd_pcm_hw_params_t *);
79 static int (*ALSA_snd_pcm_sw_params_current) (snd_pcm_t *,
80 snd_pcm_sw_params_t *);
81 static int (*ALSA_snd_pcm_sw_params_set_start_threshold)
82 (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
83 static int (*ALSA_snd_pcm_sw_params) (snd_pcm_t *, snd_pcm_sw_params_t *);
84 static int (*ALSA_snd_pcm_nonblock) (snd_pcm_t *, int);
85 static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
86 static int (*ALSA_snd_pcm_sw_params_set_avail_min)
87 (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
89 #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
90 #define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
91 #define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
93 static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
94 static void *alsa_handle = NULL;
97 load_alsa_sym(const char *fn, void **addr)
99 *addr = SDL_LoadFunction(alsa_handle, fn);
101 /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
108 /* cast funcs to char* first, to please GCC's strict aliasing rules. */
109 #define SDL_ALSA_SYM(x) \
110 if (!load_alsa_sym(#x, (void **) (char *) &ALSA_##x)) return -1
112 #define SDL_ALSA_SYM(x) ALSA_##x = x
118 SDL_ALSA_SYM(snd_pcm_open);
119 SDL_ALSA_SYM(snd_pcm_close);
120 SDL_ALSA_SYM(snd_pcm_writei);
121 SDL_ALSA_SYM(snd_pcm_recover);
122 SDL_ALSA_SYM(snd_pcm_prepare);
123 SDL_ALSA_SYM(snd_pcm_drain);
124 SDL_ALSA_SYM(snd_strerror);
125 SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
126 SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
127 SDL_ALSA_SYM(snd_pcm_hw_params_copy);
128 SDL_ALSA_SYM(snd_pcm_hw_params_any);
129 SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
130 SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
131 SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
132 SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
133 SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
134 SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
135 SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
136 SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_near);
137 SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
138 SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
139 SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
140 SDL_ALSA_SYM(snd_pcm_hw_params);
141 SDL_ALSA_SYM(snd_pcm_sw_params_current);
142 SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
143 SDL_ALSA_SYM(snd_pcm_sw_params);
144 SDL_ALSA_SYM(snd_pcm_nonblock);
145 SDL_ALSA_SYM(snd_pcm_wait);
146 SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
152 #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
155 UnloadALSALibrary(void)
157 if (alsa_handle != NULL) {
158 SDL_UnloadObject(alsa_handle);
164 LoadALSALibrary(void)
167 if (alsa_handle == NULL) {
168 alsa_handle = SDL_LoadObject(alsa_library);
169 if (alsa_handle == NULL) {
171 /* Don't call SDL_SetError(): SDL_LoadObject already did. */
173 retval = load_alsa_syms();
185 UnloadALSALibrary(void)
190 LoadALSALibrary(void)
196 #endif /* SDL_AUDIO_DRIVER_ALSA_DYNAMIC */
199 get_audio_device(int channels)
203 device = SDL_getenv("AUDIODEV"); /* Is there a standard variable name? */
204 if (device == NULL) {
207 device = "plug:surround51";
210 device = "plug:surround40";
221 /* This function waits until it is possible to write a full sound buffer */
223 ALSA_WaitDevice(_THIS)
225 /* We're in blocking mode, so there's nothing to do here */
229 /* !!! FIXME: is there a channel swizzler in alsalib instead? */
231 * http://bugzilla.libsdl.org/show_bug.cgi?id=110
232 * "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
233 * and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
236 T *ptr = (T *) this->hidden->mixbuf; \
238 for (i = 0; i < this->spec.samples; i++, ptr += 6) { \
240 tmp = ptr[2]; ptr[2] = ptr[4]; ptr[4] = tmp; \
241 tmp = ptr[3]; ptr[3] = ptr[5]; ptr[5] = tmp; \
244 static SDL_INLINE void
245 swizzle_alsa_channels_6_64bit(_THIS)
250 static SDL_INLINE void
251 swizzle_alsa_channels_6_32bit(_THIS)
256 static SDL_INLINE void
257 swizzle_alsa_channels_6_16bit(_THIS)
262 static SDL_INLINE void
263 swizzle_alsa_channels_6_8bit(_THIS)
272 * Called right before feeding this->hidden->mixbuf to the hardware. Swizzle
273 * channels from Windows/Mac order to the format alsalib will want.
275 static SDL_INLINE void
276 swizzle_alsa_channels(_THIS)
278 if (this->spec.channels == 6) {
279 const Uint16 fmtsize = (this->spec.format & 0xFF); /* bits/channel. */
281 swizzle_alsa_channels_6_16bit(this);
282 else if (fmtsize == 8)
283 swizzle_alsa_channels_6_8bit(this);
284 else if (fmtsize == 32)
285 swizzle_alsa_channels_6_32bit(this);
286 else if (fmtsize == 64)
287 swizzle_alsa_channels_6_64bit(this);
290 /* !!! FIXME: update this for 7.1 if needed, later. */
295 ALSA_PlayDevice(_THIS)
298 const Uint8 *sample_buf = (const Uint8 *) this->hidden->mixbuf;
299 const int frame_size = (((int) (this->spec.format & 0xFF)) / 8) *
301 snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t) this->spec.samples);
303 swizzle_alsa_channels(this);
305 while ( frames_left > 0 && this->enabled ) {
306 /* !!! FIXME: This works, but needs more testing before going live */
307 /* ALSA_snd_pcm_wait(this->hidden->pcm_handle, -1); */
308 status = ALSA_snd_pcm_writei(this->hidden->pcm_handle,
309 sample_buf, frames_left);
312 if (status == -EAGAIN) {
313 /* Apparently snd_pcm_recover() doesn't handle this case -
314 does it assume snd_pcm_wait() above? */
318 status = ALSA_snd_pcm_recover(this->hidden->pcm_handle, status, 0);
320 /* Hmm, not much we can do - abort */
321 fprintf(stderr, "ALSA write failed (unrecoverable): %s\n",
322 ALSA_snd_strerror(status));
323 SDL_OpenedAudioDeviceDisconnected(this);
328 sample_buf += status * frame_size;
329 frames_left -= status;
334 ALSA_GetDeviceBuf(_THIS)
336 return (this->hidden->mixbuf);
340 ALSA_CloseDevice(_THIS)
342 if (this->hidden != NULL) {
343 SDL_FreeAudioMem(this->hidden->mixbuf);
344 this->hidden->mixbuf = NULL;
345 if (this->hidden->pcm_handle) {
346 ALSA_snd_pcm_drain(this->hidden->pcm_handle);
347 ALSA_snd_pcm_close(this->hidden->pcm_handle);
348 this->hidden->pcm_handle = NULL;
350 SDL_free(this->hidden);
356 ALSA_finalize_hardware(_THIS, snd_pcm_hw_params_t *hwparams, int override)
359 snd_pcm_uframes_t bufsize;
361 /* "set" the hardware with the desired parameters */
362 status = ALSA_snd_pcm_hw_params(this->hidden->pcm_handle, hwparams);
367 /* Get samples for the actual buffer size */
368 status = ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
372 if ( !override && bufsize != this->spec.samples * 2 ) {
376 /* !!! FIXME: Is this safe to do? */
377 this->spec.samples = bufsize / 2;
379 /* This is useful for debugging */
380 if ( SDL_getenv("SDL_AUDIO_ALSA_DEBUG") ) {
381 snd_pcm_uframes_t persize = 0;
382 unsigned int periods = 0;
384 ALSA_snd_pcm_hw_params_get_period_size(hwparams, &persize, NULL);
385 ALSA_snd_pcm_hw_params_get_periods(hwparams, &periods, NULL);
388 "ALSA: period size = %ld, periods = %u, buffer size = %lu\n",
389 persize, periods, bufsize);
396 ALSA_set_period_size(_THIS, snd_pcm_hw_params_t *params, int override)
400 snd_pcm_hw_params_t *hwparams;
401 snd_pcm_uframes_t frames;
402 unsigned int periods;
404 /* Copy the hardware parameters for this setup */
405 snd_pcm_hw_params_alloca(&hwparams);
406 ALSA_snd_pcm_hw_params_copy(hwparams, params);
409 env = SDL_getenv("SDL_AUDIO_ALSA_SET_PERIOD_SIZE");
411 override = SDL_atoi(env);
412 if ( override == 0 ) {
418 frames = this->spec.samples;
419 status = ALSA_snd_pcm_hw_params_set_period_size_near(
420 this->hidden->pcm_handle, hwparams, &frames, NULL);
426 status = ALSA_snd_pcm_hw_params_set_periods_near(
427 this->hidden->pcm_handle, hwparams, &periods, NULL);
432 return ALSA_finalize_hardware(this, hwparams, override);
436 ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params, int override)
440 snd_pcm_hw_params_t *hwparams;
441 snd_pcm_uframes_t frames;
443 /* Copy the hardware parameters for this setup */
444 snd_pcm_hw_params_alloca(&hwparams);
445 ALSA_snd_pcm_hw_params_copy(hwparams, params);
448 env = SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE");
450 override = SDL_atoi(env);
451 if ( override == 0 ) {
457 frames = this->spec.samples * 2;
458 status = ALSA_snd_pcm_hw_params_set_buffer_size_near(
459 this->hidden->pcm_handle, hwparams, &frames);
464 return ALSA_finalize_hardware(this, hwparams, override);
468 ALSA_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
471 snd_pcm_t *pcm_handle = NULL;
472 snd_pcm_hw_params_t *hwparams = NULL;
473 snd_pcm_sw_params_t *swparams = NULL;
474 snd_pcm_format_t format = 0;
475 SDL_AudioFormat test_format = 0;
476 unsigned int rate = 0;
477 unsigned int channels = 0;
479 /* Initialize all variables that we clean on shutdown */
480 this->hidden = (struct SDL_PrivateAudioData *)
481 SDL_malloc((sizeof *this->hidden));
482 if (this->hidden == NULL) {
483 return SDL_OutOfMemory();
485 SDL_memset(this->hidden, 0, (sizeof *this->hidden));
487 /* Open the audio device */
488 /* Name of device should depend on # channels in spec */
489 status = ALSA_snd_pcm_open(&pcm_handle,
490 get_audio_device(this->spec.channels),
491 SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
494 ALSA_CloseDevice(this);
495 return SDL_SetError("ALSA: Couldn't open audio device: %s",
496 ALSA_snd_strerror(status));
499 this->hidden->pcm_handle = pcm_handle;
501 /* Figure out what the hardware is capable of */
502 snd_pcm_hw_params_alloca(&hwparams);
503 status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
505 ALSA_CloseDevice(this);
506 return SDL_SetError("ALSA: Couldn't get hardware config: %s",
507 ALSA_snd_strerror(status));
510 /* SDL only uses interleaved sample output */
511 status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
512 SND_PCM_ACCESS_RW_INTERLEAVED);
514 ALSA_CloseDevice(this);
515 return SDL_SetError("ALSA: Couldn't set interleaved access: %s",
516 ALSA_snd_strerror(status));
519 /* Try for a closest match on audio format */
521 for (test_format = SDL_FirstAudioFormat(this->spec.format);
522 test_format && (status < 0);) {
523 status = 0; /* if we can't support a format, it'll become -1. */
524 switch (test_format) {
526 format = SND_PCM_FORMAT_U8;
529 format = SND_PCM_FORMAT_S8;
532 format = SND_PCM_FORMAT_S16_LE;
535 format = SND_PCM_FORMAT_S16_BE;
538 format = SND_PCM_FORMAT_U16_LE;
541 format = SND_PCM_FORMAT_U16_BE;
544 format = SND_PCM_FORMAT_S32_LE;
547 format = SND_PCM_FORMAT_S32_BE;
550 format = SND_PCM_FORMAT_FLOAT_LE;
553 format = SND_PCM_FORMAT_FLOAT_BE;
560 status = ALSA_snd_pcm_hw_params_set_format(pcm_handle,
564 test_format = SDL_NextAudioFormat();
568 ALSA_CloseDevice(this);
569 return SDL_SetError("ALSA: Couldn't find any hardware audio formats");
571 this->spec.format = test_format;
573 /* Set the number of channels */
574 status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
575 this->spec.channels);
576 channels = this->spec.channels;
578 status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
580 ALSA_CloseDevice(this);
581 return SDL_SetError("ALSA: Couldn't set audio channels");
583 this->spec.channels = channels;
586 /* Set the audio rate */
587 rate = this->spec.freq;
588 status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
591 ALSA_CloseDevice(this);
592 return SDL_SetError("ALSA: Couldn't set audio frequency: %s",
593 ALSA_snd_strerror(status));
595 this->spec.freq = rate;
597 /* Set the buffer size, in samples */
598 if ( ALSA_set_period_size(this, hwparams, 0) < 0 &&
599 ALSA_set_buffer_size(this, hwparams, 0) < 0 ) {
600 /* Failed to set desired buffer size, do the best you can... */
601 if ( ALSA_set_period_size(this, hwparams, 1) < 0 ) {
602 ALSA_CloseDevice(this);
603 return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
606 /* Set the software parameters */
607 snd_pcm_sw_params_alloca(&swparams);
608 status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
610 ALSA_CloseDevice(this);
611 return SDL_SetError("ALSA: Couldn't get software config: %s",
612 ALSA_snd_strerror(status));
614 status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, this->spec.samples);
616 ALSA_CloseDevice(this);
617 return SDL_SetError("Couldn't set minimum available samples: %s",
618 ALSA_snd_strerror(status));
621 ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
623 ALSA_CloseDevice(this);
624 return SDL_SetError("ALSA: Couldn't set start threshold: %s",
625 ALSA_snd_strerror(status));
627 status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
629 ALSA_CloseDevice(this);
630 return SDL_SetError("Couldn't set software audio parameters: %s",
631 ALSA_snd_strerror(status));
634 /* Calculate the final parameters for this audio specification */
635 SDL_CalculateAudioSpec(&this->spec);
637 /* Allocate mixing buffer */
638 this->hidden->mixlen = this->spec.size;
639 this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen);
640 if (this->hidden->mixbuf == NULL) {
641 ALSA_CloseDevice(this);
642 return SDL_OutOfMemory();
644 SDL_memset(this->hidden->mixbuf, this->spec.silence, this->hidden->mixlen);
646 /* Switch to blocking mode for playback */
647 ALSA_snd_pcm_nonblock(pcm_handle, 0);
649 /* We're ready to rock and roll. :-) */
654 ALSA_Deinitialize(void)
660 ALSA_Init(SDL_AudioDriverImpl * impl)
662 if (LoadALSALibrary() < 0) {
666 /* Set the function pointers */
667 impl->OpenDevice = ALSA_OpenDevice;
668 impl->WaitDevice = ALSA_WaitDevice;
669 impl->GetDeviceBuf = ALSA_GetDeviceBuf;
670 impl->PlayDevice = ALSA_PlayDevice;
671 impl->CloseDevice = ALSA_CloseDevice;
672 impl->Deinitialize = ALSA_Deinitialize;
673 impl->OnlyHasDefaultOutputDevice = 1; /* !!! FIXME: Add device enum! */
675 return 1; /* this audio target is available. */
679 AudioBootStrap ALSA_bootstrap = {
680 "alsa", "ALSA PCM audio", ALSA_Init, 0
683 #endif /* SDL_AUDIO_DRIVER_ALSA */
685 /* vi: set ts=4 sw=4 expandtab: */