|
@@ -43,6 +43,12 @@
|
|
|
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
|
|
|
#endif
|
|
|
|
|
|
+// !!! FIXME: remove this.
|
|
|
+#define loop for(;;)
|
|
|
+#define LOGDEBUG(...) SDL_Log("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 +87,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 +177,30 @@ static bool 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 true;
|
|
|
}
|
|
@@ -205,59 +251,282 @@ static bool LoadALSALibrary(void)
|
|
|
|
|
|
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
|
|
|
};
|
|
|
|
|
|
-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_GetHint(SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE);
|
|
|
- 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;
|
|
|
}
|
|
|
|
|
|
-// Swizzle channels to match SDL defaults.
|
|
|
-// These are swizzles _from_ SDL's layouts to what ALSA wants.
|
|
|
+// 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; \
|
|
|
+ } \
|
|
|
+ }
|
|
|
|
|
|
-// 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 bool ALSA_WaitDevice(SDL_AudioDevice *device)
|
|
@@ -266,9 +535,9 @@ static bool ALSA_WaitDevice(SDL_AudioDevice *device)
|
|
|
const int delay = SDL_max(fulldelay, 10);
|
|
|
|
|
|
while (!SDL_GetAtomicInt(&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 +563,15 @@ static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu
|
|
|
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_GetAtomicInt(&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 +589,12 @@ static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu
|
|
|
|
|
|
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 +615,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,455 +641,965 @@ 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);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params)
|
|
|
-{
|
|
|
- int status;
|
|
|
- snd_pcm_hw_params_t *hwparams;
|
|
|
- snd_pcm_uframes_t persize;
|
|
|
- unsigned int periods;
|
|
|
|
|
|
- // Copy the hardware parameters for this setup
|
|
|
- snd_pcm_hw_params_alloca(&hwparams);
|
|
|
- ALSA_snd_pcm_hw_params_copy(hwparams, params);
|
|
|
-
|
|
|
- // 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);
|
|
|
- if (status < 0) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
+// Swizzle channels to match SDL defaults.
|
|
|
+// These are swizzles _from_ SDL's layouts to what ALSA wants.
|
|
|
|
|
|
- // 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 0
|
|
|
+// 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 };
|
|
|
|
|
|
- status = ALSA_snd_pcm_hw_params_set_periods_first(
|
|
|
- device->hidden->pcm_handle, hwparams, &periods, NULL);
|
|
|
- if (status < 0) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
+// 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 };
|
|
|
+#endif
|
|
|
|
|
|
- // "set" the hardware with the desired parameters
|
|
|
- status = ALSA_snd_pcm_hw_params(device->hidden->pcm_handle, hwparams);
|
|
|
- if (status < 0) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
+// To make easier to track parameters during the whole alsa pcm configuration:
|
|
|
+struct ALSA_pcm_cfg_ctx {
|
|
|
+ SDL_AudioDevice *device;
|
|
|
|
|
|
- device->sample_frames = persize;
|
|
|
+ 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,
|
|
|
+ },
|
|
|
+};
|
|
|
|
|
|
- // This is useful for debugging
|
|
|
- snd_pcm_uframes_t bufsize;
|
|
|
- ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
|
|
|
- SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO,
|
|
|
- "ALSA: period size = %ld, periods = %u, buffer size = %lu",
|
|
|
- persize, periods, bufsize);
|
|
|
- return 0;
|
|
|
+// Helper for the function right below.
|
|
|
+static bool has_pos(unsigned int *chmap, unsigned int pos)
|
|
|
+{
|
|
|
+ unsigned int chan_idx = 0;
|
|
|
+ loop {
|
|
|
+ if (chan_idx == 6)
|
|
|
+ return false;
|
|
|
+ if (chmap[chan_idx] == pos)
|
|
|
+ return true;
|
|
|
+ ++chan_idx;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
-static bool ALSA_OpenDevice(SDL_AudioDevice *device)
|
|
|
+// 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)
|
|
|
{
|
|
|
- const bool recording = device->recording;
|
|
|
- int status = 0;
|
|
|
+ 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).
|
|
|
|
|
|
- // Initialize all variables that we clean on shutdown
|
|
|
- device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
|
|
- if (!device->hidden) {
|
|
|
- return false;
|
|
|
+ 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;
|
|
|
+ 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);
|
|
|
|
|
|
- // 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_to_install,sizeof(logdebug_chmap_str),logdebug_chmap_str);
|
|
|
+ LOGDEBUG("channel map to install:%s",logdebug_chmap_str);
|
|
|
|
|
|
+ status = ALSA_snd_pcm_set_chmap(ctx->device->hidden->pcm, chmap_to_install);
|
|
|
if (status < 0) {
|
|
|
- return SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status));
|
|
|
+ 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 bool alsa_chmap_has_duplicate_position(struct ALSA_pcm_cfg_ctx *ctx, unsigned int *pos)
|
|
|
+{
|
|
|
+ unsigned int chan_idx;
|
|
|
|
|
|
- device->hidden->pcm_handle = pcm_handle;
|
|
|
-
|
|
|
- // 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));
|
|
|
+ if (ctx->chans_n < 2) {// we need at least 2 positions
|
|
|
+ LOGDEBUG("channel map:no duplicate");
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
- // 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));
|
|
|
+ chan_idx = 1;
|
|
|
+ loop {
|
|
|
+ unsigned seen_idx;
|
|
|
+ if (chan_idx == ctx->chans_n) {
|
|
|
+ LOGDEBUG("channel map:no duplicate");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ seen_idx = 0;
|
|
|
+ loop {
|
|
|
+ if (pos[seen_idx] == pos[chan_idx]) {
|
|
|
+ LOGDEBUG("channel map:have duplicate");
|
|
|
+ return 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;
|
|
|
|
|
|
- // 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;
|
|
|
+ if (*chmap_query == NULL)
|
|
|
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:
|
|
|
+ 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;
|
|
|
}
|
|
|
- if (ALSA_snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) >= 0) {
|
|
|
- break;
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ 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;
|
|
|
}
|
|
|
- if (!test_format) {
|
|
|
- return SDL_SetError("ALSA: Unsupported audio format");
|
|
|
- }
|
|
|
- device->spec.format = test_format;
|
|
|
+ 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;
|
|
|
+ }
|
|
|
|
|
|
- // 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);
|
|
|
- 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;
|
|
|
+ ALSA_snd_pcm_chmap_print(&(*chmap_query)->map,sizeof(logdebug_chmap_str),logdebug_chmap_str);
|
|
|
+ LOGDEBUG("channel map:ordered:var:%s",logdebug_chmap_str);
|
|
|
+
|
|
|
+ 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;
|
|
|
}
|
|
|
- free(chmap); // This should NOT be SDL_free()
|
|
|
+ ++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;
|
|
|
}
|
|
|
-#endif // SND_CHMAP_API_VERSION
|
|
|
+ return CHMAP_NOT_FOUND;
|
|
|
+}
|
|
|
|
|
|
- // 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 false;
|
|
|
+static int alsa_chmap_cfg_ordered(struct ALSA_pcm_cfg_ctx *ctx)
|
|
|
+{
|
|
|
+ int status;
|
|
|
+
|
|
|
+ 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;
|
|
|
}
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ 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;
|
|
|
+}
|
|
|
|
|
|
- // 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));
|
|
|
+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");
|
|
|
+ ctx->device->hidden->swizzle_func = no_swizzle;
|
|
|
+ return CHMAP_INSTALLED;
|
|
|
}
|
|
|
- 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));
|
|
|
+ //----------------------------------------------------------------------------------------------
|
|
|
+ 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
|
|
|
+}
|
|
|
+
|
|
|
+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");
|
|
|
+ }
|
|
|
|
|
|
- // 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_hw_params_any(ctx->device->hidden->pcm, ctx->hwparams);
|
|
|
+ if (status < 0) {
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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
|
|
|
+
|
|
|
+static int 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(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));
|
|
|
}
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
- // 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 false;
|
|
|
- }
|
|
|
- SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
|
|
+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;
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
-#if !SDL_ALSA_NON_BLOCKING
|
|
|
- if (!recording) {
|
|
|
- ALSA_snd_pcm_nonblock(pcm_handle, 0);
|
|
|
+ // 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;
|
|
|
}
|
|
|
-#endif
|
|
|
|
|
|
- ALSA_snd_pcm_start(pcm_handle);
|
|
|
+ // 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));
|
|
|
|
|
|
- return true; // We're ready to rock and roll. :-)
|
|
|
-}
|
|
|
+ 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
|
|
|
|
|
|
-static void add_device(const bool recording, const char *name, void *hint, ALSA_Device **pSeen)
|
|
|
-{
|
|
|
- ALSA_Device *dev = SDL_malloc(sizeof(ALSA_Device));
|
|
|
- char *desc;
|
|
|
- char *ptr;
|
|
|
+ // This is useful for debugging
|
|
|
+ if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) {
|
|
|
+ snd_pcm_uframes_t bufsize;
|
|
|
|
|
|
- if (!dev) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ ALSA_snd_pcm_hw_params_get_buffer_size(cfg_ctx.hwparams, &bufsize);
|
|
|
|
|
|
- // 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;
|
|
|
+ SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
|
|
|
+ "ALSA: period size = %ld, periods = %u, buffer size = %lu",
|
|
|
+ cfg_ctx.persize, cfg_ctx.periods, bufsize);
|
|
|
}
|
|
|
|
|
|
- SDL_assert(name != NULL);
|
|
|
|
|
|
- // 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';
|
|
|
+ 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);
|
|
|
|
|
|
- //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: adding %s device '%s' (%s)", recording ? "recording" : "playback", name, desc);
|
|
|
-
|
|
|
- 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) {
|
|
|
+ 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 true; // 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 false;
|
|
|
}
|
|
|
|
|
|
static ALSA_Device *hotplug_devices = NULL;
|
|
|
|
|
|
-static void ALSA_HotplugIteration(bool *has_default_playback, 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;
|
|
|
+ bool recording = direction == SND_PCM_STREAM_CAPTURE ? true : 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;
|
|
|
|
|
|
- free(name); // This should NOT be SDL_free()
|
|
|
+ adev = SDL_malloc(sizeof(*adev));
|
|
|
+ if (adev == NULL)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ 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 = true;
|
|
|
+ else
|
|
|
+ adev->recording = false;
|
|
|
+
|
|
|
+ if (SDL_AddAudioDevice(recording, adev->name, NULL, adev) == NULL) {
|
|
|
+ SDL_free(adev->id);
|
|
|
+ SDL_free(adev->name);
|
|
|
+ SDL_free(adev);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+#if 0
|
|
|
+ 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 = true;
|
|
|
+ else
|
|
|
+ adev->recording = false;
|
|
|
+#endif
|
|
|
+ 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(bool *has_default_output, 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 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 bool isoutput = (!ioid) || (SDL_strcmp(ioid, "Output") == 0);
|
|
|
- const bool isinput = (!ioid) || (SDL_strcmp(ioid, "Input") == 0);
|
|
|
- bool have_output = false;
|
|
|
- bool have_input = 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 = true;
|
|
|
- } else if (has_default_recording && isinput) {
|
|
|
- *has_default_recording = 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 = true;
|
|
|
- }
|
|
|
- if (isoutput) {
|
|
|
- have_output = true;
|
|
|
- }
|
|
|
- } else {
|
|
|
- prev = dev;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (isinput && !have_input) {
|
|
|
- add_device(true, name, hints[i], &seen);
|
|
|
- }
|
|
|
- if (isoutput && !have_output) {
|
|
|
- add_device(false, name, hints[i], &seen);
|
|
|
- }
|
|
|
- }
|
|
|
+ if (has_default_output != NULL)
|
|
|
+ *has_default_output = true;
|
|
|
+ if (has_default_recording != NULL)
|
|
|
+ *has_default_recording = 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
|
|
|
static SDL_AtomicInt ALSA_hotplug_shutdown;
|
|
|
static SDL_Thread *ALSA_hotplug_thread;
|
|
@@ -914,4 +1697,5 @@ AudioBootStrap ALSA_bootstrap = {
|
|
|
"alsa", "ALSA PCM audio", ALSA_Init, false
|
|
|
};
|
|
|
|
|
|
+#undef loop
|
|
|
#endif // SDL_AUDIO_DRIVER_ALSA
|