Make use of vol2attn in software and hardware mixers.

This commit is contained in:
Mike Brady
2014-05-09 17:54:30 +01:00
parent 34c52224df
commit 82cef38bb3
2 changed files with 103 additions and 153 deletions
+85 -130
View File
@@ -86,105 +86,91 @@ static void help(void) {
}
static int init(int argc, char **argv) {
debug(1,"ALSA init.\n");
int hardware_mixer = 0;
int hardware_mixer = 0;
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
argc++;
// some platforms apparently require optreset = 1; - which?
int opt;
while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) {
switch (opt) {
case 'd':
alsa_out_dev = optarg;
break;
case 't':
if (strcmp(optarg, "hardware") == 0)
hardware_mixer = 1;
break;
case 'm':
alsa_mix_dev = optarg;
break;
case 'c':
alsa_mix_ctrl = optarg;
break;
case 'i':
alsa_mix_index = strtol(optarg, NULL, 10);
break;
default:
help();
die("Invalid audio option -%c specified", opt);
}
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
argc++;
// some platforms apparently require optreset = 1; - which?
int opt;
while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) {
switch (opt) {
case 'd':
alsa_out_dev = optarg;
break;
case 't':
if (strcmp(optarg, "hardware") == 0)
hardware_mixer = 1;
break;
case 'm':
alsa_mix_dev = optarg;
break;
case 'c':
alsa_mix_ctrl = optarg;
break;
case 'i':
alsa_mix_index = strtol(optarg, NULL, 10);
break;
default:
help();
die("Invalid audio option -%c specified", opt);
}
}
if (optind < argc)
die("Invalid audio argument: %s", argv[optind]);
if (!hardware_mixer)
return 0;
if (alsa_mix_dev == NULL)
alsa_mix_dev = alsa_out_dev;
int ret = 0;
snd_mixer_selem_id_alloca(&alsa_mix_sid);
snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl);
if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0)
die ("Failed to open mixer");
if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0)
die ("Failed to attach mixer");
if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0)
die ("Failed to register mixer element");
ret = snd_mixer_load(alsa_mix_handle);
if (ret < 0)
die ("Failed to load mixer element");
alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
if (!alsa_mix_elem)
die ("Failed to find mixer element");
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;
audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
if (alsa_mix_mindb==-9999999) {
// trying to say that the lowest vol is mute, maybe? Raspberry Pi does this
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 \"volume\" of 1.\n");
}
if (optind < argc)
die("Invalid audio argument: %s", argv[optind]);
if (!hardware_mixer)
return 0;
if (alsa_mix_dev == NULL)
alsa_mix_dev = alsa_out_dev;
int ret = 0;
snd_mixer_selem_id_alloca(&alsa_mix_sid);
snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl);
if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0)
die ("Failed to open mixer");
if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0)
die ("Failed to attach mixer");
if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0)
die ("Failed to register mixer element");
ret = snd_mixer_load(alsa_mix_handle);
if (ret < 0)
die ("Failed to load mixer element");
alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
if (!alsa_mix_elem)
die ("Failed to find mixer element");
if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv)==0) {
//has_db_vol=1;
// debug(1,"Hardware mixer has dB volume from %ld to %ld.\n",alsa_mix_minv,alsa_mix_maxv);
audio_alsa.volume = &volume;
} // else {
// debug(1,"Hardware mixer does not have dB volume -- linear only??\n");
//}
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 (alsa_mix_mindb==-9999999) {
// trying to say that the lowest vol is mute, maybe? Raspberry Pi does this
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 corresponding to a volume of 1.\n");
}
debug(1,"Hardware mixer has dB volume from %ld to %ld.\n",alsa_mix_mindb,alsa_mix_maxdb);
} else {
debug(1,"Hardware mixer does not have dB volume -- linear only??\n");
}
if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) {
has_mute=1;
debug(1,"Has mute ability.\n");
}
return 0;
debug(1,"Hardware mixer has dB volume from %f to %f.\n",(1.0*alsa_mix_mindb)/100.0,(1.0*alsa_mix_maxdb)/100.0);
} else {
debug(1,"Hardware mixer does not have dB volume -- not used.\n");
}
if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) {
has_mute=1;
debug(1,"Has mute ability.\n");
}
return 0;
}
static void deinit(void) {
debug(1,"ALSA deinit.\n");
stop();
if (alsa_mix_handle) {
snd_mixer_close(alsa_mix_handle);
}
stop();
if (alsa_mix_handle) {
snd_mixer_close(alsa_mix_handle);
}
}
static void start(int sample_rate) {
@@ -287,42 +273,11 @@ static void stop(void) {
}
static void volume(double vol) {
// We use a little coordinate geometry to build a transfer function from the volume passed in to the device's dynamic range.
// The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from the bottom of the range to the top.
// We build the transfer function from one or more lines. We characterise each line with two numbers:
// the first is where on x the line starts when y=0 (from 0 to -30); the second is where on y the line stops when when x is -30.
// thus, if the line was characterised as {0,-30}, it would be an identity transfer.
// Assuming, for example, a dynamic range of lv=-70 to hv=0
// Typically we'll use three lines -- a three order transfer function
// First: {0,25} giving a gentle slope
// Second: {-17,-25-(lv+25)/2} giving a faster slope from y=0 at x=-17 to y=-50 at x=-30
// Third: {-23,lv} giving a fast slope from y=0 at x=-23 to y=-70 at x=-30
#define order 3
if ((vol<=0.0) && (vol>=-30.0)) {
long alsa_mix_rangedb = alsa_mix_maxdb-alsa_mix_mindb;
debug(1,"Volume min %ddB, max %ddB, range %ddB.\n",alsa_mix_mindb,alsa_mix_maxdb,alsa_mix_rangedb);
double first_slope = -2500.0; // this is the slope of the attenuatioon at the high end -- 25dB for the full rotation.
if (alsa_mix_rangedb<first_slope)
first_slope = alsa_mix_rangedb;
double lines[order][2] = {{0,first_slope},{-17,first_slope-(alsa_mix_rangedb+first_slope)/2},{-23,-alsa_mix_rangedb}};
double vol_setting = 0.0;
int i;
for (i=0;i<order;i++) {
if (vol<=lines[i][0]) {
double tvol = lines[i][1]*(vol-lines[i][0])/(-30-lines[i][0]);
debug(1,"On line %d, input vol %f yields output vol %f.\n",i,vol,tvol);
if (tvol<vol_setting)
vol_setting=tvol;
}
}
vol_setting+=alsa_mix_maxdb;
debug(1,"Setting volume db to %f for volume input of %f.\n",vol_setting,vol);
if(snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol_setting, -1) != 0)
die ("Failed to set playback dB volume");
}
double vol_setting = vol2attn(vol,alsa_mix_maxdb,alsa_mix_mindb);
long vs = (long) (vol_setting+0.5);
debug(1,"Setting volume db to %f, i.e. %d for volume input of %f.\n",vol_setting,vs,vol);
if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vs, -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));
}
+18 -23
View File
@@ -793,31 +793,26 @@ static void *player_thread_func(void *arg) {
// takes the volume as specified by the airplay protocol
void player_volume(double f) {
// the volume ranges -144.0 (mute) or -30 -- 0. See http://git.zx2c4.com/Airtunes2/about/#setting-volume
// see http://sound.westhost.com/project01.htm
// see also http://tangentsoft.net/audio/atten.html for data on good attenuators. Seems as if 30 dB power (60 dB amplitude) is about right
// 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.
// 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.
// I'm guessing the dB are often expressing amplitude ratios, as -57 dB on the iMic mixer sounds quite like a ratio of about 0.001 on the soft volume control
double linear_volume = pow(10.0, 0.10*f); // gives an exponentially weighted volume control from about 0.001 to 1
// 0.001 is just audible on an iMic with headphones -- to 1
// the level sorta corresponds to the same setting via the iMic's hardware mixer
// 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 out vol2attn function for an appropriate dB between -48.1 and 0 dB and translate it back to a number.
double linear_volume = pow(10,vol2attn(f,0,-4810)/1000);
if(f == -144.0)
linear_volume = 0.0;
debug(2,"iTunes volume: %f, linear volume: %f.\n",f,linear_volume);
if (config.output->volume) {
config.output->volume(f);
linear_volume=1.0; // no attenuation needed
}
pthread_mutex_lock(&vol_mutex);
volume = linear_volume;
fix_volume = 65536.0 * volume;
pthread_mutex_unlock(&vol_mutex);
if(f == -144.0)
linear_volume = 0.0;
if (config.output->volume) {
config.output->volume(f);
linear_volume=1.0; // no attenuation needed
}
pthread_mutex_lock(&vol_mutex);
volume = linear_volume;
fix_volume = 65536.0 * volume;
pthread_mutex_unlock(&vol_mutex);
}
void player_flush(uint32_t timestamp) {