]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Rebuild the volume control calculations. Implement software mute. Try to delay alsa...
authorMike Brady <mikebrady@eircom.net>
Wed, 30 Jan 2019 09:24:10 +0000 (09:24 +0000)
committerMike Brady <mikebrady@eircom.net>
Wed, 30 Jan 2019 09:24:10 +0000 (09:24 +0000)
audio.h
audio_alsa.c
common.h
player.c
player.h

diff --git a/audio.h b/audio.h
index a4f0f178e3357edb56bb8e74fb42ebdc35fe2fbe..24536b9167af15680d4cf1d476e91078a1e4afd5 100644 (file)
--- a/audio.h
+++ b/audio.h
@@ -47,7 +47,8 @@ typedef struct {
   void (*parameters)(audio_parameters *info);
 
   // may be NULL, in which case software muting is used.
-  void (*mute)(int do_mute);
+  // also, will return a 1 if it is actually using the mute facility, 0 otherwise
+  int (*mute)(int do_mute);
 
 } audio_output;
 
index 7fae429b4737c08ceeda39cabd5fcf65e36c7398..0b94e812291450976729ad6111147c8407406863 100644 (file)
@@ -59,7 +59,7 @@ static void volume(double vol);
 void do_volume(double vol);
 
 static void parameters(audio_parameters *info);
-static void mute(int do_mute);
+int mute(int do_mute); // returns true if it actually is allowed to use the mute
 static double set_volume;
 static int output_method_signalled = 0;
 
@@ -93,6 +93,7 @@ static unsigned int desired_sample_rate;
 static enum sps_format_t sample_format;
 int frame_size; // in bytes for interleaved stereo
 
+int alsa_device_initialised; // boolean to ensure the initialisation is only done once
 snd_pcm_t *alsa_handle = NULL;
 static snd_pcm_hw_params_t *alsa_params = NULL;
 static snd_pcm_sw_params_t *alsa_swparams = NULL;
@@ -620,12 +621,117 @@ int open_alsa_device(void) {
   return result;
 }
 
