From fe857037decd393acc1070b2421446e497e06adf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 31 Dec 2018 17:46:05 +0000 Subject: [PATCH 001/115] If output is stalled, don't write any more frames to the output device. --- audio_alsa.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index f32df151..0ae4ab25 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -81,6 +81,7 @@ pthread_t alsa_buffer_monitor_thread; uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's an error +int stalled; static snd_output_t *output = NULL; static unsigned int desired_sample_rate; @@ -993,6 +994,7 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; + stalled = 0; stall_monitor_start_time = 0; stall_monitor_frame_count = 0; @@ -1110,15 +1112,18 @@ int delay(long *the_delay) { // hasn't outputted anything since the last call to delay() uint64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; if (time_stalled > stall_monitor_error_threshold) { + stalled = 1; reply = sps_extra_errno_output_stalled; } } else { // is outputting stuff, so restart the monitoring here + stalled = 0; stall_monitor_start_time = stall_monitor_time_now; stall_monitor_frame_count = *the_delay; } } else { // if there is an error or the delay is zero (from which it is assumed there is no output) + stalled = 0; stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes } @@ -1180,7 +1185,7 @@ int untimed_play(void *buf, int samples) { debug(1, "NULL buffer passed to pcm_writei -- skipping it"); if (samples == 0) debug(1, "empty buffer being passed to pcm_writei -- skipping it"); - if ((samples != 0) && (buf != NULL)) { + if ((samples != 0) && (buf != NULL) && (stalled == 0)) { // debug(3, "write %d frames.", samples); err = alsa_pcm_write(alsa_handle, buf, samples); @@ -1499,8 +1504,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { buffer_size = 0; char errorstring[1024]; strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, - (char *)errorstring); + //debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + // (char *)errorstring); } if (buffer_size < frames_of_silence) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && From b4505593b27d77a38bbef58a5ade9d869e7c3c3c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 31 Dec 2018 17:46:31 +0000 Subject: [PATCH 002/115] If output is stalled, don't write any more frames to the output device. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 578dd4b5..3ef39546 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d41], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d42], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 6f8b35d121907e0a04c0b20bf552b3a5cdb8ab5c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 14:43:25 +0000 Subject: [PATCH 003/115] Try out a new alsa delay routine. Could be very buggy! --- audio_alsa.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++--- common.h | 3 +- player.c | 19 ++++---- 3 files changed, 133 insertions(+), 14 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0ae4ab25..3cf00d6d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -44,6 +44,7 @@ static void start(int i_sample_rate, int i_sample_format); static int play(void *buf, int samples); static void stop(void); static void flush(void); +// int delay(long *the_delay); int delay(long *the_delay); void do_mute(int request); int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played); @@ -468,9 +469,8 @@ int actual_open_alsa_device(void) { buffer_size); } */ - debug(1, - "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " - "length (%ld) you have chosen.", + debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " + "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } @@ -601,6 +601,14 @@ int actual_open_alsa_device(void) { break; } } + + // now open the device + ret = snd_pcm_prepare(alsa_handle); + if (ret) { + warn("alsa: can't prepare device."); + return ret; + } + return 0; } @@ -1042,6 +1050,113 @@ int my_snd_pcm_state_and_delay(snd_pcm_t *pcm, snd_pcm_state_t *state, snd_pcm_s } int delay(long *the_delay) { + // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED + // and returns the actual delay if running or 0 if prepared in *the_delay + + // otherwise return an error code + // the error code could be a Unix errno code or a snderror code, or + // the sps_extra_code_output_stalled or the sps_extra_code_output_state_cannot_make_ready codes + int ret = 0; + *the_delay = 0; + if (alsa_handle == NULL) + ret = ENODEV; + else { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); + + snd_pcm_state_t state; + snd_pcm_sframes_t delay; + + snd_pcm_status_t *alsa_snd_pcm_status; + snd_pcm_status_alloca(&alsa_snd_pcm_status); + + struct timespec tn; // time now + snd_htimestamp_t update_timestamp; // actually a struct timespec + + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) { + state = snd_pcm_status_get_state(alsa_snd_pcm_status); + + if ((state == SND_PCM_STATE_SUSPENDED) || (state == SND_PCM_STATE_XRUN)) { + debug(1, "alsa: recovering from suspended or xrun in delay."); + ret = snd_pcm_recover(alsa_handle, ret, 1); + if (ret == 0) { + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) + state = snd_pcm_status_get_state(alsa_snd_pcm_status); + } + } + + // here, the device must be either SND_PCM_STATE_RUNNING + // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING + + if (ret == 0) { + + if ((state == SND_PCM_STATE_RUNNING) || (state == SND_PCM_STATE_DRAINING)) { + + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + + if (state == SND_PCM_STATE_DRAINING) + debug(1, "alsa: draining with a delay of %d.", delay); + + clock_gettime(CLOCK_MONOTONIC, &tn); + uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t update_timestamp_ns = + update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + + // see if it's stalled + + if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == delay)) { + // hasn't outputted anything since the last call to delay() + + if (((update_timestamp_ns - stall_monitor_start_time) > + stall_monitor_error_threshold) || + ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + stalled = 1; + ret = sps_extra_code_output_stalled; + } + } else { + stalled = 0; + stall_monitor_start_time = update_timestamp_ns; + stall_monitor_frame_count = delay; + } + + if (ret == 0) { + uint64_t delta = time_now_ns - update_timestamp_ns; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = + frames_played_since_last_interrupt; + + *the_delay = delay - frames_played_since_last_interrupt_sized; + } + } else { // not running, thus no delay information, thus can't check for stall + stalled = 0; + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes + + // not running, thus no delay information, thus can't check for frame rates + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; + + if ((state != SND_PCM_STATE_PREPARED)) { + debug(1, "alsa: can't get device into valid state in delay. State is %d.",state); + ret = sps_extra_code_output_state_cannot_make_ready; + } + } + } + } + debug_mutex_unlock(&alsa_mutex, 0); + pthread_cleanup_pop(0); + pthread_setcancelstate(oldState, NULL); + } + return ret; +} + +int old_delay(long *the_delay) { *the_delay = 0; // snd_pcm_sframes_t is a signed long -- hence the return of a "long" int reply = 0; @@ -1113,7 +1228,7 @@ int delay(long *the_delay) { uint64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; if (time_stalled > stall_monitor_error_threshold) { stalled = 1; - reply = sps_extra_errno_output_stalled; + reply = sps_extra_code_output_stalled; } } else { // is outputting stuff, so restart the monitoring here @@ -1504,7 +1619,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { buffer_size = 0; char errorstring[1024]; strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - //debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + // debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, // (char *)errorstring); } if (buffer_size < frames_of_silence) { diff --git a/common.h b/common.h index 8d028b3b..c42d76a3 100644 --- a/common.h +++ b/common.h @@ -30,7 +30,8 @@ enum dbus_session_type { } dbt_type; #endif -#define sps_extra_errno_output_stalled 32768 +#define sps_extra_code_output_stalled 32768 +#define sps_extra_code_output_state_cannot_make_ready 32769 enum endian_type { SS_LITTLE_ENDIAN = 0, diff --git a/player.c b/player.c index 1ce5494a..b14b9daf 100644 --- a/player.c +++ b/player.c @@ -473,7 +473,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in abuf_t *abuf = 0; if (!conn->ab_synced) { - // if this is the first packetÉ + // if this is the first packet... debug(3, "syncing to seqno %u.", seqno); conn->ab_write = seqno; conn->ab_read = seqno; @@ -495,7 +495,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in } if (conn->ab_write == - seqno) { // if this is the expected packet (which could be the first packetÉ) + seqno) { // if this is the expected packet (which could be the first packet...) uint64_t reception_time = get_absolute_time_in_fp(); if (conn->input_frame_rate_starting_point_is_valid == 0) { if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) { @@ -1017,7 +1017,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // this might happen if a big clock adjustment was made at just the wrong // time. - debug(1, "Run a bit past the exact start time by %" PRId64 " frames with a DAC delay of %ld frames.", + debug(1, "Run a bit past the exact start time by %" PRId64 + " frames with a DAC delay of %ld frames.", -exact_frame_gap, dac_delay); if (config.output->flush) config.output->flush(); @@ -1084,7 +1085,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { 1; // even if we haven't sent silence because it's zero frames long... } } else { - if ((resp == sps_extra_errno_output_stalled) && + if ((resp == sps_extra_code_output_stalled) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; if (config.cmd_unfixable) { @@ -1973,8 +1974,8 @@ void *player_thread_func(void *arg) { if (config.output->delay) { long l_delay; resp = config.output->delay(&l_delay); - current_delay = l_delay; if (resp == 0) { // no error + current_delay = l_delay; if (current_delay < 0) { debug(2, "Underrun of %lld frames reported, but ignored.", current_delay); current_delay = @@ -1984,7 +1985,8 @@ void *player_thread_func(void *arg) { minimum_dac_queue_size = current_delay; // update for display later } } else { - if ((resp == sps_extra_errno_output_stalled) && + current_delay = 0; + if ((resp == sps_extra_code_output_stalled) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; if (config.cmd_unfixable) { @@ -2005,7 +2007,7 @@ void *player_thread_func(void *arg) { } } - if (resp >= 0) { + if (resp == 0) { uint32_t should_be_frame_32; local_time_to_frame(local_time_now, &should_be_frame_32, conn); @@ -2828,4 +2830,5 @@ int player_stop(rtsp_conn_info *conn) { // debuglev = dl; return -1; } -} \ No newline at end of file +} + From 59d26918f620f2c9ed9791f86238ad2285260c91 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 19:49:15 +0000 Subject: [PATCH 004/115] new play(), delay(), delay_prep_and_status() routines in place and working, but with the old stuff still around. --- audio_alsa.c | 299 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 215 insertions(+), 84 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 3cf00d6d..79276aba 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -254,6 +254,7 @@ int actual_open_alsa_device(void) { if (config.no_sync != 0) audio_alsa.delay = NULL; + // ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) return ret; @@ -1049,8 +1050,130 @@ int my_snd_pcm_state_and_delay(snd_pcm_t *pcm, snd_pcm_state_t *state, snd_pcm_s return 0; } +// assuming pthread cancellation is disabled +int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { + int ret; + snd_pcm_status_t *alsa_snd_pcm_status; + snd_pcm_status_alloca(&alsa_snd_pcm_status); + + struct timespec tn; // time now + snd_htimestamp_t update_timestamp; // actually a struct timespec + + ret = snd_pcm_status(pcm, alsa_snd_pcm_status); + if (ret) { + *delayp = 0; + return ret; + } + + snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); + if (state != SND_PCM_STATE_RUNNING) { + *delayp = 0; + return -EIO; // might be a better code than this... + } + + clock_gettime(CLOCK_MONOTONIC, &tn); + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + + uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + uint64_t delta = t1 - t2; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; + + *delayp = + snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; + return 0; +} + +int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { + snd_pcm_status_t *alsa_snd_pcm_status; + snd_pcm_status_alloca(&alsa_snd_pcm_status); + + struct timespec tn; // time now + snd_htimestamp_t update_timestamp; // actually a struct timespec + + int ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) { + *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + + if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { + debug(1, "alsa: recovering from suspended or xrun in delay."); + ret = snd_pcm_recover(alsa_handle, ret, 1); + if (ret == 0) { + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) + *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + } + } + + // here, the device must be either SND_PCM_STATE_RUNNING + // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING + + if (ret == 0) { + + if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { + + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + + if (*state == SND_PCM_STATE_DRAINING) + debug(1, "alsa: draining with a delay of %d.", delay); + + clock_gettime(CLOCK_MONOTONIC, &tn); + uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t update_timestamp_ns = + update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + + // see if it's stalled + + if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) { + // hasn't outputted anything since the last call to delay() + + if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || + ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + stalled = 1; + ret = sps_extra_code_output_stalled; + } + } else { + stalled = 0; + stall_monitor_start_time = update_timestamp_ns; + stall_monitor_frame_count = *delay; + } + + if (ret == 0) { + uint64_t delta = time_now_ns - update_timestamp_ns; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = + frames_played_since_last_interrupt; + + *delay = *delay - frames_played_since_last_interrupt_sized; + } + } else { // not running, thus no delay information, thus can't check for stall + stalled = 0; + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes + + // not running, thus no delay information, thus can't check for frame rates + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; + + if ((*state != SND_PCM_STATE_PREPARED)) { + debug(1, "alsa: can't get device into valid state in delay. State is %d.", state); + ret = sps_extra_code_output_state_cannot_make_ready; + } + } + } + } + return ret; +} + int delay(long *the_delay) { // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED + // or SND_PCM_STATE_DRAINING // and returns the actual delay if running or 0 if prepared in *the_delay // otherwise return an error code @@ -1062,96 +1185,26 @@ int delay(long *the_delay) { ret = ENODEV; else { int oldState; + + snd_pcm_state_t state; + snd_pcm_sframes_t my_delay; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - snd_pcm_state_t state; - snd_pcm_sframes_t delay; + ret = delay_prep_and_status(&state, &my_delay); - snd_pcm_status_t *alsa_snd_pcm_status; - snd_pcm_status_alloca(&alsa_snd_pcm_status); + ret = 0; - struct timespec tn; // time now - snd_htimestamp_t update_timestamp; // actually a struct timespec + if ((ret == 0) && (state != SND_PCM_STATE_RUNNING) && (state != SND_PCM_STATE_DRAINING) && + (state != SND_PCM_STATE_PREPARED)) + ret = sps_extra_code_output_state_cannot_make_ready; - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) { - state = snd_pcm_status_get_state(alsa_snd_pcm_status); - - if ((state == SND_PCM_STATE_SUSPENDED) || (state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from suspended or xrun in delay."); - ret = snd_pcm_recover(alsa_handle, ret, 1); - if (ret == 0) { - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) - state = snd_pcm_status_get_state(alsa_snd_pcm_status); - } - } - - // here, the device must be either SND_PCM_STATE_RUNNING - // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING - - if (ret == 0) { - - if ((state == SND_PCM_STATE_RUNNING) || (state == SND_PCM_STATE_DRAINING)) { - - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); - - if (state == SND_PCM_STATE_DRAINING) - debug(1, "alsa: draining with a delay of %d.", delay); - - clock_gettime(CLOCK_MONOTONIC, &tn); - uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t update_timestamp_ns = - update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - - // see if it's stalled - - if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == delay)) { - // hasn't outputted anything since the last call to delay() - - if (((update_timestamp_ns - stall_monitor_start_time) > - stall_monitor_error_threshold) || - ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - stalled = 1; - ret = sps_extra_code_output_stalled; - } - } else { - stalled = 0; - stall_monitor_start_time = update_timestamp_ns; - stall_monitor_frame_count = delay; - } - - if (ret == 0) { - uint64_t delta = time_now_ns - update_timestamp_ns; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = - frames_played_since_last_interrupt; - - *the_delay = delay - frames_played_since_last_interrupt_sized; - } - } else { // not running, thus no delay information, thus can't check for stall - stalled = 0; - stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush - stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - - // not running, thus no delay information, thus can't check for frame rates - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - - if ((state != SND_PCM_STATE_PREPARED)) { - debug(1, "alsa: can't get device into valid state in delay. State is %d.",state); - ret = sps_extra_code_output_state_cannot_make_ready; - } - } - } - } debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); pthread_setcancelstate(oldState, NULL); + + *the_delay = my_delay; // note: snd_pcm_sframes_t is a long } return ret; } @@ -1171,7 +1224,7 @@ int old_delay(long *the_delay) { snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); if (dac_state == SND_PCM_STATE_RUNNING) { - reply = snd_pcm_delay(alsa_handle, the_delay); + reply = my_snd_pcm_delay(alsa_handle, the_delay); if (reply != 0) { debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, @@ -1260,6 +1313,84 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { int untimed_play(void *buf, int samples) { + // debug(3,"audio_alsa play called."); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + int ret = 0; + if (alsa_handle == NULL) { + + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + ret = actual_open_alsa_device(); + if (ret == 0) { + if (audio_alsa.volume) + do_volume(set_volume); + if (audio_alsa.mute) + do_mute(0); + } + + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex + } + if (ret == 0) { + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); + + snd_pcm_state_t state; + snd_pcm_sframes_t my_delay; + ret = delay_prep_and_status(&state, &my_delay); + + if (ret == 0) { // will be non-zero if an error or a stall + if ((state == SND_PCM_STATE_PREPARED) || (state == SND_PCM_STATE_RUNNING)) { + + if ((samples != 0) && (buf != NULL)) { + + // debug(3, "write %d frames.", samples); + ret = alsa_pcm_write(alsa_handle, buf, samples); + if (ret == samples) { + stall_monitor_frame_count += samples; + + if (frame_index == 0) { + frames_sent_for_playing = samples; + } else { + frames_sent_for_playing += samples; + } + + const uint64_t start_measurement_from_this_frame = + (2 * 44100) / 352; // two seconds of frames… + + frame_index++; + + if ((frame_index == start_measurement_from_this_frame) || + ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { + + measurement_time = get_absolute_time_in_fp(); + frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; + + if (frame_index == start_measurement_from_this_frame) { + // debug(1, "Start frame counting"); + frames_played_at_measurement_start_time = frames_played_at_measurement_time; + measurement_start_time = measurement_time; + measurement_data_is_valid = 1; + } + } + } else { + debug(1, "alsa: error writing to alsa device."); + } + } + } else { + debug(1, "alsa: device in incorrect state (%d) for play.", state); + frame_index = 0; + measurement_data_is_valid = 0; + } + } + debug_mutex_unlock(&alsa_mutex, 0); + pthread_cleanup_pop(0); // release the mutex + } + pthread_setcancelstate(oldState, NULL); + return ret; +} + +int old_untimed_play(void *buf, int samples) { + // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1619,8 +1750,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { buffer_size = 0; char errorstring[1024]; strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - // debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, - // (char *)errorstring); + debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + (char *)errorstring); } if (buffer_size < frames_of_silence) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && From 8d8b8c05f6025039e7cfda6d8f225b22fcb6948a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 19:54:12 +0000 Subject: [PATCH 005/115] new play(), delay(), delay_prep_and_status() routines in place and working, but with the old stuff taken out. --- audio_alsa.c | 291 +-------------------------------------------------- 1 file changed, 1 insertion(+), 290 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 79276aba..c41c5a06 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1,7 +1,7 @@ /* * libalsa output driver. This file is part of Shairport. * Copyright (c) Muffinman, Skaman 2013 - * Copyright (c) Mike Brady 2014 -- 2018 + * Copyright (c) Mike Brady 2014 -- 2019 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -44,7 +44,6 @@ static void start(int i_sample_rate, int i_sample_format); static int play(void *buf, int samples); static void stop(void); static void flush(void); -// int delay(long *the_delay); int delay(long *the_delay); void do_mute(int request); int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played); @@ -82,7 +81,6 @@ pthread_t alsa_buffer_monitor_thread; uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's an error -int stalled; static snd_output_t *output = NULL; static unsigned int desired_sample_rate; @@ -1003,7 +1001,6 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; - stalled = 0; stall_monitor_start_time = 0; stall_monitor_frame_count = 0; @@ -1012,81 +1009,6 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form } -// assuming pthread cancellation is disabled -int my_snd_pcm_state_and_delay(snd_pcm_t *pcm, snd_pcm_state_t *state, snd_pcm_sframes_t *delayp) { - *state = SND_PCM_STATE_LAST + 1; // unknown - *delayp = 0; - - int ret; - snd_pcm_status_t *alsa_snd_pcm_status; - snd_pcm_status_alloca(&alsa_snd_pcm_status); - - struct timespec tn; // time now - snd_htimestamp_t update_timestamp; // actually a struct timespec - - ret = snd_pcm_status(pcm, alsa_snd_pcm_status); - if (ret) { - return ret; - } - - *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - - if (*state == SND_PCM_STATE_RUNNING) { - - clock_gettime(CLOCK_MONOTONIC, &tn); - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - - uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - uint64_t delta = t1 - t2; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - - *delayp = - snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; - } - return 0; -} - -// assuming pthread cancellation is disabled -int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { - int ret; - snd_pcm_status_t *alsa_snd_pcm_status; - snd_pcm_status_alloca(&alsa_snd_pcm_status); - - struct timespec tn; // time now - snd_htimestamp_t update_timestamp; // actually a struct timespec - - ret = snd_pcm_status(pcm, alsa_snd_pcm_status); - if (ret) { - *delayp = 0; - return ret; - } - - snd_pcm_state_t state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if (state != SND_PCM_STATE_RUNNING) { - *delayp = 0; - return -EIO; // might be a better code than this... - } - - clock_gettime(CLOCK_MONOTONIC, &tn); - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - - uint64_t t1 = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t t2 = update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - uint64_t delta = t1 - t2; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - - *delayp = - snd_pcm_status_get_delay(alsa_snd_pcm_status) - frames_played_since_last_interrupt_sized; - return 0; -} - int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { snd_pcm_status_t *alsa_snd_pcm_status; snd_pcm_status_alloca(&alsa_snd_pcm_status); @@ -1133,11 +1055,9 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - stalled = 1; ret = sps_extra_code_output_stalled; } } else { - stalled = 0; stall_monitor_start_time = update_timestamp_ns; stall_monitor_frame_count = *delay; } @@ -1153,7 +1073,6 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { *delay = *delay - frames_played_since_last_interrupt_sized; } } else { // not running, thus no delay information, thus can't check for stall - stalled = 0; stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes @@ -1209,95 +1128,6 @@ int delay(long *the_delay) { return ret; } -int old_delay(long *the_delay) { - *the_delay = 0; - // snd_pcm_sframes_t is a signed long -- hence the return of a "long" - int reply = 0; - // debug(3,"audio_alsa delay called."); - if (alsa_handle == NULL) { - reply = -ENODEV; - } else { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - int derr; - snd_pcm_state_t dac_state = snd_pcm_state(alsa_handle); - if (dac_state == SND_PCM_STATE_RUNNING) { - - reply = my_snd_pcm_delay(alsa_handle, the_delay); - - if (reply != 0) { - debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply, - snd_strerror(reply), *the_delay); - derr = snd_pcm_recover(alsa_handle, reply, 1); - if (derr < 0) - warn("Error %d -- could not clear an error after attempting delay(): \"%s\".", derr, - snd_strerror(derr)); - - frame_index = 0; - measurement_data_is_valid = 0; - } else { - if (*the_delay == 0) { - // there's nothing in the pipeline, so we can't measure frame rate. - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - } - } - } else { - reply = -EIO; // shomething is wrong - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - - if (dac_state == SND_PCM_STATE_PREPARED) { - debug(2, "delay not available -- state is SND_PCM_STATE_PREPARED"); - } else { - if (dac_state == SND_PCM_STATE_XRUN) { - debug(2, "delay not available -- state is SND_PCM_STATE_XRUN"); - } else { - - debug(1, "Error -- ALSA delay(): bad state: %d.", dac_state); - } - if ((derr = snd_pcm_prepare(alsa_handle))) { - debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr)); - derr = snd_pcm_recover(alsa_handle, derr, 1); - if (derr < 0) - warn("Error %d -- could not clear an error after attempting to recover following a " - "delay(): \"%s\".", - derr, snd_strerror(derr)); - } - } - } - debug_mutex_unlock(&alsa_mutex, 0); - pthread_cleanup_pop(0); - // here, occasionally pretend there's a problem with pcm_get_delay() - // if ((random() % 100000) < 3) // keep it pretty rare - // reply = -EIO; // pretend something bad has happened - pthread_setcancelstate(oldState, NULL); - } - if ((reply == 0) && (*the_delay != 0)) { - uint64_t stall_monitor_time_now = get_absolute_time_in_fp(); - if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *the_delay)) { - // hasn't outputted anything since the last call to delay() - uint64_t time_stalled = stall_monitor_time_now - stall_monitor_start_time; - if (time_stalled > stall_monitor_error_threshold) { - stalled = 1; - reply = sps_extra_code_output_stalled; - } - } else { - // is outputting stuff, so restart the monitoring here - stalled = 0; - stall_monitor_start_time = stall_monitor_time_now; - stall_monitor_frame_count = *the_delay; - } - } else { - // if there is an error or the delay is zero (from which it is assumed there is no output) - stalled = 0; - stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush - stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - } - return reply; -} - int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { int response = 0; // zero means okay if (measurement_data_is_valid) { @@ -1389,125 +1219,6 @@ int untimed_play(void *buf, int samples) { return ret; } -int old_untimed_play(void *buf, int samples) { - - // debug(3,"audio_alsa play called."); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - int ret = 0; - if (alsa_handle == NULL) { - - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - ret = actual_open_alsa_device(); - if (ret == 0) { - if (audio_alsa.volume) - do_volume(set_volume); - if (audio_alsa.mute) - do_mute(0); - } - - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex - } - if (ret == 0) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - // snd_pcm_sframes_t current_delay = 0; - int err, err2; - if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) || - (snd_pcm_state(alsa_handle) == SND_PCM_STATE_OPEN) || - (snd_pcm_state(alsa_handle) == SND_PCM_STATE_SETUP)) { - if ((err = snd_pcm_prepare(alsa_handle))) { - debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err)); - err = snd_pcm_recover(alsa_handle, err, 1); - if (err < 0) - warn("Error %d -- could not clear an error after detecting underrun in play(): \"%s\".", - err, snd_strerror(err)); - } - frame_index = 0; // we'll be starting over - measurement_data_is_valid = 0; - } else if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) || - (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) { - if (buf == NULL) - debug(1, "NULL buffer passed to pcm_writei -- skipping it"); - if (samples == 0) - debug(1, "empty buffer being passed to pcm_writei -- skipping it"); - if ((samples != 0) && (buf != NULL) && (stalled == 0)) { - // debug(3, "write %d frames.", samples); - err = alsa_pcm_write(alsa_handle, buf, samples); - - stall_monitor_frame_count += samples; - - if (err < 0) { - frame_index = 0; - measurement_data_is_valid = 0; - debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples, - snd_strerror(err)); - err = snd_pcm_recover(alsa_handle, err, 1); - if (err < 0) - warn("Error %d -- could not clear an error after attempting to write %d samples in " - "play(): \"%s\".", - err, samples, snd_strerror(err)); - } - - if (frame_index == 0) { - frames_sent_for_playing = samples; - } else { - frames_sent_for_playing += samples; - } - const uint64_t start_measurement_from_this_frame = - (2 * 44100) / 352; // two seconds of frames… - frame_index++; - if ((frame_index == start_measurement_from_this_frame) || (frame_index % 32 == 0)) { - long fl = 0; - err2 = snd_pcm_delay(alsa_handle, &fl); - if (err2 != 0) { - debug(1, "Error %d in delay in play(): \"%s\". Delay reported is %d frames.", err2, - snd_strerror(err2), fl); - err2 = snd_pcm_recover(alsa_handle, err2, 1); - if (err2 < 0) - warn("Error %d -- could not clear an error after checking delay in play(): \"%s\".", - err2, snd_strerror(err2)); - frame_index = 0; - measurement_data_is_valid = 0; - } else { - if (fl == 0) { - // there's nothing in the pipeline, so we can't measure frame rate. - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - } - } - - measurement_time = get_absolute_time_in_fp(); - frames_played_at_measurement_time = frames_sent_for_playing - fl; - if (frame_index == start_measurement_from_this_frame) { - // debug(1, "Start frame counting"); - frames_played_at_measurement_start_time = frames_played_at_measurement_time; - measurement_start_time = measurement_time; - measurement_data_is_valid = 1; - } - } - } - } else { - debug(1, "Error -- ALSA device in incorrect state (%d) for play.", - snd_pcm_state(alsa_handle)); - if ((err = snd_pcm_prepare(alsa_handle))) { - debug(1, "Error preparing after play error: \"%s\".", snd_strerror(err)); - err2 = snd_pcm_recover(alsa_handle, err, 1); - if (err2 < 0) - warn("Error %d -- could not clear an error after reporting ALSA device in incorrect " - "state for play: \"%s\".", - err2, snd_strerror(err2)); - } - frame_index = 0; - measurement_data_is_valid = 0; - } - debug_mutex_unlock(&alsa_mutex, 0); - pthread_cleanup_pop(0); // release the mutex - } - pthread_setcancelstate(oldState, NULL); - return ret; -} - static void flush(void) { // debug(2,"audio_alsa flush called."); int oldState; From 15729ebcd43005039dc40169491599b3b53177ff Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 22:40:48 +0000 Subject: [PATCH 006/115] Fix trivial error in a debug message. --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index c41c5a06..26aac7b0 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1081,7 +1081,7 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { measurement_data_is_valid = 0; if ((*state != SND_PCM_STATE_PREPARED)) { - debug(1, "alsa: can't get device into valid state in delay. State is %d.", state); + debug(1, "alsa: can't get device into valid state in delay. State is %d.", *state); ret = sps_extra_code_output_state_cannot_make_ready; } } From b973619d66e613956947841eb290f302773e7455 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 2 Jan 2019 22:42:48 +0000 Subject: [PATCH 007/115] Using new delay() and play() functions. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 3ef39546..c4b783bb 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d42], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d43], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 41c8c8b9a166941d75fa3d88bcf348b68c6698cb Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 3 Jan 2019 10:19:44 +0000 Subject: [PATCH 008/115] Strengthen the ability to recover from an XRUN by putting in a call to snd_pcm_prepare first. --- audio_alsa.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 26aac7b0..43e26749 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1021,8 +1021,10 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { *state = snd_pcm_status_get_state(alsa_snd_pcm_status); if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from suspended or xrun in delay."); - ret = snd_pcm_recover(alsa_handle, ret, 1); + debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); + ret = snd_pcm_prepare(alsa_handle); + if (ret == 0) + ret = snd_pcm_recover(alsa_handle, ret, 1); if (ret == 0) { ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); if (ret == 0) From f5707d4f180ae16539f6aff20195db526c0860ce Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 3 Jan 2019 10:48:09 +0000 Subject: [PATCH 009/115] Stop dropping metadata with a zero id -- it's not infallible way of determining if the metadata is invalid. --- metadata_hub.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/metadata_hub.c b/metadata_hub.c index 6938de5f..aacfb3a3 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -489,27 +489,15 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin track_metadata = (struct track_metadata_bundle *)malloc(sizeof(struct track_metadata_bundle)); if (track_metadata == NULL) die("Could not allocate memory for track metadata."); - memset(track_metadata, 0, sizeof(struct track_metadata_bundle)); // now we have a valid track - // metadata space, but the - // metadata itself - // might turin out to be invalid. Specifically, YouTube on iOS can generate a sequence of - // track metadata that is invalid. - // it is distinguished by having an item_id of zero. + memset(track_metadata, 0, sizeof(struct track_metadata_bundle)); break; case 'mden': if (track_metadata) { - if ((track_metadata->item_id_received == 0) || (track_metadata->item_id != 0)) { - // i.e. it's only invalid if it has definitely been given an item_id of zero - metadata_hub_modify_prolog(); - metadata_hub_release_track_metadata(metadata_store.track_metadata); - metadata_store.track_metadata = track_metadata; - track_metadata = NULL; - metadata_hub_modify_epilog(1); - } else { - debug(1, "The track information received is invalid -- dropping it"); - metadata_hub_release_track_metadata(track_metadata); - track_metadata = NULL; - } + metadata_hub_modify_prolog(); + metadata_hub_release_track_metadata(metadata_store.track_metadata); + metadata_store.track_metadata = track_metadata; + track_metadata = NULL; + metadata_hub_modify_epilog(1); } debug(2, "MH Metadata stream processing end."); break; From c35fb69e33dff6a964e5851aa4cdf0b70b2e0315 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 3 Jan 2019 10:48:54 +0000 Subject: [PATCH 010/115] Fix parsing error with progress 'prgr' metadata --- rtsp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index bf0ad370..74d1c54f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1034,8 +1034,8 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, } else #ifdef CONFIG_METADATA if (!strncmp(cp, "progress: ", strlen("progress: "))) { - char *progress = cp + strlen("volume: "); - // debug(2, "progress: \"%s\"\n",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per + char *progress = cp + strlen("progress: "); + // debug(2, "progress: \"%s\"",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per // second send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1); From 8e5733e6ae88c47d7359eca556e1616091f7cd3c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 11:29:18 +0000 Subject: [PATCH 011/115] Check before using snd_pcm_prepare in delay() --- audio_alsa.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 43e26749..0528c270 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1021,14 +1021,23 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { *state = snd_pcm_status_get_state(alsa_snd_pcm_status); if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); - ret = snd_pcm_prepare(alsa_handle); - if (ret == 0) - ret = snd_pcm_recover(alsa_handle, ret, 1); + debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); + ret = snd_pcm_recover(alsa_handle, ret, 1); if (ret == 0) { ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); if (ret == 0) *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + if (*state != SND_PCM_STATE_PREPARED) { + debug(1, "alsa: snd_pcm_recover failed -- have to use snd_pcm_prepare."); + ret = snd_pcm_prepare(alsa_handle); + if (ret == 0) + ret = snd_pcm_recover(alsa_handle, ret, 1); + if (ret == 0) { + ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); + if (ret == 0) + *state = snd_pcm_status_get_state(alsa_snd_pcm_status); + } + } } } From 4f9730e3b22745f3d4b6a6bdf625d7cbd5be2682 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 13:47:44 +0000 Subject: [PATCH 012/115] Give a nice message if the soxr library isn't installed. --- configure.ac | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index c4b783bb..f6a1c0f8 100644 --- a/configure.ac +++ b/configure.ac @@ -175,14 +175,15 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation AC_DEFINE([CONFIG_SOXR], 1, [Include support for using the SoX Resampler library for interpolation]) PKG_CHECK_MODULES( [SOXR], [soxr], - [LIBS="${SOXR_LIBS} ${LIBS}"]) + [LIBS="${SOXR_LIBS} ${LIBS}"], + [AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!)]) else AC_CHECK_LIB([avutil],[av_get_cpu_flags]) if test "x${ac_cv_lib_avutil_av_get_cpu_flags}" = xyes ; then # soxr may link against libavutil, depending on the architecture, but for the sake of simplicity link with it if it is found - AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!), [-lavutil]) + AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!), [-lavutil]) else - AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!)) + AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!)) fi fi ], ) From 4610d0870d8b5fc58f4acedec6d482916ea1e4be Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 13:48:56 +0000 Subject: [PATCH 013/115] Raise the soxr minimum buffer size to something sensible and multiply it by the output ratio --- player.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index b14b9daf..5885568b 100644 --- a/player.c +++ b/player.c @@ -95,7 +95,7 @@ #define MAX_PACKET 2048 // DAC buffer occupancy stuff -#define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 600 +#define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 2500 // static abuf_t audio_buffer[BUFFER_FRAMES]; #define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES) @@ -2214,7 +2214,7 @@ void *player_thread_func(void *arg) { } } - if ((current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) || + if ((current_delay < (DAC_BUFFER_QUEUE_MINIMUM_LENGTH * conn->output_sample_ratio)) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, From 459dd51a31fa088774cbbdb3466147d72c37d32e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 13:50:14 +0000 Subject: [PATCH 014/115] Use snd_pcm_recover only _after_ an XRUN or suspend has happened. Don't use it to remove one -- it doesn't work. --- audio_alsa.c | 191 ++++++++++++++++++++++++--------------------------- 1 file changed, 89 insertions(+), 102 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0528c270..20c30bd1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1020,82 +1020,51 @@ int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (ret == 0) { *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if ((*state == SND_PCM_STATE_SUSPENDED) || (*state == SND_PCM_STATE_XRUN)) { - debug(1, "alsa: recovering from SND_PCM_STATE_* %d in delay_prep_and_status.", *state); - ret = snd_pcm_recover(alsa_handle, ret, 1); + if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { + + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); + + if (*state == SND_PCM_STATE_DRAINING) + debug(1, "alsa: draining with a delay of %d.", delay); + + clock_gettime(CLOCK_MONOTONIC, &tn); + uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; + uint64_t update_timestamp_ns = + update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; + + // see if it's stalled + + if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) { + // hasn't outputted anything since the last call to delay() + + if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || + ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + ret = sps_extra_code_output_stalled; + } + } else { + stall_monitor_start_time = update_timestamp_ns; + stall_monitor_frame_count = *delay; + } + if (ret == 0) { - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) - *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - if (*state != SND_PCM_STATE_PREPARED) { - debug(1, "alsa: snd_pcm_recover failed -- have to use snd_pcm_prepare."); - ret = snd_pcm_prepare(alsa_handle); - if (ret == 0) - ret = snd_pcm_recover(alsa_handle, ret, 1); - if (ret == 0) { - ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status); - if (ret == 0) - *state = snd_pcm_status_get_state(alsa_snd_pcm_status); - } - } + uint64_t delta = time_now_ns - update_timestamp_ns; + + uint64_t frames_played_since_last_interrupt = + ((uint64_t)desired_sample_rate * delta) / 1000000000; + snd_pcm_sframes_t frames_played_since_last_interrupt_sized = + frames_played_since_last_interrupt; + + *delay = *delay - frames_played_since_last_interrupt_sized; } - } + } else { // not running, thus no delay information, thus can't check for stall + *delay = 0; + stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush + stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - // here, the device must be either SND_PCM_STATE_RUNNING - // or SND_PCM_STATE_PREPARED or SND_PCM_STATE_DRAINING - - if (ret == 0) { - - if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { - - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); - *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); - - if (*state == SND_PCM_STATE_DRAINING) - debug(1, "alsa: draining with a delay of %d.", delay); - - clock_gettime(CLOCK_MONOTONIC, &tn); - uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec; - uint64_t update_timestamp_ns = - update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec; - - // see if it's stalled - - if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) { - // hasn't outputted anything since the last call to delay() - - if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || - ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - ret = sps_extra_code_output_stalled; - } - } else { - stall_monitor_start_time = update_timestamp_ns; - stall_monitor_frame_count = *delay; - } - - if (ret == 0) { - uint64_t delta = time_now_ns - update_timestamp_ns; - - uint64_t frames_played_since_last_interrupt = - ((uint64_t)desired_sample_rate * delta) / 1000000000; - snd_pcm_sframes_t frames_played_since_last_interrupt_sized = - frames_played_since_last_interrupt; - - *delay = *delay - frames_played_since_last_interrupt_sized; - } - } else { // not running, thus no delay information, thus can't check for stall - stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush - stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - - // not running, thus no delay information, thus can't check for frame rates - frame_index = 0; // we'll be starting over... - measurement_data_is_valid = 0; - - if ((*state != SND_PCM_STATE_PREPARED)) { - debug(1, "alsa: can't get device into valid state in delay. State is %d.", *state); - ret = sps_extra_code_output_state_cannot_make_ready; - } - } + // not running, thus no delay information, thus can't check for frame rates + frame_index = 0; // we'll be starting over... + measurement_data_is_valid = 0; } } return ret; @@ -1180,41 +1149,59 @@ int untimed_play(void *buf, int samples) { ret = delay_prep_and_status(&state, &my_delay); if (ret == 0) { // will be non-zero if an error or a stall - if ((state == SND_PCM_STATE_PREPARED) || (state == SND_PCM_STATE_RUNNING)) { - if ((samples != 0) && (buf != NULL)) { + if ((samples != 0) && (buf != NULL)) { - // debug(3, "write %d frames.", samples); - ret = alsa_pcm_write(alsa_handle, buf, samples); - if (ret == samples) { - stall_monitor_frame_count += samples; + // debug(3, "write %d frames.", samples); + ret = alsa_pcm_write(alsa_handle, buf, samples); + if (ret == samples) { + stall_monitor_frame_count += samples; - if (frame_index == 0) { - frames_sent_for_playing = samples; - } else { - frames_sent_for_playing += samples; + if (frame_index == 0) { + frames_sent_for_playing = samples; + } else { + frames_sent_for_playing += samples; + } + + const uint64_t start_measurement_from_this_frame = + (2 * 44100) / 352; // two seconds of frames… + + frame_index++; + + if ((frame_index == start_measurement_from_this_frame) || + ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { + + measurement_time = get_absolute_time_in_fp(); + frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; + + if (frame_index == start_measurement_from_this_frame) { + // debug(1, "Start frame counting"); + frames_played_at_measurement_start_time = frames_played_at_measurement_time; + measurement_start_time = measurement_time; + measurement_data_is_valid = 1; } - - const uint64_t start_measurement_from_this_frame = - (2 * 44100) / 352; // two seconds of frames… - - frame_index++; - - if ((frame_index == start_measurement_from_this_frame) || - ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { - - measurement_time = get_absolute_time_in_fp(); - frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; - - if (frame_index == start_measurement_from_this_frame) { - // debug(1, "Start frame counting"); - frames_played_at_measurement_start_time = frames_played_at_measurement_time; - measurement_start_time = measurement_time; - measurement_data_is_valid = 1; + } + } else { + debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); + if (ret == -EPIPE) { /* underrun */ + ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); + if (ret < 0) { + ret = snd_pcm_prepare(alsa_handle); + if (ret < 0) + warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " + "snd_pcm_prepare() failed: %s.", + snd_strerror(ret)); + } else if (ret == -ESTRPIPE) { /* suspended */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) + sleep(1); /* wait until the suspend flag is released */ + if (ret < 0) { + ret = snd_pcm_prepare(alsa_handle); + if (ret < 0) + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + "failed: %s.", + snd_strerror(ret)); } } - } else { - debug(1, "alsa: error writing to alsa device."); } } } else { From 5e38fcd50fe09e0f4c74a7b32a570809cbba3da5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:06:21 +0000 Subject: [PATCH 015/115] Add default values for the interpolation threshold --- audio_jack.c | 1 + audio_pa.c | 2 ++ audio_sndio.c | 1 + 3 files changed, 4 insertions(+) diff --git a/audio_jack.c b/audio_jack.c index 2f79bee1..9f9d0a0b 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -301,6 +301,7 @@ void *open_client_if_necessary_thread_function(void *arg) { int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.500; + config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. config.jack_auto_client_open_interval = 1; // check every second // get settings from settings file first, allow them to be overridden by diff --git a/audio_pa.c b/audio_pa.c index ca79b4dc..48e4579d 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -70,6 +70,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * // set up default values first config.audio_backend_buffer_desired_length = 0.35; + config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + config.audio_backend_latency_offset = 0; // get settings from settings file diff --git a/audio_sndio.c b/audio_sndio.c index d8037edb..511d90b3 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -99,6 +99,7 @@ static int init(int argc, char **argv) { devname = SIO_DEVANY; config.audio_backend_buffer_desired_length = 1.0; + config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. config.audio_backend_latency_offset = 0; // get settings from settings file From db0e718c12af37b8ca0018fbeacd6b6d2ef720d2 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:07:21 +0000 Subject: [PATCH 016/115] Add code for getting and setting the interpolation threshold. --- audio.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/audio.c b/audio.c index a0b5f74d..4c3df7df 100644 --- a/audio.c +++ b/audio.c @@ -152,6 +152,19 @@ void parse_general_audio_options(void) { config.audio_backend_buffer_desired_length = dvalue; } } + + /* Get the minumum buffer size for fancy interpolation setting in seconds. */ + if (config_lookup_float(config.cfg, "general.audio_backend_buffer_interpolation_threshold_in_seconds", + &dvalue)) { + if ((dvalue < 0) || (dvalue > config.audio_backend_buffer_desired_length)) { + die("Invalid audio_backend_buffer_interpolation_threshold_in_seconds value: \"%f\". It " + "should be between 0 and " + "audio_backend_buffer_desired_length_in_seconds of %.3f, default is %.3f seconds", + dvalue, config.audio_backend_buffer_desired_length,config.audio_backend_buffer_interpolation_threshold_in_seconds); + } else { + config.audio_backend_buffer_interpolation_threshold_in_seconds = dvalue; + } + } /* Get the latency offset (deprecated). */ if (config_lookup_int(config.cfg, "general.audio_backend_latency_offset", &value)) { From 01dbf625fe215b426915832e71e5b336da8fd518 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:09:37 +0000 Subject: [PATCH 017/115] Add the interpolation threshold setting. --- common.h | 1 + scripts/shairport-sync.conf | 1 + 2 files changed, 2 insertions(+) diff --git a/common.h b/common.h index c42d76a3..4e2fb65a 100644 --- a/common.h +++ b/common.h @@ -168,6 +168,7 @@ typedef struct { int interface_index; // only valid if the interface string is non-NULL double audio_backend_buffer_desired_length; // this will be the length in seconds of the // audio backend buffer -- the DAC buffer for ALSA + double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 39db8077..b34c8455 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -41,6 +41,7 @@ general = // audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1. // audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise. +// audio_backend_buffer_interpolation_threshold_in_seconds = 0.075; // Advanced feature. If the buffer size drops below this, stop using time-consuming interpolation like soxr to avoid dropouts due to underrun. // audio_backend_silent_lead_in_time = 2.0; // This optional advanced setting, from 0.0 and 4.0 seconds, sets the length of the period of silence that precedes the start of the audio. The default is the latency, usually 2.0 seconds. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation. // dbus_service_bus = "system"; // The Shairport Sync dbus interface, if selected at compilation, will appear // as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session". From 7b97ea4019da9675047ce37913f7590ff139f91c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:10:26 +0000 Subject: [PATCH 018/115] Add code for calculating and recognising the interpolation threshold. --- player.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index 5885568b..f17cf685 100644 --- a/player.c +++ b/player.c @@ -1491,7 +1491,7 @@ void *player_thread_func(void *arg) { conn->first_packet_timestamp = 0; conn->flush_requested = 0; conn->fix_volume = 0x10000; - + if (conn->latency == 0) { debug(3, "No latency has (yet) been specified. Setting 88,200 (2 seconds) frames " "as a default."); @@ -1551,6 +1551,10 @@ void *player_thread_func(void *arg) { debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame); + conn->dac_buffer_queue_minimum_length = (int64_t)(config.audio_backend_buffer_interpolation_threshold_in_seconds * + config.output_rate); + debug(1,"dac_buffer_queue_minimum_length is %" PRId64 " frames.", conn->dac_buffer_queue_minimum_length); + conn->session_corrections = 0; // conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment @@ -2214,7 +2218,7 @@ void *player_thread_func(void *arg) { } } - if ((current_delay < (DAC_BUFFER_QUEUE_MINIMUM_LENGTH * conn->output_sample_ratio)) || + if ((current_delay < conn->dac_buffer_queue_minimum_length) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, From 3434761517703e18104f7e333d25910d3abf8da0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:10:57 +0000 Subject: [PATCH 019/115] Add code for calculating and recognising the interpolation threshold. --- player.h | 1 + 1 file changed, 1 insertion(+) diff --git a/player.h b/player.h index 7dd1c186..84521893 100644 --- a/player.h +++ b/player.h @@ -248,6 +248,7 @@ typedef struct { 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 + int64_t dac_buffer_queue_minimum_length; } rtsp_conn_info; uint32_t modulo_32_offset(uint32_t from, uint32_t to); From 7496364aaac9e640daeda566ce061f2709d80b23 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:20:15 +0000 Subject: [PATCH 020/115] continue to change delay() and play() to reflect use of snd_pcm_recover _after_ a XRUN has occurred -- fix rate calculations etc. --- audio_alsa.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 20c30bd1..f239d63b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -637,7 +637,7 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; - + config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.050; // below this, basic interpolation will be used to save time. 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 @@ -1009,7 +1009,7 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form } -int delay_prep_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { +int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { snd_pcm_status_t *alsa_snd_pcm_status; snd_pcm_status_alloca(&alsa_snd_pcm_status); @@ -1091,7 +1091,7 @@ int delay(long *the_delay) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - ret = delay_prep_and_status(&state, &my_delay); + ret = delay_and_status(&state, &my_delay); ret = 0; @@ -1146,11 +1146,17 @@ int untimed_play(void *buf, int samples) { snd_pcm_state_t state; snd_pcm_sframes_t my_delay; - ret = delay_prep_and_status(&state, &my_delay); + ret = delay_and_status(&state, &my_delay); if (ret == 0) { // will be non-zero if an error or a stall if ((samples != 0) && (buf != NULL)) { + + // jut check the state of the DAC + + if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && (state != SND_PCM_STATE_XRUN)) { + debug(1,"alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.",state); + } // debug(3, "write %d frames.", samples); ret = alsa_pcm_write(alsa_handle, buf, samples); @@ -1164,7 +1170,7 @@ int untimed_play(void *buf, int samples) { } const uint64_t start_measurement_from_this_frame = - (2 * 44100) / 352; // two seconds of frames… + (2 * config.output_rate) / 352; // two seconds of frames… frame_index++; @@ -1183,16 +1189,20 @@ int untimed_play(void *buf, int samples) { } } else { debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); + frame_index = 0; + measurement_data_is_valid = 0; if (ret == -EPIPE) { /* underrun */ ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); if (ret < 0) { + debug(1, "alsa: failed to recover from SND_PCM_STATE_XRUN with snd_pcm_recover(); trying snd_pcm_prepare()."); ret = snd_pcm_prepare(alsa_handle); if (ret < 0) warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " "snd_pcm_prepare() failed: %s.", snd_strerror(ret)); - } else if (ret == -ESTRPIPE) { /* suspended */ - while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) + } + } else if (ret == -ESTRPIPE) { /* suspended */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { sleep(1); /* wait until the suspend flag is released */ if (ret < 0) { ret = snd_pcm_prepare(alsa_handle); @@ -1204,12 +1214,13 @@ int untimed_play(void *buf, int samples) { } } } - } else { - debug(1, "alsa: device in incorrect state (%d) for play.", state); - frame_index = 0; - measurement_data_is_valid = 0; } + } else { + debug(1, "alsa: device status %d faulty for play.", state); + frame_index = 0; + measurement_data_is_valid = 0; } + debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); // release the mutex } From 9552b278b73c07d9bf2121881a8aac971e661fa4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:26:00 +0000 Subject: [PATCH 021/115] remove some debugging code from delay(). --- audio_alsa.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index f239d63b..0372dc53 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1093,12 +1093,6 @@ int delay(long *the_delay) { ret = delay_and_status(&state, &my_delay); - ret = 0; - - if ((ret == 0) && (state != SND_PCM_STATE_RUNNING) && (state != SND_PCM_STATE_DRAINING) && - (state != SND_PCM_STATE_PREPARED)) - ret = sps_extra_code_output_state_cannot_make_ready; - debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); pthread_setcancelstate(oldState, NULL); From 80f15e1f574f060d9f644eef176ecb37d775ddfd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:26:55 +0000 Subject: [PATCH 022/115] clang format --- audio.c | 8 +++++--- audio_alsa.c | 21 ++++++++++++--------- audio_jack.c | 4 +++- audio_pa.c | 4 +++- audio_sndio.c | 4 +++- common.h | 4 +++- player.c | 10 +++++----- 7 files changed, 34 insertions(+), 21 deletions(-) diff --git a/audio.c b/audio.c index 4c3df7df..7d0f489f 100644 --- a/audio.c +++ b/audio.c @@ -152,15 +152,17 @@ void parse_general_audio_options(void) { config.audio_backend_buffer_desired_length = dvalue; } } - + /* Get the minumum buffer size for fancy interpolation setting in seconds. */ - if (config_lookup_float(config.cfg, "general.audio_backend_buffer_interpolation_threshold_in_seconds", + if (config_lookup_float(config.cfg, + "general.audio_backend_buffer_interpolation_threshold_in_seconds", &dvalue)) { if ((dvalue < 0) || (dvalue > config.audio_backend_buffer_desired_length)) { die("Invalid audio_backend_buffer_interpolation_threshold_in_seconds value: \"%f\". It " "should be between 0 and " "audio_backend_buffer_desired_length_in_seconds of %.3f, default is %.3f seconds", - dvalue, config.audio_backend_buffer_desired_length,config.audio_backend_buffer_interpolation_threshold_in_seconds); + dvalue, config.audio_backend_buffer_desired_length, + config.audio_backend_buffer_interpolation_threshold_in_seconds); } else { config.audio_backend_buffer_interpolation_threshold_in_seconds = dvalue; } diff --git a/audio_alsa.c b/audio_alsa.c index 0372dc53..2a453b1e 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -637,7 +637,8 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; - config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.050; // below this, basic interpolation will be used to save time. + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.050; // below this, basic interpolation will be used to save time. 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 @@ -1145,11 +1146,12 @@ int untimed_play(void *buf, int samples) { if (ret == 0) { // will be non-zero if an error or a stall if ((samples != 0) && (buf != NULL)) { - + // jut check the state of the DAC - - if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && (state != SND_PCM_STATE_XRUN)) { - debug(1,"alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.",state); + + if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && + (state != SND_PCM_STATE_XRUN)) { + debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state); } // debug(3, "write %d frames.", samples); @@ -1188,7 +1190,8 @@ int untimed_play(void *buf, int samples) { if (ret == -EPIPE) { /* underrun */ ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); if (ret < 0) { - debug(1, "alsa: failed to recover from SND_PCM_STATE_XRUN with snd_pcm_recover(); trying snd_pcm_prepare()."); + debug(1, "alsa: failed to recover from SND_PCM_STATE_XRUN with snd_pcm_recover(); " + "trying snd_pcm_prepare()."); ret = snd_pcm_prepare(alsa_handle); if (ret < 0) warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " @@ -1196,8 +1199,8 @@ int untimed_play(void *buf, int samples) { snd_strerror(ret)); } } else if (ret == -ESTRPIPE) { /* suspended */ - while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { - sleep(1); /* wait until the suspend flag is released */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { + sleep(1); /* wait until the suspend flag is released */ if (ret < 0) { ret = snd_pcm_prepare(alsa_handle); if (ret < 0) @@ -1209,7 +1212,7 @@ int untimed_play(void *buf, int samples) { } } } - } else { + } else { debug(1, "alsa: device status %d faulty for play.", state); frame_index = 0; measurement_data_is_valid = 0; diff --git a/audio_jack.c b/audio_jack.c index 9f9d0a0b..11ae6072 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -301,7 +301,9 @@ void *open_client_if_necessary_thread_function(void *arg) { int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.500; - config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation + // instead. config.jack_auto_client_open_interval = 1; // check every second // get settings from settings file first, allow them to be overridden by diff --git a/audio_pa.c b/audio_pa.c index 48e4579d..cd321bd9 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -70,7 +70,9 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * // set up default values first config.audio_backend_buffer_desired_length = 0.35; - config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation + // instead. config.audio_backend_latency_offset = 0; diff --git a/audio_sndio.c b/audio_sndio.c index 511d90b3..f22ed5af 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -99,7 +99,9 @@ static int init(int argc, char **argv) { devname = SIO_DEVANY; config.audio_backend_buffer_desired_length = 1.0; - config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + config.audio_backend_buffer_interpolation_threshold_in_seconds = + 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation + // instead. config.audio_backend_latency_offset = 0; // get settings from settings file diff --git a/common.h b/common.h index 4e2fb65a..35996fef 100644 --- a/common.h +++ b/common.h @@ -168,7 +168,9 @@ typedef struct { int interface_index; // only valid if the interface string is non-NULL double audio_backend_buffer_desired_length; // this will be the length in seconds of the // audio backend buffer -- the DAC buffer for ALSA - double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation will not occur -- it'll be basic interpolation instead. + double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation + // will not occur -- it'll be + // basic interpolation instead. double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. diff --git a/player.c b/player.c index f17cf685..a085a636 100644 --- a/player.c +++ b/player.c @@ -1491,7 +1491,7 @@ void *player_thread_func(void *arg) { conn->first_packet_timestamp = 0; conn->flush_requested = 0; conn->fix_volume = 0x10000; - + if (conn->latency == 0) { debug(3, "No latency has (yet) been specified. Setting 88,200 (2 seconds) frames " "as a default."); @@ -1551,9 +1551,10 @@ void *player_thread_func(void *arg) { debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame); - conn->dac_buffer_queue_minimum_length = (int64_t)(config.audio_backend_buffer_interpolation_threshold_in_seconds * - config.output_rate); - debug(1,"dac_buffer_queue_minimum_length is %" PRId64 " frames.", conn->dac_buffer_queue_minimum_length); + conn->dac_buffer_queue_minimum_length = (int64_t)( + config.audio_backend_buffer_interpolation_threshold_in_seconds * config.output_rate); + debug(1, "dac_buffer_queue_minimum_length is %" PRId64 " frames.", + conn->dac_buffer_queue_minimum_length); conn->session_corrections = 0; // conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment @@ -2835,4 +2836,3 @@ int player_stop(rtsp_conn_info *conn) { return -1; } } - From 43a2813b8c12e96dc0e8d796bf51d857271b652a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 17:30:56 +0000 Subject: [PATCH 023/115] Extensive changes to alsa delay() and play() -- recover from XRUN _after_ sending frames; doesn't work otherwise. Update soxr buffer threshold code. Improve missing libsoxr message. Fix syntax of progress metadata. Stop dropping YouTube metadata -- the indication it was invalid is unsafe. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index f6a1c0f8..6f3d150e 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d43], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d44], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 48043a81ccdc5ce056e3af9a90ef8a261d18a7a4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 18:17:30 +0000 Subject: [PATCH 024/115] Move stall threshold calculation to init from start, duh. --- audio_alsa.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2a453b1e..02e747c6 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -640,6 +640,15 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.050; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem + + + stall_monitor_error_threshold = + (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; + stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form + debug(1,"stall_monitor_error_threshold is %" PRIX64 ", with alsa_maximum_stall_time of %f sec.", stall_monitor_error_threshold, config.alsa_maximum_stall_time); + + stall_monitor_start_time = 0; + stall_monitor_frame_count = 0; // get settings from settings file first, allow them to be overridden by // command line options @@ -1004,10 +1013,6 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; - - stall_monitor_error_threshold = - (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; - stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form } int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { @@ -1041,6 +1046,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { + debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; } } else { @@ -1067,6 +1073,8 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; } + } else { + debug(1,"alsa: can't get device's status."); } return ret; } @@ -1213,7 +1221,7 @@ int untimed_play(void *buf, int samples) { } } } else { - debug(1, "alsa: device status %d faulty for play.", state); + debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, state); frame_index = 0; measurement_data_is_valid = 0; } From 6b42abab72ea38d619c746a5d24127b1c940971f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 20:57:18 +0000 Subject: [PATCH 025/115] Use driver htstamp rather than regular one. Use untimed_delay so that a regualr delay sets the standoff timer for silence. --- audio_alsa.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 02e747c6..2d5bd8c1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1028,7 +1028,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { - snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); + snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &update_timestamp); *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); if (*state == SND_PCM_STATE_DRAINING) @@ -1061,7 +1061,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { ((uint64_t)desired_sample_rate * delta) / 1000000000; snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - + *delay = *delay - frames_played_since_last_interrupt_sized; } } else { // not running, thus no delay information, thus can't check for stall @@ -1079,7 +1079,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { return ret; } -int delay(long *the_delay) { +int untimed_delay(long *the_delay) { // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED // or SND_PCM_STATE_DRAINING // and returns the actual delay if running or 0 if prepared in *the_delay @@ -1271,6 +1271,14 @@ static int play(void *buf, int samples) { return untimed_play(buf, samples); } +int delay(long *the_delay) { + uint64_t time_now = + get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread + most_recent_write_time = time_now; + return untimed_delay(the_delay); +} + + static void stop(void) { // debug(2,"audio_alsa stop called."); // when we want to stop, we want the alsa device @@ -1470,7 +1478,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { if ((most_recent_write_time == 0) || ((present_time > most_recent_write_time) && ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - reply = delay(&buffer_size); + reply = untimed_delay(&buffer_size); if (reply != 0) { buffer_size = 0; char errorstring[1024]; @@ -1491,7 +1499,9 @@ void *alsa_buffer_monitor_thread_code(void *arg) { // frames_of_silence,most_recent_write_time); untimed_play(silence, frames_of_silence); } - } + } else { + // debug(2,"Skipping sending silence"); + } } usleep(sleep_time_ms * 1000); // has a cancellation point in it // pthread_testcancel(); From 0e37f7e78d40bda6369a6bda3c6b0a9f010ab3b0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 4 Jan 2019 21:52:36 +0000 Subject: [PATCH 026/115] Only look for snd_pcm_status_get_driver_htstamp is alsa version 1.1 or later... --- audio_alsa.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/audio_alsa.c b/audio_alsa.c index 2d5bd8c1..ea934719 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1028,7 +1028,13 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { +// must be 1.1 or later to use snd_pcm_status_get_driver_htstamp +#if SND_LIB_MINOR==0 + snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); +#else snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &update_timestamp); +#endif + *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status); if (*state == SND_PCM_STATE_DRAINING) From 67a19bf3c9e0a54515138f1bfeb7205bcf5378d5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 7 Jan 2019 15:59:58 +0000 Subject: [PATCH 027/115] Make the wait time less than the packet interval time to enable SPS to catch up if it gets behind. --- player.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.c b/player.c index a085a636..3bab2428 100644 --- a/player.c +++ b/player.c @@ -1198,8 +1198,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (wait) { uint64_t time_to_wait_for_wakeup_fp = ((uint64_t)1 << 32) / conn->input_rate; // this is time period of one frame - time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets - time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time + time_to_wait_for_wakeup_fp *= 2 * 352; // two full 352-frame packets + time_to_wait_for_wakeup_fp /= 3; // two thirds of a packet time #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp; From 4613ee6bcbc094e7f3e38f7a70684a017e3fe528 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 7 Jan 2019 16:00:58 +0000 Subject: [PATCH 028/115] Make buffer size threashold 100 ms instead of 50 ms. Remove some surplus snd_pcm_prepare calls. --- audio_alsa.c | 51 ++++++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ea934719..b1c7ae7c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -600,14 +600,6 @@ int actual_open_alsa_device(void) { break; } } - - // now open the device - ret = snd_pcm_prepare(alsa_handle); - if (ret) { - warn("alsa: can't prepare device."); - return ret; - } - return 0; } @@ -638,14 +630,14 @@ static int init(int argc, char **argv) { config.audio_backend_latency_offset = 0; config.audio_backend_buffer_desired_length = 0.15; config.audio_backend_buffer_interpolation_threshold_in_seconds = - 0.050; // below this, basic interpolation will be used to save time. + 0.100; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - - + stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form - debug(1,"stall_monitor_error_threshold is %" PRIX64 ", with alsa_maximum_stall_time of %f sec.", stall_monitor_error_threshold, config.alsa_maximum_stall_time); + debug(1, "stall_monitor_error_threshold is %" PRIX64 ", with alsa_maximum_stall_time of %f sec.", + stall_monitor_error_threshold, config.alsa_maximum_stall_time); stall_monitor_start_time = 0; stall_monitor_frame_count = 0; @@ -1029,7 +1021,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) { // must be 1.1 or later to use snd_pcm_status_get_driver_htstamp -#if SND_LIB_MINOR==0 +#if SND_LIB_MINOR == 0 snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp); #else snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &update_timestamp); @@ -1052,7 +1044,11 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); + debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 + ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 + ", stall_monitor_error_threshold %" PRIX64 ".", + time_now_ns, update_timestamp_ns, stall_monitor_start_time, + stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; } } else { @@ -1067,7 +1063,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { ((uint64_t)desired_sample_rate * delta) / 1000000000; snd_pcm_sframes_t frames_played_since_last_interrupt_sized = frames_played_since_last_interrupt; - + *delay = *delay - frames_played_since_last_interrupt_sized; } } else { // not running, thus no delay information, thus can't check for stall @@ -1080,7 +1076,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { measurement_data_is_valid = 0; } } else { - debug(1,"alsa: can't get device's status."); + debug(1, "alsa: can't get device's status."); } return ret; } @@ -1198,36 +1194,30 @@ int untimed_play(void *buf, int samples) { } } } else { + debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); frame_index = 0; measurement_data_is_valid = 0; if (ret == -EPIPE) { /* underrun */ ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); if (ret < 0) { - debug(1, "alsa: failed to recover from SND_PCM_STATE_XRUN with snd_pcm_recover(); " - "trying snd_pcm_prepare()."); - ret = snd_pcm_prepare(alsa_handle); - if (ret < 0) - warn("alsa: can't recover from SND_PCM_STATE_XRUN, snd_pcm_recover() and " - "snd_pcm_prepare() failed: %s.", - snd_strerror(ret)); + warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(ret)); } } else if (ret == -ESTRPIPE) { /* suspended */ while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { sleep(1); /* wait until the suspend flag is released */ if (ret < 0) { - ret = snd_pcm_prepare(alsa_handle); - if (ret < 0) - warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " - "failed: %s.", - snd_strerror(ret)); + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + "failed: %s.", + snd_strerror(ret)); } } } } } } else { - debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, state); + debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, + state); frame_index = 0; measurement_data_is_valid = 0; } @@ -1284,7 +1274,6 @@ int delay(long *the_delay) { return untimed_delay(the_delay); } - static void stop(void) { // debug(2,"audio_alsa stop called."); // when we want to stop, we want the alsa device @@ -1507,7 +1496,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { } } else { // debug(2,"Skipping sending silence"); - } + } } usleep(sleep_time_ms * 1000); // has a cancellation point in it // pthread_testcancel(); From caf06c5aba0925860791e36855545f9f4a4e00a8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 7 Jan 2019 17:34:50 +0000 Subject: [PATCH 029/115] Add the idea of a preflight message to tell the backend that frames will be coming... --- audio.h | 1 + audio_alsa.c | 37 +++++++++++++++++++++++++++---------- 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.h | 3 +++ 11 files changed, 39 insertions(+), 10 deletions(-) diff --git a/audio.h b/audio.h index a4f0f178..b8ecf958 100644 --- a/audio.h +++ b/audio.h @@ -22,6 +22,7 @@ typedef struct { void (*start)(int sample_rate, int sample_format); // block of samples + int (*preflight)(void *buf, int samples); // say you are about to send these samples (before interpolation) int (*play)(void *buf, int samples); void (*stop)(void); diff --git a/audio_alsa.c b/audio_alsa.c index b1c7ae7c..b7a5de8d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -42,6 +42,7 @@ static int init(int argc, char **argv); static void deinit(void); static void start(int i_sample_rate, int i_sample_format); static int play(void *buf, int samples); +int preflight(void *buf, int samples); static void stop(void); static void flush(void); int delay(long *the_delay); @@ -632,6 +633,8 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.100; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem + config.audio_backend_silence_threshold = 0.050; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_scan_interval = 0.005; //check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; @@ -1081,7 +1084,7 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { return ret; } -int untimed_delay(long *the_delay) { +int delay(long *the_delay) { // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED // or SND_PCM_STATE_DRAINING // and returns the actual delay if running or 0 if prepared in *the_delay @@ -1126,7 +1129,7 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { return response; } -int untimed_play(void *buf, int samples) { +int play(void *buf, int samples) { // debug(3,"audio_alsa play called."); int oldState; @@ -1258,6 +1261,16 @@ static void flush(void) { pthread_setcancelstate(oldState, NULL); } +int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { + uint64_t time_now = + get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread + uint64_t standoff_time = 100; // milliseconds + standoff_time = (standoff_time << 32)/1000; + most_recent_write_time = time_now + standoff_time; + return 0; +} + +/* static int play(void *buf, int samples) { uint64_t time_now = get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread @@ -1273,6 +1286,7 @@ int delay(long *the_delay) { most_recent_write_time = time_now; return untimed_delay(the_delay); } +*/ static void stop(void) { // debug(2,"audio_alsa stop called."); @@ -1451,12 +1465,14 @@ void *alsa_buffer_monitor_thread_code(void *arg) { debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); - int sleep_time_ms = 30; + int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); + long buffer_size_threshold = (long)(config.audio_backend_silence_threshold * desired_sample_rate); + uint64_t sleep_time_in_fp = sleep_time_ms; sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; + int frames_of_silence = (desired_sample_rate * sleep_time_ms * 4) / 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); @@ -1470,10 +1486,11 @@ void *alsa_buffer_monitor_thread_code(void *arg) { if (config.keep_dac_busy != 0) { uint64_t present_time = get_absolute_time_in_fp(); - if ((most_recent_write_time == 0) || - ((present_time > most_recent_write_time) && - ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - reply = untimed_delay(&buffer_size); + if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { + +// ((present_time > most_recent_write_time) && +// ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { + reply = delay(&buffer_size); if (reply != 0) { buffer_size = 0; char errorstring[1024]; @@ -1481,7 +1498,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, (char *)errorstring); } - if (buffer_size < frames_of_silence) { + if (buffer_size < buffer_size_threshold) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) use_dither = 1; @@ -1492,7 +1509,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { dither_random_number_store); // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", // frames_of_silence,most_recent_write_time); - untimed_play(silence, frames_of_silence); + play(silence, frames_of_silence); } } else { // debug(2,"Skipping sending silence"); diff --git a/audio_ao.c b/audio_ao.c index 42147bab..30f80d8d 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -134,6 +134,7 @@ audio_output audio_ao = {.name = "ao", .is_running = NULL, .flush = NULL, .delay = NULL, + .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_dummy.c b/audio_dummy.c index d8dea87f..69518cd0 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -65,6 +65,7 @@ audio_output audio_dummy = {.name = "dummy", .is_running = NULL, .flush = NULL, .delay = NULL, + .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_jack.c b/audio_jack.c index 11ae6072..90f08ac4 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -67,6 +67,7 @@ audio_output audio_jack = {.name = "jack", .is_running = &jack_is_running, .flush = &jack_flush, .delay = &jack_delay, + .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_pa.c b/audio_pa.c index cd321bd9..4ee20c75 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -312,6 +312,7 @@ audio_output audio_pa = {.name = "pa", .flush = &flush, .delay = &pa_delay, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_pipe.c b/audio_pipe.c index 89a09a0c..0d4a42fd 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -139,6 +139,7 @@ audio_output audio_pipe = {.name = "pipe", .flush = NULL, .delay = NULL, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_sndio.c b/audio_sndio.c index f22ed5af..e106b15d 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -49,6 +49,7 @@ audio_output audio_sndio = {.name = "sndio", .flush = &flush, .delay = &delay, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_soundio.c b/audio_soundio.c index 7d3b48ad..0449895b 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -221,6 +221,7 @@ audio_output audio_soundio = {.name = "soundio", .flush = &flush, .delay = NULL, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = ¶meters, .mute = NULL}; diff --git a/audio_stdout.c b/audio_stdout.c index 0438b1af..37d6f781 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -86,6 +86,7 @@ audio_output audio_stdout = {.name = "stdout", .flush = NULL, .delay = NULL, .play = &play, + .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/common.h b/common.h index 35996fef..6f07f6d3 100644 --- a/common.h +++ b/common.h @@ -171,6 +171,9 @@ typedef struct { double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation // will not occur -- it'll be // basic interpolation instead. + double audio_backend_silence_threshold; // below this, silence will be added to the output buffer + double audio_backend_silence_scan_interval; // check the threshold this often + double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. From 1b3d64ce85845c21ef40335d8803dbf9885369c1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 12:59:04 +0000 Subject: [PATCH 030/115] Quieten a debug message. --- rtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtp.c b/rtp.c index 07f19169..157bc77a 100644 --- a/rtp.c +++ b/rtp.c @@ -1098,7 +1098,7 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con double calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp; if (((calculated_frame_rate / conn->input_rate) > 1.002) || ((calculated_frame_rate / conn->input_rate) < 0.998)) { - debug(2, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); + debug(3, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate); result = 1; } else { *frames = local_frames; From c3668b3fc023b5d8f8026dbe5f067a784e9b366c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 13:00:00 +0000 Subject: [PATCH 031/115] INcrease the delay between reading chunks of the RTSP message, may it less likely to overwhelm the CPU. --- rtsp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index 74d1c54f..a90c5aa0 100644 --- a/rtsp.c +++ b/rtsp.c @@ -674,7 +674,7 @@ do { size_t read_chunk = msg_size - inbuf; if (read_chunk > max_read_chunk) read_chunk = max_read_chunk; - usleep(40000); // wait about 40 milliseconds between reads of up to about 64 kB + usleep(80000); // wait about 80 milliseconds between reads of up to about 64 kB nread = read(conn->fd, buf + inbuf, read_chunk); if (!nread) { reply = rtsp_read_request_response_error; From 2e3be271fdc26a0138d78848b36b9b2156e22a73 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 13:01:22 +0000 Subject: [PATCH 032/115] Preflight all audio material playing (not lead-in silences though...) --- player.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/player.c b/player.c index 3bab2428..724b5cab 100644 --- a/player.c +++ b/player.c @@ -1418,6 +1418,9 @@ void player_thread_cleanup_handler(void *arg) { debug(3, "Connection %d: player thread main loop exit via player_thread_cleanup_handler.", conn->connection_number); + if (config.output->stop) + config.output->stop(); + if (config.statistics_requested) { int rawSeconds = (int)difftime(time(NULL), conn->playstart); int elapsedHours = rawSeconds / 3600; @@ -1470,8 +1473,6 @@ void player_thread_cleanup_handler(void *arg) { } free_audio_buffers(conn); terminate_decoders(conn); - if (config.output->stop) - config.output->stop(); clear_reference_timestamp(conn); conn->rtp_running = 0; @@ -2219,7 +2220,10 @@ void *player_thread_func(void *arg) { } } - if ((current_delay < conn->dac_buffer_queue_minimum_length) || + if (config.output->preflight) + config.output->preflight(conn->outbuf,play_samples); + + if ((current_delay < conn->dac_buffer_queue_minimum_length) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, From 56f4b124bd5110dadd00a8bfe7adab28cb82f425 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 13:06:46 +0000 Subject: [PATCH 033/115] Adjust thresholds and timings to suit pi zero. --- audio_alsa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index b7a5de8d..4ae2d029 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -633,8 +633,8 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.100; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.050; //start sending silent frames if the delay goes below this time - config.audio_backend_silence_scan_interval = 0.005; //check silence threshold this often + config.audio_backend_silence_threshold = 0.040; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_scan_interval = 0.003; //check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; @@ -1264,7 +1264,7 @@ static void flush(void) { int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { uint64_t time_now = get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - uint64_t standoff_time = 100; // milliseconds + uint64_t standoff_time = 60; // milliseconds standoff_time = (standoff_time << 32)/1000; most_recent_write_time = time_now + standoff_time; return 0; @@ -1472,7 +1472,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - int frames_of_silence = (desired_sample_rate * sleep_time_ms * 4) / 1000; + int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 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); From e9623faa44b2e652e2e94665c7128ee1dfd6cd02 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 8 Jan 2019 17:28:38 +0000 Subject: [PATCH 034/115] Go for a 0.250 second buffer length. --- audio_alsa.c | 11 ++++++----- player.c | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 4ae2d029..b7b3e7e1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -629,12 +629,12 @@ static int init(int argc, char **argv) { 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.audio_backend_buffer_desired_length = 0.25; config.audio_backend_buffer_interpolation_threshold_in_seconds = - 0.100; // below this, basic interpolation will be used to save time. + 0.175; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.040; //start sending silent frames if the delay goes below this time - config.audio_backend_silence_scan_interval = 0.003; //check silence threshold this often + config.audio_backend_silence_threshold = 0.60; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_scan_interval = 0.004; //check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; @@ -1472,7 +1472,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; + // int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; + int frames_of_silence = 1024; 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); diff --git a/player.c b/player.c index 724b5cab..ac2226d9 100644 --- a/player.c +++ b/player.c @@ -4,7 +4,7 @@ * All rights reserved. * * Modifications for audio synchronisation - * and related work, copyright (c) Mike Brady 2014 -- 2018 + * and related work, copyright (c) Mike Brady 2014 -- 2019 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -624,6 +624,16 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume, int dither, rtsp_conn_info *conn) { + /* + { + static int old_volume = 0; + if (volume != old_volume) { + debug(1,"Volume is now %d.",volume); + old_volume = volume; + } + } + */ + int64_t hyper_sample = sample; int result = 0; @@ -1554,7 +1564,7 @@ void *player_thread_func(void *arg) { conn->dac_buffer_queue_minimum_length = (int64_t)( config.audio_backend_buffer_interpolation_threshold_in_seconds * config.output_rate); - debug(1, "dac_buffer_queue_minimum_length is %" PRId64 " frames.", + debug(3, "dac_buffer_queue_minimum_length is %" PRId64 " frames.", conn->dac_buffer_queue_minimum_length); conn->session_corrections = 0; @@ -2220,9 +2230,6 @@ void *player_thread_func(void *arg) { } } - if (config.output->preflight) - config.output->preflight(conn->outbuf,play_samples); - if ((current_delay < conn->dac_buffer_queue_minimum_length) || (config.packet_stuffing == ST_basic)) { play_samples = From 34d6d89707e2cab7d08caa55e4d5722db1570a50 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 9 Jan 2019 10:09:29 +0000 Subject: [PATCH 035/115] Don't use full flush to purge frames after an overrun -- flush will reset the buffers as well. Fix a potential bug if a flush actually occurs. --- player.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/player.c b/player.c index ac2226d9..64aa3e56 100644 --- a/player.c +++ b/player.c @@ -856,8 +856,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { curframe = NULL; // this will be returned and will cause the loop to go around again conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; - } - if ((conn->flush_rtp_timestamp != 0) && + } else if ((conn->flush_rtp_timestamp != 0) && (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > conn->input_rate / 5) && (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < @@ -2093,8 +2092,12 @@ void *player_thread_func(void *arg) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio; uint32_t frames_to_drop_sized = local_frames_to_drop; - do_flush(inframe->given_timestamp + frames_to_drop_sized, - conn); // this will reset_input_flow_metrics anyway + + debug_mutex_lock(&conn->flush_mutex, 1000, 1); + conn->flush_rtp_timestamp = inframe->given_timestamp + frames_to_drop_sized; // flush all packets up to (and including?) this + reset_input_flow_metrics(conn); + debug_mutex_unlock(&conn->flush_mutex, 3); + } else if ((sync_error < 0) && ((-sync_error) > filler_length)) { debug(2, "Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32 From 2a9d46afedb4539b2c273052cf2046b9236746df Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 9 Jan 2019 10:10:04 +0000 Subject: [PATCH 036/115] backend silence threshold should be 0.060 not 0.6! --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index b7b3e7e1..3411c160 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -633,7 +633,7 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.175; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.60; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_threshold = 0.060; //start sending silent frames if the delay goes below this time config.audio_backend_silence_scan_interval = 0.004; //check silence threshold this often stall_monitor_error_threshold = From c958d506b1791cb2a745d16ec1811996b925fc2d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 9 Jan 2019 14:15:05 +0000 Subject: [PATCH 037/115] reduce thresholds towards a buffer time of 0.2 seconds rather than the 0.25 seconds before. --- audio_alsa.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 3411c160..2f491226 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -629,17 +629,17 @@ static int init(int argc, char **argv) { 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.25; + config.audio_backend_buffer_desired_length = 0.200; config.audio_backend_buffer_interpolation_threshold_in_seconds = - 0.175; // below this, basic interpolation will be used to save time. + 0.120; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.060; //start sending silent frames if the delay goes below this time + config.audio_backend_silence_threshold = 0.040; //start sending silent frames if the delay goes below this time config.audio_backend_silence_scan_interval = 0.004; //check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form - debug(1, "stall_monitor_error_threshold is %" PRIX64 ", with alsa_maximum_stall_time of %f sec.", + debug(1, "stall_monitor_error_threshold is 0x%" PRIx64 ", with alsa_maximum_stall_time of %f sec.", stall_monitor_error_threshold, config.alsa_maximum_stall_time); stall_monitor_start_time = 0; From 9f8b8a25c947868a343f11412d42ac88f8b31dba Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:09:52 +0000 Subject: [PATCH 038/115] add actual waiting time for a failing timed mutex. --- common.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/common.c b/common.c index aa38007f..ff8da65f 100644 --- a/common.c +++ b/common.c @@ -1160,15 +1160,17 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, timeoutTime.tv_sec = time_then; timeoutTime.tv_nsec = time_then_nsec; - + int64_t start_time = get_absolute_time_in_fp(); int r = pthread_mutex_timedlock(mutex, &timeoutTime); + int64_t et = get_absolute_time_in_fp() - start_time; if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) { + et = (et * 1000000) >> 32; // microseconds char errstr[1000]; if (r == ETIMEDOUT) debug(debuglevel, - "waiting for a mutex, maximum expected time of %d microseconds exceeded \"%s\".", - dally_time, debugmessage); + "timed out waiting for a mutex, having waiting %f seconds, with a maximum waiting time of %d microseconds. \"%s\".", + (1.0 * et) / 1000000, dally_time, debugmessage); else debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r, strerror_r(r, errstr, sizeof(errstr)), debugmessage); From 8e72574a579322d976e77c17f73d06864e6bdf1d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:10:53 +0000 Subject: [PATCH 039/115] Add some diagnostics, shorten timeouts and set so_linger of zero whenever a write or read error occurs. --- dacp.c | 53 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/dacp.c b/dacp.c index 04821650..f1c91e48 100644 --- a/dacp.c +++ b/dacp.c @@ -127,9 +127,9 @@ void addrinfo_cleanup(void *arg) { } void mutex_lock_cleanup(void *arg) { - // debug(1, "mutex lock cleanup called."); pthread_mutex_t *m = (pthread_mutex_t *)arg; - pthread_mutex_unlock(m); + if (pthread_mutex_unlock(m)) + debug(1,"Error releasing mutex."); } void connect_cleanup(void *arg) { @@ -193,6 +193,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires)); response.code = 498; // Bad Address information for the DACP server } else { + uint64_t start_time = get_absolute_time_in_fp(); pthread_cleanup_push(addrinfo_cleanup, (void *)&res); // only do this one at a time -- not sure it is necessary, but better safe than sorry @@ -200,7 +201,6 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock); if (mutex_reply == 0) { pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock); - // debug(1,"dacp_conversation_lock acquired for command \"%s\".",command); // make a socket: sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); @@ -213,8 +213,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(2, "dacp_send_command: open socket %d.",sockfd); struct timeval tv; - tv.tv_sec = 2; - tv.tv_usec = 0; + tv.tv_sec = 0; + tv.tv_usec = 80000; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "dacp_send_command: error %d setting receive timeout.", errno); if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) @@ -223,7 +223,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // connect! // debug(1, "DACP socket created."); if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) { - // debug(2, "dacp_send_command: connect failed with errno %d.", errno); + debug(3, "dacp_send_command: connect failed with errno %d.", errno); response.code = 496; // Can't connect to the DACP server } else { // debug(1,"DACP connect succeeded."); @@ -233,13 +233,26 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id); // Send command - // debug(3,"dacp_send_command: connect message: \"%s\".",message); - if (send(sockfd, message, strlen(message), 0) != (ssize_t)strlen(message)) { - debug(1, "dacp_send_command: send failed."); + debug(3,"dacp_send_command: \"%s\".",command); + ssize_t wresp = send(sockfd, message, strlen(message), 0); + if (wresp == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring); + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, "Could not set the dacp socket to abort due to a write error on closing."); + } + if (wresp != (ssize_t)strlen(message)) { + // debug(1, "dacp_send_command: send failed."); response.code = 493; // Client failed to send a message } else { + response.body = malloc(2048); // it can resize this if necessary response.malloced_size = 2048; pthread_cleanup_push(malloc_cleanup, response.body); @@ -256,11 +269,21 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { const char *data = buffer; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "dacp_send_command: error %d setting receive timeout.", errno); - int ndata = recv(sockfd, buffer, sizeof(buffer), 0); + ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { - // debug(1, "dacp_send_command -- error receiving response for command \"%s\".", - // command); + if (ndata == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,(char *)errorstring); + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, "Could not set the dacp socket to abort due to a read error on closing."); + } + free(response.body); response.body = NULL; response.malloced_size = 0; @@ -308,6 +331,9 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { } pthread_cleanup_pop(1); // this should free the addrinfo // freeaddrinfo(res); + uint64_t et = get_absolute_time_in_fp() - start_time; + et = (et * 1000000) >> 32; // microseconds + debug(2,"dacp_send_command: %f seconds, response code %d, command \"%s\".",(1.0 * et) / 1000000, response.code, command); } *body = response.body; *bodysize = response.size; @@ -829,6 +855,8 @@ void dacp_monitor_start() { rc = pthread_mutex_init(&dacp_conversation_lock, &mta); if (rc) debug(1, "Error creating the DACP Conversation Lock Mutex Init"); + else + debug(1, "DACP Conversation Lock Mutex Init"); rc = pthread_mutexattr_destroy(&mta); if (rc) @@ -864,6 +892,7 @@ void dacp_monitor_stop() { pthread_cancel(dacp_monitor_thread); pthread_join(dacp_monitor_thread, NULL); pthread_mutex_destroy(&dacp_server_information_lock); + debug(1, "DACP Conversation Lock Mutex Destroyed"); pthread_mutex_destroy(&dacp_conversation_lock); } From 1c40176468fb62a58456101db364044b321a526d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:11:56 +0000 Subject: [PATCH 040/115] CLean up timings, clean up DACP interface network timings and error behaviours. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6f3d150e..4b1f2915 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d44], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d45], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 8f20e5808ba8ff4ca9ffd5fab0dc794ef082c453 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:12:57 +0000 Subject: [PATCH 041/115] clang format --- common.c | 8 ++++---- dacp.c | 60 +++++++++++++++++++++++++++++--------------------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/common.c b/common.c index ff8da65f..df3c2ca6 100644 --- a/common.c +++ b/common.c @@ -1160,16 +1160,16 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, timeoutTime.tv_sec = time_then; timeoutTime.tv_nsec = time_then_nsec; - int64_t start_time = get_absolute_time_in_fp(); + int64_t start_time = get_absolute_time_in_fp(); int r = pthread_mutex_timedlock(mutex, &timeoutTime); int64_t et = get_absolute_time_in_fp() - start_time; if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) { - et = (et * 1000000) >> 32; // microseconds + et = (et * 1000000) >> 32; // microseconds char errstr[1000]; if (r == ETIMEDOUT) - debug(debuglevel, - "timed out waiting for a mutex, having waiting %f seconds, with a maximum waiting time of %d microseconds. \"%s\".", + debug(debuglevel, "timed out waiting for a mutex, having waiting %f seconds, with a maximum " + "waiting time of %d microseconds. \"%s\".", (1.0 * et) / 1000000, dally_time, debugmessage); else debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r, diff --git a/dacp.c b/dacp.c index f1c91e48..c0c24421 100644 --- a/dacp.c +++ b/dacp.c @@ -129,7 +129,7 @@ void addrinfo_cleanup(void *arg) { void mutex_lock_cleanup(void *arg) { pthread_mutex_t *m = (pthread_mutex_t *)arg; if (pthread_mutex_unlock(m)) - debug(1,"Error releasing mutex."); + debug(1, "Error releasing mutex."); } void connect_cleanup(void *arg) { @@ -233,26 +233,25 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id); // Send command - debug(3,"dacp_send_command: \"%s\".",command); + debug(3, "dacp_send_command: \"%s\".", command); ssize_t wresp = send(sockfd, message, strlen(message), 0); if (wresp == -1) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring); - struct linger so_linger; - so_linger.l_onoff = 1; // "true" - so_linger.l_linger = 0; - int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); - if (err) - debug(1, "Could not set the dacp socket to abort due to a write error on closing."); - } + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring); + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, "Could not set the dacp socket to abort due to a write error on closing."); + } if (wresp != (ssize_t)strlen(message)) { // debug(1, "dacp_send_command: send failed."); response.code = 493; // Client failed to send a message } else { - response.body = malloc(2048); // it can resize this if necessary response.malloced_size = 2048; pthread_cleanup_push(malloc_cleanup, response.body); @@ -272,18 +271,20 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { - if (ndata == -1) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,(char *)errorstring); - struct linger so_linger; - so_linger.l_onoff = 1; // "true" - so_linger.l_linger = 0; - int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); - if (err) - debug(1, "Could not set the dacp socket to abort due to a read error on closing."); - } - + if (ndata == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno, + (char *)errorstring); + struct linger so_linger; + so_linger.l_onoff = 1; // "true" + so_linger.l_linger = 0; + int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); + if (err) + debug(1, + "Could not set the dacp socket to abort due to a read error on closing."); + } + free(response.body); response.body = NULL; response.malloced_size = 0; @@ -331,9 +332,10 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { } pthread_cleanup_pop(1); // this should free the addrinfo // freeaddrinfo(res); - uint64_t et = get_absolute_time_in_fp() - start_time; - et = (et * 1000000) >> 32; // microseconds - debug(2,"dacp_send_command: %f seconds, response code %d, command \"%s\".",(1.0 * et) / 1000000, response.code, command); + uint64_t et = get_absolute_time_in_fp() - start_time; + et = (et * 1000000) >> 32; // microseconds + debug(2, "dacp_send_command: %f seconds, response code %d, command \"%s\".", + (1.0 * et) / 1000000, response.code, command); } *body = response.body; *bodysize = response.size; @@ -856,7 +858,7 @@ void dacp_monitor_start() { if (rc) debug(1, "Error creating the DACP Conversation Lock Mutex Init"); else - debug(1, "DACP Conversation Lock Mutex Init"); + debug(1, "DACP Conversation Lock Mutex Init"); rc = pthread_mutexattr_destroy(&mta); if (rc) From fbd8377519466542653c1856a326871da4b5e01d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 16:57:40 +0000 Subject: [PATCH 042/115] Always add dither if no hardware mixer and volume control is not ignored. --- audio_alsa.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 2f491226..a1b81e4c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1500,8 +1500,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { (char *)errorstring); } if (buffer_size < buffer_size_threshold) { - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && - (config.airplay_volume != 0.0)) + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0)) use_dither = 1; else use_dither = 0; From 2ca6e42b626ad22f27fe319e0388ce8de2953325 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 10 Jan 2019 17:00:10 +0000 Subject: [PATCH 043/115] Do not add dither if at full volume or ignoring volume control --- player.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/player.c b/player.c index 64aa3e56..36461c45 100644 --- a/player.c +++ b/player.c @@ -625,15 +625,15 @@ int32_t rand_in_range(int32_t exclusive_range_limit) { static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume, int dither, rtsp_conn_info *conn) { /* - { - static int old_volume = 0; - if (volume != old_volume) { - debug(1,"Volume is now %d.",volume); - old_volume = volume; - } + { + static int old_volume = 0; + if (volume != old_volume) { + debug(1,"Volume is now %d.",volume); + old_volume = volume; + } } */ - + int64_t hyper_sample = sample; int result = 0; @@ -857,10 +857,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; } else if ((conn->flush_rtp_timestamp != 0) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > - conn->input_rate / 5) && - (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < - conn->input_rate * 10)) { + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) > + conn->input_rate / 5) && + (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) < + conn->input_rate * 10)) { debug(3, "Dropping flush request in buffer_get_frame"); conn->flush_rtp_timestamp = 0; } @@ -1827,8 +1827,9 @@ void *player_thread_func(void *arg) { } } else { - if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) || - (config.playback_mode == ST_mono)) + if (((config.output->parameters == NULL) && (config.ignore_volume_control == 0) && + (config.airplay_volume != 0.0)) || + (conn->input_bit_depth > output_bit_depth) || (config.playback_mode == ST_mono)) conn->enable_dither = 1; else conn->enable_dither = 0; @@ -2092,9 +2093,11 @@ void *player_thread_func(void *arg) { debug(2, "Large positive sync error: %" PRId64 ".", sync_error); int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio; uint32_t frames_to_drop_sized = local_frames_to_drop; - + debug_mutex_lock(&conn->flush_mutex, 1000, 1); - conn->flush_rtp_timestamp = inframe->given_timestamp + frames_to_drop_sized; // flush all packets up to (and including?) this + conn->flush_rtp_timestamp = + inframe->given_timestamp + + frames_to_drop_sized; // flush all packets up to (and including?) this reset_input_flow_metrics(conn); debug_mutex_unlock(&conn->flush_mutex, 3); @@ -2233,7 +2236,7 @@ void *player_thread_func(void *arg) { } } - if ((current_delay < conn->dac_buffer_queue_minimum_length) || + if ((current_delay < conn->dac_buffer_queue_minimum_length) || (config.packet_stuffing == ST_basic)) { play_samples = stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format, From b9d78c8af64f5e5af4a92fba3e616e3573c20142 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 11 Jan 2019 10:42:17 +0000 Subject: [PATCH 044/115] Avoid wrapping back to the first three UDP ports when assigning ports using nextFreeUDPPort because the call resetFreeUDPPorts, called when not interrupting an existing session, will assign the first three ports without checking (or knowing) if they were recently in use. --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index df3c2ca6..05edae98 100644 --- a/common.c +++ b/common.c @@ -114,7 +114,7 @@ uint16_t nextFreeUDPPort() { if (UDPPortIndex == 0) UDPPortIndex = config.udp_port_base; else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1)) - UDPPortIndex = config.udp_port_base; + UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can be assigned by resetFreeUDPPort without checking else UDPPortIndex++; return UDPPortIndex; From a74261bbd35e2821e5ef4f22496ff6dafff4181f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 11 Jan 2019 14:48:00 +0000 Subject: [PATCH 045/115] Add a D-Bus property "DisableStandby" to turn standby oo or off. --- dbus-service.c | 37 +++++++++++++++++++++++++++++-------- org.gnome.ShairportSync.xml | 1 + 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index c163ddb9..9ae1d0aa 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -378,14 +378,27 @@ gboolean notify_verbosity_callback(ShairportSyncDiagnostics *skeleton, return TRUE; } +gboolean notify_disable_standby_callback(ShairportSync *skeleton, + __attribute__((unused)) gpointer user_data) { + // debug(1, "\"notify_disable_standby_callback\" called."); + if (shairport_sync_get_disable_standby(skeleton)) { + debug(1, ">> activating disable standby"); + config.keep_dac_busy = 1; + } else { + debug(1, ">> deactivating disable standby"); + config.keep_dac_busy = 0; + } + return TRUE; +} + gboolean notify_loudness_filter_active_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { - debug(1, "\"notify_loudness_filter_active_callback\" called."); + // debug(1, "\"notify_loudness_filter_active_callback\" called."); if (shairport_sync_get_loudness_filter_active(skeleton)) { - debug(1, "activating loudness filter"); + debug(1, ">> activating loudness filter"); config.loudness = 1; } else { - debug(1, "deactivating loudness filter"); + debug(1, ">> deactivating loudness filter"); config.loudness = 0; } return TRUE; @@ -395,10 +408,10 @@ gboolean notify_loudness_threshold_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { gdouble th = shairport_sync_get_loudness_threshold(skeleton); if ((th <= 0.0) && (th >= -100.0)) { - debug(1, "Setting loudness threshhold to %f.", th); + debug(1, ">> setting loudness threshhold to %f.", th); config.loudness_reference_volume_db = th; } else { - debug(1, "Invalid loudness threshhold: %f. Ignored.", th); + debug(1, ">> invalid loudness threshhold: %f. Ignored.", th); shairport_sync_set_loudness_threshold(skeleton, config.loudness_reference_volume_db); } return TRUE; @@ -408,10 +421,10 @@ gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { gdouble dt = shairport_sync_get_drift_tolerance(skeleton); if ((dt >= 0.0) && (dt <= 2.0)) { - debug(1, "Setting drift tolerance to %f.", dt); + debug(1, ">> setting drift tolerance to %f seconds", dt); config.tolerance = dt; } else { - debug(1, "Invalid drift tolerance: %f. Ignored.", dt); + debug(1, ">> invalid drift tolerance: %f seconds. Ignored.", dt); shairport_sync_set_drift_tolerance(skeleton, config.tolerance); } return TRUE; @@ -611,7 +624,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_alacdecoder_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", G_CALLBACK(notify_volume_control_profile_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active", + g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", + G_CALLBACK(notify_disable_standby_callback), NULL); +g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active", G_CALLBACK(notify_loudness_filter_active_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold", G_CALLBACK(notify_loudness_threshold_callback), NULL); @@ -710,6 +725,12 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name else shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "flat"); + if (config.keep_dac_busy == 0) { + shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); + } else { + shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); + } + if (config.loudness == 0) { shairport_sync_set_loudness_filter_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); } else { diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index 44d90a27..279e4f0a 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -2,6 +2,7 @@ + From ca72645cb84999895a834e420e598939251ecc11 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 12 Jan 2019 19:17:43 +0000 Subject: [PATCH 046/115] If a message is dropped because the pc queue is busy, free any data that might be associated with it as well as just the rtsp message. --- rtsp.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rtsp.c b/rtsp.c index a90c5aa0..011d2ae3 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1478,10 +1478,13 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rts msg_retain(carrier); pack.carrier = carrier; int rc = pc_queue_add_item(&metadata_queue, &pack, block); - if ((rc == EBUSY) && (carrier)) - msg_free(carrier); - if (rc == EBUSY) - warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code); + if (rc == EBUSY) { + if (carrier) + msg_free(carrier); + else if (data) + free(data); + warn("Metadata queue is busy, discarding message of type 0x%08X, code 0x%08X.", type, code); + } return rc; } From 9fbdc062b404f48a369a435a92399a369ee423a3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 12 Jan 2019 21:44:52 +0000 Subject: [PATCH 047/115] Change how rtsp_messages are memory managed. Add some diagnostics. --- rtsp.c | 87 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/rtsp.c b/rtsp.c index 011d2ae3..d869c85b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -119,7 +119,10 @@ typedef struct { } pc_queue; // producer-consumer queue #endif +static int msg_indexes = 1; + typedef struct { + int index_number; uint32_t referenceCount; // we might start using this... unsigned int nheaders; char *name[16]; @@ -401,16 +404,17 @@ static char *nextline(char *in, int inbuf) { } void msg_retain(rtsp_message *msg) { - if (msg) { int rc = pthread_mutex_lock(&reference_counter_lock); if (rc) debug(1, "Error %d locking reference counter lock"); + if (msg > (rtsp_message *) 0x00010000) { msg->referenceCount++; + debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) debug(1, "Error %d unlocking reference counter lock"); } else { - debug(1, "null rtsp_message pointer passed to retain"); + debug(1, "invalid rtsp_message pointer %d passed to retain", (int) msg); } } @@ -419,9 +423,11 @@ rtsp_message *msg_init(void) { if (msg) { memset(msg, 0, sizeof(rtsp_message)); msg->referenceCount = 1; // from now on, any access to this must be protected with the lock + msg->index_number = msg_indexes++; } else { - die("can not allocate memory for an rtsp_message."); + die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); } + debug(1,"msg_init -- create item %d.", msg->index_number); return msg; } @@ -476,6 +482,37 @@ static void debug_print_msg_content(int level, rtsp_message *msg) { } */ +void msg_free(rtsp_message **msgh) { + debug_mutex_lock(&reference_counter_lock, 1000, 0); + if (*msgh > (rtsp_message *)0x00010000) { + rtsp_message *msg = *msgh; + msg->referenceCount--; + if (msg->referenceCount == 0) { + unsigned int i; + for (i = 0; i < msg->nheaders; i++) { + free(msg->name[i]); + free(msg->value[i]); + } + if (msg->content) + free(msg->content); + debug(1,"msg_free item %d -- free.",msg->index_number); + *msgh = (rtsp_message *)(msg->index_number & 0xFFFF); // put the index number of the freed message in here + free(msg); + } else { + debug(1,"msg_free item %d -- decrement reference to %d.",msg->index_number,msg->referenceCount); + } + + // else { + // debug(1,"rtsp_message reference count non-zero: + // %d!",msg->referenceCount); + //} + } else if (*msgh != NULL) { + debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message %d.",(int)*msgh); + } + debug_mutex_unlock(&reference_counter_lock, 0); +} + +/* void msg_free(rtsp_message *msg) { if (msg) { @@ -499,6 +536,7 @@ void msg_free(rtsp_message *msg) { debug(1, "null rtsp_message pointer passed to msg_free()"); } } +*/ int msg_handle_line(rtsp_message **pmsg, char *line) { rtsp_message *msg = *pmsg; @@ -550,17 +588,22 @@ int msg_handle_line(rtsp_message **pmsg, char *line) { } fail: - *pmsg = NULL; - msg_free(msg); + msg_free(pmsg); return 0; } enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { + + *the_packet = NULL; // need this for erro handling + enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; char *buf = malloc(buflen + 1); // add a NUL at the end - - rtsp_message *msg = NULL; + if (!buf) { + warn("rtsp_read_request: can't get a buffer."); + reply = rtsp_read_request_response_error; + goto shutdown; + } ssize_t nread; ssize_t inbuf = 0; @@ -607,9 +650,9 @@ do { char *next; while (msg_size < 0 && (next = nextline(buf, inbuf))) { - msg_size = msg_handle_line(&msg, buf); + msg_size = msg_handle_line(the_packet, buf); - if (!msg) { + if (!(*the_packet)) { warn("no RTSP header received"); reply = rtsp_read_request_response_bad_packet; goto shutdown; @@ -690,6 +733,7 @@ do { inbuf += nread; } + rtsp_message *msg = *the_packet; msg->contentlength = inbuf; msg->content = buf; char *jp = inbuf + buf; @@ -698,13 +742,8 @@ do { return reply; shutdown: - if (msg) { - msg_free(msg); // which will free the content and everything else - } - // in case the message wasn't formed or wasn't fully initialised - if ((msg && (msg->content == NULL)) || (!msg)) - free(buf); - *the_packet = NULL; + msg_free(the_packet); + free(buf); return reply; } @@ -1396,7 +1435,7 @@ void metadata_pack_cleanup_function(void *arg) { // debug(1, "metadata_pack_cleanup_function called"); metadata_package *pack = (metadata_package *)arg; if (pack->carrier) - msg_free(pack->carrier); // release the message + msg_free(&pack->carrier); // release the message else if (pack->data) free(pack->data); } @@ -1474,13 +1513,13 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rts pack.code = code; pack.data = data; pack.length = length; - if (carrier) - msg_retain(carrier); pack.carrier = carrier; + if (pack.carrier) + msg_retain(pack.carrier); int rc = pc_queue_add_item(&metadata_queue, &pack, block); if (rc == EBUSY) { - if (carrier) - msg_free(carrier); + if (pack.carrier) + msg_free(&pack.carrier); else if (data) free(data); warn("Metadata queue is busy, discarding message of type 0x%08X, code 0x%08X.", type, code); @@ -2171,7 +2210,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { void msg_cleanup_function(void *arg) { // debug(3, "msg_cleanup_function called."); - msg_free((rtsp_message *)arg); + msg_free((rtsp_message **)arg); } static void *rtsp_conversation_thread_func(void *pconn) { @@ -2217,9 +2256,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { int debug_level = 3; // for printing the request and response reply = rtsp_read_request(conn, &req); if (reply == rtsp_read_request_response_ok) { - pthread_cleanup_push(msg_cleanup_function, (void *)req); + pthread_cleanup_push(msg_cleanup_function, (void *)&req); resp = msg_init(); - pthread_cleanup_push(msg_cleanup_function, (void *)resp); + pthread_cleanup_push(msg_cleanup_function, (void *)&resp); resp->respcode = 400; if (strcmp(req->method, "OPTIONS") != From 6a27712e94e7574f9a04dfb94b5694b9f43b4c7e Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 13 Jan 2019 10:00:11 +0000 Subject: [PATCH 048/115] Quieten a debug message. --- dacp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dacp.c b/dacp.c index c0c24421..d82f0594 100644 --- a/dacp.c +++ b/dacp.c @@ -334,7 +334,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // freeaddrinfo(res); uint64_t et = get_absolute_time_in_fp() - start_time; et = (et * 1000000) >> 32; // microseconds - debug(2, "dacp_send_command: %f seconds, response code %d, command \"%s\".", + debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".", (1.0 * et) / 1000000, response.code, command); } *body = response.body; From 93cae8a5d1bbe80532892405a5e6d4e8e5888bc9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 13 Jan 2019 10:17:34 +0000 Subject: [PATCH 049/115] Quieten the msg_ debug messages. --- rtsp.c | 50 +++++++++++--------------------------------------- 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/rtsp.c b/rtsp.c index d869c85b..d44ae0f0 100644 --- a/rtsp.c +++ b/rtsp.c @@ -3,7 +3,7 @@ * Copyright (c) James Laird 2013 * Modifications associated with audio synchronization, mutithreading and - * metadata handling copyright (c) Mike Brady 2014-2018 + * metadata handling copyright (c) Mike Brady 2014-2019 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -409,12 +409,12 @@ void msg_retain(rtsp_message *msg) { debug(1, "Error %d locking reference counter lock"); if (msg > (rtsp_message *) 0x00010000) { msg->referenceCount++; - debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); + // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) debug(1, "Error %d unlocking reference counter lock"); } else { - debug(1, "invalid rtsp_message pointer %d passed to retain", (int) msg); + debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t) msg); } } @@ -427,7 +427,7 @@ rtsp_message *msg_init(void) { } else { die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); } - debug(1,"msg_init -- create item %d.", msg->index_number); + // debug(1,"msg_init -- create item %d.", msg->index_number); return msg; } @@ -495,49 +495,21 @@ void msg_free(rtsp_message **msgh) { } if (msg->content) free(msg->content); - debug(1,"msg_free item %d -- free.",msg->index_number); - *msgh = (rtsp_message *)(msg->index_number & 0xFFFF); // put the index number of the freed message in here + // debug(1,"msg_free item %d -- free.",msg->index_number); + uintptr_t index = (msg->index_number) & 0xFFFF; + if (index == 0) + index = 0x10000; // ensure it doesn't fold to zero. + *msgh = (rtsp_message *)(index); // put a version of the index number of the freed message in here free(msg); } else { - debug(1,"msg_free item %d -- decrement reference to %d.",msg->index_number,msg->referenceCount); + // debug(1,"msg_free item %d -- decrement reference to %d.",msg->index_number,msg->referenceCount); } - - // else { - // debug(1,"rtsp_message reference count non-zero: - // %d!",msg->referenceCount); - //} } else if (*msgh != NULL) { - debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message %d.",(int)*msgh); + debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message, number %d.",(uintptr_t)*msgh); } debug_mutex_unlock(&reference_counter_lock, 0); } -/* -void msg_free(rtsp_message *msg) { - - if (msg) { - debug_mutex_lock(&reference_counter_lock, 1000, 0); - msg->referenceCount--; - debug_mutex_unlock(&reference_counter_lock, 0); - if (msg->referenceCount == 0) { - unsigned int i; - for (i = 0; i < msg->nheaders; i++) { - free(msg->name[i]); - free(msg->value[i]); - } - if (msg->content) - free(msg->content); - free(msg); - } // else { - // debug(1,"rtsp_message reference count non-zero: - // %d!",msg->referenceCount); - //} - } else { - debug(1, "null rtsp_message pointer passed to msg_free()"); - } -} -*/ - int msg_handle_line(rtsp_message **pmsg, char *line) { rtsp_message *msg = *pmsg; From 6c912d6ce53c1762734bf30f461437f2d7d91b4c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 13 Jan 2019 10:20:26 +0000 Subject: [PATCH 050/115] Revise the code for retaining and freeing rtsp message memory allocations. Be more careful about cleaning up a possible message allocation when the rtsp channel is closed. Watch for overreleasing a message. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 4b1f2915..dfeb1147 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d45], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d46], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 68f20fe8db92b8d42176139cfaac5d8b2c117cb1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 13 Jan 2019 13:14:39 +0000 Subject: [PATCH 051/115] Add an error message if an RTSP thread can not be created. --- rtsp.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rtsp.c b/rtsp.c index d44ae0f0..6e98944f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2590,8 +2590,12 @@ void rtsp_listen_loop(void) { ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func, conn); // also acts as a memory barrier - if (ret) - die("Failed to create RTSP receiver thread %d!", conn->connection_number); + if (ret) { + char errorstring[1024]; + strerror_r(ret, (char *)errorstring, sizeof(errorstring)); + die("Connection %d: cannot create an RTSP conversation thread. Error %d: \"%s\".", + conn->connection_number, ret, (char *)errorstring); + } debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number); conn->running = 1; // this must happen before the thread is tracked track_thread(conn); From 44830377193c341ec7aff8e0320ff07fd9bbd519 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 15 Jan 2019 08:30:00 +0000 Subject: [PATCH 052/115] Block on command_execute for unfixable errors. --- common.c | 4 ++-- common.h | 2 +- player.c | 4 ++-- rtsp.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common.c b/common.c index 05edae98..729bcacd 100644 --- a/common.c +++ b/common.c @@ -739,7 +739,7 @@ void command_start(void) { } } } -void command_execute(const char *command, const char *extra_argument) { +void command_execute(const char *command, const char *extra_argument, const int block) { // this has a cancellation point if waiting is enabled if (command) { char new_command_buffer[1024]; @@ -767,7 +767,7 @@ void command_execute(const char *command, const char *extra_argument) { exit(127); /* only if execv fails */ } } else { - if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for + if (block) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ if (rc != pid) { diff --git a/common.h b/common.h index 6f07f6d3..c6d5ae3a 100644 --- a/common.h +++ b/common.h @@ -309,7 +309,7 @@ int config_set_lookup_bool(config_t *cfg, char *where, int *dst); void command_start(void); void command_stop(void); -void command_execute(const char *command, const char *extra_argument); +void command_execute(const char *command, const char *extra_argument, const int block); void command_set_volume(double volume); int mkpath(const char *path, mode_t mode); diff --git a/player.c b/player.c index 36461c45..ba40bf5b 100644 --- a/player.c +++ b/player.c @@ -1098,7 +1098,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; if (config.cmd_unfixable) { - command_execute(config.cmd_unfixable, "output_device_stalled"); + command_execute(config.cmd_unfixable, "output_device_stalled", 1); } else { warn( "an unrecoverable error, \"output_device_stalled\", has been detected.", @@ -2010,7 +2010,7 @@ void *player_thread_func(void *arg) { "stalled. Executing the " "\"run_this_if_an_unfixable_error_is_detected\" command.", conn->connection_number); - command_execute(config.cmd_unfixable, "output_device_stalled"); + command_execute(config.cmd_unfixable, "output_device_stalled", 1); } else { warn("Connection %d: An unfixable error has been detected -- output device is " "stalled. \"No " diff --git a/rtsp.c b/rtsp.c index 6e98944f..4a0be9ba 100644 --- a/rtsp.c +++ b/rtsp.c @@ -296,7 +296,7 @@ void *player_watchdog_thread_code(void *arg) { } else if (conn->watchdog_barks == 3) { if ((config.cmd_unfixable) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; - command_execute(config.cmd_unfixable, "unable_to_cancel_play_session"); + command_execute(config.cmd_unfixable, "unable_to_cancel_play_session",1); } else { warn("an unrecoverable error, \"unable_to_cancel_play_session\", has been detected.", conn->connection_number); From 020a69dfb9be8099138de580cb98578196ffeca9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:27:29 +0000 Subject: [PATCH 053/115] remove the cause of a CLANG warning --- audio_alsa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_alsa.c b/audio_alsa.c index a1b81e4c..49500038 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1100,7 +1100,7 @@ int delay(long *the_delay) { int oldState; snd_pcm_state_t state; - snd_pcm_sframes_t my_delay; + snd_pcm_sframes_t my_delay = 0; // this initialisation is to silence a clang warning pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); From f6d6700d6efd117ad87fcb4c62b81567f7ccc4a7 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:28:02 +0000 Subject: [PATCH 054/115] Quieten a debug message. --- dacp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dacp.c b/dacp.c index d82f0594..a4a04df1 100644 --- a/dacp.c +++ b/dacp.c @@ -857,8 +857,8 @@ void dacp_monitor_start() { rc = pthread_mutex_init(&dacp_conversation_lock, &mta); if (rc) debug(1, "Error creating the DACP Conversation Lock Mutex Init"); - else - debug(1, "DACP Conversation Lock Mutex Init"); + // else + // debug(1, "DACP Conversation Lock Mutex Init"); rc = pthread_mutexattr_destroy(&mta); if (rc) From 3566cf0caf5eefb1cbabed14fc7e2c22074d6c01 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:30:21 +0000 Subject: [PATCH 055/115] remove unnecessary file includes --- metadata_hub.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/metadata_hub.c b/metadata_hub.c index aacfb3a3..84cd8076 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -45,18 +45,6 @@ #include "dacp.h" #include "metadata_hub.h" -#ifdef CONFIG_MBEDTLS -#include -#endif - -#ifdef CONFIG_POLARSSL -#include -#endif - -#ifdef CONFIG_OPENSSL -#include -#endif - pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store From 99fd6b85d05903024c0109fe3a0f39e58d55b3db Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:31:36 +0000 Subject: [PATCH 056/115] make OpenSSL routines un-cancellable, in case there is a possible memory leak --- common.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common.c b/common.c index 729bcacd..a1354621 100644 --- a/common.c +++ b/common.c @@ -394,6 +394,8 @@ uint8_t *base64_dec(char *input, int *outlen) { #ifdef CONFIG_OPENSSL char *base64_enc(uint8_t *input, int length) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); BIO *bmem, *b64; BUF_MEM *bptr; b64 = BIO_new(BIO_f_base64()); @@ -414,10 +416,13 @@ char *base64_enc(uint8_t *input, int length) { BIO_free_all(bmem); + pthread_setcancelstate(oldState, NULL); return buf; } uint8_t *base64_dec(char *input, int *outlen) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); BIO *bmem, *b64; int inlen = strlen(input); @@ -442,6 +447,7 @@ uint8_t *base64_dec(char *input, int *outlen) { BIO_free_all(b64); *outlen = nread; + pthread_setcancelstate(oldState, NULL); return buf; } #endif @@ -473,6 +479,8 @@ static char super_secret_key[] = #ifdef CONFIG_OPENSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); RSA *rsa = NULL; if (!rsa) { BIO *bmem = BIO_new_mem_buf(super_secret_key, -1); @@ -491,6 +499,7 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { default: die("bad rsa mode"); } + pthread_setcancelstate(oldState, NULL); return out; } #endif From 61c870bac162844fa04da419189e785f32ce9d49 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:32:31 +0000 Subject: [PATCH 057/115] make OpenSSL routines un-cancellable, in case there is a possible memory leak --- rtsp.c | 6 ++++++ shairport.c | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/rtsp.c b/rtsp.c index 4a0be9ba..5a1cbd05 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2007,9 +2007,12 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { uint8_t digest_urp[16], digest_mu[16], digest_total[16]; + #ifdef CONFIG_OPENSSL MD5_CTX ctx; + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); MD5_Init(&ctx); MD5_Update(&ctx, username, strlen(username)); MD5_Update(&ctx, ":", 1); @@ -2022,6 +2025,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { MD5_Update(&ctx, ":", 1); MD5_Update(&ctx, uri, strlen(uri)); MD5_Final(digest_mu, &ctx); + pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS @@ -2062,6 +2066,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { snprintf((char *)buf + 2 * i, 3, "%02x", digest_urp[i]); #ifdef CONFIG_OPENSSL + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); MD5_Init(&ctx); MD5_Update(&ctx, buf, 32); MD5_Update(&ctx, ":", 1); @@ -2071,6 +2076,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); MD5_Update(&ctx, buf, 32); MD5_Final(digest_total, &ctx); + pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS diff --git a/shairport.c b/shairport.c index 65c0bd68..a02d541e 100644 --- a/shairport.c +++ b/shairport.c @@ -1577,10 +1577,14 @@ int main(int argc, char **argv) { uint8_t ap_md5[16]; #ifdef CONFIG_OPENSSL + // can't imagine this is necessary, but here it is anyway + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, config.service_name, strlen(config.service_name)); MD5_Final(ap_md5, &ctx); + pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS From a40a48693f7805d9d227c943423e1d400fa4ae70 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 18 Jan 2019 16:33:32 +0000 Subject: [PATCH 058/115] make OpenSSL routines un-cancellable, in case there might be possible memory leaks on cancellation --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index dfeb1147..14b8f39c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d46], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d47], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From a8639f6c40177da41ff923f64f08a9b767e1ed3f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 20 Jan 2019 22:15:02 +0000 Subject: [PATCH 059/115] Add an activity monitor -- can set an activity idle timeout. --- Makefile.am | 2 +- activity_monitor.c | 188 +++++++++++++++++++++++++++++++++++++++++++++ activity_monitor.h | 4 + common.h | 1 + player.c | 4 + shairport.c | 6 ++ 6 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 activity_monitor.c create mode 100644 activity_monitor.h diff --git a/Makefile.am b/Makefile.am index 7106f35a..c384807c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,7 +8,7 @@ bin_PROGRAMS = shairport-sync # See below for the flags for the test client program -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c if BUILD_FOR_FREEBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" diff --git a/activity_monitor.c b/activity_monitor.c new file mode 100644 index 00000000..3393e95d --- /dev/null +++ b/activity_monitor.c @@ -0,0 +1,188 @@ +/* + * Activity Monitor + * + * Contains code to run an activity flag and associated timer + * A pthread implements a simple state machine with three states, + * "idle", "active" and "timing out". + * + * + * This file is part of Shairport Sync. + * Copyright (c) Mike Brady 2019 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "config.h" + +#include "common.h" +#include "activity_monitor.h" + +enum am_state {am_inactive, am_active, am_timing_out} state; +enum ps_state {ps_inactive, ps_active} player_state; + +pthread_t activity_monitor_thread; +pthread_mutex_t activity_monitor_mutex; +pthread_cond_t activity_monitor_cv; + +void activity_monitor_signify_activity(int active) { + pthread_mutex_lock(&activity_monitor_mutex); + player_state = active == 0 ? ps_inactive : ps_active; + pthread_cond_signal(&activity_monitor_cv); + pthread_mutex_unlock(&activity_monitor_mutex); +} + +void going_active() { + pthread_mutex_unlock(&activity_monitor_mutex); + debug(1, "activity_monitor: state transitioning to \"active\""); + pthread_mutex_lock(&activity_monitor_mutex); +} + +void going_inactive() { + pthread_mutex_unlock(&activity_monitor_mutex); + debug(1, "activity_monitor: state transitioning to \"inactive\""); + pthread_mutex_lock(&activity_monitor_mutex); +} + +void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) { + debug(3, "activity_monitor: thread exit."); + pthread_cond_destroy(&activity_monitor_cv); + pthread_mutex_destroy(&activity_monitor_mutex); +} + +void *activity_monitor_thread_code(void *arg) { + int rc = pthread_mutex_init(&activity_monitor_mutex, NULL); + if (rc) + die("activity_monitor: error %d initialising activity_monitor_mutex.", rc); + +// set the flowcontrol condition variable to wait on a monotonic clock +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + pthread_condattr_t attr; + pthread_condattr_init(&attr); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. + rc = pthread_cond_init(&activity_monitor_cv, &attr); + pthread_condattr_destroy(&attr); + +#endif +#ifdef COMPILE_FOR_OSX + rc = pthread_cond_init(&activity_monitor_cv, NULL); +#endif + if (rc) + die("activity_monitor: error %d initialising activity_monitor_cv."); + pthread_cleanup_push(activity_thread_cleanup_handler, arg); + + uint64_t sec; + uint64_t nsec; + int rc; + struct timespec time_for_wait; + + state = am_inactive; + player_state = ps_inactive; + + pthread_mutex_lock(&activity_monitor_mutex); + do { + switch (state) { + case am_inactive: + debug(1,"am_state: am_inactive"); + while (player_state != ps_active) + pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); + state = am_active; + going_active(); + break; + case am_active: + debug(1,"am_state: am_active"); + while (player_state != ps_inactive) + pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); + if (config.active_timeout == 0.0) { + state = am_inactive; + going_inactive(); + } else { + state = am_timing_out; + + uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_timeout *1000000); // resolution of microseconds + time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; + time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; + +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp; + sec = time_of_wakeup_fp >> 32; + nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + time_for_wait.tv_sec = sec; + time_for_wait.tv_nsec = nsec; +#endif +#ifdef COMPILE_FOR_OSX + sec = time_to_wait_for_wakeup_fp >> 32; + nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + time_for_wait.tv_sec = sec; + time_for_wait.tv_nsec = nsec; +#endif + } + break; + case am_timing_out: + debug(1,"am_state: am_timing_out"); + rc = 0; + while ((player_state != ps_active) && (rc != ETIMEDOUT)) { +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); // this is a pthread cancellation point +#endif +#ifdef COMPILE_FOR_OSX + rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); +#endif + } + if (player_state == ps_active) + state = am_active; // player has gone active -- do nothing, because it's still active + else if (rc == ETIMEDOUT) { + state = am_inactive; + going_inactive(); + } else { + // activity monitor was woken up in the state am_timing_out, but not by a timeout and player is not in ps_active state + debug(1, "activity monitor was woken up in the state am_timing_out, but didn't change state"); + } + break; + default: + debug(1,"activity monitor in an illegal state!"); + state = am_inactive; + break; + } + } while (1); + pthread_mutex_unlock(&activity_monitor_mutex); + pthread_cleanup_pop(0); // should never happen + pthread_exit(NULL); +} + +void activity_monitor_start() { + debug(1,"activity_monitor_start"); + config.active_timeout = 5.5; // hack + pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); +} + +void activity_monitor_stop() { + debug(1,"activity_monitor_stop start..."); + pthread_cancel(activity_monitor_thread); + pthread_join(activity_monitor_thread,NULL); + debug(1,"activity_monitor_stop complete"); +} + diff --git a/activity_monitor.h b/activity_monitor.h new file mode 100644 index 00000000..9de33454 --- /dev/null +++ b/activity_monitor.h @@ -0,0 +1,4 @@ +#pragma once +void activity_monitor_start(); +void activity_monitor_stop(); +void activity_monitor_signify_activity(int active); // 0 means inactive, non-zero means active diff --git a/common.h b/common.h index c6d5ae3a..505c426b 100644 --- a/common.h +++ b/common.h @@ -177,6 +177,7 @@ typedef struct { double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. + double active_timeout; // the amount of time from when play ends to when the system does into the "inactive". uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. enum sps_format_t output_format; diff --git a/player.c b/player.c index ba40bf5b..6548b381 100644 --- a/player.c +++ b/player.c @@ -89,6 +89,8 @@ #include "loudness.h" +#include "activity_monitor.h" + // default buffer size // needs to be a power of 2 because of the way BUFIDX(seqno) works //#define BUFFER_FRAMES 512 @@ -1486,9 +1488,11 @@ void player_thread_cleanup_handler(void *arg) { clear_reference_timestamp(conn); conn->rtp_running = 0; pthread_setcancelstate(oldState, NULL); + activity_monitor_signify_activity(0); // inactive } void *player_thread_func(void *arg) { + activity_monitor_signify_activity(1); // active rtsp_conn_info *conn = (rtsp_conn_info *)arg; // pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); conn->packet_count = 0; diff --git a/shairport.c b/shairport.c index a02d541e..382536ec 100644 --- a/shairport.c +++ b/shairport.c @@ -63,6 +63,7 @@ #include "mdns.h" #include "rtp.h" #include "rtsp.h" +#include "activity_monitor.h" #if defined(CONFIG_DACP_CLIENT) #include "dacp.h" @@ -1160,6 +1161,9 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { #ifdef CONFIG_METADATA metadata_stop(); // close down the metadata pipe #endif + + activity_monitor_stop(0); + if (config.output->deinit) { debug(1, "Deinitialise the audio backend."); config.output->deinit(); @@ -1633,6 +1637,8 @@ int main(int argc, char **argv) { } #endif + activity_monitor_start(); + // daemon_log(LOG_INFO, "Successful Startup"); rtsp_listen_loop(); From 2e181f937f056374da0e5b9dd7e4dc07f9c262be Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 21 Jan 2019 18:20:39 +0000 Subject: [PATCH 060/115] add initial script-execution functionality to active mode. Make is possible to wait for start and stop scripts. --- activity_monitor.c | 60 +++++++++++++++++++++++++++++++--------------- common.h | 3 ++- player.c | 6 ++--- shairport.c | 20 +++++++++++++++- 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 3393e95d..aea75cd9 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -48,25 +48,46 @@ pthread_t activity_monitor_thread; pthread_mutex_t activity_monitor_mutex; pthread_cond_t activity_monitor_cv; +void going_active(int block) { + debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out"); + if (config.cmd_active_start) + command_execute(config.cmd_active_start, "", block); +} + +void going_inactive(int block) { + debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out"); + if (config.cmd_active_stop) + command_execute(config.cmd_active_stop, "", block); +} + void activity_monitor_signify_activity(int active) { pthread_mutex_lock(&activity_monitor_mutex); player_state = active == 0 ? ps_inactive : ps_active; - pthread_cond_signal(&activity_monitor_cv); + // Now, although we could simply let the state machine in the activity monitor thread + // look after eveything, we will change state here in two situations: + // 1. If the state machine is am_inactive and the player is ps_active + // we will change the state to am_active and execute the going_active() function. + // 2. If the state machine is am_active and the player is ps_inactive and + // the activity_idle_timeout is 0, then we will change the state to am_inactive and + // execute the going_inactive() function. + // + // The reason for all this is that we might want to perform the attached scripts + // and wait for them to complete before continuing. If they were perfomed in the + // activity monitor thread, then we coldn't wait for them to complete. + + // Thus, the only time the thread will execute a going_... function is when a non-zero + // timeout actually matures. + + if ((state == am_inactive) && (player_state == ps_active)) { + going_active(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay + } else if ((state == am_active) && (player_state == ps_inactive) && (config.active_mode_timeout == 0.0)) { + going_inactive(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay + } + + pthread_cond_signal(&activity_monitor_cv); pthread_mutex_unlock(&activity_monitor_mutex); } -void going_active() { - pthread_mutex_unlock(&activity_monitor_mutex); - debug(1, "activity_monitor: state transitioning to \"active\""); - pthread_mutex_lock(&activity_monitor_mutex); -} - -void going_inactive() { - pthread_mutex_unlock(&activity_monitor_mutex); - debug(1, "activity_monitor: state transitioning to \"inactive\""); - pthread_mutex_lock(&activity_monitor_mutex); -} - void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) { debug(3, "activity_monitor: thread exit."); pthread_cond_destroy(&activity_monitor_cv); @@ -110,19 +131,19 @@ void *activity_monitor_thread_code(void *arg) { while (player_state != ps_active) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); state = am_active; - going_active(); + // going_active(); // this is done in activity_monitor_signify_activity break; case am_active: debug(1,"am_state: am_active"); while (player_state != ps_inactive) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); - if (config.active_timeout == 0.0) { + if (config.active_mode_timeout == 0.0) { state = am_inactive; - going_inactive(); + // going_inactive(); // this is done in activity_monitor_signify_activity } else { state = am_timing_out; - uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_timeout *1000000); // resolution of microseconds + uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_mode_timeout *1000000); // resolution of microseconds time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; @@ -156,7 +177,9 @@ void *activity_monitor_thread_code(void *arg) { state = am_active; // player has gone active -- do nothing, because it's still active else if (rc == ETIMEDOUT) { state = am_inactive; - going_inactive(); + pthread_mutex_unlock(&activity_monitor_mutex); + going_inactive(0); // don't wait for completion -- it makes no sense + pthread_mutex_lock(&activity_monitor_mutex); } else { // activity monitor was woken up in the state am_timing_out, but not by a timeout and player is not in ps_active state debug(1, "activity monitor was woken up in the state am_timing_out, but didn't change state"); @@ -175,7 +198,6 @@ void *activity_monitor_thread_code(void *arg) { void activity_monitor_start() { debug(1,"activity_monitor_start"); - config.active_timeout = 5.5; // hack pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); } diff --git a/common.h b/common.h index 505c426b..29ec460d 100644 --- a/common.h +++ b/common.h @@ -152,6 +152,7 @@ typedef struct { int statistics_requested, use_negotiated_latencies; enum playback_mode_type playback_mode; char *cmd_start, *cmd_stop, *cmd_set_volume, *cmd_unfixable; + char *cmd_active_start, *cmd_active_stop; int cmd_blocking, cmd_start_returns_output; double tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; @@ -177,7 +178,7 @@ typedef struct { double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. - double active_timeout; // the amount of time from when play ends to when the system does into the "inactive". + double active_mode_timeout; // the amount of time from when play ends to when the system leaves into the "active" mode. uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. enum sps_format_t output_format; diff --git a/player.c b/player.c index 6548b381..0de214c4 100644 --- a/player.c +++ b/player.c @@ -1488,12 +1488,10 @@ void player_thread_cleanup_handler(void *arg) { clear_reference_timestamp(conn); conn->rtp_running = 0; pthread_setcancelstate(oldState, NULL); - activity_monitor_signify_activity(0); // inactive } void *player_thread_func(void *arg) { - activity_monitor_signify_activity(1); // active - rtsp_conn_info *conn = (rtsp_conn_info *)arg; + rtsp_conn_info *conn = (rtsp_conn_info *)arg; // pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); conn->packet_count = 0; conn->packet_count_since_flush = 0; @@ -2802,6 +2800,7 @@ int player_play(rtsp_conn_info *conn) { if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); + activity_monitor_signify_activity(1); // active, and should be before play's command hook, command_start() command_start(); pthread_t *pt = malloc(sizeof(pthread_t)); if (pt == NULL) @@ -2850,6 +2849,7 @@ int player_stop(rtsp_conn_info *conn) { #endif // debuglev = dl; command_stop(); + activity_monitor_signify_activity(0); // inactive, and should be after command_stop() return 0; } else { debug(3, "Connection %d: player thread already deleted.", conn->connection_number); diff --git a/shairport.c b/shairport.c index 382536ec..8d43629e 100644 --- a/shairport.c +++ b/shairport.c @@ -331,6 +331,7 @@ int parse_options(int argc, char **argv) { // nothing else comes in first. config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; + config.active_mode_timeout = 10.0; #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; @@ -722,7 +723,24 @@ int parse_options(int argc, char **argv) { config.cmd_stop = (char *)str; } - if (config_lookup_string(config.cfg, + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_mode", &str)) { + config.cmd_active_start = (char *)str; + } + + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_mode", &str)) { + config.cmd_active_stop = (char *)str; + } + + if (config_lookup_float(config.cfg, "sessioncontrol.active_mode_timeout", &dvalue)) { + if (dvalue < 0.0) + warn("Invalid value \"%f\" for sessioncontrol.active_mode_timeout. It must be positive. The default of %f will be used instead.", + dvalue, config.active_mode_timeout); + else + config.active_mode_timeout = dvalue; + } + + + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) { config.cmd_unfixable = (char *)str; } From 604ca8dee458a07075453c0fde2b8a505846e3f5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 21 Jan 2019 21:18:40 +0000 Subject: [PATCH 061/115] Hook up active mode stuff to metadata, add two four-char codes, improve external command calling. --- activity_monitor.c | 21 +++++++++++++++------ common.c | 38 ++++++-------------------------------- metadata_hub.c | 12 ++++++++++++ metadata_hub.h | 7 +++++++ rtsp.c | 4 +++- 5 files changed, 43 insertions(+), 39 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index aea75cd9..53e3d7f1 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -39,6 +39,7 @@ #include "config.h" #include "common.h" +#include "rtsp.h" #include "activity_monitor.h" enum am_state {am_inactive, am_active, am_timing_out} state; @@ -49,15 +50,23 @@ pthread_mutex_t activity_monitor_mutex; pthread_cond_t activity_monitor_cv; void going_active(int block) { - debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out"); + // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out"); if (config.cmd_active_start) command_execute(config.cmd_active_start, "", block); +#ifdef CONFIG_METADATA + debug(2, "abeg"); // active mode begin + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points +#endif } void going_inactive(int block) { - debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out"); + // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out"); if (config.cmd_active_stop) command_execute(config.cmd_active_stop, "", block); +#ifdef CONFIG_METADATA + debug(2, "aend"); // active mode end + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points +#endif } void activity_monitor_signify_activity(int active) { @@ -127,14 +136,14 @@ void *activity_monitor_thread_code(void *arg) { do { switch (state) { case am_inactive: - debug(1,"am_state: am_inactive"); + // debug(1,"am_state: am_inactive"); while (player_state != ps_active) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); state = am_active; // going_active(); // this is done in activity_monitor_signify_activity break; case am_active: - debug(1,"am_state: am_active"); + // debug(1,"am_state: am_active"); while (player_state != ps_inactive) pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); if (config.active_mode_timeout == 0.0) { @@ -163,7 +172,7 @@ void *activity_monitor_thread_code(void *arg) { } break; case am_timing_out: - debug(1,"am_state: am_timing_out"); + // debug(1,"am_state: am_timing_out"); rc = 0; while ((player_state != ps_active) && (rc != ETIMEDOUT)) { #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD @@ -197,7 +206,7 @@ void *activity_monitor_thread_code(void *arg) { } void activity_monitor_start() { - debug(1,"activity_monitor_start"); + // debug(1,"activity_monitor_start"); pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); } diff --git a/common.c b/common.c index a1354621..2c2ede62 100644 --- a/common.c +++ b/common.c @@ -725,7 +725,8 @@ void command_start(void) { and if blocking is true, wait for process to finish */ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ - if (rc != pid) { + if ((rc != pid) && (errno != ECHILD)) { + // In this context, ECHILD means that the child process has already completed, I think! warn("Execution of on-start command returned an error."); debug(1, "on-start command %s finished with error %d", config.cmd_start, errno); } @@ -779,7 +780,8 @@ void command_execute(const char *command, const char *extra_argument, const int if (block) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */ pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ - if (rc != pid) { + if ((rc != pid) && (errno != ECHILD)) { + // In this context, ECHILD means that the child process has already completed, I think! warn("Execution of command \"%s\" returned an error.", full_command); debug(1, "Command \"%s\" finished with error %d", full_command, errno); } @@ -791,36 +793,8 @@ void command_execute(const char *command, const char *extra_argument, const int void command_stop(void) { // this has a cancellation point if waiting is enabled - if (config.cmd_stop) { - /*Spawn a child to run the program.*/ - pid_t pid = fork(); - if (pid == 0) { /* child process */ - int argC; - char **argV; - // debug(1,"on-stop command found."); - if (poptParseArgvString(config.cmd_stop, &argC, (const char ***)&argV) != - 0) // note that argV should be free()'d after use, but we expect this fork to exit - // eventually. - debug(1, "Can't decipher on-stop command arguments"); - else { - // debug(1,"Executing on-stop command %s",config.cmd_stop); - execv(argV[0], argV); - warn("Execution of on-stop command failed to start"); - debug(1, "Error executing on-stop command %s", config.cmd_stop); - exit(127); /* only if execv fails */ - } - } else { - if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for - process to finish */ - pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ - if (rc != pid) { - warn("Execution of on-stop command returned an error."); - debug(1, "Stop command %s finished with error %d", config.cmd_stop, errno); - } - } - // debug(1,"Continue after on-stop command"); - } - } + if (config.cmd_stop) + command_execute(config.cmd_stop,"",config.cmd_blocking); } // this is for reading an unsigned 32 bit number, such as an RTP timestamp diff --git a/metadata_hub.c b/metadata_hub.c index 84cd8076..398ad35b 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -535,6 +535,18 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin break; // these could tell us about play / pause etc. but will only occur if metadata is enabled, so // we'll just ignore them + case 'abeg': { + metadata_hub_modify_prolog(); + int changed = (metadata_store.active_state != AM_ACTIVE); + metadata_store.active_state = AM_ACTIVE; + metadata_hub_modify_epilog(changed); + } break; + case 'aend': { + metadata_hub_modify_prolog(); + int changed = (metadata_store.active_state != AM_INACTIVE); + metadata_store.active_state = AM_INACTIVE; + metadata_hub_modify_epilog(changed); + } break; case 'pbeg': { metadata_hub_modify_prolog(); int changed = (metadata_store.player_state != PS_PLAYING); diff --git a/metadata_hub.h b/metadata_hub.h index 65e93a23..7acc6722 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -12,6 +12,11 @@ enum play_status_type { PS_PLAYING, } play_status_type; +enum active_mode_type { + AM_INACTIVE = 0, + AM_ACTIVE, +} active_mode_type; + enum shuffle_status_type { SS_NOT_AVAILABLE = 0, SS_OFF, @@ -77,6 +82,8 @@ typedef struct metadata_bundle { enum play_status_type player_state; // this is the state of the actual player itself, which can be a bit noisy. + enum active_mode_type + active_state; int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the // speaker volume control diff --git a/rtsp.c b/rtsp.c index 5a1cbd05..2fcd3103 100644 --- a/rtsp.c +++ b/rtsp.c @@ -974,7 +974,7 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { msg_add_header(resp, "Session", "1"); resp->respcode = 200; // it all worked out okay - debug(1, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", + debug(2, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", conn->connection_number, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); @@ -1087,6 +1087,8 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, // 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the // first few bytes to see // which. +// 'abeg' -- active mode entered. No arguments +// 'aend' -- active mode exited. No arguments // 'pbeg' -- play stream begin. No arguments // 'pend' -- play stream end. No arguments // 'pfls' -- play stream flush. No arguments From 2e4428538d9f77233de0b0b9f151339b4ef8f923 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 21 Jan 2019 21:43:55 +0000 Subject: [PATCH 062/115] Add an "active" mode that can time out after a period -- 10 seconds by default -- after play ends. --- activity_monitor.c | 189 ++++++++++++++++++++++++--------------------- audio.h | 3 +- audio_alsa.c | 30 +++---- audio_jack.c | 6 +- audio_soundio.c | 5 +- common.c | 38 ++++----- common.h | 11 +-- dacp.c | 22 ++++-- dbus-service.c | 6 +- mdns_dns_sd.c | 2 +- mdns_external.c | 2 +- metadata_hub.c | 2 +- metadata_hub.h | 3 +- player.c | 147 ++++++++++++++++++----------------- rtp.c | 17 ++-- rtsp.c | 50 +++++++----- shairport.c | 25 +++--- tinysvcmdns.c | 3 +- 18 files changed, 300 insertions(+), 261 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 53e3d7f1..6452a08e 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -1,11 +1,11 @@ /* * Activity Monitor - * + * * Contains code to run an activity flag and associated timer - * A pthread implements a simple state machine with three states, + * A pthread implements a simple state machine with three states, * "idle", "active" and "timing out". - * - * + * + * * This file is part of Shairport Sync. * Copyright (c) Mike Brady 2019 * All rights reserved. @@ -31,41 +31,43 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include #include #include +#include #include #include "config.h" +#include "activity_monitor.h" #include "common.h" #include "rtsp.h" -#include "activity_monitor.h" -enum am_state {am_inactive, am_active, am_timing_out} state; -enum ps_state {ps_inactive, ps_active} player_state; +enum am_state { am_inactive, am_active, am_timing_out } state; +enum ps_state { ps_inactive, ps_active } player_state; pthread_t activity_monitor_thread; pthread_mutex_t activity_monitor_mutex; pthread_cond_t activity_monitor_cv; void going_active(int block) { - // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out"); + // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : + // "out"); if (config.cmd_active_start) command_execute(config.cmd_active_start, "", block); #ifdef CONFIG_METADATA - debug(2, "abeg"); // active mode begin - send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points + debug(2, "abeg"); // active mode begin + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif } void going_inactive(int block) { - // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out"); + // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : + // "out"); if (config.cmd_active_stop) command_execute(config.cmd_active_stop, "", block); #ifdef CONFIG_METADATA - debug(2, "aend"); // active mode end - send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points + debug(2, "aend"); // active mode end + send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif } @@ -83,17 +85,20 @@ void activity_monitor_signify_activity(int active) { // The reason for all this is that we might want to perform the attached scripts // and wait for them to complete before continuing. If they were perfomed in the // activity monitor thread, then we coldn't wait for them to complete. - + // Thus, the only time the thread will execute a going_... function is when a non-zero // timeout actually matures. - + if ((state == am_inactive) && (player_state == ps_active)) { - going_active(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay - } else if ((state == am_active) && (player_state == ps_inactive) && (config.active_mode_timeout == 0.0)) { - going_inactive(config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay + going_active( + config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay + } else if ((state == am_active) && (player_state == ps_inactive) && + (config.active_mode_timeout == 0.0)) { + going_inactive( + config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay } - - pthread_cond_signal(&activity_monitor_cv); + + pthread_cond_signal(&activity_monitor_cv); pthread_mutex_unlock(&activity_monitor_mutex); } @@ -115,7 +120,7 @@ void *activity_monitor_thread_code(void *arg) { pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it. rc = pthread_cond_init(&activity_monitor_cv, &attr); pthread_condattr_destroy(&attr); - + #endif #ifdef COMPILE_FOR_OSX rc = pthread_cond_init(&activity_monitor_cv, NULL); @@ -123,84 +128,89 @@ void *activity_monitor_thread_code(void *arg) { if (rc) die("activity_monitor: error %d initialising activity_monitor_cv."); pthread_cleanup_push(activity_thread_cleanup_handler, arg); - + uint64_t sec; uint64_t nsec; int rc; struct timespec time_for_wait; - + state = am_inactive; player_state = ps_inactive; - + pthread_mutex_lock(&activity_monitor_mutex); do { switch (state) { - case am_inactive: - // debug(1,"am_state: am_inactive"); - while (player_state != ps_active) - pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); - state = am_active; - // going_active(); // this is done in activity_monitor_signify_activity + case am_inactive: + // debug(1,"am_state: am_inactive"); + while (player_state != ps_active) + pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); + state = am_active; + // going_active(); // this is done in activity_monitor_signify_activity break; - case am_active: - // debug(1,"am_state: am_active"); - while (player_state != ps_inactive) - pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); - if (config.active_mode_timeout == 0.0) { - state = am_inactive; - // going_inactive(); // this is done in activity_monitor_signify_activity - } else { - state = am_timing_out; - - uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_mode_timeout *1000000); // resolution of microseconds - time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; - time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; - -#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD - uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp; - sec = time_of_wakeup_fp >> 32; - nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32; - time_for_wait.tv_sec = sec; - time_for_wait.tv_nsec = nsec; -#endif -#ifdef COMPILE_FOR_OSX - sec = time_to_wait_for_wakeup_fp >> 32; - nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; - time_for_wait.tv_sec = sec; - time_for_wait.tv_nsec = nsec; -#endif - } - break; - case am_timing_out: - // debug(1,"am_state: am_timing_out"); - rc = 0; - while ((player_state != ps_active) && (rc != ETIMEDOUT)) { -#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD - rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); // this is a pthread cancellation point -#endif -#ifdef COMPILE_FOR_OSX - rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); -#endif - } - if (player_state == ps_active) - state = am_active; // player has gone active -- do nothing, because it's still active - else if (rc == ETIMEDOUT) { - state = am_inactive; - pthread_mutex_unlock(&activity_monitor_mutex); - going_inactive(0); // don't wait for completion -- it makes no sense - pthread_mutex_lock(&activity_monitor_mutex); - } else { - // activity monitor was woken up in the state am_timing_out, but not by a timeout and player is not in ps_active state - debug(1, "activity monitor was woken up in the state am_timing_out, but didn't change state"); - } - break; - default: - debug(1,"activity monitor in an illegal state!"); + case am_active: + // debug(1,"am_state: am_active"); + while (player_state != ps_inactive) + pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); + if (config.active_mode_timeout == 0.0) { state = am_inactive; + // going_inactive(); // this is done in activity_monitor_signify_activity + } else { + state = am_timing_out; + + uint64_t time_to_wait_for_wakeup_fp = + (uint64_t)(config.active_mode_timeout * 1000000); // resolution of microseconds + time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32; + time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000; + +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp; + sec = time_of_wakeup_fp >> 32; + nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + time_for_wait.tv_sec = sec; + time_for_wait.tv_nsec = nsec; +#endif +#ifdef COMPILE_FOR_OSX + sec = time_to_wait_for_wakeup_fp >> 32; + nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32; + time_for_wait.tv_sec = sec; + time_for_wait.tv_nsec = nsec; +#endif + } + break; + case am_timing_out: + // debug(1,"am_state: am_timing_out"); + rc = 0; + while ((player_state != ps_active) && (rc != ETIMEDOUT)) { +#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD + rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, + &time_for_wait); // this is a pthread cancellation point +#endif +#ifdef COMPILE_FOR_OSX + rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, + &time_for_wait); +#endif + } + if (player_state == ps_active) + state = am_active; // player has gone active -- do nothing, because it's still active + else if (rc == ETIMEDOUT) { + state = am_inactive; + pthread_mutex_unlock(&activity_monitor_mutex); + going_inactive(0); // don't wait for completion -- it makes no sense + pthread_mutex_lock(&activity_monitor_mutex); + } else { + // activity monitor was woken up in the state am_timing_out, but not by a timeout and player + // is not in ps_active state + debug(1, + "activity monitor was woken up in the state am_timing_out, but didn't change state"); + } + break; + default: + debug(1, "activity monitor in an illegal state!"); + state = am_inactive; break; } } while (1); - pthread_mutex_unlock(&activity_monitor_mutex); + pthread_mutex_unlock(&activity_monitor_mutex); pthread_cleanup_pop(0); // should never happen pthread_exit(NULL); } @@ -211,9 +221,8 @@ void activity_monitor_start() { } void activity_monitor_stop() { - debug(1,"activity_monitor_stop start..."); + debug(1, "activity_monitor_stop start..."); pthread_cancel(activity_monitor_thread); - pthread_join(activity_monitor_thread,NULL); - debug(1,"activity_monitor_stop complete"); + pthread_join(activity_monitor_thread, NULL); + debug(1, "activity_monitor_stop complete"); } - diff --git a/audio.h b/audio.h index b8ecf958..1767e416 100644 --- a/audio.h +++ b/audio.h @@ -22,7 +22,8 @@ typedef struct { void (*start)(int sample_rate, int sample_format); // block of samples - int (*preflight)(void *buf, int samples); // say you are about to send these samples (before interpolation) + int (*preflight)(void *buf, + int samples); // say you are about to send these samples (before interpolation) int (*play)(void *buf, int samples); void (*stop)(void); diff --git a/audio_alsa.c b/audio_alsa.c index 49500038..83f2086d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -469,8 +469,9 @@ int actual_open_alsa_device(void) { buffer_size); } */ - debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " - "length (%ld) you have chosen.", + debug(1, + "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " + "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } @@ -633,13 +634,15 @@ static int init(int argc, char **argv) { config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.120; // below this, basic interpolation will be used to save time. config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem - config.audio_backend_silence_threshold = 0.040; //start sending silent frames if the delay goes below this time - config.audio_backend_silence_scan_interval = 0.004; //check silence threshold this often + config.audio_backend_silence_threshold = + 0.040; // start sending silent frames if the delay goes below this time + config.audio_backend_silence_scan_interval = 0.004; // check silence threshold this often stall_monitor_error_threshold = (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds; stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form - debug(1, "stall_monitor_error_threshold is 0x%" PRIx64 ", with alsa_maximum_stall_time of %f sec.", + debug(1, + "stall_monitor_error_threshold is 0x%" PRIx64 ", with alsa_maximum_stall_time of %f sec.", stall_monitor_error_threshold, config.alsa_maximum_stall_time); stall_monitor_start_time = 0; @@ -1047,9 +1050,10 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 - ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 - ", stall_monitor_error_threshold %" PRIX64 ".", + debug(2, + "DAC seems to have stalled with time_now_ns: %" PRIX64 + ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 + ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; @@ -1264,8 +1268,8 @@ static void flush(void) { int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { uint64_t time_now = get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - uint64_t standoff_time = 60; // milliseconds - standoff_time = (standoff_time << 32)/1000; + uint64_t standoff_time = 60; // milliseconds + standoff_time = (standoff_time << 32) / 1000; most_recent_write_time = time_now + standoff_time; return 0; } @@ -1467,7 +1471,7 @@ void *alsa_buffer_monitor_thread_code(void *arg) { int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); long buffer_size_threshold = (long)(config.audio_backend_silence_threshold * desired_sample_rate); - + uint64_t sleep_time_in_fp = sleep_time_ms; sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; @@ -1489,8 +1493,8 @@ void *alsa_buffer_monitor_thread_code(void *arg) { if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { -// ((present_time > most_recent_write_time) && -// ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { + // ((present_time > most_recent_write_time) && + // ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { reply = delay(&buffer_size); if (reply != 0) { buffer_size = 0; diff --git a/audio_jack.c b/audio_jack.c index 90f08ac4..9a584385 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -336,8 +335,9 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a * we should try. */ if (config_lookup_int(config.cfg, "jack.auto_client_open_interval", &value)) { if ((value < 0) || (value > 300)) - debug(1, "Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " - "default is %d.", + debug(1, + "Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, " + "default is %d.", value, config.jack_auto_client_open_interval); else config.jack_auto_client_open_interval = value; diff --git a/audio_soundio.c b/audio_soundio.c index 0449895b..8942554d 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -27,8 +27,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer); int fill_count = fill_bytes / outstream->bytes_per_frame; - debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , " - "outstream->bytes_per_frame: %d", + debug(3, + "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , " + "outstream->bytes_per_frame: %d", frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame); if (frame_count_min > fill_count) { diff --git a/common.c b/common.c index 2c2ede62..8430c29d 100644 --- a/common.c +++ b/common.c @@ -114,7 +114,8 @@ uint16_t nextFreeUDPPort() { if (UDPPortIndex == 0) UDPPortIndex = config.udp_port_base; else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1)) - UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can be assigned by resetFreeUDPPort without checking + UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can + // be assigned by resetFreeUDPPort without checking else UDPPortIndex++; return UDPPortIndex; @@ -394,7 +395,7 @@ uint8_t *base64_dec(char *input, int *outlen) { #ifdef CONFIG_OPENSSL char *base64_enc(uint8_t *input, int length) { - int oldState; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); BIO *bmem, *b64; BUF_MEM *bptr; @@ -421,7 +422,7 @@ char *base64_enc(uint8_t *input, int length) { } uint8_t *base64_dec(char *input, int *outlen) { - int oldState; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); BIO *bmem, *b64; int inlen = strlen(input); @@ -479,7 +480,7 @@ static char super_secret_key[] = #ifdef CONFIG_OPENSSL uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { - int oldState; + int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); RSA *rsa = NULL; if (!rsa) { @@ -794,7 +795,7 @@ void command_execute(const char *command, const char *extra_argument, const int void command_stop(void) { // this has a cancellation point if waiting is enabled if (config.cmd_stop) - command_execute(config.cmd_stop,"",config.cmd_blocking); + command_execute(config.cmd_stop, "", config.cmd_blocking); } // this is for reading an unsigned 32 bit number, such as an RTP timestamp @@ -829,17 +830,17 @@ double flat_vol2attn(double vol, long max_db, long min_db) { double vol2attn(double vol, long max_db, long min_db) { -// We use a little coordinate geometry to build a transfer function from the volume passed in to -// the device's dynamic range. (See the diagram in the documents folder.) The x axis is the -// "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from -// the bottom of the range to the top. We build the transfer function from one or more lines. We -// characterise each line with two numbers: the first is where on x the line starts when y=0 (x -// can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the -// line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a -// dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer -// function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range -// Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30 -// Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30 + // We use a little coordinate geometry to build a transfer function from the volume passed in to + // the device's dynamic range. (See the diagram in the documents folder.) The x axis is the + // "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from + // the bottom of the range to the top. We build the transfer function from one or more lines. We + // characterise each line with two numbers: the first is where on x the line starts when y=0 (x + // can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the + // line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a + // dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer + // function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range + // Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30 + // Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30 #define order 3 @@ -1151,8 +1152,9 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time, et = (et * 1000000) >> 32; // microseconds char errstr[1000]; if (r == ETIMEDOUT) - debug(debuglevel, "timed out waiting for a mutex, having waiting %f seconds, with a maximum " - "waiting time of %d microseconds. \"%s\".", + debug(debuglevel, + "timed out waiting for a mutex, having waiting %f seconds, with a maximum " + "waiting time of %d microseconds. \"%s\".", (1.0 * et) / 1000000, dally_time, debugmessage); else debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r, diff --git a/common.h b/common.h index 29ec460d..c1235b71 100644 --- a/common.h +++ b/common.h @@ -172,15 +172,16 @@ typedef struct { double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation // will not occur -- it'll be // basic interpolation instead. - double audio_backend_silence_threshold; // below this, silence will be added to the output buffer + double audio_backend_silence_threshold; // below this, silence will be added to the output buffer double audio_backend_silence_scan_interval; // check the threshold this often - + double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any // fixed latency there might be in the audio path double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play. - double active_mode_timeout; // the amount of time from when play ends to when the system leaves into the "active" mode. - uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's - // native range. + double active_mode_timeout; // the amount of time from when play ends to when the system leaves + // into the "active" mode. + uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's + // native range. enum sps_format_t output_format; enum volume_control_profile_type volume_control_profile; int output_rate; diff --git a/dacp.c b/dacp.c index a4a04df1..bd84f3c7 100644 --- a/dacp.c +++ b/dacp.c @@ -111,7 +111,10 @@ static void response_code(void *opaque, int code) { } static const struct http_funcs responseFuncs = { - response_realloc, response_body, response_header, response_code, + response_realloc, + response_body, + response_header, + response_code, }; // static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER; @@ -324,9 +327,10 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size); // debug(1,"dacp_conversation_lock released."); } else { - debug(3, "dacp_send_command: could not acquire a lock on the dacp transmit/receive section " - "when attempting to " - "send the command \"%s\". Possible timeout?", + debug(3, + "dacp_send_command: could not acquire a lock on the dacp transmit/receive section " + "when attempting to " + "send the command \"%s\". Possible timeout?", command); response.code = 494; // This client is already busy } @@ -423,8 +427,9 @@ void set_dacp_server_information(rtsp_conn_info *conn) { void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { debug_mutex_lock(&dacp_server_information_lock, 500000, 2); - debug(3, "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port " - "number %d.", + debug(3, + "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port " + "number %d.", dacp_id, dacp_server.dacp_id, port); if (strcmp(dacp_id, dacp_server.dacp_id) == 0) { dacp_server.port = port; @@ -472,8 +477,9 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { (metadata_store.advanced_dacp_server_active != 0); metadata_store.dacp_server_active = 0; metadata_store.advanced_dacp_server_active = 0; - debug(2, "setting dacp_server_active and advanced_dacp_server_active to 0 with an update " - "flag value of %d", + debug(2, + "setting dacp_server_active and advanced_dacp_server_active to 0 with an update " + "flag value of %d", ch); metadata_hub_modify_epilog(ch); while (dacp_server.scan_enable == 0) { diff --git a/dbus-service.c b/dbus-service.c index 9ae1d0aa..59fdd2c0 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -379,7 +379,7 @@ gboolean notify_verbosity_callback(ShairportSyncDiagnostics *skeleton, } gboolean notify_disable_standby_callback(ShairportSync *skeleton, - __attribute__((unused)) gpointer user_data) { + __attribute__((unused)) gpointer user_data) { // debug(1, "\"notify_disable_standby_callback\" called."); if (shairport_sync_get_disable_standby(skeleton)) { debug(1, ">> activating disable standby"); @@ -624,9 +624,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_alacdecoder_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", G_CALLBACK(notify_volume_control_profile_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", + g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", G_CALLBACK(notify_disable_standby_callback), NULL); -g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active", + g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active", G_CALLBACK(notify_loudness_filter_active_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold", G_CALLBACK(notify_loudness_threshold_callback), NULL); diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c index 95bae443..cf178f1d 100644 --- a/mdns_dns_sd.c +++ b/mdns_dns_sd.c @@ -24,8 +24,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "mdns.h" #include "common.h" +#include "mdns.h" #include #include #include diff --git a/mdns_external.c b/mdns_external.c index d3879ff5..8c0ab262 100644 --- a/mdns_external.c +++ b/mdns_external.c @@ -24,8 +24,8 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "mdns.h" #include "common.h" +#include "mdns.h" #include #include #include diff --git a/metadata_hub.c b/metadata_hub.c index 398ad35b..17b221a8 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -205,7 +205,7 @@ char *metadata_write_image_file(const char *buf, int len) { char *path = NULL; // this will be what is returned uint8_t img_md5[16]; -// uint8_t ap_md5[16]; + // uint8_t ap_md5[16]; #ifdef CONFIG_SSL MD5_CTX ctx; diff --git a/metadata_hub.h b/metadata_hub.h index 7acc6722..9406851f 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -82,8 +82,7 @@ typedef struct metadata_bundle { enum play_status_type player_state; // this is the state of the actual player itself, which can be a bit noisy. - enum active_mode_type - active_state; + enum active_mode_type active_state; int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the // speaker volume control diff --git a/player.c b/player.c index 0de214c4..ca4b00df 100644 --- a/player.c +++ b/player.c @@ -285,16 +285,18 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } if (outsize > toutsize) { - debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " - "truncated, but buffer overflow possible! Encrypted = %d.", + debug(2, + "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " + "truncated, but buffer overflow possible! Encrypted = %d.", outsize, toutsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } *destlen = outsize / conn->input_bytes_per_frame; if ((outsize % conn->input_bytes_per_frame) != 0) - debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " - "and the audio frame size (%d).", + debug(1, + "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " + "and the audio frame size (%d).", *destlen, outsize, conn->input_bytes_per_frame); return reply; } @@ -454,9 +456,10 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds - debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", + debug(3, + "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", seqno, actual_timestamp, conn->flush_rtp_timestamp); conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; @@ -849,9 +852,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than ten seconds - debug(3, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", + debug(3, + "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); curframe->ready = 0; curframe->resend_level = 0; @@ -934,8 +938,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp + conn->latency + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this will go modulo 2^32 - &should_be_time, - conn); + &should_be_time, conn); conn->first_packet_time_to_play = should_be_time; @@ -956,8 +959,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp + conn->latency + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this should go modulo 2^32 - &should_be_time, - conn); + &should_be_time, conn); conn->first_packet_time_to_play = should_be_time; @@ -1028,8 +1030,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // this might happen if a big clock adjustment was made at just the wrong // time. - debug(1, "Run a bit past the exact start time by %" PRId64 - " frames with a DAC delay of %ld frames.", + debug(1, + "Run a bit past the exact start time by %" PRId64 + " frames with a DAC delay of %ld frames.", -exact_frame_gap, dac_delay); if (config.output->flush) config.output->flush(); @@ -1187,8 +1190,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) - (uint32_t)(config.audio_backend_buffer_desired_length * conn->input_rate), // this will go modulo 2^32 - &time_to_play, - conn); + &time_to_play, conn); if (local_time_now >= time_to_play) { do_wait = 0; @@ -1491,7 +1493,7 @@ void player_thread_cleanup_handler(void *arg) { } void *player_thread_func(void *arg) { - rtsp_conn_info *conn = (rtsp_conn_info *)arg; + rtsp_conn_info *conn = (rtsp_conn_info *)arg; // pthread_cleanup_push(player_thread_initial_cleanup_handler, arg); conn->packet_count = 0; conn->packet_count_since_flush = 0; @@ -1667,17 +1669,17 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - conn->tbuf = - malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->tbuf = malloc( + sizeof(int32_t) * 2 * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); // initialise this, because soxr stuffing might be chosen later - conn->sbuf = - malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + conn->sbuf = malloc( + sizeof(int32_t) * 2 * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (conn->sbuf == NULL) die("Failed to allocate memory for the sbuf buffer."); @@ -1965,8 +1967,9 @@ void *player_thread_func(void *arg) { SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. if (inframe->sequence_number != conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay - debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " - "and ab_write: %u.", + debug(2, + "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write); conn->last_seqno_read = inframe->sequence_number; // reset warning... } @@ -2191,7 +2194,7 @@ void *player_thread_func(void *arg) { #ifdef CONFIG_CONVOLUTION || config.convolution #endif - ) { + ) { int32_t *tbuf32 = (int32_t *)conn->tbuf; float fbuf_l[inbuflength]; float fbuf_r[inbuflength]; @@ -2429,42 +2432,41 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform("%*.2f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*" PRIu64 "," /* missing packets */ - "%*" PRIu64 "," /* late packets */ - "%*" PRIu64 "," /* too late packets */ - "%*" PRIu64 "," /* resend requests */ - "%*" PRId64 "," /* min DAC queue size */ - "%*" PRId32 "," /* min buffer occupancy */ - "%*" PRId32 "," /* max buffer occupancy */ - "%*.2f," /* source nominal frame rate */ - "%*.2f," /* source actual (average) frame rate */ - "%*.2f," /* output frame rate */ - "%*.2f," /* source clock drift */ - "%*d," /* source clock drift sample count */ - "%*.2f", /* rough calculated correction in ppm */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 10, - moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), - 10, moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, - minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, - conn->input_frame_rate, 11, conn->frame_rate, 10, - (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, - conn->local_to_remote_time_gradient_sample_count, 10, - (conn->frame_rate > 0.0) - ? ((conn->frame_rate - - conn->remote_frame_rate * conn->output_sample_ratio * - conn->local_to_remote_time_gradient) * - 1000000) / - conn->frame_rate - : 0.0); + inform( + "%*.2f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*" PRIu64 "," /* missing packets */ + "%*" PRIu64 "," /* late packets */ + "%*" PRIu64 "," /* too late packets */ + "%*" PRIu64 "," /* resend requests */ + "%*" PRId64 "," /* min DAC queue size */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* output frame rate */ + "%*.2f," /* source clock drift */ + "%*d," /* source clock drift sample count */ + "%*.2f", /* rough calculated correction in ppm */ + 10, 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, + moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, + 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, + conn->remote_frame_rate, 11, conn->input_frame_rate, 11, conn->frame_rate, 10, + (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, + conn->local_to_remote_time_gradient_sample_count, 10, + (conn->frame_rate > 0.0) + ? ((conn->frame_rate - conn->remote_frame_rate * + conn->output_sample_ratio * + conn->local_to_remote_time_gradient) * + 1000000) / + conn->frame_rate + : 0.0); } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2479,10 +2481,9 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, - conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, - conn->resend_requests, 7, minimum_dac_queue_size, 5, + 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, + 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, + 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, conn->input_frame_rate, 10, (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, @@ -2501,10 +2502,9 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 10, - 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, - conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, - conn->resend_requests, 5, minimum_buffer_occupancy, 5, + 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, + 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, + 7, conn->resend_requests, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, conn->input_frame_rate, 10, (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, @@ -2800,7 +2800,8 @@ int player_play(rtsp_conn_info *conn) { if (config.buffer_start_fill > BUFFER_FRAMES) die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill, BUFFER_FRAMES); - activity_monitor_signify_activity(1); // active, and should be before play's command hook, command_start() + activity_monitor_signify_activity( + 1); // active, and should be before play's command hook, command_start() command_start(); pthread_t *pt = malloc(sizeof(pthread_t)); if (pt == NULL) diff --git a/rtp.c b/rtp.c index 157bc77a..a1ba6794 100644 --- a/rtp.c +++ b/rtp.c @@ -137,8 +137,9 @@ void *rtp_audio_receiver(void *arg) { stat_mean += stat_delta / stat_n; stat_M2 += stat_delta * (time_interval_us - stat_mean); if (stat_n % 2500 == 0) { - debug(2, "Packet reception interval stats: mean, standard deviation and max for the last " - "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.", + debug(2, + "Packet reception interval stats: mean, standard deviation and max for the last " + "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.", stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us); stat_n = 0; stat_mean = 0.0; @@ -368,9 +369,10 @@ void *rtp_control_receiver(void *arg) { if (la != conn->latency) { conn->latency = la; - debug(3, "New latency detected: %" PRIu32 ", sync latency: %" PRIu32 - ", minimum latency: %" PRIu32 ", maximum " - "latency: %" PRIu32 ", fixed offset: %" PRIu32 ".", + debug(3, + "New latency detected: %" PRIu32 ", sync latency: %" PRIu32 + ", minimum latency: %" PRIu32 ", maximum " + "latency: %" PRIu32 ", fixed offset: %" PRIu32 ".", la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency, conn->maximum_latency, config.fixedLatencyOffset); } @@ -1226,8 +1228,9 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) { (struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) { char em[1024]; strerror_r(errno, em, sizeof(em)); - debug(1, "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a " - "second.", + debug(1, + "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a " + "second.", errno, em); conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp; } else { diff --git a/rtsp.c b/rtsp.c index 2fcd3103..d854ae1b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -288,15 +288,16 @@ void *player_watchdog_thread_code(void *arg) { if (time_since_last_bark >= ct) { conn->watchdog_barks++; if (conn->watchdog_barks == 1) { - debug(1, "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " - "of the heart\".", + debug(1, + "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " + "of the heart\".", conn->connection_number); conn->stop = 1; pthread_cancel(conn->thread); } else if (conn->watchdog_barks == 3) { if ((config.cmd_unfixable) && (conn->unfixable_error_reported == 0)) { conn->unfixable_error_reported = 1; - command_execute(config.cmd_unfixable, "unable_to_cancel_play_session",1); + command_execute(config.cmd_unfixable, "unable_to_cancel_play_session", 1); } else { warn("an unrecoverable error, \"unable_to_cancel_play_session\", has been detected.", conn->connection_number); @@ -404,17 +405,17 @@ static char *nextline(char *in, int inbuf) { } void msg_retain(rtsp_message *msg) { - int rc = pthread_mutex_lock(&reference_counter_lock); - if (rc) - debug(1, "Error %d locking reference counter lock"); - if (msg > (rtsp_message *) 0x00010000) { + int rc = pthread_mutex_lock(&reference_counter_lock); + if (rc) + debug(1, "Error %d locking reference counter lock"); + if (msg > (rtsp_message *)0x00010000) { msg->referenceCount++; // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) debug(1, "Error %d unlocking reference counter lock"); } else { - debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t) msg); + debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t)msg); } } @@ -499,13 +500,18 @@ void msg_free(rtsp_message **msgh) { uintptr_t index = (msg->index_number) & 0xFFFF; if (index == 0) index = 0x10000; // ensure it doesn't fold to zero. - *msgh = (rtsp_message *)(index); // put a version of the index number of the freed message in here + *msgh = + (rtsp_message *)(index); // put a version of the index number of the freed message in here free(msg); } else { - // debug(1,"msg_free item %d -- decrement reference to %d.",msg->index_number,msg->referenceCount); + // debug(1,"msg_free item %d -- decrement reference to + // %d.",msg->index_number,msg->referenceCount); } } else if (*msgh != NULL) { - debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message, number %d.",(uintptr_t)*msgh); + debug(1, + "msg_free: error attempting to free an allocated but already-freed rtsp_message, number " + "%d.", + (uintptr_t)*msgh); } debug_mutex_unlock(&reference_counter_lock, 0); } @@ -831,9 +837,10 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message * rtsp_message *resp) { debug(3, "Connection %d: OPTIONS", conn->connection_number); resp->respcode = 200; - msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " - "PAUSE, FLUSH, TEARDOWN, " - "OPTIONS, GET_PARAMETER, SET_PARAMETER"); + msg_add_header(resp, "Public", + "ANNOUNCE, SETUP, RECORD, " + "PAUSE, FLUSH, TEARDOWN, " + "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, @@ -1564,7 +1571,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m char *ct = msg_get_header(req, "Content-Type"); if (ct) { -// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); + // debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); #ifdef CONFIG_METADATA // It seems that the rtptime of the message is used as a kind of an ID that @@ -2009,7 +2016,6 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { uint8_t digest_urp[16], digest_mu[16], digest_total[16]; - #ifdef CONFIG_OPENSSL MD5_CTX ctx; @@ -2244,8 +2250,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (strcmp(req->method, "OPTIONS") != 0) // the options message is very common, so don't log it until level 3 debug_level = 2; - debug(debug_level, "Connection %d: Received an RTSP Packet of type \"%s\":", - conn->connection_number, req->method), + debug(debug_level, + "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number, + req->method), debug_print_msg_headers(debug_level, req); apple_challenge(conn->fd, req, resp); @@ -2304,8 +2311,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { - debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the " - "connection.", + debug(1, + "Connection %d: Unable to write an RTSP message response. Terminating the " + "connection.", conn->connection_number); struct linger so_linger; so_linger.l_onoff = 1; // "true" @@ -2602,7 +2610,7 @@ void rtsp_listen_loop(void) { char errorstring[1024]; strerror_r(ret, (char *)errorstring, sizeof(errorstring)); die("Connection %d: cannot create an RTSP conversation thread. Error %d: \"%s\".", - conn->connection_number, ret, (char *)errorstring); + conn->connection_number, ret, (char *)errorstring); } debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number); conn->running = 1; // this must happen before the thread is tracked diff --git a/shairport.c b/shairport.c index 8d43629e..292acf25 100644 --- a/shairport.c +++ b/shairport.c @@ -59,11 +59,11 @@ #include #endif +#include "activity_monitor.h" #include "common.h" #include "mdns.h" #include "rtp.h" #include "rtsp.h" -#include "activity_monitor.h" #if defined(CONFIG_DACP_CLIENT) #include "dacp.h" @@ -723,24 +723,26 @@ int parse_options(int argc, char **argv) { config.cmd_stop = (char *)str; } - if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_mode", &str)) { + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_mode", + &str)) { config.cmd_active_start = (char *)str; } - if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_mode", &str)) { + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_mode", + &str)) { config.cmd_active_stop = (char *)str; } if (config_lookup_float(config.cfg, "sessioncontrol.active_mode_timeout", &dvalue)) { if (dvalue < 0.0) - warn("Invalid value \"%f\" for sessioncontrol.active_mode_timeout. It must be positive. The default of %f will be used instead.", - dvalue, config.active_mode_timeout); + warn("Invalid value \"%f\" for sessioncontrol.active_mode_timeout. It must be positive. " + "The default of %f will be used instead.", + dvalue, config.active_mode_timeout); else config.active_mode_timeout = dvalue; } - - if (config_lookup_string(config.cfg, + if (config_lookup_string(config.cfg, "sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) { config.cmd_unfixable = (char *)str; } @@ -1181,7 +1183,7 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { #endif activity_monitor_stop(0); - + if (config.output->deinit) { debug(1, "Deinitialise the audio backend."); config.output->deinit(); @@ -1519,8 +1521,9 @@ int main(int argc, char **argv) { /* Print out options */ debug(1, "log verbosity is %d.", debuglev); debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off"); - debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " - "deliberately.", + debug(1, + "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " + "deliberately.", config.diagnostic_drop_packet_fraction); debug(1, "statistics_requester status is %d.", config.statistics_requested); debug(1, "daemon status is %d.", config.daemonise); @@ -1599,7 +1602,7 @@ int main(int argc, char **argv) { uint8_t ap_md5[16]; #ifdef CONFIG_OPENSSL - // can't imagine this is necessary, but here it is anyway + // can't imagine this is necessary, but here it is anyway int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable MD5_CTX ctx; diff --git a/tinysvcmdns.c b/tinysvcmdns.c index ebf6fea7..bdce032c 100644 --- a/tinysvcmdns.c +++ b/tinysvcmdns.c @@ -1713,7 +1713,8 @@ void mdnsd_stop(struct mdnsd *s) { assert(s != NULL); struct timeval tv = { - .tv_sec = 0, .tv_usec = 500 * 1000, + .tv_sec = 0, + .tv_usec = 500 * 1000, }; s->stop_flag = 1; From eda674850fb0ddb29f159f0249ae3bd9c10cab41 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 21 Jan 2019 21:44:22 +0000 Subject: [PATCH 063/115] Add an "active" mode that can time out after a period -- 10 seconds by default -- after play ends. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 14b8f39c..530bf763 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d47], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d48], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 0feb59eaa57a27a6f8997f6228ed6658e38c97d1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 16:34:05 +0000 Subject: [PATCH 064/115] move malloc_cleanup to common.c --- dacp.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dacp.c b/dacp.c index bd84f3c7..7efaea3a 100644 --- a/dacp.c +++ b/dacp.c @@ -147,11 +147,6 @@ void http_cleanup(void *arg) { http_free(rt); } -void malloc_cleanup(void *arg) { - // debug(1, "malloc cleanup called."); - free(arg); -} - int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // will malloc space for the body or set it to NULL -- the caller should free it. From 0a9063fafeeccaa16db979a81c2bef5278f9dee8 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 16:34:34 +0000 Subject: [PATCH 065/115] move malloc_cleanup to common.c --- common.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common.c b/common.c index 8430c29d..10c9cc9f 100644 --- a/common.c +++ b/common.c @@ -1243,6 +1243,11 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha return r; } +void malloc_cleanup(void *arg) { + // debug(1, "malloc cleanup called."); + free(arg); +} + void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); } char *get_version_string() { From dd1b85383d0600ee503e0357706c8f592fa975d3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 16:34:46 +0000 Subject: [PATCH 066/115] move malloc_cleanup to common.c --- common.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common.h b/common.h index c1235b71..4a111e2c 100644 --- a/common.h +++ b/common.h @@ -351,4 +351,6 @@ void sps_nanosleep(const time_t sec, 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); +void malloc_cleanup(void *arg); + #endif // _COMMON_H From bd81e733cd894fbf95b2987df2e8c2be7332e791 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 16:44:02 +0000 Subject: [PATCH 067/115] Introduce a simple state machine into the alsa backend to keep track of the connected state of it. Needed to deal properly with disabling and enabling standby dynamically. --- audio_alsa.c | 390 ++++++++++++++++++++++++++------------------------- 1 file changed, 202 insertions(+), 188 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 83f2086d..871ac86a 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -37,6 +37,12 @@ #include #include +enum alsa_backend_mode { + abm_disconnected, + abm_connected, + abm_playing +} alsa_backend_state; // under the control of alsa_mutex + static void help(void); static int init(int argc, char **argv); static void deinit(void); @@ -392,7 +398,7 @@ int actual_open_alsa_device(void) { return ret; } - // check parameters after attempting to set them… + // check parameters after attempting to set them if (set_period_size_request != 0) { snd_pcm_uframes_t actual_period_size; @@ -625,6 +631,9 @@ static int init(int argc, char **argv) { // double dvalue; // set up default values first + + alsa_backend_state = abm_disconnected; // startup state + debug(1, "alsa: init() -- alsa_backend_state => abm_disconnected."); set_period_size_request = 0; set_buffer_size_request = 0; config.alsa_use_hardware_mute = 0; // don't use it by default @@ -966,16 +975,6 @@ static int init(int argc, char **argv) { 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("audio_alsa error %d opening the alsa device. Incorrect settings or device already busy?", - ret); - } most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL); @@ -1133,136 +1132,177 @@ int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) { return response; } -int play(void *buf, int samples) { - +int do_play(void *buf, int samples) { + // assuming the alsa_mutex has been acquired // debug(3,"audio_alsa play called."); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - int ret = 0; - if (alsa_handle == NULL) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - ret = actual_open_alsa_device(); + snd_pcm_state_t state; + snd_pcm_sframes_t my_delay; + int ret = delay_and_status(&state, &my_delay); + + if (ret == 0) { // will be non-zero if an error or a stall + + if ((samples != 0) && (buf != NULL)) { + + // jut check the state of the DAC + + if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && + (state != SND_PCM_STATE_XRUN)) { + debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state); + } + + // debug(3, "write %d frames.", samples); + ret = alsa_pcm_write(alsa_handle, buf, samples); + if (ret == samples) { + stall_monitor_frame_count += samples; + + if (frame_index == 0) { + frames_sent_for_playing = samples; + } else { + frames_sent_for_playing += samples; + } + + const uint64_t start_measurement_from_this_frame = + (2 * config.output_rate) / 352; // two seconds of framesÉ + + frame_index++; + + if ((frame_index == start_measurement_from_this_frame) || + ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { + + measurement_time = get_absolute_time_in_fp(); + frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; + + if (frame_index == start_measurement_from_this_frame) { + // debug(1, "Start frame counting"); + frames_played_at_measurement_start_time = frames_played_at_measurement_time; + measurement_start_time = measurement_time; + measurement_data_is_valid = 1; + } + } + } else { + + debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); + frame_index = 0; + measurement_data_is_valid = 0; + if (ret == -EPIPE) { /* underrun */ + ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); + if (ret < 0) { + warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(ret)); + } + } else if (ret == -ESTRPIPE) { /* suspended */ + while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { + sleep(1); /* wait until the suspend flag is released */ + if (ret < 0) { + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + "failed: %s.", + snd_strerror(ret)); + } + } + } + } + } + } else { + debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, + state); + frame_index = 0; + measurement_data_is_valid = 0; + } + + pthread_setcancelstate(oldState, NULL); + return ret; +} + +int do_open() { + 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(); if (ret == 0) { if (audio_alsa.volume) do_volume(set_volume); if (audio_alsa.mute) do_mute(0); } - - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex + alsa_backend_state = abm_connected; // only do this if it really opened it. + } else { + debug(1, "alsa: do_open() -- output device already open."); } + return ret; +} + +int do_close() { + if (alsa_backend_state == abm_disconnected) + debug(1, "alsa: do_close() -- closing the output device when it is already disconnected"); + int derr = 0; + if (alsa_handle) { + // debug(1,"alsa: do_close() -- closing the output device"); + 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)); + + // flush also closes the device + if ((derr = snd_pcm_close(alsa_handle))) + debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); + alsa_handle = NULL; + } else { + debug(1, "alsa: do_close() -- output device already closed."); + } + alsa_backend_state = abm_disconnected; + return derr; +} + +int play(void *buf, int samples) { + + // play() will change the state of the alsa_backend_mode to abm_playing + // also, if the present alsa_backend_state is abm_disconnected, then first the DAC must be + // connected + + // debug(3,"audio_alsa play called."); + int ret = 0; + + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + + if (alsa_backend_state == abm_disconnected) { + debug(1, "alsa: play() -- opening output device"); + ret = do_open(); + } + if (ret == 0) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0); - - snd_pcm_state_t state; - snd_pcm_sframes_t my_delay; - ret = delay_and_status(&state, &my_delay); - - if (ret == 0) { // will be non-zero if an error or a stall - - if ((samples != 0) && (buf != NULL)) { - - // jut check the state of the DAC - - if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) && - (state != SND_PCM_STATE_XRUN)) { - debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state); - } - - // debug(3, "write %d frames.", samples); - ret = alsa_pcm_write(alsa_handle, buf, samples); - if (ret == samples) { - stall_monitor_frame_count += samples; - - if (frame_index == 0) { - frames_sent_for_playing = samples; - } else { - frames_sent_for_playing += samples; - } - - const uint64_t start_measurement_from_this_frame = - (2 * config.output_rate) / 352; // two seconds of frames… - - frame_index++; - - if ((frame_index == start_measurement_from_this_frame) || - ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) { - - measurement_time = get_absolute_time_in_fp(); - frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples; - - if (frame_index == start_measurement_from_this_frame) { - // debug(1, "Start frame counting"); - frames_played_at_measurement_start_time = frames_played_at_measurement_time; - measurement_start_time = measurement_time; - measurement_data_is_valid = 1; - } - } - } else { - - debug(1, "alsa: error %d writing %d samples to alsa device.", ret, samples); - frame_index = 0; - measurement_data_is_valid = 0; - if (ret == -EPIPE) { /* underrun */ - ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0); - if (ret < 0) { - warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(ret)); - } - } else if (ret == -ESTRPIPE) { /* suspended */ - while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { - sleep(1); /* wait until the suspend flag is released */ - if (ret < 0) { - warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " - "failed: %s.", - snd_strerror(ret)); - } - } - } - } - } - } else { - debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, - state); - frame_index = 0; - measurement_data_is_valid = 0; + if (alsa_backend_state != abm_playing) { + debug(1, "alsa: play() -- alsa_backend_state => abm_playing"); + alsa_backend_state = abm_playing; } - - debug_mutex_unlock(&alsa_mutex, 0); - pthread_cleanup_pop(0); // release the mutex + ret = do_play(buf, samples); } - pthread_setcancelstate(oldState, NULL); + + 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."); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - int derr; do_mute(1); - - if (alsa_handle) { - stall_monitor_start_time = 0; - if (config.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_hw_free(alsa_handle))) - debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); - - // flush also closes the device - if ((derr = snd_pcm_close(alsa_handle))) - debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); - alsa_handle = NULL; + if (alsa_backend_state != abm_disconnected) { // must be playing or connected... + if (config.keep_dac_busy != 0) { + debug(1, "alsa: flush() -- alsa_backend_state => abm_connected."); + alsa_backend_state = abm_connected; + } else { + debug(1, "alsa: flush() -- closing the output device"); + do_close(); // will change the state to disconnected + debug(1, "alsa: flush() -- alsa_backend_state => abm_disconnected."); } - frame_index = 0; - measurement_data_is_valid = 0; - } + } else + debug(1, "alac: flush() -- called on a disconnected alsa backend"); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex - pthread_setcancelstate(oldState, NULL); } int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { @@ -1274,32 +1314,9 @@ int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int sam return 0; } -/* -static int play(void *buf, int samples) { - uint64_t time_now = - get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - uint64_t sample_duration = ((uint64_t)samples) << 32; - sample_duration = sample_duration / desired_sample_rate; - most_recent_write_time = time_now + sample_duration; - return untimed_play(buf, samples); -} - -int delay(long *the_delay) { - uint64_t time_now = - get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - most_recent_write_time = time_now; - return untimed_delay(the_delay); -} -*/ - static void stop(void) { // debug(2,"audio_alsa stop called."); - // when we want to stop, we want the alsa device - // to be closed immediately -- we may even be killing the thread, so we - // don't wish to wait - // so we should flush first - flush(); // flush will also close the device - // close_alsa_device(); + flush(); // flush will also close the device if appropriate } static void parameters(audio_parameters *info) { @@ -1428,46 +1445,13 @@ void do_mute(int mute_state_requested) { pthread_setcancelstate(oldState, NULL); } +/* void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *arg) { debug(1, "alsa: alsa_buffer_monitor_thread_cleanup_function called."); } +*/ -void *alsa_buffer_monitor_thread_code(void *arg) { - // debug(1,"alsa: alsa_buffer_monitor_thread_code called."); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - int ret = 0; - if (alsa_handle == NULL) { - - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - ret = actual_open_alsa_device(); - if (ret == 0) { - if (audio_alsa.volume) - do_volume(set_volume); - if (audio_alsa.mute) - do_mute(0); - } - - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex - } - 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. - - // Also, if the ignore_volume_setting is set, the audio is sent through unaltered, - // so, in that circumstance, don't add dither either. - - int use_dither = 0; - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && - (config.airplay_volume != 0.0)) - use_dither = 1; - - debug(1, "alsa: dither will %sbe added to inter-session silence.", use_dither ? "" : "not "); +void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); long buffer_size_threshold = (long)(config.audio_backend_silence_threshold * desired_sample_rate); @@ -1485,17 +1469,49 @@ void *alsa_buffer_monitor_thread_code(void *arg) { debug(1, "alsa: failed to allocate memory for a silent frame buffer, thus " "disable_standby_mode is \"off\"."); } else { - long buffer_size; - int reply; + pthread_cleanup_push(malloc_cleanup, silence); + while (1) { - if (config.keep_dac_busy != 0) { + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + + // 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 + debug(1, "alsa: alsa_buffer_monitor_thread_code -- opening the output device"); + do_open(); + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_connected"); + } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { + stall_monitor_start_time = 0; + frame_index = 0; + measurement_data_is_valid = 0; + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); + do_close(); + debug(1, + "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); + } + // now, if the backend is not in the abm_disconnected state + // and config.keep_dac_busy is true (at the present, this has to be the case to be in the + // abm_connected state in the first place...) then do the silence-filling thing, if needed + if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0)) { + int use_dither = 0; + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && + (config.airplay_volume != 0.0)) + use_dither = 1; + + long buffer_size = 0; + int reply; + uint64_t present_time = get_absolute_time_in_fp(); if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { // ((present_time > most_recent_write_time) && // ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - reply = delay(&buffer_size); + + snd_pcm_state_t state; + + reply = delay_and_status(&state, &buffer_size); + if (reply != 0) { buffer_size = 0; char errorstring[1024]; @@ -1504,25 +1520,23 @@ void *alsa_buffer_monitor_thread_code(void *arg) { (char *)errorstring); } if (buffer_size < buffer_size_threshold) { - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0)) - use_dither = 1; - else - use_dither = 0; dither_random_number_store = generate_zero_frames( silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither dither_random_number_store); // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", // frames_of_silence,most_recent_write_time); - play(silence, frames_of_silence); + do_play(silence, frames_of_silence); } } else { // debug(2,"Skipping sending silence"); } } + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex usleep(sleep_time_ms * 1000); // has a cancellation point in it // pthread_testcancel(); } + pthread_cleanup_pop(1); // should never happen } - pthread_cleanup_pop(1); pthread_exit(NULL); -} +} \ No newline at end of file From 0697a40b08ae76f91d612106b985cfbe87dac6d9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 22:21:43 +0000 Subject: [PATCH 068/115] fix crypto library flags --- metadata_hub.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/metadata_hub.c b/metadata_hub.c index 17b221a8..f6c92ae8 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -45,6 +45,18 @@ #include "dacp.h" #include "metadata_hub.h" +#ifdef CONFIG_MBEDTLS +#include +#endif + +#ifdef CONFIG_POLARSSL +#include +#endif + +#ifdef CONFIG_OPENSSL +#include +#endif + pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store @@ -207,7 +219,7 @@ char *metadata_write_image_file(const char *buf, int len) { uint8_t img_md5[16]; // uint8_t ap_md5[16]; -#ifdef CONFIG_SSL +#ifdef CONFIG_OPENSSL MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, buf, len); From 9fb37e4293b72c036a01993f76c7b7792905213f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 23 Jan 2019 22:22:27 +0000 Subject: [PATCH 069/115] Reorganise code to make it respond dynamically to changes in settings. --- audio_alsa.c | 142 +++++++++++++++++++++++---------------------------- 1 file changed, 65 insertions(+), 77 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 871ac86a..5582202c 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -94,7 +94,7 @@ static unsigned int desired_sample_rate; static enum sps_format_t sample_format; int frame_size; // in bytes for interleaved stereo -static snd_pcm_t *alsa_handle = NULL; +snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; static snd_ctl_t *ctl = NULL; @@ -219,6 +219,7 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v } void actual_close_alsa_device() { + debug(1, "actual close"); if (alsa_handle) { int derr; if ((derr = snd_pcm_hw_free(alsa_handle))) @@ -1247,6 +1248,7 @@ int do_close() { debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); // flush also closes the device + debug(1, "closing alsa handle"); if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); alsa_handle = NULL; @@ -1300,7 +1302,7 @@ static void flush(void) { debug(1, "alsa: flush() -- alsa_backend_state => abm_disconnected."); } } else - debug(1, "alac: flush() -- called on a disconnected alsa backend"); + debug(1, "alsa: flush() -- called on a disconnected alsa backend"); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex } @@ -1452,91 +1454,77 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a */ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { - - int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - long buffer_size_threshold = (long)(config.audio_backend_silence_threshold * desired_sample_rate); - - uint64_t sleep_time_in_fp = sleep_time_ms; - sleep_time_in_fp = sleep_time_in_fp << 32; - sleep_time_in_fp = sleep_time_in_fp / 1000; - // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp form.",sleep_time_ms,sleep_time_in_fp); - // int frames_of_silence = (desired_sample_rate * sleep_time_ms * 2) / 1000; - int frames_of_silence = 1024; - 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, "alsa: failed to allocate memory for a silent frame buffer, thus " - "disable_standby_mode is \"off\"."); - } else { - pthread_cleanup_push(malloc_cleanup, silence); - - while (1) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - - // 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 - debug(1, "alsa: alsa_buffer_monitor_thread_code -- opening the output device"); - do_open(); + while (1) { + int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + // 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 + debug(1, "alsa: alsa_buffer_monitor_thread_code -- opening the output device"); + if (do_open() == 0) debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_connected"); - } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { - stall_monitor_start_time = 0; - frame_index = 0; - measurement_data_is_valid = 0; - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); - do_close(); - debug(1, - "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); - } - // now, if the backend is not in the abm_disconnected state - // and config.keep_dac_busy is true (at the present, this has to be the case to be in the - // abm_connected state in the first place...) then do the silence-filling thing, if needed - if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0)) { - int use_dither = 0; - if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && - (config.airplay_volume != 0.0)) - use_dither = 1; - - long buffer_size = 0; - int reply; - - uint64_t present_time = get_absolute_time_in_fp(); - - if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { - - // ((present_time > most_recent_write_time) && - // ((present_time - most_recent_write_time) > (sleep_time_in_fp)))) { - - snd_pcm_state_t state; - - reply = delay_and_status(&state, &buffer_size); - - if (reply != 0) { - buffer_size = 0; - char errorstring[1024]; - strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); - debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, - (char *)errorstring); - } - if (buffer_size < buffer_size_threshold) { + } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) { + stall_monitor_start_time = 0; + frame_index = 0; + measurement_data_is_valid = 0; + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); + do_close(); + debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); + } + // now, if the backend is not in the abm_disconnected state + // and config.keep_dac_busy is true (at the present, this has to be the case to be in the + // abm_connected state in the first place...) then do the silence-filling thing, if needed + if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0)) { + int reply; + long buffer_size = 0; + snd_pcm_state_t state; + uint64_t present_time = get_absolute_time_in_fp(); + if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) { + reply = delay_and_status(&state, &buffer_size); + if (reply != 0) { + buffer_size = 0; + char errorstring[1024]; + strerror_r(-reply, (char *)errorstring, sizeof(errorstring)); + debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply, + (char *)errorstring); + } + long buffer_size_threshold = + (long)(config.audio_backend_silence_threshold * desired_sample_rate); + if (buffer_size < buffer_size_threshold) { + uint64_t sleep_time_in_fp = sleep_time_ms; + sleep_time_in_fp = sleep_time_in_fp << 32; + sleep_time_in_fp = sleep_time_in_fp / 1000; + // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp + // form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence = (desired_sample_rate * + // sleep_time_ms * 2) / 1000; + int frames_of_silence = 1024; + size_t size_of_silence_buffer = frames_of_silence * frame_size; + // debug(1, "alsa: alsa_buffer_monitor_thread_code -- silence buffer length: %u bytes.", + // size_of_silence_buffer); + void *silence = malloc(size_of_silence_buffer); + if (silence == NULL) { + debug(1, "alsa: alsa_buffer_monitor_thread_code -- failed to allocate memory for a " + "silent frame buffer."); + } else { + pthread_cleanup_push(malloc_cleanup, silence); + int use_dither = 0; + if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && + (config.airplay_volume != 0.0)) + use_dither = 1; dither_random_number_store = generate_zero_frames( silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither dither_random_number_store); // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", - // frames_of_silence,most_recent_write_time); + // frames_of_silence,most_recent_write_time); do_play(silence, frames_of_silence); + pthread_cleanup_pop(1); } - } else { - // debug(2,"Skipping sending silence"); } } - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex - usleep(sleep_time_ms * 1000); // has a cancellation point in it - // pthread_testcancel(); } - pthread_cleanup_pop(1); // should never happen + debug_mutex_unlock(&alsa_mutex, 3); + pthread_cleanup_pop(0); // release the mutex + usleep(sleep_time_ms * 1000); // has a cancellation point in it } pthread_exit(NULL); } \ No newline at end of file From b02e5d9f208019c2525dd944419b34b3bce71232 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 09:14:41 +0000 Subject: [PATCH 070/115] Use returned codes from do_open() to control the state machine -- don't go to abm_connected unless, eh, connected. --- audio_alsa.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 5582202c..d77bfffa 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1228,8 +1228,8 @@ int do_open() { do_volume(set_volume); if (audio_alsa.mute) do_mute(0); + alsa_backend_state = abm_connected; // only do this if it really opened it. } - alsa_backend_state = abm_connected; // only do this if it really opened it. } else { debug(1, "alsa: do_open() -- output device already open."); } @@ -1271,9 +1271,10 @@ int play(void *buf, int samples) { pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); if (alsa_backend_state == abm_disconnected) { - debug(1, "alsa: play() -- opening output device"); ret = do_open(); - } + if (ret == 0) + debug(1, "alsa: play() -- opened output device"); + } if (ret == 0) { if (alsa_backend_state != abm_playing) { @@ -1460,9 +1461,8 @@ 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 - debug(1, "alsa: alsa_buffer_monitor_thread_code -- opening the output device"); if (do_open() == 0) - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_connected"); + debug(1, "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)) { stall_monitor_start_time = 0; frame_index = 0; From 7a4949b32f147b90f2cc0cb2c803bb22dd8ea474 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 09:15:09 +0000 Subject: [PATCH 071/115] quieten a returned error-code message. --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index ca4b00df..5efb679c 100644 --- a/player.c +++ b/player.c @@ -2024,7 +2024,7 @@ void *player_thread_func(void *arg) { conn->connection_number); } } else - debug(2, "Delay error %d when checking running latency.", resp); + debug(3, "Delay error %d when checking running latency.", resp); } } From c06586cb852a0f5b7751705cffb24a83b3fa7701 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 09:20:11 +0000 Subject: [PATCH 072/115] Update README.md --- README.md | 542 +----------------------------------------------------- 1 file changed, 3 insertions(+), 539 deletions(-) diff --git a/README.md b/README.md index 0800ae38..05129d9d 100644 --- a/README.md +++ b/README.md @@ -6,544 +6,8 @@ Audio played by a Shairport Sync-powered device stays synchronised with the sour Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming. -This is the unstable "development" branch. Changes and updates are incorporated into this branch quickly. To access the stable version, where changes are made after due time, please switch to the "master" branch. +**Warning** -More Information ----------- -Shairport Sync offers *full audio synchronisation*, a feature of AirPlay that previous implementations do not provide. Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source. To accomplish this, Shairport Sync needs access to audio systems – such as `alsa` on Linux and `sndio` on FreeBSD – that provide very accurate timing information about output devices. Shairport Sync must have direct access to the output device used, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem). - -Alternatively, Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing information is not as accurate as that of `alsa` or `sndio`, it is often impractical to remove or disable PulseAudio. In that case, the `pa` backend can be used. An older backend for PulseAudio called `pulse` does not support synchronisation and is deprecated. - -For other use cases, Shairport Sync can provide synchronised audio output to a unix pipe or to standard output, or to audio systems that do not provide timing information. This could perhaps be described as *partial audio synchronisation*, where synchronised audio is provided by Shairport Sync, but what happens to it in the subsequent processing chain, before it reaches the listener's ear, is outside the control of Shairport Sync. - -For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. - -Synchronisation, Latency, "Stuffing" ---------- -The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on. - -As mentioned previously, Shairport Sync implements full audio synchronisation when used with `alsa`, `sndio` or PulseAudio systems. This is done by monitoring the timestamps present in data coming from the audio source and the timing information from the audio system, e.g. `alsa`. To maintain the latency required for exact synchronisation, if the output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”. - -Stuffing is not done for partial audio synchronisation – the audio samples are simply presented at exactly the right time to the next stage in the processing chain. - -Timestamps are referenced relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. So, another thing Shairport Sync has to do is to synchronize the source clock and the local clock, and it does this usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. - -What else? --------------- -* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. -* Hardware Mute — Shairport Sync can mute properly if the hardware supports it. -* Support for the Apple ALAC decoder. -* Output bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits. -* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds with `alsa`, 0.35 seconds with `sndio`. -* Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) -* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. -* A native [MQTT](https://en.wikipedia.org/wiki/MQTT) interface for metadata and/or controls. -* Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync. -* Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available. - -Heritage -------- -Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/development/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. - -Status ------- -Shairport Sync works on a wide variety of Linux devices and FreeBSD. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Stretch, Jessie and Wheezy, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. - -Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normally runs in the *user mode* but can be configured to run in *system mode*, though this is not recommended. Shairport Sync can work with it in either mode. - -Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card – see the note below on configuring the Raspberry Pi to make best use of it. It runs well on the Raspberry Pi Zero W with a suitable USB or I2S card. - -Shairport Sync runs natively on FreeBSD using the `sndio` sound system. - -Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux, Fedora and FreeBSD inside VMWare Fusion on a Mac, but synchronisation is inaccurate — possibly because the sound card is being emulated. - -In addition, Shairport Sync can output to standard output, pipes, `soundio` and `ao` devices using appropriate backends. - -For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution. - -Building And Installing the Development Version ---------------------- -See [here](https://github.com/mikebrady/shairport-sync/blob/development/INSTALL.md) for simple installation instructions for modern Linux systems like Rapsberry Pis. - -The following procedures will build and install the `shairport-sync` application into your system. - -**Remove Old Versions of Shairport Sync and its Startup Scripts** - -You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. - -You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary. - -**FreeBSD** - -To build Shairport Sync from sources on FreeBSD please refer to [FREEBSD.md](https://github.com/mikebrady/shairport-sync/blob/master/FREEBSD.md). - -**Determine The Configuration Needed** - -Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary: -``` -$ pactl info -Server String: unix:/run/user/1000/pulse/native -Library Protocol Version: 30 -Server Protocol Version: 30 -Is Local: yes -Client Index: 9 -Tile Size: 65472 -User Name: mike -Host Name: ubuntu -Server Name: pulseaudio -Server Version: 8.0 -Default Sample Specification: s16le 2ch 44100Hz -Default Channel Map: front-left,front-right -Default Sink: alsa_output.pci-0000_02_02.0.analog-stereo -Default Source: alsa_input.pci-0000_02_02.0.analog-stereo -Cookie: 96f9:3e8d -$ -``` -If PulseAudio in not installed, you'll get something like this: -``` -$ pactl info --bash: pactl: command not found -$ -``` -If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend. - -If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it. - -**Building** - -To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions. - -The following libraries are required: -* OpenSSL or mbed TLS (PolarSSL is supported but deprecated) -* Avahi -* ALSA and/or PulseAudio -* libdaemon -* autoconf -* automake -* libtool -* libpopt -* libconfig - -Optional: -* libsoxr -* libalac (This is a library containing the Apple ALAC decoder.) -* libmosquitto (This is to enable the mqtt-metadata-interface.) - -Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. - -Debian, Ubuntu and Raspbian users can get the basics with: - -- `# apt-get install build-essential git xmltoman` – these may already be installed. -- `# apt-get install autoconf automake libtool libdaemon-dev libpopt-dev libconfig-dev` -- `# apt-get install libasound2-dev` for the ALSA libraries -- `# apt-get install libpulse-dev` for the PulseAudio libraries -- `# apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended). -- `# apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise. -- `# apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. -- `# apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md). - -If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information. - -**Download Shairport Sync:** -``` -$ git clone https://github.com/mikebrady/shairport-sync.git -``` - -Next, `cd` into the shairport-sync directory and execute the following commands: -``` -$ git checkout development -$ autoreconf -i -f -``` -(Note that the `autoreconf...` step may take some time on less powerful machines.) - -**Choose the appropriate `--with-*` options:** - -(Don't worry -- there's a recommended set of configuration options further down.) - -- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality. -- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed. -- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout). -- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe. -- `--with-soundio` include an optional backend module to enable raw audio to be output through the soundio system. -- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15. -- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL. -- `--with-soxr` for libsoxr-based resampling. -- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location. -- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. -- `--with-mqtt-client` to add support for publishing metadata and remote control via [MQTT](https://en.wikipedia.org/wiki/MQTT). -- `--with-configfiles` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten. -- `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use. -- `--with-apple-alac` to include the Apple ALAC Decoder. -- `--with-convolution` to include a convolution filter that can be used to apply effects such as frequency and phase correction, and a loudness filter that compensates for human ear non-linearity. Requires `libsndfile`. -- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. Default is not to to install. -- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. Default is not to to install. - -**Determine if it's a `systemd` or a "System V" installation:** - -If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.) - -There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system. - -The easiest way to look at the first few lines of the `init` manual. Enter the command: - -``` -$ man init -``` -In a `systemd` system, the top lines of the `man` page make it clear that it's a `systemd` system, as follows (from Ubuntu 16.04): -``` -SYSTEMD(1) systemd SYSTEMD(1) - -NAME - systemd, init - systemd system and service manager -... -``` -Other init systems will look considerably different. For instance, in an `upstart` system, the top lines of the `man` page indicate it's using `upstart` system, as follows (from Ubuntu 14.04): - -``` -init(8) System Manager's Manual init(8) - -NAME - init - Upstart process management daemon - -SYNOPSIS - init [OPTION]... -... -``` -In a System V system, the top lines of the `man` page are as follows (from Debian 7.11): -``` -INIT(8) Linux System Administrator's Manual INIT(8) - -NAME - init, telinit - process control initialization - -SYNOPSIS - /sbin/init [ -a ] [ -s ] [ -b ] [ -z xxx ] [ 0123456Ss ] - /sbin/telinit [ -t SECONDS ] [ 0123456sSQqabcUu ] - /sbin/telinit [ -e VAR[=VAL] ] - -... -``` -If your system is definitely a `systemd` system, choose `--with-systemd` below. Otherwise, choose `--with-systemv`. - -**Choose the location of the configuration file** - -A final consideration is the location of the configuration file `shairport-sync.conf`. This will be placed in the directory specified by the `sysconfdir` configuration variable, which defaults to `/usr/local/etc`. This is normal in BSD Unixes, but is unusual in Linux. Hence, for Linux installations, you need to set the `sysconfdir` variable to `/etc` using the configuration setting `--sysconfdir=/etc`. - -**Sample `./configure` command with parameters for a typical Linux `systemd` installation:** - -Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and later, and Raspbian Stretch and Jessie. It specifies both the ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot: - -`$ ./configure --sysconfdir=/etc --with-alsa --with-pa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd` - -* Omit the `--with-soxr` if the libsoxr library is not available. -* For installation into a System V system, replace the `--with-systemd` with `--with-systemv`. -* If you intend to use Shairport Sync with PulseAudio in the standard user mode, it can not be a system service, so you should omit both `--with-systemd` and `--with-systemv`. - -**Build and Install the Application:** - -Enter: - -``` -$ make -``` - -to build the application. - -Assuming you have used the `./configure` settings suggested above, you can now install the application: - -``` -$ sudo make install -``` -The user and group `shairport-sync` will be created if necessary, and a runtime folder will be created at `/var/run/shairport-sync` if you have chosen `--with-systemv`. In addition, a `man` page, a default configuration file and automatic power-on startup script will be installed. - - -**Complete installation into to a `systemd` system** - -If you have chosen the `--with-systemd` configuration option, then, to enable Shairport Sync to start automatically at system startup, enter: - -``` -$ sudo systemctl enable shairport-sync -``` - -**Complete installation into a System V system** - -If you have chosen the `--with-systemv` configuration option, enter: -``` -$ sudo update-rc.d shairport-sync defaults 90 10 -``` - -**Man Page** - -You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html - -Configuring Shairport Sync --------- -There are two logically distinct parts to getting Shairport Sync to run properly on your machine: (1) starting and stopping it and (2) ensuring it has the right settings. - -**(1) Starting and Stopping:** -Starting and stopping Shairport Sync automatically is taken care of differently in different versions of Linux – see the previous section for an example of installing into a `systemd` or a System V based system. - -If you are using PulseAudio in the standard "user" mode, you can not start Shairport Sync automatically at boot time – it must be started when the user logs in to a GUI session. Various GUI tools exist to enable you to build a Startup Application. However, there does not appear to be a similar tool for terminating the program when you log out. Also, it the GUI switches to another user, the user's PulseAudio session is disconnected from the output device. - -**(2) Settings:** -To get the best from Shairport Sync, you’ll need to (a) give Shairport Sync a service name by which it will be seen in iTunes etc. and (b) specify the backend you wish to use - `alsa` for the ALSA backend, or `pa` for the PulseAudio back end. If only one backend is included at compilation, or if the backend is ALSA, there is no need to explicitly specify the backend. - -For the ALSA backend you may need to (c) specify the output device to use and (d) specify the name of the mixer volume control to use to control the output level. To get values for (b) and (c) you might need to explore the ALSA output devices with a program like `alsamixer` or similar. - -Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card. - -Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room`, give the service the password `secret` and use `libsoxr` interpolation, then you should do the following: - -``` -general = -{ - name = "Front Room"; - password = "secret"; - interpolation = "soxr"; - // ... other general settings -}; -``` -(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) - -**Important:** You should *never* use an important password as the AirPlay password for a Shairport Sync player -- the password is stored in Shairport Sync's configuration file in plain text and is thus completely vulnerable. - -No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add: - -``` - output_backend = "pa"; -``` -to the `general` stanza. - -The `alsa` group is used to specify properties of the output device. The most obvious setting is the name of the output device which you can set using the `output_device` tag. - -The following `alsa` group settings are very important for maximum performance. If your audio device has a mixer that can be use to control the volume, then Shairport Sync can use it to give instant response to volume and mute commands and it can offload some work from the processor. -* The `mixer_control_name` tag allows you to specify the name of the mixer volume control. -* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is on the `output_device`, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`: - -``` -alsa = -{ - output_device = "hw:0,5"; - mixer_device = "hw:0"; - mixer_control_name = "PCM"; - // ... other alsa settings -}; -``` - -The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. You can also set the sink name that audio will be pushed to. Here's an example `pa` group configuration: - -``` -pa = -{ - application_name = "Shairport Sync - Bedroom"; - sink = "server_bedroom"; // Sink name or ID. If not specified, the default sink will be used. -}; -``` - -Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing. - -Note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. (This behaviour may be different from other Shairports.) - -Shairport Sync can run a program whenever the volume is set or changed. You specify them using the `general` group settings `run_this_when_volume_changes`. This is to facilitate situations where something has to be done when the volume changes, e.g. adjust an external mixer value. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the command has been completed before continuing. Again, please note that the full path to the program must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. - -Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method. - -**Raspberry Pi** - -The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line: -``` -audio_pwm_mode=2 -``` -to `/boot/config.txt` and reboot. -Apart from a loud click when used for the first time after power-up, it is quite adequate for casual listening. A problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 50 dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 50, the top 50 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -46dB, below which muting will occur. - -From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume. - -Another setting to consider is the `general` `drift_tolerance_in_seconds` setting: you should set it to a larger tolerance, say 10 milliseconds – `drift_tolerance_in_seconds=0.010;` – to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC. - -*Command Line Arguments* - -As previously mentioned, you can use command line arguments to provide settings to Shairport Sync as before, though newer settings will only be available via the configuration file. For full information, please read the Shairport Sync `man` page, also available at http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html. - -Apart from the following options, all command line options can be replaced by settings in the configuration file. Here is a brief description of command line options that are not replicated by settings in the settings file. - -* The `-c` option allows you to specify the location of the configuration file. -* The `-V` option gives you version information about Shairport Sync and then quits. -* The `-d` option causes Shairport Sync to properly daemonise itself, that is, to run in the background. You may need sudo privileges for this. -* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this. - -The System V init script at `/etc/init.d/shairport-sync` has a bare minimum : -`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file. - -Examples --------- - -Here are some examples of complete configuration files. - -``` -general = { - name = "Joe's Stereo"; -}; - -alsa = { - output_device = "hw:0"; -}; -``` - -This gives the service a particular name — "Joe's Stereo" and specifies that audio device hw:0 be used. - -For best results with the `alsa` backend — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the mixer control to be used as the `mixer_control_name`. - -Here is an example for for a Raspberry Pi using its internal soundcard — device hw:0 — that drives the headphone jack: -``` -general = { - name = "Mike's Boombox"; -}; - -alsa = { - output_device = "hw:0"; - mixer_control_name = "PCM"; -}; -``` - -Here is an example of using soxr-based resampling and driving a Topping TP30 Digital Amplifier, which has an integrated USB DAC and which is connected as audio device `hw:1`: -``` -general = { - name = "Kitchen"; - interpolation = "soxr"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "PCM"; -}; -``` - -For a cheapo "3D Sound" USB card (Stereo output and input only) on a Raspberry Pi: -``` -general = { - name = "Front Room"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "Speaker"; -}; -``` - -For a first generation Griffin iMic on a Raspberry Pi: -``` -general = { - name = "Attic"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "PCM"; -}; -``` - -For an NSLU2, which has no internal sound card, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus: - -On an NSLU2, to drive a first generation Griffin iMic: -``` -general = { - name = "Den"; -}; - -alsa = { - mixer_control_name = "PCM"; -}; -``` - -On an NSLU2, to drive the "3D Sound" USB card: -``` -general = { - name = "TV Room"; -}; - -alsa = { - mixer_control_name = "Speaker"; -}; -``` - -Finally, here is an example of using the PulseAudio backend: -``` -general = { - name = "Zoe's Computer"; - output_backend = "pa"; -}; - -``` - - -Metadata --------- - -Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. - -**Metadata broadcasting over UDP** - -As an alternative to sending metadata to a pipe, the `socket_address` and `socket_port` tags may be set in the metadata group to cause Shairport Sync to broadcast UDP packets containing the track metadata. - -The advantage of UDP is that packets can be sent to a single listener or, if a multicast address is used, to multiple listeners. It also allows metadata to be routed to a different host. However UDP has a maximum packet size of about 65000 bytes; while large enough for most data, Cover Art will often exceed this value. Any metadata exceeding this limit will not be sent over the socket interface. The maximum packet size may be set with the `socket_msglength` tag to any value between 500 and 65000 to control this - lower values may be used to ensure that each UDP packet is sent in a single network frame. The default is 500. Other than this restriction, metadata sent over the socket interface is identical to metadata sent over the pipe interface. - -The UDP metadata format is very simple - the first four bytes are the metadata *type*, and the next four bytes are the metadata *code* (both are sent in network byte order - see https://github.com/mikebrady/shairport-sync-metadata-reader for a definition of those terms). The remaining bytes of the packet, if any, make up the raw value of the metadata. - -Latency -------- -Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. - -Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them. - -Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset_in_seconds`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -0.1. - -Resynchronisation -------------- -Shairport Sync actively maintains synchronisation with the source. -If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above. -* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting. - -Tolerance ---------- -Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections. -* You can vary the tolerance with the `general` `drift_tolerance_in_seconds` setting. - -Some Statistics ---------------- -If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file: -``` -sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy -``` -This will be followed by the statistics themselves at regular intervals, for example: -``` - -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 - -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267 - -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268 - -2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267 - -2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268 - -2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267 - -2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267 - -2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266 -``` - -"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. - -"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm. - -"Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. - -For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+. - -It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... - -"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up. - -WiFi Issues ---------- -If you are using WiFi, you should ensure that WiFi power management is off. See [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for more details. - -Troubleshooting ---------------- -Please refer to [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for a few hints, contributed by users. +This is the very unstable "transit" branch. It is used to move unfinished or untested code between development sites. +To access the stable version, where changes are made after due time, please switch to the "master" branch. From 8a8b59ad8debb058aefb57a47508d723f05d6929 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 09:23:51 +0000 Subject: [PATCH 073/115] Update README.md --- README.md | 542 +----------------------------------------------------- 1 file changed, 2 insertions(+), 540 deletions(-) diff --git a/README.md b/README.md index 0800ae38..9324e75c 100644 --- a/README.md +++ b/README.md @@ -6,544 +6,6 @@ Audio played by a Shairport Sync-powered device stays synchronised with the sour Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming. -This is the unstable "development" branch. Changes and updates are incorporated into this branch quickly. To access the stable version, where changes are made after due time, please switch to the "master" branch. - -More Information ----------- -Shairport Sync offers *full audio synchronisation*, a feature of AirPlay that previous implementations do not provide. Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source. To accomplish this, Shairport Sync needs access to audio systems – such as `alsa` on Linux and `sndio` on FreeBSD – that provide very accurate timing information about output devices. Shairport Sync must have direct access to the output device used, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem). - -Alternatively, Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing information is not as accurate as that of `alsa` or `sndio`, it is often impractical to remove or disable PulseAudio. In that case, the `pa` backend can be used. An older backend for PulseAudio called `pulse` does not support synchronisation and is deprecated. - -For other use cases, Shairport Sync can provide synchronised audio output to a unix pipe or to standard output, or to audio systems that do not provide timing information. This could perhaps be described as *partial audio synchronisation*, where synchronised audio is provided by Shairport Sync, but what happens to it in the subsequent processing chain, before it reaches the listener's ear, is outside the control of Shairport Sync. - -For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki. - -Synchronisation, Latency, "Stuffing" ---------- -The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on. - -As mentioned previously, Shairport Sync implements full audio synchronisation when used with `alsa`, `sndio` or PulseAudio systems. This is done by monitoring the timestamps present in data coming from the audio source and the timing information from the audio system, e.g. `alsa`. To maintain the latency required for exact synchronisation, if the output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”. - -Stuffing is not done for partial audio synchronisation – the audio samples are simply presented at exactly the right time to the next stage in the processing chain. - -Timestamps are referenced relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. So, another thing Shairport Sync has to do is to synchronize the source clock and the local clock, and it does this usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols. - -What else? --------------- -* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. -* Hardware Mute — Shairport Sync can mute properly if the hardware supports it. -* Support for the Apple ALAC decoder. -* Output bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits. -* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds with `alsa`, 0.35 seconds with `sndio`. -* Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.) -* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. -* A native [MQTT](https://en.wikipedia.org/wiki/MQTT) interface for metadata and/or controls. -* Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync. -* Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available. - -Heritage -------- -Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/development/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0. - -Status ------- -Shairport Sync works on a wide variety of Linux devices and FreeBSD. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Stretch, Jessie and Wheezy, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. - -Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normally runs in the *user mode* but can be configured to run in *system mode*, though this is not recommended. Shairport Sync can work with it in either mode. - -Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card – see the note below on configuring the Raspberry Pi to make best use of it. It runs well on the Raspberry Pi Zero W with a suitable USB or I2S card. - -Shairport Sync runs natively on FreeBSD using the `sndio` sound system. - -Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux, Fedora and FreeBSD inside VMWare Fusion on a Mac, but synchronisation is inaccurate — possibly because the sound card is being emulated. - -In addition, Shairport Sync can output to standard output, pipes, `soundio` and `ao` devices using appropriate backends. - -For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution. - -Building And Installing the Development Version ---------------------- -See [here](https://github.com/mikebrady/shairport-sync/blob/development/INSTALL.md) for simple installation instructions for modern Linux systems like Rapsberry Pis. - -The following procedures will build and install the `shairport-sync` application into your system. - -**Remove Old Versions of Shairport Sync and its Startup Scripts** - -You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere. - -You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary. - -**FreeBSD** - -To build Shairport Sync from sources on FreeBSD please refer to [FREEBSD.md](https://github.com/mikebrady/shairport-sync/blob/master/FREEBSD.md). - -**Determine The Configuration Needed** - -Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary: -``` -$ pactl info -Server String: unix:/run/user/1000/pulse/native -Library Protocol Version: 30 -Server Protocol Version: 30 -Is Local: yes -Client Index: 9 -Tile Size: 65472 -User Name: mike -Host Name: ubuntu -Server Name: pulseaudio -Server Version: 8.0 -Default Sample Specification: s16le 2ch 44100Hz -Default Channel Map: front-left,front-right -Default Sink: alsa_output.pci-0000_02_02.0.analog-stereo -Default Source: alsa_input.pci-0000_02_02.0.analog-stereo -Cookie: 96f9:3e8d -$ -``` -If PulseAudio in not installed, you'll get something like this: -``` -$ pactl info --bash: pactl: command not found -$ -``` -If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend. - -If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it. - -**Building** - -To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions. - -The following libraries are required: -* OpenSSL or mbed TLS (PolarSSL is supported but deprecated) -* Avahi -* ALSA and/or PulseAudio -* libdaemon -* autoconf -* automake -* libtool -* libpopt -* libconfig - -Optional: -* libsoxr -* libalac (This is a library containing the Apple ALAC decoder.) -* libmosquitto (This is to enable the mqtt-metadata-interface.) - -Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up. - -Debian, Ubuntu and Raspbian users can get the basics with: - -- `# apt-get install build-essential git xmltoman` – these may already be installed. -- `# apt-get install autoconf automake libtool libdaemon-dev libpopt-dev libconfig-dev` -- `# apt-get install libasound2-dev` for the ALSA libraries -- `# apt-get install libpulse-dev` for the PulseAudio libraries -- `# apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended). -- `# apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise. -- `# apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. -- `# apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md). - -If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information. - -**Download Shairport Sync:** -``` -$ git clone https://github.com/mikebrady/shairport-sync.git -``` - -Next, `cd` into the shairport-sync directory and execute the following commands: -``` -$ git checkout development -$ autoreconf -i -f -``` -(Note that the `autoreconf...` step may take some time on less powerful machines.) - -**Choose the appropriate `--with-*` options:** - -(Don't worry -- there's a recommended set of configuration options further down.) - -- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality. -- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed. -- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout). -- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe. -- `--with-soundio` include an optional backend module to enable raw audio to be output through the soundio system. -- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15. -- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL. -- `--with-soxr` for libsoxr-based resampling. -- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location. -- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader. -- `--with-mqtt-client` to add support for publishing metadata and remote control via [MQTT](https://en.wikipedia.org/wiki/MQTT). -- `--with-configfiles` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten. -- `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use. -- `--with-apple-alac` to include the Apple ALAC Decoder. -- `--with-convolution` to include a convolution filter that can be used to apply effects such as frequency and phase correction, and a loudness filter that compensates for human ear non-linearity. Requires `libsndfile`. -- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. Default is not to to install. -- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. Default is not to to install. - -**Determine if it's a `systemd` or a "System V" installation:** - -If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.) - -There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system. - -The easiest way to look at the first few lines of the `init` manual. Enter the command: - -``` -$ man init -``` -In a `systemd` system, the top lines of the `man` page make it clear that it's a `systemd` system, as follows (from Ubuntu 16.04): -``` -SYSTEMD(1) systemd SYSTEMD(1) - -NAME - systemd, init - systemd system and service manager -... -``` -Other init systems will look considerably different. For instance, in an `upstart` system, the top lines of the `man` page indicate it's using `upstart` system, as follows (from Ubuntu 14.04): - -``` -init(8) System Manager's Manual init(8) - -NAME - init - Upstart process management daemon - -SYNOPSIS - init [OPTION]... -... -``` -In a System V system, the top lines of the `man` page are as follows (from Debian 7.11): -``` -INIT(8) Linux System Administrator's Manual INIT(8) - -NAME - init, telinit - process control initialization - -SYNOPSIS - /sbin/init [ -a ] [ -s ] [ -b ] [ -z xxx ] [ 0123456Ss ] - /sbin/telinit [ -t SECONDS ] [ 0123456sSQqabcUu ] - /sbin/telinit [ -e VAR[=VAL] ] - -... -``` -If your system is definitely a `systemd` system, choose `--with-systemd` below. Otherwise, choose `--with-systemv`. - -**Choose the location of the configuration file** - -A final consideration is the location of the configuration file `shairport-sync.conf`. This will be placed in the directory specified by the `sysconfdir` configuration variable, which defaults to `/usr/local/etc`. This is normal in BSD Unixes, but is unusual in Linux. Hence, for Linux installations, you need to set the `sysconfdir` variable to `/etc` using the configuration setting `--sysconfdir=/etc`. - -**Sample `./configure` command with parameters for a typical Linux `systemd` installation:** - -Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and later, and Raspbian Stretch and Jessie. It specifies both the ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot: - -`$ ./configure --sysconfdir=/etc --with-alsa --with-pa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd` - -* Omit the `--with-soxr` if the libsoxr library is not available. -* For installation into a System V system, replace the `--with-systemd` with `--with-systemv`. -* If you intend to use Shairport Sync with PulseAudio in the standard user mode, it can not be a system service, so you should omit both `--with-systemd` and `--with-systemv`. - -**Build and Install the Application:** - -Enter: - -``` -$ make -``` - -to build the application. - -Assuming you have used the `./configure` settings suggested above, you can now install the application: - -``` -$ sudo make install -``` -The user and group `shairport-sync` will be created if necessary, and a runtime folder will be created at `/var/run/shairport-sync` if you have chosen `--with-systemv`. In addition, a `man` page, a default configuration file and automatic power-on startup script will be installed. - - -**Complete installation into to a `systemd` system** - -If you have chosen the `--with-systemd` configuration option, then, to enable Shairport Sync to start automatically at system startup, enter: - -``` -$ sudo systemctl enable shairport-sync -``` - -**Complete installation into a System V system** - -If you have chosen the `--with-systemv` configuration option, enter: -``` -$ sudo update-rc.d shairport-sync defaults 90 10 -``` - -**Man Page** - -You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html - -Configuring Shairport Sync --------- -There are two logically distinct parts to getting Shairport Sync to run properly on your machine: (1) starting and stopping it and (2) ensuring it has the right settings. - -**(1) Starting and Stopping:** -Starting and stopping Shairport Sync automatically is taken care of differently in different versions of Linux – see the previous section for an example of installing into a `systemd` or a System V based system. - -If you are using PulseAudio in the standard "user" mode, you can not start Shairport Sync automatically at boot time – it must be started when the user logs in to a GUI session. Various GUI tools exist to enable you to build a Startup Application. However, there does not appear to be a similar tool for terminating the program when you log out. Also, it the GUI switches to another user, the user's PulseAudio session is disconnected from the output device. - -**(2) Settings:** -To get the best from Shairport Sync, you’ll need to (a) give Shairport Sync a service name by which it will be seen in iTunes etc. and (b) specify the backend you wish to use - `alsa` for the ALSA backend, or `pa` for the PulseAudio back end. If only one backend is included at compilation, or if the backend is ALSA, there is no need to explicitly specify the backend. - -For the ALSA backend you may need to (c) specify the output device to use and (d) specify the name of the mixer volume control to use to control the output level. To get values for (b) and (c) you might need to explore the ALSA output devices with a program like `alsamixer` or similar. - -Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card. - -Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room`, give the service the password `secret` and use `libsoxr` interpolation, then you should do the following: - -``` -general = -{ - name = "Front Room"; - password = "secret"; - interpolation = "soxr"; - // ... other general settings -}; -``` -(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.) - -**Important:** You should *never* use an important password as the AirPlay password for a Shairport Sync player -- the password is stored in Shairport Sync's configuration file in plain text and is thus completely vulnerable. - -No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add: - -``` - output_backend = "pa"; -``` -to the `general` stanza. - -The `alsa` group is used to specify properties of the output device. The most obvious setting is the name of the output device which you can set using the `output_device` tag. - -The following `alsa` group settings are very important for maximum performance. If your audio device has a mixer that can be use to control the volume, then Shairport Sync can use it to give instant response to volume and mute commands and it can offload some work from the processor. -* The `mixer_control_name` tag allows you to specify the name of the mixer volume control. -* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is on the `output_device`, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`: - -``` -alsa = -{ - output_device = "hw:0,5"; - mixer_device = "hw:0"; - mixer_control_name = "PCM"; - // ... other alsa settings -}; -``` - -The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. You can also set the sink name that audio will be pushed to. Here's an example `pa` group configuration: - -``` -pa = -{ - application_name = "Shairport Sync - Bedroom"; - sink = "server_bedroom"; // Sink name or ID. If not specified, the default sink will be used. -}; -``` - -Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing. - -Note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. (This behaviour may be different from other Shairports.) - -Shairport Sync can run a program whenever the volume is set or changed. You specify them using the `general` group settings `run_this_when_volume_changes`. This is to facilitate situations where something has to be done when the volume changes, e.g. adjust an external mixer value. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the command has been completed before continuing. Again, please note that the full path to the program must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. - -Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method. - -**Raspberry Pi** - -The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line: -``` -audio_pwm_mode=2 -``` -to `/boot/config.txt` and reboot. -Apart from a loud click when used for the first time after power-up, it is quite adequate for casual listening. A problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 50 dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 50, the top 50 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -46dB, below which muting will occur. - -From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume. - -Another setting to consider is the `general` `drift_tolerance_in_seconds` setting: you should set it to a larger tolerance, say 10 milliseconds – `drift_tolerance_in_seconds=0.010;` – to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC. - -*Command Line Arguments* - -As previously mentioned, you can use command line arguments to provide settings to Shairport Sync as before, though newer settings will only be available via the configuration file. For full information, please read the Shairport Sync `man` page, also available at http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html. - -Apart from the following options, all command line options can be replaced by settings in the configuration file. Here is a brief description of command line options that are not replicated by settings in the settings file. - -* The `-c` option allows you to specify the location of the configuration file. -* The `-V` option gives you version information about Shairport Sync and then quits. -* The `-d` option causes Shairport Sync to properly daemonise itself, that is, to run in the background. You may need sudo privileges for this. -* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this. - -The System V init script at `/etc/init.d/shairport-sync` has a bare minimum : -`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file. - -Examples --------- - -Here are some examples of complete configuration files. - -``` -general = { - name = "Joe's Stereo"; -}; - -alsa = { - output_device = "hw:0"; -}; -``` - -This gives the service a particular name — "Joe's Stereo" and specifies that audio device hw:0 be used. - -For best results with the `alsa` backend — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the mixer control to be used as the `mixer_control_name`. - -Here is an example for for a Raspberry Pi using its internal soundcard — device hw:0 — that drives the headphone jack: -``` -general = { - name = "Mike's Boombox"; -}; - -alsa = { - output_device = "hw:0"; - mixer_control_name = "PCM"; -}; -``` - -Here is an example of using soxr-based resampling and driving a Topping TP30 Digital Amplifier, which has an integrated USB DAC and which is connected as audio device `hw:1`: -``` -general = { - name = "Kitchen"; - interpolation = "soxr"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "PCM"; -}; -``` - -For a cheapo "3D Sound" USB card (Stereo output and input only) on a Raspberry Pi: -``` -general = { - name = "Front Room"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "Speaker"; -}; -``` - -For a first generation Griffin iMic on a Raspberry Pi: -``` -general = { - name = "Attic"; -}; - -alsa = { - output_device = "hw:1"; - mixer_control_name = "PCM"; -}; -``` - -For an NSLU2, which has no internal sound card, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus: - -On an NSLU2, to drive a first generation Griffin iMic: -``` -general = { - name = "Den"; -}; - -alsa = { - mixer_control_name = "PCM"; -}; -``` - -On an NSLU2, to drive the "3D Sound" USB card: -``` -general = { - name = "TV Room"; -}; - -alsa = { - mixer_control_name = "Speaker"; -}; -``` - -Finally, here is an example of using the PulseAudio backend: -``` -general = { - name = "Zoe's Computer"; - output_backend = "pa"; -}; - -``` - - -Metadata --------- - -Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS. - -**Metadata broadcasting over UDP** - -As an alternative to sending metadata to a pipe, the `socket_address` and `socket_port` tags may be set in the metadata group to cause Shairport Sync to broadcast UDP packets containing the track metadata. - -The advantage of UDP is that packets can be sent to a single listener or, if a multicast address is used, to multiple listeners. It also allows metadata to be routed to a different host. However UDP has a maximum packet size of about 65000 bytes; while large enough for most data, Cover Art will often exceed this value. Any metadata exceeding this limit will not be sent over the socket interface. The maximum packet size may be set with the `socket_msglength` tag to any value between 500 and 65000 to control this - lower values may be used to ensure that each UDP packet is sent in a single network frame. The default is 500. Other than this restriction, metadata sent over the socket interface is identical to metadata sent over the pipe interface. - -The UDP metadata format is very simple - the first four bytes are the metadata *type*, and the next four bytes are the metadata *code* (both are sent in network byte order - see https://github.com/mikebrady/shairport-sync-metadata-reader for a definition of those terms). The remaining bytes of the packet, if any, make up the raw value of the metadata. - -Latency -------- -Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer. - -Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them. - -Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset_in_seconds`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -0.1. - -Resynchronisation -------------- -Shairport Sync actively maintains synchronisation with the source. -If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above. -* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting. - -Tolerance ---------- -Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections. -* You can vary the tolerance with the `general` `drift_tolerance_in_seconds` setting. - -Some Statistics ---------------- -If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file: -``` -sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy -``` -This will be followed by the statistics themselves at regular intervals, for example: -``` - -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 - -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267 - -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268 - -2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267 - -2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268 - -2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267 - -2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267 - -2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266 -``` - -"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. - -"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm. - -"Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. - -For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+. - -It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... - -"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up. - -WiFi Issues ---------- -If you are using WiFi, you should ensure that WiFi power management is off. See [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for more details. - -Troubleshooting ---------------- -Please refer to [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for a few hints, contributed by users. +**Warning** +This is the "unstable" branch. It is used for very preliminary unfinished or untested code and can change at any time and without warning. From c807513422b9344ac4439235253f1bd37ba0df1c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 24 Jan 2019 16:33:50 +0000 Subject: [PATCH 074/115] quieten a few debug messages, bring a standard one forward. --- audio_alsa.c | 16 ++++++++-------- rtsp.c | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index d77bfffa..b71d3b6b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1248,7 +1248,7 @@ int do_close() { debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); // flush also closes the device - debug(1, "closing alsa handle"); + debug(2, "alsa: do_close() -- closing alsa handle"); if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); alsa_handle = NULL; @@ -1273,12 +1273,12 @@ int play(void *buf, int samples) { if (alsa_backend_state == abm_disconnected) { ret = do_open(); if (ret == 0) - debug(1, "alsa: play() -- opened output device"); + debug(2, "alsa: play() -- opened output device"); } if (ret == 0) { if (alsa_backend_state != abm_playing) { - debug(1, "alsa: play() -- alsa_backend_state => abm_playing"); + debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; } ret = do_play(buf, samples); @@ -1295,15 +1295,15 @@ static void flush(void) { do_mute(1); if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy != 0) { - debug(1, "alsa: flush() -- alsa_backend_state => abm_connected."); + debug(2, "alsa: flush() -- alsa_backend_state => abm_connected."); alsa_backend_state = abm_connected; } else { - debug(1, "alsa: flush() -- closing the output device"); + debug(2, "alsa: flush() -- closing the output device"); do_close(); // will change the state to disconnected - debug(1, "alsa: flush() -- alsa_backend_state => abm_disconnected."); + debug(2, "alsa: flush() -- alsa_backend_state => abm_disconnected."); } } else - debug(1, "alsa: flush() -- called on a disconnected alsa backend"); + debug(3, "alsa: flush() -- called on a disconnected alsa backend"); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex } @@ -1527,4 +1527,4 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { usleep(sleep_time_ms * 1000); // has a cancellation point in it } pthread_exit(NULL); -} \ No newline at end of file +} diff --git a/rtsp.c b/rtsp.c index d854ae1b..f7c6acce 100644 --- a/rtsp.c +++ b/rtsp.c @@ -981,7 +981,7 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { msg_add_header(resp, "Session", "1"); resp->respcode = 200; // it all worked out okay - debug(2, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", + debug(1, "Connection %d: SETUP with UDP ports Control: %d, Timing: %d and Audio: %d.", conn->connection_number, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); From c998400f7cf07dbd4780e6bc8e70903cbe54156f Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 26 Jan 2019 15:52:21 +0000 Subject: [PATCH 075/115] restore includes of openssl/mbedtls/polarssl includes --- metadata_hub.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/metadata_hub.c b/metadata_hub.c index 17b221a8..f399f200 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -45,6 +45,18 @@ #include "dacp.h" #include "metadata_hub.h" +#ifdef CONFIG_MBEDTLS +#include +#endif + +#ifdef CONFIG_POLARSSL +#include +#endif + +#ifdef CONFIG_OPENSSL +#include +#endif + pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store From 4188eba571939f4b7434309f34ba15b395854289 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 13:48:06 +0000 Subject: [PATCH 076/115] Fix a cause of a memory leak using OpenSSL for Base 64 encoding. --- common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common.c b/common.c index 8430c29d..043618f1 100644 --- a/common.c +++ b/common.c @@ -2,7 +2,7 @@ * Utility routines. This file is part of Shairport. * Copyright (c) James Laird 2013 * The volume to attenuation function vol2attn copyright (c) Mike Brady 2014 - * Further changes (c) Mike Brady 2014 -- 2018 + * Further changes and additions (c) Mike Brady 2014 -- 2019 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -415,7 +415,7 @@ char *base64_enc(uint8_t *input, int length) { buf[bptr->length - 1] = 0; } - BIO_free_all(bmem); + BIO_free_all(b64); pthread_setcancelstate(oldState, NULL); return buf; @@ -444,7 +444,6 @@ uint8_t *base64_dec(char *input, int *outlen) { nread = BIO_read(b64, buf, bufsize); - // BIO_free_all(bmem); BIO_free_all(b64); *outlen = nread; @@ -500,6 +499,7 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { default: die("bad rsa mode"); } + RSA_free(rsa); pthread_setcancelstate(oldState, NULL); return out; } From 1f67f66735c723d3f934fd0fc605bd2151743243 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 13:49:39 +0000 Subject: [PATCH 077/115] use the correct label -- CONFIG_OPENSSL -- rather than the incorrect CONFIG_SSL for including code to create a filename using the OpenSSL library. --- metadata_hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata_hub.c b/metadata_hub.c index f399f200..f6c92ae8 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -219,7 +219,7 @@ char *metadata_write_image_file(const char *buf, int len) { uint8_t img_md5[16]; // uint8_t ap_md5[16]; -#ifdef CONFIG_SSL +#ifdef CONFIG_OPENSSL MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, buf, len); From ea770b2b13df95cb00e30366855917b78c0290ce Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 13:50:54 +0000 Subject: [PATCH 078/115] Remove some unnecessary code "protecting" some use of the OpenSSL library. --- shairport.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shairport.c b/shairport.c index 292acf25..15f8b9c8 100644 --- a/shairport.c +++ b/shairport.c @@ -2,7 +2,7 @@ * Shairport, an Apple Airplay receiver * Copyright (c) James Laird 2013 * All rights reserved. - * Modifications (c) Mike Brady 2014--2018 + * Modifications and additions (c) Mike Brady 2014--2019 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -1602,14 +1602,10 @@ int main(int argc, char **argv) { uint8_t ap_md5[16]; #ifdef CONFIG_OPENSSL - // can't imagine this is necessary, but here it is anyway - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, config.service_name, strlen(config.service_name)); MD5_Final(ap_md5, &ctx); - pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS From 7639bfadbf87977d1524206cec5cc33c17bac358 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 13:52:21 +0000 Subject: [PATCH 079/115] Fix a number of memory leaks and one #include error when using the OpenSSL library. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 530bf763..af9268e8 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d48], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d49], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 56bf8862066d9d4974f2539f0bfbbeb6806e74bd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 14:19:39 +0000 Subject: [PATCH 080/115] gracefully handle situation when the output device is already in use; release it properly when finished. --- audio_alsa.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index d77bfffa..0b2a91e1 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -634,7 +634,7 @@ static int init(int argc, char **argv) { // set up default values first alsa_backend_state = abm_disconnected; // startup state - debug(1, "alsa: init() -- alsa_backend_state => abm_disconnected."); + debug(2, "alsa: init() -- alsa_backend_state => abm_disconnected."); set_period_size_request = 0; set_buffer_size_request = 0; config.alsa_use_hardware_mute = 0; // don't use it by default @@ -1166,7 +1166,7 @@ int do_play(void *buf, int samples) { } const uint64_t start_measurement_from_this_frame = - (2 * config.output_rate) / 352; // two seconds of framesÉ + (2 * config.output_rate) / 352; // two seconds of frames frame_index++; @@ -1248,7 +1248,7 @@ int do_close() { debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr)); // flush also closes the device - debug(1, "closing alsa handle"); + debug(2, "closing alsa handle"); if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); alsa_handle = NULL; @@ -1273,12 +1273,12 @@ int play(void *buf, int samples) { if (alsa_backend_state == abm_disconnected) { ret = do_open(); if (ret == 0) - debug(1, "alsa: play() -- opened output device"); + debug(2, "alsa: play() -- opened output device"); } if (ret == 0) { if (alsa_backend_state != abm_playing) { - debug(1, "alsa: play() -- alsa_backend_state => abm_playing"); + debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; } ret = do_play(buf, samples); @@ -1295,15 +1295,15 @@ static void flush(void) { do_mute(1); if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy != 0) { - debug(1, "alsa: flush() -- alsa_backend_state => abm_connected."); + debug(2, "alsa: flush() -- alsa_backend_state => abm_connected."); alsa_backend_state = abm_connected; } else { - debug(1, "alsa: flush() -- closing the output device"); + debug(2, "alsa: flush() -- closing the output device"); do_close(); // will change the state to disconnected - debug(1, "alsa: flush() -- alsa_backend_state => abm_disconnected."); + debug(2, "alsa: flush() -- alsa_backend_state => abm_disconnected."); } } else - debug(1, "alsa: flush() -- called on a disconnected alsa backend"); + debug(3, "alsa: flush() -- called on a disconnected alsa backend"); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex } @@ -1462,14 +1462,14 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode if (do_open() == 0) - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; alsa_backend_state => abm_connected"); + 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)) { stall_monitor_start_time = 0; frame_index = 0; measurement_data_is_valid = 0; - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); do_close(); - debug(1, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); } // now, if the backend is not in the abm_disconnected state // and config.keep_dac_busy is true (at the present, this has to be the case to be in the @@ -1527,4 +1527,4 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { usleep(sleep_time_ms * 1000); // has a cancellation point in it } pthread_exit(NULL); -} \ No newline at end of file +} From 90ed88dbc556c78c0d4add533c0b6b7195927631 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 15:15:05 +0000 Subject: [PATCH 081/115] add hooks for active mode start and end and add in the timeout into the configuration file. --- scripts/shairport-sync.conf | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index b34c8455..031ce572 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -49,15 +49,22 @@ general = // as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session". }; -// Advanced parameters for controlling how Shairport Sync runs a play session +// Advanced parameters for controlling how Shairport Sync stays active and how it runs a session sessioncontrol = { // run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line + +// "active" mode starts when play begins and ends when the active_mode_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed. +// run_this_before_entering_active_mode = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line +// run_this_after_exiting_active_mode = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line +// active_mode_timeout = 10; // wait for this number of seconds after play ends before leaving the active mode, unless another play session begins. + // run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument. // Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem. // You could hook on a program to do this automatically, but beware -- the device may then power off and restart without warning! // wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing + // allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source // session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again. }; From aa5698fce22c20fc2dcc8e05b14e0c0c2a716e29 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 27 Jan 2019 15:53:36 +0000 Subject: [PATCH 082/115] Remove the preflight call - don't seem to need it. --- audio.h | 2 -- audio_alsa.c | 10 ---------- audio_ao.c | 1 - audio_dummy.c | 1 - audio_jack.c | 3 +-- audio_pa.c | 1 - audio_pipe.c | 1 - audio_sndio.c | 1 - audio_soundio.c | 1 - audio_stdout.c | 1 - 10 files changed, 1 insertion(+), 21 deletions(-) diff --git a/audio.h b/audio.h index 1767e416..a4f0f178 100644 --- a/audio.h +++ b/audio.h @@ -22,8 +22,6 @@ typedef struct { void (*start)(int sample_rate, int sample_format); // block of samples - int (*preflight)(void *buf, - int samples); // say you are about to send these samples (before interpolation) int (*play)(void *buf, int samples); void (*stop)(void); diff --git a/audio_alsa.c b/audio_alsa.c index e9ac05f9..7fae429b 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -48,7 +48,6 @@ static int init(int argc, char **argv); static void deinit(void); static void start(int i_sample_rate, int i_sample_format); static int play(void *buf, int samples); -int preflight(void *buf, int samples); static void stop(void); static void flush(void); int delay(long *the_delay); @@ -1308,15 +1307,6 @@ static void flush(void) { pthread_cleanup_pop(0); // release the mutex } -int preflight(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) { - uint64_t time_now = - get_absolute_time_in_fp(); // this is to regulate access by the silence filler thread - uint64_t standoff_time = 60; // milliseconds - standoff_time = (standoff_time << 32) / 1000; - most_recent_write_time = time_now + standoff_time; - return 0; -} - static void stop(void) { // debug(2,"audio_alsa stop called."); flush(); // flush will also close the device if appropriate diff --git a/audio_ao.c b/audio_ao.c index 30f80d8d..42147bab 100644 --- a/audio_ao.c +++ b/audio_ao.c @@ -134,7 +134,6 @@ audio_output audio_ao = {.name = "ao", .is_running = NULL, .flush = NULL, .delay = NULL, - .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_dummy.c b/audio_dummy.c index 69518cd0..d8dea87f 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -65,7 +65,6 @@ audio_output audio_dummy = {.name = "dummy", .is_running = NULL, .flush = NULL, .delay = NULL, - .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, diff --git a/audio_jack.c b/audio_jack.c index 9a584385..c8a8b9ee 100644 --- a/audio_jack.c +++ b/audio_jack.c @@ -66,7 +66,6 @@ audio_output audio_jack = {.name = "jack", .is_running = &jack_is_running, .flush = &jack_flush, .delay = &jack_delay, - .preflight = NULL, .play = &play, .volume = NULL, .parameters = NULL, @@ -434,4 +433,4 @@ void jack_stop(void) { // debug(1, "jack stop"); if (config.jack_auto_client_disconnect) jack_close(); -} \ No newline at end of file +} diff --git a/audio_pa.c b/audio_pa.c index 4ee20c75..cd321bd9 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -312,7 +312,6 @@ audio_output audio_pa = {.name = "pa", .flush = &flush, .delay = &pa_delay, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_pipe.c b/audio_pipe.c index 0d4a42fd..89a09a0c 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -139,7 +139,6 @@ audio_output audio_pipe = {.name = "pipe", .flush = NULL, .delay = NULL, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_sndio.c b/audio_sndio.c index e106b15d..f22ed5af 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -49,7 +49,6 @@ audio_output audio_sndio = {.name = "sndio", .flush = &flush, .delay = &delay, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; diff --git a/audio_soundio.c b/audio_soundio.c index 8942554d..f57f9933 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -222,7 +222,6 @@ audio_output audio_soundio = {.name = "soundio", .flush = &flush, .delay = NULL, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = ¶meters, .mute = NULL}; diff --git a/audio_stdout.c b/audio_stdout.c index 37d6f781..0438b1af 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -86,7 +86,6 @@ audio_output audio_stdout = {.name = "stdout", .flush = NULL, .delay = NULL, .play = &play, - .preflight = NULL, .volume = NULL, .parameters = NULL, .mute = NULL}; From c5ff0dfed0d11605d75c4795ab059e903fa42c02 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 30 Jan 2019 09:24:10 +0000 Subject: [PATCH 083/115] Rebuild the volume control calculations. Implement software mute. Try to delay alsa initialisation as long as possible. --- audio.h | 3 +- audio_alsa.c | 224 ++++++++++++++++------------- common.h | 2 + player.c | 396 ++++++++++++++++++++++++--------------------------- player.h | 17 +-- 5 files changed, 324 insertions(+), 318 deletions(-) diff --git a/audio.h b/audio.h index a4f0f178..24536b91 100644 --- a/audio.h +++ b/audio.h @@ -47,7 +47,8 @@ typedef struct { void (*parameters)(audio_parameters *info); // may be NULL, in which case software muting is used. - void (*mute)(int do_mute); + // also, will return a 1 if it is actually using the mute facility, 0 otherwise + int (*mute)(int do_mute); } audio_output; diff --git a/audio_alsa.c b/audio_alsa.c index 7fae429b..0b94e812 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -59,7 +59,7 @@ static void volume(double vol); void do_volume(double vol); static void parameters(audio_parameters *info); -static void mute(int do_mute); +int mute(int do_mute); // returns true if it actually is allowed to use the mute static double set_volume; static int output_method_signalled = 0; @@ -93,6 +93,7 @@ static unsigned int desired_sample_rate; static enum sps_format_t sample_format; int frame_size; // in bytes for interleaved stereo +int alsa_device_initialised; // boolean to ensure the initialisation is only done once snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; @@ -620,12 +621,117 @@ int open_alsa_device(void) { return result; } +int do_alsa_device_init_if_needed() { + debug(1,"do_alsa_device_init_if_needed()"); + int response = 0; + // do any alsa device initialisation (general case) if needed + // at present, this is only needed if a hardware mixer is being used + // if there's a hardware mixer, it needs to be initialised before first use + if (alsa_device_initialised == 0) { + alsa_device_initialised = 1; + 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); + } + } + return response; +} + 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. + alsa_device_initialised = 0; const char *str; int value; // double dvalue; @@ -871,101 +977,9 @@ static int init(int argc, char **argv) { } 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."); - } + + do_alsa_device_init_if_needed(); + alsa_mix_handle = NULL; // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the @@ -994,7 +1008,7 @@ static void deinit(void) { } static void start(int i_sample_rate, int i_sample_format) { - // debug(2,"audio_alsa start called."); + debug(1,"audio_alsa start called."); if (i_sample_rate == 0) desired_sample_rate = 44100; // default else @@ -1010,6 +1024,10 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; + if (alsa_device_initialised == 0) { + debug(1,"alsa: start() calling do_alsa_device_init_if_needed."); + do_alsa_device_init_if_needed(); + } } int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { @@ -1380,7 +1398,10 @@ static void linear_volume(double vol) { } */ -static void mute(int mute_state_requested) { +int mute(int mute_state_requested) { + int response = 0; + if (config.alsa_use_hardware_mute == 1) + response = 1; // return true if actually using the mute facility // debug(1,"External Mute Request: %d",mute_state_requested); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); mute_request_pending = 1; @@ -1388,6 +1409,7 @@ static void mute(int mute_state_requested) { do_mute(mute_state_requested); debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex + return response; } void do_mute(int mute_state_requested) { @@ -1447,8 +1469,12 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { while (1) { int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1); // check possible state transitions here + if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { + debug(1,"alsa: alsa_buffer_monitor_thread_code() calling do_alsa_device_init_if_needed."); + do_alsa_device_init_if_needed(); + } if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode if (do_open() == 0) diff --git a/common.h b/common.h index 4a111e2c..463e49b4 100644 --- a/common.h +++ b/common.h @@ -182,6 +182,8 @@ typedef struct { // into the "active" mode. uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. + int volume_range_hw_priority; // when 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_rate; diff --git a/player.c b/player.c index 5efb679c..52356ce0 100644 --- a/player.c +++ b/player.c @@ -1662,6 +1662,7 @@ void *player_thread_func(void *arg) { (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 // we need an intermediate "transition" buffer @@ -2276,8 +2277,14 @@ void *player_thread_func(void *arg) { else { if (play_samples == 0) debug(1, "play_samples==0 skipping it (1)."); - else + else { + if (conn->software_mute_enabled) { + generate_zero_frames( + conn->outbuf, play_samples, + config.output_format, conn->enable_dither, conn->previous_random_number); + } config.output->play(conn->outbuf, play_samples); + } } // check for loss of sync @@ -2311,8 +2318,14 @@ void *player_thread_func(void *arg) { conn->outbuf, 0, conn->enable_dither, conn); if (conn->outbuf == NULL) debug(1, "NULL outbuf to play -- skipping it."); - else + else { + if (conn->software_mute_enabled) { + generate_zero_frames( + conn->outbuf, play_samples, + config.output_format, conn->enable_dither, conn->previous_random_number); + } config.output->play(conn->outbuf, play_samples); // remove the (short*)! + } } // mark the frame as finished @@ -2530,235 +2543,198 @@ void *player_thread_func(void *arg) { pthread_exit(NULL); } -// takes the volume as specified by the airplay protocol void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { + debug(1,"player_volume_without_notification %f",airplay_volume); +//first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw attenuation on top - // no cancellation points here if we assume that the mute call to the back end has no cancellation - // points - - // The volume ranges -144.0 (mute) or -30 -- 0. See - // http://git.zx2c4.com/Airtunes2/about/#setting-volume - // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30 - // equal increments. Since the human ear's response is roughly logarithmic, we imagine these to - // be power dB, i.e. from -30dB to 0dB. - - // We may have a hardware mixer, and if so, we will give it priority. - // If a desired volume range is given, then we will try to accommodate it from - // the top of the hardware mixer's range downwards. - - // If no desired volume range is given, we will use the native resolution of the hardware mixer, - // if any, - // or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0 - // dB, - // corresponding to a multiplier of 1 to 65535. - - // Otherwise, we will accommodate the desired volume range in the combination of the software and - // hardware mixer - // Intuitively (!), it seems best to give the hardware mixer as big a role as possible, so - // we will use its full range and then accommodate the rest of the attenuation in software. - // A problem is that we don't know whether the lowest hardware volume actually mutes the output - // so we must assume that it does, and for this reason, the volume control goes at the "bottom" of - // the adjustment range - - // The dB range of a value from 1 to 65536 is about 96.3 dB (log10 of 65536 is 4.8164). - // Since the levels correspond with amplitude, they correspond to voltage, hence voltage dB, - // or 20 times the log of the ratio. Then multiplied by 100 for convenience. - // Thus, we ask our vol2attn function for an appropriate dB between -96.3 and 0 dB and translate - // it back to a number. - - int32_t hw_min_db, hw_max_db, hw_range_db, min_db, - max_db; // hw_range_db is a flag; if 0 means no mixer - - if (config.output->parameters) { // no cancellation points in here + enum volume_mode_type {vol_sw_only, vol_hw_only, vol_both} volume_mode; + + // take account of whether there is a hardware mixer, if a max volume has been specified and if a range has been specified + // the range might imply that both hw and software mixers are needed, so calculate this + + int32_t hw_max_db, hw_min_db; + int32_t sw_max_db = 0, sw_min_db = -9630; + if (config.output->parameters) { + volume_mode = vol_hw_only; audio_parameters audio_information; - // have a hardware mixer config.output->parameters(&audio_information); hw_max_db = audio_information.maximum_volume_dB; hw_min_db = audio_information.minimum_volume_dB; - hw_range_db = hw_max_db - hw_min_db; - } else { - // don't have a hardware mixer - hw_max_db = hw_min_db = hw_range_db = 0; - } - - int32_t sw_min_db = -9630; - int32_t sw_max_db = 0; - int32_t sw_range_db = sw_max_db - sw_min_db; - int32_t desired_range_db = 0; // this is also used as a flag; if 0 means no desired range - - if (config.volume_range_db) - desired_range_db = (int32_t)trunc(config.volume_range_db * 100); - - // This is wrong, I think -- it doesn't work properly if the volume range is composite and you - // want to set a maximum value which should affect the hardware mixer. - - if (config.volume_max_db_set) { - if (hw_range_db) { - if (((config.volume_max_db * 100) < hw_max_db) && - ((config.volume_max_db * 100) > hw_min_db)) { - hw_max_db = (int)config.volume_max_db * 100; - hw_range_db = hw_max_db - hw_min_db; - } else { - inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB " - "to %d dB. It will be ignored.", - (int)(hw_max_db / 100), (int)(hw_min_db / 100)); - } - } else { - if (((config.volume_max_db * 100) < sw_max_db) && - ((config.volume_max_db * 100) > sw_min_db)) { - sw_max_db = (int)config.volume_max_db * 100; - sw_range_db = sw_max_db - sw_min_db; - } else { - inform("The volume_max_db setting is out of range of the software attenuation's limits of " - "0 dB to -96.3 dB. It will be ignored."); - } - } - } - - if (desired_range_db) { - // debug(1,"An attenuation range of %d is requested.",desired_range_db); - // we have a desired volume range. - if (hw_range_db) { - // we have a hardware mixer - if (hw_range_db >= desired_range_db) { - // the hardware mixer can accommodate the desired range - max_db = hw_max_db; - min_db = max_db - desired_range_db; - } else { - // we have a hardware mixer and a desired range greater than the mixer's range. - if ((hw_range_db + sw_range_db) < desired_range_db) { - inform("The volume attenuation range %f is greater than can be accommodated by the " - "hardware and software -- set to %f.", - config.volume_range_db, hw_range_db + sw_range_db); - desired_range_db = hw_range_db + sw_range_db; - } - min_db = hw_min_db; - max_db = min_db + desired_range_db; - } - } else { - // we have a desired volume range and no hardware mixer - if (sw_range_db < desired_range_db) { - inform("The volume attenuation range %f is greater than can be accommodated by the " - "software -- set to %f.", - config.volume_range_db, sw_range_db); - desired_range_db = sw_range_db; - } - max_db = sw_max_db; - min_db = max_db - desired_range_db; - } - } else { - // we do not have a desired volume range, so use the mixer's volume range, if there is one. - // debug(1,"No attenuation range requested."); - if (hw_range_db) { - min_db = hw_min_db; - max_db = hw_max_db; - } else { - min_db = sw_min_db; - max_db = sw_max_db; - } - } - - /* if (config.volume_max_db_set) { - if ((config.volume_max_db*100<=max_db) && (config.volume_max_db*100>=min_db)) { - debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db); - max_db = (int)(config.volume_max_db*100); + if (((config.volume_max_db * 100) <= hw_max_db) && + ((config.volume_max_db * 100) >= hw_min_db)) + hw_max_db = (int32_t)config.volume_max_db * 100; + else if (config.volume_range_db) { + hw_max_db = hw_min_db; + sw_max_db = (config.volume_max_db * 100) - hw_min_db; } else { - inform("The value of volume_max_db is invalid. It must be in the range %d to - %d.",max_db,min_db); - } + warn("The maximum output level is outside the range of the hardware mixer -- ignored"); + } } - */ - double hardware_attenuation = 0.0, software_attenuation = 0.0; - double scaled_attenuation = hw_min_db + sw_min_db; - - // now, we can map the input to the desired output volume - if ((airplay_volume == -144.0) && (config.ignore_volume_control == 0)) { - // do a mute - // needed even with hardware mute, as when sound is unmuted it might otherwise be very loud. - hardware_attenuation = hw_min_db; - if (config.output->mute) { - // allow the audio material to reach the mixer, but mute the mixer - // it the mute is removed externally, the material with be there - software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and - // the max is +40, this will be -36 - // (all by 100, of course) - // debug(1,"Mute, with hardware mute and software_attenuation set to - // %d.",software_attenuation); - config.output->mute(1); // use real mute if it's there - } else { - if (config.output->volume == NULL) { // if there is also no hardware volume control - software_attenuation = sw_min_db; // set any software output to zero too - // debug(1,"Mute, with no hardware mute and software_attenuation set to - // %d.",software_attenuation); - } + + // here, we have set limits on the hw_max_db and the sw_max_db + // but we haven't actually decided whether we need both hw and software attenuation + // only if a range is specified could we need both + if (config.volume_range_db) { + // see if the range requested exceeds the hardware range available + int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100); + if ((desired_range_db) > (hw_max_db - hw_min_db)) { + volume_mode = vol_both; + int32_t desired_sw_range = desired_range_db - (hw_max_db - hw_min_db); + if ((sw_max_db - desired_sw_range) < sw_min_db) + warn("The range requested is too large to accommodate -- ignored."); + else + sw_min_db = (sw_max_db - desired_sw_range); + } } } else { - if (config.output->mute) - config.output->mute(0); // unmute mute if it's there - if (config.ignore_volume_control == 1) - scaled_attenuation = max_db; - else if (config.volume_control_profile == VCP_standard) - scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points - else if (config.volume_control_profile == VCP_flat) - scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points - else - debug(1, "Unrecognised volume control profile"); - - if (hw_range_db) { - // if there is a hardware mixer - if (scaled_attenuation <= hw_max_db) { - // the attenuation is so low that's it's in the hardware mixer's range - // debug(1,"Attenuation all taken care of by the hardware mixer."); - hardware_attenuation = scaled_attenuation; - software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and - // the max is +40, this will be -36 - // (all by 100, of course) - } else { - // debug(1,"Attenuation taken care of by hardware and software mixer."); - hardware_attenuation = hw_max_db; // the hardware mixer is turned up full - software_attenuation = sw_max_db - (max_db - scaled_attenuation); - } - } else { - // if there is no hardware mixer, the scaled_volume is the software volume - // debug(1,"Attenuation all taken care of by the software mixer."); - software_attenuation = scaled_attenuation; + // debug(1,"has no hardware mixer"); + volume_mode = vol_sw_only; + if (config.volume_max_db_set) { + if (((config.volume_max_db * 100) <= sw_max_db) && + ((config.volume_max_db * 100) >= sw_min_db)) + sw_max_db = (int32_t)config.volume_max_db * 100; } + if (config.volume_range_db) { + // see if the range requested exceeds the software range available + int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100); + if ((desired_range_db) > (sw_max_db - sw_min_db)) + warn("The range requested is too large to accommodate -- ignored."); + else + sw_min_db = (sw_max_db - desired_range_db); + } } + + // here, we know whether it's hw volume control only, sw only or both, and we have the hw and sw limits. + // if it's both, we haven't decided whether hw or sw should be on top + // we have to consider the settings ignore_volume_control and mute. + + if (config.ignore_volume_control == 0) { + if (airplay_volume == -144.0) { + + if ((config.output->mute) && (config.output->mute(1) != 0)) + debug(1,"hardware mute is enabled."); + else { + conn->software_mute_enabled = 1; + debug(1,"software mute is enabled."); + + } - if ((config.output->volume) && (hw_range_db)) { - config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value - // debug(1,"Hardware attenuation set to %f for airplay volume of - // %f.",hardware_attenuation,airplay_volume); - } - double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); - // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of - // %f",software_attenuation,temp_fix_volume,airplay_volume); + } else { + int32_t max_db,min_db; + switch (volume_mode) { + case vol_hw_only: + max_db = hw_max_db; + min_db = hw_min_db; + break; + case vol_sw_only: + max_db = sw_max_db; + min_db = sw_min_db; + break; + case vol_both: + debug(1,"dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); + max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested + min_db = 0; + break; + default: + debug(1,"error in pv -- not in a volume mode"); + break; + } + double scaled_attenuation = 0.0; + if (config.volume_control_profile == VCP_standard) + scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points + else if (config.volume_control_profile == VCP_flat) + scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + else + debug(1, "Unrecognised volume control profile"); + + // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward. + double hardware_attenuation = 0.0; + double software_attenuation = 0.0; - conn->fix_volume = temp_fix_volume; - memory_barrier(); // no cancellation points + switch (volume_mode) { + case vol_hw_only: + hardware_attenuation = scaled_attenuation; + break; + case vol_sw_only: + software_attenuation = scaled_attenuation; + break; + case vol_both: + // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers + // if we give the hw priority, that means when lowering the volume, set the hw volume to its lowest + // before using the sw attenuation. + // similarly, if we give the sw priority, that means when lowering the volume, set the sw volume to its lowest + // before using the hw attenuation. + // one imagines that hw priority is likely to be much better + if (config.volume_range_hw_priority) { + // hw priority + if ((sw_max_db - sw_min_db) > scaled_attenuation) { + software_attenuation = sw_min_db + scaled_attenuation; + hardware_attenuation = hw_min_db; + } else { + software_attenuation = sw_max_db; + hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db); + } + } else { + // sw priority + if ((hw_max_db - hw_min_db) > scaled_attenuation) { + hardware_attenuation = hw_min_db + scaled_attenuation; + software_attenuation = sw_min_db; + } else { + hardware_attenuation = hw_max_db; + software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db); + } + } + break; + default: + debug(1,"error in pv -- not in a volume mode"); + break; + } + + + if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) + config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value + // debug(1,"Hardware attenuation set to %f for airplay volume of + // %f.",hardware_attenuation,airplay_volume); + + if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { + double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); + // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of + // %f",software_attenuation,temp_fix_volume,airplay_volume); - if (config.loudness) - loudness_set_volume(software_attenuation / 100); + conn->fix_volume = temp_fix_volume; + memory_barrier(); // no cancellation points - if (config.logOutputLevel) { - inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0); - } + if (config.loudness) + loudness_set_volume(software_attenuation / 100); + } + + + if (config.logOutputLevel) { + inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0); + } #ifdef CONFIG_METADATA - char *dv = malloc(128); // will be freed in the metadata thread - if (dv) { - memset(dv, 0, 128); - if (config.ignore_volume_control == 1) - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0); - else - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0, - min_db / 100.0, max_db / 100.0); - send_ssnc_metadata('pvol', dv, strlen(dv), 1); - } + char *dv = malloc(128); // will be freed in the metadata thread + if (dv) { + memset(dv, 0, 128); + if (config.ignore_volume_control == 1) + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0); + else + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0, + min_db / 100.0, max_db / 100.0); + send_ssnc_metadata('pvol', dv, strlen(dv), 1); + } #endif + // here, store the volume for possible use in the future - // here, store the volume for possible use in the future + if (config.output->mute) + config.output->mute(0); + conn->software_mute_enabled = 0; + + debug(1,"pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting is disabled.", volume_mode, software_attenuation, hardware_attenuation); + } + } config.airplay_volume = airplay_volume; } diff --git a/player.h b/player.h index 84521893..00793a92 100644 --- a/player.h +++ b/player.h @@ -68,14 +68,15 @@ typedef struct { } stream_cfg; typedef struct { - int connection_number; // for debug ID purposes, nothing else... - int resend_interval; // this is really just for debugging - int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency - uint32_t latency; // the actual latency used for this play session - uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero - // otherwise - uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero - // otherwise + int connection_number; // for debug ID purposes, nothing else... + int resend_interval; // this is really just for debugging + int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency + uint32_t latency; // the actual latency used for this play session + uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero + // otherwise + uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero + // otherwise + int software_mute_enabled; // if we don't have a real mute that we can use int fd; int authorized; // set if a password is required and has been supplied From fd08511f1f41c06a8fe0d230d666cd17227ef030 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 30 Jan 2019 17:43:15 +0000 Subject: [PATCH 084/115] Make it work with deferred initialisation of the alsa device -- deferred to first use. Investigate hardware mute again. --- audio_alsa.c | 16 ++++++++-------- player.c | 4 +++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 0b94e812..ca636708 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -977,10 +977,8 @@ static int init(int argc, char **argv) { } debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); - - do_alsa_device_init_if_needed(); - - alsa_mix_handle = NULL; + + alsa_mix_handle = NULL; // indicates alsa handle isn't open // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the // length of the queue @@ -1297,6 +1295,7 @@ int play(void *buf, int samples) { if (alsa_backend_state != abm_playing) { debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; + do_mute(0); } ret = do_play(buf, samples); } @@ -1399,6 +1398,7 @@ static void linear_volume(double vol) { */ int mute(int mute_state_requested) { + debug(1,"alsa: mute(%d)",mute_state_requested); int response = 0; if (config.alsa_use_hardware_mute == 1) response = 1; // return true if actually using the mute facility @@ -1413,7 +1413,7 @@ int mute(int mute_state_requested) { } void do_mute(int mute_state_requested) { - debug(3, "Setting mute to %d.", mute_state_requested); + debug(1, "alsa: do_mute(%d).", mute_state_requested); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1468,13 +1468,13 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { while (1) { - int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1); - // check possible state transitions here if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { debug(1,"alsa: alsa_buffer_monitor_thread_code() calling do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } + int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1); + // 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) diff --git a/player.c b/player.c index 52356ce0..642b6e2c 100644 --- a/player.c +++ b/player.c @@ -2692,10 +2692,12 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } - if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) + if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) { config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value // debug(1,"Hardware attenuation set to %f for airplay volume of // %f.",hardware_attenuation,airplay_volume); + conn->fix_volume = 0x10000; + } if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); From 555bb97a39757025ae2ce1e696a3ad1500d45cd9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 31 Jan 2019 09:01:35 +0000 Subject: [PATCH 085/115] Rebuild the hardware mute code -- clear up the logic. Needs to check if disconnected though. --- audio_alsa.c | 512 ++++++++++++++++++++++++++++----------------------- player.c | 331 +++++++++++++++++---------------- 2 files changed, 452 insertions(+), 391 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ca636708..9be90890 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -51,7 +51,6 @@ static int play(void *buf, int samples); static void stop(void); static void flush(void); int delay(long *the_delay); -void do_mute(int request); int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played); void *alsa_buffer_monitor_thread_code(void *arg); @@ -61,7 +60,7 @@ void do_volume(double vol); static void parameters(audio_parameters *info); int mute(int do_mute); // returns true if it actually is allowed to use the mute static double set_volume; -static int output_method_signalled = 0; +static int output_method_signalled = 0; // for reporting whether it's using mmap or not audio_output audio_alsa = { .name = "alsa", @@ -75,25 +74,40 @@ audio_output audio_alsa = { .delay = &delay, .play = &play, .rate_info = &get_rate_information, - .mute = NULL, // a function will be provided if it can, and is allowed to, do hardware mute - .volume = NULL, // a function will be provided if it can do hardware volume + .mute = NULL, // a function will be provided if it can, and is allowed to, + // do hardware mute + .volume = NULL, // a function will be provided if it can do hardware volume .parameters = NULL}; // a function will be provided if it can do hardware volume static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t alsa_buffer_monitor_thread; +// for deciding when to activate mute +// there are two sources of requests to mute -- the backend itself, e.g. when it +// is flushing +// and the player, e.g. when volume goes down to -144, i.e. mute. + +// we may not be allowed to use hardware mute, so we must reflect that too. + +int mute_requested_externally = 0; +int mute_requested_internally = 0; + // for tracking how long the output device has stalled -uint64_t stall_monitor_start_time; // zero if not initialised / not started / zeroed by flush -long stall_monitor_frame_count; // set to delay at start of time, incremented by any writes -uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's an error +uint64_t stall_monitor_start_time; // zero if not initialised / not started / + // zeroed by flush +long stall_monitor_frame_count; // set to delay at start of time, incremented by + // any writes +uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's + // an error static snd_output_t *output = NULL; static unsigned int desired_sample_rate; static enum sps_format_t sample_format; int frame_size; // in bytes for interleaved stereo -int alsa_device_initialised; // boolean to ensure the initialisation is only done once +int alsa_device_initialised; // boolean to ensure the initialisation is only + // done once snd_pcm_t *alsa_handle = NULL; static snd_pcm_hw_params_t *alsa_params = NULL; static snd_pcm_sw_params_t *alsa_swparams = NULL; @@ -114,13 +128,13 @@ static int has_softvol = 0; 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. -int overriding_mute_state_requested = 0; // 1 = mute; 0 = unmute requested -int mixer_volume_setting_gives_mute = - 0; // set when it is discovered that particular mixer volume setting causes a mute. -long alsa_mix_mute; // setting the volume to this value mutes output, if - // mixer_volume_setting_gives_mute is true +static int volume_set_request = 0; // set when an external request is made to set the volume. + +int mixer_volume_setting_gives_mute = 0; // set when it is discovered that + // particular mixer volume setting + // causes a mute. +long alsa_mix_mute; // setting the volume to this value mutes output, if + // mixer_volume_setting_gives_mute is true int volume_based_mute_is_active = 0; // set when muting is being done by a setting the volume to a magic value @@ -223,8 +237,9 @@ void actual_close_alsa_device() { if (alsa_handle) { int derr; if ((derr = snd_pcm_hw_free(alsa_handle))) - debug(1, "Error %d (\"%s\") freeing the output device hardware while closing it.", derr, - snd_strerror(derr)); + debug(1, "Error %d (\"%s\") freeing the output device hardware while " + "closing it.", + derr, snd_strerror(derr)); if ((derr = snd_pcm_close(alsa_handle))) debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr)); @@ -241,7 +256,8 @@ int actual_open_alsa_device(void) { /* const snd_pcm_uframes_t requested_buffer_headroom = minimal_buffer_headroom + 2048; // we ask for this much headroom in the - // hardware buffer, but we'll accept less + // hardware buffer, but we'll accept + less */ int ret, dir = 0; @@ -250,17 +266,20 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_buffer_length; snd_pcm_access_t access; - // ensure no calls are made to the alsa device enquiring about the buffer length if + // ensure no calls are made to the alsa device enquiring about the buffer + // length if // synchronisation is disabled. if (config.no_sync != 0) audio_alsa.delay = NULL; - // ensure no calls are made to the alsa device enquiring about the buffer length if + // ensure no calls are made to the alsa device enquiring about the buffer + // length if // synchronisation is disabled. if (config.no_sync != 0) audio_alsa.delay = NULL; - // ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + // ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, + // SND_PCM_NONBLOCK); ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0); if (ret < 0) return ret; @@ -369,7 +388,8 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_period_size; snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); if (actual_period_size != period_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " + inform("Actual period size set to a different value than requested. " + "Requested: %lu, actual " "setting: %lu", period_size_requested, actual_period_size); } @@ -386,7 +406,8 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_buffer_size; snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); if (actual_buffer_size != buffer_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " + inform("Actual period size set to a different value than requested. " + "Requested: %lu, actual " "setting: %lu", buffer_size_requested, actual_buffer_size); } @@ -405,7 +426,8 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_period_size; snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir); if (actual_period_size != period_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " + inform("Actual period size set to a different value than requested. " + "Requested: %lu, actual " "setting: %lu", period_size_requested, actual_period_size); } @@ -414,7 +436,8 @@ int actual_open_alsa_device(void) { snd_pcm_uframes_t actual_buffer_size; snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size); if (actual_buffer_size != buffer_size_requested) - inform("Actual period size set to a different value than requested. Requested: %lu, actual " + inform("Actual period size set to a different value than requested. " + "Requested: %lu, actual " "setting: %lu", buffer_size_requested, actual_buffer_size); } @@ -433,8 +456,9 @@ int actual_open_alsa_device(void) { ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams); if (ret < 0) { - warn("audio_alsa: Unable to get current sw parameters for device \"%s\": %s.", alsa_out_dev, - snd_strerror(ret)); + warn("audio_alsa: Unable to get current sw parameters for device \"%s\": " + "%s.", + alsa_out_dev, snd_strerror(ret)); return ret; } @@ -476,9 +500,9 @@ int actual_open_alsa_device(void) { buffer_size); } */ - debug(1, - "The alsa buffer is smaller (%lu bytes) than the desired backend buffer " - "length (%ld) you have chosen.", + debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend " + "buffer " + "length (%ld) you have chosen.", actual_buffer_length, config.audio_backend_buffer_desired_length); } @@ -499,7 +523,10 @@ int actual_open_alsa_device(void) { // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); // if (ret < 0) { - // die("audio_alsa: Cannpot get configuration for device \"%s\": no + // die("audio_alsa: Cannpot get configuration for + // device + //\"%s\": + // no // configurations //" // "available", @@ -622,105 +649,116 @@ int open_alsa_device(void) { } int do_alsa_device_init_if_needed() { - debug(1,"do_alsa_device_init_if_needed()"); + debug(1, "do_alsa_device_init_if_needed()"); int response = 0; // do any alsa device initialisation (general case) if needed // at present, this is only needed if a hardware mixer is being used // if there's a hardware mixer, it needs to be initialised before first use if (alsa_device_initialised == 0) { - alsa_device_initialised = 1; - if (hardware_mixer) { - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + alsa_device_initialised = 1; + 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; + 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) { + // 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); + 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, "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 + 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); - pthread_cleanup_pop(0); - pthread_setcancelstate(oldState, NULL); - } + 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); + } } return response; } @@ -766,7 +804,8 @@ static int init(int argc, char **argv) { // 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! + // do the "general" audio options. Note, these options are in the "general" + // stanza! parse_general_audio_options(); if (config.cfg != NULL) { @@ -803,7 +842,8 @@ static int init(int argc, char **argv) { else if (strcasecmp(str, "yes") == 0) config.no_sync = 1; else { - warn("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or " + warn("Invalid disable_synchronization option choice \"%s\". It should " + "be \"yes\" or " "\"no\". It is set to \"no\"."); config.no_sync = 0; } @@ -818,7 +858,8 @@ static int init(int argc, char **argv) { 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 " + 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; } @@ -831,7 +872,8 @@ static int init(int argc, char **argv) { 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 " + 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; } @@ -854,7 +896,8 @@ static int init(int argc, char **argv) { 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\", " + warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", " + "\"S16\", \"S24\", " "\"S24_3LE\", \"S24_3BE\" or " "\"S32\". It is set to \"S16\".", str); @@ -873,7 +916,8 @@ static int init(int argc, char **argv) { 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 " + 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; @@ -887,7 +931,8 @@ static int init(int argc, char **argv) { 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\". " + warn("Invalid use_mmap_if_available option choice \"%s\". It should be " + "\"yes\" or \"no\". " "It is set to \"yes\"."); config.no_mmap = 0; } @@ -977,10 +1022,11 @@ static int init(int argc, char **argv) { } debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); - + alsa_mix_handle = NULL; // indicates alsa handle isn't open - // so, now, if the option to keep the DAC running has been selected, start a thread to monitor the + // 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 @@ -1005,8 +1051,39 @@ static void deinit(void) { pthread_setcancelstate(oldState, NULL); } +int set_mute_state() { + debug(1, "set_mute_state"); + int response = 1; // some problem expected, e.g. no mixer or not allowed to use it + if (config.alsa_use_hardware_mute == 1) { + int mute = 0; + if ((mute_requested_externally != 0) || (mute_requested_internally != 0)) + mute = 1; + if (open_mixer() == 1) { + response = 0; // okay if actually using the mute facility + if (mute == 1) { + debug(1, "Hardware mute switched on"); + if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); + else { + volume_based_mute_is_active = 1; + do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); + } + } else { + debug(1, "Hardware mute switched off"); + if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); + else { + volume_based_mute_is_active = 0; + do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume); + } + } + close_mixer(); + } + } + return response; +} static void start(int i_sample_rate, int i_sample_format) { - debug(1,"audio_alsa start called."); + debug(1, "audio_alsa start called."); if (i_sample_rate == 0) desired_sample_rate = 44100; // default else @@ -1023,7 +1100,7 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; if (alsa_device_initialised == 0) { - debug(1,"alsa: start() calling do_alsa_device_init_if_needed."); + debug(1, "alsa: start() calling do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } } @@ -1065,10 +1142,9 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) || ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) { - debug(2, - "DAC seems to have stalled with time_now_ns: %" PRIX64 - ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 - ", stall_monitor_error_threshold %" PRIX64 ".", + debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64 + ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64 + ", stall_monitor_error_threshold %" PRIX64 ".", time_now_ns, update_timestamp_ns, stall_monitor_start_time, stall_monitor_error_threshold); ret = sps_extra_code_output_stalled; @@ -1088,12 +1164,14 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { *delay = *delay - frames_played_since_last_interrupt_sized; } - } else { // not running, thus no delay information, thus can't check for stall + } else { // not running, thus no delay information, thus can't check for + // stall *delay = 0; stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes - // not running, thus no delay information, thus can't check for frame rates + // not running, thus no delay information, thus can't check for frame + // rates frame_index = 0; // we'll be starting over... measurement_data_is_valid = 0; } @@ -1104,13 +1182,15 @@ int delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay) { } int delay(long *the_delay) { - // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or SND_PCM_STATE_PREPARED + // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or + // SND_PCM_STATE_PREPARED // or SND_PCM_STATE_DRAINING // and returns the actual delay if running or 0 if prepared in *the_delay // otherwise return an error code // the error code could be a Unix errno code or a snderror code, or - // the sps_extra_code_output_stalled or the sps_extra_code_output_state_cannot_make_ready codes + // the sps_extra_code_output_stalled or the + // sps_extra_code_output_state_cannot_make_ready codes int ret = 0; *the_delay = 0; if (alsa_handle == NULL) @@ -1212,7 +1292,8 @@ int do_play(void *buf, int samples) { while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) { sleep(1); /* wait until the suspend flag is released */ if (ret < 0) { - warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, snd_pcm_prepare() " + warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, " + "snd_pcm_prepare() " "failed: %s.", snd_strerror(ret)); } @@ -1221,8 +1302,9 @@ int do_play(void *buf, int samples) { } } } else { - debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* %d for play.", ret, - state); + debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* " + "%d for play.", + ret, state); frame_index = 0; measurement_data_is_valid = 0; } @@ -1234,15 +1316,21 @@ int do_play(void *buf, int samples) { int do_open() { int ret = 0; if (alsa_backend_state != abm_disconnected) - debug(1, "alsa: do_open() -- opening the output device when it is already connected"); + 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(); if (ret == 0) { + mute_requested_internally = 0; if (audio_alsa.volume) do_volume(set_volume); - if (audio_alsa.mute) - do_mute(0); + if (audio_alsa.mute) { + debug(1, "do_open() set_mute_state"); + set_mute_state(); // the mute_requested_externally flag will have been + // set accordingly + // do_mute(0); // complete unmute + } alsa_backend_state = abm_connected; // only do this if it really opened it. } } else { @@ -1253,7 +1341,8 @@ int do_open() { int do_close() { if (alsa_backend_state == abm_disconnected) - debug(1, "alsa: do_close() -- closing the output device when it is already disconnected"); + debug(1, "alsa: do_close() -- closing the output device when it is already " + "disconnected"); int derr = 0; if (alsa_handle) { // debug(1,"alsa: do_close() -- closing the output device"); @@ -1277,7 +1366,8 @@ int do_close() { int play(void *buf, int samples) { // play() will change the state of the alsa_backend_mode to abm_playing - // also, if the present alsa_backend_state is abm_disconnected, then first the DAC must be + // also, if the present alsa_backend_state is abm_disconnected, then first the + // DAC must be // connected // debug(3,"audio_alsa play called."); @@ -1289,13 +1379,17 @@ int play(void *buf, int samples) { ret = do_open(); if (ret == 0) debug(2, "alsa: play() -- opened output device"); - } + } if (ret == 0) { if (alsa_backend_state != abm_playing) { debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; - do_mute(0); + mute_requested_internally = 0; // stop requesting a mute for backend's own + // reasons, which might have been a flush + debug(1, "play() set_mute_state"); + set_mute_state(); // try to action the request and return a status + // do_mute(0); // unmute for backend's reason } ret = do_play(buf, samples); } @@ -1308,7 +1402,10 @@ int play(void *buf, int samples) { static void flush(void) { // debug(2,"audio_alsa flush called."); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - do_mute(1); + mute_requested_internally = 1; // request a mute for backend's reasons + debug(1, "flush() set_mute_state"); + set_mute_state(); + // do_mute(1); // mute for backend's own reasons if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy != 0) { debug(2, "alsa: flush() -- alsa_backend_state => abm_connected."); @@ -1334,7 +1431,8 @@ static void parameters(audio_parameters *info) { info->maximum_volume_dB = alsa_mix_maxdb; } -void do_volume(double vol) { // caller is assumed to have the alsa_mutex when using this function +void do_volume(double vol) { // caller is assumed to have the alsa_mutex when + // using this function debug(3, "Setting volume db to %f.", vol); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1386,7 +1484,8 @@ static void linear_volume(double vol) { if (hardware_mixer && alsa_mix_handle) { double linear_volume = pow(10, vol); // debug(1,"Linear volume is %f.",linear_volume); - long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume; + long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * +linear_volume; // debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol); if (alsa_mix_handle) { if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0) @@ -1397,71 +1496,17 @@ static void linear_volume(double vol) { } */ -int mute(int mute_state_requested) { - debug(1,"alsa: mute(%d)",mute_state_requested); - int response = 0; - if (config.alsa_use_hardware_mute == 1) - response = 1; // return true if actually using the mute facility - // debug(1,"External Mute Request: %d",mute_state_requested); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - mute_request_pending = 1; - overriding_mute_state_requested = mute_state_requested; - do_mute(mute_state_requested); - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex - return response; +int mute(int mute_state_requested) { // these would be for external reasons, not + // because of the + // state of the backend. + debug(1, "alsa: mute(%d)", mute_state_requested); + mute_requested_externally = mute_state_requested; // request a mute for external reasons + debug(1, "mute(%d) set_mute_state", mute_state_requested); + return set_mute_state(); } - -void do_mute(int mute_state_requested) { - debug(1, "alsa: do_mute(%d).", mute_state_requested); - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable - - // if a mute is requested now, then - // if an external mute request is in place, leave everything muted - // otherwise, if an external mute request is pending, action it - // otherwise, action the do_mute request - - int local_mute_state_requested = - overriding_mute_state_requested; // go with whatever was asked by the external "mute" call - - // The mute state requested will be actioned unless mute_request_pending is set - // If it is set, then that the pending request will be actioned. - // If the hardware isn't there, or we are not allowed to use it, nothing will be done - // The caller must have the alsa mutex - - if (config.alsa_use_hardware_mute == 1) { - if (mute_request_pending == 0) - local_mute_state_requested = mute_state_requested; - if (open_mixer() == 1) { - if (local_mute_state_requested) { - // debug(1,"Playback Switch mute actually done"); - if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); - else { - // debug(1,"Activating volume-based mute."); - volume_based_mute_is_active = 1; - do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); - } - } else if (overriding_mute_state_requested == 0) { - // debug(1,"Playback Switch unmute actually done"); - if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); - else { - // debug(1,"Deactivating volume-based mute."); - volume_based_mute_is_active = 0; - do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume); - } - } - close_mixer(); - } - } - mute_request_pending = 0; - pthread_setcancelstate(oldState, NULL); -} - /* -void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *arg) { +void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void +*arg) { debug(1, "alsa: alsa_buffer_monitor_thread_cleanup_function called."); } */ @@ -1469,7 +1514,8 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void *a void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { while (1) { if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { - debug(1,"alsa: alsa_buffer_monitor_thread_code() calling do_alsa_device_init_if_needed."); + debug(1, "alsa: alsa_buffer_monitor_thread_code() calling " + "do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); @@ -1478,18 +1524,23 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) { // open the dac and move to abm_connected mode if (do_open() == 0) - debug(2, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; alsa_backend_state => abm_connected"); + 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)) { stall_monitor_start_time = 0; frame_index = 0; measurement_data_is_valid = 0; - debug(2, "alsa: alsa_buffer_monitor_thread_code() -- closing the output device"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- closing the output " + "device"); do_close(); - debug(2, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state => abm_disconnected"); + debug(2, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state " + "=> abm_disconnected"); } // now, if the backend is not in the abm_disconnected state - // and config.keep_dac_busy is true (at the present, this has to be the case to be in the - // abm_connected state in the first place...) then do the silence-filling thing, if needed + // and config.keep_dac_busy is true (at the present, this has to be the case + // to be in the + // abm_connected state in the first place...) then do the silence-filling + // thing, if needed if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0)) { int reply; long buffer_size = 0; @@ -1511,15 +1562,18 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { sleep_time_in_fp = sleep_time_in_fp << 32; sleep_time_in_fp = sleep_time_in_fp / 1000; // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp - // form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence = (desired_sample_rate * + // form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence = + // (desired_sample_rate * // sleep_time_ms * 2) / 1000; int frames_of_silence = 1024; size_t size_of_silence_buffer = frames_of_silence * frame_size; - // debug(1, "alsa: alsa_buffer_monitor_thread_code -- silence buffer length: %u bytes.", + // debug(1, "alsa: alsa_buffer_monitor_thread_code -- silence buffer + // length: %u bytes.", // size_of_silence_buffer); void *silence = malloc(size_of_silence_buffer); if (silence == NULL) { - debug(1, "alsa: alsa_buffer_monitor_thread_code -- failed to allocate memory for a " + debug(1, "alsa: alsa_buffer_monitor_thread_code -- failed to " + "allocate memory for a " "silent frame buffer."); } else { pthread_cleanup_push(malloc_cleanup, silence); @@ -1527,10 +1581,12 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) && (config.airplay_volume != 0.0)) use_dither = 1; - dither_random_number_store = generate_zero_frames( - silence, frames_of_silence, config.output_format, use_dither, // i.e. with dither - dither_random_number_store); - // debug(1,"Play %d frames of silence with most_recent_write_time of %" PRIx64 ".", + dither_random_number_store = + generate_zero_frames(silence, frames_of_silence, config.output_format, + use_dither, // i.e. with dither + dither_random_number_store); + // debug(1,"Play %d frames of silence with most_recent_write_time of + // %" PRIx64 ".", // frames_of_silence,most_recent_write_time); do_play(silence, frames_of_silence); pthread_cleanup_pop(1); diff --git a/player.c b/player.c index 642b6e2c..eb2be192 100644 --- a/player.c +++ b/player.c @@ -285,18 +285,16 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } if (outsize > toutsize) { - debug(2, - "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " - "truncated, but buffer overflow possible! Encrypted = %d.", + debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " + "truncated, but buffer overflow possible! Encrypted = %d.", outsize, toutsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } *destlen = outsize / conn->input_bytes_per_frame; if ((outsize % conn->input_bytes_per_frame) != 0) - debug(1, - "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " - "and the audio frame size (%d).", + debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " + "and the audio frame size (%d).", *destlen, outsize, conn->input_bytes_per_frame); return reply; } @@ -456,10 +454,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than 10 seconds - debug(3, - "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", + debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", seqno, actual_timestamp, conn->flush_rtp_timestamp); conn->initial_reference_time = 0; conn->initial_reference_timestamp = 0; @@ -852,10 +849,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (curframe->given_timestamp != conn->flush_rtp_timestamp) && (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) < conn->input_rate * 10)) { // if it's less than ten seconds - debug(3, - "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 - ", flushing to " - "timestamp: %" PRIu32 ".", + debug(3, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32 + ", flushing to " + "timestamp: %" PRIu32 ".", curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp); curframe->ready = 0; curframe->resend_level = 0; @@ -938,7 +934,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp + conn->latency + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this will go modulo 2^32 - &should_be_time, conn); + &should_be_time, + conn); conn->first_packet_time_to_play = should_be_time; @@ -959,7 +956,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { frame_to_local_time(conn->first_packet_timestamp + conn->latency + (uint32_t)(config.audio_backend_latency_offset * conn->input_rate), // this should go modulo 2^32 - &should_be_time, conn); + &should_be_time, + conn); conn->first_packet_time_to_play = should_be_time; @@ -1030,9 +1028,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // this might happen if a big clock adjustment was made at just the wrong // time. - debug(1, - "Run a bit past the exact start time by %" PRId64 - " frames with a DAC delay of %ld frames.", + debug(1, "Run a bit past the exact start time by %" PRId64 + " frames with a DAC delay of %ld frames.", -exact_frame_gap, dac_delay); if (config.output->flush) config.output->flush(); @@ -1190,7 +1187,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) - (uint32_t)(config.audio_backend_buffer_desired_length * conn->input_rate), // this will go modulo 2^32 - &time_to_play, conn); + &time_to_play, + conn); if (local_time_now >= time_to_play) { do_wait = 0; @@ -1670,17 +1668,17 @@ void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - conn->tbuf = malloc( - sizeof(int32_t) * 2 * - (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); + conn->tbuf = + malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->tbuf == NULL) die("Failed to allocate memory for the transition buffer."); // initialise this, because soxr stuffing might be chosen later - conn->sbuf = malloc( - sizeof(int32_t) * 2 * - (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); + conn->sbuf = + malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + + conn->max_frame_size_change)); if (conn->sbuf == NULL) die("Failed to allocate memory for the sbuf buffer."); @@ -1968,9 +1966,8 @@ void *player_thread_func(void *arg) { SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. if (inframe->sequence_number != conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay - debug(2, - "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " - "and ab_write: %u.", + debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write); conn->last_seqno_read = inframe->sequence_number; // reset warning... } @@ -2195,7 +2192,7 @@ void *player_thread_func(void *arg) { #ifdef CONFIG_CONVOLUTION || config.convolution #endif - ) { + ) { int32_t *tbuf32 = (int32_t *)conn->tbuf; float fbuf_l[inbuflength]; float fbuf_r[inbuflength]; @@ -2279,9 +2276,8 @@ void *player_thread_func(void *arg) { debug(1, "play_samples==0 skipping it (1)."); else { if (conn->software_mute_enabled) { - generate_zero_frames( - conn->outbuf, play_samples, - config.output_format, conn->enable_dither, conn->previous_random_number); + generate_zero_frames(conn->outbuf, play_samples, config.output_format, + conn->enable_dither, conn->previous_random_number); } config.output->play(conn->outbuf, play_samples); } @@ -2320,9 +2316,8 @@ void *player_thread_func(void *arg) { debug(1, "NULL outbuf to play -- skipping it."); else { if (conn->software_mute_enabled) { - generate_zero_frames( - conn->outbuf, play_samples, - config.output_format, conn->enable_dither, conn->previous_random_number); + generate_zero_frames(conn->outbuf, play_samples, config.output_format, + conn->enable_dither, conn->previous_random_number); } config.output->play(conn->outbuf, play_samples); // remove the (short*)! } @@ -2445,41 +2440,42 @@ void *player_thread_func(void *arg) { if ((config.output->delay)) { if (config.no_sync == 0) { - inform( - "%*.2f," /* Sync error in milliseconds */ - "%*.1f," /* net correction in ppm */ - "%*.1f," /* corrections in ppm */ - "%*d," /* total packets */ - "%*" PRIu64 "," /* missing packets */ - "%*" PRIu64 "," /* late packets */ - "%*" PRIu64 "," /* too late packets */ - "%*" PRIu64 "," /* resend requests */ - "%*" PRId64 "," /* min DAC queue size */ - "%*" PRId32 "," /* min buffer occupancy */ - "%*" PRId32 "," /* max buffer occupancy */ - "%*.2f," /* source nominal frame rate */ - "%*.2f," /* source actual (average) frame rate */ - "%*.2f," /* output frame rate */ - "%*.2f," /* source clock drift */ - "%*d," /* source clock drift sample count */ - "%*.2f", /* rough calculated correction in ppm */ - 10, 1000 * moving_average_sync_error / config.output_rate, 10, - moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, - moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, - 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, - conn->remote_frame_rate, 11, conn->input_frame_rate, 11, conn->frame_rate, 10, - (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, - conn->local_to_remote_time_gradient_sample_count, 10, - (conn->frame_rate > 0.0) - ? ((conn->frame_rate - conn->remote_frame_rate * - conn->output_sample_ratio * - conn->local_to_remote_time_gradient) * - 1000000) / - conn->frame_rate - : 0.0); + inform("%*.2f," /* Sync error in milliseconds */ + "%*.1f," /* net correction in ppm */ + "%*.1f," /* corrections in ppm */ + "%*d," /* total packets */ + "%*" PRIu64 "," /* missing packets */ + "%*" PRIu64 "," /* late packets */ + "%*" PRIu64 "," /* too late packets */ + "%*" PRIu64 "," /* resend requests */ + "%*" PRId64 "," /* min DAC queue size */ + "%*" PRId32 "," /* min buffer occupancy */ + "%*" PRId32 "," /* max buffer occupancy */ + "%*.2f," /* source nominal frame rate */ + "%*.2f," /* source actual (average) frame rate */ + "%*.2f," /* output frame rate */ + "%*.2f," /* source clock drift */ + "%*d," /* source clock drift sample count */ + "%*.2f", /* rough calculated correction in ppm */ + 10, + 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), + 10, moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 7, + minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, + conn->input_frame_rate, 11, conn->frame_rate, 10, + (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, + conn->local_to_remote_time_gradient_sample_count, 10, + (conn->frame_rate > 0.0) + ? ((conn->frame_rate - + conn->remote_frame_rate * conn->output_sample_ratio * + conn->local_to_remote_time_gradient) * + 1000000) / + conn->frame_rate + : 0.0); } else { inform("%*.2f," /* Sync error in milliseconds */ "%*d," /* total packets */ @@ -2494,9 +2490,10 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, - 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, - 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, + 10, + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, + conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, conn->input_frame_rate, 10, (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, @@ -2515,9 +2512,10 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number, - 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, - 7, conn->resend_requests, 5, minimum_buffer_occupancy, 5, + 10, + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, + conn->resend_requests, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, conn->input_frame_rate, 10, (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, @@ -2544,14 +2542,16 @@ void *player_thread_func(void *arg) { } void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { - debug(1,"player_volume_without_notification %f",airplay_volume); -//first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw attenuation on top + debug(1, "player_volume_without_notification %f", airplay_volume); + // first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw + // attenuation on top - enum volume_mode_type {vol_sw_only, vol_hw_only, vol_both} volume_mode; - - // take account of whether there is a hardware mixer, if a max volume has been specified and if a range has been specified + enum volume_mode_type { vol_sw_only, vol_hw_only, vol_both } volume_mode; + + // take account of whether there is a hardware mixer, if a max volume has been specified and if a + // range has been specified // the range might imply that both hw and software mixers are needed, so calculate this - + int32_t hw_max_db, hw_min_db; int32_t sw_max_db = 0, sw_min_db = -9630; if (config.output->parameters) { @@ -2569,9 +2569,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c sw_max_db = (config.volume_max_db * 100) - hw_min_db; } else { warn("The maximum output level is outside the range of the hardware mixer -- ignored"); - } + } } - + // here, we have set limits on the hw_max_db and the sw_max_db // but we haven't actually decided whether we need both hw and software attenuation // only if a range is specified could we need both @@ -2583,9 +2583,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c int32_t desired_sw_range = desired_range_db - (hw_max_db - hw_min_db); if ((sw_max_db - desired_sw_range) < sw_min_db) warn("The range requested is too large to accommodate -- ignored."); - else + else sw_min_db = (sw_max_db - desired_sw_range); - } + } } } else { // debug(1,"has no hardware mixer"); @@ -2593,112 +2593,116 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (config.volume_max_db_set) { if (((config.volume_max_db * 100) <= sw_max_db) && ((config.volume_max_db * 100) >= sw_min_db)) - sw_max_db = (int32_t)config.volume_max_db * 100; + sw_max_db = (int32_t)config.volume_max_db * 100; } if (config.volume_range_db) { - // see if the range requested exceeds the software range available - int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100); - if ((desired_range_db) > (sw_max_db - sw_min_db)) - warn("The range requested is too large to accommodate -- ignored."); - else - sw_min_db = (sw_max_db - desired_range_db); - } + // see if the range requested exceeds the software range available + int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100); + if ((desired_range_db) > (sw_max_db - sw_min_db)) + warn("The range requested is too large to accommodate -- ignored."); + else + sw_min_db = (sw_max_db - desired_range_db); + } } - - // here, we know whether it's hw volume control only, sw only or both, and we have the hw and sw limits. + + // here, we know whether it's hw volume control only, sw only or both, and we have the hw and sw + // limits. // if it's both, we haven't decided whether hw or sw should be on top // we have to consider the settings ignore_volume_control and mute. - + if (config.ignore_volume_control == 0) { if (airplay_volume == -144.0) { - - if ((config.output->mute) && (config.output->mute(1) != 0)) - debug(1,"hardware mute is enabled."); + + if ((config.output->mute) && (config.output->mute(1) == 0)) + debug(1, "player: hardware mute is enabled."); else { conn->software_mute_enabled = 1; - debug(1,"software mute is enabled."); - + debug(1, "player: software mute is enabled."); } } else { - int32_t max_db,min_db; + int32_t max_db, min_db; switch (volume_mode) { - case vol_hw_only: - max_db = hw_max_db; - min_db = hw_min_db; - break; - case vol_sw_only: - max_db = sw_max_db; - min_db = sw_min_db; - break; - case vol_both: - debug(1,"dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); - max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested - min_db = 0; - break; - default: - debug(1,"error in pv -- not in a volume mode"); - break; + case vol_hw_only: + max_db = hw_max_db; + min_db = hw_min_db; + break; + case vol_sw_only: + max_db = sw_max_db; + min_db = sw_min_db; + break; + case vol_both: + debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, + sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); + max_db = + (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested + min_db = 0; + break; + default: + debug(1, "error in pv -- not in a volume mode"); + break; } double scaled_attenuation = 0.0; if (config.volume_control_profile == VCP_standard) scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points else if (config.volume_control_profile == VCP_flat) - scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points + scaled_attenuation = + flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else debug(1, "Unrecognised volume control profile"); - + // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward. double hardware_attenuation = 0.0; double software_attenuation = 0.0; switch (volume_mode) { - case vol_hw_only: - hardware_attenuation = scaled_attenuation; - break; - case vol_sw_only: - software_attenuation = scaled_attenuation; - break; - case vol_both: - // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers - // if we give the hw priority, that means when lowering the volume, set the hw volume to its lowest - // before using the sw attenuation. - // similarly, if we give the sw priority, that means when lowering the volume, set the sw volume to its lowest - // before using the hw attenuation. - // one imagines that hw priority is likely to be much better - if (config.volume_range_hw_priority) { - // hw priority - if ((sw_max_db - sw_min_db) > scaled_attenuation) { - software_attenuation = sw_min_db + scaled_attenuation; - hardware_attenuation = hw_min_db; - } else { - software_attenuation = sw_max_db; - hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db); - } - } else { - // sw priority - if ((hw_max_db - hw_min_db) > scaled_attenuation) { - hardware_attenuation = hw_min_db + scaled_attenuation; - software_attenuation = sw_min_db; - } else { - hardware_attenuation = hw_max_db; - software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db); - } + case vol_hw_only: + hardware_attenuation = scaled_attenuation; + break; + case vol_sw_only: + software_attenuation = scaled_attenuation; + break; + case vol_both: + // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers + // if we give the hw priority, that means when lowering the volume, set the hw volume to its + // lowest + // before using the sw attenuation. + // similarly, if we give the sw priority, that means when lowering the volume, set the sw + // volume to its lowest + // before using the hw attenuation. + // one imagines that hw priority is likely to be much better + if (config.volume_range_hw_priority) { + // hw priority + if ((sw_max_db - sw_min_db) > scaled_attenuation) { + software_attenuation = sw_min_db + scaled_attenuation; + hardware_attenuation = hw_min_db; + } else { + software_attenuation = sw_max_db; + hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db); } - break; - default: - debug(1,"error in pv -- not in a volume mode"); - break; + } else { + // sw priority + if ((hw_max_db - hw_min_db) > scaled_attenuation) { + hardware_attenuation = hw_min_db + scaled_attenuation; + software_attenuation = sw_min_db; + } else { + hardware_attenuation = hw_max_db; + software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db); + } + } + break; + default: + debug(1, "error in pv -- not in a volume mode"); + break; } - - + if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) { config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value // debug(1,"Hardware attenuation set to %f for airplay volume of // %f.",hardware_attenuation,airplay_volume); conn->fix_volume = 0x10000; } - + if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000); // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of @@ -2711,7 +2715,6 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c loudness_set_volume(software_attenuation / 100); } - if (config.logOutputLevel) { inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0); } @@ -2733,8 +2736,10 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (config.output->mute) config.output->mute(0); conn->software_mute_enabled = 0; - - debug(1,"pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting is disabled.", volume_mode, software_attenuation, hardware_attenuation); + + debug(1, "pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting " + "is disabled.", + volume_mode, software_attenuation, hardware_attenuation); } } config.airplay_volume = airplay_volume; From 60487c86b1ca8409faae05037dcb9818ee7d1cf0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 31 Jan 2019 13:35:11 +0000 Subject: [PATCH 086/115] Make mute work only if not diconnected. Clewan up some debug messages. --- audio_alsa.c | 74 +++++++++++++++++++++++++--------------------------- player.c | 16 ++++++------ 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 9be90890..a9f89cfe 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -521,17 +521,17 @@ int actual_open_alsa_device(void) { debug(log_level, "PCM handle name = '%s'", snd_pcm_name(alsa_handle)); - // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); - // if (ret < 0) { - // die("audio_alsa: Cannpot get configuration for + // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params); + // if (ret < 0) { + // die("audio_alsa: Cannpot get configuration for // device //\"%s\": // no // configurations //" - // "available", - // alsa_out_dev); - // } + // "available", + // alsa_out_dev); + // } debug(log_level, "alsa device parameters:"); @@ -649,7 +649,6 @@ int open_alsa_device(void) { } int do_alsa_device_init_if_needed() { - debug(1, "do_alsa_device_init_if_needed()"); int response = 0; // do any alsa device initialisation (general case) if needed // at present, this is only needed if a hardware mixer is being used @@ -657,6 +656,7 @@ int do_alsa_device_init_if_needed() { if (alsa_device_initialised == 0) { alsa_device_initialised = 1; if (hardware_mixer) { + debug(2, "alsa: hardware mixer init"); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -1052,38 +1052,37 @@ static void deinit(void) { } int set_mute_state() { - debug(1, "set_mute_state"); - int response = 1; // some problem expected, e.g. no mixer or not allowed to use it - if (config.alsa_use_hardware_mute == 1) { + int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected + if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && (open_mixer() == 1)) { + response = 0; // okay if actually using the mute facility + debug(1, "set_mute_state"); int mute = 0; if ((mute_requested_externally != 0) || (mute_requested_internally != 0)) - mute = 1; - if (open_mixer() == 1) { - response = 0; // okay if actually using the mute facility - if (mute == 1) { - debug(1, "Hardware mute switched on"); - if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); - else { - volume_based_mute_is_active = 1; - do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); - } - } else { - debug(1, "Hardware mute switched off"); - if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) - snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); - else { - volume_based_mute_is_active = 0; - do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume); - } + mute = 1; + if (mute == 1) { + debug(1, "Hardware mute switched on"); + if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); + else { + volume_based_mute_is_active = 1; + do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); + } + } else { + debug(1, "Hardware mute switched off"); + if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) + snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); + else { + volume_based_mute_is_active = 0; + do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume); } - close_mixer(); } + close_mixer(); } return response; } + static void start(int i_sample_rate, int i_sample_format) { - debug(1, "audio_alsa start called."); + debug(3, "audio_alsa start called."); if (i_sample_rate == 0) desired_sample_rate = 44100; // default else @@ -1100,7 +1099,7 @@ static void start(int i_sample_rate, int i_sample_format) { stall_monitor_start_time = 0; stall_monitor_frame_count = 0; if (alsa_device_initialised == 0) { - debug(1, "alsa: start() calling do_alsa_device_init_if_needed."); + debug(2, "alsa: start() calling do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } } @@ -1326,7 +1325,7 @@ int do_open() { if (audio_alsa.volume) do_volume(set_volume); if (audio_alsa.mute) { - debug(1, "do_open() set_mute_state"); + debug(2, "do_open() set_mute_state"); set_mute_state(); // the mute_requested_externally flag will have been // set accordingly // do_mute(0); // complete unmute @@ -1387,7 +1386,7 @@ int play(void *buf, int samples) { alsa_backend_state = abm_playing; mute_requested_internally = 0; // stop requesting a mute for backend's own // reasons, which might have been a flush - debug(1, "play() set_mute_state"); + debug(2, "play() set_mute_state"); set_mute_state(); // try to action the request and return a status // do_mute(0); // unmute for backend's reason } @@ -1403,7 +1402,7 @@ static void flush(void) { // debug(2,"audio_alsa flush called."); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); mute_requested_internally = 1; // request a mute for backend's reasons - debug(1, "flush() set_mute_state"); + debug(2, "flush() set_mute_state"); set_mute_state(); // do_mute(1); // mute for backend's own reasons if (alsa_backend_state != abm_disconnected) { // must be playing or connected... @@ -1499,9 +1498,8 @@ linear_volume; int mute(int mute_state_requested) { // these would be for external reasons, not // because of the // state of the backend. - debug(1, "alsa: mute(%d)", mute_state_requested); mute_requested_externally = mute_state_requested; // request a mute for external reasons - debug(1, "mute(%d) set_mute_state", mute_state_requested); + debug(2, "mute(%d) set_mute_state", mute_state_requested); return set_mute_state(); } /* @@ -1514,7 +1512,7 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { while (1) { if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { - debug(1, "alsa: alsa_buffer_monitor_thread_code() calling " + debug(2, "alsa: alsa_buffer_monitor_thread_code() calling " "do_alsa_device_init_if_needed."); do_alsa_device_init_if_needed(); } diff --git a/player.c b/player.c index eb2be192..fa2e2c3c 100644 --- a/player.c +++ b/player.c @@ -2542,7 +2542,7 @@ void *player_thread_func(void *arg) { } void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { - debug(1, "player_volume_without_notification %f", airplay_volume); + debug(2, "player_volume_without_notification %f", airplay_volume); // first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw // attenuation on top @@ -2614,10 +2614,10 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (airplay_volume == -144.0) { if ((config.output->mute) && (config.output->mute(1) == 0)) - debug(1, "player: hardware mute is enabled."); + debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, hardware mute is enabled.", volume_mode, airplay_volume); else { conn->software_mute_enabled = 1; - debug(1, "player: software mute is enabled."); + debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, software mute is enabled.", volume_mode, airplay_volume); } } else { @@ -2639,7 +2639,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c min_db = 0; break; default: - debug(1, "error in pv -- not in a volume mode"); + debug(1, "player_volume_without_notification: error: not in a volume mode"); break; } double scaled_attenuation = 0.0; @@ -2649,7 +2649,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points else - debug(1, "Unrecognised volume control profile"); + debug(1, "player_volume_without_notification: unrecognised volume control profile"); // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward. double hardware_attenuation = 0.0; @@ -2692,7 +2692,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } break; default: - debug(1, "error in pv -- not in a volume mode"); + debug(1, "player_volume_without_notification: error: not in a volume mode"); break; } @@ -2737,9 +2737,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c config.output->mute(0); conn->software_mute_enabled = 0; - debug(1, "pv: volume mode is %d, software_attenuation: %f, hardware_attenuation: %f, muting " + debug(2, "player_volume_without_notification: volume mode is %d, airplay volume is %f, software_attenuation: %f, hardware_attenuation: %f, muting " "is disabled.", - volume_mode, software_attenuation, hardware_attenuation); + volume_mode, airplay_volume, software_attenuation, hardware_attenuation); } } config.airplay_volume = airplay_volume; From 2c0250b32a9a01b584785d7e55598eb1570d8363 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 31 Jan 2019 17:11:33 +0000 Subject: [PATCH 087/115] Don't do a hardware mute when doing a flush -- if disable_standby is true, the mode goes to connected and then if player goes back to play immediately, you might hear some of the sound still moving through the output buffer. This happens due to a bug in IOS player. --- audio_alsa.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index a9f89cfe..ddb393d5 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1053,12 +1053,13 @@ static void deinit(void) { int set_mute_state() { int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected - if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && (open_mixer() == 1)) { + if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && + (open_mixer() == 1)) { response = 0; // okay if actually using the mute facility debug(1, "set_mute_state"); int mute = 0; if ((mute_requested_externally != 0) || (mute_requested_internally != 0)) - mute = 1; + mute = 1; if (mute == 1) { debug(1, "Hardware mute switched on"); if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) @@ -1401,9 +1402,9 @@ int play(void *buf, int samples) { static void flush(void) { // debug(2,"audio_alsa flush called."); pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); - mute_requested_internally = 1; // request a mute for backend's reasons - debug(2, "flush() set_mute_state"); - set_mute_state(); + // mute_requested_internally = 1; // request a mute for backend's reasons + // debug(2, "flush() set_mute_state"); + // set_mute_state(); // do_mute(1); // mute for backend's own reasons if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy != 0) { @@ -1495,9 +1496,9 @@ linear_volume; } */ -int mute(int mute_state_requested) { // these would be for external reasons, not - // because of the - // state of the backend. +int mute(int mute_state_requested) { // these would be for external reasons, not + // because of the + // state of the backend. mute_requested_externally = mute_state_requested; // request a mute for external reasons debug(2, "mute(%d) set_mute_state", mute_state_requested); return set_mute_state(); From 0c5521488c77ca9b618bbbb68f13e1552b011ec9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 12:38:45 +0000 Subject: [PATCH 088/115] add a special mixer-only mutex, do make open/close mixer stuff safer and probably quicker, not waiting for the main alsa mutex. --- audio_alsa.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index ddb393d5..c85712f8 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -80,6 +80,7 @@ audio_output audio_alsa = { .parameters = NULL}; // a function will be provided if it can do hardware volume static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t alsa_mixer_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_t alsa_buffer_monitor_thread; @@ -665,7 +666,7 @@ int do_alsa_device_init_if_needed() { // Now, start trying to initialise the alsa device with the settings // obtained - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mixer_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) @@ -754,8 +755,7 @@ int do_alsa_device_init_if_needed() { } close_mixer(); } - debug_mutex_unlock(&alsa_mutex, 3); // release the mutex - + debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex pthread_cleanup_pop(0); pthread_setcancelstate(oldState, NULL); } @@ -1023,8 +1023,6 @@ static int init(int argc, char **argv) { debug(1, "alsa: output device name is \"%s\".", alsa_out_dev); - alsa_mix_handle = NULL; // indicates alsa handle isn't open - // so, now, if the option to keep the DAC running has been selected, start a // thread to monitor the // length of the queue @@ -1053,6 +1051,7 @@ static void deinit(void) { int set_mute_state() { int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected + pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 10000, 0); if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && (open_mixer() == 1)) { response = 0; // okay if actually using the mute facility @@ -1079,6 +1078,8 @@ int set_mute_state() { } close_mixer(); } + debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex + pthread_cleanup_pop(0); // release the mutex return response; } @@ -1385,10 +1386,11 @@ int play(void *buf, int samples) { if (alsa_backend_state != abm_playing) { debug(2, "alsa: play() -- alsa_backend_state => abm_playing"); alsa_backend_state = abm_playing; - mute_requested_internally = 0; // stop requesting a mute for backend's own + + // mute_requested_internally = 0; // stop requesting a mute for backend's own // reasons, which might have been a flush - debug(2, "play() set_mute_state"); - set_mute_state(); // try to action the request and return a status + //debug(2, "play() set_mute_state"); + //set_mute_state(); // try to action the request and return a status // do_mute(0); // unmute for backend's reason } ret = do_play(buf, samples); @@ -1437,6 +1439,7 @@ void do_volume(double vol) { // caller is assumed to have the alsa_mutex when int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable set_volume = vol; + pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1); if (volume_set_request && (open_mixer() == 1)) { if (has_softvol) { if (ctl && elem_id) { @@ -1466,15 +1469,14 @@ void do_volume(double vol) { // caller is assumed to have the alsa_mutex when volume_set_request = 0; // any external request that has been made is now satisfied close_mixer(); } + debug_mutex_unlock(&alsa_mixer_mutex, 3); + pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); } void volume(double vol) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 1000, 1); volume_set_request = 1; // an external request has been made to set the volume do_volume(vol); - debug_mutex_unlock(&alsa_mutex, 3); - pthread_cleanup_pop(0); // release the mutex } /* From 02bc269a4a1f4ec3d77729ffca94eb57d2652068 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 16:17:53 +0000 Subject: [PATCH 089/115] Fix a situation where cancellation could lose a malloced buffer. --- rtsp.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rtsp.c b/rtsp.c index f7c6acce..c60f859e 100644 --- a/rtsp.c +++ b/rtsp.c @@ -576,13 +576,13 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes enum rtsp_read_request_response reply = rtsp_read_request_response_ok; ssize_t buflen = 4096; + int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay char *buf = malloc(buflen + 1); // add a NUL at the end if (!buf) { warn("rtsp_read_request: can't get a buffer."); - reply = rtsp_read_request_response_error; - goto shutdown; - } - + return(rtsp_read_request_response_error); + } + pthread_cleanup_push(malloc_cleanup, buf); ssize_t nread; ssize_t inbuf = 0; int msg_size = -1; @@ -717,11 +717,12 @@ do { char *jp = inbuf + buf; *jp = '\0'; *the_packet = msg; - return reply; - shutdown: - msg_free(the_packet); - free(buf); + if (reply != rtsp_read_request_response_ok) { + msg_free(the_packet); + release_buffer = 1; // allow the buffer to be released + } + pthread_cleanup_pop(release_buffer); return reply; } From bfc7b9b774c9f50f4408a283ae0b0bc7483c7c72 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 16:19:27 +0000 Subject: [PATCH 090/115] lengthen some mutex timeouts to allow valgrind to work okay on a Pi 3B. Make operations that open the mixer uncancellable until it's closed again to stop a few small memory leaks when a player is cancelled. --- audio_alsa.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index c85712f8..e54230da 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1051,16 +1051,18 @@ static void deinit(void) { int set_mute_state() { int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 10000, 0); if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) && (open_mixer() == 1)) { response = 0; // okay if actually using the mute facility - debug(1, "set_mute_state"); + debug(2, "alsa: actually set_mute_state"); int mute = 0; if ((mute_requested_externally != 0) || (mute_requested_internally != 0)) mute = 1; if (mute == 1) { - debug(1, "Hardware mute switched on"); + debug(2, "alsa: hardware mute switched on"); if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0); else { @@ -1068,7 +1070,7 @@ int set_mute_state() { do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute); } } else { - debug(1, "Hardware mute switched off"); + debug(2, "alsa: hardware mute switched off"); if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1); else { @@ -1080,6 +1082,7 @@ int set_mute_state() { } debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex pthread_cleanup_pop(0); // release the mutex + pthread_setcancelstate(oldState, NULL); return response; } @@ -1374,7 +1377,7 @@ int play(void *buf, int samples) { // debug(3,"audio_alsa play called."); int ret = 0; - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 1); if (alsa_backend_state == abm_disconnected) { ret = do_open(); @@ -1520,7 +1523,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { do_alsa_device_init_if_needed(); } int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 20000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 1); // 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 From 08e84ece27b4c9f4b00af3d3b5a4cc82f4d54964 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 21:07:13 +0000 Subject: [PATCH 091/115] add a volume control mutex to serialise multiple attempts to change the volume --- rtsp.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rtsp.c b/rtsp.c index c60f859e..67e2a88a 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2164,7 +2164,10 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { } // remove flow control and mutexes - int rc = pthread_cond_destroy(&conn->flowcontrol); + int rc = pthread_mutex_destroy(&conn->volume_control_mutex); + if (rc) + debug(1, "Connection %d: error %d destroying volume_control_mutex.", conn->connection_number, rc); + rc = pthread_cond_destroy(&conn->flowcontrol); if (rc) debug(1, "Connection %d: error %d destroying flow control condition variable.", conn->connection_number, rc); @@ -2227,6 +2230,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (rc) die("Connection %d: error %d initialising flow control condition variable.", conn->connection_number, rc); + rc = pthread_mutex_init(&conn->volume_control_mutex, NULL); + if (rc) + die("Connection %d: error %d initialising volume_control_mutex.", conn->connection_number, rc); // nothing before this is cancellable pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); From 46d117b69a94a4792cebdee61b38dfeda0a1d0cd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 21:07:35 +0000 Subject: [PATCH 092/115] add a volume control mutex to serialise multiple attempts to change the volume --- player.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.h b/player.h index 00793a92..28b0341b 100644 --- a/player.h +++ b/player.h @@ -133,7 +133,7 @@ typedef struct { int32_t last_seqno_read; // mutexes and condition variables pthread_cond_t flowcontrol; - pthread_mutex_t ab_mutex, flush_mutex; + pthread_mutex_t ab_mutex, flush_mutex, volume_control_mutex; int fix_volume; uint32_t timestamp_epoch, last_timestamp, maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2 From 601ac4d760b2fefb2892f651b988aa59f3ad3322 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 21:08:41 +0000 Subject: [PATCH 093/115] add a volume control mutex to serialise multiple attempts to change the volume, stop setting the fixed volume to max if using hybrid volume setup --- player.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/player.c b/player.c index fa2e2c3c..c0b4b54f 100644 --- a/player.c +++ b/player.c @@ -2542,6 +2542,7 @@ void *player_thread_func(void *arg) { } void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) { + debug_mutex_lock(&conn->volume_control_mutex, 5000, 1); debug(2, "player_volume_without_notification %f", airplay_volume); // first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw // attenuation on top @@ -2632,8 +2633,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c min_db = sw_min_db; break; case vol_both: - debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, - sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); + //debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, + // sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested min_db = 0; @@ -2671,7 +2672,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // volume to its lowest // before using the hw attenuation. // one imagines that hw priority is likely to be much better - if (config.volume_range_hw_priority) { + // if (config.volume_range_hw_priority) { + if (1) { // hw priority if ((sw_max_db - sw_min_db) > scaled_attenuation) { software_attenuation = sw_min_db + scaled_attenuation; @@ -2700,7 +2702,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value // debug(1,"Hardware attenuation set to %f for airplay volume of // %f.",hardware_attenuation,airplay_volume); - conn->fix_volume = 0x10000; + if (volume_mode == vol_hw_only) + conn->fix_volume = 0x10000; } if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) { @@ -2709,7 +2712,6 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // %f",software_attenuation,temp_fix_volume,airplay_volume); conn->fix_volume = temp_fix_volume; - memory_barrier(); // no cancellation points if (config.loudness) loudness_set_volume(software_attenuation / 100); @@ -2743,6 +2745,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } } config.airplay_volume = airplay_volume; + debug_mutex_unlock(&conn->volume_control_mutex, 3); } void player_volume(double airplay_volume, rtsp_conn_info *conn) { From cb9a3076b76f9c7a9fa989e31b3db3bdc27bc3aa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 1 Feb 2019 21:14:24 +0000 Subject: [PATCH 094/115] New volume code, new mute code, fix cancellation memnory leaks, defer alsa device initialisation until needed. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index af9268e8..0252f709 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d49], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d50], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From cf03983694e87c764a6346dc5119fe6e080e6996 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 2 Feb 2019 13:05:49 +0000 Subject: [PATCH 095/115] Fix warnings about deprecated mbedtls API calls --- metadata_hub.c | 6 +++--- player.c | 2 +- rtsp.c | 38 +++++++++++++++++++------------------- shairport.c | 6 +++--- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/metadata_hub.c b/metadata_hub.c index f6c92ae8..015327f0 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -228,9 +228,9 @@ char *metadata_write_image_file(const char *buf, int len) { #ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (const unsigned char *)buf, len); - mbedtls_md5_finish(&tctx, img_md5); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len); + mbedtls_md5_finish_ret(&tctx, img_md5); #endif #ifdef CONFIG_POLARSSL diff --git a/player.c b/player.c index c0b4b54f..e94e249b 100644 --- a/player.c +++ b/player.c @@ -2622,7 +2622,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c } } else { - int32_t max_db, min_db; + int32_t max_db =0, min_db = 0; switch (volume_mode) { case vol_hw_only: max_db = hw_max_db; diff --git a/rtsp.c b/rtsp.c index 67e2a88a..3143dba5 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2039,18 +2039,18 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); - mbedtls_md5_finish(&tctx, digest_urp); - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); - mbedtls_md5_finish(&tctx, digest_mu); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)username, strlen(username)); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)realm, strlen(realm)); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)config.password, strlen(config.password)); + mbedtls_md5_finish_ret(&tctx, digest_urp); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)req->method, strlen(req->method)); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri)); + mbedtls_md5_finish_ret(&tctx, digest_mu); #endif #ifdef CONFIG_POLARSSL @@ -2089,15 +2089,15 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #endif #ifdef CONFIG_MBEDTLS - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, buf, 32); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); - mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); - mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, buf, 32); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); + mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); - mbedtls_md5_update(&tctx, buf, 32); - mbedtls_md5_finish(&tctx, digest_total); + mbedtls_md5_update_ret(&tctx, buf, 32); + mbedtls_md5_finish_ret(&tctx, digest_total); #endif #ifdef CONFIG_POLARSSL diff --git a/shairport.c b/shairport.c index 15f8b9c8..9a26a368 100644 --- a/shairport.c +++ b/shairport.c @@ -1610,9 +1610,9 @@ int main(int argc, char **argv) { #ifdef CONFIG_MBEDTLS mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); - mbedtls_md5_finish(&tctx, ap_md5); + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish_ret(&tctx, ap_md5); #endif #ifdef CONFIG_POLARSSL From 4fb88a3aa56e02f8b0b3a98e83d3473788e4ff97 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 2 Feb 2019 15:03:27 +0000 Subject: [PATCH 096/115] allow for updated or old mbedtls library --- metadata_hub.c | 15 +++++++++++---- rtsp.c | 28 ++++++++++++++++++++++++++++ shairport.c | 15 +++++++++++---- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/metadata_hub.c b/metadata_hub.c index 015327f0..3ec7a414 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -227,10 +227,17 @@ char *metadata_write_image_file(const char *buf, int len) { #endif #ifdef CONFIG_MBEDTLS - mbedtls_md5_context tctx; - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len); - mbedtls_md5_finish_ret(&tctx, img_md5); + #if MBEDTLS_VERSION_MINOR >= 7 + mbedtls_md5_context tctx; + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len); + mbedtls_md5_finish_ret(&tctx, img_md5); + #else + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)buf, len); + mbedtls_md5_finish(&tctx, img_md5); + #endif #endif #ifdef CONFIG_POLARSSL diff --git a/rtsp.c b/rtsp.c index 3143dba5..fb66d4c3 100644 --- a/rtsp.c +++ b/rtsp.c @@ -2038,6 +2038,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #endif #ifdef CONFIG_MBEDTLS + #if MBEDTLS_VERSION_MINOR >= 7 mbedtls_md5_context tctx; mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, (const unsigned char *)username, strlen(username)); @@ -2051,6 +2052,21 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri)); mbedtls_md5_finish_ret(&tctx, digest_mu); + #else + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); + mbedtls_md5_finish(&tctx, digest_urp); + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); + mbedtls_md5_finish(&tctx, digest_mu); + #endif #endif #ifdef CONFIG_POLARSSL @@ -2089,6 +2105,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { #endif #ifdef CONFIG_MBEDTLS + #if MBEDTLS_VERSION_MINOR >= 7 mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, buf, 32); mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); @@ -2098,6 +2115,17 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); mbedtls_md5_update_ret(&tctx, buf, 32); mbedtls_md5_finish_ret(&tctx, digest_total); + #else + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, buf, 32); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); + mbedtls_md5_update(&tctx, (unsigned char *)":", 1); + for (i = 0; i < 16; i++) + snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); + mbedtls_md5_update(&tctx, buf, 32); + mbedtls_md5_finish(&tctx, digest_total); + #endif #endif #ifdef CONFIG_POLARSSL diff --git a/shairport.c b/shairport.c index 9a26a368..617e9009 100644 --- a/shairport.c +++ b/shairport.c @@ -1609,10 +1609,17 @@ int main(int argc, char **argv) { #endif #ifdef CONFIG_MBEDTLS - mbedtls_md5_context tctx; - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); - mbedtls_md5_finish_ret(&tctx, ap_md5); + #if MBEDTLS_VERSION_MINOR >= 7 + mbedtls_md5_context tctx; + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish_ret(&tctx, ap_md5); + #else + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish(&tctx, ap_md5); + #endif #endif #ifdef CONFIG_POLARSSL From 9239a7c56e43a2f4b20ed36dcfb1146cf8a237cf Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 2 Feb 2019 15:27:26 +0000 Subject: [PATCH 097/115] Include the mbedtls version.h file so that conditional compilation can be done right --- metadata_hub.c | 1 + rtsp.c | 1 + shairport.c | 1 + 3 files changed, 3 insertions(+) diff --git a/metadata_hub.c b/metadata_hub.c index 3ec7a414..56869647 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -46,6 +46,7 @@ #include "metadata_hub.h" #ifdef CONFIG_MBEDTLS +#include #include #endif diff --git a/rtsp.c b/rtsp.c index fb66d4c3..4b4b28e9 100644 --- a/rtsp.c +++ b/rtsp.c @@ -51,6 +51,7 @@ #endif #ifdef CONFIG_MBEDTLS +#include #include #endif diff --git a/shairport.c b/shairport.c index 617e9009..3c939c64 100644 --- a/shairport.c +++ b/shairport.c @@ -44,6 +44,7 @@ #include "config.h" #ifdef CONFIG_MBEDTLS +#include #include #endif From 7b8ce7974a714a92c53e1fc45411ed13d9491413 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 3 Feb 2019 16:52:36 +0000 Subject: [PATCH 098/115] allow configuration numbers to convert between floats and integers automatically. --- shairport.c | 1 + 1 file changed, 1 insertion(+) diff --git a/shairport.c b/shairport.c index 3c939c64..23409b76 100644 --- a/shairport.c +++ b/shairport.c @@ -366,6 +366,7 @@ int parse_options(int argc, char **argv) { /* Read the file. If there is an error, report it and exit. */ if (config_read_file(&config_file_stuff, config_file_real_path)) { free(config_file_real_path); + config_set_auto_convert (&config_file_stuff, 1); // allow autoconversion from int/float to int/float // make config.cfg point to it config.cfg = &config_file_stuff; /* Get the Service Name. */ From 550f22a344f88bcffd72a2648e8acfe6f90d2c52 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 3 Feb 2019 17:38:33 +0000 Subject: [PATCH 099/115] Include some more settings in the debug listing. --- shairport.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/shairport.c b/shairport.c index 23409b76..9ef40521 100644 --- a/shairport.c +++ b/shairport.c @@ -1535,10 +1535,14 @@ int main(int argc, char **argv) { debug(1, "udp port range is %d.", config.udp_port_range); debug(1, "player name is \"%s\".", config.service_name); debug(1, "backend is \"%s\".", config.output_name); - debug(1, "on-start action is \"%s\".", config.cmd_start); - debug(1, "on-stop action is \"%s\".", config.cmd_stop); + debug(1, "run_this_before_play_begins action is \"%s\".", config.cmd_start); + debug(1, "run_this_after_play_ends action is \"%s\".", config.cmd_stop); debug(1, "wait-cmd status is %d.", config.cmd_blocking); - debug(1, "on-start returns output is %d.", config.cmd_start_returns_output); + debug(1, "run_this_before_play_begins may return output is %d.", config.cmd_start_returns_output); + debug(1, "run_this_if_an_unfixable_error_is_detected action is \"%s\".", config.cmd_unfixable); + debug(1, "run_this_before_entering_active_mode action is \"%s\".", config.cmd_active_start); + debug(1, "run_this_after_exiting_active_mode action is \"%s\".", config.cmd_active_stop); + debug(1, "active_mode_timeout is %f seconds.", config.active_mode_timeout); debug(1, "mdns backend \"%s\".", config.mdns_name); debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing); @@ -1548,6 +1552,7 @@ int main(int argc, char **argv) { debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); + debug(1, "combined attenuators (0 -- software is / 1 -- hardware is top of attenuation range) is %d.", config.volume_range_hw_priority); if (config.volume_max_db_set) debug(1, "volume_max_db is %d.", config.volume_max_db); else From d54025486a4a4e2c66079341d0fbf954c0a6bf4d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 4 Feb 2019 16:58:55 +0000 Subject: [PATCH 100/115] Include the mdns_external interface even if avahi is not included. --- mdns.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mdns.c b/mdns.c index de703975..56b93448 100644 --- a/mdns.c +++ b/mdns.c @@ -34,7 +34,6 @@ #ifdef CONFIG_AVAHI extern mdns_backend mdns_avahi; -extern mdns_backend mdns_external_avahi; #endif #ifdef CONFIG_DNS_SD extern mdns_backend mdns_dns_sd; @@ -43,11 +42,11 @@ extern mdns_backend mdns_external_dns_sd; #ifdef CONFIG_TINYSVCMDNS extern mdns_backend mdns_tinysvcmdns; #endif +extern mdns_backend mdns_external_avahi; static mdns_backend *mdns_backends[] = { #ifdef CONFIG_AVAHI &mdns_avahi, - &mdns_external_avahi, #endif #ifdef CONFIG_DNS_SD &mdns_dns_sd, @@ -56,6 +55,7 @@ static mdns_backend *mdns_backends[] = { #ifdef CONFIG_TINYSVCMDNS &mdns_tinysvcmdns, #endif + &mdns_external_avahi, NULL}; void mdns_register(void) { From 3162977c712496adcce6a19f9aa40c20a32dce42 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 09:45:16 +0000 Subject: [PATCH 101/115] make mdns-external-avahi a conditional compilation --- Makefile.am | 6 +++++- configure.ac | 4 +++- mdns.c | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index c384807c..f6d2fdf7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,7 +8,7 @@ bin_PROGRAMS = shairport-sync # See below for the flags for the test client program -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c if BUILD_FOR_FREEBSD AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\" @@ -40,6 +40,10 @@ if USE_TINYSVCMDNS shairport_sync_SOURCES += mdns_tinysvcmdns.c tinysvcmdns.c endif +if USE_EXTERNAL_MDNS +shairport_sync_SOURCES += mdns_external.c +endif + if USE_ALSA shairport_sync_SOURCES += audio_alsa.c endif diff --git a/configure.ac b/configure.ac index 0252f709..02d49f0c 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,6 @@ AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "xyes" ]) AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], ) - AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "xyes" ]) AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], ) @@ -95,6 +94,9 @@ AC_ARG_WITH([freebsd-service], [ --with-freebsd-service = install a FreeBSD startup script during a make install], , ) AM_CONDITIONAL([INSTALL_FREEBSD_SERVICE], [test "x$with_freebsd_service" = "xyes"]) +AC_ARG_WITH([external-mdns],[ --with-external-mdns = support the use of 'avahi-publish-service' or 'mDNSPublish' to advertise the service on Bonjour/ZeroConf ],[ AC_MSG_RESULT(>>Including external mdns support) AC_DEFINE([CONFIG_EXTERNAL_MDNS], 1, [Needed by the compiler.]) ], ) +AM_CONDITIONAL([USE_EXTERNAL_MDNS], [test "x$with_external_mdns" = "xyes" ]) + # Add the libconfig package if test "x${with_pkg_config}" = xyes ; then PKG_CHECK_MODULES( diff --git a/mdns.c b/mdns.c index 56b93448..8550f520 100644 --- a/mdns.c +++ b/mdns.c @@ -42,7 +42,10 @@ extern mdns_backend mdns_external_dns_sd; #ifdef CONFIG_TINYSVCMDNS extern mdns_backend mdns_tinysvcmdns; #endif + +#ifdef CONFIG_EXTERNAL_MDNS extern mdns_backend mdns_external_avahi; +#endif static mdns_backend *mdns_backends[] = { #ifdef CONFIG_AVAHI @@ -55,7 +58,9 @@ static mdns_backend *mdns_backends[] = { #ifdef CONFIG_TINYSVCMDNS &mdns_tinysvcmdns, #endif +#ifdef CONFIG_EXTERNAL_MDNS &mdns_external_avahi, +#endif NULL}; void mdns_register(void) { From 1ae7856dd27d3cab5aa6942e253e96f27a0b3de1 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 10:23:24 +0000 Subject: [PATCH 102/115] Stop a segmentation error if a bsackend has no help routine --- audio.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/audio.c b/audio.c index 7d0f489f..a2c21287 100644 --- a/audio.c +++ b/audio.c @@ -112,8 +112,12 @@ void audio_ls_outputs(void) { for (out = outputs; *out; out++) { printf("\n"); - printf("Options for output %s:\n", (*out)->name); - (*out)->help(); + if ((*out)->help) { + printf("Options for output %s:\n", (*out)->name); + (*out)->help(); + } else { + printf("No options for output %s:\n", (*out)->name); + } } } From 552218ab6ea149bcd2d26a3bf9e393094708481a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 11:26:54 +0000 Subject: [PATCH 103/115] Clean up audio help texts, add in the volume_range_hw_priority setting. --- audio.c | 6 +++--- audio_alsa.c | 11 +++++------ audio_dummy.c | 3 +-- audio_pa.c | 4 +--- audio_pipe.c | 2 +- audio_stdout.c | 4 +--- common.h | 2 +- player.c | 2 +- scripts/shairport-sync.conf | 1 + shairport.c | 12 +++++++----- 10 files changed, 22 insertions(+), 25 deletions(-) diff --git a/audio.c b/audio.c index a2c21287..78686376 100644 --- a/audio.c +++ b/audio.c @@ -106,17 +106,17 @@ audio_output *audio_get_output(char *name) { void audio_ls_outputs(void) { audio_output **out; - printf("Available audio outputs:\n"); + printf("Available audio backends:\n"); for (out = outputs; *out; out++) printf(" %s%s\n", (*out)->name, out == outputs ? " (default)" : ""); for (out = outputs; *out; out++) { printf("\n"); if ((*out)->help) { - printf("Options for output %s:\n", (*out)->name); + printf("Settings and options for the audio backend \"%s\":\n", (*out)->name); (*out)->help(); } else { - printf("No options for output %s:\n", (*out)->name); + printf("There are no settings or options for the audio backend \"%s\".\n", (*out)->name); } } } diff --git a/audio_alsa.c b/audio_alsa.c index e54230da..2fd4497d 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -162,11 +162,10 @@ static uint64_t frame_index; static int measurement_data_is_valid; static void help(void) { - printf(" -d output-device set the output device [default*|...]\n" - " -m mixer-device set the mixer device ['output-device'*|...]\n" - " -c mixer-control set the mixer control [Master*|...]\n" - " -i mixer-index set the mixer index [0*|...]\n" - " *) default option\n"); + printf(" -d output-device set the output device, default is \"default\".\n" + " -c mixer-control set the mixer control name, default is to use no mixer.\n" + " -m mixer-device set the mixer device, default is the output device.\n" + " -i mixer-index set the mixer index, default is 0.\n"); } void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; } @@ -202,7 +201,7 @@ int open_mixer() { debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl); alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid); if (!alsa_mix_elem) { - debug(1, "Failed to find mixer element"); + warn("failed to find mixer control \"%s\".", alsa_mix_ctrl); response = -5; } else { response = 1; // we found a hardware mixer and successfully opened it diff --git a/audio_dummy.c b/audio_dummy.c index d8dea87f..88c63c94 100644 --- a/audio_dummy.c +++ b/audio_dummy.c @@ -54,10 +54,9 @@ static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int s static void stop(void) { debug(1, "dummy audio stopped\n"); } -static void help(void) { printf(" There are no options for dummy audio.\n"); } audio_output audio_dummy = {.name = "dummy", - .help = &help, + .help = NULL, .init = &init, .deinit = &deinit, .start = &start, diff --git a/audio_pa.c b/audio_pa.c index cd321bd9..8dd38b8b 100644 --- a/audio_pa.c +++ b/audio_pa.c @@ -300,10 +300,8 @@ static void stop(void) { pa_stream_disconnect(stream); } -static void help(void) { printf(" no settings.\n"); } - audio_output audio_pa = {.name = "pa", - .help = &help, + .help = NULL, .init = &init, .deinit = &deinit, .start = &start, diff --git a/audio_pipe.c b/audio_pipe.c index 89a09a0c..e97f6034 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -127,7 +127,7 @@ static void deinit(void) { close(fd); } -static void help(void) { printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); } +static void help(void) { printf(" specify the pathname of the pipe to write to.\n"); } audio_output audio_pipe = {.name = "pipe", .help = &help, diff --git a/audio_stdout.c b/audio_stdout.c index 0438b1af..ea48e73d 100644 --- a/audio_stdout.c +++ b/audio_stdout.c @@ -74,10 +74,8 @@ static void deinit(void) { // don't close stdout } -static void help(void) { printf(" stdout takes no arguments\n"); } - audio_output audio_stdout = {.name = "stdout", - .help = &help, + .help = NULL, .init = &init, .deinit = &deinit, .start = &start, diff --git a/common.h b/common.h index 463e49b4..59fa72ce 100644 --- a/common.h +++ b/common.h @@ -182,7 +182,7 @@ typedef struct { // into the "active" mode. uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's // native range. - int volume_range_hw_priority; // when lowering the volume, use all the hw attenuation before using + 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; diff --git a/player.c b/player.c index e94e249b..8cf3df90 100644 --- a/player.c +++ b/player.c @@ -2673,7 +2673,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // before using the hw attenuation. // one imagines that hw priority is likely to be much better // if (config.volume_range_hw_priority) { - if (1) { + if (config.volume_range_hw_priority != 0) { // hw priority if ((sw_max_db - sw_min_db) > scaled_attenuation) { software_attenuation = sw_min_db + scaled_attenuation; diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 031ce572..4a6ebe29 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -29,6 +29,7 @@ general = // volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume. // "standard" makes the volume change more quickly at lower volumes and slower at higher volumes. // "flat" makes the volume change at the same rate at all volumes. +// volume_range_hw_priority = "yes"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware attenuator first, then the software attenuator. // run_this_when_volume_is_set = "/full/path/to/application/and/args"; // Run the specified application whenever the volume control is set or changed. // The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument. // AirPlay volume goes from 0 to -30 and -144 means "mute". diff --git a/shairport.c b/shairport.c index 9ef40521..e2e7b3c4 100644 --- a/shairport.c +++ b/shairport.c @@ -62,7 +62,6 @@ #include "activity_monitor.h" #include "common.h" -#include "mdns.h" #include "rtp.h" #include "rtsp.h" @@ -96,7 +95,7 @@ #include #endif -static int shutting_down = 0; +// static int shutting_down = 0; char configuration_file_path[4096 + 1]; char actual_configuration_file_path[4096 + 1]; @@ -113,11 +112,9 @@ static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused) static void sig_child(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar, __attribute__((unused)) void *baz) { + // wait for child processes to exit pid_t pid; while ((pid = waitpid((pid_t)-1, 0, WNOHANG)) > 0) { - if (pid == mdns_pid && !shutting_down) { - die("MDNS child process died unexpectedly!"); - } } } @@ -333,6 +330,8 @@ int parse_options(int argc, char **argv) { config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; config.active_mode_timeout = 10.0; + config.volume_range_hw_priority = 1; // if combining software and hardware volume control, give the hardware priority + // i.e. when reducing volume, reduce the hw first before reducing the software. #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; @@ -608,6 +607,9 @@ int parse_options(int argc, char **argv) { die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default) " "or \"flat\""); } + + config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority", &config.volume_range_hw_priority); + /* Get the interface to listen on, if specified Default is all interfaces */ /* we keep the interface name and the index */ From 3d1ee2e03fd4a22b842b60ba8b383c03932890de Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 11:38:11 +0000 Subject: [PATCH 104/115] Tidy code and slightly reorder settings listing. --- player.c | 15 ++++++++++----- shairport.c | 50 +++++++++++++++++++++++++++----------------------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/player.c b/player.c index 8cf3df90..ccae0795 100644 --- a/player.c +++ b/player.c @@ -2615,14 +2615,18 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c if (airplay_volume == -144.0) { if ((config.output->mute) && (config.output->mute(1) == 0)) - debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, hardware mute is enabled.", volume_mode, airplay_volume); + debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, " + "hardware mute is enabled.", + volume_mode, airplay_volume); else { conn->software_mute_enabled = 1; - debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, software mute is enabled.", volume_mode, airplay_volume); + debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, " + "software mute is enabled.", + volume_mode, airplay_volume); } } else { - int32_t max_db =0, min_db = 0; + int32_t max_db = 0, min_db = 0; switch (volume_mode) { case vol_hw_only: max_db = hw_max_db; @@ -2633,7 +2637,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c min_db = sw_min_db; break; case vol_both: - //debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, + // debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db, // sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db)); max_db = (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested @@ -2739,7 +2743,8 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c config.output->mute(0); conn->software_mute_enabled = 0; - debug(2, "player_volume_without_notification: volume mode is %d, airplay volume is %f, software_attenuation: %f, hardware_attenuation: %f, muting " + debug(2, "player_volume_without_notification: volume mode is %d, airplay volume is %f, " + "software_attenuation: %f, hardware_attenuation: %f, muting " "is disabled.", volume_mode, airplay_volume, software_attenuation, hardware_attenuation); } diff --git a/shairport.c b/shairport.c index e2e7b3c4..fd95ad9c 100644 --- a/shairport.c +++ b/shairport.c @@ -44,8 +44,8 @@ #include "config.h" #ifdef CONFIG_MBEDTLS -#include #include +#include #endif #ifdef CONFIG_POLARSSL @@ -330,8 +330,9 @@ int parse_options(int argc, char **argv) { config.fixedLatencyOffset = 11025; // this sounds like it works properly. config.diagnostic_drop_packet_fraction = 0.0; config.active_mode_timeout = 10.0; - config.volume_range_hw_priority = 1; // if combining software and hardware volume control, give the hardware priority - // i.e. when reducing volume, reduce the hw first before reducing the software. + config.volume_range_hw_priority = + 1; // if combining software and hardware volume control, give the hardware priority +// i.e. when reducing volume, reduce the hw first before reducing the software. #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; @@ -365,7 +366,8 @@ int parse_options(int argc, char **argv) { /* Read the file. If there is an error, report it and exit. */ if (config_read_file(&config_file_stuff, config_file_real_path)) { free(config_file_real_path); - config_set_auto_convert (&config_file_stuff, 1); // allow autoconversion from int/float to int/float + config_set_auto_convert(&config_file_stuff, + 1); // allow autoconversion from int/float to int/float // make config.cfg point to it config.cfg = &config_file_stuff; /* Get the Service Name. */ @@ -607,9 +609,9 @@ int parse_options(int argc, char **argv) { die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default) " "or \"flat\""); } - - config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority", &config.volume_range_hw_priority); + config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority", + &config.volume_range_hw_priority); /* Get the interface to listen on, if specified Default is all interfaces */ /* we keep the interface name and the index */ @@ -1525,9 +1527,8 @@ int main(int argc, char **argv) { /* Print out options */ debug(1, "log verbosity is %d.", debuglev); debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off"); - debug(1, - "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " - "deliberately.", + debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " + "deliberately.", config.diagnostic_drop_packet_fraction); debug(1, "statistics_requester status is %d.", config.statistics_requested); debug(1, "daemon status is %d.", config.daemonise); @@ -1554,11 +1555,16 @@ int main(int argc, char **argv) { debug(1, "drift tolerance is %f seconds.", config.tolerance); debug(1, "password is \"%s\".", config.password); debug(1, "ignore_volume_control is %d.", config.ignore_volume_control); - debug(1, "combined attenuators (0 -- software is / 1 -- hardware is top of attenuation range) is %d.", config.volume_range_hw_priority); if (config.volume_max_db_set) debug(1, "volume_max_db is %d.", config.volume_max_db); else debug(1, "volume_max_db is not set"); + debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", + config.volume_range_db); + debug( + 1, + "combined attenuators (0 -- software is / 1 -- hardware is top of attenuation range) is %d.", + config.volume_range_hw_priority); debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).", config.playback_mode); debug(1, "disable_synchronization is %d.", config.no_sync); @@ -1572,8 +1578,6 @@ int main(int argc, char **argv) { debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset); debug(1, "audio backend silence lead-in time is %f seconds. A value -1.0 means use the default.", config.audio_backend_silent_lead_in_time); - debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", - config.volume_range_db); debug(1, "zeroconf regtype is \"%s\".", config.regtype); debug(1, "decoders_supported field is %d.", config.decoders_supported); debug(1, "use_apple_decoder is %d.", config.use_apple_decoder); @@ -1618,17 +1622,17 @@ int main(int argc, char **argv) { #endif #ifdef CONFIG_MBEDTLS - #if MBEDTLS_VERSION_MINOR >= 7 - mbedtls_md5_context tctx; - mbedtls_md5_starts_ret(&tctx); - mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); - mbedtls_md5_finish_ret(&tctx, ap_md5); - #else - mbedtls_md5_context tctx; - mbedtls_md5_starts(&tctx); - mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); - mbedtls_md5_finish(&tctx, ap_md5); - #endif +#if MBEDTLS_VERSION_MINOR >= 7 + mbedtls_md5_context tctx; + mbedtls_md5_starts_ret(&tctx); + mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish_ret(&tctx, ap_md5); +#else + mbedtls_md5_context tctx; + mbedtls_md5_starts(&tctx); + mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name)); + mbedtls_md5_finish(&tctx, ap_md5); +#endif #endif #ifdef CONFIG_POLARSSL From 1fc1247bb0eaaad7e43e5228a33b22e371c6c090 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 12:18:16 +0000 Subject: [PATCH 105/115] Quieten an incorrect compiler warning --- player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.c b/player.c index ccae0795..72a79ef8 100644 --- a/player.c +++ b/player.c @@ -2553,7 +2553,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // range has been specified // the range might imply that both hw and software mixers are needed, so calculate this - int32_t hw_max_db, hw_min_db; + int32_t hw_max_db = 0, hw_min_db = 0; // zeroed to quieten an incorrect uninitialised warning int32_t sw_max_db = 0, sw_min_db = -9630; if (config.output->parameters) { volume_mode = vol_hw_only; From e76cfa69fec6d17c06639f358fc76e323abb2c20 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 15:12:51 +0000 Subject: [PATCH 106/115] remove dependency on libdaemon -- leave it in by default, but allow the new configuration option --with-libdaemon. --- common.c | 60 +++++++++++++++++--- common.h | 5 +- configure.ac | 37 +++++++++--- shairport.c | 155 +++++++++++++++++++++++++-------------------------- 4 files changed, 161 insertions(+), 96 deletions(-) diff --git a/common.c b/common.c index 87526f46..d8be92a4 100644 --- a/common.c +++ b/common.c @@ -75,10 +75,13 @@ #include #include #include - #endif +#ifdef CONFIG_LIBDAEMON #include +#else +#include +#endif #ifdef CONFIG_ALSA void set_alsa_out_dev(char *); @@ -143,13 +146,24 @@ void die(const char *format, ...) { va_end(args); if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) - daemon_log(LOG_EMERG, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); + + #ifdef CONFIG_LIBDAEMON + daemon_log(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) - daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tsl, s); + daemon_log(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s); else if ((debuglev) && (config.debugger_show_elapsed_time)) - daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tss, s); + daemon_log(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s); else - daemon_log(LOG_EMERG, "fatal error: %s", s); + daemon_log(LOG_ERR, "fatal error: %s", s); + #else + syslog(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s); + else if ((debuglev) && (config.debugger_show_relative_time)) + syslog(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s); + else if ((debuglev) && (config.debugger_show_elapsed_time)) + syslog(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s); + else + syslog(LOG_ERR, "fatal error: %s", s); + #endif pthread_setcancelstate(oldState, NULL); exit(1); } @@ -170,7 +184,7 @@ void warn(const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); - +#ifdef CONFIG_LIBDAEMON if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) daemon_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) @@ -179,6 +193,16 @@ void warn(const char *format, ...) { daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); else daemon_log(LOG_WARNING, "%s", s); +#else + if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) + syslog(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s); + else if ((debuglev) && (config.debugger_show_relative_time)) + syslog(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s); + else if ((debuglev) && (config.debugger_show_elapsed_time)) + syslog(LOG_WARNING, "% 20.9f|*warning: %s", tss, s); + else + syslog(LOG_WARNING, "%s", s); +#endif pthread_setcancelstate(oldState, NULL); } @@ -200,6 +224,7 @@ void debug(int level, const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); +#ifdef CONFIG_LIBDAEMON if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) daemon_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); else if (config.debugger_show_relative_time) @@ -208,6 +233,16 @@ void debug(int level, const char *format, ...) { daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s); else daemon_log(LOG_DEBUG, "%s", s); +#else + if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) + syslog(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s); + else if (config.debugger_show_relative_time) + syslog(LOG_DEBUG, "% 20.9f|%s", tsl, s); + else if (config.debugger_show_elapsed_time) + syslog(LOG_DEBUG, "% 20.9f|%s", tss, s); + else + syslog(LOG_DEBUG, "%s", s); +#endif pthread_setcancelstate(oldState, NULL); } @@ -227,7 +262,7 @@ void inform(const char *format, ...) { va_start(args, format); vsnprintf(s, sizeof(s), format, args); va_end(args); - +#ifdef CONFIG_LIBDAEMON if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) daemon_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s); else if ((debuglev) && (config.debugger_show_relative_time)) @@ -236,6 +271,17 @@ void inform(const char *format, ...) { daemon_log(LOG_INFO, "% 20.9f|%s", tss, s); else daemon_log(LOG_INFO, "%s", s); +#else + if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time)) + syslog(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s); + else if ((debuglev) && (config.debugger_show_relative_time)) + syslog(LOG_INFO, "% 20.9f|%s", tsl, s); + else if ((debuglev) && (config.debugger_show_elapsed_time)) + syslog(LOG_INFO, "% 20.9f|%s", tss, s); + else + syslog(LOG_INFO, "%s", s); + +#endif pthread_setcancelstate(oldState, NULL); } diff --git a/common.h b/common.h index 59fa72ce..870cb1a6 100644 --- a/common.h +++ b/common.h @@ -141,10 +141,13 @@ typedef struct { uint32_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual // total latency // the total latency will be limited to the min and max-latency values, if supplied +#ifdef CONFIG_LIBDAEMON int daemonise; int daemonise_store_pid; // don't try to save a PID file char *piddir; char *computed_piddir; // the actual pid directory to create, if any + char *pidfile; +#endif int logOutputLevel; // log output level int debugger_show_elapsed_time; // in the debug message, display the time since startup @@ -159,7 +162,6 @@ typedef struct { int decoders_supported; int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by // David Hammerton - char *pidfile; // char *logfile; // char *errfile; char *configfile; @@ -320,7 +322,6 @@ void command_set_volume(double volume); int mkpath(const char *path, mode_t mode); void shairport_shutdown(); -// void shairport_startup_complete(void); extern sigset_t pselect_sigset; diff --git a/configure.ac b/configure.ac index 02d49f0c..fd934930 100644 --- a/configure.ac +++ b/configure.ac @@ -44,14 +44,35 @@ AM_CONDITIONAL([BUILD_FOR_OPENBSD], [test "x${with_os}" = xopenbsd ]) AC_ARG_WITH([pkg_config], [ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=yes]) -AC_MSG_RESULT(>>Including libdaemon) -if test "x${with_pkg_config}" = xyes ; then - PKG_CHECK_MODULES( - [DAEMON], [libdaemon], - [LIBS="${DAEMON_LIBS} ${LIBS}"]) -else - AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed)) -fi +# Look for libdaemon +AC_ARG_WITH(libdaemon, [ --with-libdaemon = include support for daemonising in older non-systemd systems], + if test "x${with_libdaemon}" = xyes ; then + [AC_MSG_RESULT(>>Including libdaemon support for older systems) + AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon]) + REQUESTED_LIBDAEMON=1 + if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [DAEMON], [libdaemon], + [LIBS="${DAEMON_LIBS} ${LIBS}"]) + else + AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed)) + fi + ] + fi, + #default is to include libdaemon + [AC_MSG_RESULT(>>Including libdaemon support for older systems) + AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon]) + REQUESTED_LIBDAEMON=1 + if test "x${with_pkg_config}" = xyes ; then + PKG_CHECK_MODULES( + [DAEMON], [libdaemon], + [LIBS="${DAEMON_LIBS} ${LIBS}"]) + else + AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed)) + fi + ] + ) +AM_CONDITIONAL([USE_LIBDAEMON], [test "x$REQUESTED_LIBDAEMON" = "x1"]) ##### The following check for the pthreads library doesn't put the compiler into the correct pthread mode ##### so we add the -pthread compilation flag in AMFLAGS in the Makefile.am as well. diff --git a/shairport.c b/shairport.c index fd95ad9c..e1689d3e 100644 --- a/shairport.c +++ b/shairport.c @@ -85,11 +85,15 @@ #include "mpris-service.h" #endif +#ifdef CONFIG_LIBDAEMON #include #include #include #include #include +#else +#include +#endif #ifdef CONFIG_CONVOLUTION #include @@ -104,9 +108,10 @@ static void sig_ignore(__attribute__((unused)) int foo, __attribute__((unused)) static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar, __attribute__((unused)) void *baz) { debug(1, "shutdown requested..."); - // daemon_log(LOG_NOTICE, "exit..."); +#ifdef CONFIG_LIBDAEMON daemon_retval_send(255); daemon_pid_file_remove(); +#endif exit(0); } @@ -148,12 +153,12 @@ void usage(char *progname) { printf("\n"); printf("Options:\n"); printf(" -h, --help show this help.\n"); +#ifdef CONFIG_LIBDAEMON printf(" -d, --daemon daemonise.\n"); printf(" -j, --justDaemoniseNoPIDFile daemonise without a PID file.\n"); +#endif printf(" -V, --version show version information.\n"); printf(" -k, --kill kill the existing shairport daemon.\n"); - printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n"); - printf(" -R, --reconnectToOutput reconnect to the output device.\n"); printf(" -c, --configfile=FILE read configuration settings from FILE. Default is " "/etc/shairport-sync.conf.\n"); @@ -313,6 +318,7 @@ int parse_options(int argc, char **argv) { poptFreeContext(optCon); +#ifdef CONFIG_LIBDAEMON if ((daemonisewith) && (daemonisewithout)) die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected " "both!"); @@ -321,6 +327,7 @@ int parse_options(int argc, char **argv) { if (daemonisewith) config.daemonise_store_pid = 1; }; +#endif config.resyncthreshold = 1.0 * fResyncthreshold / 44100; config.tolerance = 1.0 * fTolerance / 44100; @@ -374,6 +381,7 @@ int parse_options(int argc, char **argv) { if (config_lookup_string(config.cfg, "general.name", &str)) { raw_service_name = (char *)str; } +#ifdef CONFIG_LIBDAEMON int daemonisewithout = 0; int daemonisewith = 0; /* Get the Daemonize setting. */ @@ -394,7 +402,7 @@ int parse_options(int argc, char **argv) { /* Get the directory path for the pid file created when the program is daemonised. */ if (config_lookup_string(config.cfg, "sessioncontrol.daemon_pid_dir", &str)) config.piddir = (char *)str; - +#endif /* Get the mdns_backend setting. */ if (config_lookup_string(config.cfg, "general.mdns_backend", &str)) config.mdns_name = (char *)str; @@ -1049,6 +1057,8 @@ int parse_options(int argc, char **argv) { } #endif +#ifdef CONFIG_LIBDAEMON + // now, check and calculate the pid directory #ifdef DEFINED_CUSTOM_PID_DIR char *use_this_pid_dir = PIDDIR; @@ -1060,7 +1070,7 @@ int parse_options(int argc, char **argv) { use_this_pid_dir = config.piddir; if (use_this_pid_dir) config.computed_piddir = strdup(use_this_pid_dir); - +#endif return optind + 1; } @@ -1116,14 +1126,7 @@ void signal_setup(void) { sigaction(SIGCHLD, &sa, NULL); } -// forked daemon lets the spawner know it's up and running OK -// should be called only once! -void shairport_startup_complete(void) { - if (config.daemonise) { - // daemon_ready(); - } -} - +#ifdef CONFIG_LIBDAEMON char pid_file_path_string[4096] = "\0"; const char *pid_file_proc(void) { @@ -1132,29 +1135,10 @@ const char *pid_file_proc(void) { // debug(1,"pid_file_path_string \"%s\".",pid_file_path_string); return pid_file_path_string; } - -void exit_function() { - debug(1, "exit function called..."); - // cancel_all_RTSP_threads(); - if (conns) - free(conns); // make sure the connections have been deleted first - if (config.service_name) - free(config.service_name); - if (config.regtype) - free(config.regtype); - if (config.computed_piddir) - free(config.computed_piddir); - if (ranarray) - free((void *)ranarray); - if (config.cfg) - config_destroy(config.cfg); - if (config.appName) - free(config.appName); - // probably should be freeing malloc'ed memory here, including strdup-created strings... -} +#endif void main_cleanup_handler(__attribute__((unused)) void *arg) { - + // it doesn't look like this is called when the main function terminates. debug(1, "main cleanup handler called."); #ifdef CONFIG_MQTT if (config.mqtt_enabled) { @@ -1194,24 +1178,44 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { debug(1, "Deinitialise the audio backend."); config.output->deinit(); } - daemon_log(LOG_NOTICE, "Unexpected exit..."); +#ifdef CONFIG_LIBDAEMON daemon_retval_send(0); daemon_pid_file_remove(); daemon_signal_done(); +#endif + debug(1, "Exit..."); exit(0); } +void exit_function() { + debug(1, "exit function called..."); + main_cleanup_handler(NULL); + if (conns) + free(conns); // make sure the connections have been deleted first + if (config.service_name) + free(config.service_name); + if (config.regtype) + free(config.regtype); +#ifdef CONFIG_LIBDAEMON + if (config.computed_piddir) + free(config.computed_piddir); +#endif + if (ranarray) + free((void *)ranarray); + if (config.cfg) + config_destroy(config.cfg); + if (config.appName) + free(config.appName); + // probably should be freeing malloc'ed memory here, including strdup-created strings... +} + int main(int argc, char **argv) { conns = NULL; // no connections active memset((void *)&main_thread_id, 0, sizeof(main_thread_id)); + memset(&config, 0, sizeof(config)); // also clears all strings, BTW fp_time_at_startup = get_absolute_time_in_fp(); fp_time_at_last_debug_message = fp_time_at_startup; - // debug(1,"startup"); - daemon_set_verbosity(LOG_DEBUG); - memset(&config, 0, sizeof(config)); // also clears all strings, BTW - atexit(exit_function); - - // this is a bit weird, but apparently necessary + // this is a bit weird, but necessary -- basename() may modify the argument passed in char *basec = strdup(argv[0]); char *bname = basename(basec); config.appName = strdup(bname); @@ -1219,9 +1223,18 @@ int main(int argc, char **argv) { die("can not allocate memory for the app name!"); free(basec); + // debug(1,"startup"); +#ifdef CONFIG_LIBDAEMON + daemon_set_verbosity(LOG_DEBUG); +#else + setlogmask (LOG_UPTO (LOG_DEBUG)); + openlog(NULL,0,LOG_DAEMON); +#endif + atexit(exit_function); + // set defaults - // get thje endianness + // get the endianness union { uint32_t u32; uint8_t arr[4]; @@ -1298,6 +1311,8 @@ int main(int argc, char **argv) { exit(1); } +#ifdef CONFIG_LIBDAEMON + pid_t pid; /* Reset signal handlers */ @@ -1317,44 +1332,22 @@ int main(int argc, char **argv) { daemon_pid_file_proc = pid_file_proc; - /* Check if we are called with -D or --disconnectFromOutput parameter */ - if (argc >= 2 && - ((strcmp(argv[1], "-D") == 0) || (strcmp(argv[1], "--disconnectFromOutput") == 0))) { - if ((pid = daemon_pid_file_is_running()) >= 0) { - if (kill(pid, SIGUSR2) != 0) { // try to send the signal - daemon_log(LOG_WARNING, - "Failed trying to send disconnectFromOutput command to daemon pid: %d: %s", pid, - strerror(errno)); - } - } else { - daemon_log(LOG_WARNING, - "Can't send a disconnectFromOutput request -- Failed to find daemon: %s", - strerror(errno)); - } - exit(1); - } +#endif - /* Check if we are called with -R or --reconnectToOutput parameter */ - if (argc >= 2 && - ((strcmp(argv[1], "-R") == 0) || (strcmp(argv[1], "--reconnectToOutput") == 0))) { - if ((pid = daemon_pid_file_is_running()) >= 0) { - if (kill(pid, SIGHUP) != 0) { // try to send the signal - daemon_log(LOG_WARNING, - "Failed trying to send reconnectToOutput command to daemon pid: %d: %s", pid, - strerror(errno)); - } - } else { - daemon_log(LOG_WARNING, "Can't send a reconnectToOutput request -- Failed to find daemon: %s", - strerror(errno)); - } - exit(1); - } // parse arguments into config -- needed to locate pid_dir int audio_arg = parse_options(argc, argv); + // mDNS supports maximum of 63-character names (we append 13). + if (strlen(config.service_name) > 50) { + warn("Supplied name too long (max 50 characters)"); + config.service_name[50] = '\0'; // truncate it and carry on... + } + /* Check if we are called with -k or --kill parameter */ if (argc >= 2 && ((strcmp(argv[1], "-k") == 0) || (strcmp(argv[1], "--kill") == 0))) { + +#ifdef CONFIG_LIBDAEMON int ret; /* Kill daemon with SIGTERM */ @@ -1364,20 +1357,20 @@ int main(int argc, char **argv) { else daemon_pid_file_remove(); return ret < 0 ? 1 : 0; +#else + syslog(LOG_ERR, "shairport-sync was built without support for the -k or --kill option"); + return 1; +#endif + } +#ifdef CONFIG_LIBDAEMON /* If we are going to daemonise, check that the daemon is not running already.*/ if ((config.daemonise) && ((pid = daemon_pid_file_is_running()) >= 0)) { daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid); return 1; } - // mDNS supports maximum of 63-character names (we append 13). - if (strlen(config.service_name) > 50) { - warn("Supplied name too long (max 50 characters)"); - config.service_name[50] = '\0'; // truncate it and carry on... - } - /* here, daemonise with libdaemon */ if (config.daemonise) { @@ -1460,6 +1453,8 @@ int main(int argc, char **argv) { /* end libdaemon stuff */ } +#endif + main_thread_id = pthread_self(); if (!main_thread_id) debug(1, "Main thread is set up to be NULL!"); @@ -1531,8 +1526,10 @@ int main(int argc, char **argv) { "deliberately.", config.diagnostic_drop_packet_fraction); debug(1, "statistics_requester status is %d.", config.statistics_requested); +#if CONFIG_LIBDAEMON debug(1, "daemon status is %d.", config.daemonise); debug(1, "deamon pid file path is \"%s\".", pid_file_proc()); +#endif debug(1, "rtsp listening port is %d.", config.port); debug(1, "udp base port is %d.", config.udp_port_base); debug(1, "udp port range is %d.", config.udp_port_range); From 916edea5a196424a855e3d74514f74ec1350f4cc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 15:13:16 +0000 Subject: [PATCH 107/115] remove dependency on libdaemon -- leave it in by default, but allow the new configuration option --with-libdaemon. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index fd934930..a92b3365 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d50], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d51], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 79e22063efc660118063df548853761a97c63754 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 5 Feb 2019 15:51:16 +0000 Subject: [PATCH 108/115] check that the systems are started before trying to stop them, duh. --- activity_monitor.c | 13 +++++++++---- dacp.c | 16 ++++++++++------ metadata_hub.c | 23 ++++++++++++++--------- rtsp.c | 11 ++++++++--- shairport.c | 14 ++++++++------ 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 6452a08e..86af012a 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -45,6 +45,8 @@ enum am_state { am_inactive, am_active, am_timing_out } state; enum ps_state { ps_inactive, ps_active } player_state; +int activity_monitor_running = 0; + pthread_t activity_monitor_thread; pthread_mutex_t activity_monitor_mutex; pthread_cond_t activity_monitor_cv; @@ -218,11 +220,14 @@ void *activity_monitor_thread_code(void *arg) { void activity_monitor_start() { // debug(1,"activity_monitor_start"); pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); + activity_monitor_running = 1; } void activity_monitor_stop() { - debug(1, "activity_monitor_stop start..."); - pthread_cancel(activity_monitor_thread); - pthread_join(activity_monitor_thread, NULL); - debug(1, "activity_monitor_stop complete"); + if (activity_monitor_running) { + debug(1, "activity_monitor_stop start..."); + pthread_cancel(activity_monitor_thread); + pthread_join(activity_monitor_thread, NULL); + debug(1, "activity_monitor_stop complete"); + } } diff --git a/dacp.c b/dacp.c index 7efaea3a..4f40e2d0 100644 --- a/dacp.c +++ b/dacp.c @@ -58,6 +58,7 @@ typedef struct { void *port_monitor_private_storage; } dacp_server_record; +int dacp_monitor_initialised = 0; pthread_t dacp_monitor_thread; dacp_server_record dacp_server; void *mdns_dacp_monitor_private_storage_pointer; @@ -888,15 +889,18 @@ void dacp_monitor_start() { memset(&dacp_server, 0, sizeof(dacp_server_record)); pthread_create(&dacp_monitor_thread, NULL, dacp_monitor_thread_code, NULL); + dacp_monitor_initialised = 1; } void dacp_monitor_stop() { - debug(1, "dacp_monitor_stop"); - pthread_cancel(dacp_monitor_thread); - pthread_join(dacp_monitor_thread, NULL); - pthread_mutex_destroy(&dacp_server_information_lock); - debug(1, "DACP Conversation Lock Mutex Destroyed"); - pthread_mutex_destroy(&dacp_conversation_lock); + if (dacp_monitor_initialised) { // only if it's been started and initialised + debug(1, "dacp_monitor_stop"); + pthread_cancel(dacp_monitor_thread); + pthread_join(dacp_monitor_thread, NULL); + pthread_mutex_destroy(&dacp_server_information_lock); + debug(1, "DACP Conversation Lock Mutex Destroyed"); + pthread_mutex_destroy(&dacp_conversation_lock); + } } uint32_t dacp_tlv_crawl(char **p, int32_t *length) { diff --git a/metadata_hub.c b/metadata_hub.c index 56869647..f116f272 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -58,6 +58,8 @@ #include #endif +int metadata_hub_initialised = 0; + pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER; struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store @@ -100,18 +102,21 @@ void metadata_hub_init(void) { // debug(1, "Metadata bundle initialisation."); memset(&metadata_store, 0, sizeof(metadata_store)); track_metadata = NULL; + metadata_hub_initialised = 1; } void metadata_hub_stop(void) { - debug(1, "metadata_hub_stop."); - metadata_hub_release_track_artwork(); - if (metadata_store.track_metadata) { - metadata_hub_release_track_metadata(metadata_store.track_metadata); - metadata_store.track_metadata = NULL; - } - if (track_metadata) { - metadata_hub_release_track_metadata(track_metadata); - track_metadata = NULL; + if (metadata_hub_initialised) { + debug(1, "metadata_hub_stop."); + metadata_hub_release_track_artwork(); + if (metadata_store.track_metadata) { + metadata_hub_release_track_metadata(metadata_store.track_metadata); + metadata_store.track_metadata = NULL; + } + if (track_metadata) { + metadata_hub_release_track_metadata(track_metadata); + track_metadata = NULL; + } } } diff --git a/rtsp.c b/rtsp.c index 4b4b28e9..9aff0e65 100644 --- a/rtsp.c +++ b/rtsp.c @@ -91,6 +91,8 @@ enum rtsp_read_request_response { // Mike Brady's part... +int metadata_running = 0; + // always lock use this when accessing the playing conn value static pthread_mutex_t playing_conn_lock = PTHREAD_MUTEX_INITIALIZER; @@ -1454,12 +1456,15 @@ void metadata_init(void) { int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); if (ret) debug(1, "Failed to create metadata thread!"); + metadata_running = 1; } void metadata_stop(void) { - debug(1, "metadata_stop called."); - pthread_cancel(metadata_thread); - pthread_join(metadata_thread, NULL); + if (metadata_running) { + debug(1, "metadata_stop called."); + pthread_cancel(metadata_thread); + pthread_join(metadata_thread, NULL); + } } int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, diff --git a/shairport.c b/shairport.c index e1689d3e..2bea884f 100644 --- a/shairport.c +++ b/shairport.c @@ -1075,7 +1075,7 @@ int parse_options(int argc, char **argv) { } #if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) -GMainLoop *g_main_loop; +static GMainLoop *g_main_loop = NULL; pthread_t dbus_thread; void *dbus_thread_func(__attribute__((unused)) void *arg) { @@ -1138,7 +1138,7 @@ const char *pid_file_proc(void) { #endif void main_cleanup_handler(__attribute__((unused)) void *arg) { - // it doesn't look like this is called when the main function terminates. + // it doesn't look like this is called when the main function is cancelled eith a pthread cancel. debug(1, "main cleanup handler called."); #ifdef CONFIG_MQTT if (config.mqtt_enabled) { @@ -1153,9 +1153,11 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { #ifdef CONFIG_DBUS_INTERFACE stop_dbus_service(); #endif - debug(1, "Stopping DBUS Loop Thread"); - g_main_loop_quit(g_main_loop); - pthread_join(dbus_thread, NULL); + if (g_main_loop) { + debug(1, "Stopping DBUS Loop Thread"); + g_main_loop_quit(g_main_loop); + pthread_join(dbus_thread, NULL); + } #endif #ifdef CONFIG_DACP_CLIENT @@ -1174,7 +1176,7 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) { activity_monitor_stop(0); - if (config.output->deinit) { + if ((config.output) && (config.output->deinit)) { debug(1, "Deinitialise the audio backend."); config.output->deinit(); } From c86823c343a70f6106fd71e314255a8fce978e4d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 09:04:33 +0000 Subject: [PATCH 109/115] Starting to connect active mode with disable standby. --- activity_monitor.c | 6 +++++- activity_monitor.h | 4 ++++ audio_alsa.c | 24 +++++++++++++++++++----- common.h | 7 +++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 86af012a..94321f8b 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -42,7 +42,7 @@ #include "common.h" #include "rtsp.h" -enum am_state { am_inactive, am_active, am_timing_out } state; +enum am_state state; enum ps_state { ps_inactive, ps_active } player_state; int activity_monitor_running = 0; @@ -217,6 +217,10 @@ void *activity_monitor_thread_code(void *arg) { pthread_exit(NULL); } +enum am_state activity_status() { + return (state); +} + void activity_monitor_start() { // debug(1,"activity_monitor_start"); pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); diff --git a/activity_monitor.h b/activity_monitor.h index 9de33454..d9e1ffd6 100644 --- a/activity_monitor.h +++ b/activity_monitor.h @@ -1,4 +1,8 @@ #pragma once + +enum am_state { am_inactive, am_active, am_timing_out }; + void activity_monitor_start(); void activity_monitor_stop(); void activity_monitor_signify_activity(int active); // 0 means inactive, non-zero means active +enum am_state activity_status(); // true if non inactive; false if inactive diff --git a/audio_alsa.c b/audio_alsa.c index 2fd4497d..4cb187b2 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -975,12 +975,26 @@ static int init(int argc, char **argv) { } } - // Get the optional "Keep DAC Busy setting" - int kdb; - if (config_set_lookup_bool(config.cfg, "alsa.disable_standby_mode", &kdb)) { - config.keep_dac_busy = kdb; + + /* Get the optional disable_standby_mode setting. */ + config.disable_standby_mode = disable_standby_while_active; + config.keep_dac_busy = 0; + if (config_lookup_string(config.cfg, "alsa.disable_standby_mode", &str)) { + if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) || (strcasecmp(str, "never") == 0)) + config.disable_standby_mode = disable_standby_off; + else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) || (strcasecmp(str, "always") == 0)) { + config.disable_standby_mode = disable_standby_on; + config.keep_dac_busy = 1; + } else if (strcasecmp(str, "while active") == 0) + config.disable_standby_mode = disable_standby_while_active; + else { + warn("Invalid disable_standby_mode option choice \"%s\". It should be " + "\"always\", \"while active\" or \"never\". " + "It is set to \"while active\"."); + } } - debug(1, "alsa: disable_standby_mode is %s.", config.keep_dac_busy ? "on" : "off"); + + debug(1, "alsa: disable_standby_mode is %d.", config.disable_standby_mode); } optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour diff --git a/common.h b/common.h index 870cb1a6..e13ebf9e 100644 --- a/common.h +++ b/common.h @@ -62,6 +62,12 @@ enum decoders_supported_type { decoder_apple_alac, } decoders_supported_type; +enum disable_standby_mode_type { + disable_standby_off = 0, + disable_standby_while_active, + disable_standby_always +} + // the following enum is for the formats recognised -- currently only S16LE is recognised for input, // so these are output only for the present @@ -201,6 +207,7 @@ typedef struct { float loudness_reference_volume_db; int alsa_use_hardware_mute; double alsa_maximum_stall_time; + enum disable_standby_mode_type disable_standby_mode; volatile int keep_dac_busy; #if defined(CONFIG_DBUS_INTERFACE) From 8b589879be8a3587ea9a8c80b4896b473f3e8f75 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 09:40:51 +0000 Subject: [PATCH 110/115] fix a few minor compilation errors --- audio_alsa.c | 10 +++++++--- common.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 4cb187b2..ef65a345 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -27,8 +27,6 @@ #define ALSA_PCM_NEW_HW_PARAMS_API -#include "audio.h" -#include "common.h" #include #include #include @@ -37,6 +35,12 @@ #include #include +#include "config.h" + +#include "common.h" +#include "activity_monitor.h" +#include "audio.h" + enum alsa_backend_mode { abm_disconnected, abm_connected, @@ -983,7 +987,7 @@ static int init(int argc, char **argv) { if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) || (strcasecmp(str, "never") == 0)) config.disable_standby_mode = disable_standby_off; else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) || (strcasecmp(str, "always") == 0)) { - config.disable_standby_mode = disable_standby_on; + 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; diff --git a/common.h b/common.h index e13ebf9e..6fdf38c1 100644 --- a/common.h +++ b/common.h @@ -66,7 +66,7 @@ enum disable_standby_mode_type { disable_standby_off = 0, disable_standby_while_active, disable_standby_always -} +}; // the following enum is for the formats recognised -- currently only S16LE is recognised for input, // so these are output only for the present From 34351a99cb17014c9e8c0172ddbec734da038109 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 16:57:39 +0000 Subject: [PATCH 111/115] Continue hooking up active mode and disable standby stuff --- activity_monitor.c | 23 ++++++++++++++++ dbus-service.c | 55 ++++++++++++++++++++++++++++++++++++- org.gnome.ShairportSync.xml | 2 ++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/activity_monitor.c b/activity_monitor.c index 94321f8b..796e3ed0 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -40,6 +40,7 @@ #include "activity_monitor.h" #include "common.h" +#include "dbus-service.h" #include "rtsp.h" enum am_state state; @@ -60,6 +61,17 @@ void going_active(int block) { debug(2, "abeg"); // active mode begin send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif + +#ifdef CONFIG_DBUS_INTERFACE + shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); +#endif + + if (config.disable_standby_mode == disable_standby_while_active) { + config.keep_dac_busy = 1; +#ifdef CONFIG_DBUS_INTERFACE + shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); +#endif + } } void going_inactive(int block) { @@ -71,6 +83,17 @@ void going_inactive(int block) { debug(2, "aend"); // active mode end send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points #endif + +#ifdef CONFIG_DBUS_INTERFACE + shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); +#endif + + if (config.disable_standby_mode == disable_standby_while_active) { + config.keep_dac_busy = 0; +#ifdef CONFIG_DBUS_INTERFACE + shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); +#endif + } } void activity_monitor_signify_activity(int active) { diff --git a/dbus-service.c b/dbus-service.c index 59fdd2c0..c6156cb5 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -430,6 +430,36 @@ gboolean notify_drift_tolerance_callback(ShairportSync *skeleton, return TRUE; } +gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton, + __attribute__((unused)) gpointer user_data) { + char *th = (char *)shairport_sync_get_disable_standby_mode(skeleton); + if ((strcasecmp(th, "no") == 0) || (strcasecmp(th, "off") == 0) || (strcasecmp(th, "never") == 0)) { + config.disable_standby_mode = disable_standby_off; + config.keep_dac_busy = 0; + } 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 { + warn("An unrecognised disable_standby_mode: \"%s\" was requested via D-Bus interface.", th); + switch (config.disable_standby_mode) { + case disable_standby_off: + shairport_sync_set_disable_standby_mode(skeleton, "off"); + break; + 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"); + break; + default: + break; + } + } + return TRUE; +} + gboolean notify_alacdecoder_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_alacdecoder(skeleton); @@ -622,7 +652,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name G_CALLBACK(notify_interpolation_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::alacdecoder", G_CALLBACK(notify_alacdecoder_callback), NULL); - g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", + g_signal_connect(shairportSyncSkeleton, "notify::disable-standby-mode", + G_CALLBACK(notify_disable_standby_mode_callback), NULL); + g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile", G_CALLBACK(notify_volume_control_profile_callback), NULL); g_signal_connect(shairportSyncSkeleton, "notify::disable-standby", G_CALLBACK(notify_disable_standby_callback), NULL); @@ -706,6 +738,27 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name #endif + shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); + debug(1, ">> Active set to \"false\""); + + switch (config.disable_standby_mode) { + case disable_standby_off: + shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "off"); + debug(1, ">> disable standby mode set to \"off\""); + break; + case disable_standby_always: + 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\""); + break; + default: + debug(1,"invalid disable_standby mode!"); + break; + } + #ifdef CONFIG_SOXR if (config.packet_stuffing == ST_basic) { shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic"); diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index 279e4f0a..cea574bc 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -2,7 +2,9 @@ + + From 58acd161cd7da5d112c45ff0cfda09b8b4c3f752 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 20:48:29 +0000 Subject: [PATCH 112/115] make including of reference to dbus-service conditional on CONFIG_DBUS_INTERFACE --- activity_monitor.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activity_monitor.c b/activity_monitor.c index 796e3ed0..2d418d6d 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -40,9 +40,13 @@ #include "activity_monitor.h" #include "common.h" -#include "dbus-service.h" #include "rtsp.h" +#ifdef CONFIG_DBUS_INTERFACE +#include "dbus-service.h" +#endif + + enum am_state state; enum ps_state { ps_inactive, ps_active } player_state; From 97a4766e93c73e741b6ea4682f4e80ad14076d8a Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Fri, 8 Feb 2019 22:31:50 +0000 Subject: [PATCH 113/115] Add call to check if the dbus service is started. --- activity_monitor.c | 14 ++++++++++++-- audio_alsa.c | 5 +++++ dbus-service.c | 8 ++++++++ dbus-service.h | 1 + 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/activity_monitor.c b/activity_monitor.c index 2d418d6d..697bec95 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -67,13 +67,18 @@ void going_active(int block) { #endif #ifdef CONFIG_DBUS_INTERFACE + if (dbus_service_is_running()) shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); #endif if (config.disable_standby_mode == disable_standby_while_active) { - config.keep_dac_busy = 1; #ifdef CONFIG_DBUS_INTERFACE + if (dbus_service_is_running()) shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); + else + config.keep_dac_busy = 1; +#else + config.keep_dac_busy = 1; #endif } } @@ -89,13 +94,18 @@ void going_inactive(int block) { #endif #ifdef CONFIG_DBUS_INTERFACE + if (dbus_service_is_running()) shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); #endif if (config.disable_standby_mode == disable_standby_while_active) { - config.keep_dac_busy = 0; #ifdef CONFIG_DBUS_INTERFACE + if (dbus_service_is_running()) shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); + else + config.keep_dac_busy = 0; +#else + config.keep_dac_busy = 0; #endif } } diff --git a/audio_alsa.c b/audio_alsa.c index ef65a345..9658832a 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1533,7 +1533,12 @@ void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void */ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { + int okb = -1; while (1) { + if (okb != config.keep_dac_busy) { + debug(1,"keep_dac_busy is now %d",config.keep_dac_busy); + okb = config.keep_dac_busy; + } if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) { debug(2, "alsa: alsa_buffer_monitor_thread_code() calling " "do_alsa_device_init_if_needed."); diff --git a/dbus-service.c b/dbus-service.c index c6156cb5..8656d919 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -15,6 +15,8 @@ #include "dbus-service.h" +int service_is_running = 0; + ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL; ShairportSyncRemoteControl *shairportSyncRemoteControlSkeleton = NULL; ShairportSyncAdvancedRemoteControl *shairportSyncAdvancedRemoteControlSkeleton = NULL; @@ -841,6 +843,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name debug(1, "Shairport Sync native D-Bus service started at \"%s\" on the %s bus.", name, (config.dbus_service_bus_type == DBT_session) ? "session" : "system"); + service_is_running = 1; } static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection, @@ -887,4 +890,9 @@ void stop_dbus_service() { g_bus_unown_name(ownerID); else debug(1, "Zero OwnerID for \"org.gnome.ShairportSync\"."); + service_is_running = 0; +} + +int dbus_service_is_running() { + return service_is_running; } diff --git a/dbus-service.h b/dbus-service.h index a00d06e0..5cfc02e2 100644 --- a/dbus-service.h +++ b/dbus-service.h @@ -7,5 +7,6 @@ ShairportSync *shairportSyncSkeleton; int start_dbus_service(); void stop_dbus_service(); +int dbus_service_is_running(); #endif /* #ifndef DBUS_SERVICE_H */ From f3865d77b0dcbb35e2c679d05f1aa2a376d09e92 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 9 Feb 2019 17:35:41 +0000 Subject: [PATCH 114/115] quieten a few debug messages --- audio_alsa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 9658832a..711a1767 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -1545,7 +1545,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { do_alsa_device_init_if_needed(); } int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000); - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0); // 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 @@ -1620,7 +1620,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) { } } } - debug_mutex_unlock(&alsa_mutex, 3); + debug_mutex_unlock(&alsa_mutex, 0); pthread_cleanup_pop(0); // release the mutex usleep(sleep_time_ms * 1000); // has a cancellation point in it } From 804e62d49545b0fb2820b1f25ad74045b5e5da68 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 9 Feb 2019 18:23:52 +0000 Subject: [PATCH 115/115] Hook up the active mode to the disable standby facility, enabling a 'while active' setting for it. Expose them to D-Bus. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index a92b3365..f8c72f71 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3d51], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.3d52], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h])