From a9e3f31ebed8ec67634b62b576537b202ce726dd Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 23 Jul 2024 13:17:56 -0700 Subject: [PATCH] Added support for ALSA dmix audio output (thanks @sylware!) Fixes https://github.com/libsdl-org/SDL/issues/8577 --- src/audio/alsa/SDL_alsa_audio.c | 1575 +++++++++++++++++++++++-------- src/audio/alsa/SDL_alsa_audio.h | 7 +- 2 files changed, 1170 insertions(+), 412 deletions(-) diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index e2827f418d4b1..a6d4c43133365 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -43,6 +43,10 @@ #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC #endif +#define loop for(;;) +#define LOGDEBUG(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO,"ALSA:" __VA_ARGS__) + +//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); @@ -81,10 +85,30 @@ 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 *); -#ifdef SND_CHMAP_API_VERSION -static snd_pcm_chmap_t *(*ALSA_snd_pcm_get_chmap)(snd_pcm_t *); -static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *map, size_t maxlen, char *buf); -#endif +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 @@ -151,10 +175,30 @@ static int load_alsa_syms(void) SDL_ALSA_SYM(snd_device_name_get_hint); SDL_ALSA_SYM(snd_device_name_free_hint); SDL_ALSA_SYM(snd_pcm_avail); -#ifdef SND_CHMAP_API_VERSION - SDL_ALSA_SYM(snd_pcm_get_chmap); + 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); -#endif return 0; } @@ -205,59 +249,281 @@ static int LoadALSALibrary(void) typedef struct ALSA_Device { + // the unicity key is the couple (id,recording) + char *id; // empty means canonical default char *name; - SDL_bool recording; struct ALSA_Device *next; + SDL_bool recording; } ALSA_Device; -static const ALSA_Device default_playback_handle = { +static const ALSA_Device default_output_handle = { + "", "default", - SDL_FALSE, - NULL + NULL, + SDL_FALSE }; static const ALSA_Device default_recording_handle = { + "", "default", - SDL_TRUE, - NULL + NULL, + SDL_TRUE }; -static const char *get_audio_device(void *handle, const int channels) +// 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; + size_t pcm_len; + char *pcm_str; - ALSA_Device *dev = (ALSA_Device *)handle; - if (SDL_strcmp(dev->name, "default") == 0) { - const char *device = SDL_getenv("AUDIODEV"); // Is there a standard variable name? - if (device) { - return device; - } else if (channels == 6) { - return "plug:surround51"; - } else if (channels == 4) { - return "plug:surround40"; - } - return "default"; + SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3. + dev = (ALSA_Device *)handle; + + // 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. + if (dev->recording) + pcm_str = (char*)SDL_getenv("SDL_AUDIO_ALSA_PCM_RECORDING"); + else + pcm_str = (char*)SDL_getenv("SDL_AUDIO_ALSA_PCM_PLAYBACK"); + if (pcm_str) + return SDL_strdup(pcm_str); + + if (SDL_strlen(dev->id) == 0) + pcm_str = SDL_strdup("default"); + else { +#define PCM_STR_FMT "default:CARD=%s" + pcm_len = (size_t)SDL_snprintf(0, 0, PCM_STR_FMT, dev->id); + + pcm_str = SDL_malloc(pcm_len + 1); + if (pcm_str != NULL) + SDL_snprintf(pcm_str, pcm_len + 1, PCM_STR_FMT, dev->id); +#undef PCM_STR_FMT } - - return dev->name; + return pcm_str; } +// SDL channel map with alsa names "FL FR" +// The literal names are SDL names. +// Faith: loading the whole frame in one shot may help naive compilers. +#define SWIZ2(T) \ + static void swizzle_alsa_channels_2_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \ + { \ + T *ptr = (T *)buffer; \ + Uint32 i; \ + for (i = 0; i < bufferlen; i++, ptr += 2) { \ + const T front_left = ptr[0]; \ + const T front_right = ptr[1]; \ + ptr[swizzle_map[0]] = front_left; \ + ptr[swizzle_map[1]] = front_right; \ + } \ + } +// SDL channel map with alsa names "FL FR LFE" +// The literal names are SDL names. +// Faith: loading the whole frame in one shot may help naive compilers. +#define SWIZ3(T) \ + static void swizzle_alsa_channels_3_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \ + { \ + T *ptr = (T *)buffer; \ + Uint32 i; \ + for (i = 0; i < bufferlen; i++, ptr += 3) { \ + const T front_left = ptr[0]; \ + const T front_right = ptr[1]; \ + const T subwoofer = ptr[2]; \ + ptr[swizzle_map[0]] = front_left; \ + ptr[swizzle_map[1]] = front_right; \ + ptr[swizzle_map[2]] = subwoofer; \ + } \ + } +// SDL channel map with alsa names "FL FR RL RR"; +// The literal names are SDL names. +// Faith: loading the whole frame in one shot may help naive compilers. +#define SWIZ4(T) \ + static void swizzle_alsa_channels_4_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \ + { \ + T *ptr = (T *)buffer; \ + Uint32 i; \ + for (i = 0; i < bufferlen; i++, ptr += 4) { \ + const T front_left = ptr[0]; \ + const T front_right = ptr[1]; \ + const T back_left = ptr[2]; \ + const T back_right = ptr[3]; \ + ptr[swizzle_map[0]] = front_left; \ + ptr[swizzle_map[1]] = front_right; \ + ptr[swizzle_map[2]] = back_left; \ + ptr[swizzle_map[3]] = back_right; \ + } \ + } +// SDL channel map with alsa names "FL FR LFE RL RR" +// The literal names are SDL names. +// Faith: loading the whole frame in one shot may help naive compilers. +#define SWIZ5(T) \ + static void swizzle_alsa_channels_5_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \ + { \ + T *ptr = (T *)buffer; \ + Uint32 i; \ + for (i = 0; i < bufferlen; i++, ptr += 5) { \ + const T front_left = ptr[0]; \ + const T front_right = ptr[1]; \ + const T subwoofer = ptr[2]; \ + const T back_left = ptr[3]; \ + const T back_right = ptr[4]; \ + ptr[swizzle_map[0]] = front_left; \ + ptr[swizzle_map[1]] = front_right; \ + ptr[swizzle_map[2]] = subwoofer; \ + ptr[swizzle_map[3]] = back_left; \ + ptr[swizzle_map[4]] = back_right; \ + } \ + } +// SDL channel map with alsa names "FL FR FC LFE [SL|RL] [SR|RR]" +// The literal names are SDL names. +// Faith: loading the whole frame in one shot may help naive compilers. +#define SWIZ6(T) \ + static void swizzle_alsa_channels_6_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \ + { \ + T *ptr = (T *)buffer; \ + Uint32 i; \ + for (i = 0; i < bufferlen; i++, ptr += 6) { \ + const T front_left = ptr[0]; \ + const T front_right = ptr[1]; \ + const T front_center = ptr[2]; \ + const T subwoofer = ptr[3]; \ + const T side_left = ptr[4]; \ + const T side_right = ptr[5]; \ + ptr[swizzle_map[0]] = front_left; \ + ptr[swizzle_map[1]] = front_right; \ + ptr[swizzle_map[2]] = front_center; \ + ptr[swizzle_map[3]] = subwoofer; \ + ptr[swizzle_map[4]] = side_left; \ + ptr[swizzle_map[5]] = side_right; \ + } \ + } +// SDL channel map with alsa names "FL FR FC LFE RC SL SR". +// The literal names are SDL names. +// Faith: loading the whole frame in one shot may help naive compilers. +#define SWIZ7(T) \ + static void swizzle_alsa_channels_7_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \ + { \ + T *ptr = (T *)buffer; \ + Uint32 i; \ + for (i = 0; i < bufferlen; i++, ptr += 7) { \ + const T front_left = ptr[0]; \ + const T front_right = ptr[1]; \ + const T front_center = ptr[2]; \ + const T subwoofer = ptr[3]; \ + const T back_center = ptr[4]; \ + const T side_left = ptr[5]; \ + const T side_right = ptr[6]; \ + ptr[swizzle_map[0]] = front_left; \ + ptr[swizzle_map[1]] = front_right; \ + ptr[swizzle_map[2]] = front_center; \ + ptr[swizzle_map[3]] = subwoofer; \ + ptr[swizzle_map[4]] = back_center; \ + ptr[swizzle_map[5]] = side_left; \ + ptr[swizzle_map[6]] = side_right; \ + } \ + } -// Swizzle channels to match SDL defaults. -// These are swizzles _from_ SDL's layouts to what ALSA wants. - -// 5.1 swizzle: -// https://bugzilla.libsdl.org/show_bug.cgi?id=110 -// "For Linux ALSA, this is FL-FR-RL-RR-C-LFE -// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR" -static const int swizzle_alsa_channels_6[6] = { 0, 1, 4, 5, 2, 3 }; +// SDL channel map with alsa names "FL FR FC LFE RL RR SL SR" +// The literal names are SDL names. +// Faith: loading the whole frame in one shot may help naive compilers. +#define SWIZ8(T) \ + static void swizzle_alsa_channels_8_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \ + { \ + T *ptr = (T *)buffer; \ + Uint32 i; \ + for (i = 0; i < bufferlen; i++, ptr += 8) { \ + const T front_left = ptr[0]; \ + const T front_right = ptr[1]; \ + const T front_center = ptr[2]; \ + const T subwoofer = ptr[3]; \ + const T back_left = ptr[4]; \ + const T back_right = ptr[5]; \ + const T side_left = ptr[6]; \ + const T side_right = ptr[7]; \ + ptr[swizzle_map[0]] = front_left; \ + ptr[swizzle_map[1]] = front_right; \ + ptr[swizzle_map[2]] = front_center; \ + ptr[swizzle_map[3]] = subwoofer; \ + ptr[swizzle_map[4]] = back_left; \ + ptr[swizzle_map[5]] = back_right; \ + ptr[swizzle_map[6]] = side_left; \ + ptr[swizzle_map[7]] = side_right; \ + } \ + } -// 7.1 swizzle: -// https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations -// For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR -// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR" -static const int swizzle_alsa_channels_8[8] = { 0, 1, 6, 7, 2, 3, 4, 5 }; +#define CHANNEL_SWIZZLE(x) \ + x(Uint64) \ + x(Uint32) \ + x(Uint16) \ + x(Uint8) + +CHANNEL_SWIZZLE(SWIZ2) +CHANNEL_SWIZZLE(SWIZ3) +CHANNEL_SWIZZLE(SWIZ4) +CHANNEL_SWIZZLE(SWIZ5) +CHANNEL_SWIZZLE(SWIZ6) +CHANNEL_SWIZZLE(SWIZ7) +CHANNEL_SWIZZLE(SWIZ8) + +#undef CHANNEL_SWIZZLE +#undef SWIZ2 +#undef SWIZ3 +#undef SWIZ4 +#undef SWIZ5 +#undef SWIZ6 +#undef SWIZ7 +#undef SWIZ8 + +// Called right before feeding device->hidden->mixbuf to the hardware. Swizzle +// channels from Windows/Mac order to the format alsalib will want. +static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) +{ + int *swizzle_map = device->hidden->swizzle_map; + switch (device->spec.channels) { +#define CHANSWIZ(chans) \ + case chans: \ + switch ((device->spec.format & (0xFF))) { \ + case 8: \ + swizzle_alsa_channels_##chans##_Uint8(swizzle_map, buffer, bufferlen); \ + break; \ + case 16: \ + swizzle_alsa_channels_##chans##_Uint16(swizzle_map, buffer, bufferlen); \ + break; \ + case 32: \ + swizzle_alsa_channels_##chans##_Uint32(swizzle_map, buffer, bufferlen); \ + break; \ + case 64: \ + swizzle_alsa_channels_##chans##_Uint64(swizzle_map, buffer, bufferlen); \ + break; \ + default: \ + SDL_assert(!"unhandled bitsize"); \ + break; \ + } \ + return; + CHANSWIZ(2); + CHANSWIZ(3); + CHANSWIZ(4); + CHANSWIZ(5); + CHANSWIZ(6); + CHANSWIZ(7); + CHANSWIZ(8); +#undef CHANSWIZ + default: + break; + } +} +// Some devices have the right channel map, no swizzling necessary +static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) +{ +} // This function waits until it is possible to write a full sound buffer static int ALSA_WaitDevice(SDL_AudioDevice *device) @@ -266,9 +532,9 @@ static int ALSA_WaitDevice(SDL_AudioDevice *device) const int delay = SDL_max(fulldelay, 10); while (!SDL_AtomicGet(&device->shutdown)) { - const int rc = ALSA_snd_pcm_wait(device->hidden->pcm_handle, delay); + 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_handle, 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: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -294,13 +560,15 @@ static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buf const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec); snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size); + device->hidden->swizzle_func(device, sample_buf, frames_left); + while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) { - const int rc = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left); + 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_handle, 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 write failed (unrecoverable): %s", ALSA_snd_strerror(rc)); @@ -318,12 +586,12 @@ static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buf static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle); + 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_handle); + rc = ALSA_snd_pcm_avail(device->hidden->pcm); if (rc <= 0) { // We'll catch it next time *buffer_size = 0; @@ -344,21 +612,23 @@ 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_handle); + 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_handle, buffer, total_frames); + 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_handle, 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. + } else if (rc > 0) { + device->hidden->swizzle_func(device, buffer, total_frames - rc); } //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: recorded %d bytes", rc * frame_size); @@ -368,458 +638,940 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) static void ALSA_FlushRecording(SDL_AudioDevice *device) { - ALSA_snd_pcm_reset(device->hidden->pcm_handle); + ALSA_snd_pcm_reset(device->hidden->pcm); } static void ALSA_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { - if (device->hidden->pcm_handle) { + 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_handle); + 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 + { + }, + // 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 SDL_bool has_pos(unsigned int *chmap, unsigned int pos) +{ + unsigned int chan_idx = 0; + loop { + if (chan_idx == 6) + return SDL_FALSE; + if (chmap[chan_idx] == pos) + return SDL_TRUE; + ++chan_idx; + } +} +// 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) +{ + unsigned int chan_idx; + unsigned int state; + // 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; + } + chan_idx = 0; + state = HAVE_NONE; + loop { + if (chan_idx == 6) + break; + 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; + } + } + ++chan_idx; + } + 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, + unsigned int sdl_pos_idx) +{ + unsigned int alsa_pos_idx = 0; + loop { + SDL_assert(alsa_pos_idx != ctx->chans_n); // no 0 channels or not found matching position should happen here (actually enforce playback/recording symmetry). -static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params) + 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); + ctx->device->hidden->swizzle_map[sdl_pos_idx] = alsa_pos_idx; + return; + } + ++alsa_pos_idx; + } +} +// XXX: this must stay playback/recording symetric. +static void swizzle_map_compute(struct ALSA_pcm_cfg_ctx *ctx) +{ + unsigned int sdl_pos_idx = 0; + loop { + if (sdl_pos_idx == ctx->chans_n) + break; + swizzle_map_compute_alsa_subscan(ctx, sdl_pos_idx); + ++sdl_pos_idx; + } +} +#define CHMAP_INSTALLED 0 +#define REDUCE_CHANS_N 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) { int status; - snd_pcm_hw_params_t *hwparams; - snd_pcm_uframes_t persize; - unsigned int periods; + char logdebug_chmap_str[128]; + snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t*)SDL_stack_alloc(unsigned int, + 1 + ctx->chans_n); + chmap_to_install->channels = ctx->chans_n; + SDL_memcpy(chmap_to_install->pos, chmap, sizeof(unsigned int) * ctx->chans_n); - // Copy the hardware parameters for this setup - snd_pcm_hw_params_alloca(&hwparams); - ALSA_snd_pcm_hw_params_copy(hwparams, params); + ALSA_snd_pcm_chmap_print(chmap_to_install,sizeof(logdebug_chmap_str),logdebug_chmap_str); + LOGDEBUG("channel map to install:%s",logdebug_chmap_str); - // Attempt to match the period size to the requested buffer size - persize = device->sample_frames; - status = ALSA_snd_pcm_hw_params_set_period_size_near( - device->hidden->pcm_handle, hwparams, &persize, NULL); + status = ALSA_snd_pcm_set_chmap(ctx->device->hidden->pcm, chmap_to_install); if (status < 0) { - return -1; + return SDL_SetError("ALSA: failed to install channel map: %s", ALSA_snd_strerror(status)); } + SDL_memcpy(ctx->alsa_chmap_installed, chmap, ctx->chans_n * sizeof(unsigned int)); + 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 SDL_bool alsa_chmap_has_duplicate_position(struct ALSA_pcm_cfg_ctx *ctx, unsigned int *pos) +{ + unsigned int chan_idx; - // Need to at least double buffer - periods = 2; - status = ALSA_snd_pcm_hw_params_set_periods_min( - device->hidden->pcm_handle, hwparams, &periods, NULL); - if (status < 0) { - return -1; + if (ctx->chans_n < 2) {// we need at least 2 positions + LOGDEBUG("channel map:no duplicate"); + return SDL_FALSE; } - status = ALSA_snd_pcm_hw_params_set_periods_first( - device->hidden->pcm_handle, hwparams, &periods, NULL); - if (status < 0) { - return -1; + chan_idx = 1; + loop { + unsigned seen_idx; + if (chan_idx == ctx->chans_n) { + LOGDEBUG("channel map:no duplicate"); + return SDL_FALSE; + } + seen_idx = 0; + loop { + if (pos[seen_idx] == pos[chan_idx]) { + LOGDEBUG("channel map:have duplicate"); + return SDL_TRUE; + } + ++seen_idx; + if (seen_idx == chan_idx) + break; + } + ++chan_idx; } +} +static int alsa_chmap_cfg_ordered_fixed_or_paired(struct ALSA_pcm_cfg_ctx *ctx) +{ + char logdebug_chmap_str[128]; + snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; + loop { + unsigned int chan_idx; + unsigned int *alsa_chmap; - // "set" the hardware with the desired parameters - status = ALSA_snd_pcm_hw_params(device->hidden->pcm_handle, hwparams); - if (status < 0) { - return -1; - } + if (*chmap_query == NULL) + break; + if (((*chmap_query)->map.channels != ctx->chans_n) + || ((*chmap_query)->type != SND_CHMAP_TYPE_FIXED + && (*chmap_query)->type != SND_CHMAP_TYPE_PAIRED)) { + ++chmap_query; + continue; + } - device->sample_frames = persize; + 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); - // This is useful for debugging - if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) { - snd_pcm_uframes_t bufsize; + alsa_chmap = (*chmap_query)->map.pos; + SDL_memcpy(ctx->sdl_chmap, sdl_channel_maps[ctx->chans_n], ctx->chans_n + * sizeof(unsigned int)); + 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)) { + ++chmap_query; + continue; + } + chan_idx = 0; + loop { + if (chan_idx == ctx->chans_n) + return alsa_chmap_install(ctx, alsa_chmap); + + if (ctx->sdl_chmap[chan_idx] != alsa_chmap[chan_idx]) + break; // nope, try next alsa channel map. + ++chan_idx; + } + ++chmap_query; + } + 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) +{ + char logdebug_chmap_str[128]; + snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; + loop { + unsigned int pos_matches_n; + unsigned int chan_idx; + unsigned int *alsa_chmap; + + if (*chmap_query == NULL) + break; + if ((*chmap_query)->map.channels != ctx->chans_n + || (*chmap_query)->type != SND_CHMAP_TYPE_VAR) { + ++chmap_query; + continue; + } - ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize); + ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str); + LOGDEBUG("channel map:ordered:var:%s",logdebug_chmap_str); - SDL_LogError(SDL_LOG_CATEGORY_AUDIO, - "ALSA: period size = %ld, periods = %u, buffer size = %lu", - persize, periods, bufsize); + alsa_chmap = (*chmap_query)->map.pos; + SDL_memcpy(ctx->sdl_chmap, sdl_channel_maps[ctx->chans_n], ctx->chans_n + * sizeof(unsigned int)); + 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)) { + ++chmap_query; + continue; + } + pos_matches_n = 0; + chan_idx = 0; + loop { + unsigned int subscan_chan_idx; + + if (chan_idx == ctx->chans_n) + break; + subscan_chan_idx = 0; + loop { + if (subscan_chan_idx == ctx->chans_n) + break; + if (ctx->sdl_chmap[chan_idx] == alsa_chmap[subscan_chan_idx]) { + ++pos_matches_n; + break; + } + ++subscan_chan_idx; + } + ++chan_idx; + } + if (pos_matches_n == ctx->chans_n) + return alsa_chmap_install(ctx, ctx->sdl_chmap); // XXX: we program the SDL chmap here + ++chmap_query; } - - return 0; + return CHMAP_NOT_FOUND; } -static int ALSA_OpenDevice(SDL_AudioDevice *device) +static int alsa_chmap_cfg_ordered(struct ALSA_pcm_cfg_ctx *ctx) { - const SDL_bool recording = device->recording; - int status = 0; + int status; - // Initialize all variables that we clean on shutdown - device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (!device->hidden) { - return -1; - } + status = alsa_chmap_cfg_ordered_fixed_or_paired(ctx); + if (status != CHMAP_NOT_FOUND) + return status; + return 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) +{ + char logdebug_chmap_str[128]; + snd_pcm_chmap_query_t **chmap_query = ctx->chmap_queries; + loop { + unsigned int pos_matches_n; + unsigned int chan_idx; + unsigned int *alsa_chmap; + + if (*chmap_query == NULL) + break; + if (((*chmap_query)->map.channels != ctx->chans_n) + || ((*chmap_query)->type != SND_CHMAP_TYPE_FIXED + && (*chmap_query)->type != SND_CHMAP_TYPE_PAIRED)) { + ++chmap_query; + continue; + } - // Open the audio device - // Name of device should depend on # channels in spec - snd_pcm_t *pcm_handle = NULL; - status = ALSA_snd_pcm_open(&pcm_handle, - get_audio_device(device->handle, device->spec.channels), - recording ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, - SND_PCM_NONBLOCK); + 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); - if (status < 0) { - return SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status)); + alsa_chmap = (*chmap_query)->map.pos; + SDL_memcpy(ctx->sdl_chmap, sdl_channel_maps[ctx->chans_n], ctx->chans_n + * sizeof(unsigned int)); + 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)) { + ++chmap_query; + continue; + } + pos_matches_n = 0; + chan_idx = 0; + loop { + unsigned int subscan_chan_idx; + + if (chan_idx == ctx->chans_n) + break; + subscan_chan_idx = 0; + loop { + if (subscan_chan_idx == ctx->chans_n) + break; + if (ctx->sdl_chmap[chan_idx] == alsa_chmap[subscan_chan_idx]) { + ++pos_matches_n; + break; + } + ++subscan_chan_idx; + } + ++chan_idx; + } + if (pos_matches_n == ctx->chans_n) + return alsa_chmap_install(ctx, alsa_chmap); + ++chmap_query; } + return CHMAP_NOT_FOUND; +} - device->hidden->pcm_handle = pcm_handle; +static int alsa_chmap_cfg(struct ALSA_pcm_cfg_ctx *ctx) +{ + int status; - // Figure out what the hardware is capable of - snd_pcm_hw_params_t *hwparams = NULL; - snd_pcm_hw_params_alloca(&hwparams); - status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams); - if (status < 0) { - return SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(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"); + ctx->device->hidden->swizzle_func = no_swizzle; + return CHMAP_INSTALLED; } - // SDL only uses interleaved sample output - status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams, - SND_PCM_ACCESS_RW_INTERLEAVED); - if (status < 0) { - return SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status)); + //---------------------------------------------------------------------------------------------- + status = alsa_chmap_cfg_ordered(ctx); // we prefer first channel maps we don't need to swizzle + if (status == CHMAP_INSTALLED) { + LOGDEBUG("swizzling off"); + ctx->device->hidden->swizzle_func = no_swizzle; + return status; + } + 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"); + + swizzle_map_compute(ctx); // fine grained swizzle configuration + ctx->device->hidden->swizzle_func = swizzle_alsa_channels; + return status; } + if (status == CHMAP_NOT_FOUND) + return REDUCE_CHANS_N; + return status; // < 0 error code +} - // Try for a closest match on audio format - snd_pcm_format_t format = 0; - const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); - SDL_AudioFormat test_format; - while ((test_format = *(closefmts++)) != 0) { - switch (test_format) { - case SDL_AUDIO_U8: - format = SND_PCM_FORMAT_U8; - break; - case SDL_AUDIO_S8: - format = SND_PCM_FORMAT_S8; - break; - case SDL_AUDIO_S16LE: - format = SND_PCM_FORMAT_S16_LE; - break; - case SDL_AUDIO_S16BE: - format = SND_PCM_FORMAT_S16_BE; - break; - case SDL_AUDIO_S32LE: - format = SND_PCM_FORMAT_S32_LE; - break; - case SDL_AUDIO_S32BE: - format = SND_PCM_FORMAT_S32_BE; - break; - case SDL_AUDIO_F32LE: - format = SND_PCM_FORMAT_FLOAT_LE; - break; - case SDL_AUDIO_F32BE: - format = SND_PCM_FORMAT_FLOAT_BE; - break; - default: - continue; - } - if (ALSA_snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) >= 0) { - break; +static int ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx) +{ + unsigned int target_chans_n = ctx->device->spec.channels; // we start at what was specified + loop { + int status; + snd_pcm_format_t alsa_format; + const SDL_AudioFormat *closefmts; + + if (target_chans_n == 0) { + return SDL_SetError("ALSA: tried all numbers of channels"); } - } - if (!test_format) { - return SDL_SetError("ALSA: Unsupported audio format"); - } - device->spec.format = test_format; - // Set the number of channels - status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams, - device->spec.channels); - unsigned int channels = device->spec.channels; - if (status < 0) { - status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels); + status = ALSA_snd_pcm_hw_params_any(ctx->device->hidden->pcm, ctx->hwparams); if (status < 0) { - return SDL_SetError("ALSA: Couldn't set audio channels"); - } - device->spec.channels = channels; - } - - const int *swizmap = NULL; - if (channels == 6) { - swizmap = swizzle_alsa_channels_6; - } else if (channels == 8) { - swizmap = swizzle_alsa_channels_8; - } - -#ifdef SND_CHMAP_API_VERSION - if (swizmap) { - snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle); - if (chmap) { - char chmap_str[64]; - if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) { - if ( (channels == 6) && - ((SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0) || - (SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0)) ) { - swizmap = NULL; - } else if ((channels == 8) && (SDL_strcmp("FL FR FC LFE SL SR RL RR", chmap_str) == 0)) { - swizmap = NULL; - } + return SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status)); + } + // 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) { + return SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status)); + } + // Try for a closest match on audio format + alsa_format = 0; + 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; } - free(chmap); // This should NOT be SDL_free() } - } -#endif // SND_CHMAP_API_VERSION - - // Validate number of channels and determine if swizzling is necessary. - // Assume original swizzling, until proven otherwise. - if (swizmap) { - device->chmap = SDL_ChannelMapDup(swizmap, channels); - if (!device->chmap) { - return -1; + if (ctx->matched_sdl_format == 0) { + return SDL_SetError("ALSA: Unsupported audio format: %s", ALSA_snd_strerror(status)); } + // 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) { + return SDL_SetError("ALSA: Couldn't set audio channels: %s", ALSA_snd_strerror(status)); + } + // 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) { + return SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status)); + } + // 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) { + return SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status)); + } + // 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) { + return SDL_SetError("ALSA: Couldn't set the minimum number of periods per buffer: %s", ALSA_snd_strerror(status)); + } + // 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) { + return SDL_SetError("ALSA: Couldn't set the number of periods per buffer: %s", ALSA_snd_strerror(status)); + } + // install the hw parameters + status = ALSA_snd_pcm_hw_params(ctx->device->hidden->pcm, ctx->hwparams); + if (status < 0) { + return SDL_SetError("ALSA: installation of hardware parameter failed: %s", ALSA_snd_strerror(status)); + } + //========================================================================================== + // 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 reduce the number of channels though. + status = alsa_chmap_cfg(ctx); + if (status < 0) + return status; // we forward the SDL error + if (status == CHMAP_INSTALLED) + return 0; // we are finished here + // status == REDUCE_CHANS_N + LOGDEBUG("reducing target chans_n to %u\n",target_chans_n-1); + ALSA_snd_pcm_free_chmaps(ctx->chmap_queries); + ALSA_snd_pcm_hw_free(ctx->device->hidden->pcm); // uninstall those hw params + target_chans_n--; } +} +#undef CHMAP_INSTALLED +#undef REDUCE_CHANS_N +#undef CHMAP_NOT_FOUND - // Set the audio rate - unsigned int rate = device->spec.freq; - status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, - &rate, NULL); - if (status < 0) { - return SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status)); - } - device->spec.freq = rate; - - // Set the buffer size, in samples - status = ALSA_set_buffer_size(device, hwparams); - if (status < 0) { - return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status)); - } +static int ALSA_pcm_cfg_sw(struct ALSA_pcm_cfg_ctx *ctx) +{ + int status; - // Set the software parameters - snd_pcm_sw_params_t *swparams = NULL; - snd_pcm_sw_params_alloca(&swparams); - status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams); + 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(pcm_handle, swparams, device->sample_frames); + 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(pcm_handle, swparams, 1); + 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(pcm_handle, swparams); + 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)); } - - // Calculate the final parameters for this audio specification - SDL_UpdatedAudioDeviceFormat(device); - - // Allocate mixing buffer - if (!recording) { - device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (!device->hidden->mixbuf) { - return -1; - } - SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); - } - -#if !SDL_ALSA_NON_BLOCKING - if (!recording) { - ALSA_snd_pcm_nonblock(pcm_handle, 0); - } -#endif - - ALSA_snd_pcm_start(pcm_handle); - - return 0; // We're ready to rock and roll. :-) + return 0; } -static void add_device(const SDL_bool recording, const char *name, void *hint, ALSA_Device **pSeen) +static int ALSA_OpenDevice(SDL_AudioDevice *device) { - ALSA_Device *dev = SDL_malloc(sizeof(ALSA_Device)); - char *desc; - char *ptr; + struct ALSA_pcm_cfg_ctx cfg_ctx; // used to track everything here + int status = 0; + char *pcm_str; + const SDL_bool recording = device->recording; - if (!dev) { - return; + //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; + + memset(&cfg_ctx,0,sizeof(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 == NULL) { + return -1; } + // Open the audio device + pcm_str = get_pcm_str(cfg_ctx.device->handle); + if (pcm_str == NULL) { + status = -1; + 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) { + status = 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)); - // Not all alsa devices are enumerable via snd_device_name_get_hint - // (i.e. bluetooth devices). Therefore if hint is passed in to this - // function as NULL, assume name contains desc. - // Make sure not to free the storage associated with desc in this case - if (hint) { - desc = ALSA_snd_device_name_get_hint(hint, "DESC"); - if (!desc) { - SDL_free(dev); - return; - } - } else { - desc = (char *)name; + status = ALSA_pcm_cfg_hw(&cfg_ctx); // alsa pcm "hardware" part of the pcm + if (status < 0) { + 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_getenv("SDL_AUDIO_ALSA_DEBUG")) { + snd_pcm_uframes_t bufsize; - SDL_assert(name != NULL); + ALSA_snd_pcm_hw_params_get_buffer_size(cfg_ctx.hwparams, &bufsize); - // some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output". - // just chop the extra lines off, this seems to get a reasonable device - // name without extra details. - ptr = SDL_strchr(desc, '\n'); - if (ptr) { - *ptr = '\0'; + SDL_LogError(SDL_LOG_CATEGORY_AUDIO, + "ALSA: period size = %ld, periods = %u, buffer size = %lu", + cfg_ctx.persize, cfg_ctx.periods, bufsize); } - //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: adding %s device '%s' (%s)", recording ? "recording" : "playback", name, desc); + status = ALSA_pcm_cfg_sw(&cfg_ctx); // alsa pcm "software" part of the pcm + if (status < 0) { + 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); - dev->name = SDL_strdup(name); - if (!dev->name) { - if (hint) { - free(desc); // This should NOT be SDL_free() + // 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) { + status = -1; + goto err_cleanup_ctx; } - SDL_free(dev->name); - SDL_free(dev); - return; + SDL_memset(cfg_ctx.device->hidden->mixbuf, cfg_ctx.device->silence_value, + cfg_ctx.device->buffer_size); } - // Note that spec is NULL, because we are required to open the device before - // acquiring the mix format, making this information inaccessible at - // enumeration time - SDL_AddAudioDevice(recording, desc, NULL, dev); - if (hint) { - free(desc); // This should NOT be SDL_free() +#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 0; // We're ready to rock and roll. :-) - dev->recording = recording; - dev->next = *pSeen; - *pSeen = dev; +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 status; } static ALSA_Device *hotplug_devices = NULL; -static void ALSA_HotplugIteration(SDL_bool *has_default_playback, SDL_bool *has_default_recording) -{ - void **hints = NULL; - ALSA_Device *unseen = NULL; - ALSA_Device *seen = NULL; - - if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) { - const char *match = NULL; - int bestmatch = 0xFFFF; - int has_default = -1; - size_t match_len = 0; - static const char *const prefixes[] = { - "hw:", "sysdefault:", "default:", NULL - }; - - unseen = hotplug_devices; - seen = NULL; - - // 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. - for (int i = 0; hints[i]; i++) { - char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME"); - if (!name) { - continue; +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) +{ + int r; + unsigned int subdevs_n; + unsigned int subdev_idx; + snd_pcm_info_t *pcm_info; + SDL_bool recording = direction == SND_PCM_STREAM_CAPTURE ? SDL_TRUE : SDL_FALSE; // used for the unicity of the device + + pcm_info = (snd_pcm_info_t*)SDL_stack_alloc(Uint8,ALSA_snd_pcm_info_sizeof()); + memset(pcm_info,0,ALSA_snd_pcm_info_sizeof()); + + subdev_idx = 0; + subdevs_n = 1; // we have at least one subdevice (substream since the direction is a stream in alsa terminology) + loop { + ALSA_Device *unseen_prev_adev; + ALSA_Device *adev; + + 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 + + r = ALSA_snd_ctl_pcm_info(ctl, pcm_info); + if (r < 0) { + // 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. + unseen_prev_adev = NULL; + adev = *unseen; + loop { + if (adev == NULL) + break; + // 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; + adev = adev->next; + } - if (SDL_strcmp(name, "default") == 0) { - if (has_default < 0) { - has_default = i; - } - } else { - for (int j = 0; prefixes[j]; j++) { - const char *prefix = prefixes[j]; - const size_t prefixlen = SDL_strlen(prefix); - if (SDL_strncmp(name, prefix, prefixlen) == 0) { - if (j < bestmatch) { - bestmatch = j; - match = prefix; - match_len = prefixlen; - } - } - } + if (adev == NULL) { // newly seen device + int name_len; + + adev = SDL_malloc(sizeof(*adev)); + if (adev == NULL) + return -1; - free(name); // This should NOT be SDL_free() + adev->id = SDL_strdup(ALSA_snd_ctl_card_info_get_id(ctl_card_info)); + if (adev->id == NULL) { + SDL_free(adev); + return -1; + } +#define NAME_FMT "%s:%s" + name_len = SDL_snprintf(0,0,NAME_FMT, ALSA_snd_ctl_card_info_get_name(ctl_card_info), + ALSA_snd_pcm_info_get_name(pcm_info)); + adev->name = SDL_malloc((size_t)(name_len + 1)); + if (adev->name == NULL) { + SDL_free(adev->id); + SDL_free(adev); + return -1; } + SDL_snprintf(adev->name,(size_t)(name_len + 1),NAME_FMT, + ALSA_snd_ctl_card_info_get_name(ctl_card_info), + ALSA_snd_pcm_info_get_name(pcm_info)); +#undef NAME_FMT + if (direction == SND_PCM_STREAM_CAPTURE) + adev->recording = SDL_TRUE; + else + adev->recording = SDL_FALSE; + + if (SDL_AddAudioDevice(recording, adev->name, NULL, adev) == NULL) { + SDL_free(adev->id); + SDL_free(adev->name); + SDL_free(adev); + return -1; + } + + adev->id = SDL_strdup(ALSA_snd_ctl_card_info_get_id(ctl_card_info)); + SDL_free(adev->name); + adev->name = SDL_strdup(ALSA_snd_pcm_info_get_name(pcm_info)); + if (direction == SND_PCM_STREAM_CAPTURE) + adev->recording = SDL_TRUE; + else + adev->recording = SDL_FALSE; + + adev->next = *seen; + *seen = adev; } + ++subdev_idx; + if (subdev_idx == subdevs_n) + return 0; + memset(pcm_info,0,ALSA_snd_pcm_info_sizeof()); + } +} - // look through the list of device names to find matches - if (match || (has_default >= 0)) { // did we find a device name prefix we like at all...? - for (int i = 0; hints[i]; i++) { - char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME"); - if (!name) { - continue; - } +static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_default_recording) +{ + int r; + snd_ctl_t *ctl; + int card_idx, dev_idx; + snd_ctl_card_info_t *ctl_card_info; + ALSA_Device *unseen; + ALSA_Device *seen; + char ctl_name[sizeof("hw:")+sizeof("4294967295")-1]; - // only want physical hardware interfaces - const SDL_bool is_default = (has_default == i); - if (is_default || (match && SDL_strncmp(name, match, match_len) == 0)) { - char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID"); - const SDL_bool isoutput = (!ioid) || (SDL_strcmp(ioid, "Output") == 0); - const SDL_bool isinput = (!ioid) || (SDL_strcmp(ioid, "Input") == 0); - SDL_bool have_output = SDL_FALSE; - SDL_bool have_input = SDL_FALSE; - - free(ioid); // This should NOT be SDL_free() - - if (!isoutput && !isinput) { - free(name); // This should NOT be SDL_free() - continue; - } - - if (is_default) { - if (has_default_playback && isoutput) { - *has_default_playback = SDL_TRUE; - } else if (has_default_recording && isinput) { - *has_default_recording = SDL_TRUE; - } - free(name); // This should NOT be SDL_free() - continue; - } - - ALSA_Device *prev = NULL; - ALSA_Device *next; - for (ALSA_Device *dev = unseen; dev; dev = next) { - next = dev->next; - if ((SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->recording) || ((isoutput) && !dev->recording))) { - if (prev) { - prev->next = next; - } else { - unseen = next; - } - dev->next = seen; - seen = dev; - if (isinput) { - have_input = SDL_TRUE; - } - if (isoutput) { - have_output = SDL_TRUE; - } - } else { - prev = dev; - } - } - - if (isinput && !have_input) { - add_device(SDL_TRUE, name, hints[i], &seen); - } - if (isoutput && !have_output) { - add_device(SDL_FALSE, name, hints[i], &seen); - } - } + if (has_default_output != NULL) + *has_default_output = SDL_TRUE; + if (has_default_recording != NULL) + *has_default_recording = SDL_TRUE; - free(name); // This should NOT be SDL_free() - } - } + ctl_card_info = alloca(ALSA_snd_ctl_card_info_sizeof()); + memset(ctl_card_info,0,ALSA_snd_ctl_card_info_sizeof()); - ALSA_snd_device_name_free_hint(hints); + unseen = hotplug_devices; + seen = NULL; - hotplug_devices = seen; // now we have a known-good list of attached devices. + card_idx = -1; - // report anything still in unseen as removed. - ALSA_Device *next = NULL; - for (ALSA_Device *dev = unseen; dev; dev = next) { - //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: removing %s device '%s'", dev->recording ? "recording" : "playback", dev->name); - next = dev->next; - SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(dev)); - SDL_free(dev->name); - SDL_free(dev); + loop { + r = ALSA_snd_card_next(&card_idx); + if (r < 0) + goto error_remove_all_devices; + + if (card_idx == -1) + break; + + sprintf(ctl_name, "hw:%d", card_idx); // card_idx >= 0 + 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 error_close_ctl; + dev_idx = -1; + loop { + r = ALSA_snd_ctl_pcm_next_device(ctl, &dev_idx); + if (r < 0) + goto error_close_ctl; + + 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 error_close_ctl; + + r = hotplug_device_process(ctl, ctl_card_info, dev_idx, SND_PCM_STREAM_CAPTURE, + &unseen, &seen); + if (r < 0) + goto error_close_ctl; } + ALSA_snd_ctl_close(ctl); + ALSA_snd_ctl_card_info_clear(ctl_card_info); } + // remove only the unseen devices + loop { + ALSA_Device *next; + if (unseen == NULL) + break; + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(unseen)); + SDL_free(unseen->name); + SDL_free(unseen->id); + next = unseen->next; + SDL_free(unseen); + unseen = next; + } + // update hotplug devices to be the seen devices + hotplug_devices = seen; + return; + +error_close_ctl: + ALSA_snd_ctl_close(ctl); + +error_remove_all_devices: + // remove the unseen + loop { + ALSA_Device *next; + if (unseen == NULL) + break; + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(unseen)); + SDL_free(unseen->name); + SDL_free(unseen->id); + next = unseen->next; + SDL_free(unseen); + unseen = next; + } + // remove the seen + loop { + ALSA_Device *next; + if (seen == NULL) + break; + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(seen)); + SDL_free(seen->name); + SDL_free(seen->id); + next = seen->next; + SDL_free(seen); + seen = next; + } + hotplug_devices = NULL; + return; } #if SDL_ALSA_HOTPLUG_THREAD @@ -844,14 +1596,14 @@ static int SDLCALL ALSA_HotplugThread(void *arg) } #endif -static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) +static void ALSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_recording) { // 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. - SDL_bool has_default_playback = SDL_FALSE, has_default_recording = SDL_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=*/SDL_FALSE, "ALSA default playback device", NULL, (void*)&default_playback_handle); + SDL_bool has_default_output = SDL_FALSE, has_default_recording = SDL_FALSE; + ALSA_HotplugIteration(&has_default_output, &has_default_recording); // run once now before a thread continues to check. + if (has_default_output) { + *default_output = SDL_AddAudioDevice(/*recording=*/SDL_FALSE, "ALSA default output device", NULL, (void*)&default_output_handle); } if (has_default_recording) { *default_recording = SDL_AddAudioDevice(/*recording=*/SDL_TRUE, "ALSA default recording device", NULL, (void*)&default_recording_handle); @@ -879,7 +1631,7 @@ static void ALSA_DeinitializeStart(void) // 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); + //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: at shutdown, removing %s device '%s'", dev->recording ? "recording" : "output", dev->name); next = dev->next; SDL_free(dev->name); SDL_free(dev); @@ -919,4 +1671,5 @@ AudioBootStrap ALSA_bootstrap = { "alsa", "ALSA PCM audio", ALSA_Init, SDL_FALSE }; +#undef loop #endif // SDL_AUDIO_DRIVER_ALSA diff --git a/src/audio/alsa/SDL_alsa_audio.h b/src/audio/alsa/SDL_alsa_audio.h index 1209fe2d12583..952bfb26b7c88 100644 --- a/src/audio/alsa/SDL_alsa_audio.h +++ b/src/audio/alsa/SDL_alsa_audio.h @@ -27,16 +27,21 @@ #include "../SDL_sysaudio.h" +#define SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX 8 +#define SDL_AUDIO_ALSA__SDL_CHMAPS_N 9 // from 0 channels to 8 channels struct SDL_PrivateAudioData { // The audio device handle - snd_pcm_t *pcm_handle; + snd_pcm_t *pcm; // Raw mixing buffer Uint8 *mixbuf; // swizzle function void (*swizzle_func)(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen); + // Up to a channel map of 8 channels, will define the sample indexes into the alsa frame + // from a sdl sample index. + int swizzle_map[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX]; }; #endif // SDL_ALSA_audio_h_