From 83c0405d038849f0edd59709a69f4155ca13a5a4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 7 May 2019 10:12:09 +0100 Subject: [PATCH] Add automatic bit depth and speed selection for alsa devices. Set rate and format to "auto" by default. Always look for the greatest bit depth, but the lowest multiple of 44,100. Improve support for big-endian CPUs by adding support for explicit -endian formats, i.e. S16_LE, S16_BE, S24_LE, S24_BE, S32_LE, S32_BE. For the "disable_standby_mode" setting, change "while_active" to "auto". --- activity_monitor.c | 4 +- audio.h | 2 + audio_alsa.c | 315 ++++++++++++++++++++++++------------ audio_ao.c | 1 + audio_dummy.c | 1 + audio_jack.c | 1 + audio_pa.c | 1 + audio_pipe.c | 1 + audio_sndio.c | 1 + audio_soundio.c | 1 + audio_stdout.c | 1 + common.c | 7 +- common.h | 8 +- dbus-service.c | 14 +- man/shairport-sync.7 | 2 +- man/shairport-sync.7.xml | 2 +- man/shairport-sync.html | 2 +- player.c | 16 +- rtsp.c | 4 +- scripts/shairport-sync.conf | 6 +- shairport.c | 12 +- 21 files changed, 272 insertions(+), 130 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 3c525c196..c6966bcba 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -71,7 +71,7 @@ void going_active(int block) { shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); #endif - if (config.disable_standby_mode == disable_standby_while_active) { + if (config.disable_standby_mode == disable_standby_auto) { #ifdef CONFIG_DBUS_INTERFACE if (dbus_service_is_running()) shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); @@ -98,7 +98,7 @@ void going_inactive(int block) { shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); #endif - if (config.disable_standby_mode == disable_standby_while_active) { + if (config.disable_standby_mode == disable_standby_auto) { #ifdef CONFIG_DBUS_INTERFACE if (dbus_service_is_running()) shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); diff --git a/audio.h b/audio.h index 24536b916..5fc5f391f 100644 --- a/audio.h +++ b/audio.h @@ -18,6 +18,8 @@ typedef struct { int (*init)(int argc, char **argv); // at end of program void (*deinit)(void); + + int (*prepare)(void); // looks and sets stuff in the config data structure void (*start)(int sample_rate, int sample_format); diff --git a/audio_alsa.c b/audio_alsa.c index 2c305e4fe..7ee8ac5ff 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -47,6 +47,11 @@ enum alsa_backend_mode { abm_playing } alsa_backend_state; // under the control of alsa_mutex +typedef struct { + snd_pcm_format_t alsa_code; + int frame_size; +} format_record; + static void help(void); static int init(int argc, char **argv); static void deinit(void); @@ -60,6 +65,7 @@ void *alsa_buffer_monitor_thread_code(void *arg); static void volume(double vol); void do_volume(double vol); +int prepare(void); static void parameters(audio_parameters *info); int mute(int do_mute); // returns true if it actually is allowed to use the mute @@ -73,6 +79,7 @@ audio_output audio_alsa = { .help = &help, .init = &init, .deinit = &deinit, + .prepare = &prepare, .start = &start, .stop = &stop, .is_running = NULL, @@ -268,8 +275,71 @@ void actual_close_alsa_device() { } } +// This array is a sequence of the output rates to be tried if automatic speed selection is requested. +// There is no benefit to upconverting the frame rate, other than for compatibility. +// The lowest rate that the DAC is capable of is chosen. + +unsigned int auto_speed_output_rates[] = { + 44100, + 88200, + 176400, + 352800, +}; + +// This array is of all the formats known to Shairport Sync, in order of the SPS_FORMAT definitions, with their equivalent alsa codes and their frame sizes. +// If just one format is requested, then its entry is searched for in the array and checked on the device +// If auto format is requested, then each entry in turn is tried until a working format is found. +// So, it should be in the search order. + + format_record fr[] = { + {SND_PCM_FORMAT_UNKNOWN,0}, // unknown + {SND_PCM_FORMAT_S8,2}, + {SND_PCM_FORMAT_U8,2}, + {SND_PCM_FORMAT_S16,4}, + {SND_PCM_FORMAT_S16_LE,4}, + {SND_PCM_FORMAT_S16_BE,4}, + {SND_PCM_FORMAT_S24,4}, + {SND_PCM_FORMAT_S24_LE,8}, + {SND_PCM_FORMAT_S24_BE,8}, + {SND_PCM_FORMAT_S24_3LE,6}, + {SND_PCM_FORMAT_S24_3BE,6}, + {SND_PCM_FORMAT_S32,8}, + {SND_PCM_FORMAT_S32_LE,8}, + {SND_PCM_FORMAT_S32_BE,8}, + {SND_PCM_FORMAT_UNKNOWN,0}, // auto + {SND_PCM_FORMAT_UNKNOWN,0}, // illegal + }; + + // This array is the sequence of formats to be tried if automatic selection of the format is requested. + // Ideally, audio should pass through Shairport Sync unaltered, apart from occasional interpolation. + // If the user chooses a hardware mixer, then audio could go straight through, unaltered, as signed 16 bit stereo. + // However, the user might, at any point, select an option that requires modification, such as stereo to mono mixing, + // additional volume attenuation, convolution, and so on. For this reason, + // we look for the greatest depth the DAC is capable of, since upconverting it is completely lossless. + // If audio processing is required, then the dither that must be added will + // be added at the lowest possible level. + // Hence, selecting the greatest bit depth is always either beneficial or neutral. + + enum sps_format_t auto_format_check_sequence[] = { + SPS_FORMAT_S32, + SPS_FORMAT_S32_LE, + SPS_FORMAT_S32_BE, + SPS_FORMAT_S24, + SPS_FORMAT_S24_LE, + SPS_FORMAT_S24_BE, + SPS_FORMAT_S24_3LE, + SPS_FORMAT_S24_3BE, + SPS_FORMAT_S16, + SPS_FORMAT_S16_LE, + SPS_FORMAT_S16_BE, + SPS_FORMAT_S8, + SPS_FORMAT_U8, + }; + // assuming pthread cancellation is disabled -int actual_open_alsa_device(void) { +// if do_auto_setting is true and auto format or auto speed has been requested, +// select the settings as appropriate and store them +int actual_open_alsa_device(int do_auto_setup) { // the alsa mutex is already acquired when this is called const snd_pcm_uframes_t minimal_buffer_headroom = 352 * 2; // we accept this much headroom in the hardware buffer, but we'll @@ -355,82 +425,88 @@ int actual_open_alsa_device(void) { return ret; } - ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir); - if (ret < 0) { - warn("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate, - snd_strerror(ret)); - return ret; - } - snd_pcm_format_t sf; - switch (sample_format) { - case SPS_FORMAT_S8: - sf = SND_PCM_FORMAT_S8; - frame_size = 2; - break; - case SPS_FORMAT_U8: - sf = SND_PCM_FORMAT_U8; - frame_size = 2; - break; - case SPS_FORMAT_S16: - sf = SND_PCM_FORMAT_S16; - frame_size = 4; - break; - case SPS_FORMAT_S16_LE: - sf = SND_PCM_FORMAT_S16_LE; - frame_size = 4; - break; - case SPS_FORMAT_S16_BE: - sf = SND_PCM_FORMAT_S16_BE; - frame_size = 4; - break; - case SPS_FORMAT_S24: - sf = SND_PCM_FORMAT_S24; - frame_size = 8; - break; - case SPS_FORMAT_S24_LE: - sf = SND_PCM_FORMAT_S24_LE; - frame_size = 8; - break; - case SPS_FORMAT_S24_BE: - sf = SND_PCM_FORMAT_S24_BE; - frame_size = 8; - break; - case SPS_FORMAT_S24_3LE: - sf = SND_PCM_FORMAT_S24_3LE; - frame_size = 6; - break; - case SPS_FORMAT_S24_3BE: - sf = SND_PCM_FORMAT_S24_3BE; - frame_size = 6; - break; - case SPS_FORMAT_S32: - sf = SND_PCM_FORMAT_S32; - frame_size = 8; - break; - case SPS_FORMAT_S32_LE: - sf = SND_PCM_FORMAT_S32_LE; - frame_size = 8; - break; - case SPS_FORMAT_S32_BE: - sf = SND_PCM_FORMAT_S32_BE; - frame_size = 8; - break; - default: - sf = SND_PCM_FORMAT_S16_LE; // this is just to quieten a compiler warning - frame_size = 4; - debug(1, "Unsupported output format at audio_alsa.c"); - return -EINVAL; - } - - ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); - if (ret < 0) { - warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, - alsa_out_dev, snd_strerror(ret)); - return ret; + + if ((do_auto_setup == 0) || (config.output_format_auto_requested == 0)) { // no auto format + if ((sample_format > SPS_FORMAT_UNKNOWN) && (sample_format < SPS_FORMAT_AUTO)) { + sf = fr[sample_format].alsa_code; + frame_size = fr[sample_format].frame_size; + } else { + warn("alsa: unexpected output format %d",sample_format); + sf = config.output_format; + } + ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); + if (ret < 0) { + warn("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format, + alsa_out_dev, snd_strerror(ret)); + return ret; + } + } else { // auto format + int number_of_formats_to_try; + enum sps_format_t *formats; + formats = auto_format_check_sequence; + number_of_formats_to_try = sizeof(auto_format_check_sequence)/sizeof(sps_format_t); + int i = 0; + int format_found = 0; + enum sps_format_t trial_format = SPS_FORMAT_UNKNOWN; + while ((i < number_of_formats_to_try) && (format_found == 0)) { + trial_format = formats[i]; + sf = fr[trial_format].alsa_code; + frame_size = fr[trial_format].frame_size; + ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf); + if (ret == 0) + format_found = 1; + else + i++; + } + if (ret == 0) { + config.output_format = trial_format; + debug(1,"alsa: output format chosen is \"%s\".",sps_format_description_string(config.output_format)); + } else { + warn("audio_alsa: Could not automatically set the output format for device \"%s\": %s", + alsa_out_dev, snd_strerror(ret)); + return ret; + } } - + if ((do_auto_setup == 0) || (config.output_rate_auto_requested == 0)) { // no auto format + ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir); + if (ret < 0) { + warn("audio_alsa: Rate %iHz not available for playback: %s", config.output_rate, + snd_strerror(ret)); + return ret; + } + } else { + int number_of_speeds_to_try; + unsigned int *speeds; + + speeds = auto_speed_output_rates; + number_of_speeds_to_try = sizeof(auto_speed_output_rates)/sizeof(int); + + int i = 0; + int speed_found = 0; + + while ((i < number_of_speeds_to_try) && (speed_found == 0)) { + my_sample_rate = speeds[i]; + ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir); + if (ret == 0) { + speed_found = 1; + if (my_sample_rate != speeds[i]) + warn("Speed requested: %d. Speed available: %d.",speeds[i],my_sample_rate); + } else { + i++; + } + } + if (ret == 0) { + config.output_rate = my_sample_rate; + debug(1,"alsa: output speed chosen is %d.",config.output_rate); + } else { + warn("audio_alsa: Could not automatically set the output rate for device \"%s\": %s", + alsa_out_dev, snd_strerror(ret)); + return ret; + } + } + if (set_period_size_request != 0) { debug(1, "Attempting to set the period size"); ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested, @@ -576,7 +652,7 @@ int actual_open_alsa_device(void) { else if (config.use_precision_timing == YNA_AUTO) { if (precision_delay_available()) { delay_and_status = precision_delay_and_status; - debug(1,"alsa: precision timing selected for \"auto\" mode"); + debug(2,"alsa: precision timing selected for \"auto\" mode"); } } @@ -713,11 +789,11 @@ int actual_open_alsa_device(void) { return 0; } -int open_alsa_device(void) { +int open_alsa_device(int do_auto_setup) { int result; int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - result = actual_open_alsa_device(); + result = actual_open_alsa_device(do_auto_setup); pthread_setcancelstate(oldState, NULL); return result; } @@ -955,6 +1031,7 @@ static int init(int argc, char **argv) { 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)) { @@ -984,6 +1061,8 @@ static int init(int argc, char **argv) { config.output_format = SPS_FORMAT_U8; else if (strcasecmp(str, "S8") == 0) config.output_format = SPS_FORMAT_S8; + else if (strcasecmp(str, "auto") == 0) + config.output_format_auto_requested = 1; else { warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", " "\"S16\", \"S24\", \"S24_LE\", \"S24_BE\", " @@ -993,23 +1072,28 @@ static int init(int argc, char **argv) { } } - /* 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; - } + if (config_lookup_string(config.cfg, "alsa.output_rate", &str)) { + if (strcasecmp(str, "auto") == 0) { + config.output_rate_auto_requested = 1; + } else { + /* 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 \"auto\" or a multiple of 44,100 up " + "to 352,800. It is " + "set to %d.", + value,config.output_rate); + } + } + } } /* Get the use_mmap_if_available setting. */ @@ -1072,11 +1156,11 @@ static int init(int argc, char **argv) { else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) || (strcasecmp(str, "always") == 0)) { config.disable_standby_mode = disable_standby_always; config.keep_dac_busy = 1; - } else if (strcasecmp(str, "while_active") == 0) - config.disable_standby_mode = disable_standby_while_active; + } else if (strcasecmp(str, "auto") == 0) + config.disable_standby_mode = disable_standby_auto; else { warn("Invalid disable_standby_mode option choice \"%s\". It should be " - "\"always\", \"while_active\" or \"never\". " + "\"always\", \"auto\" or \"never\". " "It is set to \"never\"."); } } @@ -1097,7 +1181,7 @@ static int init(int argc, char **argv) { } } - debug(1, "alsa: disable_standby_mode is \"%s\".", config.disable_standby_mode == disable_standby_off ? "never" : config.disable_standby_mode == disable_standby_always ? "always" : "while_active"); + debug(1, "alsa: disable_standby_mode is \"%s\".", config.disable_standby_mode == disable_standby_off ? "never" : config.disable_standby_mode == disable_standby_always ? "always" : "auto"); } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour @@ -1202,8 +1286,10 @@ int set_mute_state() { return response; } -static void start(int i_sample_rate, int i_sample_format) { +static void start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) { debug(3, "audio_alsa start called."); + + /* if (i_sample_rate == 0) desired_sample_rate = 44100; // default else @@ -1213,7 +1299,8 @@ static void start(int i_sample_rate, int i_sample_format) { sample_format = SPS_FORMAT_S16; // default else sample_format = i_sample_format; - + */ + frame_index = 0; measurement_data_is_valid = 0; @@ -1294,7 +1381,7 @@ int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay, } else { // diagnostic if (delay_type_notified != 0) { - debug(1,"alsa: delay_and_status using snd_pcm_status_get_delay() to calculate delay"); + debug(2,"alsa: delay_and_status using snd_pcm_status_get_delay() to calculate delay"); delay_type_notified = 0; } } @@ -1510,14 +1597,14 @@ int do_play(void *buf, int samples) { return ret; } -int do_open() { +int do_open(int do_auto_setup) { int ret = 0; if (alsa_backend_state != abm_disconnected) debug(1, "alsa: do_open() -- opening the output device when it is already " "connected"); if (alsa_handle == NULL) { // debug(1,"alsa: do_open() -- opening the output device"); - ret = open_alsa_device(); + ret = open_alsa_device(do_auto_setup); if (ret == 0) { mute_requested_internally = 0; if (audio_alsa.volume) @@ -1574,7 +1661,7 @@ int play(void *buf, int samples) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0); if (alsa_backend_state == abm_disconnected) { - ret = do_open(); + ret = do_open(0); // don't try to auto setup if (ret == 0) debug(2, "alsa: play() -- opened output device"); } @@ -1598,6 +1685,24 @@ int play(void *buf, int samples) { return ret; } +int prepare(void) { + // this will leave the DAC open / connected. + int ret = 0; + + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0); + + if (alsa_backend_state == abm_disconnected) { + ret = do_open(1); // do auto setup + if (ret == 0) + debug(2, "alsa: prepare() -- opened output device"); + + } + + debug_mutex_unlock(&alsa_mutex, 0); + pthread_cleanup_pop(0); // release the mutex + return ret; +} + static void flush(void) { // debug(2,"audio_alsa flush called."); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); @@ -1726,7 +1831,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { // check possible state transitions here if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode - if (do_open() == 0) + if (do_open(1) == 0) // no automatic setup of rate and speed if necessary debug(2, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; " "alsa_backend_state => abm_connected"); } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { diff --git a/audio_ao.c b/audio_ao.c index 42147bab9..38953b69c 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -129,6 +129,7 @@ audio_output audio_ao = {.name = "ao", .help = &help, .init = &init, .deinit = &deinit, + .prepare = NULL, .start = &start, .stop = &stop, .is_running = NULL, diff --git a/audio_dummy.c b/audio_dummy.c index 88c63c943..483dbe426 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -59,6 +59,7 @@ audio_output audio_dummy = {.name = "dummy", .help = NULL, .init = &init, .deinit = &deinit, + .prepare = NULL, .start = &start, .stop = &stop, .is_running = NULL, diff --git a/audio_jack.c b/audio_jack.c index 7b00d1d2f..746d3d614 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -49,6 +49,7 @@ audio_output audio_jack = {.name = "jack", .help = NULL, .init = &jack_init, .deinit = &jack_deinit, + .prepare = NULL, .start = &jack_start, .stop = NULL, .is_running = NULL, diff --git a/audio_pa.c b/audio_pa.c index 860edc04d..e6a5b5767 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -303,6 +303,7 @@ audio_output audio_pa = {.name = "pa", .help = NULL, .init = &init, .deinit = &deinit, + .prepare = NULL, .start = &start, .stop = &stop, .is_running = NULL, diff --git a/audio_pipe.c b/audio_pipe.c index a26bc9f34..f0ced371a 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -140,6 +140,7 @@ audio_output audio_pipe = {.name = "pipe", .help = &help, .init = &init, .deinit = &deinit, + .prepare = NULL, .start = &start, .stop = &stop, .is_running = NULL, diff --git a/audio_sndio.c b/audio_sndio.c index fcfcc4f6d..a591add4c 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -43,6 +43,7 @@ audio_output audio_sndio = {.name = "sndio", .help = &help, .init = &init, .deinit = &deinit, + .prepare = NULL, .start = &start, .stop = &stop, .is_running = NULL, diff --git a/audio_soundio.c b/audio_soundio.c index f57f99330..7983733b3 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -216,6 +216,7 @@ audio_output audio_soundio = {.name = "soundio", .help = &help, .init = &init, .deinit = &deinit, + .prepare = NULL, .start = &start, .stop = &stop, .is_running = NULL, diff --git a/audio_stdout.c b/audio_stdout.c index ea48e73d1..00ded166e 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -78,6 +78,7 @@ audio_output audio_stdout = {.name = "stdout", .help = NULL, .init = &init, .deinit = &deinit, + .prepare = NULL, .start = &start, .stop = &stop, .is_running = NULL, diff --git a/common.c b/common.c index 452f14d2e..b097ddabe 100644 --- a/common.c +++ b/common.c @@ -87,10 +87,10 @@ void set_alsa_out_dev(char *); #endif -const char * sps_format_description_string_array[] = {"unknown", "S8", "U8" ,"S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "invalid" }; +const char * sps_format_description_string_array[] = {"unknown", "S8", "U8" ,"S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid" }; const char * sps_format_description_string(enum sps_format_t format) { - if ((format >= SPS_FORMAT_UNKNOWN) && (format < SPS_FORMAT_INVALID)) + if ((format >= SPS_FORMAT_UNKNOWN) && (format <= SPS_FORMAT_AUTO)) return sps_format_description_string_array[format]; else return sps_format_description_string_array[SPS_FORMAT_INVALID]; @@ -1413,6 +1413,9 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask."); break; + case SPS_FORMAT_AUTO: + die("Unexpected SPS_FORMAT_AUTO while calculating dither mask."); + break; case SPS_FORMAT_INVALID: die("Unexpected SPS_FORMAT_INVALID while calculating dither mask."); break; diff --git a/common.h b/common.h index a65727fb3..a68b4c87e 100644 --- a/common.h +++ b/common.h @@ -79,7 +79,7 @@ enum decoders_supported_type { enum disable_standby_mode_type { disable_standby_off = 0, - disable_standby_while_active, + disable_standby_auto, disable_standby_always }; @@ -101,6 +101,7 @@ enum sps_format_t { SPS_FORMAT_S32, SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE, + SPS_FORMAT_AUTO, SPS_FORMAT_INVALID, } sps_format_t; @@ -219,8 +220,11 @@ typedef struct { // native range. int volume_range_hw_priority; // when extending the volume range by combining sw and hw attenuators, lowering the volume, use all the hw attenuation before using // sw attenuation - enum sps_format_t output_format; enum volume_control_profile_type volume_control_profile; + + int output_format_auto_requested; // true if the configuration requests auto configuration + enum sps_format_t output_format; + int output_rate_auto_requested; // true if the configuration requests auto configuration int output_rate; #ifdef CONFIG_CONVOLUTION diff --git a/dbus-service.c b/dbus-service.c index 5025344f8..47c5b846a 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -441,8 +441,8 @@ gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, } else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || (strcasecmp(th, "always") == 0)) { config.disable_standby_mode = disable_standby_always; config.keep_dac_busy = 1; - } else if (strcasecmp(th, "while_active") == 0) - config.disable_standby_mode = disable_standby_while_active; + } else if (strcasecmp(th, "auto") == 0) + config.disable_standby_mode = disable_standby_auto; else { warn("An unrecognised disable_standby_mode: \"%s\" was requested via D-Bus interface.", th); switch (config.disable_standby_mode) { @@ -452,8 +452,8 @@ gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, case disable_standby_always: shairport_sync_set_disable_standby_mode(skeleton, "always"); break; - case disable_standby_while_active: - shairport_sync_set_disable_standby_mode(skeleton, "while_active"); + case disable_standby_auto: + shairport_sync_set_disable_standby_mode(skeleton, "auto"); break; default: break; @@ -757,9 +757,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "always"); debug(1, ">> disable standby mode set to \"always\""); break; - case disable_standby_while_active: - shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while_active"); - debug(1, ">> disable standby mode set to \"while_active\""); + case disable_standby_auto: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "auto"); + debug(1, ">> disable standby mode set to \"auto\""); break; default: debug(1,"invalid disable_standby mode!"); diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index c3ba6ad54..01163330a 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -252,7 +252,7 @@ If an output device fails to accept any audio frames for more than the time, in Implemented for the ALSA back end only. .TP \fBdisable_standby_mode=\f1\fI"never"\f1\fB;\f1 -Shairport Sync has a "Disable Standby" feature to eliminate certain faint-but-annoying audible pops and clicks. When activsted, it prevents an output device from entering standby mode and thus it minimises standby/busy transitions, which can sometimes be heard. Use this setting to control when the Disable Standby feature is active: "never" means it will never be activated, "always" means it will be active as soon as shairport-sync starts running, and "while_active" means it will be active while shairport-sync is in the "active" state. +Shairport Sync has a "Disable Standby" feature to eliminate certain faint-but-annoying audible pops and clicks. When activsted, it prevents an output device from entering standby mode and thus it minimises standby/busy transitions, which can sometimes be heard. Use this setting to control when the Disable Standby feature is active: "never" means it will never be activated, "always" means it will be active as soon as shairport-sync starts running, and "auto" means it will be active while shairport-sync is in the "active" state. Shairport Sync goes "active" when a play session starts. When the play session ends, the system will stay active until the time specified in the active_state_timeout setting elapses. If a new play session starts before that, the system will remain active. Otherwise, the system will go inactive. .TP diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 3822cfa59..199e97fd8 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -649,7 +649,7 @@ an output device from entering standby mode and thus it minimises standby/busy transitions, which can sometimes be heard. Use this setting to control when the Disable Standby feature is active: "never" means it will never be activated, "always" - means it will be active as soon as shairport-sync starts running, and "while_active" + means it will be active as soon as shairport-sync starts running, and "auto" means it will be active while shairport-sync is in the "active" state.

