Fix dither, add dither to silences during lead-in, interpolatied frames and keeping out of standby, only if the output device doesn't use a mixer. Check configuration at initialisation.

This commit is contained in:
Mike Brady
2018-12-14 15:07:00 +00:00
parent 5dd9fbc4fb
commit 4046c0e73d
6 changed files with 592 additions and 411 deletions
+406 -371
View File
@@ -104,7 +104,9 @@ static char *alsa_mix_ctrl = "Master";
static int alsa_mix_index = 0;
static int hardware_mixer = 0;
static int has_softvol = 0;
int keep_dac_busy = 0; // true if the dac must be kept busy with samples
int64_t dither_random_number_store = 0;
static int volume_set_request = 0; // set when an external request is made to set the volume.
int mute_request_pending = 0; // set when an external request is made to mute or unmute.
@@ -208,359 +210,17 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v
}
}
static int init(int argc, char **argv) {
// for debugging
snd_output_stdio_attach(&output, stdout, 0);
void actual_close_alsa_device() {
if (alsa_handle) {
int derr;
if ((derr = snd_pcm_hw_free(alsa_handle)))
debug(1, "Error %d (\"%s\") freeing the output device hardware while closing it.", derr,
snd_strerror(derr));
// debug(2,"audio_alsa init called.");
int response = 0; // this will be what we return to the caller.
const char *str;
int value;
// double dvalue;
// set up default values first
set_period_size_request = 0;
set_buffer_size_request = 0;
config.alsa_use_hardware_mute = 0; // don't use it by default
config.audio_backend_latency_offset = 0;
config.audio_backend_buffer_desired_length = 0.15;
config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem
// get settings from settings file first, allow them to be overridden by
// command line options
// do the "general" audio options. Note, these options are in the "general" stanza!
parse_general_audio_options();
if (config.cfg != NULL) {
double dvalue;
/* Get the Output Device Name. */
if (config_lookup_string(config.cfg, "alsa.output_device", &str)) {
alsa_out_dev = (char *)str;
}
/* Get the Mixer Type setting. */
if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) {
inform("The alsa mixer_type setting is deprecated and has been ignored. "
"FYI, using the \"mixer_control_name\" setting automatically "
"chooses a hardware mixer.");
}
/* Get the Mixer Device Name. */
if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) {
alsa_mix_dev = (char *)str;
}
/* Get the Mixer Control Name. */
if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) {
alsa_mix_ctrl = (char *)str;
hardware_mixer = 1;
}
/* Get the disable_synchronization setting. */
if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) {
if (strcasecmp(str, "no") == 0)
config.no_sync = 0;
else if (strcasecmp(str, "yes") == 0)
config.no_sync = 1;
else {
warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or "
"\"no\". It is set to \"no\".");
config.no_sync = 0;
}
}
/* Get the mute_using_playback_switch setting. */
if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) {
inform("The alsa \"mute_using_playback_switch\" setting is deprecated. "
"Please use the \"use_hardware_mute_if_available\" setting instead.");
if (strcasecmp(str, "no") == 0)
config.alsa_use_hardware_mute = 0;
else if (strcasecmp(str, "yes") == 0)
config.alsa_use_hardware_mute = 1;
else {
warn("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or "
"\"no\". It is set to \"no\".");
config.alsa_use_hardware_mute = 0;
}
}
/* Get the use_hardware_mute_if_available setting. */
if (config_lookup_string(config.cfg, "alsa.use_hardware_mute_if_available", &str)) {
if (strcasecmp(str, "no") == 0)
config.alsa_use_hardware_mute = 0;
else if (strcasecmp(str, "yes") == 0)
config.alsa_use_hardware_mute = 1;
else {
warn("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or "
"\"no\". It is set to \"no\".");
config.alsa_use_hardware_mute = 0;
}
}
/* Get the output format, using the same names as aplay does*/
if (config_lookup_string(config.cfg, "alsa.output_format", &str)) {
if (strcasecmp(str, "S16") == 0)
config.output_format = SPS_FORMAT_S16;
else if (strcasecmp(str, "S24") == 0)
config.output_format = SPS_FORMAT_S24;
else if (strcasecmp(str, "S24_3LE") == 0)
config.output_format = SPS_FORMAT_S24_3LE;
else if (strcasecmp(str, "S24_3BE") == 0)
config.output_format = SPS_FORMAT_S24_3BE;
else if (strcasecmp(str, "S32") == 0)
config.output_format = SPS_FORMAT_S32;
else if (strcasecmp(str, "U8") == 0)
config.output_format = SPS_FORMAT_U8;
else if (strcasecmp(str, "S8") == 0)
config.output_format = SPS_FORMAT_S8;
else {
warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", "
"\"S24_3LE\", \"S24_3BE\" or "
"\"S32\". It is set to \"S16\".",
str);
config.output_format = SPS_FORMAT_S16;
}
}
/* Get the output rate, which must be a multiple of 44,100*/
if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) {
debug(1, "alsa output rate is %d frames per second", value);
switch (value) {
case 44100:
case 88200:
case 176400:
case 352800:
config.output_rate = value;
break;
default:
warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800. It is "
"set to 44,100",
value);
config.output_rate = 44100;
}
}
/* Get the use_mmap_if_available setting. */
if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) {
if (strcasecmp(str, "no") == 0)
config.no_mmap = 1;
else if (strcasecmp(str, "yes") == 0)
config.no_mmap = 0;
else {
warn("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\". "
"It is set to \"yes\".");
config.no_mmap = 0;
}
}
/* Get the optional period size value */
if (config_lookup_int(config.cfg, "alsa.period_size", &value)) {
set_period_size_request = 1;
debug(1, "Value read for period size is %d.", value);
if (value < 0) {
warn("Invalid alsa period size setting \"%d\". It "
"must be greater than 0. No setting is made.",
value);
set_period_size_request = 0;
} else {
period_size_requested = value;
}
}
/* Get the optional buffer size value */
if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) {
set_buffer_size_request = 1;
debug(1, "Value read for buffer size is %d.", value);
if (value < 0) {
warn("Invalid alsa buffer size setting \"%d\". It "
"must be greater than 0. No setting is made.",
value);
set_buffer_size_request = 0;
} else {
buffer_size_requested = value;
}
}
/* Get the optional alsa_maximum_stall_time setting. */
if (config_lookup_float(config.cfg, "alsa.maximum_stall_time", &dvalue)) {
if (dvalue < 0.0) {
warn("Invalid alsa maximum write time setting \"%f\". It "
"must be greater than 0. Default is \"%f\". No setting is made.",
dvalue, config.alsa_maximum_stall_time);
} else {
config.alsa_maximum_stall_time = dvalue;
}
}
// Get the optional "Keep DAC Busy setting"
config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &keep_dac_busy);
debug(1, "alsa: disable_standby_mode is set to \"%s\".", keep_dac_busy ? "yes" : "no");
if ((derr = snd_pcm_close(alsa_handle)))
debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr));
alsa_handle = NULL;
}
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':
inform("The alsa backend -t option is deprecated and has been ignored. "
"FYI, using the -c option automatically chooses a hardware "
"mixer.");
break;
case 'm':
alsa_mix_dev = optarg;
break;
case 'c':
alsa_mix_ctrl = optarg;
hardware_mixer = 1;
break;
case 'i':
alsa_mix_index = strtol(optarg, NULL, 10);
break;
default:
warn("Invalid audio option \"-%c\" specified -- ignored.", opt);
help();
}
}
if (optind < argc) {
warn("Invalid audio argument: \"%s\" -- ignored", argv[optind]);
}
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 = &parameters; // 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 = &parameters; // 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 = &parameters; // 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.");
}
alsa_mix_handle = NULL;
// so, now, if the option to keep the DAC running has been selected, start a thread to monitor the
// length of the queue
// if the queue gets too short, stuff it with silence
desired_sample_rate = config.output_rate;
sample_format = config.output_format;
if (keep_dac_busy)
pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL);
return response;
}
static void deinit(void) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
// debug(2,"audio_alsa deinit called.");
stop();
if (keep_dac_busy) {
debug(1, "Cancel buffer monitor thread.");
pthread_cancel(alsa_buffer_monitor_thread);
debug(1, "Join buffer monitor thread.");
pthread_join(alsa_buffer_monitor_thread, NULL);
}
pthread_setcancelstate(oldState, NULL);
}
// assuming pthread cancellation is disabled
@@ -950,6 +610,370 @@ int open_alsa_device(void) {
return result;
}
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.
const char *str;
int value;
// double dvalue;
// set up default values first
set_period_size_request = 0;
set_buffer_size_request = 0;
config.alsa_use_hardware_mute = 0; // don't use it by default
config.audio_backend_latency_offset = 0;
config.audio_backend_buffer_desired_length = 0.15;
config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem
// get settings from settings file first, allow them to be overridden by
// command line options
// do the "general" audio options. Note, these options are in the "general" stanza!
parse_general_audio_options();
if (config.cfg != NULL) {
double dvalue;
/* Get the Output Device Name. */
if (config_lookup_string(config.cfg, "alsa.output_device", &str)) {
alsa_out_dev = (char *)str;
}
/* Get the Mixer Type setting. */
if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) {
inform("The alsa mixer_type setting is deprecated and has been ignored. "
"FYI, using the \"mixer_control_name\" setting automatically "
"chooses a hardware mixer.");
}
/* Get the Mixer Device Name. */
if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) {
alsa_mix_dev = (char *)str;
}
/* Get the Mixer Control Name. */
if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) {
alsa_mix_ctrl = (char *)str;
hardware_mixer = 1;
}
/* Get the disable_synchronization setting. */
if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) {
if (strcasecmp(str, "no") == 0)
config.no_sync = 0;
else if (strcasecmp(str, "yes") == 0)
config.no_sync = 1;
else {
warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or "
"\"no\". It is set to \"no\".");
config.no_sync = 0;
}
}
/* Get the mute_using_playback_switch setting. */
if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) {
inform("The alsa \"mute_using_playback_switch\" setting is deprecated. "
"Please use the \"use_hardware_mute_if_available\" setting instead.");
if (strcasecmp(str, "no") == 0)
config.alsa_use_hardware_mute = 0;
else if (strcasecmp(str, "yes") == 0)
config.alsa_use_hardware_mute = 1;
else {
warn("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or "
"\"no\". It is set to \"no\".");
config.alsa_use_hardware_mute = 0;
}
}
/* Get the use_hardware_mute_if_available setting. */
if (config_lookup_string(config.cfg, "alsa.use_hardware_mute_if_available", &str)) {
if (strcasecmp(str, "no") == 0)
config.alsa_use_hardware_mute = 0;
else if (strcasecmp(str, "yes") == 0)
config.alsa_use_hardware_mute = 1;
else {
warn("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or "
"\"no\". It is set to \"no\".");
config.alsa_use_hardware_mute = 0;
}
}
/* Get the output format, using the same names as aplay does*/
if (config_lookup_string(config.cfg, "alsa.output_format", &str)) {
if (strcasecmp(str, "S16") == 0)
config.output_format = SPS_FORMAT_S16;
else if (strcasecmp(str, "S24") == 0)
config.output_format = SPS_FORMAT_S24;
else if (strcasecmp(str, "S24_3LE") == 0)
config.output_format = SPS_FORMAT_S24_3LE;
else if (strcasecmp(str, "S24_3BE") == 0)
config.output_format = SPS_FORMAT_S24_3BE;
else if (strcasecmp(str, "S32") == 0)
config.output_format = SPS_FORMAT_S32;
else if (strcasecmp(str, "U8") == 0)
config.output_format = SPS_FORMAT_U8;
else if (strcasecmp(str, "S8") == 0)
config.output_format = SPS_FORMAT_S8;
else {
warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", "
"\"S24_3LE\", \"S24_3BE\" or "
"\"S32\". It is set to \"S16\".",
str);
config.output_format = SPS_FORMAT_S16;
}
}
/* Get the output rate, which must be a multiple of 44,100*/
if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) {
debug(1, "alsa output rate is %d frames per second", value);
switch (value) {
case 44100:
case 88200:
case 176400:
case 352800:
config.output_rate = value;
break;
default:
warn("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800. It is "
"set to 44,100",
value);
config.output_rate = 44100;
}
}
/* Get the use_mmap_if_available setting. */
if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) {
if (strcasecmp(str, "no") == 0)
config.no_mmap = 1;
else if (strcasecmp(str, "yes") == 0)
config.no_mmap = 0;
else {
warn("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\". "
"It is set to \"yes\".");
config.no_mmap = 0;
}
}
/* Get the optional period size value */
if (config_lookup_int(config.cfg, "alsa.period_size", &value)) {
set_period_size_request = 1;
debug(1, "Value read for period size is %d.", value);
if (value < 0) {
warn("Invalid alsa period size setting \"%d\". It "
"must be greater than 0. No setting is made.",
value);
set_period_size_request = 0;
} else {
period_size_requested = value;
}
}
/* Get the optional buffer size value */
if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) {
set_buffer_size_request = 1;
debug(1, "Value read for buffer size is %d.", value);
if (value < 0) {
warn("Invalid alsa buffer size setting \"%d\". It "
"must be greater than 0. No setting is made.",
value);
set_buffer_size_request = 0;
} else {
buffer_size_requested = value;
}
}
/* Get the optional alsa_maximum_stall_time setting. */
if (config_lookup_float(config.cfg, "alsa.maximum_stall_time", &dvalue)) {
if (dvalue < 0.0) {
warn("Invalid alsa maximum write time setting \"%f\". It "
"must be greater than 0. Default is \"%f\". No setting is made.",
dvalue, config.alsa_maximum_stall_time);
} else {
config.alsa_maximum_stall_time = dvalue;
}
}
// Get the optional "Keep DAC Busy setting"
config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &keep_dac_busy);
debug(1, "alsa: disable_standby_mode is %s.", keep_dac_busy ? "on" : "off");
}
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':
inform("The alsa backend -t option is deprecated and has been ignored. "
"FYI, using the -c option automatically chooses a hardware "
"mixer.");
break;
case 'm':
alsa_mix_dev = optarg;
break;
case 'c':
alsa_mix_ctrl = optarg;
hardware_mixer = 1;
break;
case 'i':
alsa_mix_index = strtol(optarg, NULL, 10);
break;
default:
warn("Invalid audio option \"-%c\" specified -- ignored.", opt);
help();
}
}
if (optind < argc) {
warn("Invalid audio argument: \"%s\" -- ignored", argv[optind]);
}
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 = &parameters; // 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 = &parameters; // 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 = &parameters; // 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.");
}
alsa_mix_handle = NULL;
// so, now, if the option to keep the DAC running has been selected, start a thread to monitor the
// length of the queue
// if the queue gets too short, stuff it with silence
desired_sample_rate = config.output_rate;
sample_format = config.output_format;
if (response == 0) {
// try opening the device.
int ret = actual_open_alsa_device();
if (ret == 0)
actual_close_alsa_device();
else
die("Could not open the alsa device with the settings given.");
}
if (keep_dac_busy)
pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL);
return response;
}
static void deinit(void) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
// debug(2,"audio_alsa deinit called.");
stop();
if (keep_dac_busy) {
debug(1, "Cancel buffer monitor thread.");
pthread_cancel(alsa_buffer_monitor_thread);
debug(1, "Join buffer monitor thread.");
pthread_join(alsa_buffer_monitor_thread, NULL);
}
pthread_setcancelstate(oldState, NULL);
}
static void start(int i_sample_rate, int i_sample_format) {
// debug(2,"audio_alsa start called.");
if (i_sample_rate == 0)
@@ -1239,8 +1263,8 @@ static void flush(void) {
if (alsa_handle) {
stall_monitor_start_time = 0;
if (keep_dac_busy == 0) {
if ((derr = snd_pcm_drop(alsa_handle)))
debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr));
if ((derr = snd_pcm_drop(alsa_handle)))
debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr));
if ((derr = snd_pcm_hw_free(alsa_handle)))
debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr));
@@ -1346,7 +1370,7 @@ static void mute(int mute_state_requested) {
}
void do_mute(int mute_state_requested) {
debug(3,"Setting mute to %d.",mute_state_requested);
debug(3, "Setting mute to %d.", mute_state_requested);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
@@ -1419,31 +1443,42 @@ void *alsa_buffer_monitor_thread_code(void *arg) {
pthread_cleanup_push(alsa_buffer_monitor_thread_cleanup_function, arg);
pthread_setcancelstate(oldState, NULL);
// The thinking is, if the device has a hardware mixer, then
// (if no source transformation is happening), Shairport Sync
// will deliver fill-in silences and the audio material without adding dither.
// So don't insert dither into the silences sent to keep the DAC busy.
int use_dither = 0;
if (hardware_mixer == 0)
use_dither = 1;
debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not ");
int sleep_time_ms = 80;
int frames_of_silence = desired_sample_rate * sleep_time_ms / 1000;
size_t size_of_silence_buffer = frames_of_silence * frame_size;
// debug(1,"Silence buffer length: %u bytes.",size_of_silence_buffer);
void *silence = malloc(size_of_silence_buffer);
if (silence == NULL) {
debug(1, "Failed to allocate memory for a silent frame buffer.");
debug(1, "alsa: failed to allocate memory for a silent frame buffer, thus "
"disable_standby_mode is \"off\".");
} else {
}
long buffer_size;
int reply;
while (1) {
usleep((sleep_time_ms * 1000) / 2);
reply = delay(&buffer_size);
if (reply != 0)
debug(1, "error %d during buffer monitoring of delay", reply);
if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) {
// debug(1,"Writing %d frames of silence because only %lu frames remain in the
// buffer.",frames_of_silence,buffer_size);
memset(silence, 0, size_of_silence_buffer);
play(silence, frames_of_silence);
long buffer_size;
int reply;
while (1) {
if (buffer_size < (((int)desired_sample_rate * sleep_time_ms) / (1000))) {
dither_random_number_store = generate_zero_frames(
silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither
dither_random_number_store);
play(silence, frames_of_silence);
}
usleep((sleep_time_ms * 1000) / 2);
reply = delay(&buffer_size);
if (reply != 0)
debug(1, "error %d during buffer monitoring of delay", reply);
pthread_testcancel();
}
pthread_testcancel();
}
pthread_cleanup_pop(0);
pthread_cleanup_pop(1);
pthread_exit(NULL);
}
+123 -3
View File
@@ -1058,7 +1058,7 @@ uint64_t r64u() { return (ranval(&rx)); }
int64_t r64i() { return (ranval(&rx) >> 1); }
/* generate an array of 64-bit random numbers */
const int ranarraylength = 1009; // these will be 8-byte numbers.
const int ranarraylength = 1009 * 203; // these will be 8-byte numbers.
int ranarraynext;
@@ -1083,9 +1083,11 @@ uint64_t ranarrayval() {
void r64arrayinit() { ranarrayinit(); }
uint64_t ranarray64u() { return (ranarrayval()); }
// uint64_t ranarray64u() { return (ranarrayval()); }
uint64_t ranarray64u() { return (ranval(&rx)); }
int64_t ranarray64i() { return (ranarrayval() >> 1); }
// int64_t ranarray64i() { return (ranarrayval() >> 1); }
int64_t ranarray64i() { return (ranval(&rx) >> 1); }
uint32_t nctohl(const uint8_t *p) { // read 4 characters from *p and do ntohl on them
// this is to avoid possible aliasing violations
@@ -1325,3 +1327,121 @@ char *get_version_string() {
}
return version_string;
}
int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format,
int with_dither, int64_t random_number_in) {
// return the last random number used
// assuming the buffer has been assigned
int64_t previous_random_number = random_number_in;
char *p = outp;
size_t sample_number;
for (sample_number = 0; sample_number < number_of_frames * 2; sample_number++) {
int64_t hyper_sample = 0;
// add a TPDF dither -- see
// http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf
// and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25
// I think, for a 32 --> 16 bits, the range of
// random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from
// -32768 to +32767
// See the original paper at
// http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf
// by Lipshitz, Wannamaker and Vanderkooy, 1992.
int64_t dither_mask = 0;
switch (format) {
case SPS_FORMAT_S32:
dither_mask = (int64_t)1 << (64 + 1 - 32);
break;
case SPS_FORMAT_S24:
case SPS_FORMAT_S24_3LE:
case SPS_FORMAT_S24_3BE:
dither_mask = (int64_t)1 << (64 + 1 - 24);
break;
case SPS_FORMAT_S16:
dither_mask = (int64_t)1 << (64 + 1 - 16);
break;
case SPS_FORMAT_S8:
case SPS_FORMAT_U8:
dither_mask = (int64_t)1 << (64 + 1 - 8);
break;
case SPS_FORMAT_UNKNOWN:
die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask.");
}
dither_mask -= 1;
// int64_t r = r64i();
int64_t r = ranarray64i();
int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask);
// add dither if permitted -- no need to check for clipping, as the sample is, uh, zero
if (with_dither != 0)
hyper_sample += tpdf;
// move the result to the desired position in the int64_t
char *op = p;
int result; // this is the length of the sample
uint8_t byt;
switch (format) {
case SPS_FORMAT_S32:
hyper_sample >>= (64 - 32);
*(int32_t *)op = hyper_sample;
result = 4;
break;
case SPS_FORMAT_S24_3LE:
hyper_sample >>= (64 - 24);
byt = (uint8_t)hyper_sample;
*op++ = byt;
byt = (uint8_t)(hyper_sample >> 8);
*op++ = byt;
byt = (uint8_t)(hyper_sample >> 16);
*op++ = byt;
result = 3;
break;
case SPS_FORMAT_S24_3BE:
hyper_sample >>= (64 - 24);
byt = (uint8_t)(hyper_sample >> 16);
*op++ = byt;
byt = (uint8_t)(hyper_sample >> 8);
*op++ = byt;
byt = (uint8_t)hyper_sample;
*op++ = byt;
result = 3;
break;
case SPS_FORMAT_S24:
hyper_sample >>= (64 - 24);
*(int32_t *)op = hyper_sample;
result = 4;
break;
case SPS_FORMAT_S16:
hyper_sample >>= (64 - 16);
*(int16_t *)op = (int16_t)hyper_sample;
result = 2;
break;
case SPS_FORMAT_S8:
hyper_sample >>= (int8_t)(64 - 8);
*op = hyper_sample;
result = 1;
break;
case SPS_FORMAT_U8:
hyper_sample >>= (uint8_t)(64 - 8);
hyper_sample += 128;
*op = hyper_sample;
result = 1;
break;
default:
result = 0; // stop a compiler warning
die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples");
}
p += result;
previous_random_number = r;
}
// hack
// memset(outp,0,number_of_frames * 4);
return previous_random_number;
}
+2 -1
View File
@@ -337,6 +337,7 @@ char *get_version_string(); // mallocs a string space -- remember to free it aft
void sps_nanosleep(const time_t sec,
const long nanosec); // waits for this time, even through interruptions
int usleep_uncancellable(useconds_t usec);
int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format,
int with_dither, int64_t random_number_in);
#endif // _COMMON_H
+53 -30
View File
@@ -1032,7 +1032,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// this could happen if the dac delay mysteriously grows between samples,
// which could happen in a transition between having no interpolation and
// having interpolated buffer numbers.
debug(2,
// this will happen benignly if standby is being prevented, because a
// thread in the alsa back end will be stuffing frames of silence in there
// to keep it busy
debug(3,
"frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",
max_dac_delay, dac_delay);
fs = 0;
@@ -1062,7 +1067,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (silence == NULL)
debug(1, "Failed to allocate %d byte silence buffer.", fs);
else {
memset(silence, 0, conn->output_bytes_per_frame * fs);
conn->previous_random_number = generate_zero_frames(
silence, fs, config.output_format, conn->enable_dither,
conn->previous_random_number);
// debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d
// packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read,
// conn->ab_write, conn->ab_read));
@@ -1106,7 +1115,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
debug(1, "Failed to allocate %d frame silence buffer.", fs);
else {
// debug(1, "No delay function -- outputting %d frames of silence.", fs);
memset(silence, 0, conn->output_bytes_per_frame * fs);
conn->previous_random_number =
generate_zero_frames(silence, fs, config.output_format,
conn->enable_dither, conn->previous_random_number);
config.output->play(silence, fs);
free(silence);
}
@@ -1427,22 +1438,22 @@ void player_thread_cleanup_handler(void *arg) {
mdns_dacp_monitor_set_id(NULL); // say we're not interested in following that DACP id any more
#endif
debug(2, "Cancelling timing, control and audio threads...");
debug(2, "Cancel timing thread.");
debug(3, "Cancelling timing, control and audio threads...");
debug(3, "Cancel timing thread.");
pthread_cancel(conn->rtp_timing_thread);
debug(2, "Join timing thread.");
debug(3, "Join timing thread.");
pthread_join(conn->rtp_timing_thread, NULL);
debug(2, "Timing thread terminated.");
debug(2, "Cancel control thread.");
debug(3, "Timing thread terminated.");
debug(3, "Cancel control thread.");
pthread_cancel(conn->rtp_control_thread);
debug(2, "Join control thread.");
debug(3, "Join control thread.");
pthread_join(conn->rtp_control_thread, NULL);
debug(2, "Control thread terminated.");
debug(2, "Cancel audio thread.");
debug(3, "Control thread terminated.");
debug(3, "Cancel audio thread.");
pthread_cancel(conn->rtp_audio_thread);
debug(2, "Join audio thread.");
debug(3, "Join audio thread.");
pthread_join(conn->rtp_audio_thread, NULL);
debug(2, "Audio thread terminated.");
debug(3, "Audio thread terminated.");
if (conn->outbuf) {
free(conn->outbuf);
@@ -1486,8 +1497,6 @@ void *player_thread_func(void *arg) {
conn->latency = 88200;
}
config.output->start(config.output_rate, config.output_format); // will need a corresponding stop
init_decoder((int32_t *)&conn->stream.fmtp,
conn); // this sets up incoming rate, bit depth, channels.
// No pthread cancellation point in here
@@ -1625,10 +1634,16 @@ void *player_thread_func(void *arg) {
debug(3, "Dithering will be enabled because the input bit depth is greater than the output bit "
"depth");
}
if (conn->fix_volume != 0x10000) {
if (config.output->parameters == NULL) {
debug(3, "Dithering will be enabled because the output volume is being altered in software");
}
if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) ||
(config.playback_mode == ST_mono))
conn->enable_dither = 1;
config.output->start(config.output_rate, config.output_format); // will need a corresponding stop
// we need an intermediate "transition" buffer
// if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) {
@@ -1766,8 +1781,9 @@ void *player_thread_func(void *arg) {
} else {
// the player may change the contents of the buffer, so it has to be zeroed each time;
// might as well malloc and freee it locally
memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet *
conn->output_sample_ratio);
conn->previous_random_number = generate_zero_frames(
silence, conn->max_frames_per_packet * conn->output_sample_ratio,
config.output_format, conn->enable_dither, conn->previous_random_number);
config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
free(silence);
}
@@ -1787,16 +1803,19 @@ void *player_thread_func(void *arg) {
} else {
// the player may change the contents of the buffer, so it has to be zeroed each time;
// might as well malloc and freee it locally
memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet *
conn->output_sample_ratio);
conn->previous_random_number = generate_zero_frames(
silence, conn->max_frames_per_packet * conn->output_sample_ratio,
config.output_format, conn->enable_dither, conn->previous_random_number);
config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
free(silence);
}
} else {
int enable_dither = 0;
if ((conn->fix_volume != 0x10000) || (conn->input_bit_depth > output_bit_depth) ||
if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) ||
(config.playback_mode == ST_mono))
enable_dither = 1;
conn->enable_dither = 1;
else
conn->enable_dither = 0;
// here, let's transform the frame of data, if necessary
@@ -2069,7 +2088,11 @@ void *player_thread_func(void *arg) {
size_t silence_length_sized = silence_length;
char *long_silence = malloc(conn->output_bytes_per_frame * silence_length_sized);
if (long_silence) {
memset(long_silence, 0, conn->output_bytes_per_frame * silence_length_sized);
conn->previous_random_number =
generate_zero_frames(long_silence, silence_length_sized, config.output_format,
conn->enable_dither, conn->previous_random_number);
debug(2, "Play a silence of %d frames.", silence_length_sized);
config.output->play(long_silence, silence_length_sized);
free(long_silence);
@@ -2193,14 +2216,14 @@ void *player_thread_func(void *arg) {
(config.packet_stuffing == ST_basic)) {
play_samples =
stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format,
conn->outbuf, amount_to_stuff, enable_dither, conn);
conn->outbuf, amount_to_stuff, conn->enable_dither, conn);
}
#ifdef CONFIG_SOXR
else if (config.packet_stuffing == ST_soxr) {
// if (amount_to_stuff) debug(1,"Soxr stuff...");
play_samples = stuff_buffer_soxr_32((int32_t *)conn->tbuf, (int32_t *)conn->sbuf,
inbuflength, config.output_format, conn->outbuf,
amount_to_stuff, enable_dither, conn);
amount_to_stuff, conn->enable_dither, conn);
}
#endif
@@ -2256,7 +2279,7 @@ void *player_thread_func(void *arg) {
// synchronising
play_samples =
stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format,
conn->outbuf, 0, enable_dither, conn);
conn->outbuf, 0, conn->enable_dither, conn);
if (conn->outbuf == NULL)
debug(1, "NULL outbuf to play -- skipping it.");
else
@@ -2780,16 +2803,16 @@ int player_stop(rtsp_conn_info *conn) {
// debuglev = 3;
debug(3, "player_stop");
if (conn->player_thread) {
debug(2, "player_thread cancel...");
debug(3, "player_thread cancel...");
pthread_cancel(*conn->player_thread);
debug(2, "player_thread join...");
debug(3, "player_thread join...");
if (pthread_join(*conn->player_thread, NULL) == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "Connection %d: error %d joining player thread: \"%s\".", conn->connection_number,
errno, (char *)errorstring);
} else {
debug(2, "player_thread joined.");
debug(3, "player_thread joined.");
}
free(conn->player_thread);
conn->player_thread = NULL;
+2
View File
@@ -246,6 +246,8 @@ typedef struct {
// zero
uint32_t dacp_active_remote; // key to send to the remote controller
void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled.
int enable_dither; // needed for filling silences before play actually starts
} rtsp_conn_info;
uint32_t modulo_32_offset(uint32_t from, uint32_t to);
+6 -6
View File
@@ -265,7 +265,7 @@ int have_player(rtsp_conn_info *conn) {
void player_watchdog_thread_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
debug(2, "Connection %d: Watchdog Exit.", conn->connection_number);
debug(3, "Connection %d: Watchdog Exit.", conn->connection_number);
}
void *player_watchdog_thread_code(void *arg) {
@@ -2127,22 +2127,22 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
if (rc)
debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc);
debug(2, "Cancel watchdog thread.");
debug(3, "Cancel watchdog thread.");
pthread_cancel(conn->player_watchdog_thread);
debug(2, "Join watchdog thread.");
debug(3, "Join watchdog thread.");
pthread_join(conn->player_watchdog_thread, NULL);
debug(2, "Delete watchdog mutex.");
debug(3, "Delete watchdog mutex.");
pthread_mutex_destroy(&conn->watchdog_mutex);
debug(3, "Connection %d: Checking play lock.", conn->connection_number);
debug_mutex_lock(&playing_conn_lock, 1000000, 3); // get it
if (playing_conn == conn) { // if it's ours
debug(1, "Connection %d: Unlocking play lock -- end of play session.", conn->connection_number);
debug(3, "Connection %d: Unlocking play lock.", conn->connection_number);
playing_conn = NULL; // let it go
}
debug_mutex_unlock(&playing_conn_lock, 3);
debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number);
debug(2, "Connection %d: terminated.", conn->connection_number);
conn->running = 0;
pthread_setcancelstate(oldState, NULL);
}