Rebuild the volume control calculations. Implement software mute. Try to delay alsa initialisation as long as possible.
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
+125
-99
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
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
|
||||
|
||||
// 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
|
||||
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;
|
||||
} 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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);
|
||||
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 value of volume_max_db is invalid. It must be in the range %d to
|
||||
%d.",max_db,min_db);
|
||||
}
|
||||
warn("The maximum output level is outside the range of the hardware mixer -- ignored");
|
||||
}
|
||||
}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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);
|
||||
}
|
||||
} 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;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.");
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
conn->fix_volume = temp_fix_volume;
|
||||
memory_barrier(); // no cancellation points
|
||||
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);
|
||||
|
||||
if (config.loudness)
|
||||
loudness_set_volume(software_attenuation / 100);
|
||||
conn->fix_volume = temp_fix_volume;
|
||||
memory_barrier(); // no cancellation points
|
||||
|
||||
if (config.logOutputLevel) {
|
||||
inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
|
||||
}
|
||||
if (config.loudness)
|
||||
loudness_set_volume(software_attenuation / 100);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user