+int do_alsa_device_init_if_needed() {
+  debug(1,"do_alsa_device_init_if_needed()");
+  int response = 0;
+  // do any alsa device initialisation (general case) if needed
+  // at present, this is only needed if a hardware mixer is being used
+  // if there's a hardware mixer, it needs to be initialised before first use
+  if (alsa_device_initialised == 0) {
+  alsa_device_initialised = 1;
+  if (hardware_mixer) {
+    int oldState;
+    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+
+    if (alsa_mix_dev == NULL)
+      alsa_mix_dev = alsa_out_dev;
+
+    // Now, start trying to initialise the alsa device with the settings obtained
+    pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1);
+    if (open_mixer() == 1) {
+      if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
+          0)
+        debug(1, "Can't read mixer's [linear] min and max volumes.");
+      else {
+        if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
+                                                  &alsa_mix_maxdb) == 0) {
+
+          audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
+          audio_alsa.parameters = &parameters; // likewise the parameters stuff
+          if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
+            // For instance, the Raspberry Pi does this
+            debug(1, "Lowest dB value is a mute");
+            mixer_volume_setting_gives_mute = 1;
+            alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always
+            // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
+            // debug(1, "Try minimum volume + 1 as lowest true attenuation value");
+            if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
+                                                    &alsa_mix_mindb) != 0)
+              debug(1, "Can't get dB value corresponding to a minimum volume + 1.");
+          }
+          debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
+                (1.0 * alsa_mix_maxdb) / 100.0);
+        } else {
+          // use the linear scale and do the db conversion ourselves
+          warn("The hardware mixer specified -- \"%s\" -- does not have "
+               "a dB volume scale.",
+               alsa_mix_ctrl);
+
+          if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
+            warn("Cannot open control \"%s\"", alsa_mix_dev);
+            response = -1;
+          }
+          if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
+            debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
+            elem_id = NULL;
+            response = -2;
+          } else {
+            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, "alsa: hardware mixer \"%s\" selected, with 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;
+              audio_alsa.volume =
+                  &volume; // insert the volume function now we know it can do dB stuff
+              audio_alsa.parameters = &parameters; // likewise the parameters stuff
+            } 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);
+          alsa_mix_maxdb = 0;
+          if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
+            alsa_mix_mindb =
+          -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
+          else if (alsa_mix_maxv!=0)
+            alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
+          audio_alsa.volume = &linear_volume; // insert the linear volume function
+          audio_alsa.parameters = &parameters; // likewise the parameters stuff
+          debug(1,"Max and min dB calculated are %d and
+          %d.",alsa_mix_maxdb,alsa_mix_mindb);
+          */
+        }
+      }
+      if (((config.alsa_use_hardware_mute == 1) &&
+           (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
+          mixer_volume_setting_gives_mute) {
+        audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff
+        // debug(1, "Has mixer and mute ability we will use.");
+      } else {
+        // debug(1, "Has mixer but not using hardware mute.");
+      }
+      close_mixer();
+    }
+    debug_mutex_unlock(&alsa_mutex, 3); // release the mutex
+
+    pthread_cleanup_pop(0);
+    pthread_setcancelstate(oldState, NULL);
+  }
+  }
+  return response;
+}
+
 static int init(int argc, char **argv) {
   // for debugging
   snd_output_stdio_attach(&output, stdout, 0);
 
   // debug(2,"audio_alsa init called.");
   int response = 0; // this will be what we return to the caller.
+  alsa_device_initialised = 0;
   const char *str;
   int value;
   // double dvalue;
@@ -871,101 +977,9 @@ static int init(int argc, char **argv) {
   }
 
   debug(1, "alsa: output device name is \"%s\".", alsa_out_dev);
-
-  if (hardware_mixer) {
-    int oldState;
-    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
-
-    if (alsa_mix_dev == NULL)
-      alsa_mix_dev = alsa_out_dev;
-
-    // Now, start trying to initialise the alsa device with the settings obtained
-    pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1);
-    if (open_mixer() == 1) {
-      if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
-          0)
-        debug(1, "Can't read mixer's [linear] min and max volumes.");
-      else {
-        if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
-                                                  &alsa_mix_maxdb) == 0) {
-
-          audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
-          audio_alsa.parameters = &parameters; // likewise the parameters stuff
-          if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
-            // For instance, the Raspberry Pi does this
-            debug(1, "Lowest dB value is a mute");
-            mixer_volume_setting_gives_mute = 1;
-            alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always
-            // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
-            // debug(1, "Try minimum volume + 1 as lowest true attenuation value");
-            if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
-                                                    &alsa_mix_mindb) != 0)
-              debug(1, "Can't get dB value corresponding to a minimum volume + 1.");
-          }
-          debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
-                (1.0 * alsa_mix_maxdb) / 100.0);
-        } else {
-          // use the linear scale and do the db conversion ourselves
-          warn("The hardware mixer specified -- \"%s\" -- does not have "
-               "a dB volume scale.",
-               alsa_mix_ctrl);
-
-          if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
-            warn("Cannot open control \"%s\"", alsa_mix_dev);
-            response = -1;
-          }
-          if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
-            debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
-            elem_id = NULL;
-            response = -2;
-          } else {
-            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, "alsa: hardware mixer \"%s\" selected, with 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;
-              audio_alsa.volume =
-                  &volume; // insert the volume function now we know it can do dB stuff
-              audio_alsa.parameters = &parameters; // likewise the parameters stuff
-            } 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);
-          alsa_mix_maxdb = 0;
-          if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
-            alsa_mix_mindb =
-          -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
-          else if (alsa_mix_maxv!=0)
-            alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
-          audio_alsa.volume = &linear_volume; // insert the linear volume function
-          audio_alsa.parameters = &parameters; // likewise the parameters stuff
-          debug(1,"Max and min dB calculated are %d and
-          %d.",alsa_mix_maxdb,alsa_mix_mindb);
-          */
-        }
-      }
-      if (((config.alsa_use_hardware_mute == 1) &&
-           (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
-          mixer_volume_setting_gives_mute) {
-        audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff
-        // debug(1, "Has mixer and mute ability we will use.");
-      } else {
-        // debug(1, "Has mixer but not using hardware mute.");
-      }
-      close_mixer();
-    }
-    debug_mutex_unlock(&alsa_mutex, 3); // release the mutex
-
-    pthread_cleanup_pop(0);
-    pthread_setcancelstate(oldState, NULL);
-  } else {
-    debug(1, "alsa: no hardware mixer selected.");
-  }
+  
+  do_alsa_device_init_if_needed();
+  
   alsa_mix_handle = NULL;
 
   // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the
