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