diff options
-rw-r--r-- | audio_alsa.c | 777 | ||||
-rw-r--r-- | common.c | 126 | ||||
-rw-r--r-- | common.h | 3 | ||||
-rw-r--r-- | player.c | 83 | ||||
-rw-r--r-- | player.h | 2 | ||||
-rw-r--r-- | rtsp.c | 12 |
6 files changed, 592 insertions, 411 deletions
diff --git a/audio_alsa.c b/audio_alsa.c index 21e1c5e..ed0a54a 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -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); - - // 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"); - } - - 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 = ¶meters; // 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 = ¶meters; // 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 = ¶meters; // 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; -} +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)); -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); + if ((derr = snd_pcm_close(alsa_handle))) + debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); + alsa_handle = 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 = ¶meters; // 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 = ¶meters; // 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 = ¶meters; // 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); } @@ -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; +} @@ -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 @@ -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; @@ -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); @@ -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); } |