@@ -994,7 +1008,7 @@ static void deinit(void) {
 }
 
 static void start(int i_sample_rate, int i_sample_format) {
-  // debug(2,"audio_alsa start called.");
+  debug(1,"audio_alsa start called.");
   if (i_sample_rate == 0)
     desired_sample_rate = 44100; // default
   else
@@ -1010,6 +1024,10 @@ static void start(int i_sample_rate, int i_sample_format) {
 
   stall_monitor_start_time = 0;
   stall_monitor_frame_count = 0;
+  if (alsa_device_initialised == 0) {
+    debug(1,"alsa: start() calling do_alsa_device_init_if_needed.");
+    do_alsa_device_init_if_needed();
+  }
 }
 
 int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) {
@@ -1380,7 +1398,10 @@ static void linear_volume(double vol) {
 }
 */
 
-static void mute(int mute_state_requested) {
+int mute(int mute_state_requested) {
+  int response = 0;
+  if (config.alsa_use_hardware_mute == 1)
+    response = 1; // return true if actually using the mute facility
   // debug(1,"External Mute Request: %d",mute_state_requested);
   pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1);
   mute_request_pending = 1;
@@ -1388,6 +1409,7 @@ static void mute(int mute_state_requested) {
   do_mute(mute_state_requested);
   debug_mutex_unlock(&alsa_mutex, 3);
   pthread_cleanup_pop(0); // release the mutex
+  return response;
 }
 
 void do_mute(int mute_state_requested) {
@@ -1447,8 +1469,12 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a
 void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
   while (1) {
     int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000);
-    pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1);
+    pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1);
     // check possible state transitions here
+    if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) {
+      debug(1,"alsa: alsa_buffer_monitor_thread_code() calling do_alsa_device_init_if_needed.");
+      do_alsa_device_init_if_needed();
+    }
     if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) {
       // open the dac and move to abm_connected mode
       if (do_open() == 0)
index 4a111e2cf68428312c27f38540402552268ce5d6..463e49b472180c44d21d785e15444b2463bb0d8f 100644 (file)
--- a/common.h
+++ b/common.h
@@ -182,6 +182,8 @@ typedef struct {
                               // into the "active" mode.
   uint32_t volume_range_db;   // the range, in dB, from max dB to min dB. Zero means use the mixer's
                               // native range.
+  int volume_range_hw_priority; // when lowering the volume, use all the hw attenuation before using
+                                // sw attenuation
   enum sps_format_t output_format;
   enum volume_control_profile_type volume_control_profile;
   int output_rate;
index 5efb679cc1675ccfbed64b9f61317771327bc72d..52356ce027af2d7ab00696f7c3a4775aee4476c8 100644 (file)
--- a/player.c
+++ b/player.c
@@ -1662,6 +1662,7 @@ void *player_thread_func(void *arg) {
       (config.playback_mode == ST_mono))
     conn->enable_dither = 1;
 
+  // remember, the output device may never have been initialised prior to this call
   config.output->start(config.output_rate, config.output_format); // will need a corresponding stop
 
   // we need an intermediate "transition" buffer
@@ -2276,8 +2277,14 @@ void *player_thread_func(void *arg) {
               else {
                 if (play_samples == 0)
                   debug(1, "play_samples==0 skipping it (1).");
-                else
+                else {
+                  if (conn->software_mute_enabled) {
+                    generate_zero_frames(
+                      conn->outbuf, play_samples,
+                      config.output_format, conn->enable_dither, conn->previous_random_number);
+                  }
                   config.output->play(conn->outbuf, play_samples);
+                }
               }
 
               // check for loss of sync
@@ -2311,8 +2318,14 @@ void *player_thread_func(void *arg) {
                                       conn->outbuf, 0, conn->enable_dither, conn);
             if (conn->outbuf == NULL)
               debug(1, "NULL outbuf to play -- skipping it.");
-            else
+            else {
+              if (conn->software_mute_enabled) {
+                generate_zero_frames(
+                  conn->outbuf, play_samples,
+                  config.output_format, conn->enable_dither, conn->previous_random_number);
+              }
               config.output->play(conn->outbuf, play_samples); // remove the (short*)!
+            }
           }
 
           // mark the frame as finished
@@ -2530,235 +2543,198 @@ void *player_thread_func(void *arg) {
   pthread_exit(NULL);
 }
 
-// takes the volume as specified by the airplay protocol
 void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) {
-
-  // no cancellation points here if we assume that the mute call to the back end has no cancellation
-  // points
-
-  // The volume ranges -144.0 (mute) or -30 -- 0. See
-  // http://git.zx2c4.com/Airtunes2/about/#setting-volume
-  // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30
-  // equal increments. Since the human ear's response is roughly logarithmic, we imagine these to
-  // be power dB, i.e. from -30dB to 0dB.
-
-  // We may have a hardware mixer, and if so, we will give it priority.
-  // If a desired volume range is given, then we will try to accommodate it from
-  // the top of the hardware mixer's range downwards.
-
-  // If no desired volume range is given, we will use the native resolution of the hardware mixer,
-  // if any,
-  // or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0
-  // dB,
-  // corresponding to a multiplier of 1 to 65535.
-
-  // Otherwise, we will accommodate the desired volume range in the combination of the software and
-  // hardware mixer
-  // Intuitively (!), it seems best to give the hardware mixer as big a role as possible, so
-  // we will use its full range and then accommodate the rest of the attenuation in software.
-  // A problem is that we don't know whether the lowest hardware volume actually mutes the output
-  // so we must assume that it does, and for this reason, the volume control goes at the "bottom" of
-  // the adjustment range
-
-  // The dB range of a value from 1 to 65536 is about 96.3 dB (log10 of 65536 is 4.8164).
-  // Since the levels correspond with amplitude, they correspond to voltage, hence voltage dB,
-  // or 20 times the log of the ratio. Then multiplied by 100 for convenience.
-  // Thus, we ask our vol2attn function for an appropriate dB between -96.3 and 0 dB and translate
-  // it back to a number.
-
-  int32_t hw_min_db, hw_max_db, hw_range_db, min_db,
-      max_db; // hw_range_db is a flag; if 0 means no mixer
-
-  if (config.output->parameters) { // no cancellation points in here
+  debug(1,"player_volume_without_notification %f",airplay_volume);
+//first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw attenuation on top
+
+  enum volume_mode_type {vol_sw_only, vol_hw_only, vol_both} volume_mode;
+  
+  // take account of whether there is a hardware mixer, if a max volume has been specified and if a range has been specified
+  // the range might imply that both hw and software mixers are needed, so calculate this
+  
+  int32_t hw_max_db, hw_min_db;
+  int32_t sw_max_db = 0, sw_min_db = -9630;
+  if (config.output->parameters) {
+    volume_mode = vol_hw_only;
     audio_parameters audio_information;
-    // have a hardware mixer
     config.output->parameters(&audio_information);
     hw_max_db = audio_information.maximum_volume_dB;
     hw_min_db = audio_information.minimum_volume_dB;
-    hw_range_db = hw_max_db - hw_min_db;
-  } else {
-    // don't have a hardware mixer
-    hw_max_db = hw_min_db = hw_range_db = 0;
-  }
-
-  int32_t sw_min_db = -9630;
-  int32_t sw_max_db = 0;
-  int32_t sw_range_db = sw_max_db - sw_min_db;
-  int32_t desired_range_db = 0; // this is also used as a flag; if 0 means no desired range
-
-  if (config.volume_range_db)
-    desired_range_db = (int32_t)trunc(config.volume_range_db * 100);
-
-  // This is wrong, I think -- it doesn't work properly if the volume range is composite and you
-  // want to set a maximum value which should affect the hardware mixer.
-
-  if (config.volume_max_db_set) {
-    if (hw_range_db) {
-      if (((config.volume_max_db * 100) < hw_max_db) &&
-          ((config.volume_max_db * 100) > hw_min_db)) {
-        hw_max_db = (int)config.volume_max_db * 100;
-        hw_range_db = hw_max_db - hw_min_db;
-      } else {
-        inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB "
-               "to %d dB. It will be ignored.",
-               (int)(hw_max_db / 100), (int)(hw_min_db / 100));
-      }
-    } else {
-      if (((config.volume_max_db * 100) < sw_max_db) &&
-          ((config.volume_max_db * 100) > sw_min_db)) {
-        sw_max_db = (int)config.volume_max_db * 100;
-        sw_range_db = sw_max_db - sw_min_db;
+    if (config.volume_max_db_set) {
+      if (((config.volume_max_db * 100) <= hw_max_db) &&
+          ((config.volume_max_db * 100) >= hw_min_db))
+        hw_max_db = (int32_t)config.volume_max_db * 100;
+      else if (config.volume_range_db) {
+        hw_max_db = hw_min_db;
+        sw_max_db = (config.volume_max_db * 100) - hw_min_db;
       } else {
-        inform("The volume_max_db setting is out of range of the software attenuation's limits of "
-               "0 dB to -96.3 dB. It will be ignored.");
-      }
+        warn("The maximum output level is outside the range of the hardware mixer -- ignored");
+      }    
     }
-  }
-
-  if (desired_range_db) {
-    // debug(1,"An attenuation range of %d is requested.",desired_range_db);
-    // we have a desired volume range.
-    if (hw_range_db) {
-      // we have a hardware mixer
-      if (hw_range_db >= desired_range_db) {
-        // the hardware mixer can accommodate the desired range
-        max_db = hw_max_db;
-        min_db = max_db - desired_range_db;
-      } else {
-        // we have a hardware mixer and a desired range greater than the mixer's range.
-        if ((hw_range_db + sw_range_db) < desired_range_db) {
-          inform("The volume attenuation range %f is greater than can be accommodated by the "
-                 "hardware and software -- set to %f.",
-                 config.volume_range_db, hw_range_db + sw_range_db);
-          desired_range_db = hw_range_db + sw_range_db;
-        }
-        min_db = hw_min_db;
-        max_db = min_db + desired_range_db;
-      }
-    } else {
-      // we have a desired volume range and no hardware mixer
-      if (sw_range_db < desired_range_db) {
-        inform("The volume attenuation range %f is greater than can be accommodated by the "
-               "software -- set to %f.",
-               config.volume_range_db, sw_range_db);
-        desired_range_db = sw_range_db;
-      }
-      max_db = sw_max_db;
-      min_db = max_db - desired_range_db;
+    
+    // here, we have set limits on the hw_max_db and the sw_max_db
+    // but we haven't actually decided whether we need both hw and software attenuation
+    // only if a range is specified could we need both
+    if (config.volume_range_db) {
+      // see if the range requested exceeds the hardware range available
+      int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100);
+      if ((desired_range_db) > (hw_max_db - hw_min_db)) {
+        volume_mode = vol_both;
+        int32_t desired_sw_range = desired_range_db - (hw_max_db - hw_min_db);
+        if ((sw_max_db - desired_sw_range) < sw_min_db)
+          warn("The range requested is too large to accommodate -- ignored.");
+        else 
+          sw_min_db = (sw_max_db - desired_sw_range);
+      }    
     }
   } else {
-    // we do not have a desired volume range, so use the mixer's volume range, if there is one.
-    // debug(1,"No attenuation range requested.");
-    if (hw_range_db) {
-      min_db = hw_min_db;
-      max_db = hw_max_db;
-    } else {
-      min_db = sw_min_db;
-      max_db = sw_max_db;
+    // debug(1,"has no hardware mixer");
+    volume_mode = vol_sw_only;
+    if (config.volume_max_db_set) {
+      if (((config.volume_max_db * 100) <= sw_max_db) &&
+          ((config.volume_max_db * 100) >= sw_min_db))
+        sw_max_db = (int32_t)config.volume_max_db * 100; 
     }
+    if (config.volume_range_db) {
+    // see if the range requested exceeds the software range available
+    int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100);
+    if ((desired_range_db) > (sw_max_db - sw_min_db))
+      warn("The range requested is too large to accommodate -- ignored.");
+    else 
+      sw_min_db = (sw_max_db - desired_range_db);
+    }    
   }
-
-  /*
-    if (config.volume_max_db_set) {
-      if ((config.volume_max_db*100<=max_db) && (config.volume_max_db*100>=min_db)) {
-        debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db);
-        max_db = (int)(config.volume_max_db*100);
-      } else {
-        inform("The value of volume_max_db is invalid. It must be in the range %d to
-    %d.",max_db,min_db);
+  
+  // here, we know whether it's hw volume control only, sw only or both, and we have the hw and sw limits.
+  // if it's both, we haven't decided whether hw or sw should be on top
+  // we have to consider the settings ignore_volume_control and mute.
+  
+  if (config.ignore_volume_control == 0) {
+    if (airplay_volume == -144.0) {
+            
+      if ((config.output->mute) && (config.output->mute(1) != 0))
+        debug(1,"hardware mute is enabled.");
+      else {
+        conn->software_mute_enabled = 1;
+        debug(1,"software mute is enabled.");
+        
       }
-    }
-  */
-  double hardware_attenuation = 0.0, software_attenuation = 0.0;
-  double scaled_attenuation = hw_min_db + sw_min_db;
-
-  // now, we can map the input to the desired output volume
-  if ((airplay_volume == -144.0) && (config.ignore_volume_control == 0)) {
-    // do a mute
-    // needed even with hardware mute, as when sound is unmuted it might otherwise be very loud.
-    hardware_attenuation = hw_min_db;
-    if (config.output->mute) {
-      // allow the audio material to reach the mixer, but mute the mixer
-      // it the mute is removed externally, the material with be there
-      software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db  is +4 and
-                                                               // the max is +40, this will be -36
-                                                               // (all by 100, of course)
-      // debug(1,"Mute, with hardware mute and software_attenuation set to
-      // %d.",software_attenuation);
-      config.output->mute(1); // use real mute if it's there
+
     } else {
-      if (config.output->volume == NULL) { // if there is also no hardware volume control
-        software_attenuation = sw_min_db;  // set any software output to zero too
-        // debug(1,"Mute, with no hardware mute and software_attenuation set to
-        // %d.",software_attenuation);
+      int32_t max_db,min_db;
+      switch (volume_mode) {
+        case vol_hw_only:
+          max_db = hw_max_db;
+          min_db = hw_min_db;
+          break;
+        case vol_sw_only:
+          max_db = sw_max_db;
+          min_db = sw_min_db;
+          break;
+        case vol_both:
+          debug(1,"dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db));
+          max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested
+          min_db = 0;
+          break;
+        default:
+          debug(1,"error in pv -- not in a volume mode");
+          break;
       }
-    }
-  } else {
-    if (config.output->mute)
-      config.output->mute(0); // unmute mute if it's there
-    if (config.ignore_volume_control == 1)
-      scaled_attenuation = max_db;
-    else if (config.volume_control_profile == VCP_standard)
-      scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points
-    else if (config.volume_control_profile == VCP_flat)
-      scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points
-    else
-      debug(1, "Unrecognised volume control profile");
-
-    if (hw_range_db) {
-      // if there is a hardware mixer
-      if (scaled_attenuation <= hw_max_db) {
-        // the attenuation is so low that's it's in the hardware mixer's range
-        // debug(1,"Attenuation all taken care of by the hardware mixer.");
-        hardware_attenuation = scaled_attenuation;
-        software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db  is +4 and
-                                                                 // the max is +40, this will be -36
-                                                                 // (all by 100, of course)
-      } else {
-        // debug(1,"Attenuation taken care of by hardware and software mixer.");
-        hardware_attenuation = hw_max_db; // the hardware mixer is turned up full
-        software_attenuation = sw_max_db - (max_db - scaled_attenuation);
+      double scaled_attenuation = 0.0;
+      if (config.volume_control_profile == VCP_standard)
+        scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points
+      else if (config.volume_control_profile == VCP_flat)
+        scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points
+      else
+        debug(1, "Unrecognised volume control profile");
+      
+      // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward.
+      double hardware_attenuation = 0.0;
+      double software_attenuation = 0.0;
+
+      switch (volume_mode) {
+        case vol_hw_only:
+          hardware_attenuation = scaled_attenuation;
+          break;
+        case vol_sw_only:
+          software_attenuation = scaled_attenuation;
+          break;
+        case vol_both:
+          // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers
+          // if we give the hw priority, that means when lowering the volume, set the hw volume to its lowest
+          // before using the sw attenuation.
+          // similarly, if we give the sw priority, that means when lowering the volume, set the sw volume to its lowest
+          // before using the hw attenuation.
+          // one imagines that hw priority is likely to be much better
+          if (config.volume_range_hw_priority) {
+            // hw priority
+            if ((sw_max_db - sw_min_db) > scaled_attenuation) {
+              software_attenuation = sw_min_db + scaled_attenuation;
+              hardware_attenuation = hw_min_db;
+            } else {
+              software_attenuation = sw_max_db;
+              hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db);
+            }
+           } else {
+            // sw priority
+            if ((hw_max_db - hw_min_db) > scaled_attenuation) {
+              hardware_attenuation = hw_min_db + scaled_attenuation;
+              software_attenuation = sw_min_db;
+            } else {
+              hardware_attenuation = hw_max_db;
+              software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db);
+            }
+          }
+          break;
+        default:
+          debug(1,"error in pv -- not in a volume mode");
+          break;
+      }
+      
+      
+      if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume))
+        config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value
+        // debug(1,"Hardware attenuation set to %f for airplay volume of
+        // %f.",hardware_attenuation,airplay_volume);
+    
+      if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) {
+        double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000);
+        // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of
+        // %f",software_attenuation,temp_fix_volume,airplay_volume);
+
+        conn->fix_volume = temp_fix_volume;
+        memory_barrier(); // no cancellation points
+
+        if (config.loudness)
+          loudness_set_volume(software_attenuation / 100);
       }
-    } else {
-      // if there is no hardware mixer, the scaled_volume is the software volume
-      // debug(1,"Attenuation all taken care of by the software mixer.");
-      software_attenuation = scaled_attenuation;
-    }
-  }
-
-  if ((config.output->volume) && (hw_range_db)) {
-    config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value
-    // debug(1,"Hardware attenuation set to %f for airplay volume of
-    // %f.",hardware_attenuation,airplay_volume);
-  }
-  double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000);
-  // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of
-  // %f",software_attenuation,temp_fix_volume,airplay_volume);
-
-  conn->fix_volume = temp_fix_volume;
-  memory_barrier(); // no cancellation points
 
