1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#ifdef SDL_AUDIO_DRIVER_ALSA
24
25#ifndef SDL_ALSA_NON_BLOCKING
26#define SDL_ALSA_NON_BLOCKING 0
27#endif
28
29// without the thread, you will detect devices on startup, but will not get further hotplug events. But that might be okay.
30#ifndef SDL_ALSA_HOTPLUG_THREAD
31#define SDL_ALSA_HOTPLUG_THREAD 1
32#endif
33
34// this turns off debug logging completely (but by default this goes to the bitbucket).
35#ifndef SDL_ALSA_DEBUG
36#define SDL_ALSA_DEBUG 1
37#endif
38
39#include "../SDL_sysaudio.h"
40#include "SDL_alsa_audio.h"
41
42#if SDL_ALSA_DEBUG
43#define LOGDEBUG(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "ALSA: " __VA_ARGS__)
44#else
45#define LOGDEBUG(...)
46#endif
47
48//TODO: cleanup once the code settled down
49
50static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
51static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm);
52static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm);
53static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t);
54static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t);
55static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int);
56static int (*ALSA_snd_pcm_prepare)(snd_pcm_t *);
57static int (*ALSA_snd_pcm_drain)(snd_pcm_t *);
58static const char *(*ALSA_snd_strerror)(int);
59static size_t (*ALSA_snd_pcm_hw_params_sizeof)(void);
60static size_t (*ALSA_snd_pcm_sw_params_sizeof)(void);
61static void (*ALSA_snd_pcm_hw_params_copy)(snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
62static int (*ALSA_snd_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *);
63static int (*ALSA_snd_pcm_hw_params_set_access)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
64static int (*ALSA_snd_pcm_hw_params_set_format)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
65static int (*ALSA_snd_pcm_hw_params_set_channels)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
66static int (*ALSA_snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *, unsigned int *);
67static int (*ALSA_snd_pcm_hw_params_set_rate_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
68static int (*ALSA_snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
69static int (*ALSA_snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
70static int (*ALSA_snd_pcm_hw_params_set_periods_min)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
71static int (*ALSA_snd_pcm_hw_params_set_periods_first)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
72static int (*ALSA_snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *, unsigned int *, int *);
73static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
74static int (*ALSA_snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
75static int (*ALSA_snd_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *);
76static int (*ALSA_snd_pcm_sw_params_current)(snd_pcm_t *,
77 snd_pcm_sw_params_t *);
78static int (*ALSA_snd_pcm_sw_params_set_start_threshold)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
79static int (*ALSA_snd_pcm_sw_params)(snd_pcm_t *, snd_pcm_sw_params_t *);
80static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int);
81static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
82static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
83static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
84static int (*ALSA_snd_device_name_hint)(int, const char *, void ***);
85static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *);
86static int (*ALSA_snd_device_name_free_hint)(void **);
87static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
88static size_t (*ALSA_snd_ctl_card_info_sizeof)(void);
89static size_t (*ALSA_snd_pcm_info_sizeof)(void);
90static int (*ALSA_snd_card_next)(int*);
91static int (*ALSA_snd_ctl_open)(snd_ctl_t **,const char *,int);
92static int (*ALSA_snd_ctl_close)(snd_ctl_t *);
93static int (*ALSA_snd_ctl_card_info)(snd_ctl_t *, snd_ctl_card_info_t *);
94static int (*ALSA_snd_ctl_pcm_next_device)(snd_ctl_t *, int *);
95static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
96static void (*ALSA_snd_pcm_info_set_device)(snd_pcm_info_t *, unsigned int);
97static void (*ALSA_snd_pcm_info_set_subdevice)(snd_pcm_info_t *, unsigned int);
98static void (*ALSA_snd_pcm_info_set_stream)(snd_pcm_info_t *, snd_pcm_stream_t);
99static int (*ALSA_snd_ctl_pcm_info)(snd_ctl_t *, snd_pcm_info_t *);
100static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
101static const char *(*ALSA_snd_ctl_card_info_get_id)(const snd_ctl_card_info_t *);
102static const char *(*ALSA_snd_pcm_info_get_name)(const snd_pcm_info_t *);
103static const char *(*ALSA_snd_pcm_info_get_subdevice_name)(const snd_pcm_info_t *);
104static const char *(*ALSA_snd_ctl_card_info_get_name)(const snd_ctl_card_info_t *);
105static void (*ALSA_snd_ctl_card_info_clear)(snd_ctl_card_info_t *);
106static int (*ALSA_snd_pcm_hw_free)(snd_pcm_t *);
107static int (*ALSA_snd_pcm_hw_params_set_channels_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *);
108static snd_pcm_chmap_query_t **(*ALSA_snd_pcm_query_chmaps)(snd_pcm_t *pcm);
109static void (*ALSA_snd_pcm_free_chmaps)(snd_pcm_chmap_query_t **maps);
110static int (*ALSA_snd_pcm_set_chmap)(snd_pcm_t *, const snd_pcm_chmap_t *);
111static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *, size_t, char *);
112
113#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
114#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
115#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
116
117static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
118static SDL_SharedObject *alsa_handle = NULL;
119
120static bool load_alsa_sym(const char *fn, void **addr)
121{
122 *addr = SDL_LoadFunction(alsa_handle, fn);
123 if (!*addr) {
124 // Don't call SDL_SetError(): SDL_LoadFunction already did.
125 return false;
126 }
127
128 return true;
129}
130
131// cast funcs to char* first, to please GCC's strict aliasing rules.
132#define SDL_ALSA_SYM(x) \
133 if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \
134 return false
135#else
136#define SDL_ALSA_SYM(x) ALSA_##x = x
137#endif
138
139static bool load_alsa_syms(void)
140{
141 SDL_ALSA_SYM(snd_pcm_open);
142 SDL_ALSA_SYM(snd_pcm_close);
143 SDL_ALSA_SYM(snd_pcm_start);
144 SDL_ALSA_SYM(snd_pcm_writei);
145 SDL_ALSA_SYM(snd_pcm_readi);
146 SDL_ALSA_SYM(snd_pcm_recover);
147 SDL_ALSA_SYM(snd_pcm_prepare);
148 SDL_ALSA_SYM(snd_pcm_drain);
149 SDL_ALSA_SYM(snd_strerror);
150 SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
151 SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
152 SDL_ALSA_SYM(snd_pcm_hw_params_copy);
153 SDL_ALSA_SYM(snd_pcm_hw_params_any);
154 SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
155 SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
156 SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
157 SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
158 SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
159 SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
160 SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
161 SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_min);
162 SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_first);
163 SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
164 SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
165 SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
166 SDL_ALSA_SYM(snd_pcm_hw_params);
167 SDL_ALSA_SYM(snd_pcm_sw_params_current);
168 SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
169 SDL_ALSA_SYM(snd_pcm_sw_params);
170 SDL_ALSA_SYM(snd_pcm_nonblock);
171 SDL_ALSA_SYM(snd_pcm_wait);
172 SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
173 SDL_ALSA_SYM(snd_pcm_reset);
174 SDL_ALSA_SYM(snd_device_name_hint);
175 SDL_ALSA_SYM(snd_device_name_get_hint);
176 SDL_ALSA_SYM(snd_device_name_free_hint);
177 SDL_ALSA_SYM(snd_pcm_avail);
178 SDL_ALSA_SYM(snd_ctl_card_info_sizeof);
179 SDL_ALSA_SYM(snd_pcm_info_sizeof);
180 SDL_ALSA_SYM(snd_card_next);
181 SDL_ALSA_SYM(snd_ctl_open);
182 SDL_ALSA_SYM(snd_ctl_close);
183 SDL_ALSA_SYM(snd_ctl_card_info);
184 SDL_ALSA_SYM(snd_ctl_pcm_next_device);
185 SDL_ALSA_SYM(snd_pcm_info_get_subdevices_count);
186 SDL_ALSA_SYM(snd_pcm_info_set_device);
187 SDL_ALSA_SYM(snd_pcm_info_set_subdevice);
188 SDL_ALSA_SYM(snd_pcm_info_set_stream);
189 SDL_ALSA_SYM(snd_ctl_pcm_info);
190 SDL_ALSA_SYM(snd_pcm_info_get_subdevices_count);
191 SDL_ALSA_SYM(snd_ctl_card_info_get_id);
192 SDL_ALSA_SYM(snd_pcm_info_get_name);
193 SDL_ALSA_SYM(snd_pcm_info_get_subdevice_name);
194 SDL_ALSA_SYM(snd_ctl_card_info_get_name);
195 SDL_ALSA_SYM(snd_ctl_card_info_clear);
196 SDL_ALSA_SYM(snd_pcm_hw_free);
197 SDL_ALSA_SYM(snd_pcm_hw_params_set_channels_near);
198 SDL_ALSA_SYM(snd_pcm_query_chmaps);
199 SDL_ALSA_SYM(snd_pcm_free_chmaps);
200 SDL_ALSA_SYM(snd_pcm_set_chmap);
201 SDL_ALSA_SYM(snd_pcm_chmap_print);
202
203 return true;
204}
205
206#undef SDL_ALSA_SYM
207
208#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
209
210static void UnloadALSALibrary(void)
211{
212 if (alsa_handle) {
213 SDL_UnloadObject(alsa_handle);
214 alsa_handle = NULL;
215 }
216}
217
218static bool LoadALSALibrary(void)
219{
220 bool retval = true;
221 if (!alsa_handle) {
222 alsa_handle = SDL_LoadObject(alsa_library);
223 if (!alsa_handle) {
224 retval = false;
225 // Don't call SDL_SetError(): SDL_LoadObject already did.
226 } else {
227 retval = load_alsa_syms();
228 if (!retval) {
229 UnloadALSALibrary();
230 }
231 }
232 }
233 return retval;
234}
235
236#else
237
238static void UnloadALSALibrary(void)
239{
240}
241
242static bool LoadALSALibrary(void)
243{
244 load_alsa_syms();
245 return true;
246}
247
248#endif // SDL_AUDIO_DRIVER_ALSA_DYNAMIC
249
250static const char *ALSA_device_prefix = NULL;
251static void ALSA_guess_device_prefix(void)
252{
253 if (ALSA_device_prefix) {
254 return; // already calculated.
255 }
256
257 // Apparently there are several different ways that ALSA lists
258 // actual hardware. It could be prefixed with "hw:" or "default:"
259 // or "sysdefault:" and maybe others. Go through the list and see
260 // if we can find a preferred prefix for the system.
261
262 static const char *const prefixes[] = {
263 "hw:", "sysdefault:", "default:"
264 };
265
266 void **hints = NULL;
267 if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) {
268 for (int i = 0; hints[i]; i++) {
269 char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
270 if (name) {
271 for (int j = 0; j < SDL_arraysize(prefixes); j++) {
272 const char *prefix = prefixes[j];
273 const size_t prefixlen = SDL_strlen(prefix);
274 if (SDL_strncmp(name, prefix, prefixlen) == 0) {
275 ALSA_device_prefix = prefix;
276 break;
277 }
278 }
279 free(name); // This should NOT be SDL_free()
280
281 if (ALSA_device_prefix) {
282 break;
283 }
284 }
285 }
286 }
287
288 if (!ALSA_device_prefix) {
289 ALSA_device_prefix = prefixes[0]; // oh well.
290 }
291
292 LOGDEBUG("device prefix is probably '%s'", ALSA_device_prefix);
293}
294
295typedef struct ALSA_Device
296{
297 // the unicity key is the couple (id,recording)
298 char *id; // empty means canonical default
299 char *name;
300 bool recording;
301 struct ALSA_Device *next;
302} ALSA_Device;
303
304static const ALSA_Device default_playback_handle = {
305 "",
306 "default",
307 false,
308 NULL
309};
310
311static const ALSA_Device default_recording_handle = {
312 "",
313 "default",
314 true,
315 NULL
316};
317
318// TODO: Figure out the "right"(TM) way. For the moment we presume that if a system is using a
319// software mixer for application audio sharing which is not the linux native alsa[dmix], for
320// instance jack/pulseaudio2[pipewire]/pulseaudio1/esound/etc, we expect the system integrators did
321// configure the canonical default to the right alsa PCM plugin for their software mixer.
322//
323// All the above may be completely wrong.
324static char *get_pcm_str(void *handle)
325{
326 SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3.
327 ALSA_Device *dev = (ALSA_Device *)handle;
328 char *pcm_str = NULL;
329
330 if (SDL_strlen(dev->id) == 0) {
331 // If the user does not want to go thru the default PCM or the canonical default, the
332 // the configuration space being _massive_, give the user the ability to specify
333 // its own PCMs using environment variables. It will have to fit SDL constraints though.
334 const char *devname = SDL_GetHint(dev->recording ? SDL_HINT_AUDIO_ALSA_DEFAULT_RECORDING_DEVICE : SDL_HINT_AUDIO_ALSA_DEFAULT_PLAYBACK_DEVICE);
335 if (!devname) {
336 devname = SDL_GetHint(SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE);
337 if (!devname) {
338 devname = "default";
339 }
340 }
341 pcm_str = SDL_strdup(devname);
342 } else {
343 SDL_asprintf(&pcm_str, "%sCARD=%s", ALSA_device_prefix, dev->id);
344 }
345 return pcm_str;
346}
347
348// This function waits until it is possible to write a full sound buffer
349static bool ALSA_WaitDevice(SDL_AudioDevice *device)
350{
351 const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq);
352 const int delay = SDL_max(fulldelay, 10);
353
354 while (!SDL_GetAtomicInt(&device->shutdown)) {
355 const int rc = ALSA_snd_pcm_wait(device->hidden->pcm, delay);
356 if (rc < 0 && (rc != -EAGAIN)) {
357 const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
358 if (status < 0) {
359 // Hmm, not much we can do - abort
360 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
361 return false;
362 }
363 continue;
364 }
365
366 if (rc > 0) {
367 break; // ready to go!
368 }
369
370 // Timed out! Make sure we aren't shutting down and then wait again.
371 }
372
373 return true;
374}
375
376static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
377{
378 SDL_assert(buffer == device->hidden->mixbuf);
379 Uint8 *sample_buf = (Uint8 *) buffer; // !!! FIXME: deal with this without casting away constness
380 const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
381 snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size);
382
383 while ((frames_left > 0) && !SDL_GetAtomicInt(&device->shutdown)) {
384 const int rc = ALSA_snd_pcm_writei(device->hidden->pcm, sample_buf, frames_left);
385 //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA PLAYDEVICE: WROTE %d of %d bytes", (rc >= 0) ? ((int) (rc * frame_size)) : rc, (int) (frames_left * frame_size));
386 SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space.
387 if (rc < 0) {
388 SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
389 const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
390 if (status < 0) {
391 // Hmm, not much we can do - abort
392 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc));
393 return false;
394 }
395 continue;
396 }
397
398 sample_buf += rc * frame_size;
399 frames_left -= rc;
400 }
401
402 return true;
403}
404
405static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
406{
407 snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm);
408 if (rc <= 0) {
409 // Wait a bit and try again, maybe the hardware isn't quite ready yet?
410 SDL_Delay(1);
411
412 rc = ALSA_snd_pcm_avail(device->hidden->pcm);
413 if (rc <= 0) {
414 // We'll catch it next time
415 *buffer_size = 0;
416 return NULL;
417 }
418 }
419
420 const int requested_frames = SDL_min(device->sample_frames, rc);
421 const int requested_bytes = requested_frames * SDL_AUDIO_FRAMESIZE(device->spec);
422 SDL_assert(requested_bytes <= *buffer_size);
423 //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA GETDEVICEBUF: NEED %d BYTES", requested_bytes);
424 *buffer_size = requested_bytes;
425 return device->hidden->mixbuf;
426}
427
428static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
429{
430 const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
431 SDL_assert((buflen % frame_size) == 0);
432
433 const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm);
434 const int total_frames = SDL_min(buflen / frame_size, total_available);
435
436 const int rc = ALSA_snd_pcm_readi(device->hidden->pcm, buffer, total_frames);
437
438 SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
439
440 if (rc < 0) {
441 const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
442 if (status < 0) {
443 // Hmm, not much we can do - abort
444 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc));
445 return -1;
446 }
447 return 0; // go back to WaitDevice and try again.
448 }
449
450 //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: recorded %d bytes", rc * frame_size);
451
452 return rc * frame_size;
453}
454
455static void ALSA_FlushRecording(SDL_AudioDevice *device)
456{
457 ALSA_snd_pcm_reset(device->hidden->pcm);
458}
459
460static void ALSA_CloseDevice(SDL_AudioDevice *device)
461{
462 if (device->hidden) {
463 if (device->hidden->pcm) {
464 // Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that.
465 SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2);
466 ALSA_snd_pcm_close(device->hidden->pcm);
467 }
468 SDL_free(device->hidden->mixbuf);
469 SDL_free(device->hidden);
470 }
471}
472
473
474// To make easier to track parameters during the whole alsa pcm configuration:
475struct ALSA_pcm_cfg_ctx {
476 SDL_AudioDevice *device;
477
478 snd_pcm_hw_params_t *hwparams;
479 snd_pcm_sw_params_t *swparams;
480
481 SDL_AudioFormat matched_sdl_format;
482 unsigned int chans_n;
483 unsigned int target_chans_n;
484 unsigned int rate;
485 snd_pcm_uframes_t persize; // alsa period size, SDL audio device sample_frames
486 snd_pcm_chmap_query_t **chmap_queries;
487 unsigned int sdl_chmap[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
488 unsigned int alsa_chmap_installed[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
489
490 unsigned int periods;
491};
492// The following are SDL channel maps with alsa position values, from 0 channels to 8 channels.
493// See SDL3/SDL_audio.h
494// Strictly speaking those are "parameters" of channel maps, like alsa hwparams and swparams, they
495// have to be "reduced/refined" until an exact channel map. Only the 6 channels map requires such
496// "reduction/refine".
497static enum snd_pcm_chmap_position sdl_channel_maps[SDL_AUDIO_ALSA__SDL_CHMAPS_N][SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX] = {
498 // 0 channels
499 {
500 0
501 },
502 // 1 channel
503 {
504 SND_CHMAP_MONO,
505 },
506 // 2 channels
507 {
508 SND_CHMAP_FL,
509 SND_CHMAP_FR,
510 },
511 // 3 channels
512 {
513 SND_CHMAP_FL,
514 SND_CHMAP_FR,
515 SND_CHMAP_LFE,
516 },
517 // 4 channels
518 {
519 SND_CHMAP_FL,
520 SND_CHMAP_FR,
521 SND_CHMAP_RL,
522 SND_CHMAP_RR,
523 },
524 // 5 channels
525 {
526 SND_CHMAP_FL,
527 SND_CHMAP_FR,
528 SND_CHMAP_LFE,
529 SND_CHMAP_RL,
530 SND_CHMAP_RR,
531 },
532 // 6 channels
533 // XXX: here we encode not a uniq channel map but a set of channel maps. We will reduce it each
534 // time we are going to work with an alsa 6 channels map.
535 {
536 SND_CHMAP_FL,
537 SND_CHMAP_FR,
538 SND_CHMAP_FC,
539 SND_CHMAP_LFE,
540 // The 2 following channel positions are (SND_CHMAP_SL,SND_CHMAP_SR) or
541 // (SND_CHMAP_RL,SND_CHMAP_RR)
542 SND_CHMAP_UNKNOWN,
543 SND_CHMAP_UNKNOWN,
544 },
545 // 7 channels
546 {
547 SND_CHMAP_FL,
548 SND_CHMAP_FR,
549 SND_CHMAP_FC,
550 SND_CHMAP_LFE,
551 SND_CHMAP_RC,
552 SND_CHMAP_SL,
553 SND_CHMAP_SR,
554 },
555 // 8 channels
556 {
557 SND_CHMAP_FL,
558 SND_CHMAP_FR,
559 SND_CHMAP_FC,
560 SND_CHMAP_LFE,
561 SND_CHMAP_RL,
562 SND_CHMAP_RR,
563 SND_CHMAP_SL,
564 SND_CHMAP_SR,
565 },
566};
567
568// Helper for the function right below.
569static bool has_pos(const unsigned int *chmap, unsigned int pos)
570{
571 for (unsigned int chan_idx = 0; ; chan_idx++) {
572 if (chan_idx == 6) {
573 return false;
574 }
575 if (chmap[chan_idx] == pos) {
576 return true;
577 }
578 }
579 SDL_assert(!"Shouldn't hit this code.");
580 return false;
581}
582
583// XXX: Each time we are going to work on an alsa 6 channels map, we must reduce the set of channel
584// maps which is encoded in sdl_channel_maps[6] to a uniq one.
585#define HAVE_NONE 0
586#define HAVE_REAR 1
587#define HAVE_SIDE 2
588#define HAVE_BOTH 3
589static void sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(unsigned int *sdl_6chans, const unsigned int *alsa_6chans)
590{
591 // For alsa channel maps with 6 channels and with SND_CHMAP_FL,SND_CHMAP_FR,SND_CHMAP_FC,
592 // SND_CHMAP_LFE, reduce our 6 channels maps to a uniq one.
593 if ( !has_pos(alsa_6chans, SND_CHMAP_FL) ||
594 !has_pos(alsa_6chans, SND_CHMAP_FR) ||
595 !has_pos(alsa_6chans, SND_CHMAP_FC) ||
596 !has_pos(alsa_6chans, SND_CHMAP_LFE)) {
597 sdl_6chans[4] = SND_CHMAP_UNKNOWN;
598 sdl_6chans[5] = SND_CHMAP_UNKNOWN;
599 LOGDEBUG("6channels:unsupported channel map");
600 return;
601 }
602
603 unsigned int state = HAVE_NONE;
604 for (unsigned int chan_idx = 0; chan_idx < 6; chan_idx++) {
605 if ((alsa_6chans[chan_idx] == SND_CHMAP_SL) || (alsa_6chans[chan_idx] == SND_CHMAP_SR)) {
606 if (state == HAVE_NONE) {
607 state = HAVE_SIDE;
608 } else if (state == HAVE_REAR) {
609 state = HAVE_BOTH;
610 break;
611 }
612 } else if ((alsa_6chans[chan_idx] == SND_CHMAP_RL) || (alsa_6chans[chan_idx] == SND_CHMAP_RR)) {
613 if (state == HAVE_NONE) {
614 state = HAVE_REAR;
615 } else if (state == HAVE_SIDE) {
616 state = HAVE_BOTH;
617 break;
618 }
619 }
620 }
621
622 if ((state == HAVE_BOTH) || (state == HAVE_NONE)) {
623 sdl_6chans[4] = SND_CHMAP_UNKNOWN;
624 sdl_6chans[5] = SND_CHMAP_UNKNOWN;
625 LOGDEBUG("6channels:unsupported channel map");
626 } else if (state == HAVE_REAR) {
627 sdl_6chans[4] = SND_CHMAP_RL;
628 sdl_6chans[5] = SND_CHMAP_RR;
629 LOGDEBUG("6channels:sdl map set to rear");
630 } else { // state == HAVE_SIDE
631 sdl_6chans[4] = SND_CHMAP_SL;
632 sdl_6chans[5] = SND_CHMAP_SR;
633 LOGDEBUG("6channels:sdl map set to side");
634 }
635}
636#undef HAVE_NONE
637#undef HAVE_REAR
638#undef HAVE_SIDE
639#undef HAVE_BOTH
640
641static void swizzle_map_compute_alsa_subscan(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, unsigned int sdl_pos_idx)
642{
643 swizzle_map[sdl_pos_idx] = -1;
644 for (unsigned int alsa_pos_idx = 0; ; alsa_pos_idx++) {
645 SDL_assert(alsa_pos_idx != ctx->chans_n); // no 0 channels or not found matching position should happen here (actually enforce playback/recording symmetry).
646 if (ctx->alsa_chmap_installed[alsa_pos_idx] == ctx->sdl_chmap[sdl_pos_idx]) {
647 LOGDEBUG("swizzle SDL %u <-> alsa %u", sdl_pos_idx,alsa_pos_idx);
648 swizzle_map[sdl_pos_idx] = (int) alsa_pos_idx;
649 return;
650 }
651 }
652}
653
654// XXX: this must stay playback/recording symetric.
655static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle)
656{
657 *needs_swizzle = false;
658 for (unsigned int sdl_pos_idx = 0; sdl_pos_idx != ctx->chans_n; sdl_pos_idx++) {
659 swizzle_map_compute_alsa_subscan(ctx, swizzle_map, sdl_pos_idx);
660 if (swizzle_map[sdl_pos_idx] != sdl_pos_idx) {
661 *needs_swizzle = true;
662 }
663 }
664}
665
666#define CHMAP_INSTALLED 0
667#define CHANS_N_NEXT 1
668#define CHMAP_NOT_FOUND 2
669// Should always be a queried alsa channel map unless the queried alsa channel map was of type VAR,
670// namely we can program the channel positions directly from the SDL channel map.
671static int alsa_chmap_install(struct ALSA_pcm_cfg_ctx *ctx, const unsigned int *chmap)
672{
673 bool isstack;
674 snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t*)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack);
675 if (!chmap_to_install) {
676 return -1;
677 }
678
679 chmap_to_install->channels = ctx->chans_n;
680 SDL_memcpy(chmap_to_install->pos, chmap, sizeof (unsigned int) * ctx->chans_n);
681
682 #if SDL_ALSA_DEBUG
683 char logdebug_chmap_str[128];
684 ALSA_snd_pcm_chmap_print(chmap_to_install,sizeof(logdebug_chmap_str),logdebug_chmap_str);
685 LOGDEBUG("channel map to install:%s",logdebug_chmap_str);
686 #endif
687
688 int status = ALSA_snd_pcm_set_chmap(ctx->device->hidden->pcm, chmap_to_install);
689 if (status < 0) {
690 SDL_SetError("ALSA: failed to install channel map: %s", ALSA_snd_strerror(status));
691 return -1;
692 }
693 SDL_memcpy(ctx->alsa_chmap_installed, chmap, ctx->chans_n * sizeof (unsigned int));
694
695 SDL_small_free(chmap_to_install, isstack);
696 return CHMAP_INSTALLED;
697}
698
699// We restrict the alsa channel maps because in the unordered matches we do only simple accounting.
700// In the end, this will handle mostly alsa channel maps with more than one SND_CHMAP_NA position fillers.
701static bool alsa_chmap_has_duplicate_position(const struct ALSA_pcm_cfg_ctx *ctx, const unsigned int *pos)
702{
703 if (ctx->chans_n < 2) {// we need at least 2 positions
704 LOGDEBUG("channel map:no duplicate");
705 return false;
706 }
707
708 for (unsigned int chan_idx = 1; chan_idx != ctx->chans_n; chan_idx++) {
709 for (unsigned int seen_idx = 0; seen_idx != chan_idx; seen_idx++) {
710 if (pos[seen_idx] == pos[chan_idx]) {
711 LOGDEBUG("channel map:have duplicate");
712 return true;
713 }
714 }
715 }
716
717 LOGDEBUG("channel map:no duplicate");
718 return false;
719}
720
721static int alsa_chmap_cfg_ordered_fixed_or_paired(struct ALSA_pcm_cfg_ctx *ctx)
722{
723 for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
724 if ( ((*chmap_query)->map.channels != ctx->chans_n) ||
725 (((*chmap_query)->type != SND_CHMAP_TYPE_FIXED) && ((*chmap_query)->type != SND_CHMAP_TYPE_PAIRED)) ) {
726 continue;
727 }
728
729 #if SDL_ALSA_DEBUG
730 char logdebug_chmap_str[128];
731 ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
732 LOGDEBUG("channel map:ordered:fixed|paired:%s",logdebug_chmap_str);
733 #endif
734
735 for (int i = 0; i < ctx->chans_n; i++) {
736 ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
737 }
738
739 unsigned int *alsa_chmap = (*chmap_query)->map.pos;
740 if (ctx->chans_n == 6) {
741 sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
742 }
743 if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
744 continue;
745 }
746
747 for (unsigned int chan_idx = 0; ctx->sdl_chmap[chan_idx] == alsa_chmap[chan_idx]; chan_idx++) {
748 if (chan_idx == ctx->chans_n) {
749 return alsa_chmap_install(ctx, alsa_chmap);
750 }
751 }
752 }
753 return CHMAP_NOT_FOUND;
754}
755
756// Here, the alsa channel positions can be programmed in the alsa frame (cf HDMI).
757// If the alsa channel map is VAR, we only check we have the unordered set of channel positions we
758// are looking for.
759static int alsa_chmap_cfg_ordered_var(struct ALSA_pcm_cfg_ctx *ctx)
760{
761 for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
762 if (((*chmap_query)->map.channels != ctx->chans_n) || ((*chmap_query)->type != SND_CHMAP_TYPE_VAR)) {
763 continue;
764 }
765
766 #if SDL_ALSA_DEBUG
767 char logdebug_chmap_str[128];
768 ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
769 LOGDEBUG("channel map:ordered:var:%s",logdebug_chmap_str);
770 #endif
771
772 for (int i = 0; i < ctx->chans_n; i++) {
773 ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
774 }
775
776 unsigned int *alsa_chmap = (*chmap_query)->map.pos;
777 if (ctx->chans_n == 6) {
778 sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
779 }
780 if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
781 continue;
782 }
783
784 unsigned int pos_matches_n = 0;
785 for (unsigned int chan_idx = 0; chan_idx != ctx->chans_n; chan_idx++) {
786 for (unsigned int subscan_chan_idx = 0; subscan_chan_idx != ctx->chans_n; subscan_chan_idx++) {
787 if (ctx->sdl_chmap[chan_idx] == alsa_chmap[subscan_chan_idx]) {
788 pos_matches_n++;
789 break;
790 }
791 }
792 }
793
794 if (pos_matches_n == ctx->chans_n) {
795 return alsa_chmap_install(ctx, ctx->sdl_chmap); // XXX: we program the SDL chmap here
796 }
797 }
798
799 return CHMAP_NOT_FOUND;
800}
801
802static int alsa_chmap_cfg_ordered(struct ALSA_pcm_cfg_ctx *ctx)
803{
804 const int status = alsa_chmap_cfg_ordered_fixed_or_paired(ctx);
805 return (status != CHMAP_NOT_FOUND) ? status : alsa_chmap_cfg_ordered_var(ctx);
806}
807
808// In the unordered case, we are just interested to get the same unordered set of alsa channel
809// positions than in the SDL channel map since we will swizzle (no duplicate channel position).
810static int alsa_chmap_cfg_unordered(struct ALSA_pcm_cfg_ctx *ctx)
811{
812 for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
813 if ( ((*chmap_query)->map.channels != ctx->chans_n) ||
814 (((*chmap_query)->type != SND_CHMAP_TYPE_FIXED) && ((*chmap_query)->type != SND_CHMAP_TYPE_PAIRED)) ) {
815 continue;
816 }
817
818 #if SDL_ALSA_DEBUG
819 char logdebug_chmap_str[128];
820 ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
821 LOGDEBUG("channel map:unordered:fixed|paired:%s",logdebug_chmap_str);
822 #endif
823
824 for (int i = 0; i < ctx->chans_n; i++) {
825 ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
826 }
827
828 unsigned int *alsa_chmap = (*chmap_query)->map.pos;
829 if (ctx->chans_n == 6) {
830 sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
831 }
832
833 if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
834 continue;
835 }
836
837 unsigned int pos_matches_n = 0;
838 for (unsigned int chan_idx = 0; chan_idx != ctx->chans_n; chan_idx++) {
839 for (unsigned int subscan_chan_idx = 0; subscan_chan_idx != ctx->chans_n; subscan_chan_idx++) {
840 if (ctx->sdl_chmap[chan_idx] == alsa_chmap[subscan_chan_idx]) {
841 pos_matches_n++;
842 break;
843 }
844 }
845 }
846
847 if (pos_matches_n == ctx->chans_n) {
848 return alsa_chmap_install(ctx, alsa_chmap);
849 }
850 }
851
852 return CHMAP_NOT_FOUND;
853}
854
855static int alsa_chmap_cfg(struct ALSA_pcm_cfg_ctx *ctx)
856{
857 int status;
858
859 ctx->chmap_queries = ALSA_snd_pcm_query_chmaps(ctx->device->hidden->pcm);
860 if (ctx->chmap_queries == NULL) {
861 // We couldn't query the channel map, assume no swizzle necessary
862 LOGDEBUG("couldn't query channel map, swizzling off");
863 return CHMAP_INSTALLED;
864 }
865
866 //----------------------------------------------------------------------------------------------
867 status = alsa_chmap_cfg_ordered(ctx); // we prefer first channel maps we don't need to swizzle
868 if (status == CHMAP_INSTALLED) {
869 LOGDEBUG("swizzling off");
870 return status;
871 } else if (status != CHMAP_NOT_FOUND) {
872 return status; // < 0 error code
873 }
874
875 // Fall-thru
876 //----------------------------------------------------------------------------------------------
877 status = alsa_chmap_cfg_unordered(ctx); // those we will have to swizzle
878 if (status == CHMAP_INSTALLED) {
879 LOGDEBUG("swizzling on");
880
881 bool isstack;
882 int *swizzle_map = SDL_small_alloc(int, ctx->chans_n, &isstack);
883 if (!swizzle_map) {
884 status = -1;
885 } else {
886 bool needs_swizzle;
887 swizzle_map_compute(ctx, swizzle_map, &needs_swizzle); // fine grained swizzle configuration
888 if (needs_swizzle) {
889 // let SDL's swizzler handle this one.
890 ctx->device->chmap = SDL_ChannelMapDup(swizzle_map, ctx->chans_n);
891 if (!ctx->device->chmap) {
892 status = -1;
893 }
894 }
895 SDL_small_free(swizzle_map, isstack);
896 }
897 }
898
899 if (status == CHMAP_NOT_FOUND) {
900 return CHANS_N_NEXT;
901 }
902
903 return status; // < 0 error code
904}
905
906#define CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N 0 // target more hardware pressure
907#define CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N 1 // target less hardware pressure
908#define CHANS_N_CONFIGURED 0
909#define CHANS_N_NOT_CONFIGURED 1
910static int ALSA_pcm_cfg_hw_chans_n_scan(struct ALSA_pcm_cfg_ctx *ctx, unsigned int mode)
911{
912 unsigned int target_chans_n = ctx->device->spec.channels; // we start at what was specified
913 if (mode == CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N) {
914 target_chans_n--;
915 }
916 while (true) {
917 if (mode == CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N) {
918 if (target_chans_n > SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX) {
919 return CHANS_N_NOT_CONFIGURED;
920 }
921 // else: CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
922 } else if (target_chans_n == 0) {
923 return CHANS_N_NOT_CONFIGURED;
924 }
925
926 LOGDEBUG("target chans_n is %u", target_chans_n);
927
928 int status = ALSA_snd_pcm_hw_params_any(ctx->device->hidden->pcm, ctx->hwparams);
929 if (status < 0) {
930 SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status));
931 return -1;
932 }
933 // SDL only uses interleaved sample output
934 status = ALSA_snd_pcm_hw_params_set_access(ctx->device->hidden->pcm, ctx->hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
935 if (status < 0) {
936 SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status));
937 return -1;
938 }
939 // Try for a closest match on audio format
940 snd_pcm_format_t alsa_format = 0;
941 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(ctx->device->spec.format);
942 ctx->matched_sdl_format = 0;
943 while ((ctx->matched_sdl_format = *(closefmts++)) != 0) {
944 // XXX: we are forcing the same endianness, namely we won't need byte swapping upon
945 // writing/reading to/from the SDL audio buffer.
946 switch (ctx->matched_sdl_format) {
947 case SDL_AUDIO_U8:
948 alsa_format = SND_PCM_FORMAT_U8;
949 break;
950 case SDL_AUDIO_S8:
951 alsa_format = SND_PCM_FORMAT_S8;
952 break;
953 case SDL_AUDIO_S16LE:
954 alsa_format = SND_PCM_FORMAT_S16_LE;
955 break;
956 case SDL_AUDIO_S16BE:
957 alsa_format = SND_PCM_FORMAT_S16_BE;
958 break;
959 case SDL_AUDIO_S32LE:
960 alsa_format = SND_PCM_FORMAT_S32_LE;
961 break;
962 case SDL_AUDIO_S32BE:
963 alsa_format = SND_PCM_FORMAT_S32_BE;
964 break;
965 case SDL_AUDIO_F32LE:
966 alsa_format = SND_PCM_FORMAT_FLOAT_LE;
967 break;
968 case SDL_AUDIO_F32BE:
969 alsa_format = SND_PCM_FORMAT_FLOAT_BE;
970 break;
971 default:
972 continue;
973 }
974 if (ALSA_snd_pcm_hw_params_set_format(ctx->device->hidden->pcm, ctx->hwparams, alsa_format) >= 0) {
975 break;
976 }
977 }
978 if (ctx->matched_sdl_format == 0) {
979 SDL_SetError("ALSA: Unsupported audio format: %s", ALSA_snd_strerror(status));
980 return -1;
981 }
982 // let alsa approximate the number of channels
983 ctx->chans_n = target_chans_n;
984 status = ALSA_snd_pcm_hw_params_set_channels_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->chans_n));
985 if (status < 0) {
986 SDL_SetError("ALSA: Couldn't set audio channels: %s", ALSA_snd_strerror(status));
987 return -1;
988 }
989 // let alsa approximate the audio rate
990 ctx->rate = ctx->device->spec.freq;
991 status = ALSA_snd_pcm_hw_params_set_rate_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->rate), NULL);
992 if (status < 0) {
993 SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status));
994 return -1;
995 }
996 // let approximate the period size to the requested buffer size
997 ctx->persize = ctx->device->sample_frames;
998 status = ALSA_snd_pcm_hw_params_set_period_size_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->persize), NULL);
999 if (status < 0) {
1000 SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status));
1001 return -1;
1002 }
1003 // let approximate the minimun number of periods per buffer (we target a double buffer)
1004 ctx->periods = 2;
1005 status = ALSA_snd_pcm_hw_params_set_periods_min(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL);
1006 if (status < 0) {
1007 SDL_SetError("ALSA: Couldn't set the minimum number of periods per buffer: %s", ALSA_snd_strerror(status));
1008 return -1;
1009 }
1010 // restrict the number of periods per buffer to an approximation of the approximated minimum
1011 // number of periods per buffer done right above
1012 status = ALSA_snd_pcm_hw_params_set_periods_first(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL);
1013 if (status < 0) {
1014 SDL_SetError("ALSA: Couldn't set the number of periods per buffer: %s", ALSA_snd_strerror(status));
1015 return -1;
1016 }
1017 // install the hw parameters
1018 status = ALSA_snd_pcm_hw_params(ctx->device->hidden->pcm, ctx->hwparams);
1019 if (status < 0) {
1020 SDL_SetError("ALSA: installation of hardware parameter failed: %s", ALSA_snd_strerror(status));
1021 return -1;
1022 }
1023 //==========================================================================================
1024 // Here the alsa pcm is in SND_PCM_STATE_PREPARED state, let's figure out a good fit for
1025 // SDL channel map, it may request to change the target number of channels though.
1026 status = alsa_chmap_cfg(ctx);
1027 if (status < 0) {
1028 return status; // we forward the SDL error
1029 } else if (status == CHMAP_INSTALLED) {
1030 return CHANS_N_CONFIGURED; // we are finished here
1031 }
1032
1033 // status == CHANS_N_NEXT
1034 ALSA_snd_pcm_free_chmaps(ctx->chmap_queries);
1035 ALSA_snd_pcm_hw_free(ctx->device->hidden->pcm); // uninstall those hw params
1036
1037 if (mode == CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N) {
1038 target_chans_n++;
1039 } else { // CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
1040 target_chans_n--;
1041 }
1042 }
1043
1044 SDL_assert(!"Shouldn't reach this code.");
1045 return CHANS_N_NOT_CONFIGURED;
1046}
1047#undef CHMAP_INSTALLED
1048#undef CHANS_N_NEXT
1049#undef CHMAP_NOT_FOUND
1050
1051static bool ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx)
1052{
1053 LOGDEBUG("target chans_n, equal or above requested chans_n mode");
1054 int status = ALSA_pcm_cfg_hw_chans_n_scan(ctx, CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N);
1055 if (status < 0) { // something went too wrong
1056 return false;
1057 } else if (status == CHANS_N_CONFIGURED) {
1058 return true;
1059 }
1060
1061 // Here, status == CHANS_N_NOT_CONFIGURED
1062 LOGDEBUG("target chans_n, below requested chans_n mode");
1063 status = ALSA_pcm_cfg_hw_chans_n_scan(ctx, CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N);
1064 if (status < 0) { // something went too wrong
1065 return false;
1066 } else if (status == CHANS_N_CONFIGURED) {
1067 return true;
1068 }
1069
1070 // Here, status == CHANS_N_NOT_CONFIGURED
1071 return SDL_SetError("ALSA: Coudn't configure targetting any SDL supported channel number");
1072}
1073#undef CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N
1074#undef CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
1075#undef CHANS_N_CONFIGURED
1076#undef CHANS_N_NOT_CONFIGURED
1077
1078
1079static bool ALSA_pcm_cfg_sw(struct ALSA_pcm_cfg_ctx *ctx)
1080{
1081 int status;
1082
1083 status = ALSA_snd_pcm_sw_params_current(ctx->device->hidden->pcm, ctx->swparams);
1084 if (status < 0) {
1085 return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
1086 }
1087
1088 status = ALSA_snd_pcm_sw_params_set_avail_min(ctx->device->hidden->pcm, ctx->swparams, ctx->persize); // will become device->sample_frames if the alsa pcm configuration is successful
1089 if (status < 0) {
1090 return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
1091 }
1092
1093 status = ALSA_snd_pcm_sw_params_set_start_threshold(ctx->device->hidden->pcm, ctx->swparams, 1);
1094 if (status < 0) {
1095 return SDL_SetError("ALSA: Couldn't set start threshold: %s", ALSA_snd_strerror(status));
1096 }
1097 status = ALSA_snd_pcm_sw_params(ctx->device->hidden->pcm, ctx->swparams);
1098 if (status < 0) {
1099 return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
1100 }
1101 return true;
1102}
1103
1104
1105static bool ALSA_OpenDevice(SDL_AudioDevice *device)
1106{
1107 const bool recording = device->recording;
1108 struct ALSA_pcm_cfg_ctx cfg_ctx; // used to track everything here
1109 char *pcm_str;
1110 int status = 0;
1111
1112 //device->spec.channels = 8;
1113 //SDL_SetLogPriority(SDL_LOG_CATEGORY_AUDIO, SDL_LOG_PRIORITY_VERBOSE);
1114 LOGDEBUG("channels requested %u",device->spec.channels);
1115 // XXX: We do not use the SDL internal swizzler yet.
1116 device->chmap = NULL;
1117
1118 SDL_zero(cfg_ctx);
1119 cfg_ctx.device = device;
1120
1121 // Initialize all variables that we clean on shutdown
1122 cfg_ctx.device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*cfg_ctx.device->hidden));
1123 if (!cfg_ctx.device->hidden) {
1124 return false;
1125 }
1126
1127 // Open the audio device
1128 pcm_str = get_pcm_str(cfg_ctx.device->handle);
1129 if (pcm_str == NULL) {
1130 goto err_free_device_hidden;
1131 }
1132 LOGDEBUG("PCM open '%s'", pcm_str);
1133 status = ALSA_snd_pcm_open(&cfg_ctx.device->hidden->pcm,
1134 pcm_str,
1135 recording ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
1136 SND_PCM_NONBLOCK);
1137 SDL_free(pcm_str);
1138 if (status < 0) {
1139 SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status));
1140 goto err_free_device_hidden;
1141 }
1142
1143 // Now we need to configure the opened pcm as close as possible from the requested parameters we
1144 // can reasonably deal with (and that could change)
1145 snd_pcm_hw_params_alloca(&(cfg_ctx.hwparams));
1146 snd_pcm_sw_params_alloca(&(cfg_ctx.swparams));
1147
1148 if (!ALSA_pcm_cfg_hw(&cfg_ctx)) { // alsa pcm "hardware" part of the pcm
1149 goto err_close_pcm;
1150 }
1151
1152 // from here, we get only the alsa chmap queries in cfg_ctx to explicitely clean, hwparams is
1153 // uninstalled upon pcm closing
1154
1155 // This is useful for debugging
1156 #if SDL_ALSA_DEBUG
1157 snd_pcm_uframes_t bufsize;
1158 ALSA_snd_pcm_hw_params_get_buffer_size(cfg_ctx.hwparams, &bufsize);
1159 SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
1160 "ALSA: period size = %ld, periods = %u, buffer size = %lu",
1161 cfg_ctx.persize, cfg_ctx.periods, bufsize);
1162 #endif
1163
1164 if (!ALSA_pcm_cfg_sw(&cfg_ctx)) { // alsa pcm "software" part of the pcm
1165 goto err_cleanup_ctx;
1166 }
1167
1168 // Now we can update the following parameters in the spec:
1169 cfg_ctx.device->spec.format = cfg_ctx.matched_sdl_format;
1170 cfg_ctx.device->spec.channels = cfg_ctx.chans_n;
1171 cfg_ctx.device->spec.freq = cfg_ctx.rate;
1172 cfg_ctx.device->sample_frames = cfg_ctx.persize;
1173 // Calculate the final parameters for this audio specification
1174 SDL_UpdatedAudioDeviceFormat(cfg_ctx.device);
1175
1176 // Allocate mixing buffer
1177 if (!recording) {
1178 cfg_ctx.device->hidden->mixbuf = (Uint8 *)SDL_malloc(cfg_ctx.device->buffer_size);
1179 if (cfg_ctx.device->hidden->mixbuf == NULL) {
1180 goto err_cleanup_ctx;
1181 }
1182 SDL_memset(cfg_ctx.device->hidden->mixbuf, cfg_ctx.device->silence_value, cfg_ctx.device->buffer_size);
1183 }
1184
1185#if !SDL_ALSA_NON_BLOCKING
1186 if (!recording) {
1187 ALSA_snd_pcm_nonblock(cfg_ctx.device->hidden->pcm, 0);
1188 }
1189#endif
1190 ALSA_snd_pcm_start(cfg_ctx.device->hidden->pcm);
1191 return true; // We're ready to rock and roll. :-)
1192
1193err_cleanup_ctx:
1194 ALSA_snd_pcm_free_chmaps(cfg_ctx.chmap_queries);
1195err_close_pcm:
1196 ALSA_snd_pcm_close(cfg_ctx.device->hidden->pcm);
1197err_free_device_hidden:
1198 SDL_free(cfg_ctx.device->hidden);
1199 cfg_ctx.device->hidden = NULL;
1200 return false;
1201}
1202
1203static ALSA_Device *hotplug_devices = NULL;
1204
1205static int hotplug_device_process(snd_ctl_t *ctl, snd_ctl_card_info_t *ctl_card_info, int dev_idx,
1206 snd_pcm_stream_t direction, ALSA_Device **unseen, ALSA_Device **seen)
1207{
1208 unsigned int subdevs_n = 1; // we have at least one subdevice (substream since the direction is a stream in alsa terminology)
1209 unsigned int subdev_idx = 0;
1210 const bool recording = direction == SND_PCM_STREAM_CAPTURE ? true : false; // used for the unicity of the device
1211 bool isstack;
1212 snd_pcm_info_t *pcm_info = (snd_pcm_info_t*)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack);
1213 SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof());
1214
1215 while (true) {
1216 ALSA_snd_pcm_info_set_stream(pcm_info, direction);
1217 ALSA_snd_pcm_info_set_device(pcm_info, dev_idx);
1218 ALSA_snd_pcm_info_set_subdevice(pcm_info, subdev_idx); // we have at least one subdevice (substream) of index 0
1219
1220 const int r = ALSA_snd_ctl_pcm_info(ctl, pcm_info);
1221 if (r < 0) {
1222 SDL_small_free(pcm_info, isstack);
1223 // first call to ALSA_snd_ctl_pcm_info
1224 if (subdev_idx == 0 && r == -ENOENT) { // no such direction/stream for this device
1225 return 0;
1226 }
1227 return -1;
1228 }
1229
1230 if (subdev_idx == 0) {
1231 subdevs_n = ALSA_snd_pcm_info_get_subdevices_count(pcm_info);
1232 }
1233
1234 // building the unseen list scanning the list of hotplug devices, if it is already there
1235 // using the id, move it to the seen list.
1236 ALSA_Device *unseen_prev_adev = NULL;
1237 ALSA_Device *adev;
1238 for (adev = *unseen; adev; adev = adev->next) {
1239 // the unicity key is the couple (id,recording)
1240 if ((SDL_strcmp(adev->id, ALSA_snd_ctl_card_info_get_id(ctl_card_info)) == 0) && (adev->recording == recording)) {
1241 // unchain from unseen
1242 if (*unseen == adev) { // head
1243 *unseen = adev->next;
1244 } else {
1245 unseen_prev_adev->next = adev->next;
1246 }
1247 // chain to seen
1248 adev->next = *seen;
1249 *seen = adev;
1250 break;
1251 }
1252 unseen_prev_adev = adev;
1253 }
1254
1255 if (adev == NULL) { // newly seen device
1256 adev = SDL_calloc(1, sizeof(*adev));
1257 if (adev == NULL) {
1258 SDL_small_free(pcm_info, isstack);
1259 return -1;
1260 }
1261
1262 adev->id = SDL_strdup(ALSA_snd_ctl_card_info_get_id(ctl_card_info));
1263 if (adev->id == NULL) {
1264 SDL_small_free(pcm_info, isstack);
1265 SDL_free(adev);
1266 return -1;
1267 }
1268
1269 if (SDL_asprintf(&adev->name, "%s:%s", ALSA_snd_ctl_card_info_get_name(ctl_card_info), ALSA_snd_pcm_info_get_name(pcm_info)) == -1) {
1270 SDL_small_free(pcm_info, isstack);
1271 SDL_free(adev->id);
1272 SDL_free(adev);
1273 return -1;
1274 }
1275
1276 if (direction == SND_PCM_STREAM_CAPTURE) {
1277 adev->recording = true;
1278 } else {
1279 adev->recording = false;
1280 }
1281
1282 if (SDL_AddAudioDevice(recording, adev->name, NULL, adev) == NULL) {
1283 SDL_small_free(pcm_info, isstack);
1284 SDL_free(adev->id);
1285 SDL_free(adev->name);
1286 SDL_free(adev);
1287 return -1;
1288 }
1289
1290 adev->next = *seen;
1291 *seen = adev;
1292 }
1293
1294 subdev_idx++;
1295 if (subdev_idx == subdevs_n) {
1296 SDL_small_free(pcm_info, isstack);
1297 return 0;
1298 }
1299
1300 SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof());
1301 }
1302
1303 SDL_small_free(pcm_info, isstack);
1304 SDL_assert(!"Shouldn't reach this code");
1305 return -1;
1306}
1307
1308static void ALSA_HotplugIteration(bool *has_default_output, bool *has_default_recording)
1309{
1310 if (has_default_output != NULL) {
1311 *has_default_output = true;
1312 }
1313
1314 if (has_default_recording != NULL) {
1315 *has_default_recording = true;
1316 }
1317
1318 bool isstack;
1319 snd_ctl_card_info_t *ctl_card_info = (snd_ctl_card_info_t *) SDL_small_alloc(Uint8, ALSA_snd_ctl_card_info_sizeof(), &isstack);
1320 if (!ctl_card_info) {
1321 return; // oh well.
1322 }
1323
1324 SDL_memset(ctl_card_info, 0, ALSA_snd_ctl_card_info_sizeof());
1325
1326 snd_ctl_t *ctl = NULL;
1327 ALSA_Device *unseen = hotplug_devices;
1328 ALSA_Device *seen = NULL;
1329 int card_idx = -1;
1330 while (true) {
1331 int r = ALSA_snd_card_next(&card_idx);
1332 if (r < 0) {
1333 goto failed;
1334 } else if (card_idx == -1) {
1335 break;
1336 }
1337
1338 char ctl_name[64];
1339 SDL_snprintf(ctl_name, sizeof (ctl_name), "%s%d", ALSA_device_prefix, card_idx); // card_idx >= 0
1340 LOGDEBUG("hotplug ctl_name = '%s'", ctl_name);
1341
1342 r = ALSA_snd_ctl_open(&ctl, ctl_name, 0);
1343 if (r < 0) {
1344 continue;
1345 }
1346
1347 r = ALSA_snd_ctl_card_info(ctl, ctl_card_info);
1348 if (r < 0) {
1349 goto failed;
1350 }
1351
1352 int dev_idx = -1;
1353 while (true) {
1354 r = ALSA_snd_ctl_pcm_next_device(ctl, &dev_idx);
1355 if (r < 0) {
1356 goto failed;
1357 } else if (dev_idx == -1) {
1358 break;
1359 }
1360
1361 r = hotplug_device_process(ctl, ctl_card_info, dev_idx, SND_PCM_STREAM_PLAYBACK, &unseen, &seen);
1362 if (r < 0) {
1363 goto failed;
1364 }
1365
1366 r = hotplug_device_process(ctl, ctl_card_info, dev_idx, SND_PCM_STREAM_CAPTURE, &unseen, &seen);
1367 if (r < 0) {
1368 goto failed;
1369 }
1370 }
1371 ALSA_snd_ctl_close(ctl);
1372 ALSA_snd_ctl_card_info_clear(ctl_card_info);
1373 }
1374
1375 // remove only the unseen devices
1376 while (unseen) {
1377 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(unseen));
1378 SDL_free(unseen->name);
1379 SDL_free(unseen->id);
1380 ALSA_Device *next = unseen->next;
1381 SDL_free(unseen);
1382 unseen = next;
1383 }
1384
1385 // update hotplug devices to be the seen devices
1386 hotplug_devices = seen;
1387 SDL_small_free(ctl_card_info, isstack);
1388 return;
1389
1390failed:
1391 if (ctl) {
1392 ALSA_snd_ctl_close(ctl);
1393 }
1394
1395 // remove the unseen
1396 while (unseen) {
1397 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(unseen));
1398 SDL_free(unseen->name);
1399 SDL_free(unseen->id);
1400 ALSA_Device *next = unseen->next;
1401 SDL_free(unseen);
1402 unseen = next;
1403 }
1404
1405 // remove the seen
1406 while (seen) {
1407 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(seen));
1408 SDL_free(seen->name);
1409 SDL_free(seen->id);
1410 ALSA_Device *next = seen->next;
1411 SDL_free(seen);
1412 seen = next;
1413 }
1414
1415 hotplug_devices = NULL;
1416 SDL_small_free(ctl_card_info, isstack);
1417}
1418
1419
1420#if SDL_ALSA_HOTPLUG_THREAD
1421static SDL_AtomicInt ALSA_hotplug_shutdown;
1422static SDL_Thread *ALSA_hotplug_thread;
1423
1424static int SDLCALL ALSA_HotplugThread(void *arg)
1425{
1426 SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_LOW);
1427
1428 while (!SDL_GetAtomicInt(&ALSA_hotplug_shutdown)) {
1429 // Block awhile before checking again, unless we're told to stop.
1430 const Uint64 ticks = SDL_GetTicks() + 5000;
1431 while (!SDL_GetAtomicInt(&ALSA_hotplug_shutdown) && (SDL_GetTicks() < ticks)) {
1432 SDL_Delay(100);
1433 }
1434
1435 ALSA_HotplugIteration(NULL, NULL); // run the check.
1436 }
1437
1438 return 0;
1439}
1440#endif
1441
1442static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
1443{
1444 ALSA_guess_device_prefix();
1445
1446 // ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default
1447 // device here. It's the best we can do at this level.
1448 bool has_default_playback = false, has_default_recording = false;
1449 ALSA_HotplugIteration(&has_default_playback, &has_default_recording); // run once now before a thread continues to check.
1450 if (has_default_playback) {
1451 *default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void*)&default_playback_handle);
1452 }
1453 if (has_default_recording) {
1454 *default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void*)&default_recording_handle);
1455 }
1456
1457#if SDL_ALSA_HOTPLUG_THREAD
1458 SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0);
1459 ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
1460 // if the thread doesn't spin, oh well, you just don't get further hotplug events.
1461#endif
1462}
1463
1464static void ALSA_DeinitializeStart(void)
1465{
1466 ALSA_Device *dev;
1467 ALSA_Device *next;
1468
1469#if SDL_ALSA_HOTPLUG_THREAD
1470 if (ALSA_hotplug_thread) {
1471 SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 1);
1472 SDL_WaitThread(ALSA_hotplug_thread, NULL);
1473 ALSA_hotplug_thread = NULL;
1474 }
1475#endif
1476
1477 // Shutting down! Clean up any data we've gathered.
1478 for (dev = hotplug_devices; dev; dev = next) {
1479 //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: at shutdown, removing %s device '%s'", dev->recording ? "recording" : "playback", dev->name);
1480 next = dev->next;
1481 SDL_free(dev->name);
1482 SDL_free(dev);
1483 }
1484 hotplug_devices = NULL;
1485}
1486
1487static void ALSA_Deinitialize(void)
1488{
1489 UnloadALSALibrary();
1490}
1491
1492static bool ALSA_Init(SDL_AudioDriverImpl *impl)
1493{
1494 if (!LoadALSALibrary()) {
1495 return false;
1496 }
1497
1498 impl->DetectDevices = ALSA_DetectDevices;
1499 impl->OpenDevice = ALSA_OpenDevice;
1500 impl->WaitDevice = ALSA_WaitDevice;
1501 impl->GetDeviceBuf = ALSA_GetDeviceBuf;
1502 impl->PlayDevice = ALSA_PlayDevice;
1503 impl->CloseDevice = ALSA_CloseDevice;
1504 impl->DeinitializeStart = ALSA_DeinitializeStart;
1505 impl->Deinitialize = ALSA_Deinitialize;
1506 impl->WaitRecordingDevice = ALSA_WaitDevice;
1507 impl->RecordDevice = ALSA_RecordDevice;
1508 impl->FlushRecording = ALSA_FlushRecording;
1509
1510 impl->HasRecordingSupport = true;
1511
1512 return true;
1513}
1514
1515AudioBootStrap ALSA_bootstrap = {
1516 "alsa", "ALSA PCM audio", ALSA_Init, false, false
1517};
1518
1519#endif // SDL_AUDIO_DRIVER_ALSA
1520