diff --git a/audio.h b/audio.h index 4738a071..359f3402 100644 --- a/audio.h +++ b/audio.h @@ -5,13 +5,9 @@ #include typedef struct { - double airplay_volume; double current_volume_dB; - int minimum_volume_dB; - int maximum_volume_dB; - int has_true_mute; - int is_muted; - int valid; + int32_t minimum_volume_dB; + int32_t maximum_volume_dB; } audio_parameters; typedef struct { @@ -42,6 +38,10 @@ typedef struct { // may be NULL, in which case soft volume parameters are used void (*parameters)(audio_parameters *info); + + // may be NULL, in which case software muting is used. + void (*mute)(int do_mute); + } audio_output; audio_output *audio_get_output(char *name); diff --git a/audio_alsa.c b/audio_alsa.c index f9851f18..4a11260d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -46,6 +46,7 @@ static uint32_t delay(void); static void volume(double vol); static void linear_volume(double vol); static void parameters(audio_parameters *info); +static void mute(int do_mute); static int has_mute = 0; static double set_volume; @@ -234,13 +235,6 @@ static int init(int argc, char **argv) { } 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); - if (config.volume_range_db) { - long suggested_alsa_min_db = alsa_mix_maxdb - (long)trunc(config.volume_range_db*100); - if (suggested_alsa_min_db > alsa_mix_mindb) - alsa_mix_mindb = suggested_alsa_min_db; - else - inform("The volume_range_db setting, %f is greater than the native range of the mixer %f, so it is ignored.",config.volume_range_db,(alsa_mix_maxdb-alsa_mix_mindb)/100.0); - } } else { // use the linear scale and do the db conversion ourselves debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have a dB volume scale, so it can't be used.",alsa_mix_ctrl); @@ -447,35 +441,30 @@ static void stop(void) { } static void parameters(audio_parameters *info) { - info->has_true_mute = has_mute; - info->is_muted = ((has_mute) && (set_volume == -144.0)); info->minimum_volume_dB = alsa_mix_mindb; info->maximum_volume_dB = alsa_mix_maxdb; - info->airplay_volume = set_volume; - info->current_volume_dB = vol2attn(set_volume, alsa_mix_maxdb, alsa_mix_mindb); } static void volume(double vol) { - // debug(1,"Volume called %f.",vol); - set_volume = vol; - double vol_setting = vol2attn(vol, alsa_mix_maxdb, alsa_mix_mindb); // debug(1,"Setting volume db to %f, for volume input of %f.",vol_setting/100,vol); - if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol_setting, -1) != 0) + if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0) die("Failed to set playback dB volume"); - if (has_mute) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0)); } static void linear_volume(double vol) { - set_volume = vol; - double vol_setting = vol2attn(vol, 0, alsa_mix_mindb)/2000; - // debug(1,"Adjusted volume is %f.",vol_setting); - double linear_volume = pow(10, vol_setting); + double linear_volume = pow(10, vol); // debug(1,"Linear volume is %f.",linear_volume); long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume; // debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol); if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0) die("Failed to set playback volume"); - if (has_mute) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0)); +} + +static void mute(int do_mute) { + if (has_mute) { + if (do_mute) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); + else + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); + } } diff --git a/audio_ao.c b/audio_ao.c index a6f9cf25..6cd98eab 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -153,4 +153,5 @@ audio_output audio_ao = {.name = "ao", .delay = NULL, .play = &play, .volume = NULL, - .parameters = NULL}; + .parameters = NULL, + .mute = NULL}; diff --git a/audio_dummy.c b/audio_dummy.c index 9742c58c..a544031b 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -60,4 +60,5 @@ audio_output audio_dummy = {.name = "dummy", .delay = NULL, .play = &play, .volume = NULL, - .parameters = NULL}; + .parameters = NULL, + .mute = NULL}; diff --git a/audio_pipe.c b/audio_pipe.c index d38cd8f8..e18b4e83 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -133,4 +133,5 @@ audio_output audio_pipe = {.name = "pipe", .delay = NULL, .play = &play, .volume = NULL, - .parameters = NULL}; + .parameters = NULL, + .mute = NULL}; diff --git a/audio_pulse.c b/audio_pulse.c index 29feed88..96c547c2 100644 --- a/audio_pulse.c +++ b/audio_pulse.c @@ -137,4 +137,5 @@ audio_output audio_pulse = {.name = "pulse", .delay = NULL, .play = &play, .volume = NULL, - .parameters = NULL}; + .parameters = NULL, + .mute = NULL}; diff --git a/audio_sndio.c b/audio_sndio.c index e59dbb85..eb3be35d 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -81,4 +81,5 @@ audio_output audio_sndio = {.name = "sndio", .delay = NULL, .play = &play, .volume = &volume, - .parameters = NULL}; + .parameters = NULL, + .mute= NULL}; diff --git a/audio_stdout.c b/audio_stdout.c index 1637c825..3b908db4 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -96,4 +96,5 @@ audio_output audio_stdout = {.name = "stdout", .delay = NULL, .play = &play, .volume = NULL, - .parameters = NULL}; + .parameters = NULL, + .mute = NULL}; diff --git a/common.h b/common.h index 16c3c0e9..72e2758c 100644 --- a/common.h +++ b/common.h @@ -121,7 +121,7 @@ uint32_t uatoi(const char *nptr); shairport_cfg config; config_t config_file_stuff; -uint32_t buffer_occupancy; +int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative int64_t session_corrections; uint32_t play_segment_reference_frame; uint64_t play_segment_reference_frame_remote_time; diff --git a/player.c b/player.c index 92ebedf9..4538e4de 100644 --- a/player.c +++ b/player.c @@ -95,7 +95,6 @@ static uint64_t packet_count = 0; static int32_t last_seqno_read; // interthread variables -static double software_mixer_volume = 1.0; static int fix_volume = 0x10000; static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -452,7 +451,7 @@ static abuf_t *buffer_get_frame(void) { if (ab_synced) { do { curframe = audio_buffer + BUFIDX(ab_read); - if (curframe->ready) { + if ((ab_read!=ab_write) && (curframe->ready)) { // it could be synced and empty, under exceptional circumstances, with the frame unused, thus apparently ready if (curframe->sequence_number != ab_read) { // some kind of sync problem has occurred. @@ -838,7 +837,7 @@ static int stuff_buffer_soxr(short *inptr, short *outptr, int stuff) { } // finally, adjust the volume, if necessary - if (software_mixer_volume != 1.0) { + if (fix_volume != 65536.0) { // pthread_mutex_lock(&vol_mutex); op = outptr; for (i = 0; i < frame_size + stuff; i++) { @@ -1171,7 +1170,7 @@ static void *player_thread_func(void *arg) { inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f " "(ppm); missing packets %llu; late packets %llu; too late packets %llu; " "resend requests %llu; min DAC queue size %lli, min and max buffer occupancy " - "%u and %u.", + "%d and %d.", moving_average_sync_error, moving_average_correction * 1000000 / 352, moving_average_insertions_plus_deletions * 1000000 / 352, missing_packets, late_packets, too_late_packets, resend_requests, minimum_dac_queue_size, @@ -1179,7 +1178,7 @@ static void *player_thread_func(void *arg) { else inform("Synchronisation disabled. Missing packets %llu; late packets %llu; too late packets %llu; " "resend requests %llu; min and max buffer occupancy " - "%u and %u.", + "%d and %d.", missing_packets, late_packets, too_late_packets, resend_requests, minimum_buffer_occupancy, maximum_buffer_occupancy); @@ -1201,7 +1200,7 @@ static void *player_thread_func(void *arg) { } // takes the volume as specified by the airplay protocol -void player_volume(double f) { +void player_volume(double airplay_volume) { // The volume ranges -144.0 (mute) or -30 -- 0. See // http://git.zx2c4.com/Airtunes2/about/#setting-volume @@ -1218,50 +1217,71 @@ void player_volume(double f) { // 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. - - double linear_volume = 0.0; - - if (config.output->volume) { - // debug(1,"Set volume to %f.",f); - config.output->volume(f); // volume will be sent as metadata by the config.output device - linear_volume = 1.0; // no attenuation needed -- this value is used as a flag to avoid calculations - } - - if (config.output->parameters) + + + // get the audio parameters + + int32_t min,max; + + if (config.output->parameters) { config.output->parameters(&audio_information); - else { - long mindb = -9630; - if (config.volume_range_db) { - long suggested_alsa_min_db = -(long)trunc(config.volume_range_db*100); - if (suggested_alsa_min_db > mindb) - mindb = suggested_alsa_min_db; - else - inform("The volume_range_db setting, %f is greater than the native range of the mixer %f, so it is ignored.",config.volume_range_db,mindb/100.0); - } - double scaled_volume = vol2attn(f, 0, mindb); - linear_volume = pow(10, scaled_volume / 2000); - audio_information.airplay_volume = f; - audio_information.minimum_volume_dB = mindb; - audio_information.maximum_volume_dB = 0; - audio_information.current_volume_dB = scaled_volume; - audio_information.has_true_mute = 0; - audio_information.is_muted = 0; - // debug(1,"Minimum software volume set to %d centi-dB",f,mindb); + max = audio_information.maximum_volume_dB; + min = audio_information.minimum_volume_dB; + } else { + max = 0; + min = -9630; } - audio_information.valid = 1; - // debug(1,"Software volume set to %f on scale with a %f dB",f,linear_volume); + + double temp_fix_volume = 65536.0; + double scaled_volume; + + if (airplay_volume==-144.0) { // if we are muting + scaled_volume = min; // this is just for the metadata + if (config.output->mute) + config.output->mute(1); // use real mute if it's there + else if (config.output->volume) { + config.output_.volume(min); // otherwise set the output to the lowest value + temp_fix_volume = 0; // and set the software multiplier to 0. + } + } else { // not muting + + if (config.volume_range_db) { + int32_t possible_min = max - (int)trunc(config.volume_range_db*100); + if (possible_min > min) + min = possible_min; + } + + // now we have the true desired range, get the volume that is being set + scaled_volume = vol2attn(airplay_volume, max, min); + double software_volume = 0; dB or attenuation + + if (config.output->volume) { + if (scaled_volume>=min) { + config.output->volume(scaled_volume); + } else { + config.output->volume(min); + software_volume = scaled_volume-min; // this is the attenuation in dB needed in software + } + } else { + software_volume = scaled_volume; + } + if (config.output->mute) // if there is hardware mute... + config.output->mute(0); // turn it off + temp_fix_volume = 65536.0 * pow(10, software_volume / 2000); + } + // debug(1,"Software volume set to %f on scale with a %f dB",airplay_volume,pow(10, software_volume / 2000)); pthread_mutex_lock(&vol_mutex); - software_mixer_volume = linear_volume; - fix_volume = 65536.0 * software_mixer_volume; + fix_volume = temp_fix_volume; pthread_mutex_unlock(&vol_mutex); + #ifdef CONFIG_METADATA char *dv = malloc(128); // will be freed in the metadata thread if (dv) { memset(dv, 0, 128); - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", audio_information.airplay_volume, - audio_information.current_volume_dB / 100.0, - audio_information.minimum_volume_dB / 100.0, - audio_information.maximum_volume_dB / 100.0); + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, + scaled_volume / 100.0, + min / 100.0, + max / 100.0); send_ssnc_metadata('pvol', dv, strlen(dv), 1); } #endif