-  if (config.loudness)
-    loudness_set_volume(software_attenuation / 100);
 
-  if (config.logOutputLevel) {
-    inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
-  }
+      if (config.logOutputLevel) {
+        inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
+      }
 
 #ifdef CONFIG_METADATA
-  char *dv = malloc(128); // will be freed in the metadata thread
-  if (dv) {
-    memset(dv, 0, 128);
-    if (config.ignore_volume_control == 1)
-      snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
-    else
-      snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
-               min_db / 100.0, max_db / 100.0);
-    send_ssnc_metadata('pvol', dv, strlen(dv), 1);
-  }
+      char *dv = malloc(128); // will be freed in the metadata thread
+      if (dv) {
+        memset(dv, 0, 128);
+        if (config.ignore_volume_control == 1)
+          snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
+        else
+          snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
+                   min_db / 100.0, max_db / 100.0);
+        send_ssnc_metadata('pvol', dv, strlen(dv), 1);
+      }
 #endif
+      // here, store the volume for possible use in the future
 
-  // here, store the volume for possible use in the future
+      if (config.output->mute)
+        config.output->mute(0);
+      conn->software_mute_enabled = 0;
+      
+      debug(1,"pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting is disabled.", volume_mode, software_attenuation, hardware_attenuation);
+    }
+  }
   config.airplay_volume = airplay_volume;
 }
 
