This repository has been archived by the owner on Feb 11, 2021. It is now read-only.
/
SDL_alsa_audio.c
615 lines (544 loc) · 18.3 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
static int (*ALSA_snd_pcm_open)
46
47
(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
static int (*ALSA_snd_pcm_close) (snd_pcm_t * pcm);
48
static snd_pcm_sframes_t(*ALSA_snd_pcm_writei)
49
50
51
52
53
54
55
56
(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 *);
57
static int (*ALSA_snd_pcm_hw_params_set_access)
58
(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
59
static int (*ALSA_snd_pcm_hw_params_set_format)
60
(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
61
static int (*ALSA_snd_pcm_hw_params_set_channels)
62
63
64
(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
*);
65
static unsigned int (*ALSA_snd_pcm_hw_params_set_rate_near)
66
67
68
69
70
(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 *);
71
static unsigned int (*ALSA_snd_pcm_hw_params_set_periods_near)
72
73
74
75
76
(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 *);
77
static int (*ALSA_snd_pcm_sw_params_set_start_threshold)
78
(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
79
static int (*ALSA_snd_pcm_sw_params_set_avail_min)
80
81
82
(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);
83
84
85
#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
86
87
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
88
89
static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
90
91
static void *alsa_handle = NULL;
92
93
static int
load_alsa_sym(const char *fn, void **addr)
94
{
95
96
97
98
99
100
101
102
103
104
/*
* !!! FIXME:
* Eventually, this will deal with fallbacks, version changes, and
* missing symbols we can workaround. But for now, it doesn't.
*/
#if HAVE_DLVSYM
*addr = dlvsym(alsa_handle, fn, "ALSA_0.9");
if (*addr == NULL)
#endif
105
{
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
*addr = dlsym(alsa_handle, fn);
if (*addr == NULL) {
SDL_SetError("dlsym('%s') failed: %s", fn, strerror(errno));
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
123
124
static int
load_alsa_syms(void)
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
150
151
152
{
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;
}
153
154
155
156
#undef SDL_ALSA_SYM
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
157
158
159
160
static void
UnloadALSALibrary(void)
{
161
if (alsa_handle != NULL) {
162
163
164
dlclose(alsa_handle);
alsa_handle = NULL;
}
165
166
}
167
168
169
static int
LoadALSALibrary(void)
{
170
171
172
173
174
175
int retval = 0;
if (alsa_handle == NULL) {
alsa_handle = dlopen(alsa_library, RTLD_NOW);
if (alsa_handle == NULL) {
retval = -1;
SDL_SetError("ALSA: dlopen('%s') failed: %s\n",
176
alsa_library, strerror(errno));
177
178
179
} else {
retval = load_alsa_syms();
if (retval < 0) {
180
181
182
183
184
UnloadALSALibrary();
}
}
}
return retval;
185
186
187
188
}
#else
189
190
191
static void
UnloadALSALibrary(void)
{
192
193
}
194
195
196
static int
LoadALSALibrary(void)
{
197
load_alsa_syms();
198
return 0;
199
200
}
201
#endif /* SDL_AUDIO_DRIVER_ALSA_DYNAMIC */
202
203
204
static const char *
get_audio_device(int channels)
205
{
206
207
208
209
210
211
212
213
214
215
216
217
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;
218
219
220
221
}
/* This function waits until it is possible to write a full sound buffer */
222
static void
223
ALSA_WaitDevice(_THIS)
224
{
225
226
227
228
229
230
/* 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.
*/
231
232
233
/* Check every 10 loops */
if (this->hidden->parent && (((++cnt) % 10) == 0)) {
if (kill(this->hidden->parent, 0) < 0) {
234
235
236
237
this->enabled = 0;
}
}
}
238
239
}
240
241
/* !!! FIXME: is there a channel swizzler in alsalib instead? */
242
243
244
245
246
247
/*
* 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) \
248
T *ptr = (T *) this->hidden->mixbuf; \
249
250
251
252
253
254
255
256
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; \
}
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
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);
}
277
278
279
280
281
#undef SWIZ6
/*
282
283
* Called right before feeding this->hidden->mixbuf to the hardware. Swizzle
* channels from Windows/Mac order to the format alsalib will want.
284
*/
285
286
static __inline__ void
swizzle_alsa_channels(_THIS)
287
288
{
if (this->spec.channels == 6) {
289
const Uint16 fmtsize = (this->spec.format & 0xFF); /* bits/channel. */
290
291
292
293
294
295
296
297
298
299
300
301
302
303
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. */
}
304
static void
305
ALSA_PlayDevice(_THIS)
306
{
307
308
309
310
311
312
313
int status;
int sample_len;
signed short *sample_buf;
swizzle_alsa_channels(this);
sample_len = this->spec.samples;
314
sample_buf = (signed short *) this->hidden->mixbuf;
315
316
while (sample_len > 0) {
317
318
319
status = ALSA_snd_pcm_writei(this->hidden->pcm_handle,
sample_buf, sample_len);
320
321
322
323
324
325
326
327
if (status < 0) {
if (status == -EAGAIN) {
SDL_Delay(1);
continue;
}
if (status == -ESTRPIPE) {
do {
SDL_Delay(1);
328
status = ALSA_snd_pcm_resume(this->hidden->pcm_handle);
329
330
331
} while (status == -EAGAIN);
}
if (status < 0) {
332
status = ALSA_snd_pcm_prepare(this->hidden->pcm_handle);
333
334
335
336
337
338
339
340
341
342
343
}
if (status < 0) {
/* Hmm, not much we can do - abort */
this->enabled = 0;
return;
}
continue;
}
sample_buf += status * this->spec.channels;
sample_len -= status;
}
344
345
}
346
static Uint8 *
347
ALSA_GetDeviceBuf(_THIS)
348
{
349
return (this->hidden->mixbuf);
350
351
}
352
static void
353
ALSA_CloseDevice(_THIS)
354
{
355
356
357
358
359
360
361
362
363
364
365
366
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;
367
}
368
369
}
370
static int
371
ALSA_OpenDevice(_THIS, const char *devname, int iscapture)
372
{
373
374
375
376
377
378
379
380
381
382
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 *)
383
SDL_malloc((sizeof *this->hidden));
384
385
386
387
388
if (this->hidden == NULL) {
SDL_OutOfMemory();
return 0;
}
SDL_memset(this->hidden, 0, (sizeof *this->hidden));
389
390
391
/* Open the audio device */
/* Name of device should depend on # channels in spec */
392
393
394
status = ALSA_snd_pcm_open(&pcm_handle,
get_audio_device(this->spec.channels),
SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
395
396
if (status < 0) {
397
398
399
400
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't open audio device: %s",
ALSA_snd_strerror(status));
return 0;
401
402
}
403
404
this->hidden->pcm_handle = pcm_handle;
405
406
/* Figure out what the hardware is capable of */
snd_pcm_hw_params_alloca(&hwparams);
407
status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
408
if (status < 0) {
409
410
411
412
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't get hardware config: %s",
ALSA_snd_strerror(status));
return 0;
413
414
415
}
/* SDL only uses interleaved sample output */
416
417
status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
418
if (status < 0) {
419
420
421
422
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set interleaved access: %s",
ALSA_snd_strerror(status));
return 0;
423
424
425
426
}
/* Try for a closest match on audio format */
status = -1;
427
for (test_format = SDL_FirstAudioFormat(this->spec.format);
428
test_format && (status < 0);) {
429
status = 0; /* if we can't support a format, it'll become -1. */
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
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;
449
case AUDIO_S32LSB:
450
format = SND_PCM_FORMAT_S32_LE;
451
452
break;
case AUDIO_S32MSB:
453
format = SND_PCM_FORMAT_S32_BE;
454
455
456
457
458
459
460
break;
case AUDIO_F32LSB:
format = SND_PCM_FORMAT_FLOAT_LE;
break;
case AUDIO_F32MSB:
format = SND_PCM_FORMAT_FLOAT_BE;
break;
461
default:
462
status = -1;
463
464
break;
}
465
if (status >= 0) {
466
467
status = ALSA_snd_pcm_hw_params_set_format(pcm_handle,
hwparams, format);
468
469
470
471
472
473
}
if (status < 0) {
test_format = SDL_NextAudioFormat();
}
}
if (status < 0) {
474
475
476
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't find any hardware audio formats");
return 0;
477
}
478
this->spec.format = test_format;
479
480
/* Set the number of channels */
481
482
status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
this->spec.channels);
483
if (status < 0) {
484
status = ALSA_snd_pcm_hw_params_get_channels(hwparams);
485
if ((status <= 0) || (status > 2)) {
486
487
488
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set audio channels");
return 0;
489
}
490
this->spec.channels = status;
491
492
493
}
/* Set the audio rate */
494
495
status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
this->spec.freq, NULL);
496
if (status < 0) {
497
498
499
500
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set audio frequency: %s",
ALSA_snd_strerror(status));
return 0;
501
}
502
this->spec.freq = status;
503
504
/* Set the buffer size, in samples */
505
506
507
508
509
frames = this->spec.samples;
frames = ALSA_snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams,
frames, NULL);
this->spec.samples = frames;
ALSA_snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, 2, NULL);
510
511
/* "set" the hardware with the desired parameters */
512
status = ALSA_snd_pcm_hw_params(pcm_handle, hwparams);
513
if (status < 0) {
514
515
516
517
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set hardware audio parameters: %s",
ALSA_snd_strerror(status));
return 0;
518
}
519
#if AUDIO_DEBUG
520
521
522
523
524
525
526
527
{
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);
}
528
#endif
529
530
531
/* Set the software parameters */
snd_pcm_sw_params_alloca(&swparams);
532
status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
533
if (status < 0) {
534
535
536
537
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't get software config: %s",
ALSA_snd_strerror(status));
return 0;
538
}
539
540
status =
ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 0);
541
if (status < 0) {
542
543
544
545
ALSA_CloseDevice(this);
SDL_SetError("ALSA: Couldn't set start threshold: %s",
ALSA_snd_strerror(status));
return 0;
546
}
547
548
status =
ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, frames);
549
if (status < 0) {
550
551
552
ALSA_CloseDevice(this);
SDL_SetError("Couldn't set avail min: %s", ALSA_snd_strerror(status));
return 0;
553
}
554
status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
555
if (status < 0) {
556
ALSA_CloseDevice(this);
557
SDL_SetError("Couldn't set software audio parameters: %s",
558
559
ALSA_snd_strerror(status));
return 0;
560
561
562
}
/* Calculate the final parameters for this audio specification */
563
SDL_CalculateAudioSpec(&this->spec);
564
565
/* Allocate mixing buffer */
566
567
568
569
570
571
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;
572
}
573
SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size);
574
575
/* Get the parent process id (we're the parent of the audio thread) */
576
this->hidden->parent = getpid();
577
578
/* Switch to blocking mode for playback */
579
ALSA_snd_pcm_nonblock(pcm_handle, 0);
580
581
/* We're ready to rock and roll. :-) */
582
583
584
585
586
587
588
return 1;
}
static void
ALSA_Deinitialize(void)
{
UnloadALSALibrary();
589
}
590
591
static int
592
ALSA_Init(SDL_AudioDriverImpl * impl)
593
594
595
596
597
598
599
600
601
602
603
604
{
if (LoadALSALibrary() < 0) {
return 0;
}
/* 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->Deinitialize = ALSA_Deinitialize;
605
impl->OnlyHasDefaultOutputDevice = 1; /* !!! FIXME: Add device enum! */
606
607
608
609
610
611
612
613
614
return 1;
}
AudioBootStrap ALSA_bootstrap = {
DRIVER_NAME, "ALSA 0.9 PCM audio", ALSA_Init, 0
};
615
/* vi: set ts=4 sw=4 expandtab: */