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