Merge pull request #154 from mikebrady/volume-extend

Volume extend and a small improvement in buffer overflow handling
This commit is contained in:
Mike Brady
2015-11-08 12:21:17 +00:00
11 changed files with 165 additions and 85 deletions
+1
View File
@@ -1,6 +1,7 @@
Version 2.7 -- Development Version
----
**New Features**
* Extend the volume range for some DACs. Background: some of the cheaper DACS have a very small volume range (that is, the ratio of the highest to the lowest volume, expressed in decibels). In some really cheap DACs it's only around 30 dB. That means that the difference betweeen the lowest and highest volume settings isn't large enough. With the new feature, if you set the `general` `volume_range_db` to more than the hardware mixer's range, Shairport Sync will combine the hardware mixer's range with a software attenuator to give the desired range. For example, suppose you want a volume range of 70 dB and the hardware mixer offers only 30 dB, then Shairport Sync will make up ther other 40 dB with a software attenuator. The drawbacks are that, when the volume is being changed, there may be a slight delay (0.15 seconds by default) as the audio, whose volume may have been adjusted in software, propagetes through the system. The other slight possible drawback is a slightly heavier load on the processor.
* Add extra debug messages to the alsa back end to diagnose strange DACs.
* Add configuration file for the libao back end -- to change the buffer size and the latency offset, same is for stdout.
* Add shairport-sync.exe to .gitignore.
+6 -6
View File
@@ -5,13 +5,9 @@
#include <libconfig.h>
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);
+15 -25
View File
@@ -46,7 +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 int has_mute = 0;
static void mute(int do_mute);
static double set_volume;
audio_output audio_alsa = {
@@ -59,6 +59,7 @@ audio_output audio_alsa = {
.flush = &flush,
.delay = &delay,
.play = &play,
.mute = NULL, // to be set later on...
.volume = NULL, // to be set later on...
.parameters = NULL // to be set later on...
};
@@ -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);
@@ -258,7 +252,7 @@ static int init(int argc, char **argv) {
}
}
if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) {
has_mute = 1;
audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff
debug(1, "Has mute ability.");
}
return 0;
@@ -447,35 +441,31 @@ 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 (do_mute) {
// debug(1,"Mute");
snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0);
} else {
// debug(1,"Unmute");
snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1);
}
}
+2 -1
View File
@@ -153,4 +153,5 @@ audio_output audio_ao = {.name = "ao",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+2 -1
View File
@@ -60,4 +60,5 @@ audio_output audio_dummy = {.name = "dummy",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+2 -1
View File
@@ -133,4 +133,5 @@ audio_output audio_pipe = {.name = "pipe",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+2 -1
View File
@@ -137,4 +137,5 @@ audio_output audio_pulse = {.name = "pulse",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+2 -1
View File
@@ -81,4 +81,5 @@ audio_output audio_sndio = {.name = "sndio",
.delay = NULL,
.play = &play,
.volume = &volume,
.parameters = NULL};
.parameters = NULL,
.mute= NULL};
+2 -1
View File
@@ -96,4 +96,5 @@ audio_output audio_stdout = {.name = "stdout",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+1 -1
View File
@@ -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;
+130 -47
View File
@@ -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++) {
@@ -888,7 +887,6 @@ static void *player_thread_func(void *arg) {
int32_t minimum_buffer_occupancy = BUFFER_FRAMES;
int32_t maximum_buffer_occupancy = 0;
audio_information.valid = 0;
buffer_occupancy = 0;
int play_samples;
@@ -1171,7 +1169,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 +1177,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,67 +1199,152 @@ 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
// 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.
// 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.
// 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.
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)
int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db, max_db; // hw_range_db is a flag; if 0 means no mixer
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; // this is 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);
else
desired_range_db = 0;
if (config.output->parameters) {
// have a hardware mixer
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);
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;
}
audio_information.valid = 1;
// debug(1,"Software volume set to %f on scale with a %f dB",f,linear_volume);
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 {
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;
}
}
double hardware_attenuation, software_attenuation;
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) {
// do a mute
if (config.output->mute) {
config.output->mute(1); // use real mute if it's there
software_attenuation = sw_min_db; // needed for when sound is unmuted; otherwise it might be very loud.
} else {
hardware_attenuation = hw_min_db;
software_attenuation = sw_min_db;
// debug(1,"Software mute.");
}
} else {
if (config.output->mute)
config.output->mute(0); // unmute mute if it's there
scaled_attenuation = vol2attn(airplay_volume, max_db, min_db);
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;
}
}
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);
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_attenuation / 100.0,
min_db / 100.0,
max_db / 100.0);
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
}
#endif