index 845218936cba6ae9ec5ad224190a5a9522ddba14..00793a927c53af9c290b15bee9338c37dd651273 100644 (file)
--- a/player.h
+++ b/player.h
@@ -68,14 +68,15 @@ typedef struct {
 } stream_cfg;
 
 typedef struct {
-  int connection_number;    // for debug ID purposes, nothing else...
-  int resend_interval;      // this is really just for debugging
-  int AirPlayVersion;       // zero if not an AirPlay session. Used to help calculate latency
-  uint32_t latency;         // the actual latency used for this play session
-  uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero
-                            // otherwise
-  uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero
-                            // otherwise
+  int connection_number;     // for debug ID purposes, nothing else...
+  int resend_interval;       // this is really just for debugging
+  int AirPlayVersion;        // zero if not an AirPlay session. Used to help calculate latency
+  uint32_t latency;          // the actual latency used for this play session
+  uint32_t minimum_latency;  // set if an a=min-latency: line appears in the ANNOUNCE message; zero
+                             // otherwise
+  uint32_t maximum_latency;  // set if an a=max-latency: line appears in the ANNOUNCE message; zero
+                             // otherwise
+  int software_mute_enabled; // if we don't have a real mute that we can use
 
   int fd;
   int authorized;   // set if a password is required and has been supplied