Shairport Sync goes "active" when a play session starts. When the play session ends, the system will stay active until the time diff --git a/man/shairport-sync.html b/man/shairport-sync.html index 204bcff5e..c16a56ea0 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -628,7 +628,7 @@

Configuration File Settings

an output device from entering standby mode and thus it minimises standby/busy transitions, which can sometimes be heard. Use this setting to control when the Disable Standby feature is active: "never" means it will never be activated, "always" - means it will be active as soon as shairport-sync starts running, and "while_active" + means it will be active as soon as shairport-sync starts running, and "auto" means it will be active while shairport-sync is in the "active" state.

Shairport Sync goes "active" when a play session starts. When the play session ends, the system will stay active until the time diff --git a/player.c b/player.c index 5f6f23373..021e37d25 100644 --- a/player.c +++ b/player.c @@ -687,6 +687,9 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask."); break; + case SPS_FORMAT_AUTO: + die("Unexpected SPS_FORMAT_AUTO while calculating dither mask."); + break; case SPS_FORMAT_INVALID: die("Unexpected SPS_FORMAT_INVALID while calculating dither mask."); break; @@ -829,6 +832,9 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t case SPS_FORMAT_UNKNOWN: die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples"); break; + case SPS_FORMAT_AUTO: + die("Unexpected SPS_FORMAT_AUTO while outputting samples"); + break; case SPS_FORMAT_INVALID: die("Unexpected SPS_FORMAT_INVALID while outputting samples"); break; @@ -1767,8 +1773,11 @@ void *player_thread_func(void *arg) { case SPS_FORMAT_UNKNOWN: die("Unknown format choosing output bit depth"); break; + case SPS_FORMAT_AUTO: + die("Invalid format -- SPS_FORMAT_AUTO -- choosing output bit depth"); + break; case SPS_FORMAT_INVALID: - die("Invalid format choosing output bit depth"); + die("Invalid format -- SPS_FORMAT_INVALID -- choosing output bit depth"); break; } @@ -1785,6 +1794,7 @@ void *player_thread_func(void *arg) { if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) || (config.playback_mode == ST_mono)) conn->enable_dither = 1; + // remember, the output device may never have been initialised prior to this call config.output->start(config.output_rate, config.output_format); // will need a corresponding stop @@ -2924,6 +2934,10 @@ int player_play(rtsp_conn_info *conn) { activity_monitor_signify_activity( 1); // active, and should be before play's command hook, command_start() command_start(); + // call on the output device to prepare itself + if ((config.output) && (config.output->prepare)) + config.output->prepare(); + pthread_t *pt = malloc(sizeof(pthread_t)); if (pt == NULL) die("Couldn't allocate space for pthread_t"); diff --git a/rtsp.c b/rtsp.c index 74372dbb9..20fc6b8b5 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1789,7 +1789,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag } if (pUncompressedCDAudio) { - debug(1, "An uncompressed PCM stream has been detected."); + debug(2, "An uncompressed PCM stream has been detected."); conn->stream.type = ast_uncompressed; conn->max_frames_per_packet = 352; // number of audio frames per packet. conn->input_rate = 44100; @@ -1865,7 +1865,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag if (pfmtp) { conn->stream.type = ast_apple_lossless; - debug(1, "An ALAC stream has been detected."); + debug(3, "An ALAC stream has been detected."); unsigned int i; for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 8c5d0decd..3662b468a 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -83,8 +83,8 @@ alsa = // output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc. // mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. // mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this. -// output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability. -// output_format = "S16_LE"; // can be "U8", "S8", "S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE" or "S32_BE" but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor. +// output_rate = "auto"; // can be "auto", 44100, 88200, 176400 or 352800, but the device must have the capability. +// output_format = "auto"; // can be "auto", ""U8", "S8", "S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE" or "S32_BE" but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor. // disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no". // period_size = ; // Use this optional advanced setting to set the alsa period size near to this value // buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value @@ -92,7 +92,7 @@ alsa = // use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players. // maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms. // use_precision_timing = "auto"; // If the output device is a real hardware device, precision timing will be used, which is needed for "disable_standby_mode" below. Choose "no" for more compatible standard timing, choose yes to force the use of precision timing, which may cause problems. -// disable_standby_mode = "never"; // Some DACs make small "popping" noises when they go in and out of standby mode. This prevents entry to standby mode. Settings can be: "always", "while_active" or "never". Default is "never", but only for backwards compatibility. You can use "yes" instead of "always" and "no instead of "never". Needs precision timing to be available. +// disable_standby_mode = "never"; // This setting prevents the DAC from entering the standby mode. Some DACs make small "popping" noises when they go in and out of standby mode. Settings can be: "always", "auto" or "never". Default is "never", but only for backwards compatibility. The "auto" setting prevents entry to standby mode while Shairport Sync is in the "active" mode. You can use "yes" instead of "always" and "no" instead of "never". Needs precision timing to be available. }; // Parameters for the "sndio" audio back end. All are optional. diff --git a/shairport.c b/shairport.c index 2d25f3521..ead5f39c4 100644 --- a/shairport.c +++ b/shairport.c @@ -226,7 +226,7 @@ void* soxr_time_check(__attribute__((unused)) void *arg) { // free(outbuffer); // free(inbuffer); config.soxr_delay_index = (int)(0.9 + soxr_execution_time_us/(number_of_iterations *1000)); - debug(1,"soxr_delay_index: %d.", config.soxr_delay_index); + debug(2,"soxr_delay_index: %d.", config.soxr_delay_index); if ((config.packet_stuffing == ST_soxr) && (config.soxr_delay_index > config.soxr_delay_threshold)) inform("Note: this device may be too slow for \"soxr\" interpolation. Consider choosing the \"basic\" or \"auto\" interpolation setting."); if (config.packet_stuffing == ST_auto) @@ -1426,7 +1426,9 @@ int main(int argc, char **argv) { config.udp_port_base = 6001; config.udp_port_range = 10; config.output_format = SPS_FORMAT_S16_LE; // default + config.output_format_auto_requested = 1; // default auto select format config.output_rate = 44100; // default + config.output_rate_auto_requested = 1; // default auto select format config.decoders_supported = 1 << decoder_hammerton; // David Hammerton's decoder supported by default #ifdef CONFIG_APPLE_ALAC @@ -1710,10 +1712,14 @@ int main(int argc, char **argv) { config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1); - debug(1, "output_rate is %d.", config.output_rate); - debug(1, + debug(1, "output_format automatic selection is %sabled.", config.output_format_auto_requested ? "en" : "dis"); + if (config.output_format_auto_requested == 0) + debug(1, "output_format is \"%s\".", sps_format_description_string(config.output_format)); + debug(1, "output_rate automatic selection is %sabled.", config.output_rate_auto_requested ? "en" : "dis"); + if (config.output_rate_auto_requested == 0) + debug(1, "output_rate is %d.", config.output_rate); debug(1, "audio backend desired buffer length is %f seconds.", config.audio_backend_buffer_desired_length); debug(1, "audio_backend_buffer_interpolation_threshold_in_seconds is %f seconds.", config.audio_backend_buffer_interpolation_threshold_in_seconds);