From: Alexandru Gagniuc Date: Wed, 26 Dec 2012 18:11:33 +0000 (-0600) Subject: alsa: Find supported samplerates during device scan X-Git-Tag: dsupstream~388 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=65faa197ae4c607c9c127af99af6c8e5b6acd7b3;p=libsigrok.git alsa: Find supported samplerates during device scan Since we are using the 'hw' interface of ALSA, we don't have the luxury of samplerate conversion, given by the 'plughw' interface. If we try to set a samplerate that is not supported, ALSA will just throw an error. We can test for the supported samplerates, and create a list of supported samplerates, then limit the selection to only those values. The frontend can query the list of supported samplerates. Signed-off-by: Alexandru Gagniuc --- diff --git a/hardware/alsa/api.c b/hardware/alsa/api.c index a8806fc2..48a97cd4 100644 --- a/hardware/alsa/api.c +++ b/hardware/alsa/api.c @@ -28,10 +28,6 @@ #include #include -#define DEFAULT_PROBES 2 -#define SAMPLE_WIDTH 16 -#define DEFAULT_SAMPLERATE 44100 - static const int hwcaps[] = { SR_HWCAP_SAMPLERATE, SR_HWCAP_LIMIT_SAMPLES, @@ -171,6 +167,13 @@ static int hw_info_get(int info_id, const void **data, case SR_DI_CUR_SAMPLERATE: *data = &devc->cur_samplerate; break; + case SR_DI_SAMPLERATES: + if (!devc->supp_rates.list) { + sr_err("Instance did not contain a samplerate list"); + return SR_ERR_ARG; + } + *data = &devc->supp_rates; + break; default: sr_err("Invalid info_id: %d.", info_id); return SR_ERR_ARG; @@ -188,7 +191,7 @@ static int hw_dev_config_set(const struct sr_dev_inst *sdi, int hwcap, switch (hwcap) { case SR_HWCAP_SAMPLERATE: - devc->cur_samplerate = *(const uint64_t *)value; + alsa_set_samplerate(sdi, *(const uint64_t *)value); break; case SR_HWCAP_LIMIT_SAMPLES: devc->limit_samples = *(const uint64_t *)value; diff --git a/hardware/alsa/protocol.c b/hardware/alsa/protocol.c index a501ec8f..ebc175a5 100644 --- a/hardware/alsa/protocol.c +++ b/hardware/alsa/protocol.c @@ -24,6 +24,34 @@ #include "libsigrok.h" #include "libsigrok-internal.h" +/* + * There is no way to get a list of supported samplerates from ALSA. We could + * use the 'plughw' interface of ALSA, in which case any format and/or + * samplerate conversion would be performed by ALSA. However, we are interested + * in the hardware capabilities, and have the infrastructure in sigrok to do so. + * We therefore use the 'hw' interface. The downside is that the code gets a + * little bulkier, as we have to keep track of the hardware capabilities, and + * only use those that the hardware supports. Case in point, ALSA will not give + * us a list of capabilities; we have to test for each one individually. Hence, + * we keep lists of the capabilities we are interested in. + */ +static const unsigned int rates[] = { + 5512, + 8000, + 11025, + 16000, + 22050, + 32000, + 44100, + 48000, + 64000, + 88200, + 96000, + 176400, + 192000, + 384000, /* Yes, there are sound cards that go this high */ +}; + static void alsa_scan_handle_dev(GSList **devices, const char *cardname, const char *alsaname, struct sr_dev_driver *di, @@ -34,17 +62,20 @@ static void alsa_scan_handle_dev(GSList **devices, struct dev_context *devc = NULL; struct sr_probe *probe; int ret; - unsigned int i, channels; + unsigned int i, offset, channels, minrate, maxrate, rate; + uint64_t hwrates[ARRAY_SIZE(rates)]; + uint64_t *devrates = NULL; 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 + * Get hardware parameters: + * The number of channels, for example, are our sigrok probes. Getting + * this information needs a detour. We need to open the device, then + * query it and/or test different parameters. 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. */ @@ -70,12 +101,33 @@ static void alsa_scan_handle_dev(GSList **devices, snd_pcm_hw_params_get_channels_max(hw_params, &channels); + /* + * We need to test if each samplerate between min and max is supported. + * Unfortunately, ALSA won't just throw a list at us. + */ + snd_pcm_hw_params_get_rate_min(hw_params, &minrate, 0); + snd_pcm_hw_params_get_rate_max(hw_params, &maxrate, 0); + for (i = 0, offset = 0; i < ARRAY_SIZE(rates); i++) + { + rate = rates[i]; + if (rate < minrate) + continue; + if (rate > maxrate) + break; + ret = snd_pcm_hw_params_test_rate(temp_handle, hw_params, + rate, 0); + if (ret >= 0) + hwrates[offset++] = rate; + } + hwrates[offset++] = 0; + 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. + * Now we are done querying the hardware parameters. + * If we made it here, we know everything we want to know, and 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:", @@ -87,10 +139,17 @@ static void alsa_scan_handle_dev(GSList **devices, sr_err("Device context malloc failed."); goto scan_error_cleanup; } + if (!(devrates = g_try_malloc(offset * sizeof(uint64_t)))) { + sr_err("Samplerate list malloc failed."); + goto scan_error_cleanup; + } devc->hwdev = g_strdup(alsaname); devc->num_probes = channels; devc->hw_params = hw_params; + memcpy(devrates, hwrates, offset * sizeof(uint64_t)); + devc->supp_rates.list = devrates; + sdi->priv = devc; sdi->driver = di; @@ -112,6 +171,8 @@ scan_error_cleanup: g_free((void*)devc->hwdev); g_free(devc); } + if (devrates) + g_free(devrates); if (sdi) sr_dev_inst_free(sdi); if (hw_params) @@ -221,9 +282,50 @@ SR_PRIV void alsa_dev_inst_clear(struct sr_dev_inst *sdi) return; snd_pcm_hw_params_free(devc->hw_params); + g_free((void*)devc->supp_rates.list); sr_dev_inst_free(sdi); } +/* + * Sets the samplerate of the ALSA device + * + * Changes the samplerate of the given ALSA device if the specified samplerate + * is supported by the hardware. + * The new samplerate is recorded, but it is not applied to the hardware. The + * samplerate is applied to the hardware only when acquisition is started via + * dev_acquisition_start(), and cannot be changed during acquisition. To change + * the samplerate, several steps are needed: + * 1) If acquisition is running, it must first be stopped. + * 2) dev_config_set() must be called with the new samplerate. + * 3) When starting a new acquisition, the new samplerate is applied. + */ +SR_PRIV int alsa_set_samplerate(const struct sr_dev_inst *sdi, + const uint64_t newrate) +{ + struct dev_context *devc; + size_t i; + uint64_t rate = 0; + + if (!(devc = sdi->priv)) + return SR_ERR_ARG; + + i = 0; + do { + if (newrate == devc->supp_rates.list[i]) { + rate = newrate; + break; + } + } while (devc->supp_rates.list[i++] != 0); + + if (!rate) { + sr_err("Sample rate " PRIu64 " not supported.", newrate); + return SR_ERR_ARG; + } + + devc->cur_samplerate = rate; + return SR_OK; +} + SR_PRIV int alsa_receive_data(int fd, int revents, void *cb_data) { struct sr_dev_inst *sdi; diff --git a/hardware/alsa/protocol.h b/hardware/alsa/protocol.h index 61aafb38..999310d0 100644 --- a/hardware/alsa/protocol.h +++ b/hardware/alsa/protocol.h @@ -44,15 +44,20 @@ struct dev_context { uint64_t limit_samples; uint64_t num_samples; uint8_t num_probes; + struct sr_samplerates supp_rates; 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_set_samplerate(const struct sr_dev_inst *sdi, + const uint64_t newrate); + SR_PRIV int alsa_receive_data(int fd, int revents, void *cb_data); #endif /* LIBSIGROK_HARDWARE_ALSA_PROTOCOL_H */