From: Mike Brady Date: Wed, 30 Jan 2019 09:24:10 +0000 (+0000) Subject: Rebuild the volume control calculations. Implement software mute. Try to delay alsa... X-Git-Tag: 3.3RC0~66^2~32 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c5ff0dfed0d11605d75c4795ab059e903fa42c02;p=thirdparty%2Fshairport-sync.git Rebuild the volume control calculations. Implement software mute. Try to delay alsa initialisation as long as possible. --- diff --git a/audio.h b/audio.h index a4f0f178..24536b91 100644 --- 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; diff --git a/audio_alsa.c b/audio_alsa.c index 7fae429b..0b94e812 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -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 = ¶meters; // 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 = ¶meters; // 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 = ¶meters; // 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 = ¶meters; // 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 = ¶meters; // 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 = ¶meters; // 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) diff --git a/common.h b/common.h index 4a111e2c..463e49b4 100644 --- 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; diff --git a/player.c b/player.c index 5efb679c..52356ce0 100644 --- 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; } diff --git a/player.h b/player.h index 84521893..00793a92 100644 --- 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