This repository has been archived by the owner on Feb 11, 2021. It is now read-only.
/
SDL_alsa_audio.c
622 lines (547 loc) · 18.6 KB
1
2
/*
SDL - Simple DirectMedia Layer
3
Copyright (C) 1997-2004 Sam Lantinga
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Sam Lantinga
20
slouken@libsdl.org
21
*/
22
#include "SDL_config.h"
23
24
25
26
/* Allow access to a raw mixing buffer */
#include <sys/types.h>
27
#include <signal.h> /* For kill() */
28
29
30
#include <dlfcn.h>
#include <errno.h>
#include <string.h>
31
32
#include "SDL_timer.h"
33
34
35
#include "SDL_audio.h"
#include "../SDL_audiomem.h"
#include "../SDL_audio_c.h"
36
37
#include "SDL_alsa_audio.h"
38
39
40
41
/* The tag name used by ALSA audio */
#define DRIVER_NAME "alsa"
42
/* The default ALSA audio driver */
43
#define DEFAULT_DEVICE "default"
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
static int (*ALSA_snd_pcm_open)
(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
static int (*ALSA_snd_pcm_close)(snd_pcm_t * pcm);
static snd_pcm_sframes_t(*ALSA_snd_pcm_writei)
(snd_pcm_t *,const void *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_resume)(snd_pcm_t *);
static int (*ALSA_snd_pcm_prepare)(snd_pcm_t *);
static int (*ALSA_snd_pcm_drain)(snd_pcm_t *);
static const char *(*ALSA_snd_strerror)(int);
static size_t(*ALSA_snd_pcm_hw_params_sizeof)(void);
static size_t(*ALSA_snd_pcm_sw_params_sizeof)(void);
static int (*ALSA_snd_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_hw_params_set_access)
(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
static int (*ALSA_snd_pcm_hw_params_set_format)
(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
static int (*ALSA_snd_pcm_hw_params_set_channels)
(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
static int (*ALSA_snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *);
static unsigned int (*ALSA_snd_pcm_hw_params_set_rate_near)
(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int *);
static snd_pcm_uframes_t (*ALSA_snd_pcm_hw_params_set_period_size_near)
(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t, int *);
static snd_pcm_sframes_t (*ALSA_snd_pcm_hw_params_get_period_size)
(const snd_pcm_hw_params_t *);
static unsigned int (*ALSA_snd_pcm_hw_params_set_periods_near)
(snd_pcm_t *,snd_pcm_hw_params_t *, unsigned int, int *);
static int (*ALSA_snd_pcm_hw_params_get_periods)(snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_sw_params_current)(snd_pcm_t*, snd_pcm_sw_params_t*);
static int (*ALSA_snd_pcm_sw_params_set_start_threshold)
(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_sw_params_set_avail_min)
(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_sw_params)(snd_pcm_t *, snd_pcm_sw_params_t *);
static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int);
#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
85
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
86
87
static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
88
89
static void *alsa_handle = NULL;
90
91
static int
load_alsa_sym(const char *fn, void **addr)
92
{
93
94
95
96
97
98
/*
* !!! FIXME:
* Eventually, this will deal with fallbacks, version changes, and
* missing symbols we can workaround. But for now, it doesn't.
*/
99
100
101
102
#if HAVE_DLVSYM
*addr = dlvsym(alsa_handle, fn, "ALSA_0.9");
if (*addr == NULL)
#endif
103
{
104
105
*addr = dlsym(alsa_handle, fn);
if (*addr == NULL) {
106
SDL_SetError("dlsym('%s') failed: %s", fn, strerror(errno));
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
return 0;
}
}
return 1;
}
/* cast funcs to char* first, to please GCC's strict aliasing rules. */
#define SDL_ALSA_SYM(x) \
if (!load_alsa_sym(#x, (void **) (char *) &ALSA_##x)) return -1
#else
#define SDL_ALSA_SYM(x) ALSA_##x = x
#endif
static int load_alsa_syms(void)
{
SDL_ALSA_SYM(snd_pcm_open);
SDL_ALSA_SYM(snd_pcm_close);
SDL_ALSA_SYM(snd_pcm_writei);
SDL_ALSA_SYM(snd_pcm_resume);
SDL_ALSA_SYM(snd_pcm_prepare);
SDL_ALSA_SYM(snd_pcm_drain);
SDL_ALSA_SYM(snd_strerror);
SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
SDL_ALSA_SYM(snd_pcm_hw_params_any);
SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_near);
SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
SDL_ALSA_SYM(snd_pcm_hw_params);
SDL_ALSA_SYM(snd_pcm_sw_params_current);
SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
SDL_ALSA_SYM(snd_pcm_sw_params);
SDL_ALSA_SYM(snd_pcm_nonblock);
return 0;
}
150
#undef SDL_ALSA_SYM
151
152
153
154
155
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
static int library_load_count = 0;
156
157
158
static void
UnloadALSALibrary(void)
{
159
if ((alsa_handle != NULL) && (--library_load_count == 0)) {
160
161
162
dlclose(alsa_handle);
alsa_handle = NULL;
}
163
164
}
165
166
167
static int
LoadALSALibrary(void)
{
168
169
170
171
172
173
174
175
176
177
178
179
180
int retval = 0;
if (library_load_count++ == 0) {
alsa_handle = dlopen(alsa_library, RTLD_NOW);
if (alsa_handle == NULL) {
library_load_count--;
retval = -1;
SDL_SetError("ALSA: dlopen('%s') failed: %s\n",
alsa_library, strerror(errno));
} else {
retval = load_alsa_syms();
if (retval < 0) {
UnloadALSALibrary();
}
181
182
183
}
}
return retval;
184
185
186
187
}
#else
188
189
190
static void
UnloadALSALibrary(void)
{
191
192
}
193
194
195
static int
LoadALSALibrary(void)
{
196
load_alsa_syms();
197
return 0;
198
199
}
200
#endif /* SDL_AUDIO_DRIVER_ALSA_DYNAMIC */
201
202
203
static const char *
get_audio_device(int channels)
204
{
205
206
207
208
209
210
211
212
213
214
215
216
const char *device;
device = SDL_getenv("AUDIODEV"); /* Is there a standard variable name? */
if (device == NULL) {
if (channels == 6)
device = "surround51";
else if (channels == 4)
device = "surround40";
else
device = DEFAULT_DEVICE;
}
return device;
217
218
219
}
220
static int
221
ALSA_Available(void)
222
{
223
int available = 0;
224
225
if (LoadALSALibrary() >= 0) {
226
available = 1;
227
UnloadALSALibrary();
228
229
}
return (available);
230
231
}
232
233
234
/* This function waits until it is possible to write a full sound buffer */
235
static void
236
ALSA_WaitDevice(_THIS)
237
{
238
239
240
241
242
243
/* Check to see if the thread-parent process is still alive */
{
static int cnt = 0;
/* Note that this only works with thread implementations
that use a different process id for each thread.
*/
244
245
246
/* Check every 10 loops */
if (this->hidden->parent && (((++cnt) % 10) == 0)) {
if (kill(this->hidden->parent, 0) < 0) {
247
248
249
250
this->enabled = 0;
}
}
}
251
252
}
253
254
/* !!! FIXME: is there a channel swizzler in alsalib instead? */
255
256
257
258
259
260
/*
* http://bugzilla.libsdl.org/show_bug.cgi?id=110
* "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
* and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
*/
#define SWIZ6(T) \
261
T *ptr = (T *) this->hidden->mixbuf; \
262
263
264
265
266
267
268
269
const Uint32 count = (this->spec.samples / 6); \
Uint32 i; \
for (i = 0; i < count; i++, ptr += 6) { \
T tmp; \
tmp = ptr[2]; ptr[2] = ptr[4]; ptr[4] = tmp; \
tmp = ptr[3]; ptr[3] = ptr[5]; ptr[5] = tmp; \
}
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
static __inline__ void
swizzle_alsa_channels_6_64bit(_THIS)
{
SWIZ6(Uint64);
}
static __inline__ void
swizzle_alsa_channels_6_32bit(_THIS)
{
SWIZ6(Uint32);
}
static __inline__ void
swizzle_alsa_channels_6_16bit(_THIS)
{
SWIZ6(Uint16);
}
static __inline__ void
swizzle_alsa_channels_6_8bit(_THIS)
{
SWIZ6(Uint8);
}
290
291
292
293
294
#undef SWIZ6
/*
295
296
* Called right before feeding this->hidden->mixbuf to the hardware. Swizzle
* channels from Windows/Mac order to the format alsalib will want.
297
*/
298
299
static __inline__ void
swizzle_alsa_channels(_THIS)
300
301
{
if (this->spec.channels == 6) {
302
const Uint16 fmtsize = (this->spec.format & 0xFF); /* bits/channel. */
303
304
305
306
307
308
309
310
311
312
313
314
315
316
if (fmtsize == 16)
swizzle_alsa_channels_6_16bit(this);
else if (fmtsize == 8)
swizzle_alsa_channels_6_8bit(this);
else if (fmtsize == 32)
swizzle_alsa_channels_6_32bit(this);
else if (fmtsize == 64)
swizzle_alsa_channels_6_64bit(this);
}
/* !!! FIXME: update this for 7.1 if needed, later. */
}
317
static void
318
ALSA_PlayDevice(_THIS)
319
{
320
321
322
323
324
325
326
int status;
int sample_len;
signed short *sample_buf;
swizzle_alsa_channels(this);
sample_len = this->spec.samples;
327
sample_buf = (signed short *) this->hidden->mixbuf;
328
329
while (sample_len > 0) {
330
331
332
status = ALSA_snd_pcm_writei(this->hidden->pcm_handle,
sample_buf, sample_len);
333
334
335
336
337
338
339
340
if (status < 0) {
if (status == -EAGAIN) {
SDL_Delay(1);
continue;
}
if (status == -ESTRPIPE) {
do {
SDL_Delay(1);
341
status = ALSA_snd_pcm_resume(this->hidden->pcm_handle);
342
343
344
} while (status == -EAGAIN);
}
if (status < 0) {
345
status = ALSA_snd_pcm_prepare(this->hidden->pcm_handle);
346
347
348
349
350
351
352
353
354
355
356
}
if (status < 0) {
/* Hmm, not much we can do - abort */
this->enabled = 0;
return;
}
continue;
}
sample_buf += status * this->spec.channels;
sample_len -= status;
}
357
358
}
359
static Uint8 *
360
ALSA_GetDeviceBuf(_THIS)
361
{
362
return (this->hidden->mixbuf);
363
364
}
365
static void
366
ALSA_CloseDevice(_THIS)
367
{
368
369
370
371
372
373
374
375
376
377
378
379
if (this->hidden != NULL) {
if (this->hidden->mixbuf != NULL) {
SDL_FreeAudioMem(this->hidden->mixbuf);
this->hidden->mixbuf = NULL;
}
if (this->hidden->pcm_handle) {
ALSA_snd_pcm_drain(this->hidden->pcm_handle);
ALSA_snd_pcm_close(this->hidden->pcm_handle);
this->hidden->pcm_handle = NULL;
}
SDL_free(this->hidden);
this->hidden = NULL;
380
UnloadALSALibrary();
381
}
382
383
}
384
static int
385
ALSA_OpenDevice(_THIS, const char *devname, int iscapture)
386
{
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
int status = 0;
snd_pcm_t *pcm_handle = NULL;
snd_pcm_hw_params_t *hwparams = NULL;
snd_pcm_sw_params_t *swparams = NULL;
snd_pcm_format_t format = 0;
snd_pcm_uframes_t frames = 0;
SDL_AudioFormat test_format = 0;
/* Initialize all variables that we clean on shutdown */
this->hidden = (struct SDL_PrivateAudioData *)
SDL_malloc((sizeof *this->hidden));
if (this->hidden == NULL) {
SDL_OutOfMemory();
return 0;
}
SDL_memset(this->hidden, 0, (sizeof *this->hidden));
403
404
405
406
407
408
if (LoadALSALibrary() < 0) {
ALSA_CloseDevice(this);
return 0;
}
409
410
/* Open the audio device */
/* Name of device should depend on # channels in spec */
411
status = ALSA_snd_pcm_open(&pcm_handle,
412
get_audio_device(this->spec.channels),
413
SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
414
415
if (status < 0) {
416
417
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't open audio device: %s",
418
ALSA_snd_strerror(status));
419
return 0;
420
421
}
422
423
this->hidden->pcm_handle = pcm_handle;
424
425
/* Figure out what the hardware is capable of */
snd_pcm_hw_params_alloca(&hwparams);
426
status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
427
if (status < 0) {
428
429
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't get hardware config: %s",
430
ALSA_snd_strerror(status));
431
return 0;
432
433
434
}
/* SDL only uses interleaved sample output */
435
436
status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
437
if (status < 0) {
438
439
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set interleaved access: %s",
440
ALSA_snd_strerror(status));
441
return 0;
442
443
444
445
}
/* Try for a closest match on audio format */
status = -1;
446
for (test_format = SDL_FirstAudioFormat(this->spec.format);
447
test_format && (status < 0);) {
448
status = 0; /* if we can't support a format, it'll become -1. */
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
switch (test_format) {
case AUDIO_U8:
format = SND_PCM_FORMAT_U8;
break;
case AUDIO_S8:
format = SND_PCM_FORMAT_S8;
break;
case AUDIO_S16LSB:
format = SND_PCM_FORMAT_S16_LE;
break;
case AUDIO_S16MSB:
format = SND_PCM_FORMAT_S16_BE;
break;
case AUDIO_U16LSB:
format = SND_PCM_FORMAT_U16_LE;
break;
case AUDIO_U16MSB:
format = SND_PCM_FORMAT_U16_BE;
break;
468
case AUDIO_S32LSB:
469
format = SND_PCM_FORMAT_S32_LE;
470
471
break;
case AUDIO_S32MSB:
472
format = SND_PCM_FORMAT_S32_BE;
473
474
475
476
477
478
479
break;
case AUDIO_F32LSB:
format = SND_PCM_FORMAT_FLOAT_LE;
break;
case AUDIO_F32MSB:
format = SND_PCM_FORMAT_FLOAT_BE;
break;
480
default:
481
status = -1;
482
483
break;
}
484
if (status >= 0) {
485
486
status = ALSA_snd_pcm_hw_params_set_format(pcm_handle,
hwparams, format);
487
488
489
490
491
492
}
if (status < 0) {
test_format = SDL_NextAudioFormat();
}
}
if (status < 0) {
493
494
495
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't find any hardware audio formats");
return 0;
496
}
497
this->spec.format = test_format;
498
499
/* Set the number of channels */
500
status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
501
this->spec.channels);
502
if (status < 0) {
503
status = ALSA_snd_pcm_hw_params_get_channels(hwparams);
504
if ((status <= 0) || (status > 2)) {
505
506
507
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set audio channels");
return 0;
508
}
509
this->spec.channels = status;
510
511
512
}
/* Set the audio rate */
513
status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
514
this->spec.freq, NULL);
515
if (status < 0) {
516
517
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set audio frequency: %s",
518
ALSA_snd_strerror(status));
519
return 0;
520
}
521
this->spec.freq = status;
522
523
/* Set the buffer size, in samples */
524
frames = this->spec.samples;
525
526
frames = ALSA_snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams,
frames, NULL);
527
this->spec.samples = frames;
528
ALSA_snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, 2, NULL);
529
530
/* "set" the hardware with the desired parameters */
531
status = ALSA_snd_pcm_hw_params(pcm_handle, hwparams);
532
if (status < 0) {
533
534
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set hardware audio parameters: %s",
535
ALSA_snd_strerror(status));
536
return 0;
537
}
538
539
540
541
542
543
544
545
#if AUDIO_DEBUG
{
snd_pcm_sframes_t bufsize;
int fragments;
bufsize = ALSA_snd_pcm_hw_params_get_period_size(hwparams);
fragments = ALSA_snd_pcm_hw_params_get_periods(hwparams);
fprintf(stderr,"ALSA: bufsize = %ld, fragments = %d\n",bufsize,fragments);
546
}
547
#endif
548
549
550
/* Set the software parameters */
snd_pcm_sw_params_alloca(&swparams);
551
status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
552
if (status < 0) {
553
554
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't get software config: %s",
555
ALSA_snd_strerror(status));
556
return 0;
557
}
558
status = ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle,swparams,0);
559
if (status < 0) {
560
561
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set start threshold: %s",
562
ALSA_snd_strerror(status));
563
return 0;
564
}
565
status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, frames);
566
if (status < 0) {
567
ALSA_CloseDevice(this);
568
SDL_SetError("Couldn't set avail min: %s", ALSA_snd_strerror(status));
569
return 0;
570
}
571
status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
572
if (status < 0) {
573
ALSA_CloseDevice(this);
574
SDL_SetError("Couldn't set software audio parameters: %s",
575
ALSA_snd_strerror(status));
576
return 0;
577
578
579
}
/* Calculate the final parameters for this audio specification */
580
SDL_CalculateAudioSpec(&this->spec);
581
582
/* Allocate mixing buffer */
583
584
585
586
587
588
this->hidden->mixlen = this->spec.size;
this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen);
if (this->hidden->mixbuf == NULL) {
ALSA_CloseDevice(this);
SDL_OutOfMemory();
return 0;
589
}
590
SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
591
592
/* Get the parent process id (we're the parent of the audio thread) */
593
this->hidden->parent = getpid();
594
595
/* Switch to blocking mode for playback */
596
ALSA_snd_pcm_nonblock(pcm_handle, 0);
597
598
/* We're ready to rock and roll. :-) */
599
return 1;
600
}
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
static int
ALSA_Init(SDL_AudioDriverImpl *impl)
{
/* Set the function pointers */
impl->OpenDevice = ALSA_OpenDevice;
impl->WaitDevice = ALSA_WaitDevice;
impl->GetDeviceBuf = ALSA_GetDeviceBuf;
impl->PlayDevice = ALSA_PlayDevice;
impl->CloseDevice = ALSA_CloseDevice;
impl->OnlyHasDefaultOutputDevice = 1; /* !!! FIXME: Add device enum! */
return 1;
}
AudioBootStrap ALSA_bootstrap = {
DRIVER_NAME, "ALSA 0.9 PCM audio",
ALSA_Available, ALSA_Init, 0
};
622
/* vi: set ts=4 sw=4 expandtab: */