]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Add support for ALSA softvol plugin 293/head
authorJörg Krause <joerg.krause@embedded.rocks>
Fri, 6 May 2016 20:03:44 +0000 (22:03 +0200)
committerJörg Krause <joerg.krause@embedded.rocks>
Fri, 6 May 2016 20:23:16 +0000 (22:23 +0200)
The ALSA softvol plugin allows the user to add a new volume control and control
the sound volume by software. This is necessary if the sound card can't control
the volume by hardware.

An example configuration in the `~/.asoundrc` file might look like this:
```
pcm.softvol {
  type softvol
  slave {
    pcm "hw:0"
  }
  control {
    name "Master"
    card 0
  }
  min_dB -57.2
  max_dB -6.2
}
ctl.softvol {
  type hw
  card 0
}
```

The corresponding entries in the shairport-sync would be:
```
alsa =
{
  output_device = "softvol";
  mixer_control_name = "Master";
}
```

Please check the (ALSA documentation)[http://alsa.opensrc.org/Softvol] and the
(ALSA PCM library reference)[http://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html]

If shairport-syncs alsa backend does not have success in getting the dB volume
information from the hardware device it tries to get these information from a
software volume control with the name mixer device name as for the hardware
mixer.

audio_alsa.c

index 2a2096a656faa3ba57ffe6dc0093d18206e0fdff..f29f3b302d41cf543c7ffbaaf0d3d2e5b69924ff 100644 (file)
@@ -59,9 +59,9 @@ audio_output audio_alsa = {
     .flush = &flush,
     .delay = &delay,
     .play = &play,
-    .mute = NULL,      // to be set later on...
-    .volume = NULL,    // to be set later on...
-    .parameters = NULL // to be set later on...
+    .mute = &mute,
+    .volume = &volume,
+    .parameters = &parameters
 };
 
 static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -70,7 +70,8 @@ static unsigned int desired_sample_rate;
 
 static snd_pcm_t *alsa_handle = NULL;
 static snd_pcm_hw_params_t *alsa_params = NULL;
-
+static snd_ctl_t *ctl = NULL;
+static snd_ctl_elem_id_t *elem_id = NULL;
 static snd_mixer_t *alsa_mix_handle = NULL;
 static snd_mixer_elem_t *alsa_mix_elem = NULL;
 static snd_mixer_selem_id_t *alsa_mix_sid = NULL;
@@ -82,7 +83,7 @@ static char *alsa_mix_dev = NULL;
 static char *alsa_mix_ctrl = "Master";
 static int alsa_mix_index = 0;
 static int hardware_mixer = 0;
-
+static int has_softvol = 0;
 
 static int play_number;
 static int64_t accumulated_delay, accumulated_da_delay;
@@ -299,8 +300,29 @@ static int init(int argc, char **argv) {
       } else {
         // use the linear scale and do the db conversion ourselves
         debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have "
-                 "a dB volume scale, so it can't be used.",
+                 "a dB volume scale, so it can't be used. Trying software "
+                 "volume control.",
               alsa_mix_ctrl);
+
+        if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0)
+          die("Cannot open control \"%s\"", alsa_mix_dev);
+        if (snd_ctl_elem_id_malloc(&elem_id) < 0)
+          die("Cannot allocate memory for control \"%s\"", alsa_mix_dev);
+        snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
+        snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
+
+        if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb,
+                                               &alsa_mix_maxdb) == 0) {
+          debug(1, "Volume control \"%s\" has dB volume from %f to %f.",
+                    alsa_mix_ctrl,
+                    (1.0 * alsa_mix_mindb) / 100.0,
+                    (1.0 * alsa_mix_maxdb) / 100.0);
+          has_softvol = 1;
+        } else {
+          debug(1, "Cannot get the dB range from the volume control \"%s\"",
+                    alsa_mix_ctrl);
+        }
+
         /*
         debug(1, "Min and max volumes are %d and
         %d.",alsa_mix_minv,alsa_mix_maxv);
@@ -728,11 +750,30 @@ static void volume(double vol) {
        debug(2, "Setting volume db to %f.", vol);
   set_volume = vol;
   if (hardware_mixer && alsa_mix_handle) {
-    if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 0) != 0) {
-      debug(1, "Can't set playback volume accurately to %f dB.", vol);
-      if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0)
-        if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 1) != 0)
-          die("Failed to set playback dB volume");
+    if (has_softvol) {
+      if (ctl && elem_id) {
+        snd_ctl_elem_value_t *value;
+        long raw;
+
+        if (snd_ctl_convert_from_dB(ctl, elem_id, (long) vol, &raw, 0) < 0)
+        debug(1, "Failed converting dB gain to raw volume value for the "
+                 "software volume control.");
+
+        snd_ctl_elem_value_alloca(&value);
+        snd_ctl_elem_value_set_id(value, elem_id);
+        snd_ctl_elem_value_set_integer(value, 0, raw);
+        snd_ctl_elem_value_set_integer(value, 1, raw);
+        if (snd_ctl_elem_write(ctl, value) < 0)
+        debug(1, "Failed to set playback dB volume for the software volume "
+                 "control.");
+      }
+    } else {
+      if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 0) != 0) {
+        debug(1, "Can't set playback volume accurately to %f dB.", vol);
+        if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0)
+          if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 1) != 0)
+            die("Failed to set playback dB volume");
+      }
     }
   }
   pthread_mutex_unlock(&alsa_mutex);