diff --git a/activity_monitor.c b/activity_monitor.c index 3c525c19..c6966bcb 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 24536b91..5fc5f391 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 2c305e4f..7ee8ac5f 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 42147bab..38953b69 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 88c63c94..483dbe42 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 7b00d1d2..746d3d61 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 860edc04..e6a5b576 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 a26bc9f3..f0ced371 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 fcfcc4f6..a591add4 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 f57f9933..7983733b 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 ea48e73d..00ded166 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 452f14d2..b097ddab 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 a65727fb..a68b4c87 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 5025344f..47c5b846 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 c3ba6ad5..01163330 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 3822cfa5..199e97fd 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 204bcff5..c16a56ea 100644 --- a/man/shairport-sync.html +++ b/man/shairport-sync.html @@ -628,7 +628,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/player.c b/player.c
index 5f6f2337..021e37d2 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 74372dbb..20fc6b8b 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 8c5d0dec..3662b468 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 =