From ba02dee44322e9600c8024ddac8046811c83b90c Mon Sep 17 00:00:00 2001 From: Mke Brady Date: Sat, 3 Oct 2015 12:49:56 +0100 Subject: [PATCH] playing with using just a linear mixer --- audio_alsa.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++----- player.c | 33 ++++++++++++++------------- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index d4e54973..309a8314 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include "common.h" @@ -43,9 +44,9 @@ static void stop(void); static void flush(void); static uint32_t delay(void); static void volume(double vol); +static void linear_volume(double vol); static void parameters(audio_parameters *info); static int has_mute = 0; -static int has_db_vol = 0; static double set_volume; audio_output audio_alsa = { @@ -209,12 +210,37 @@ static int init(int argc, char **argv) { alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); if (!alsa_mix_elem) die("Failed to find mixer element"); + // Check that the output device has two playback channels + snd_mixer_selem_channel_id_t chn; + + + if (snd_mixer_selem_has_playback_volume(alsa_mix_elem) || + snd_mixer_selem_has_playback_switch(alsa_mix_elem)) { + if (snd_mixer_selem_is_playback_mono(alsa_mix_elem)) { + debug(1,"Mono Capable Output Device Only"); + } else { + for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++){ + if (snd_mixer_selem_has_playback_channel(alsa_mix_elem, chn)) { + debug(1,"Playback channel \"%s\" discovered", snd_mixer_selem_channel_name(chn)); + // see if it's got a dB range + int64_t cmin,cmax; + if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem,&cmin,&cmax)==0) + debug(1,"Range in dB is from %ld to %ld",cmin,cmax); + else + debug(1,"Can't get dB volume range"); + } + } + } + } + + + 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_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv, &alsa_mix_mindb) == 0) && - (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_maxv, &alsa_mix_maxdb) == 0)) { - has_db_vol = 1; + if (snd_mixer_selem_get_playback_dB_range (alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) { +// if ((snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv, &alsa_mix_mindb) == 0) && +// (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_maxv, &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 == -9999999) { @@ -226,8 +252,17 @@ 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); } else { + // use the linear scale and do the db conversion ourselves inform("note: the hardware mixer specified -- \"%s\" -- does not have dB volume, so it can not be used for volume control.",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 + debug(1,"Max and min dB calculated are %d and %d.",alsa_mix_maxdb,alsa_mix_mindb); + } } if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) { has_mute = 1; @@ -320,7 +355,7 @@ static void play(short buf[], int samples) { if (alsa_handle == NULL) { ret = open_alsa_device(); if ((ret == 0) && (audio_alsa.volume)) - volume(set_volume); + audio_alsa.volume(set_volume); } if (ret == 0) { pthread_mutex_lock(&alsa_mutex); @@ -390,6 +425,7 @@ static void parameters(audio_parameters *info) { } static void volume(double vol) { + debug(1,"Setting volume in dB."); 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); @@ -398,3 +434,18 @@ static void volume(double vol) { if (has_mute) snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0)); } + +static void linear_volume(double vol) { + debug(1,"Setting linear volume for %f.",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); + 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)); +} diff --git a/player.c b/player.c index 80fcae22..b7224e5e 100644 --- a/player.c +++ b/player.c @@ -1196,32 +1196,35 @@ void player_volume(double f) { // 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 - // So, we will pass this on without any weighting if we have a hardware mixer, as we expect the - // mixer to be calibrated in dB. - - // Here, we ask for an attenuation we will apply in software. The dB range of a value from 1 to - // 65536 is about 48.1 dB (log10 of 65536 is 4.8164). - // Thus, we ask our vol2attn function for an appropriate dB between -48.1 and 0 dB and translate + // equal increments. Since the human ear's response is roughly logarithmic, we imagine these to + // be power dB, i.e. from -30dB to 0dB. + + // So, if we have a hardware mixer, we will pass this on to its dB volume settings. + + // Without a hardware mixer, we have to do attenuation in software, so + // here, we ask for an attenuation we will apply to the signal amplitude in software. + // 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. - double scaled_volume = vol2attn(f, 0, -4810); - double linear_volume = pow(10, scaled_volume / 1000); + double scaled_volume = vol2attn(f, 0, -9630); + double linear_volume = pow(10, scaled_volume / 20*100); if (f == -144.0) linear_volume = 0.0; if (config.output->volume) { 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 + linear_volume = 1.0; // no attenuation needed -- this value is used as a flag to avoid calculations } if (config.output->parameters) config.output->parameters(&audio_information); else { audio_information.airplay_volume = f; - audio_information.minimum_volume_dB = -4810; + audio_information.minimum_volume_dB = -9630; audio_information.maximum_volume_dB = 0; audio_information.current_volume_dB = scaled_volume; audio_information.has_true_mute = 0; @@ -1233,10 +1236,10 @@ void player_volume(double f) { fix_volume = 65536.0 * software_mixer_volume; pthread_mutex_unlock(&vol_mutex); #ifdef CONFIG_METADATA - char *dv = malloc(64); // will be freed in the metadata thread + char *dv = malloc(128); // will be freed in the metadata thread if (dv) { - memset(dv, 0, 64); - snprintf(dv, 63, "%.2f,%.2f,%.2f,%.2f", audio_information.airplay_volume, + 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);