From: Alexandru Gagniuc Date: Thu, 20 Dec 2012 19:47:09 +0000 (-0600) Subject: alsa: Scan all soundcards and create a sigrok device per input X-Git-Tag: dsupstream~390 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=6944b2d02f23cf562574d3d1b37a2d698bdbde4e;p=libsigrok.git alsa: Scan all soundcards and create a sigrok device per input The alsa driver only works with device "default". This limits the driver's scope to whatever device ALSA deems to be "default". It is desirable to have access to all ALSA devices from sigrok. Change the alsa device scan so that: Each alsa device (not alsa card) gets its own sigrok device For example, hw:1,0 == sigrok device 0 hw:1,1 == sigrok device 1 hw:2,0 == sigrok device 2 hw:2,1 == sigrok device 3 hw:2,2 == sigrok device 4 [...] We don't currently look at alsa subdevices. We only use subdevice 0. Every input device will have its own channels (left, right, etc). Each of those channels gets mapped to a different sigrok probe. A device with 4 channels will have 4 probes from sigrok's perspective. Signed-off-by: Alexandru Gagniuc --- diff --git a/hardware/alsa/api.c b/hardware/alsa/api.c index 22611ca8..dc762e06 100644 --- a/hardware/alsa/api.c +++ b/hardware/alsa/api.c @@ -1,8 +1,9 @@ /* - * This file is part of the sigrok project. + * This file is part of the libsigrok project. * * Copyright (C) 2011 Daniel Ribeiro * Copyright (C) 2012 Uwe Hermann + * Copyright (C) 2012 Alexandru Gagniuc * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,8 +31,6 @@ #define DEFAULT_PROBES 2 #define SAMPLE_WIDTH 16 #define DEFAULT_SAMPLERATE 44100 -// #define AUDIO_DEV "plughw:0,0" -#define AUDIO_DEV "default" static const int hwcaps[] = { SR_HWCAP_SAMPLERATE, @@ -50,7 +49,13 @@ static struct sr_dev_driver *di = &alsa_driver_info; static int clear_instances(void) { - /* TODO */ + struct drv_context *drvc; + + if (!(drvc = di->priv)) + return SR_OK; + + g_slist_free_full(drvc->instances, (GDestroyNotify)alsa_dev_inst_clear); + drvc->instances = NULL; return SR_OK; } @@ -72,50 +77,7 @@ static int hw_init(struct sr_context *sr_ctx) static GSList *hw_scan(GSList *options) { - struct drv_context *drvc; - struct dev_context *devc; - struct sr_dev_inst *sdi; - struct sr_probe *probe; - GSList *devices; - int i; - - (void)options; - - drvc = di->priv; - drvc->instances = NULL; - - devices = NULL; - - if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) { - sr_err("Device context malloc failed."); - return NULL; - } - - if (!(sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, "alsa", NULL, NULL))) { - sr_err("Failed to create device instance."); - return NULL; - } - - /* Set the samplerate to a default value for now. */ - devc->cur_samplerate = DEFAULT_SAMPLERATE; - devc->num_probes = DEFAULT_PROBES; - - sdi->priv = devc; - sdi->driver = di; - - for (i = 0; probe_names[i]; i++) { - if (!(probe = sr_probe_new(i, SR_PROBE_ANALOG, TRUE, - probe_names[i]))) { - sr_err("Failed to create probe."); - return NULL; - } - sdi->probes = g_slist_append(sdi->probes, probe); - } - - drvc->instances = g_slist_append(drvc->instances, sdi); - devices = g_slist_append(devices, sdi); - - return devices; + return alsa_scan(options, di); } static GSList *hw_dev_list(void) @@ -134,22 +96,19 @@ static int hw_dev_open(struct sr_dev_inst *sdi) devc = sdi->priv; - sr_dbg("Opening audio device '%s' for stream capture.", AUDIO_DEV); - ret = snd_pcm_open(&devc->capture_handle, AUDIO_DEV, + if (!(devc->hwdev)) { + sr_err("devc->hwdev was NULL."); + return SR_ERR_BUG; + } + + sr_dbg("Opening audio device '%s' for stream capture.", devc->hwdev); + ret = snd_pcm_open(&devc->capture_handle, devc->hwdev, SND_PCM_STREAM_CAPTURE, 0); if (ret < 0) { sr_err("Can't open audio device: %s.", snd_strerror(ret)); return SR_ERR; } - sr_dbg("Allocating hardware parameter structure."); - ret = snd_pcm_hw_params_malloc(&devc->hw_params); - if (ret < 0) { - sr_err("Can't allocate hardware parameter structure: %s.", - snd_strerror(ret)); - return SR_ERR_MALLOC; - } - sr_dbg("Initializing hardware parameter structure."); ret = snd_pcm_hw_params_any(devc->capture_handle, devc->hw_params); if (ret < 0) { @@ -170,18 +129,12 @@ static int hw_dev_close(struct sr_dev_inst *sdi) sr_dbg("Closing device."); - if (devc->hw_params) { - sr_dbg("Freeing hardware parameters."); - snd_pcm_hw_params_free(devc->hw_params); - } else { - sr_dbg("No hardware parameters, no need to free."); - } - if (devc->capture_handle) { sr_dbg("Closing PCM device."); if ((ret = snd_pcm_close(devc->capture_handle)) < 0) { sr_err("Failed to close device: %s.", snd_strerror(ret)); + devc->capture_handle = NULL; } } else { sr_dbg("No capture handle, no need to close audio device."); @@ -210,7 +163,7 @@ static int hw_info_get(int info_id, const void **data, *data = hwcaps; break; case SR_DI_NUM_PROBES: - *data = GINT_TO_POINTER(DEFAULT_PROBES); + *data = &devc->num_probes; break; case SR_DI_PROBE_NAMES: *data = probe_names; diff --git a/hardware/alsa/protocol.c b/hardware/alsa/protocol.c index 61b03cab..1c1d8849 100644 --- a/hardware/alsa/protocol.c +++ b/hardware/alsa/protocol.c @@ -24,6 +24,206 @@ #include "libsigrok.h" #include "libsigrok-internal.h" +static void alsa_scan_handle_dev(GSList **devices, + const char *cardname, const char *alsaname, + struct sr_dev_driver *di, + snd_pcm_info_t *pcminfo) +{ + struct drv_context *drvc = NULL; + struct sr_dev_inst *sdi = NULL; + struct dev_context *devc = NULL; + struct sr_probe *probe; + int ret; + unsigned int i, channels; + snd_pcm_t *temp_handle = NULL; + snd_pcm_hw_params_t *hw_params = NULL; + + drvc = di->priv; + + /* + * Get the number of input channels. Those are our sigrok probes. + * Getting this information needs a detour. We need to open the device, + * then query it for the number of channels. A side-effect of is that we + * create a snd_pcm_hw_params_t object. We take advantage of the + * situation, and pass this object in our dev_context->hw_params, + * eliminating the need to free() it and malloc() it later. + */ + ret = snd_pcm_open(&temp_handle, alsaname, SND_PCM_STREAM_CAPTURE, 0); + if (ret < 0) { + sr_err("Cannot open device: %s.", snd_strerror(ret)); + goto scan_error_cleanup; + } + + ret = snd_pcm_hw_params_malloc(&hw_params); + if (ret < 0) { + sr_err("Error allocating hardware parameter structure: %s.", + snd_strerror(ret)); + goto scan_error_cleanup; + } + + ret = snd_pcm_hw_params_any(temp_handle, hw_params); + if (ret < 0) { + sr_err("Error initializing hardware parameter structure: %s.", + snd_strerror(ret)); + goto scan_error_cleanup; + } + + snd_pcm_hw_params_get_channels_max(hw_params, &channels); + + snd_pcm_close(temp_handle); + temp_handle = NULL; + + /* + * Now we are done querying the number of channels + * If we made it here, then it's time to create our sigrok device. + */ + sr_info("Device %s has %d channels.", alsaname, channels); + if (!(sdi = sr_dev_inst_new(0, SR_ST_INACTIVE, "ALSA:", + cardname, snd_pcm_info_get_name(pcminfo)))) { + sr_err("Device instance malloc failed."); + goto scan_error_cleanup; + } + if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) { + sr_err("Device context malloc failed."); + goto scan_error_cleanup; + } + + devc->hwdev = g_strdup(alsaname); + devc->num_probes = channels; + devc->hw_params = hw_params; + sdi->priv = devc; + sdi->driver = di; + + for (i = 0; i < devc->num_probes; i++) { + char p_name[32]; + snprintf(p_name, sizeof(p_name), "Ch_%d", i); + if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, p_name))) + goto scan_error_cleanup; + sdi->probes = g_slist_append(sdi->probes, probe); + } + + drvc->instances = g_slist_append(drvc->instances, sdi); + *devices = g_slist_append(*devices, sdi); + return; + +scan_error_cleanup: + if (devc) { + if (devc->hwdev) + g_free((void*)devc->hwdev); + g_free(devc); + } + if (sdi) + sr_dev_inst_free(sdi); + if (hw_params) + snd_pcm_hw_params_free(hw_params); + if (temp_handle) + snd_pcm_close(temp_handle); + return; +} + +/** + * \brief Scan all alsa devices, and translate them to sigrok devices + * + * Each alsa device (not alsa card) gets its own sigrok device + * For example, + * hw:1,0 == sigrok device 0 + * hw:1,1 == sigrok device 1 + * hw:2,0 == sigrok device 2 + * hw:2,1 == sigrok device 3 + * hw:2,2 == sigrok device 4 + * [...] + * \n + * We don't currently look at alsa subdevices. We only use subdevice 0. + * Every input device will have a its own channels (Left, Right, etc). Each of + * those channels gets mapped to a different sigrok probe. A device with 4 + * channels will have 4 probes from sigrok's perspective. + */ +SR_PRIV GSList *alsa_scan(GSList *options, struct sr_dev_driver *di) +{ + GSList *devices = NULL; + snd_ctl_t *handle; + int card, ret, dev; + snd_ctl_card_info_t *info; + snd_pcm_info_t *pcminfo; + const char* cardname; + /* TODO */ + (void)options; + + if (snd_ctl_card_info_malloc(&info) < 0) { + sr_err("Cannot malloc card info."); + return NULL; + } + if (snd_pcm_info_malloc(&pcminfo) < 0) { + sr_err("Cannot malloc pcm info."); + return NULL; + } + + card = -1; + while (snd_card_next(&card) >= 0 && card >= 0) { + char hwcard[32]; + snprintf(hwcard, sizeof(hwcard), "hw:%d", card); + if ((ret = snd_ctl_open(&handle, hwcard, 0)) < 0) { + sr_err("Cannot open (%i): %s", card, snd_strerror(ret)); + continue; + } + if ((ret = snd_ctl_card_info(handle, info)) < 0) { + sr_err("Cannot get hardware info (%i): %s", + card, snd_strerror(ret)); + snd_ctl_close(handle); + continue; + } + dev = -1; + while (snd_ctl_pcm_next_device(handle, &dev) >= 0 && dev >= 0) { + char hwdev[32]; + snprintf(hwdev, sizeof(hwdev), "%s,%d", hwcard, dev); + /* + * TODO: We always use subdevice 0, but we have yet to + * explore the possibilities opened up by other + * subdevices. Most hardware only has subdevice 0. + */ + snd_pcm_info_set_device(pcminfo, dev); + snd_pcm_info_set_subdevice(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, + SND_PCM_STREAM_CAPTURE); + if ((ret = snd_ctl_pcm_info(handle, pcminfo)) < 0) { + sr_err("Cannot get device info: %s", + snd_strerror(ret)); + continue; + } + + cardname = snd_ctl_card_info_get_name(info); + sr_info("card %i: %s [%s], device %i: %s [%s]", + card, snd_ctl_card_info_get_id(info), cardname, + dev, snd_pcm_info_get_id(pcminfo), + snd_pcm_info_get_name(pcminfo)); + + alsa_scan_handle_dev(&devices, cardname, hwdev, + di, pcminfo); + } + snd_ctl_close(handle); + } + + snd_pcm_info_free(pcminfo); + snd_ctl_card_info_free(info); + + return devices; +} + +/* + * Helper to be used with g_slist_free_full(); for properly freeing an alsa + * dev instance. + */ +SR_PRIV void alsa_dev_inst_clear(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) + return; + + snd_pcm_hw_params_free(devc->hw_params); + sr_dev_inst_free(sdi); +} + SR_PRIV int alsa_receive_data(int fd, int revents, void *cb_data) { struct sr_dev_inst *sdi; @@ -90,3 +290,4 @@ SR_PRIV int alsa_receive_data(int fd, int revents, void *cb_data) return TRUE; } + diff --git a/hardware/alsa/protocol.h b/hardware/alsa/protocol.h index c66fc1ec..61aafb38 100644 --- a/hardware/alsa/protocol.h +++ b/hardware/alsa/protocol.h @@ -44,11 +44,14 @@ struct dev_context { uint64_t limit_samples; uint64_t num_samples; uint8_t num_probes; + const char *hwdev; snd_pcm_t *capture_handle; snd_pcm_hw_params_t *hw_params; struct pollfd *ufds; void *cb_data; }; +SR_PRIV GSList *alsa_scan(GSList *options, struct sr_dev_driver *di); +SR_PRIV void alsa_dev_inst_clear(struct sr_dev_inst *sdi); SR_PRIV int alsa_receive_data(int fd, int revents, void *cb_data);