diff --git a/README.md b/README.md index 5cc7613..bcd7c7d 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ make sudo make install ``` -### Building With MXE +### Building for Windows You can build libsoundio with [mxe](http://mxe.cc/). Follow the [requirements](http://mxe.cc/#requirements) section to install the @@ -115,6 +115,8 @@ view `coverage/index.html` in a browser. ## Roadmap + 0. ALSA `list_devices` should list default, dmix, etc. + 0. ALSA default devices are "default" and "default" respectively 0. implement ALSA (Linux) backend, get examples working 0. pipe record to playback example working with dummy linux, osx, windows 0. pipe record to playback example working with pulseaudio linux diff --git a/example/list_devices.c b/example/list_devices.c index c68a057..5d9b88d 100644 --- a/example/list_devices.c +++ b/example/list_devices.c @@ -33,6 +33,7 @@ static void print_channel_layout(const struct SoundIoChannelLayout *layout) { static void print_device(struct SoundIoDevice *device, bool is_default) { const char *purpose_str; const char *default_str; + const char *raw_str; if (soundio_device_purpose(device) == SoundIoDevicePurposeOutput) { purpose_str = "playback"; default_str = is_default ? " (default)" : ""; @@ -40,9 +41,10 @@ static void print_device(struct SoundIoDevice *device, bool is_default) { purpose_str = "recording"; default_str = is_default ? " (default)" : ""; } + raw_str = device->is_raw ? "(raw) " : ""; const char *description = soundio_device_description(device); int sample_rate = soundio_device_sample_rate(device); - fprintf(stderr, "%s device: ", purpose_str); + fprintf(stderr, "%s%s device: ", raw_str, purpose_str); print_channel_layout(soundio_device_channel_layout(device)); fprintf(stderr, " %d Hz %s%s\n", sample_rate, description, default_str); } diff --git a/src/alsa.cpp b/src/alsa.cpp index e0c4b10..a84fe08 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -70,8 +70,145 @@ static void destroy_alsa(SoundIo *soundio) { soundio->backend_data = nullptr; } +static char * str_partition_on_char(char *str, char c) { + while (*str) { + if (*str == c) { + *str = 0; + return str + 1; + } + str += 1; + } + return nullptr; +} + static int refresh_devices(SoundIo *soundio) { SoundIoAlsa *sia = (SoundIoAlsa *)soundio->backend_data; + + SoundIoDevicesInfo *devices_info = create(); + if (!devices_info) + return SoundIoErrorNoMem; + + void **hints; + if (snd_device_name_hint(-1, "pcm", &hints) < 0) { + destroy(devices_info); + return SoundIoErrorNoMem; + } + + for (void **hint_ptr = hints; *hint_ptr; hint_ptr += 1) { + char *name = snd_device_name_get_hint(*hint_ptr, "NAME"); + // null - libsoundio has its own dummy backend. API clients should use + // that instead of alsa null device. + if (strcmp(name, "null") == 0 || + // sysdefault is confusing - the name and description is identical + // to default, and my best guess for what it does is ignore ~/.asoundrc + // which is just an accident waiting to happen. + str_has_prefix(name, "sysdefault:") || + // all these surround devices are clutter + str_has_prefix(name, "front:") || + str_has_prefix(name, "surround21:") || + str_has_prefix(name, "surround40:") || + str_has_prefix(name, "surround41:") || + str_has_prefix(name, "surround50:") || + str_has_prefix(name, "surround51:") || + str_has_prefix(name, "surround71:")) + { + free(name); + continue; + } + + char *descr = snd_device_name_get_hint(*hint_ptr, "DESC"); + char *descr1 = str_partition_on_char(descr, '\n'); + + char *io = snd_device_name_get_hint(*hint_ptr, "IOID"); + bool is_playback; + bool is_capture; + if (io) { + if (strcmp(io, "Input") == 0) { + is_playback = false; + is_capture = true; + } else if (strcmp(io, "Output") == 0) { + is_playback = true; + is_capture = false; + } else { + soundio_panic("invalid io hint value"); + } + free(io); + } else { + is_playback = true; + is_capture = true; + } + + for (int stream_type_i = 0; stream_type_i < array_length(stream_types); stream_type_i += 1) { + snd_pcm_stream_t stream = stream_types[stream_type_i]; + if (stream == SND_PCM_STREAM_PLAYBACK && !is_playback) continue; + if (stream == SND_PCM_STREAM_CAPTURE && !is_capture) continue; + if (stream == SND_PCM_STREAM_CAPTURE && descr1 && + (strstr(descr1, "Output") || strstr(descr1, "output"))) + { + continue; + } + + + SoundIoDevice *device = create(); + if (!device) { + free(name); + free(descr); + destroy(devices_info); + snd_device_name_free_hint(hints); + return SoundIoErrorNoMem; + } + device->ref_count = 1; + device->soundio = soundio; + device->name = strdup(name); + device->description = descr1 ? + soundio_alloc_sprintf(nullptr, "%s: %s", descr, descr1) : strdup(descr); + device->is_raw = false; + + if (!device->name || !device->description) { + soundio_device_unref(device); + free(name); + free(descr); + destroy(devices_info); + snd_device_name_free_hint(hints); + return SoundIoErrorNoMem; + } + + + // TODO: device->channel_layout + // TODO: device->default_sample_format + // TODO: device->default_latency + // TODO: device->default_sample_rate + + SoundIoList *device_list; + if (stream == SND_PCM_STREAM_PLAYBACK) { + device->purpose = SoundIoDevicePurposeOutput; + device_list = &devices_info->output_devices; + if (str_has_prefix(name, "default:")) + devices_info->default_output_index = device_list->length; + } else { + assert(stream == SND_PCM_STREAM_CAPTURE); + device->purpose = SoundIoDevicePurposeInput; + device_list = &devices_info->input_devices; + if (str_has_prefix(name, "default:")) + devices_info->default_input_index = device_list->length; + } + + if (device_list->append(device)) { + soundio_device_unref(device); + free(name); + free(descr); + destroy(devices_info); + snd_device_name_free_hint(hints); + return SoundIoErrorNoMem; + } + } + + free(name); + free(descr); + } + + snd_device_name_free_hint(hints); + int card_index = -1; if (snd_card_next(&card_index) < 0) @@ -83,10 +220,6 @@ static int refresh_devices(SoundIo *soundio) { snd_pcm_info_t *pcm_info; snd_pcm_info_alloca(&pcm_info); - SoundIoDevicesInfo *devices_info = create(); - if (!devices_info) - return SoundIoErrorNoMem; - while (card_index >= 0) { int err; snd_ctl_t *handle; @@ -145,8 +278,9 @@ static int refresh_devices(SoundIo *soundio) { } device->ref_count = 1; device->soundio = soundio; - device->name = soundio_alloc_sprintf(nullptr, "hw:%d,%d,%d", card_index, device_index, 0); + device->name = soundio_alloc_sprintf(nullptr, "hw:%d,%d", card_index, device_index); device->description = soundio_alloc_sprintf(nullptr, "%s %s", card_name, device_name); + device->is_raw = true; if (!device->name || !device->description) { soundio_device_unref(device); diff --git a/src/soundio.h b/src/soundio.h index 4606fb1..87a2ae4 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -120,6 +120,7 @@ struct SoundIoDevice { int default_sample_rate; enum SoundIoDevicePurpose purpose; int ref_count; + bool is_raw; }; struct SoundIoOutputDevice { diff --git a/src/util.hpp b/src/util.hpp index 8c06fc2..a619d06 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -9,6 +9,7 @@ #define SOUNDIO_UTIL_HPP #include +#include #include #include @@ -103,4 +104,8 @@ static inline T min(T a, T b) { return (a <= b) ? a : b; } +static inline bool str_has_prefix(const char *big_str, const char *prefix) { + return strncmp(big_str, prefix, strlen(prefix)) == 0; +} + #endif