]> sigrok.org Git - libsigrok.git/commitdiff
alsa: Find supported samplerates during device scan
authorAlexandru Gagniuc <redacted>
Wed, 26 Dec 2012 18:11:33 +0000 (12:11 -0600)
committerUwe Hermann <redacted>
Mon, 31 Dec 2012 23:37:17 +0000 (00:37 +0100)
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 <redacted>
hardware/alsa/api.c
hardware/alsa/protocol.c
hardware/alsa/protocol.h

index a8806fc2f6df044d6f8542fdfe3be6f7d03c26c6..48a97cd4319d445a35a2f230160236100a5d5feb 100644 (file)
 #include <unistd.h>
 #include <string.h>
 
-#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;
index a501ec8f97bb9b7d048169a9cce7c0e2ea2d0ff2..ebc175a5762ad9689771d935dc67bb5ea19dc0c7 100644 (file)
 #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;
index 61aafb384cb1e58835fac50a5c6c2f2862126aca..999310d0ac4079e937b7dc4563daa63b2e097697 100644 (file)
@@ -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 */