1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "SDL_internal.h"
- #ifdef SDL_AUDIO_DRIVER_ALSA
- #ifndef SDL_ALSA_NON_BLOCKING
- #define SDL_ALSA_NON_BLOCKING 0
- #endif
- // without the thread, you will detect devices on startup, but will not get further hotplug events. But that might be okay.
- #ifndef SDL_ALSA_HOTPLUG_THREAD
- #define SDL_ALSA_HOTPLUG_THREAD 1
- #endif
- // this turns off debug logging completely (but by default this goes to the bitbucket).
- #ifndef SDL_ALSA_DEBUG
- #define SDL_ALSA_DEBUG 1
- #endif
- #include "../SDL_sysaudio.h"
- #include "SDL_alsa_audio.h"
- #if SDL_ALSA_DEBUG
- #define LOGDEBUG(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "ALSA: " __VA_ARGS__)
- #else
- #define LOGDEBUG(...)
- #endif
- //TODO: cleanup once the code settled down
- static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
- static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm);
- static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm);
- static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t);
- static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t);
- static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int);
- static int (*ALSA_snd_pcm_prepare)(snd_pcm_t *);
- static int (*ALSA_snd_pcm_drain)(snd_pcm_t *);
- static const char *(*ALSA_snd_strerror)(int);
- static size_t (*ALSA_snd_pcm_hw_params_sizeof)(void);
- static size_t (*ALSA_snd_pcm_sw_params_sizeof)(void);
- static void (*ALSA_snd_pcm_hw_params_copy)(snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
- static int (*ALSA_snd_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *);
- static int (*ALSA_snd_pcm_hw_params_set_access)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
- static int (*ALSA_snd_pcm_hw_params_set_format)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
- static int (*ALSA_snd_pcm_hw_params_set_channels)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
- static int (*ALSA_snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *, unsigned int *);
- static int (*ALSA_snd_pcm_hw_params_set_rate_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
- static int (*ALSA_snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
- static int (*ALSA_snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
- static int (*ALSA_snd_pcm_hw_params_set_periods_min)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
- static int (*ALSA_snd_pcm_hw_params_set_periods_first)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
- static int (*ALSA_snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *, unsigned int *, int *);
- static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
- static int (*ALSA_snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
- static int (*ALSA_snd_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *);
- static int (*ALSA_snd_pcm_sw_params_current)(snd_pcm_t *,
- snd_pcm_sw_params_t *);
- static int (*ALSA_snd_pcm_sw_params_set_start_threshold)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
- static int (*ALSA_snd_pcm_sw_params)(snd_pcm_t *, snd_pcm_sw_params_t *);
- static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int);
- static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
- static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
- static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
- static int (*ALSA_snd_device_name_hint)(int, const char *, void ***);
- static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *);
- static int (*ALSA_snd_device_name_free_hint)(void **);
- static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
- static size_t (*ALSA_snd_ctl_card_info_sizeof)(void);
- static size_t (*ALSA_snd_pcm_info_sizeof)(void);
- static int (*ALSA_snd_card_next)(int*);
- static int (*ALSA_snd_ctl_open)(snd_ctl_t **,const char *,int);
- static int (*ALSA_snd_ctl_close)(snd_ctl_t *);
- static int (*ALSA_snd_ctl_card_info)(snd_ctl_t *, snd_ctl_card_info_t *);
- static int (*ALSA_snd_ctl_pcm_next_device)(snd_ctl_t *, int *);
- static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
- static void (*ALSA_snd_pcm_info_set_device)(snd_pcm_info_t *, unsigned int);
- static void (*ALSA_snd_pcm_info_set_subdevice)(snd_pcm_info_t *, unsigned int);
- static void (*ALSA_snd_pcm_info_set_stream)(snd_pcm_info_t *, snd_pcm_stream_t);
- static int (*ALSA_snd_ctl_pcm_info)(snd_ctl_t *, snd_pcm_info_t *);
- static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
- static const char *(*ALSA_snd_ctl_card_info_get_id)(const snd_ctl_card_info_t *);
- static const char *(*ALSA_snd_pcm_info_get_name)(const snd_pcm_info_t *);
- static const char *(*ALSA_snd_pcm_info_get_subdevice_name)(const snd_pcm_info_t *);
- static const char *(*ALSA_snd_ctl_card_info_get_name)(const snd_ctl_card_info_t *);
- static void (*ALSA_snd_ctl_card_info_clear)(snd_ctl_card_info_t *);
- static int (*ALSA_snd_pcm_hw_free)(snd_pcm_t *);
- static int (*ALSA_snd_pcm_hw_params_set_channels_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *);
- static snd_pcm_chmap_query_t **(*ALSA_snd_pcm_query_chmaps)(snd_pcm_t *pcm);
- static void (*ALSA_snd_pcm_free_chmaps)(snd_pcm_chmap_query_t **maps);
- static int (*ALSA_snd_pcm_set_chmap)(snd_pcm_t *, const snd_pcm_chmap_t *);
- static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *, size_t, char *);
- #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
- #define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
- #define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
- static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
- static SDL_SharedObject *alsa_handle = NULL;
- static bool load_alsa_sym(const char *fn, void **addr)
- {
- *addr = SDL_LoadFunction(alsa_handle, fn);
- if (!*addr) {
- // Don't call SDL_SetError(): SDL_LoadFunction already did.
- return false;
- }
- return true;
- }
- // cast funcs to char* first, to please GCC's strict aliasing rules.
- #define SDL_ALSA_SYM(x) \
- if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \
- return false
- #else
- #define SDL_ALSA_SYM(x) ALSA_##x = x
- #endif
- static bool load_alsa_syms(void)
- {
- SDL_ALSA_SYM(snd_pcm_open);
- SDL_ALSA_SYM(snd_pcm_close);
- SDL_ALSA_SYM(snd_pcm_start);
- SDL_ALSA_SYM(snd_pcm_writei);
- SDL_ALSA_SYM(snd_pcm_readi);
- SDL_ALSA_SYM(snd_pcm_recover);
- SDL_ALSA_SYM(snd_pcm_prepare);
- SDL_ALSA_SYM(snd_pcm_drain);
- SDL_ALSA_SYM(snd_strerror);
- SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
- SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
- SDL_ALSA_SYM(snd_pcm_hw_params_copy);
- SDL_ALSA_SYM(snd_pcm_hw_params_any);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
- SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
- SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_min);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_first);
- SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
- SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
- SDL_ALSA_SYM(snd_pcm_hw_params);
- SDL_ALSA_SYM(snd_pcm_sw_params_current);
- SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
- SDL_ALSA_SYM(snd_pcm_sw_params);
- SDL_ALSA_SYM(snd_pcm_nonblock);
- SDL_ALSA_SYM(snd_pcm_wait);
- SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
- SDL_ALSA_SYM(snd_pcm_reset);
- SDL_ALSA_SYM(snd_device_name_hint);
- SDL_ALSA_SYM(snd_device_name_get_hint);
- SDL_ALSA_SYM(snd_device_name_free_hint);
- SDL_ALSA_SYM(snd_pcm_avail);
- SDL_ALSA_SYM(snd_ctl_card_info_sizeof);
- SDL_ALSA_SYM(snd_pcm_info_sizeof);
- SDL_ALSA_SYM(snd_card_next);
- SDL_ALSA_SYM(snd_ctl_open);
- SDL_ALSA_SYM(snd_ctl_close);
- SDL_ALSA_SYM(snd_ctl_card_info);
- SDL_ALSA_SYM(snd_ctl_pcm_next_device);
- SDL_ALSA_SYM(snd_pcm_info_get_subdevices_count);
- SDL_ALSA_SYM(snd_pcm_info_set_device);
- SDL_ALSA_SYM(snd_pcm_info_set_subdevice);
- SDL_ALSA_SYM(snd_pcm_info_set_stream);
- SDL_ALSA_SYM(snd_ctl_pcm_info);
- SDL_ALSA_SYM(snd_pcm_info_get_subdevices_count);
- SDL_ALSA_SYM(snd_ctl_card_info_get_id);
- SDL_ALSA_SYM(snd_pcm_info_get_name);
- SDL_ALSA_SYM(snd_pcm_info_get_subdevice_name);
- SDL_ALSA_SYM(snd_ctl_card_info_get_name);
- SDL_ALSA_SYM(snd_ctl_card_info_clear);
- SDL_ALSA_SYM(snd_pcm_hw_free);
- SDL_ALSA_SYM(snd_pcm_hw_params_set_channels_near);
- SDL_ALSA_SYM(snd_pcm_query_chmaps);
- SDL_ALSA_SYM(snd_pcm_free_chmaps);
- SDL_ALSA_SYM(snd_pcm_set_chmap);
- SDL_ALSA_SYM(snd_pcm_chmap_print);
- return true;
- }
- #undef SDL_ALSA_SYM
- #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
- static void UnloadALSALibrary(void)
- {
- if (alsa_handle) {
- SDL_UnloadObject(alsa_handle);
- alsa_handle = NULL;
- }
- }
- static bool LoadALSALibrary(void)
- {
- bool retval = true;
- if (!alsa_handle) {
- alsa_handle = SDL_LoadObject(alsa_library);
- if (!alsa_handle) {
- retval = false;
- // Don't call SDL_SetError(): SDL_LoadObject already did.
- } else {
- retval = load_alsa_syms();
- if (!retval) {
- UnloadALSALibrary();
- }
- }
- }
- return retval;
- }
- #else
- static void UnloadALSALibrary(void)
- {
- }
- static bool LoadALSALibrary(void)
- {
- load_alsa_syms();
- return true;
- }
- #endif // SDL_AUDIO_DRIVER_ALSA_DYNAMIC
- static const char *ALSA_device_prefix = NULL;
- static void ALSA_guess_device_prefix(void)
- {
- if (ALSA_device_prefix) {
- return; // already calculated.
- }
- // Apparently there are several different ways that ALSA lists
- // actual hardware. It could be prefixed with "hw:" or "default:"
- // or "sysdefault:" and maybe others. Go through the list and see
- // if we can find a preferred prefix for the system.
- static const char *const prefixes[] = {
- "hw:", "sysdefault:", "default:"
- };
- void **hints = NULL;
- if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) {
- for (int i = 0; hints[i]; i++) {
- char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
- if (name) {
- for (int j = 0; j < SDL_arraysize(prefixes); j++) {
- const char *prefix = prefixes[j];
- const size_t prefixlen = SDL_strlen(prefix);
- if (SDL_strncmp(name, prefix, prefixlen) == 0) {
- ALSA_device_prefix = prefix;
- break;
- }
- }
- free(name); // This should NOT be SDL_free()
- if (ALSA_device_prefix) {
- break;
- }
- }
- }
- }
- if (!ALSA_device_prefix) {
- ALSA_device_prefix = prefixes[0]; // oh well.
- }
- LOGDEBUG("device prefix is probably '%s'", ALSA_device_prefix);
- }
- typedef struct ALSA_Device
- {
- // the unicity key is the couple (id,recording)
- char *id; // empty means canonical default
- char *name;
- bool recording;
- struct ALSA_Device *next;
- } ALSA_Device;
- static const ALSA_Device default_playback_handle = {
- "",
- "default",
- false,
- NULL
- };
- static const ALSA_Device default_recording_handle = {
- "",
- "default",
- true,
- NULL
- };
- // TODO: Figure out the "right"(TM) way. For the moment we presume that if a system is using a
- // software mixer for application audio sharing which is not the linux native alsa[dmix], for
- // instance jack/pulseaudio2[pipewire]/pulseaudio1/esound/etc, we expect the system integrators did
- // configure the canonical default to the right alsa PCM plugin for their software mixer.
- //
- // All the above may be completely wrong.
- static char *get_pcm_str(void *handle)
- {
- SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3.
- ALSA_Device *dev = (ALSA_Device *)handle;
- char *pcm_str = NULL;
- if (SDL_strlen(dev->id) == 0) {
- // If the user does not want to go thru the default PCM or the canonical default, the
- // the configuration space being _massive_, give the user the ability to specify
- // its own PCMs using environment variables. It will have to fit SDL constraints though.
- const char *devname = SDL_GetHint(dev->recording ? SDL_HINT_AUDIO_ALSA_DEFAULT_RECORDING_DEVICE : SDL_HINT_AUDIO_ALSA_DEFAULT_PLAYBACK_DEVICE);
- if (!devname) {
- devname = SDL_GetHint(SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE);
- if (!devname) {
- devname = "default";
- }
- }
- pcm_str = SDL_strdup(devname);
- } else {
- SDL_asprintf(&pcm_str, "%sCARD=%s", ALSA_device_prefix, dev->id);
- }
- return pcm_str;
- }
- // This function waits until it is possible to write a full sound buffer
- static bool ALSA_WaitDevice(SDL_AudioDevice *device)
- {
- const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq);
- const int delay = SDL_max(fulldelay, 10);
- while (!SDL_GetAtomicInt(&device->shutdown)) {
- const int rc = ALSA_snd_pcm_wait(device->hidden->pcm, delay);
- if (rc < 0 && (rc != -EAGAIN)) {
- const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
- if (status < 0) {
- // Hmm, not much we can do - abort
- SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
- return false;
- }
- continue;
- }
- if (rc > 0) {
- break; // ready to go!
- }
- // Timed out! Make sure we aren't shutting down and then wait again.
- }
- return true;
- }
- static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
- {
- SDL_assert(buffer == device->hidden->mixbuf);
- Uint8 *sample_buf = (Uint8 *) buffer; // !!! FIXME: deal with this without casting away constness
- const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
- snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size);
- while ((frames_left > 0) && !SDL_GetAtomicInt(&device->shutdown)) {
- const int rc = ALSA_snd_pcm_writei(device->hidden->pcm, sample_buf, frames_left);
- //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));
- SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space.
- if (rc < 0) {
- 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!
- const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
- if (status < 0) {
- // Hmm, not much we can do - abort
- SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc));
- return false;
- }
- continue;
- }
- sample_buf += rc * frame_size;
- frames_left -= rc;
- }
- return true;
- }
- static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
- {
- snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm);
- if (rc <= 0) {
- // Wait a bit and try again, maybe the hardware isn't quite ready yet?
- SDL_Delay(1);
- rc = ALSA_snd_pcm_avail(device->hidden->pcm);
- if (rc <= 0) {
- // We'll catch it next time
- *buffer_size = 0;
- return NULL;
- }
- }
- const int requested_frames = SDL_min(device->sample_frames, rc);
- const int requested_bytes = requested_frames * SDL_AUDIO_FRAMESIZE(device->spec);
- SDL_assert(requested_bytes <= *buffer_size);
- //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA GETDEVICEBUF: NEED %d BYTES", requested_bytes);
- *buffer_size = requested_bytes;
- return device->hidden->mixbuf;
- }
- static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
- {
- const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
- SDL_assert((buflen % frame_size) == 0);
- const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm);
- const int total_frames = SDL_min(buflen / frame_size, total_available);
- const int rc = ALSA_snd_pcm_readi(device->hidden->pcm, buffer, total_frames);
- 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!
- if (rc < 0) {
- const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
- if (status < 0) {
- // Hmm, not much we can do - abort
- SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc));
- return -1;
- }
- return 0; // go back to WaitDevice and try again.
- }
- //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: recorded %d bytes", rc * frame_size);
- return rc * frame_size;
- }
- static void ALSA_FlushRecording(SDL_AudioDevice *device)
- {
- ALSA_snd_pcm_reset(device->hidden->pcm);
- }
- static void ALSA_CloseDevice(SDL_AudioDevice *device)
- {
- if (device->hidden) {
- if (device->hidden->pcm) {
- // Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that.
- SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2);
- ALSA_snd_pcm_close(device->hidden->pcm);
- }
- SDL_free(device->hidden->mixbuf);
- SDL_free(device->hidden);
- }
- }
- // To make easier to track parameters during the whole alsa pcm configuration:
- struct ALSA_pcm_cfg_ctx {
- SDL_AudioDevice *device;
- snd_pcm_hw_params_t *hwparams;
- snd_pcm_sw_params_t *swparams;
- SDL_AudioFormat matched_sdl_format;
- unsigned int chans_n;
- unsigned int target_chans_n;
- unsigned int rate;
- snd_pcm_uframes_t persize; // alsa period size, SDL audio device sample_frames
- snd_pcm_chmap_query_t **chmap_queries;
- unsigned int sdl_chmap[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
- unsigned int alsa_chmap_installed[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
- unsigned int periods;
- };
- // The following are SDL channel maps with alsa position values, from 0 channels to 8 channels.
- // See SDL3/SDL_audio.h
- // Strictly speaking those are "parameters" of channel maps, like alsa hwparams and swparams, they
- // have to be "reduced/refined" until an exact channel map. Only the 6 channels map requires such
- // "reduction/refine".
- static enum snd_pcm_chmap_position sdl_channel_maps[SDL_AUDIO_ALSA__SDL_CHMAPS_N][SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX] = {
- // 0 channels
- {
- 0
- },
- // 1 channel
- {
- SND_CHMAP_MONO,
- },
- // 2 channels
- {
- SND_CHMAP_FL,
- SND_CHMAP_FR,
- },
- // 3 channels
- {
- SND_CHMAP_FL,
- SND_CHMAP_FR,
- SND_CHMAP_LFE,
- },
- // 4 channels
- {
- SND_CHMAP_FL,
- SND_CHMAP_FR,
- SND_CHMAP_RL,
- SND_CHMAP_RR,
- },
- // 5 channels
- {
- SND_CHMAP_FL,
- SND_CHMAP_FR,
- SND_CHMAP_LFE,
- SND_CHMAP_RL,
- SND_CHMAP_RR,
- },
- // 6 channels
- // XXX: here we encode not a uniq channel map but a set of channel maps. We will reduce it each
- // time we are going to work with an alsa 6 channels map.
- {
- SND_CHMAP_FL,
- SND_CHMAP_FR,
- SND_CHMAP_FC,
- SND_CHMAP_LFE,
- // The 2 following channel positions are (SND_CHMAP_SL,SND_CHMAP_SR) or
- // (SND_CHMAP_RL,SND_CHMAP_RR)
- SND_CHMAP_UNKNOWN,
- SND_CHMAP_UNKNOWN,
- },
- // 7 channels
- {
- SND_CHMAP_FL,
- SND_CHMAP_FR,
- SND_CHMAP_FC,
- SND_CHMAP_LFE,
- SND_CHMAP_RC,
- SND_CHMAP_SL,
- SND_CHMAP_SR,
- },
- // 8 channels
- {
- SND_CHMAP_FL,
- SND_CHMAP_FR,
- SND_CHMAP_FC,
- SND_CHMAP_LFE,
- SND_CHMAP_RL,
- SND_CHMAP_RR,
- SND_CHMAP_SL,
- SND_CHMAP_SR,
- },
- };
- // Helper for the function right below.
- static bool has_pos(unsigned int *chmap, unsigned int pos)
- {
- for (unsigned int chan_idx = 0; ; chan_idx++) {
- if (chan_idx == 6) {
- return false;
- }
- if (chmap[chan_idx] == pos) {
- return true;
- }
- }
- SDL_assert(!"Shouldn't hit this code.");
- return false;
- }
- // XXX: Each time we are going to work on an alsa 6 channels map, we must reduce the set of channel
- // maps which is encoded in sdl_channel_maps[6] to a uniq one.
- #define HAVE_NONE 0
- #define HAVE_REAR 1
- #define HAVE_SIDE 2
- #define HAVE_BOTH 3
- static void sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(unsigned int *sdl_6chans, unsigned int *alsa_6chans)
- {
- // For alsa channel maps with 6 channels and with SND_CHMAP_FL,SND_CHMAP_FR,SND_CHMAP_FC,
- // SND_CHMAP_LFE, reduce our 6 channels maps to a uniq one.
- if ( !has_pos(alsa_6chans, SND_CHMAP_FL) ||
- !has_pos(alsa_6chans, SND_CHMAP_FR) ||
- !has_pos(alsa_6chans, SND_CHMAP_FC) ||
- !has_pos(alsa_6chans, SND_CHMAP_LFE)) {
- sdl_6chans[4] = SND_CHMAP_UNKNOWN;
- sdl_6chans[5] = SND_CHMAP_UNKNOWN;
- LOGDEBUG("6channels:unsupported channel map");
- return;
- }
- unsigned int state = HAVE_NONE;
- for (unsigned int chan_idx = 0; chan_idx < 6; chan_idx++) {
- if ((alsa_6chans[chan_idx] == SND_CHMAP_SL) || (alsa_6chans[chan_idx] == SND_CHMAP_SR)) {
- if (state == HAVE_NONE) {
- state = HAVE_SIDE;
- } else if (state == HAVE_REAR) {
- state = HAVE_BOTH;
- break;
- }
- } else if ((alsa_6chans[chan_idx] == SND_CHMAP_RL) || (alsa_6chans[chan_idx] == SND_CHMAP_RR)) {
- if (state == HAVE_NONE) {
- state = HAVE_REAR;
- } else if (state == HAVE_SIDE) {
- state = HAVE_BOTH;
- break;
- }
- }
- }
- if ((state == HAVE_BOTH) || (state == HAVE_NONE)) {
- sdl_6chans[4] = SND_CHMAP_UNKNOWN;
- sdl_6chans[5] = SND_CHMAP_UNKNOWN;
- LOGDEBUG("6channels:unsupported channel map");
- } else if (state == HAVE_REAR) {
- sdl_6chans[4] = SND_CHMAP_RL;
- sdl_6chans[5] = SND_CHMAP_RR;
- LOGDEBUG("6channels:sdl map set to rear");
- } else { // state == HAVE_SIDE
- sdl_6chans[4] = SND_CHMAP_SL;
- sdl_6chans[5] = SND_CHMAP_SR;
- LOGDEBUG("6channels:sdl map set to side");
- }
- }
- #undef HAVE_NONE
- #undef HAVE_REAR
- #undef HAVE_SIDE
- #undef HAVE_BOTH
- static void swizzle_map_compute_alsa_subscan(struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, unsigned int sdl_pos_idx)
- {
- swizzle_map[sdl_pos_idx] = -1;
- for (unsigned int alsa_pos_idx = 0; ; alsa_pos_idx++) {
- SDL_assert(alsa_pos_idx != ctx->chans_n); // no 0 channels or not found matching position should happen here (actually enforce playback/recording symmetry).
- if (ctx->alsa_chmap_installed[alsa_pos_idx] == ctx->sdl_chmap[sdl_pos_idx]) {
- LOGDEBUG("swizzle SDL %u <-> alsa %u", sdl_pos_idx,alsa_pos_idx);
- swizzle_map[sdl_pos_idx] = (int) alsa_pos_idx;
- return;
- }
- }
- }
- // XXX: this must stay playback/recording symetric.
- static void swizzle_map_compute(struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle)
- {
- *needs_swizzle = false;
- for (unsigned int sdl_pos_idx = 0; sdl_pos_idx != ctx->chans_n; sdl_pos_idx++) {
- swizzle_map_compute_alsa_subscan(ctx, swizzle_map, sdl_pos_idx);
- if (swizzle_map[sdl_pos_idx] != sdl_pos_idx) {
- *needs_swizzle = true;
- }
- }
- }
- #define CHMAP_INSTALLED 0
- #define CHANS_N_NEXT 1
- #define CHMAP_NOT_FOUND 2
- // Should always be a queried alsa channel map unless the queried alsa channel map was of type VAR,
- // namely we can program the channel positions directly from the SDL channel map.
- static int alsa_chmap_install(struct ALSA_pcm_cfg_ctx *ctx, unsigned int *chmap)
- {
- bool isstack;
- snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t*)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack);
- if (!chmap_to_install) {
- return -1;
- }
- chmap_to_install->channels = ctx->chans_n;
- SDL_memcpy(chmap_to_install->pos, chmap, sizeof (unsigned int) * ctx->chans_n);
- #if SDL_ALSA_DEBUG
- char logdebug_chmap_str[128];
- ALSA_snd_pcm_chmap_print(chmap_to_install,sizeof(logdebug_chmap_str),logdebug_chmap_str);
- LOGDEBUG("channel map to install:%s",logdebug_chmap_str);
- #endif
- int status = ALSA_snd_pcm_set_chmap(ctx->device->hidden->pcm, chmap_to_install);
- if (status < 0) {
- SDL_SetError("ALSA: failed to install channel map: %s", ALSA_snd_strerror(status));
- return -1;
- }
- SDL_memcpy(ctx->alsa_chmap_installed, chmap, ctx->chans_n * sizeof (unsigned int));
- SDL_small_free(chmap_to_install, isstack);
- return CHMAP_INSTALLED;
- }
- // We restrict the alsa channel maps because in the unordered matches we do only simple accounting.
- // In the end, this will handle mostly alsa channel maps with more than one SND_CHMAP_NA position fillers.
- static bool alsa_chmap_has_duplicate_position(struct ALSA_pcm_cfg_ctx *ctx, unsigned int *pos)
- {
- if (ctx->chans_n < 2) {// we need at least 2 positions
- LOGDEBUG("channel map:no duplicate");
- return false;
- }
- for (unsigned int chan_idx = 1; chan_idx != ctx->chans_n; chan_idx++) {
- for (unsigned int seen_idx = 0; seen_idx != chan_idx; seen_idx++) {
- if (pos[seen_idx] == pos[chan_idx]) {
- LOGDEBUG("channel map:have duplicate");
- return true;
- }
- }
- }
- LOGDEBUG("channel map:no duplicate");
- return false;
- }
- static int alsa_chmap_cfg_ordered_fixed_or_paired(struct ALSA_pcm_cfg_ctx *ctx)
- {
- for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
- if ( ((*chmap_query)->map.channels != ctx->chans_n) ||
- (((*chmap_query)->type != SND_CHMAP_TYPE_FIXED) && ((*chmap_query)->type != SND_CHMAP_TYPE_PAIRED)) ) {
- continue;
- }
- #if SDL_ALSA_DEBUG
- char logdebug_chmap_str[128];
- ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
- LOGDEBUG("channel map:ordered:fixed|paired:%s",logdebug_chmap_str);
- #endif
- for (int i = 0; i < ctx->chans_n; i++) {
- ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
- }
- unsigned int *alsa_chmap = (*chmap_query)->map.pos;
- if (ctx->chans_n == 6) {
- sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
- }
- if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
- continue;
- }
- for (unsigned int chan_idx = 0; ctx->sdl_chmap[chan_idx] == alsa_chmap[chan_idx]; chan_idx++) {
- if (chan_idx == ctx->chans_n) {
- return alsa_chmap_install(ctx, alsa_chmap);
- }
- }
- }
- return CHMAP_NOT_FOUND;
- }
- // Here, the alsa channel positions can be programmed in the alsa frame (cf HDMI).
- // If the alsa channel map is VAR, we only check we have the unordered set of channel positions we
- // are looking for.
- static int alsa_chmap_cfg_ordered_var(struct ALSA_pcm_cfg_ctx *ctx)
- {
- for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
- if (((*chmap_query)->map.channels != ctx->chans_n) || ((*chmap_query)->type != SND_CHMAP_TYPE_VAR)) {
- continue;
- }
- #if SDL_ALSA_DEBUG
- char logdebug_chmap_str[128];
- ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
- LOGDEBUG("channel map:ordered:var:%s",logdebug_chmap_str);
- #endif
- for (int i = 0; i < ctx->chans_n; i++) {
- ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
- }
- unsigned int *alsa_chmap = (*chmap_query)->map.pos;
- if (ctx->chans_n == 6) {
- sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
- }
- if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
- continue;
- }
- unsigned int pos_matches_n = 0;
- for (unsigned int chan_idx = 0; chan_idx != ctx->chans_n; chan_idx++) {
- for (unsigned int subscan_chan_idx = 0; subscan_chan_idx != ctx->chans_n; subscan_chan_idx++) {
- if (ctx->sdl_chmap[chan_idx] == alsa_chmap[subscan_chan_idx]) {
- pos_matches_n++;
- break;
- }
- }
- }
- if (pos_matches_n == ctx->chans_n) {
- return alsa_chmap_install(ctx, ctx->sdl_chmap); // XXX: we program the SDL chmap here
- }
- }
- return CHMAP_NOT_FOUND;
- }
- static int alsa_chmap_cfg_ordered(struct ALSA_pcm_cfg_ctx *ctx)
- {
- const int status = alsa_chmap_cfg_ordered_fixed_or_paired(ctx);
- return (status != CHMAP_NOT_FOUND) ? status : alsa_chmap_cfg_ordered_var(ctx);
- }
- // In the unordered case, we are just interested to get the same unordered set of alsa channel
- // positions than in the SDL channel map since we will swizzle (no duplicate channel position).
- static int alsa_chmap_cfg_unordered(struct ALSA_pcm_cfg_ctx *ctx)
- {
- for (snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; *chmap_query; chmap_query++) {
- if ( ((*chmap_query)->map.channels != ctx->chans_n) ||
- (((*chmap_query)->type != SND_CHMAP_TYPE_FIXED) && ((*chmap_query)->type != SND_CHMAP_TYPE_PAIRED)) ) {
- continue;
- }
- #if SDL_ALSA_DEBUG
- char logdebug_chmap_str[128];
- ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
- LOGDEBUG("channel map:unordered:fixed|paired:%s",logdebug_chmap_str);
- #endif
- for (int i = 0; i < ctx->chans_n; i++) {
- ctx->sdl_chmap[i] = (unsigned int) sdl_channel_maps[ctx->chans_n][i];
- }
- unsigned int *alsa_chmap = (*chmap_query)->map.pos;
- if (ctx->chans_n == 6) {
- sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(ctx->sdl_chmap, alsa_chmap);
- }
- if (alsa_chmap_has_duplicate_position(ctx, alsa_chmap)) {
- continue;
- }
- unsigned int pos_matches_n = 0;
- for (unsigned int chan_idx = 0; chan_idx != ctx->chans_n; chan_idx++) {
- for (unsigned int subscan_chan_idx = 0; subscan_chan_idx != ctx->chans_n; subscan_chan_idx++) {
- if (ctx->sdl_chmap[chan_idx] == alsa_chmap[subscan_chan_idx]) {
- pos_matches_n++;
- break;
- }
- }
- }
- if (pos_matches_n == ctx->chans_n) {
- return alsa_chmap_install(ctx, alsa_chmap);
- }
- }
- return CHMAP_NOT_FOUND;
- }
- static int alsa_chmap_cfg(struct ALSA_pcm_cfg_ctx *ctx)
- {
- int status;
- ctx->chmap_queries = ALSA_snd_pcm_query_chmaps(ctx->device->hidden->pcm);
- if (ctx->chmap_queries == NULL) {
- // We couldn't query the channel map, assume no swizzle necessary
- LOGDEBUG("couldn't query channel map, swizzling off");
- return CHMAP_INSTALLED;
- }
- //----------------------------------------------------------------------------------------------
- status = alsa_chmap_cfg_ordered(ctx); // we prefer first channel maps we don't need to swizzle
- if (status == CHMAP_INSTALLED) {
- LOGDEBUG("swizzling off");
- return status;
- } else if (status != CHMAP_NOT_FOUND) {
- return status; // < 0 error code
- }
- // Fall-thru
- //----------------------------------------------------------------------------------------------
- status = alsa_chmap_cfg_unordered(ctx); // those we will have to swizzle
- if (status == CHMAP_INSTALLED) {
- LOGDEBUG("swizzling on");
- bool isstack;
- int *swizzle_map = SDL_small_alloc(int, ctx->chans_n, &isstack);
- if (!swizzle_map) {
- status = -1;
- } else {
- bool needs_swizzle;
- swizzle_map_compute(ctx, swizzle_map, &needs_swizzle); // fine grained swizzle configuration
- if (needs_swizzle) {
- // let SDL's swizzler handle this one.
- ctx->device->chmap = SDL_ChannelMapDup(swizzle_map, ctx->chans_n);
- if (!ctx->device->chmap) {
- status = -1;
- }
- }
- SDL_small_free(swizzle_map, isstack);
- }
- }
- if (status == CHMAP_NOT_FOUND) {
- return CHANS_N_NEXT;
- }
- return status; // < 0 error code
- }
- #define CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N 0 // target more hardware pressure
- #define CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N 1 // target less hardware pressure
- #define CHANS_N_CONFIGURED 0
- #define CHANS_N_NOT_CONFIGURED 1
- static int ALSA_pcm_cfg_hw_chans_n_scan(struct ALSA_pcm_cfg_ctx *ctx, unsigned int mode)
- {
- unsigned int target_chans_n = ctx->device->spec.channels; // we start at what was specified
- if (mode == CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N) {
- target_chans_n--;
- }
- while (true) {
- if (mode == CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N) {
- if (target_chans_n > SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX) {
- return CHANS_N_NOT_CONFIGURED;
- }
- // else: CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
- } else if (target_chans_n == 0) {
- return CHANS_N_NOT_CONFIGURED;
- }
- LOGDEBUG("target chans_n is %u", target_chans_n);
- int status = ALSA_snd_pcm_hw_params_any(ctx->device->hidden->pcm, ctx->hwparams);
- if (status < 0) {
- SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status));
- return -1;
- }
- // SDL only uses interleaved sample output
- status = ALSA_snd_pcm_hw_params_set_access(ctx->device->hidden->pcm, ctx->hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
- if (status < 0) {
- SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status));
- return -1;
- }
- // Try for a closest match on audio format
- snd_pcm_format_t alsa_format = 0;
- const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(ctx->device->spec.format);
- ctx->matched_sdl_format = 0;
- while ((ctx->matched_sdl_format = *(closefmts++)) != 0) {
- // XXX: we are forcing the same endianness, namely we won't need byte swapping upon
- // writing/reading to/from the SDL audio buffer.
- switch (ctx->matched_sdl_format) {
- case SDL_AUDIO_U8:
- alsa_format = SND_PCM_FORMAT_U8;
- break;
- case SDL_AUDIO_S8:
- alsa_format = SND_PCM_FORMAT_S8;
- break;
- case SDL_AUDIO_S16LE:
- alsa_format = SND_PCM_FORMAT_S16_LE;
- break;
- case SDL_AUDIO_S16BE:
- alsa_format = SND_PCM_FORMAT_S16_BE;
- break;
- case SDL_AUDIO_S32LE:
- alsa_format = SND_PCM_FORMAT_S32_LE;
- break;
- case SDL_AUDIO_S32BE:
- alsa_format = SND_PCM_FORMAT_S32_BE;
- break;
- case SDL_AUDIO_F32LE:
- alsa_format = SND_PCM_FORMAT_FLOAT_LE;
- break;
- case SDL_AUDIO_F32BE:
- alsa_format = SND_PCM_FORMAT_FLOAT_BE;
- break;
- default:
- continue;
- }
- if (ALSA_snd_pcm_hw_params_set_format(ctx->device->hidden->pcm, ctx->hwparams, alsa_format) >= 0) {
- break;
- }
- }
- if (ctx->matched_sdl_format == 0) {
- SDL_SetError("ALSA: Unsupported audio format: %s", ALSA_snd_strerror(status));
- return -1;
- }
- // let alsa approximate the number of channels
- ctx->chans_n = target_chans_n;
- status = ALSA_snd_pcm_hw_params_set_channels_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->chans_n));
- if (status < 0) {
- SDL_SetError("ALSA: Couldn't set audio channels: %s", ALSA_snd_strerror(status));
- return -1;
- }
- // let alsa approximate the audio rate
- ctx->rate = ctx->device->spec.freq;
- status = ALSA_snd_pcm_hw_params_set_rate_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->rate), NULL);
- if (status < 0) {
- SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status));
- return -1;
- }
- // let approximate the period size to the requested buffer size
- ctx->persize = ctx->device->sample_frames;
- status = ALSA_snd_pcm_hw_params_set_period_size_near(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->persize), NULL);
- if (status < 0) {
- SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status));
- return -1;
- }
- // let approximate the minimun number of periods per buffer (we target a double buffer)
- ctx->periods = 2;
- status = ALSA_snd_pcm_hw_params_set_periods_min(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL);
- if (status < 0) {
- SDL_SetError("ALSA: Couldn't set the minimum number of periods per buffer: %s", ALSA_snd_strerror(status));
- return -1;
- }
- // restrict the number of periods per buffer to an approximation of the approximated minimum
- // number of periods per buffer done right above
- status = ALSA_snd_pcm_hw_params_set_periods_first(ctx->device->hidden->pcm, ctx->hwparams, &(ctx->periods), NULL);
- if (status < 0) {
- SDL_SetError("ALSA: Couldn't set the number of periods per buffer: %s", ALSA_snd_strerror(status));
- return -1;
- }
- // install the hw parameters
- status = ALSA_snd_pcm_hw_params(ctx->device->hidden->pcm, ctx->hwparams);
- if (status < 0) {
- SDL_SetError("ALSA: installation of hardware parameter failed: %s", ALSA_snd_strerror(status));
- return -1;
- }
- //==========================================================================================
- // Here the alsa pcm is in SND_PCM_STATE_PREPARED state, let's figure out a good fit for
- // SDL channel map, it may request to change the target number of channels though.
- status = alsa_chmap_cfg(ctx);
- if (status < 0) {
- return status; // we forward the SDL error
- } else if (status == CHMAP_INSTALLED) {
- return CHANS_N_CONFIGURED; // we are finished here
- }
- // status == CHANS_N_NEXT
- ALSA_snd_pcm_free_chmaps(ctx->chmap_queries);
- ALSA_snd_pcm_hw_free(ctx->device->hidden->pcm); // uninstall those hw params
- if (mode == CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N) {
- target_chans_n++;
- } else { // CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
- target_chans_n--;
- }
- }
- SDL_assert(!"Shouldn't reach this code.");
- return CHANS_N_NOT_CONFIGURED;
- }
- #undef CHMAP_INSTALLED
- #undef CHANS_N_NEXT
- #undef CHMAP_NOT_FOUND
- static bool ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx)
- {
- LOGDEBUG("target chans_n, equal or above requested chans_n mode");
- int status = ALSA_pcm_cfg_hw_chans_n_scan(ctx, CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N);
- if (status < 0) { // something went too wrong
- return false;
- } else if (status == CHANS_N_CONFIGURED) {
- return true;
- }
- // Here, status == CHANS_N_NOT_CONFIGURED
- LOGDEBUG("target chans_n, below requested chans_n mode");
- status = ALSA_pcm_cfg_hw_chans_n_scan(ctx, CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N);
- if (status < 0) { // something went too wrong
- return false;
- } else if (status == CHANS_N_CONFIGURED) {
- return true;
- }
- // Here, status == CHANS_N_NOT_CONFIGURED
- return SDL_SetError("ALSA: Coudn't configure targetting any SDL supported channel number");
- }
- #undef CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N
- #undef CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
- #undef CHANS_N_CONFIGURED
- #undef CHANS_N_NOT_CONFIGURED
- static bool ALSA_pcm_cfg_sw(struct ALSA_pcm_cfg_ctx *ctx)
- {
- int status;
- status = ALSA_snd_pcm_sw_params_current(ctx->device->hidden->pcm, ctx->swparams);
- if (status < 0) {
- return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
- }
- 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
- if (status < 0) {
- return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
- }
- status = ALSA_snd_pcm_sw_params_set_start_threshold(ctx->device->hidden->pcm, ctx->swparams, 1);
- if (status < 0) {
- return SDL_SetError("ALSA: Couldn't set start threshold: %s", ALSA_snd_strerror(status));
- }
- status = ALSA_snd_pcm_sw_params(ctx->device->hidden->pcm, ctx->swparams);
- if (status < 0) {
- return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
- }
- return true;
- }
- static bool ALSA_OpenDevice(SDL_AudioDevice *device)
- {
- const bool recording = device->recording;
- struct ALSA_pcm_cfg_ctx cfg_ctx; // used to track everything here
- char *pcm_str;
- int status = 0;
- //device->spec.channels = 8;
- //SDL_SetLogPriority(SDL_LOG_CATEGORY_AUDIO, SDL_LOG_PRIORITY_VERBOSE);
- LOGDEBUG("channels requested %u",device->spec.channels);
- // XXX: We do not use the SDL internal swizzler yet.
- device->chmap = NULL;
- SDL_zero(cfg_ctx);
- cfg_ctx.device = device;
- // Initialize all variables that we clean on shutdown
- cfg_ctx.device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*cfg_ctx.device->hidden));
- if (!cfg_ctx.device->hidden) {
- return false;
- }
- // Open the audio device
- pcm_str = get_pcm_str(cfg_ctx.device->handle);
- if (pcm_str == NULL) {
- goto err_free_device_hidden;
- }
- LOGDEBUG("PCM open '%s'", pcm_str);
- status = ALSA_snd_pcm_open(&cfg_ctx.device->hidden->pcm,
- pcm_str,
- recording ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
- SND_PCM_NONBLOCK);
- SDL_free(pcm_str);
- if (status < 0) {
- SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status));
- goto err_free_device_hidden;
- }
- // Now we need to configure the opened pcm as close as possible from the requested parameters we
- // can reasonably deal with (and that could change)
- snd_pcm_hw_params_alloca(&(cfg_ctx.hwparams));
- snd_pcm_sw_params_alloca(&(cfg_ctx.swparams));
- if (!ALSA_pcm_cfg_hw(&cfg_ctx)) { // alsa pcm "hardware" part of the pcm
- goto err_close_pcm;
- }
- // from here, we get only the alsa chmap queries in cfg_ctx to explicitely clean, hwparams is
- // uninstalled upon pcm closing
- // This is useful for debugging
- #if SDL_ALSA_DEBUG
- snd_pcm_uframes_t bufsize;
- ALSA_snd_pcm_hw_params_get_buffer_size(cfg_ctx.hwparams, &bufsize);
- SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
- "ALSA: period size = %ld, periods = %u, buffer size = %lu",
- cfg_ctx.persize, cfg_ctx.periods, bufsize);
- #endif
- if (!ALSA_pcm_cfg_sw(&cfg_ctx)) { // alsa pcm "software" part of the pcm
- goto err_cleanup_ctx;
- }
- // Now we can update the following parameters in the spec:
- cfg_ctx.device->spec.format = cfg_ctx.matched_sdl_format;
- cfg_ctx.device->spec.channels = cfg_ctx.chans_n;
- cfg_ctx.device->spec.freq = cfg_ctx.rate;
- cfg_ctx.device->sample_frames = cfg_ctx.persize;
- // Calculate the final parameters for this audio specification
- SDL_UpdatedAudioDeviceFormat(cfg_ctx.device);
- // Allocate mixing buffer
- if (!recording) {
- cfg_ctx.device->hidden->mixbuf = (Uint8 *)SDL_malloc(cfg_ctx.device->buffer_size);
- if (cfg_ctx.device->hidden->mixbuf == NULL) {
- goto err_cleanup_ctx;
- }
- SDL_memset(cfg_ctx.device->hidden->mixbuf, cfg_ctx.device->silence_value, cfg_ctx.device->buffer_size);
- }
- #if !SDL_ALSA_NON_BLOCKING
- if (!recording) {
- ALSA_snd_pcm_nonblock(cfg_ctx.device->hidden->pcm, 0);
- }
- #endif
- ALSA_snd_pcm_start(cfg_ctx.device->hidden->pcm);
- return true; // We're ready to rock and roll. :-)
- err_cleanup_ctx:
- ALSA_snd_pcm_free_chmaps(cfg_ctx.chmap_queries);
- err_close_pcm:
- ALSA_snd_pcm_close(cfg_ctx.device->hidden->pcm);
- err_free_device_hidden:
- SDL_free(cfg_ctx.device->hidden);
- cfg_ctx.device->hidden = NULL;
- return false;
- }
- static ALSA_Device *hotplug_devices = NULL;
- static int hotplug_device_process(snd_ctl_t *ctl, snd_ctl_card_info_t *ctl_card_info, int dev_idx,
- snd_pcm_stream_t direction, ALSA_Device **unseen, ALSA_Device **seen)
- {
- unsigned int subdevs_n = 1; // we have at least one subdevice (substream since the direction is a stream in alsa terminology)
- unsigned int subdev_idx = 0;
- const bool recording = direction == SND_PCM_STREAM_CAPTURE ? true : false; // used for the unicity of the device
- bool isstack;
- snd_pcm_info_t *pcm_info = (snd_pcm_info_t*)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack);
- SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof());
- while (true) {
- ALSA_snd_pcm_info_set_stream(pcm_info, direction);
- ALSA_snd_pcm_info_set_device(pcm_info, dev_idx);
- ALSA_snd_pcm_info_set_subdevice(pcm_info, subdev_idx); // we have at least one subdevice (substream) of index 0
- const int r = ALSA_snd_ctl_pcm_info(ctl, pcm_info);
- if (r < 0) {
- SDL_small_free(pcm_info, isstack);
- // first call to ALSA_snd_ctl_pcm_info
- if (subdev_idx == 0 && r == -ENOENT) { // no such direction/stream for this device
- return 0;
- }
- return -1;
- }
- if (subdev_idx == 0) {
- subdevs_n = ALSA_snd_pcm_info_get_subdevices_count(pcm_info);
- }
- // building the unseen list scanning the list of hotplug devices, if it is already there
- // using the id, move it to the seen list.
- ALSA_Device *unseen_prev_adev = NULL;
- ALSA_Device *adev;
- for (adev = *unseen; adev; adev = adev->next) {
- // the unicity key is the couple (id,recording)
- if ((SDL_strcmp(adev->id, ALSA_snd_ctl_card_info_get_id(ctl_card_info)) == 0) && (adev->recording == recording)) {
- // unchain from unseen
- if (*unseen == adev) { // head
- *unseen = adev->next;
- } else {
- unseen_prev_adev->next = adev->next;
- }
- // chain to seen
- adev->next = *seen;
- *seen = adev;
- break;
- }
- unseen_prev_adev = adev;
- }
- if (adev == NULL) { // newly seen device
- adev = SDL_calloc(1, sizeof(*adev));
- if (adev == NULL) {
- SDL_small_free(pcm_info, isstack);
- return -1;
- }
- adev->id = SDL_strdup(ALSA_snd_ctl_card_info_get_id(ctl_card_info));
- if (adev->id == NULL) {
- SDL_small_free(pcm_info, isstack);
- SDL_free(adev);
- return -1;
- }
- 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) {
- SDL_small_free(pcm_info, isstack);
- SDL_free(adev->id);
- SDL_free(adev);
- return -1;
- }
- if (direction == SND_PCM_STREAM_CAPTURE) {
- adev->recording = true;
- } else {
- adev->recording = false;
- }
- if (SDL_AddAudioDevice(recording, adev->name, NULL, adev) == NULL) {
- SDL_small_free(pcm_info, isstack);
- SDL_free(adev->id);
- SDL_free(adev->name);
- SDL_free(adev);
- return -1;
- }
- adev->next = *seen;
- *seen = adev;
- }
- subdev_idx++;
- if (subdev_idx == subdevs_n) {
- SDL_small_free(pcm_info, isstack);
- return 0;
- }
- SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof());
- }
- SDL_small_free(pcm_info, isstack);
- SDL_assert(!"Shouldn't reach this code");
- return -1;
- }
- static void ALSA_HotplugIteration(bool *has_default_output, bool *has_default_recording)
- {
- if (has_default_output != NULL) {
- *has_default_output = true;
- }
- if (has_default_recording != NULL) {
- *has_default_recording = true;
- }
- bool isstack;
- snd_ctl_card_info_t *ctl_card_info = (snd_ctl_card_info_t *) SDL_small_alloc(Uint8, ALSA_snd_ctl_card_info_sizeof(), &isstack);
- if (!ctl_card_info) {
- return; // oh well.
- }
- SDL_memset(ctl_card_info, 0, ALSA_snd_ctl_card_info_sizeof());
- snd_ctl_t *ctl = NULL;
- ALSA_Device *unseen = hotplug_devices;
- ALSA_Device *seen = NULL;
- int card_idx = -1;
- while (true) {
- int r = ALSA_snd_card_next(&card_idx);
- if (r < 0) {
- goto failed;
- } else if (card_idx == -1) {
- break;
- }
- char ctl_name[64];
- SDL_snprintf(ctl_name, sizeof (ctl_name), "%s%d", ALSA_device_prefix, card_idx); // card_idx >= 0
- LOGDEBUG("hotplug ctl_name = '%s'", ctl_name);
- r = ALSA_snd_ctl_open(&ctl, ctl_name, 0);
- if (r < 0) {
- continue;
- }
- r = ALSA_snd_ctl_card_info(ctl, ctl_card_info);
- if (r < 0) {
- goto failed;
- }
- int dev_idx = -1;
- while (true) {
- r = ALSA_snd_ctl_pcm_next_device(ctl, &dev_idx);
- if (r < 0) {
- goto failed;
- } else if (dev_idx == -1) {
- break;
- }
- r = hotplug_device_process(ctl, ctl_card_info, dev_idx, SND_PCM_STREAM_PLAYBACK, &unseen, &seen);
- if (r < 0) {
- goto failed;
- }
- r = hotplug_device_process(ctl, ctl_card_info, dev_idx, SND_PCM_STREAM_CAPTURE, &unseen, &seen);
- if (r < 0) {
- goto failed;
- }
- }
- ALSA_snd_ctl_close(ctl);
- ALSA_snd_ctl_card_info_clear(ctl_card_info);
- }
- // remove only the unseen devices
- while (unseen) {
- SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(unseen));
- SDL_free(unseen->name);
- SDL_free(unseen->id);
- ALSA_Device *next = unseen->next;
- SDL_free(unseen);
- unseen = next;
- }
- // update hotplug devices to be the seen devices
- hotplug_devices = seen;
- SDL_small_free(ctl_card_info, isstack);
- return;
- failed:
- if (ctl) {
- ALSA_snd_ctl_close(ctl);
- }
- // remove the unseen
- while (unseen) {
- SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(unseen));
- SDL_free(unseen->name);
- SDL_free(unseen->id);
- ALSA_Device *next = unseen->next;
- SDL_free(unseen);
- unseen = next;
- }
- // remove the seen
- while (seen) {
- SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(seen));
- SDL_free(seen->name);
- SDL_free(seen->id);
- ALSA_Device *next = seen->next;
- SDL_free(seen);
- seen = next;
- }
- hotplug_devices = NULL;
- SDL_small_free(ctl_card_info, isstack);
- }
- #if SDL_ALSA_HOTPLUG_THREAD
- static SDL_AtomicInt ALSA_hotplug_shutdown;
- static SDL_Thread *ALSA_hotplug_thread;
- static int SDLCALL ALSA_HotplugThread(void *arg)
- {
- SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_LOW);
- while (!SDL_GetAtomicInt(&ALSA_hotplug_shutdown)) {
- // Block awhile before checking again, unless we're told to stop.
- const Uint64 ticks = SDL_GetTicks() + 5000;
- while (!SDL_GetAtomicInt(&ALSA_hotplug_shutdown) && (SDL_GetTicks() < ticks)) {
- SDL_Delay(100);
- }
- ALSA_HotplugIteration(NULL, NULL); // run the check.
- }
- return 0;
- }
- #endif
- static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
- {
- ALSA_guess_device_prefix();
- // ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default
- // device here. It's the best we can do at this level.
- bool has_default_playback = false, has_default_recording = false;
- ALSA_HotplugIteration(&has_default_playback, &has_default_recording); // run once now before a thread continues to check.
- if (has_default_playback) {
- *default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void*)&default_playback_handle);
- }
- if (has_default_recording) {
- *default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void*)&default_recording_handle);
- }
- #if SDL_ALSA_HOTPLUG_THREAD
- SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0);
- ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
- // if the thread doesn't spin, oh well, you just don't get further hotplug events.
- #endif
- }
- static void ALSA_DeinitializeStart(void)
- {
- ALSA_Device *dev;
- ALSA_Device *next;
- #if SDL_ALSA_HOTPLUG_THREAD
- if (ALSA_hotplug_thread) {
- SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 1);
- SDL_WaitThread(ALSA_hotplug_thread, NULL);
- ALSA_hotplug_thread = NULL;
- }
- #endif
- // Shutting down! Clean up any data we've gathered.
- for (dev = hotplug_devices; dev; dev = next) {
- //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: at shutdown, removing %s device '%s'", dev->recording ? "recording" : "playback", dev->name);
- next = dev->next;
- SDL_free(dev->name);
- SDL_free(dev);
- }
- hotplug_devices = NULL;
- }
- static void ALSA_Deinitialize(void)
- {
- UnloadALSALibrary();
- }
- static bool ALSA_Init(SDL_AudioDriverImpl *impl)
- {
- if (!LoadALSALibrary()) {
- return false;
- }
- impl->DetectDevices = ALSA_DetectDevices;
- impl->OpenDevice = ALSA_OpenDevice;
- impl->WaitDevice = ALSA_WaitDevice;
- impl->GetDeviceBuf = ALSA_GetDeviceBuf;
- impl->PlayDevice = ALSA_PlayDevice;
- impl->CloseDevice = ALSA_CloseDevice;
- impl->DeinitializeStart = ALSA_DeinitializeStart;
- impl->Deinitialize = ALSA_Deinitialize;
- impl->WaitRecordingDevice = ALSA_WaitDevice;
- impl->RecordDevice = ALSA_RecordDevice;
- impl->FlushRecording = ALSA_FlushRecording;
- impl->HasRecordingSupport = true;
- return true;
- }
- AudioBootStrap ALSA_bootstrap = {
- "alsa", "ALSA PCM audio", ALSA_Init, false
- };
- #endif // SDL_AUDIO_DRIVER_ALSA
|