From 7285b1ee0364a9d3cda1824cc7f4799faa42b1b0 Mon Sep 17 00:00:00 2001
From: Pieter De Gendt
Date: Fri, 29 Nov 2019 14:57:35 +0100
Subject: [PATCH 001/186] Jack output add soxr resampling
---
audio_jack.c | 162 +++++++++++++++++++++++++++++++++++++++++++--------
common.h | 3 +
2 files changed, 140 insertions(+), 25 deletions(-)
diff --git a/audio_jack.c b/audio_jack.c
index 0aff5025..a8600de9 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -29,10 +29,20 @@
#include
#include
-// Two-channel, 16bit audio:
-static const int bytes_per_frame = 4;
+#ifdef CONFIG_SOXR
+#include
+#endif
+
+#define NPORTS 2
+
+typedef jack_default_audio_sample_t sample_t;
+
+#define jack_sample_size sizeof(sample_t)
+
+// Two-channel, 32bit audio:
+static const int bytes_per_frame = NPORTS * jack_sample_size;
// Four seconds buffer -- should be plenty
-#define buffer_size (44100 * 4 * bytes_per_frame)
+#define buffer_size (48000u * 4u * bytes_per_frame)
static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -62,7 +72,6 @@ audio_output audio_jack = {.name = "jack",
// This also affects deinterlacing.
// So make it exactly the number of incoming audio channels!
-#define NPORTS 2
static jack_port_t *port[NPORTS];
static const char *port_name[NPORTS] = {"out_L", "out_R"};
@@ -76,7 +85,36 @@ static int flush_please = 0;
static jack_latency_range_t latest_latency_range[NPORTS];
static int64_t time_of_latest_transfer;
-static inline jack_default_audio_sample_t sample_conv(short sample) {
+#ifdef CONFIG_SOXR
+typedef struct soxr_recipe {
+ int recipe;
+ const char *name;
+} soxr_recipe_t;
+
+static soxr_recipe_t soxr_quality_table[] = {
+ { SOXR_VHQ, "very high" },
+ { SOXR_HQ, "high" },
+ { SOXR_MQ, "medium" },
+ { SOXR_LQ, "low" },
+ { SOXR_QQ, "quick" },
+ { -1, NULL }
+};
+
+static int parse_soxr_recipe_name(const char *name) {
+ for (soxr_recipe_t *s = soxr_quality_table; s->name != NULL; ++s) {
+ if (!strcmp(s->name, name)) {
+ return s->recipe;
+ }
+ }
+ return -1;
+}
+
+static soxr_t soxr = NULL;
+static soxr_quality_spec_t quality_spec;
+static soxr_io_spec_t io_spec;
+#endif
+
+static inline sample_t sample_conv(short sample) {
// It sounds correct, but I don't understand it.
// Zero int needs to be zero float. Check.
// Plus 32767 int is 1.0. Check.
@@ -86,17 +124,17 @@ static inline jack_default_audio_sample_t sample_conv(short sample) {
return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX));
}
-static void deinterleave_and_convert(const char *interleaved_input_buffer,
- jack_default_audio_sample_t *jack_output_buffer[],
- jack_nframes_t offset, jack_nframes_t nframes) {
+static void deinterleave(const char *interleaved_input_buffer,
+ sample_t *jack_output_buffer[],
+ jack_nframes_t offset, jack_nframes_t nframes) {
jack_nframes_t f;
// We're dealing with 16bit audio here:
- short *ifp = (short *)interleaved_input_buffer;
+ sample_t *ifp = (sample_t *)interleaved_input_buffer;
// Zero-copy, we're working directly on the target and destination buffers,
// so deal with an offset for the second part of the input ringbuffer
for (f = offset; f < (nframes + offset); f++) {
for (int i = 0; i < NPORTS; i++) {
- jack_output_buffer[i][f] = sample_conv(*ifp++);
+ jack_output_buffer[i][f] = *ifp++;
}
}
}
@@ -107,7 +145,7 @@ static void deinterleave_and_convert(const char *interleaved_input_buffer,
// output, no file access, no mutexes...
// The JACK ringbuffer we use to get the data in here is explicitly lock-free.
static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) {
- jack_default_audio_sample_t *buffer[NPORTS];
+ sample_t *buffer[NPORTS];
// Expect an array of two elements because of possible ringbuffer wrap-around:
jack_ringbuffer_data_t v[2] = {0};
jack_nframes_t i, thisbuf;
@@ -115,7 +153,7 @@ static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) {
int frames_required = 0;
for (i = 0; i < NPORTS; i++) {
- buffer[i] = (jack_default_audio_sample_t *)jack_port_get_buffer(port[i], nframes);
+ buffer[i] = (sample_t *)jack_port_get_buffer(port[i], nframes);
}
if (flush_please) {
// We just move the read pointer ahead without doing anything with the data.
@@ -131,7 +169,7 @@ static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) {
} else {
frames_required = thisbuf;
}
- deinterleave_and_convert(v[i].buf, buffer, frames_written, frames_required);
+ deinterleave(v[i].buf, buffer, frames_written, frames_required);
frames_written += frames_required;
nframes -= frames_required;
}
@@ -183,6 +221,9 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
// Do the "general" audio options. Note, these options are in the "general" stanza!
parse_general_audio_options();
+#ifdef CONFIG_SOXR
+ config.jack_soxr_resample_recipe = -1; // don't resample by default
+#endif
// Now the options specific to the backend, from the "jack" stanza:
if (config.cfg != NULL) {
@@ -193,6 +234,12 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
if (config_lookup_string(config.cfg, "jack.autoconnect_pattern", &str)) {
config.jack_autoconnect_pattern = (char *)str;
}
+#ifdef CONFIG_SOXR
+ if (config_lookup_string(config.cfg, "jack.soxr_resample_recipe", &str)) {
+ debug(1, "SOXR quality %s", str);
+ config.jack_soxr_resample_recipe = parse_soxr_recipe_name(str);
+ }
+#endif
}
if (config.jack_client_name == NULL)
config.jack_client_name = strdup("shairport-sync");
@@ -213,6 +260,12 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
die("Could not start JACK server. JackStatus is %x", status);
}
sample_rate = jack_get_sample_rate(client);
+#ifdef CONFIG_SOXR
+ if (config.jack_soxr_resample_recipe >= SOXR_QQ) {
+ quality_spec = soxr_quality_spec(config.jack_soxr_resample_recipe, 0);
+ io_spec = soxr_io_spec(SOXR_INT16_I, SOXR_FLOAT32_I);
+ } else
+#endif
if (sample_rate != 44100) {
die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync."
" Must be 44100 Hz.",
@@ -287,14 +340,38 @@ void jack_deinit() {
warn("Error closing jack client");
pthread_mutex_unlock(&client_mutex);
jack_ringbuffer_free(jackbuf);
+#ifdef CONFIG_SOXR
+ if (soxr) {
+ soxr_delete(soxr);
+ soxr = NULL;
+ }
+#endif
}
-void jack_start(__attribute__((unused)) int i_sample_rate,
+void jack_start(int i_sample_rate,
__attribute__((unused)) int i_sample_format) {
// Nothing to do, JACK client has already been set up at jack_init().
// Also, we have no say over the sample rate or sample format of JACK,
- // We convert the 16bit samples to float, and die if the sample rate is != 44k1.
- // FIXME: later, resampling would be nice. Fold into soxr if possible.
+ // We convert the 16bit samples to float, and die if the sample rate is != 44k1 without soxr.
+#ifdef CONFIG_SOXR
+ if (config.jack_soxr_resample_recipe >= SOXR_QQ) {
+ // we might improve a bit with soxr_clear if the sample_rate doesn't change
+ if (soxr) {
+ soxr_delete(soxr);
+ }
+ soxr_error_t e = NULL;
+ soxr = soxr_create(i_sample_rate,
+ sample_rate,
+ NPORTS,
+ &e,
+ &io_spec,
+ &quality_spec,
+ NULL);
+ if (!soxr) {
+ die("Unable to create soxr resampler for JACK: %s", e);
+ }
+ }
+#endif
}
void jack_flush() {
@@ -319,7 +396,7 @@ int jack_delay(long *the_delay) {
debug(2, "audio_occupancy_now is %d.", audio_occupancy_now);
pthread_mutex_unlock(&buffer_mutex);
- int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 32;
+ int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) >> 32;
// debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check);
// jack_latency is set by the graph() callback, it's the average of the maximum
// latencies of all our output ports. Adjust this constant baseline delay according
@@ -330,18 +407,53 @@ int jack_delay(long *the_delay) {
}
int play(void *buf, int samples) {
- // debug(1,"jack_play of %d samples.",samples);
- // copy the samples into the queue
- size_t bytes_to_transfer, bytes_transferred;
- bytes_to_transfer = samples * bytes_per_frame;
+ jack_ringbuffer_data_t v[2] = {0};
+ size_t i, j, c;
+ jack_nframes_t thisbuf;
// It's ok to lock here since we're not in the realtime callback:
pthread_mutex_lock(&buffer_mutex);
- bytes_transferred = jack_ringbuffer_write(jackbuf, buf, bytes_to_transfer);
+ jack_ringbuffer_get_write_vector(jackbuf, v);
+ short *in = (short *)buf;
+ sample_t *out;
+ for (i = 0; i < 2; ++i) {
+ thisbuf = v[i].len / (jack_sample_size * NPORTS); // #samples per channel
+ out = (sample_t *)v[i].buf;
+#ifdef CONFIG_SOXR
+ if (soxr) {
+ size_t i_done, o_done;
+ soxr_error_t e;
+ while (samples > 0 && thisbuf > 0) {
+ e = soxr_process(soxr,
+ (soxr_in_t)in,
+ samples,
+ &i_done,
+ (soxr_out_t)out,
+ thisbuf,
+ &o_done);
+ if (e)
+ die("Error during soxr process: %s", e);
+
+ samples -= i_done;
+ thisbuf -= o_done;
+ jack_ringbuffer_write_advance(jackbuf, o_done * jack_sample_size * NPORTS);
+ }
+ } else {
+#endif
+ j = 0;
+ for (j = 0; j < thisbuf && samples > 0; ++j) {
+ for (c = 0; c < NPORTS; ++c)
+ out[j * NPORTS + c] = sample_conv(*in++);
+ --samples;
+ }
+ jack_ringbuffer_write_advance(jackbuf, j * jack_sample_size * NPORTS);
+#ifdef CONFIG_SOXR
+ }
+#endif
+ }
time_of_latest_transfer = get_absolute_time_in_fp();
pthread_mutex_unlock(&buffer_mutex);
- if (bytes_transferred < bytes_to_transfer) {
- warn("JACK ringbuffer overrun. Only wrote %d of %d bytes.", bytes_transferred,
- bytes_to_transfer);
+ if (samples) {
+ warn("JACK ringbuffer overrun. Dropped %d samples.", samples);
}
return 0;
}
diff --git a/common.h b/common.h
index 34cd10ba..45fc9683 100644
--- a/common.h
+++ b/common.h
@@ -270,6 +270,9 @@ typedef struct {
#ifdef CONFIG_JACK
char *jack_client_name;
char *jack_autoconnect_pattern;
+#ifdef CONFIG_SOXR
+ int jack_soxr_resample_recipe;
+#endif
#endif
} shairport_cfg;
From 311909a2dd0665a5058937f3d9d87f691f97ae60 Mon Sep 17 00:00:00 2001
From: Pieter De Gendt
Date: Tue, 3 Dec 2019 12:14:52 +0100
Subject: [PATCH 002/186] Fix input buffer while resampling
---
audio_jack.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/audio_jack.c b/audio_jack.c
index a8600de9..499687a3 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -433,6 +433,7 @@ int play(void *buf, int samples) {
if (e)
die("Error during soxr process: %s", e);
+ in += i_done * NPORTS; // advance our input buffer
samples -= i_done;
thisbuf -= o_done;
jack_ringbuffer_write_advance(jackbuf, o_done * jack_sample_size * NPORTS);
From ed9f8d174c450ee477023f9def2539a4f1f13a86 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 18 Dec 2019 10:40:41 +0000
Subject: [PATCH 003/186] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index b0aea260..48bd955c 100644
--- a/README.md
+++ b/README.md
@@ -597,11 +597,11 @@ This will be followed by the statistics themselves at regular intervals, for exa
1.99, -22.7, 22.7, 54162, 0, 0, 0, 0, 8567, 216, 225, 44100.00, 44103.12, 44099.87, 24.57, 92, -27.45
```
-"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 0.7 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.
+"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 0.15 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. In this case, the drift is negligible, but it can routinely be up to 150 ppm, especially with older machines.
-"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.
+"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 are being made. Third party programs tend to have much larger levels of corrections.
"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.2 seconds = 8,820 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.
From fe8198a23b1083272dbf3dbf3e06b41e7b3ed123 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 20 Dec 2019 17:17:21 +0000
Subject: [PATCH 004/186] Add SetAirplayVolume to the D-Bus RemoteControl
interface. Add SetVolume to the MPRIS interface. Hook up the Volume property
in the MPRIS interface. Modify RemoteCommand in the D-Bus interface to return
the HTTP status and response. Change the type of airplay_volume from int to
double in the metadata hub. Add a few sample commands in the D-Bus document.
---
dacp.c | 2 ++
dacp.h | 2 ++
dbus-service.c | 40 +++++++++++++++++++++++++++++++---
documents/sample dbus commands | 15 +++++++++++++
metadata_hub.h | 2 +-
mpris-service.c | 36 ++++++++++++++++++++++++++++--
org.gnome.ShairportSync.xml | 5 +++++
org.mpris.MediaPlayer2.xml | 3 +++
8 files changed, 99 insertions(+), 6 deletions(-)
diff --git a/dacp.c b/dacp.c
index 79447afb..7797f794 100644
--- a/dacp.c
+++ b/dacp.c
@@ -592,6 +592,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
// debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
result = dacp_send_command(command, &response, &le);
// debug(1,"Response to \"%s\" is %d.",command,result);
+ // remember: unless the revision_number you pass in is 1,
+ // response will be 200 only if there's something new to report.
if (result == 200) {
// if (0) {
char *sp = response;
diff --git a/dacp.h b/dacp.h
index ce1e1278..ea44aacb 100644
--- a/dacp.h
+++ b/dacp.h
@@ -32,6 +32,8 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP
// longer associated with it.
void dacp_monitor_port_update_callback(
char *dacp_id, uint16_t port); // a callback to say the port is no longer in use
+
+int dacp_send_command(const char *command, char **body, ssize_t *bodysize);
int send_simple_dacp_command(const char *command);
int dacp_set_include_speaker_volume(int64_t machine_number, int32_t vo);
diff --git a/dbus-service.c b/dbus-service.c
index 0c85a981..3ebd88f7 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -337,6 +337,19 @@ static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
return TRUE;
}
+static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleton,
+ GDBusMethodInvocation *invocation, const gdouble volume,
+ __attribute__((unused)) gpointer user_data) {
+ debug(2, "Set airplay volume to %.6f.", volume);
+ char command[256] = "";
+ snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume);
+ send_simple_dacp_command(command);
+ shairport_sync_remote_control_complete_set_airplay_volume(skeleton, invocation);
+ return TRUE;
+}
+
+
+
gboolean notify_elapsed_time_callback(ShairportSyncDiagnostics *skeleton,
__attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_elapsed_time_callback\" called.");
@@ -714,12 +727,30 @@ static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *i
static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
const gchar *command,
__attribute__((unused)) gpointer user_data) {
- debug(1, "RemoteCommand with command \"%s\".", command);
- send_simple_dacp_command((const char *)command);
- shairport_sync_complete_remote_command(skeleton, invocation);
+ debug(1, "RemoteCommand with command \"%s\".", command);
+ int reply = 0;
+ char *server_reply = NULL;
+ ssize_t reply_size = 0;
+ reply = dacp_send_command((const char *)command, &server_reply, &reply_size);
+ char *server_reply_hex = alloca(reply_size * 2 + 1);
+ if (server_reply_hex) {
+ char *p = server_reply_hex;
+ if (server_reply) {
+ char *q = server_reply;
+ int i;
+ for (i = 0; i < reply_size; i++) {
+ snprintf(p, 3, "%02X", *q);
+ p += 2;
+ q++;
+ }
+ }
+ *p = '\0';
+ }
+ shairport_sync_complete_remote_command(skeleton, invocation, reply, server_reply_hex);
return TRUE;
}
+
static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name,
__attribute__((unused)) gpointer user_data) {
@@ -814,6 +845,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
G_CALLBACK(on_handle_volume_up), NULL);
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-volume-down",
G_CALLBACK(on_handle_volume_down), NULL);
+ g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-set-airplay-volume",
+ G_CALLBACK(on_handle_set_airplay_volume), NULL);
+
g_signal_connect(shairportSyncAdvancedRemoteControlSkeleton, "handle-set-volume",
G_CALLBACK(on_handle_set_volume), NULL);
diff --git a/documents/sample dbus commands b/documents/sample dbus commands
index 5b173d4a..96219c19 100644
--- a/documents/sample dbus commands
+++ b/documents/sample dbus commands
@@ -1,3 +1,6 @@
+# For the "Native" Shairport Sync D-Bus Interface support, Shairport Sync must be built with the D-Bus interface support. Add the '--with-dbus-interface' flag at the ./configure stage.
+# There is a simple test client that you can have built -- add the '--with-dbus-test-client' flag at the ./configure stage. You'll get an executable called shairport-sync-dbus-test-client
+
# Get Log Verbosity
dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:Verbosity
# Return Log Verbosity
@@ -59,6 +62,11 @@ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/Shair
# Set Convolution Impulse Response File:
dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:ConvolutionImpulseResponseFile variant:string:"/etc/shairport-sync/boom.wav"
+# Set Airplay Volume using Remote Control. Airplay Volume is between -30.0 and 0.0 and maps linearly onto the slider, with -30.0 being lowest and 0.0 being highest.
+dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.RemoteControl.SetAirplayVolume double:-10.0
+
+
+# AdvancedRemoteControl interface.
# Some commands and properties are accessible only through the AdvancedRemoteControl interface.
# However, only iTunes or the macOS Music app from 10.15.2 onwards
# provide the functionality needed for the AdvancedRemoteControl interface to work.
@@ -70,3 +78,10 @@ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/Shair
# Set Volume using Advanced Remote Control -- only works if the org.gnome.ShairportSync.AdvancedRemoteControl is available.
dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50
+
+# MPRIS interface commands.
+# For MPRIS support, Shairport Sync must be built with the MPRIS interface support. Add the '--with-mpris-interface' flag at the ./configure stage.
+# There is a simple test client that you can have built -- add the '--with-mpris-test-client' flag at the ./configure stage. You'll get an executable called shairport-sync-mpris-test-client.
+# This is mostly compatible with the MPRIS standard, except that Volume is read-only, with a separate SetVolume method.
+# Set Volume, which must be between 0.0 and 1.0 and maps linearly onto the slider.
+dbus-send --system --print-reply --type=method_call --dest=org.mpris.MediaPlayer2.ShairportSync '/org/mpris/MediaPlayer2' org.mpris.MediaPlayer2.Player.SetVolume double:0.3
diff --git a/metadata_hub.h b/metadata_hub.h
index 5b565bba..ed7d947d 100644
--- a/metadata_hub.h
+++ b/metadata_hub.h
@@ -129,7 +129,7 @@ typedef struct metadata_bundle {
int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the
// speaker volume control
- int airplay_volume;
+ double airplay_volume;
metadata_watcher watchers[number_of_watchers]; // functions to call if the metadata is changed.
void *watchers_data[number_of_watchers]; // their individual data
diff --git a/mpris-service.c b/mpris-service.c
index 37e7bf8a..707c063c 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -14,10 +14,28 @@
#include "metadata_hub.h"
#include "mpris-service.h"
+double airplay_volume_to_mpris_volume(double sp) {
+ if (sp < -30.0)
+ sp = -30.0;
+ if (sp > 0.0)
+ sp = 0.0;
+ sp = (sp/30.0)+1;
+ return sp;
+}
+
+double mpris_volume_to_airplay_volume(double sp) {
+ sp = (sp-1.0)*30.0;
+ if (sp < -30.0)
+ sp = -30.0;
+ if (sp > 0.0)
+ sp = 0.0;
+ return sp;
+}
+
void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
// debug(1, "MPRIS metadata watcher called");
char response[100];
-
+ media_player2_player_set_volume(mprisPlayerPlayerSkeleton, airplay_volume_to_mpris_volume(argc->airplay_volume));
switch (argc->repeat_status) {
case RS_NOT_AVAILABLE:
strcpy(response, "Not Available");
@@ -229,6 +247,18 @@ static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocati
return TRUE;
}
+static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton,
+ GDBusMethodInvocation *invocation, const gdouble volume,
+ __attribute__((unused)) gpointer user_data) {
+ double ap_volume = mpris_volume_to_airplay_volume(volume);
+ debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume);
+ char command[256] = "";
+ snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", ap_volume);
+ send_simple_dacp_command(command);
+ media_player2_player_complete_play(skeleton, invocation);
+ return TRUE;
+}
+
static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *name,
__attribute__((unused)) gpointer user_data) {
@@ -254,7 +284,6 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Stopped");
media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, "None");
- media_player2_player_set_volume(mprisPlayerPlayerSkeleton, 0.5);
media_player2_player_set_minimum_rate(mprisPlayerPlayerSkeleton, 1.0);
media_player2_player_set_maximum_rate(mprisPlayerPlayerSkeleton, 1.0);
media_player2_player_set_can_go_next(mprisPlayerPlayerSkeleton, TRUE);
@@ -274,6 +303,9 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL);
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous),
NULL);
+ g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume),
+ NULL);
+
add_metadata_watcher(mpris_metadata_watcher, NULL);
diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml
index 15317faa..91c31671 100644
--- a/org.gnome.ShairportSync.xml
+++ b/org.gnome.ShairportSync.xml
@@ -13,6 +13,8 @@
+
+
@@ -45,6 +47,9 @@
+
+
+
diff --git a/org.mpris.MediaPlayer2.xml b/org.mpris.MediaPlayer2.xml
index ae34e1e5..64ee2cca 100755
--- a/org.mpris.MediaPlayer2.xml
+++ b/org.mpris.MediaPlayer2.xml
@@ -41,6 +41,9 @@
+
+
+
From 32646f22e2b40ae1b212fc1fe00486d0dc90d982 Mon Sep 17 00:00:00 2001
From: Pieter De Gendt
Date: Mon, 23 Dec 2019 09:10:08 +0100
Subject: [PATCH 005/186] jack: Rename soxr "recipe" to "quality" and
configurable buffer size
---
audio_jack.c | 39 +++++++++++++++++++++----------------
common.h | 2 +-
scripts/shairport-sync.conf | 2 ++
3 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/audio_jack.c b/audio_jack.c
index 499687a3..db6e1899 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -41,8 +41,6 @@ typedef jack_default_audio_sample_t sample_t;
// Two-channel, 32bit audio:
static const int bytes_per_frame = NPORTS * jack_sample_size;
-// Four seconds buffer -- should be plenty
-#define buffer_size (48000u * 4u * bytes_per_frame)
static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -86,12 +84,12 @@ static jack_latency_range_t latest_latency_range[NPORTS];
static int64_t time_of_latest_transfer;
#ifdef CONFIG_SOXR
-typedef struct soxr_recipe {
- int recipe;
+typedef struct soxr_quality {
+ int quality;
const char *name;
-} soxr_recipe_t;
+} soxr_quality_t;
-static soxr_recipe_t soxr_quality_table[] = {
+static soxr_quality_t soxr_quality_table[] = {
{ SOXR_VHQ, "very high" },
{ SOXR_HQ, "high" },
{ SOXR_MQ, "medium" },
@@ -100,10 +98,10 @@ static soxr_recipe_t soxr_quality_table[] = {
{ -1, NULL }
};
-static int parse_soxr_recipe_name(const char *name) {
- for (soxr_recipe_t *s = soxr_quality_table; s->name != NULL; ++s) {
+static int parse_soxr_quality_name(const char *name) {
+ for (soxr_quality_t *s = soxr_quality_table; s->name != NULL; ++s) {
if (!strcmp(s->name, name)) {
- return s->recipe;
+ return s->quality;
}
}
return -1;
@@ -213,6 +211,7 @@ static void info(const char *desc) { inform("JACK information: \"%s\"", desc); }
int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
int i;
+ int bufsz = -1;
config.audio_backend_latency_offset = 0;
config.audio_backend_buffer_desired_length = 0.500;
// Below this, soxr interpolation will not occur -- it'll be basic interpolation
@@ -222,7 +221,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
// Do the "general" audio options. Note, these options are in the "general" stanza!
parse_general_audio_options();
#ifdef CONFIG_SOXR
- config.jack_soxr_resample_recipe = -1; // don't resample by default
+ config.jack_soxr_resample_quality = -1; // don't resample by default
#endif
// Now the options specific to the backend, from the "jack" stanza:
@@ -235,18 +234,24 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
config.jack_autoconnect_pattern = (char *)str;
}
#ifdef CONFIG_SOXR
- if (config_lookup_string(config.cfg, "jack.soxr_resample_recipe", &str)) {
+ if (config_lookup_string(config.cfg, "jack.soxr_resample_quality", &str)) {
debug(1, "SOXR quality %s", str);
- config.jack_soxr_resample_recipe = parse_soxr_recipe_name(str);
+ config.jack_soxr_resample_quality = parse_soxr_quality_name(str);
}
#endif
+ if (config_lookup_int(config.cfg, "jack.bufsz", &bufsz) && bufsz <= 0)
+ die("jack: bufsz must be > 0");
}
if (config.jack_client_name == NULL)
config.jack_client_name = strdup("shairport-sync");
- jackbuf = jack_ringbuffer_create(buffer_size);
+ // by default a buffer that can hold up to 4 seconds of 48kHz samples
+ if (bufsz <= 0)
+ bufsz = 48000 * 4 * bytes_per_frame;
+
+ jackbuf = jack_ringbuffer_create((size_t)bufsz);
if (jackbuf == NULL)
- die("Can't allocate %d bytes for the JACK ringbuffer.", buffer_size);
+ die("Can't allocate %d bytes for the JACK ringbuffer.", bufsz);
// Lock the ringbuffer into memory so that it never gets paged out, which would
// break realtime constraints.
jack_ringbuffer_mlock(jackbuf);
@@ -261,8 +266,8 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
}
sample_rate = jack_get_sample_rate(client);
#ifdef CONFIG_SOXR
- if (config.jack_soxr_resample_recipe >= SOXR_QQ) {
- quality_spec = soxr_quality_spec(config.jack_soxr_resample_recipe, 0);
+ if (config.jack_soxr_resample_quality >= SOXR_QQ) {
+ quality_spec = soxr_quality_spec(config.jack_soxr_resample_quality, 0);
io_spec = soxr_io_spec(SOXR_INT16_I, SOXR_FLOAT32_I);
} else
#endif
@@ -354,7 +359,7 @@ void jack_start(int i_sample_rate,
// Also, we have no say over the sample rate or sample format of JACK,
// We convert the 16bit samples to float, and die if the sample rate is != 44k1 without soxr.
#ifdef CONFIG_SOXR
- if (config.jack_soxr_resample_recipe >= SOXR_QQ) {
+ if (config.jack_soxr_resample_quality >= SOXR_QQ) {
// we might improve a bit with soxr_clear if the sample_rate doesn't change
if (soxr) {
soxr_delete(soxr);
diff --git a/common.h b/common.h
index 45fc9683..37217ab8 100644
--- a/common.h
+++ b/common.h
@@ -271,7 +271,7 @@ typedef struct {
char *jack_client_name;
char *jack_autoconnect_pattern;
#ifdef CONFIG_SOXR
- int jack_soxr_resample_recipe;
+ int jack_soxr_resample_quality;
#endif
#endif
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index bec0d4fc..9c875156 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -134,6 +134,8 @@ jack =
// "jack_mixer:in_2[78]"
// Beware: if you make a syntax error, libjack might crash. In that case, fix it and start over.
// For a good overview, look here: https://www.ibm.com/support/knowledgecenter/SS8NLW_11.0.1/com.ibm.swg.im.infosphere.dataexpl.engine.doc/c_posix-regex-examples.html
+// soxr_resample_quality = "none"; // Enable resampling by setting this to "very high", "high", "medium", "low" or "quick"
+// bufsz = ; // advanced optional setting to set the buffer size to this value
};
// Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done.
From 362ae3f7d8660a6858ceda4e02543276f8f86d67 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 23 Dec 2019 12:03:21 +0000
Subject: [PATCH 006/186] Update configure.ac
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index eca3d41b..cc855318 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.3.6d1], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d2], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 26f08b4188ba5490554f92e4efc2927ebed6fa27 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 23 Dec 2019 12:09:05 +0000
Subject: [PATCH 007/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 775d9c69..ebdba4fe 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,12 @@
+Version 3.3.6d2
+====
+**New Features**
+* Resampling has been added for the [Jack Audio](http://jackaudio.org) backend. This can be used to up-sample to 192kHz. Thanks to [Pieter De Gendt](https://github.com/pdgendt) for this addition.
+
+Version 3.3.6d1
+====
+This is release version 3.3.5.
+
Version 3.3.5d1
====
**Pesky Changes You May Not Be Able To Ignore**
From 272bd2db487065575c1684abfbb707e3913ebc66 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 23 Dec 2019 12:12:43 +0000
Subject: [PATCH 008/186] Add the ability to set the volume directly to the
D-Bus RemoteControl interface and to the MPRIS interface. Both use the
recently-discovered ability to set the device_volume.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index cc855318..7b5d83da 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.3.6d2], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d3], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 772ba1178c15ca4b4ff41ebcf637441af9f4a0aa Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 23 Dec 2019 12:18:48 +0000
Subject: [PATCH 009/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index ebdba4fe..4dfe20cf 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,16 @@
+Versioon 3.3.6d3
+====
+**New Features**
+* Add `SetAirplayVolume` to the native D-Bus `RemoteControl` interface.
+* Add `SetVolume` to the `MPRIS` interface.
+
+**Bug Fixes**
+* Hook up the `Volume` property in the `MPRIS` interface.
+
+**Enhancements**
+* Modify `RemoteCommand` in the D-Bus interface to return the HTTP status and response in hex.
+* Add a few sample commands, including `MPRIS` commands, to the [Sample D-Bus Commands](https://github.com/mikebrady/shairport-sync/blob/development/documents/sample%20dbus%20commands) document.
+
Version 3.3.6d2
====
**New Features**
From 7cd075dd613558bcdaef1d27a3a8cf8c2cc3662d Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 23 Dec 2019 12:19:01 +0000
Subject: [PATCH 010/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 4dfe20cf..bf94f4fd 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,4 +1,4 @@
-Versioon 3.3.6d3
+Version 3.3.6d3
====
**New Features**
* Add `SetAirplayVolume` to the native D-Bus `RemoteControl` interface.
From 1f75fe4e7aa0b618f98ad22548217c36b65c7b28 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 27 Dec 2019 14:37:01 +0000
Subject: [PATCH 011/186] Add or update some of the copyright notices
---
alac.h | 30 ++++++++++++++++++++++++++++++
apple_alac.cpp | 26 ++++++++++++++++++++++++++
apple_alac.h | 25 +++++++++++++++++++++++++
audio.c | 2 +-
dbus-service.c | 26 ++++++++++++++++++++++++++
metadata_hub.c | 2 +-
mpris-service.c | 25 +++++++++++++++++++++++++
shairport-sync-dbus-test-client.c | 25 +++++++++++++++++++++++++
shairport-sync-mpris-test-client.c | 26 ++++++++++++++++++++++++++
9 files changed, 185 insertions(+), 2 deletions(-)
diff --git a/alac.h b/alac.h
index 33a35928..7cdd507d 100644
--- a/alac.h
+++ b/alac.h
@@ -1,3 +1,33 @@
+/*
+ * ALAC (Apple Lossless Audio Codec) decoder
+ * Copyright (c) 2005 David Hammerton
+ * All rights reserved.
+ *
+ * http://crazney.net/programs/itunes/alac.html
+ *
+ * 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.
+ *
+ */
+
+
#ifndef __ALAC__DECOMP_H
#define __ALAC__DECOMP_H
diff --git a/apple_alac.cpp b/apple_alac.cpp
index 6d543b2e..485c5bfe 100644
--- a/apple_alac.cpp
+++ b/apple_alac.cpp
@@ -1,3 +1,29 @@
+/*
+ * 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
// these are headers for the ALAC decoder, utilities and endian utilities
diff --git a/apple_alac.h b/apple_alac.h
index eb8bcf6c..be11e86e 100644
--- a/apple_alac.h
+++ b/apple_alac.h
@@ -1,3 +1,28 @@
+/*
+ * 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.
+ */
#ifndef __APPLE_ALAC_H
#define __APPLE_ALAC_H
diff --git a/audio.c b/audio.c
index c71ac939..8b2762f4 100644
--- a/audio.c
+++ b/audio.c
@@ -1,7 +1,7 @@
/*
* Audio driver handler. This file is part of Shairport.
* Copyright (c) James Laird 2013
- * Modifications (c) Mike Brady 2014 -- 2018
+ * Modifications (c) Mike Brady 2014 -- 2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
diff --git a/dbus-service.c b/dbus-service.c
index 3ebd88f7..d9d6e95d 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -1,3 +1,29 @@
+/*
+ * This file is part of Shairport Sync.
+ * Copyright (c) Mike Brady 2018 -- 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
diff --git a/metadata_hub.c b/metadata_hub.c
index 0b5aa412..fb459b6b 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -5,7 +5,7 @@
* then you need a metadata hub,
* where everything is stored
* This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2017--2018
+ * Copyright (c) Mike Brady 2017--2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
diff --git a/mpris-service.c b/mpris-service.c
index 707c063c..387b3a23 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -1,3 +1,28 @@
+/*
+ * This file is part of Shairport Sync.
+ * Copyright (c) Mike Brady 2018 -- 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
diff --git a/shairport-sync-dbus-test-client.c b/shairport-sync-dbus-test-client.c
index bf968c45..6b8f122c 100644
--- a/shairport-sync-dbus-test-client.c
+++ b/shairport-sync-dbus-test-client.c
@@ -1,3 +1,28 @@
+/*
+ * 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 "dbus-interface.h"
#include
#include
diff --git a/shairport-sync-mpris-test-client.c b/shairport-sync-mpris-test-client.c
index c15c71bc..7af29aea 100644
--- a/shairport-sync-mpris-test-client.c
+++ b/shairport-sync-mpris-test-client.c
@@ -1,3 +1,29 @@
+/*
+ * 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 "mpris-interface.h"
#include
#include
From 85b17cdaef3f5878c7afd793aa8be9001d6b1730 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 08:31:13 +0000
Subject: [PATCH 012/186] Remove timeout on reception of DACP responses to
allow for long poll behaviour of some players, e.g. Forked Daapd. Add some
examples to the dbus client.
---
dacp.c | 608 +++++++++++++++---------------
shairport-sync-dbus-test-client.c | 39 +-
2 files changed, 341 insertions(+), 306 deletions(-)
diff --git a/dacp.c b/dacp.c
index 7797f794..a47d1608 100644
--- a/dacp.c
+++ b/dacp.c
@@ -150,12 +150,12 @@ void http_cleanup(void *arg) {
int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
int result;
- // debug(1,"dacp_send_command: command is: \"%s\".",command);
-
- if (dacp_server.port == 0) {
- debug(1,"No DACP port specified yet");
- result = 490; // no port specified
- } else {
+ // debug(1,"dacp_send_command: command is: \"%s\".",command);
+
+ if (dacp_server.port == 0) {
+ debug(1, "No DACP port specified yet");
+ result = 490; // no port specified
+ } else {
// will malloc space for the body or set it to NULL -- the caller should free it.
@@ -213,20 +213,27 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1) {
- // debug(1, "DACP socket could not be created -- error %d: \"%s\".",errno,strerror(errno));
+ // debug(1, "DACP socket could not be created -- error %d:
+ // \"%s\".",errno,strerror(errno));
response.code = 497; // Can't establish a socket to the DACP server
} else {
pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
// debug(2, "dacp_send_command: open socket %d.",sockfd);
+
+ // This is for limiting the time to be spent waiting for a response.
+
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500000;
- if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
+ // Don't place a timeout on the receive side of these calls, as some clients may provide
+ // a "long poll" mechanism whereby a reply will not be sent until a change has occurred
+ // 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)
debug(1, "dacp_send_command: error %d setting send timeout.", errno);
+
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
@@ -240,7 +247,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
snprintf(message, sizeof(message),
"GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
- command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id);
+ command, dacp_server.ip_string, dacp_server.port,
+ dacp_server.active_remote_id);
// Send command
debug(3, "dacp_send_command: \"%s\".", command);
@@ -276,8 +284,12 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
memset(buffer, 0, sizeof(buffer));
while (needmore && !looperror) {
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);
+
+ // Don't place a timeout on the receive, as some clients may provide
+ // a "long poll" mechanism whereby a reply will not be sent until a change has occurred
+
+ // if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
+ // debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0);
// debug(3, "Received %d bytes: \"%s\".", ndata, buffer);
if (ndata <= 0) {
@@ -289,10 +301,12 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
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);
+ 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.");
+ debug(
+ 1,
+ "Could not set the dacp socket to abort due to a read error on closing.");
}
free(response.body);
@@ -487,7 +501,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
metadata_store.dacp_server_active = 0;
metadata_store.advanced_dacp_server_active = 0;
debug(2,
- "setting metadata_store.dacp_server_active and metadata_store.advanced_dacp_server_active to 0 with an update "
+ "setting metadata_store.dacp_server_active and "
+ "metadata_store.advanced_dacp_server_active to 0 with an update "
"flag value of %d",
ch);
metadata_hub_modify_epilog(ch);
@@ -499,9 +514,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
// so dacp_server.scan_enable will be true at this point
bad_result_count = 0;
idle_scan_count = 0;
-
}
-
+
result = dacp_get_volume(&the_volume); // just want the http code
pthread_cleanup_pop(1);
@@ -514,25 +528,27 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
bad_result_count++;
}
-
+
// here, do the debouncing calculations to see
// if the dacp server and the
- // advanced dacp server are available
-
+ // advanced dacp server are available
+
// -1 means we don't know because some bad statuses have been reported
// 0 means definitely no
// +1 means definitely yes
-
+
int dacp_server_status_now = -1;
int advanced_dacp_server_status_now = -1;
-
- if (bad_result_count == 0) {
+
+ if (bad_result_count == 0) {
dacp_server_status_now = 1;
if (result == 200)
advanced_dacp_server_status_now = 1;
else if (result == 400)
advanced_dacp_server_status_now = 0;
- } else if (bad_result_count == config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs, then it's gone
+ } else if (bad_result_count ==
+ config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
+ // then it's gone
dacp_server_status_now = 0;
advanced_dacp_server_status_now = 0;
}
@@ -545,14 +561,14 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
idle_scan_count);
-/* not used
-// decide if idle for too long
- if (idle_scan_count == config.scan_max_inactive_count) {
- debug(2, "DACP server status scanning stopped.");
- dacp_server.scan_enable = 0;
- }
-*/
-
+ /* not used
+ // decide if idle for too long
+ if (idle_scan_count == config.scan_max_inactive_count) {
+ debug(2, "DACP server status scanning stopped.");
+ dacp_server.scan_enable = 0;
+ }
+ */
+
int update_needed = 0;
metadata_hub_modify_prolog();
if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
@@ -562,7 +578,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
update_needed = 1;
}
}
- if (advanced_dacp_server_status_now != -1) { // if advanced_dacp_server_status_now is actually known...
+ if (advanced_dacp_server_status_now !=
+ -1) { // if advanced_dacp_server_status_now is actually known...
if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
@@ -571,286 +588,289 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
}
metadata_hub_modify_epilog(update_needed);
-
+
// pthread_mutex_unlock(&dacp_server_information_lock);
// debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
// dacp_server.ip_string, dacp_server.port, scan_index);
+ if (result == 200) {
+ metadata_hub_modify_prolog();
+ int diff = metadata_store.speaker_volume != the_volume;
+ if (diff)
+ metadata_store.speaker_volume = the_volume;
+ metadata_hub_modify_epilog(diff);
+
+ ssize_t le;
+ char *response = NULL;
+ int32_t item_size;
+ char command[1024] = "";
+ snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
+ revision_number);
+ // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
+ result = dacp_send_command(command, &response, &le);
+ // debug(1,"Response to \"%s\" is %d.",command,result);
+ // remember: unless the revision_number you pass in is 1,
+ // response will be 200 only if there's something new to report.
if (result == 200) {
- metadata_hub_modify_prolog();
- int diff = metadata_store.speaker_volume != the_volume;
- if (diff)
- metadata_store.speaker_volume = the_volume;
- metadata_hub_modify_epilog(diff);
+ // if (0) {
+ char *sp = response;
+ if (le >= 8) {
+ // here start looking for the contents of the status update
+ if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
+ // here, we know that we are receiving playerstatusupdates, so set a flag
+ metadata_hub_modify_prolog();
+ // debug(1, "playstatusupdate release track metadata");
+ // metadata_hub_reset_track_metadata();
+ // metadata_store.playerstatusupdates_are_received = 1;
+ sp -= item_size; // drop down into the array -- don't skip over it
+ le -= 8;
+ // char typestring[5];
+ // we need to acquire the metadata data structure and possibly update it
+ while (le >= 8) {
+ uint32_t type = dacp_tlv_crawl(&sp, &item_size);
+ le -= item_size + 8;
+ char *t;
+ // char u;
+ // char *st;
+ int32_t r;
+ uint32_t ui;
+ // uint64_t v;
+ // int i;
- ssize_t le;
- char *response = NULL;
- int32_t item_size;
- char command[1024] = "";
- snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
- revision_number);
- // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
- result = dacp_send_command(command, &response, &le);
- // debug(1,"Response to \"%s\" is %d.",command,result);
- // remember: unless the revision_number you pass in is 1,
- // response will be 200 only if there's something new to report.
- if (result == 200) {
- // if (0) {
- char *sp = response;
- if (le >= 8) {
- // here start looking for the contents of the status update
- if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
- // here, we know that we are receiving playerstatusupdates, so set a flag
- metadata_hub_modify_prolog();
- // debug(1, "playstatusupdate release track metadata");
- // metadata_hub_reset_track_metadata();
- // metadata_store.playerstatusupdates_are_received = 1;
- sp -= item_size; // drop down into the array -- don't skip over it
- le -= 8;
- // char typestring[5];
- // we need to acquire the metadata data structure and possibly update it
- while (le >= 8) {
- uint32_t type = dacp_tlv_crawl(&sp, &item_size);
- le -= item_size + 8;
- char *t;
- // char u;
- // char *st;
- int32_t r;
- uint32_t ui;
- // uint64_t v;
- // int i;
-
- switch (type) {
- case 'cmsr': // revision number
- t = sp - item_size;
- revision_number = ntohl(*(uint32_t *)(t));
- // debug(1," Serial Number: %d", revision_number);
- break;
- case 'caps': // play status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 2:
- if (metadata_store.play_status != PS_STOPPED) {
- metadata_store.play_status = PS_STOPPED;
- debug(2, "Play status is \"stopped\".");
- }
- break;
- case 3:
- if (metadata_store.play_status != PS_PAUSED) {
- metadata_store.play_status = PS_PAUSED;
- debug(2, "Play status is \"paused\".");
- }
- break;
- case 4:
- if (metadata_store.play_status != PS_PLAYING) {
- metadata_store.play_status = PS_PLAYING;
- debug(2, "Play status changed to \"playing\".");
- }
- break;
- default:
- debug(1, "Unrecognised play status %d received.", r);
- break;
+ switch (type) {
+ case 'cmsr': // revision number
+ t = sp - item_size;
+ revision_number = ntohl(*(uint32_t *)(t));
+ // debug(1," Serial Number: %d", revision_number);
+ break;
+ case 'caps': // play status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 2:
+ if (metadata_store.play_status != PS_STOPPED) {
+ metadata_store.play_status = PS_STOPPED;
+ debug(2, "Play status is \"stopped\".");
}
break;
- case 'cash': // shuffle status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 0:
- if (metadata_store.shuffle_status != SS_OFF) {
- metadata_store.shuffle_status = SS_OFF;
- debug(2, "Shuffle status is \"off\".");
- }
- break;
- case 1:
- if (metadata_store.shuffle_status != SS_ON) {
- metadata_store.shuffle_status = SS_ON;
- debug(2, "Shuffle status is \"on\".");
- }
- break;
- default:
- debug(1, "Unrecognised shuffle status %d received.", r);
- break;
+ case 3:
+ if (metadata_store.play_status != PS_PAUSED) {
+ metadata_store.play_status = PS_PAUSED;
+ debug(2, "Play status is \"paused\".");
}
break;
- case 'carp': // repeat status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 0:
- if (metadata_store.repeat_status != RS_OFF) {
- metadata_store.repeat_status = RS_OFF;
- debug(2, "Repeat status is \"none\".");
- }
- break;
- case 1:
- if (metadata_store.repeat_status != RS_ONE) {
- metadata_store.repeat_status = RS_ONE;
- debug(2, "Repeat status is \"one\".");
- }
- break;
- case 2:
- if (metadata_store.repeat_status != RS_ALL) {
- metadata_store.repeat_status = RS_ALL;
- debug(2, "Repeat status is \"all\".");
- }
- break;
- default:
- debug(1, "Unrecognised repeat status %d received.", r);
- break;
+ case 4:
+ if (metadata_store.play_status != PS_PLAYING) {
+ metadata_store.play_status = PS_PLAYING;
+ debug(2, "Play status changed to \"playing\".");
}
break;
- case 'cann': // track name
- debug(2, "DACP Track Name seen");
- if (string_update_with_size(&metadata_store.track_name, &metadata_store.track_name_changed,
- sp - item_size, item_size)) {
- debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
- }
- break;
- case 'cana': // artist name
- debug(2, "DACP Artist Name seen");
- if (string_update_with_size(&metadata_store.artist_name,
- &metadata_store.artist_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
- }
- break;
- case 'canl': // album name
- debug(2, "DACP Album Name seen");
- if (string_update_with_size(&metadata_store.album_name, &metadata_store.album_name_changed,
- sp - item_size, item_size)) {
- debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
- }
- break;
- case 'cang': // genre
- debug(2, "DACP Genre seen");
- if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
- sp - item_size, item_size)) {
- debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
- }
- break;
- case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
- // see reference above)
- debug(2, "DACP Composite ID seen");
- if (memcmp(metadata_store.item_composite_id, sp - item_size,
- sizeof(metadata_store.item_composite_id)) != 0) {
- memcpy(metadata_store.item_composite_id, sp - item_size,
- sizeof(metadata_store.item_composite_id));
- char st[33];
- char *pt = st;
- int it;
- for (it = 0; it < 16; it++) {
- snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
- pt += 2;
- }
- *pt = 0;
- debug(2, "Item composite ID changed to 0x%s.", st);
- metadata_store.item_composite_id_changed = 1;
- }
- break;
- case 'astm':
- t = sp - item_size;
- ui = ntohl(*(uint32_t *)(t));
- debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
- if (ui != metadata_store.songtime_in_milliseconds) {
- metadata_store.songtime_in_milliseconds = ui;
- metadata_store.songtime_in_milliseconds_changed = 1;
- debug(2, "DACP Song Time set to: \"%u\"",
- metadata_store.songtime_in_milliseconds);
- }
- break;
-
- /*
- case 'mstt':
- case 'cant':
- case 'cast':
- case 'cmmk':
- case 'caas':
- case 'caar':
- t = sp - item_size;
- r = ntohl(*(uint32_t *)(t));
- printf(" %d", r);
- printf(" (0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- printf(")");
- break;
- case 'asai':
- t = sp - item_size;
- s = ntohl(*(uint32_t *)(t));
- s = s << 32;
- t += 4;
- v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
- s += v;
- printf(" %lu", s);
- printf(" (0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- printf(")");
- break;
- */
default:
- /*
- printf(" 0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- */
+ debug(1, "Unrecognised play status %d received.", r);
break;
}
- // printf("\n");
- }
+ break;
+ case 'cash': // shuffle status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 0:
+ if (metadata_store.shuffle_status != SS_OFF) {
+ metadata_store.shuffle_status = SS_OFF;
+ debug(2, "Shuffle status is \"off\".");
+ }
+ break;
+ case 1:
+ if (metadata_store.shuffle_status != SS_ON) {
+ metadata_store.shuffle_status = SS_ON;
+ debug(2, "Shuffle status is \"on\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised shuffle status %d received.", r);
+ break;
+ }
+ break;
+ case 'carp': // repeat status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 0:
+ if (metadata_store.repeat_status != RS_OFF) {
+ metadata_store.repeat_status = RS_OFF;
+ debug(2, "Repeat status is \"none\".");
+ }
+ break;
+ case 1:
+ if (metadata_store.repeat_status != RS_ONE) {
+ metadata_store.repeat_status = RS_ONE;
+ debug(2, "Repeat status is \"one\".");
+ }
+ break;
+ case 2:
+ if (metadata_store.repeat_status != RS_ALL) {
+ metadata_store.repeat_status = RS_ALL;
+ debug(2, "Repeat status is \"all\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised repeat status %d received.", r);
+ break;
+ }
+ break;
+ case 'cann': // track name
+ debug(2, "DACP Track Name seen");
+ if (string_update_with_size(&metadata_store.track_name,
+ &metadata_store.track_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
+ }
+ break;
+ case 'cana': // artist name
+ debug(2, "DACP Artist Name seen");
+ if (string_update_with_size(&metadata_store.artist_name,
+ &metadata_store.artist_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
+ }
+ break;
+ case 'canl': // album name
+ debug(2, "DACP Album Name seen");
+ if (string_update_with_size(&metadata_store.album_name,
+ &metadata_store.album_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
+ }
+ break;
+ case 'cang': // genre
+ debug(2, "DACP Genre seen");
+ if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
+ sp - item_size, item_size)) {
+ debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
+ }
+ break;
+ case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
+ // see reference above)
+ debug(2, "DACP Composite ID seen");
+ if (memcmp(metadata_store.item_composite_id, sp - item_size,
+ sizeof(metadata_store.item_composite_id)) != 0) {
+ memcpy(metadata_store.item_composite_id, sp - item_size,
+ sizeof(metadata_store.item_composite_id));
+ char st[33];
+ char *pt = st;
+ int it;
+ for (it = 0; it < 16; it++) {
+ snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
+ pt += 2;
+ }
+ *pt = 0;
+ debug(2, "Item composite ID changed to 0x%s.", st);
+ metadata_store.item_composite_id_changed = 1;
+ }
+ break;
+ case 'astm':
+ t = sp - item_size;
+ ui = ntohl(*(uint32_t *)(t));
+ debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
+ if (ui != metadata_store.songtime_in_milliseconds) {
+ metadata_store.songtime_in_milliseconds = ui;
+ metadata_store.songtime_in_milliseconds_changed = 1;
+ debug(2, "DACP Song Time set to: \"%u\"",
+ metadata_store.songtime_in_milliseconds);
+ }
+ break;
- // finished possibly writing to the metadata hub
- metadata_hub_modify_epilog(1); // should really see if this can be made responsive to changes
- } else {
- debug(1, "Status Update not found.\n");
+ /*
+ case 'mstt':
+ case 'cant':
+ case 'cast':
+ case 'cmmk':
+ case 'caas':
+ case 'caar':
+ t = sp - item_size;
+ r = ntohl(*(uint32_t *)(t));
+ printf(" %d", r);
+ printf(" (0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ printf(")");
+ break;
+ case 'asai':
+ t = sp - item_size;
+ s = ntohl(*(uint32_t *)(t));
+ s = s << 32;
+ t += 4;
+ v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
+ s += v;
+ printf(" %lu", s);
+ printf(" (0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ printf(")");
+ break;
+ */
+ default:
+ /*
+ printf(" 0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ */
+ break;
+ }
+ // printf("\n");
}
+
+ // finished possibly writing to the metadata hub
+ metadata_hub_modify_epilog(
+ 1); // should really see if this can be made responsive to changes
} else {
- debug(1, "Can't find any content in playerstatusupdate request");
+ debug(1, "Status Update not found.\n");
}
- } /* else {
- if (result != 403)
- debug(1, "Unexpected response %d to playerstatusupdate request", result);
- } */
- if (response) {
- free(response);
- response = NULL;
- };
+ } else {
+ debug(1, "Can't find any content in playerstatusupdate request");
+ }
+ } /* else {
+ if (result != 403)
+ debug(1, "Unexpected response %d to playerstatusupdate request", result);
+ } */
+ if (response) {
+ free(response);
+ response = NULL;
};
- /*
- strcpy(command,"nowplayingartwork?mw=320&mh=320");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- strcpy(command,"getproperty?properties=dmcp.volume");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- strcpy(command,"setproperty?dmcp.volume=100.000000");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- */
- if (metadata_store.player_thread_active)
- sleep(config.scan_interval_when_active);
- else
- sleep(config.scan_interval_when_inactive);
+ };
+ /*
+ strcpy(command,"nowplayingartwork?mw=320&mh=320");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ strcpy(command,"getproperty?properties=dmcp.volume");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ strcpy(command,"setproperty?dmcp.volume=100.000000");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ */
+ if (metadata_store.player_thread_active)
+ sleep(config.scan_interval_when_active);
+ else
+ sleep(config.scan_interval_when_inactive);
}
debug(1, "DACP monitor thread exiting -- should never happen.");
pthread_exit(NULL);
diff --git a/shairport-sync-dbus-test-client.c b/shairport-sync-dbus-test-client.c
index 6b8f122c..11100234 100644
--- a/shairport-sync-dbus-test-client.c
+++ b/shairport-sync-dbus-test-client.c
@@ -65,8 +65,7 @@ void on_properties_changed(__attribute__((unused)) GDBusProxy *proxy, GVariant *
}
}
-void notify_loudness_callback(ShairportSync *proxy,
- __attribute__((unused)) gpointer user_data) {
+void notify_loudness_callback(ShairportSync *proxy, __attribute__((unused)) gpointer user_data) {
// printf("\"notify_loudness_callback\" called with a gpointer of
// %lx.\n",(int64_t)user_data);
gboolean ebl = shairport_sync_get_loudness(proxy);
@@ -187,28 +186,44 @@ int main(int argc, char *argv[]) {
g_signal_connect(proxy4, "notify::volume", G_CALLBACK(notify_volume_callback),
"ShairportSync.AdvancedRemoteControl");
+
+
g_print("Starting test...\n");
+ g_print("Using the RemoteControl interface, set airplay volume to -30, -20, -10, 0 and -15 for two seconds each...\n");
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -30, NULL, NULL, 0);
+ sleep(2);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -20, NULL, NULL, 0);
+ sleep(2);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -10, NULL, NULL, 0);
+ sleep(2);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), 0, NULL, NULL, 0);
+ sleep(2);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -15, NULL, NULL, 0);
+ sleep(2);
+
+ g_print("Using the AdvancedRemoteControl interface, set airplay volume to 20%%, 100%%, 40%% and 60%% for two seconds each...\n");
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 20, NULL, NULL, 0);
- sleep(5);
+ sleep(2);
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 100, NULL, NULL, 0);
- sleep(5);
+ sleep(2);
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 40, NULL, NULL, 0);
- sleep(5);
+ sleep(2);
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 60, NULL, NULL, 0);
-
- sleep(5);
- g_print("Volume up for five seconds...\n");
- shairport_sync_remote_control_call_volume_up(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, NULL);
+ sleep(2);
+ g_print("Using the RemoteControl interface, increase volume for five seconds...\n");
+ shairport_sync_remote_control_call_volume_up(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL,
+ NULL);
sleep(5);
- g_print("Volume down\n");
- shairport_sync_remote_control_call_volume_down(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, NULL);
-
+ g_print("Using the RemoteControl interface, decrease volume...\n");
+ shairport_sync_remote_control_call_volume_down(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL,
+ NULL);
+
/*
// sleep(1);
shairport_sync_set_loudness_filter_active(SHAIRPORT_SYNC(proxy), TRUE);
From 99d99b417af39127911b3314bd48d59cea58f430 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 08:31:40 +0000
Subject: [PATCH 013/186] Remove timeout on reception of DACP responses to
allow for long poll behaviour of some players, e.g. Forked Daapd. Add some
examples to the dbus client.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 7b5d83da..15556ea7 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.3.6d3], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d4], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 71e8da22a25cceb42e248da38289a349ff82e9e2 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 08:47:54 +0000
Subject: [PATCH 014/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index bf94f4fd..48b226f8 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,11 @@
+Version 3.3.6d4
+====
+**Bug Fix**
+* Fix an incompatibilty with Forked Daapd that was causing Forked Daapd to lock up. Thanks to [@tomgadow](https://github.com/tomgadow) and [@ejurgensen](https://github.com/ejurgensen) for their help in finding and (hopefully) fixing this issue.
+
+**Enhancements**
+* Add examples of using the RemoteControl interface's SetAirplayVolume feature to the shairport-sync-dbus-test-client source.
+
Version 3.3.6d3
====
**New Features**
From 98a091420cdd163802375b59c0ab73212607522b Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 08:51:42 +0000
Subject: [PATCH 015/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 48b226f8..b56e0b17 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,7 +1,7 @@
Version 3.3.6d4
====
**Bug Fix**
-* Fix an incompatibilty with Forked Daapd that was causing Forked Daapd to lock up. Thanks to [@tomgadow](https://github.com/tomgadow) and [@ejurgensen](https://github.com/ejurgensen) for their help in finding and (hopefully) fixing this issue.
+* Fix an incompatibilty with Forked Daapd that was causing Forked Daapd to lock up. Thanks to [@tomgadow](https://github.com/tomgadow) and [@ejurgensen](https://github.com/ejurgensen) for their help in finding and (hopefully) fixing this issue. Addresses Issue [#953](https://github.com/mikebrady/shairport-sync/issues/953) and [Forked Daapd Issue #870](https://github.com/ejurgensen/forked-daapd/issues/870).
**Enhancements**
* Add examples of using the RemoteControl interface's SetAirplayVolume feature to the shairport-sync-dbus-test-client source.
From 27e42a666aa4461a5f9620b65809b189ab95d981 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 17:20:25 +0000
Subject: [PATCH 016/186] Store the UserAgent so as to recognise forked-daapd
so as to always send a revision_number of 1 when asking for playerstatus of
forked-daapd.
---
dacp.c | 40 ++++++++++++++++++++++++++--------------
player.h | 1 +
rtsp.c | 14 ++++++++++----
3 files changed, 37 insertions(+), 18 deletions(-)
diff --git a/dacp.c b/dacp.c
index a47d1608..f3111a83 100644
--- a/dacp.c
+++ b/dacp.c
@@ -53,6 +53,7 @@ typedef struct {
char dacp_id[256]; // the DACP ID string
uint16_t port; // zero if no port discovered
short connection_family; // AF_INET6 or AF_INET
+ int always_use_revision_number_1; // for dealing with forked-daapd;
uint32_t scope_id; // if it's an ipv6 connection, this will be its scope id
char ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
uint32_t active_remote_id; // send this when you want to send remote control commands
@@ -226,10 +227,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500000;
- // Don't place a timeout on the receive side of these calls, as some clients may provide
- // a "long poll" mechanism whereby a reply will not be sent until a change has occurred
- // 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_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)
debug(1, "dacp_send_command: error %d setting send timeout.", errno);
@@ -284,12 +283,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
memset(buffer, 0, sizeof(buffer));
while (needmore && !looperror) {
const char *data = buffer;
-
- // Don't place a timeout on the receive, as some clients may provide
- // a "long poll" mechanism whereby a reply will not be sent until a change has occurred
-
- // 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_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
+ debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0);
// debug(3, "Received %d bytes: \"%s\".", ndata, buffer);
if (ndata <= 0) {
@@ -413,9 +408,21 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
dacp_server.connection_family = conn->connection_ip_family;
dacp_server.scope_id = conn->self_scope_id;
strncpy(dacp_server.ip_string, conn->client_ip_string, INET6_ADDRSTRLEN);
- debug(3, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
+ debug(2, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
dacp_server.ip_string, dacp_server.dacp_id);
+ // If the client is forked-daapd, then we always use revision number 1
+ // because otherwise the return read will hang in a "long poll" if there
+ // are no changes.
+ // This is different to other AirPlay clients
+ // which return immediately with a 403 code if there are no changes.
+ dacp_server.always_use_revision_number_1 = 0;
+ char *p = strstr(conn->UserAgent, "forked-daapd");
+ if ((p != 0) && (p == conn->UserAgent)) {// must exist and be at the start of the UserAgent string
+ dacp_server.always_use_revision_number_1 = 1;
+ }
+
+
mdns_dacp_monitor_set_id(dacp_server.dacp_id);
metadata_hub_modify_prolog();
@@ -480,6 +487,7 @@ void dacp_monitor_thread_code_cleanup(__attribute__((unused)) void *arg) {
void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
int scan_index = 0;
+ int always_use_revision_number_1 = 0;
// char server_reply[10000];
// debug(1, "DACP monitor thread started.");
// wait until we get a valid port number to begin monitoring it
@@ -516,6 +524,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
idle_scan_count = 0;
}
+ always_use_revision_number_1 = dacp_server.always_use_revision_number_1; // set this while access is locked
+
result = dacp_get_volume(&the_volume); // just want the http code
pthread_cleanup_pop(1);
@@ -604,6 +614,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
char *response = NULL;
int32_t item_size;
char command[1024] = "";
+ if (always_use_revision_number_1 != 0) // for forked-daapd
+ revision_number = 1;
snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
revision_number);
// debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
@@ -641,7 +653,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
case 'cmsr': // revision number
t = sp - item_size;
revision_number = ntohl(*(uint32_t *)(t));
- // debug(1," Serial Number: %d", revision_number);
+ // debug(1,"New revision number received: %d", revision_number);
break;
case 'caps': // play status
t = sp - item_size;
@@ -1003,7 +1015,7 @@ int dacp_set_include_speaker_volume(int64_t machine_number, int32_t vo) {
snprintf(message, sizeof(message),
"setproperty?include-speaker-id=%" PRId64 "&dmcp.volume=%" PRId32 "", machine_number,
vo);
- debug(1, "sending \"%s\"", message);
+ debug(2, "sending \"%s\"", message);
return send_simple_dacp_command(message);
// should return 204
}
@@ -1013,7 +1025,7 @@ int dacp_set_speaker_volume(int64_t machine_number, int32_t vo) {
memset(message, 0, sizeof(message));
snprintf(message, sizeof(message), "setproperty?speaker-id=%" PRId64 "&dmcp.volume=%" PRId32 "",
machine_number, vo);
- debug(1, "sending \"%s\"", message);
+ debug(2, "sending \"%s\"", message);
return send_simple_dacp_command(message);
// should return 204
}
diff --git a/player.h b/player.h
index 9246f448..3be249dc 100644
--- a/player.h
+++ b/player.h
@@ -79,6 +79,7 @@ typedef struct {
typedef struct {
int connection_number; // for debug ID purposes, nothing else...
int resend_interval; // this is really just for debugging
+ char *UserAgent; // free this on teardown
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
diff --git a/rtsp.c b/rtsp.c
index 757bcd36..0bf68457 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -626,16 +626,16 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
goto shutdown;
}
-/* // this outputs the message received
+/* // this outputs the message received
{
void *pt = malloc(nread+1);
memset(pt, 0, nread+1);
memcpy(pt, buf + inbuf, nread);
debug(1, "Incoming string on port: \"%s\"",pt);
- free(pt);
+ free(pt);
}
*/
-
+
inbuf += nread;
char *next;
@@ -1889,7 +1889,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]);
char* found;
while ((found = strsep(&pfmtp, " \t")) != NULL && i < max_param) {
- conn->stream.fmtp[i++] = atoi(found);
+ conn->stream.fmtp[i++] = atoi(found);
}
// here we should check the sanity of the fmtp values
// for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
@@ -1930,6 +1930,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
}
hdr = msg_get_header(req, "User-Agent");
if (hdr) {
+ conn->UserAgent = strdup(hdr);
debug(2, "Play connection from user agent \"%s\" on RTSP conversation thread %d.", hdr,
conn->connection_number);
// if the user agent is AirPlay and has a version number of 353 or less (from iOS 11.1,2)
@@ -2267,6 +2268,11 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
conn->dacp_id = NULL;
}
+ if (conn->UserAgent) {
+ free(conn->UserAgent);
+ conn->UserAgent = NULL;
+ }
+
// remove flow control and mutexes
int rc = pthread_mutex_destroy(&conn->volume_control_mutex);
if (rc)
From 249a6deff9d401778100a1576d1eddd3c1ac3551 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 17:20:53 +0000
Subject: [PATCH 017/186] Bump copyright date.
---
mpris-service.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mpris-service.c b/mpris-service.c
index 387b3a23..168ce396 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -1,6 +1,6 @@
/*
* This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2018 -- 2019
+ * Copyright (c) Mike Brady 2018 -- 2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -330,7 +330,7 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
NULL);
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume),
NULL);
-
+
add_metadata_watcher(mpris_metadata_watcher, NULL);
From ec665d9be45f8d655da1e444d8514a2da4e27106 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 17:21:55 +0000
Subject: [PATCH 018/186] Change from talking about a server to talking about a
client. Technically, e.g. iTunes is a client of Shairport Sync.
---
dbus-service.c | 34 ++++++++++++++++++----------------
org.gnome.ShairportSync.xml | 4 ++--
2 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/dbus-service.c b/dbus-service.c
index d9d6e95d..3efde8f1 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -56,7 +56,7 @@ guint ownerID = 0;
void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
char response[100];
gboolean current_status, new_status;
-
+
const char *th;
shairport_sync_advanced_remote_control_set_volume(shairportSyncAdvancedRemoteControlSkeleton,
argc->speaker_volume);
@@ -64,8 +64,10 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairport_sync_remote_control_set_airplay_volume(shairportSyncRemoteControlSkeleton,
argc->airplay_volume);
- shairport_sync_remote_control_set_server(shairportSyncRemoteControlSkeleton, argc->client_ip);
+ shairport_sync_remote_control_set_client(shairportSyncRemoteControlSkeleton, argc->client_ip);
+
+ // although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their term).
if (argc->dacp_server_active) {
shairport_sync_remote_control_set_available(shairportSyncRemoteControlSkeleton, TRUE);
} else {
@@ -161,7 +163,7 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairportSyncAdvancedRemoteControlSkeleton, response);
}
-
+
switch (argc->shuffle_status) {
case SS_NOT_AVAILABLE:
new_status = FALSE;
@@ -176,10 +178,10 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
new_status = FALSE;
debug(1, "Unknown shuffle status -- this should never happen.");
}
-
+
current_status = shairport_sync_advanced_remote_control_get_shuffle(
shairportSyncAdvancedRemoteControlSkeleton);
-
+
// only set this if it's different
if (current_status != new_status) {
debug(3, "Shuffle State should be changed");
@@ -243,7 +245,7 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
// debug(1, "Set tracklength to %lu.", track_length_in_microseconds);
GVariant *tracklength = g_variant_new("x", track_length_in_microseconds);
g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength);
- }
+ }
GVariant *dict = g_variant_builder_end(dict_builder);
g_variant_builder_unref(dict_builder);
@@ -753,16 +755,16 @@ static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *i
static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
const gchar *command,
__attribute__((unused)) gpointer user_data) {
- debug(1, "RemoteCommand with command \"%s\".", command);
+ debug(1, "RemoteCommand with command \"%s\".", command);
int reply = 0;
- char *server_reply = NULL;
+ char *client_reply = NULL;
ssize_t reply_size = 0;
- reply = dacp_send_command((const char *)command, &server_reply, &reply_size);
- char *server_reply_hex = alloca(reply_size * 2 + 1);
- if (server_reply_hex) {
- char *p = server_reply_hex;
- if (server_reply) {
- char *q = server_reply;
+ reply = dacp_send_command((const char *)command, &client_reply, &reply_size);
+ char *client_reply_hex = alloca(reply_size * 2 + 1);
+ if (client_reply_hex) {
+ char *p = client_reply_hex;
+ if (client_reply) {
+ char *q = client_reply;
int i;
for (i = 0; i < reply_size; i++) {
snprintf(p, 3, "%02X", *q);
@@ -772,7 +774,7 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
}
*p = '\0';
}
- shairport_sync_complete_remote_command(skeleton, invocation, reply, server_reply_hex);
+ shairport_sync_complete_remote_command(skeleton, invocation, reply, client_reply_hex);
return TRUE;
}
@@ -974,7 +976,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
// else
// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), NULL);
#endif
-
+
shairport_sync_set_version(SHAIRPORT_SYNC(shairportSyncSkeleton), PACKAGE_VERSION);
char *vs = get_version_string();
shairport_sync_set_version_string(SHAIRPORT_SYNC(shairportSyncSkeleton), vs);
diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml
index 91c31671..10eb5477 100644
--- a/org.gnome.ShairportSync.xml
+++ b/org.gnome.ShairportSync.xml
@@ -13,7 +13,7 @@
-
+
@@ -45,7 +45,7 @@
-
+
From c75311c934d888c4272a1ab7881d953558eed8a8 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 17:22:28 +0000
Subject: [PATCH 019/186] Add a few more commands and clarify some text.
---
shairport-sync-dbus-test-client.c | 44 ++++++++++++++++++++++---------
1 file changed, 32 insertions(+), 12 deletions(-)
diff --git a/shairport-sync-dbus-test-client.c b/shairport-sync-dbus-test-client.c
index 11100234..d0b15bb2 100644
--- a/shairport-sync-dbus-test-client.c
+++ b/shairport-sync-dbus-test-client.c
@@ -190,32 +190,51 @@ int main(int argc, char *argv[]) {
g_print("Starting test...\n");
- g_print("Using the RemoteControl interface, set airplay volume to -30, -20, -10, 0 and -15 for two seconds each...\n");
+ g_print("Using the RemoteControl interface, play for five seconds, pause for five seconds and then resume play...\n");
+ g_print("Play...\n");
+ shairport_sync_remote_control_call_play(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
+ sleep(5);
+ g_print("Pause...\n");
+ shairport_sync_remote_control_call_pause(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
+ sleep(5);
+ g_print("Play...\n");
+ shairport_sync_remote_control_call_play(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
+ sleep(5);
+ g_print("Using the RemoteControl interface, set AirPlay Volume (range -30 to 0) to -30, -20, -10, 0 and -15 for five seconds each...\n");
+ g_print("Set AirPlay Volume (range -30 to 0) to -30\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -30, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
+ g_print("Set AirPlay Volume (range -30 to 0) to -20\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -20, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
+ g_print("Set AirPlay Volume (range -30 to 0) to -10\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -10, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
+ g_print("Set AirPlay Volume (range -30 to 0) to -0\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), 0, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
+ g_print("Set AirPlay Volume (range -30 to 0) to -15\n");
shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -15, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
- g_print("Using the AdvancedRemoteControl interface, set airplay volume to 20%%, 100%%, 40%% and 60%% for two seconds each...\n");
+ g_print("Using the AdvancedRemoteControl interface, set Volume to 20%%, 100%%, 40%% and 60%% for five seconds each...\n");
+ g_print("Set Volume to 20%%\n");
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 20, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
+ g_print("Set Volume to 100%%\n");
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 100, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
+ g_print("Set Volume to 40%%\n");
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 40, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
+ g_print("Set Volume to 50%%\n");
shairport_sync_advanced_remote_control_call_set_volume(
- SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 60, NULL, NULL, 0);
+ SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 50, NULL, NULL, 0);
- sleep(2);
+ sleep(5);
g_print("Using the RemoteControl interface, increase volume for five seconds...\n");
shairport_sync_remote_control_call_volume_up(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL,
NULL);
@@ -241,6 +260,7 @@ int main(int argc, char *argv[]) {
shairport_sync_call_remote_command(SHAIRPORT_SYNC(proxy), "string",NULL,NULL,NULL);
*/
+ sleep(1);
g_print("Finished test. Listening for property changes...\n");
// g_main_loop_quit(loop);
pthread_join(dbus_thread, NULL);
From a7018e4cb8ad6ea4d780cb14cb92e2307e8a305f Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 17:23:31 +0000
Subject: [PATCH 020/186] Use a different workaround for forked-daapd remote
access and clean up a few small issues.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 15556ea7..03177b7f 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.3.6d4], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d5], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From adbba7de161c50416fa66808404b559589c729a7 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 10 Jan 2020 17:44:33 +0000
Subject: [PATCH 021/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index b56e0b17..abcaf344 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,10 +1,17 @@
-Version 3.3.6d4
+Version 3.3.6d5
====
**Bug Fix**
* Fix an incompatibilty with Forked Daapd that was causing Forked Daapd to lock up. Thanks to [@tomgadow](https://github.com/tomgadow) and [@ejurgensen](https://github.com/ejurgensen) for their help in finding and (hopefully) fixing this issue. Addresses Issue [#953](https://github.com/mikebrady/shairport-sync/issues/953) and [Forked Daapd Issue #870](https://github.com/ejurgensen/forked-daapd/issues/870).
**Enhancements**
-* Add examples of using the RemoteControl interface's SetAirplayVolume feature to the shairport-sync-dbus-test-client source.
+* Add some extra examples of using the RemoteControl interface's SetAirplayVolume feature to the shairport-sync-dbus-test-client source.
+
+**Pesky Changes You May Not Be Able To Ignore**
+* Renamed the `Server` property in the D-Bus interface to `Client`. The rather strange language in use has it that a player like iTunes is a "client" of the AirPlay device like Shairport Sync, which is therefore considered the "server". The newly-renamed "Client" property is the IP number of the player.
+
+Version 3.3.6d4
+====
+A fix for issue #953 that wouldn't work.
Version 3.3.6d3
====
From 400bd752272fd3d53cee59599c2b1bd962c57f6f Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 26 Jan 2020 12:39:36 +0000
Subject: [PATCH 022/186] Update and rename CONTRIBUTING.md to REPORTING
ISSUES.md
---
CONTRIBUTING.md => REPORTING ISSUES.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename CONTRIBUTING.md => REPORTING ISSUES.md (97%)
diff --git a/CONTRIBUTING.md b/REPORTING ISSUES.md
similarity index 97%
rename from CONTRIBUTING.md
rename to REPORTING ISSUES.md
index 2b879f79..5c502091 100644
--- a/CONTRIBUTING.md
+++ b/REPORTING ISSUES.md
@@ -1,5 +1,5 @@
-Contributing to Shairport Sync
+Reporting Issues and Making Pull Requests
====
Pull Requests
From 537158571f97eefaf399331d55daef45b8226cfe Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 11 Feb 2020 22:44:07 +0000
Subject: [PATCH 023/186] Add a SIGCHLD handler to reap zombie processes
generated after script invocations where wait_for_completion is set to "no".
---
shairport.c | 46 ++++++++++++++++++++++++++++++++++------------
1 file changed, 34 insertions(+), 12 deletions(-)
diff --git a/shairport.c b/shairport.c
index d2a1a494..5e375916 100644
--- a/shairport.c
+++ b/shairport.c
@@ -170,7 +170,7 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
outbuffer, buffer_length + 1, &odone, // Output.
&io_spec, // Input, output and transfer spec.
NULL, NULL); // Default configuration.
-
+
io_spec.itype = SOXR_INT32_I;
io_spec.otype = SOXR_INT32_I;
io_spec.scale = 1.0; // this seems to crash if not = 1.0
@@ -766,8 +766,8 @@ int parse_options(int argc, char **argv) {
} else
die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"");
}
-
-
+
+
/* Get the resend control settings. */
if (config_lookup_float(config.cfg, "general.resend_control_first_check_time",
&dvalue)) {
@@ -1197,14 +1197,14 @@ int parse_options(int argc, char **argv) {
char hostname[100];
gethostname(hostname, 100);
-
-
+
+
char *i0;
if (raw_service_name == NULL)
i0 = strdup("%H"); // this is the default it the Service Name wasn't specified
else
i0 = strdup(raw_service_name);
-
+
// here, do the substitutions for %h, %H, %v and %V
char *i1 = str_replace(i0, "%h", hostname);
if ((hostname[0] >= 'a') && (hostname[0] <= 'z'))
@@ -1284,7 +1284,7 @@ void exit_function() {
Actually, there is no terminate_mqtt() function.
#ifdef CONFIG_MQTT
if (config.mqtt_enabled) {
- terminate_mqtt();
+ terminate_mqtt();
}
#endif
*/
@@ -1333,14 +1333,14 @@ Actually, there is no stop_mpris_service() function.
pthread_join(soxr_time_check_thread, NULL);
#endif
-
+
if (conns)
free(conns); // make sure the connections have been deleted first
if (config.service_name)
free(config.service_name);
-
-#ifdef CONFIG_CONVOLUTION
+
+#ifdef CONFIG_CONVOLUTION
if (config.convolution_ir_file)
free(config.convolution_ir_file);
#endif
@@ -1364,6 +1364,16 @@ Actually, there is no stop_mpris_service() function.
// probably should be freeing malloc'ed memory here, including strdup-created strings...
}
+// for removing zombie script processes
+// see: http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
+// used with thanks.
+
+void handle_sigchld(__attribute__((unused)) int sig) {
+ int saved_errno = errno;
+ while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}
+ errno = saved_errno;
+}
+
int main(int argc, char **argv) {
/* Check if we are called with -V or --version parameter */
if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
@@ -1581,8 +1591,8 @@ int main(int argc, char **argv) {
}
return ret;
} else { /* pid == 0 means we are the daemon */
-
- this_is_the_daemon_process = 1; //
+
+ this_is_the_daemon_process = 1; //
/* Close FDs */
if (daemon_close_all(-1) < 0) {
@@ -1624,6 +1634,18 @@ int main(int argc, char **argv) {
#endif
debug(1, "Started!");
+
+ // install a zombie process reaper
+ // see: http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
+ struct sigaction sa;
+ sa.sa_handler = &handle_sigchld;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
+ if (sigaction(SIGCHLD, &sa, 0) == -1) {
+ perror(0);
+ exit(1);
+ }
+
main_thread_id = pthread_self();
if (!main_thread_id)
debug(1, "Main thread is set up to be NULL!");
From 17759c97edd35f5464763ca0b89a0a29a6a265e1 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 11 Feb 2020 22:44:50 +0000
Subject: [PATCH 024/186] Add a SIGCHLD handler to reap zombie processes
generated after script invocations where wait_for_completion is set to "no".
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 03177b7f..a47a248d 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.3.6d5], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d6], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 89af8e88c1d8b1e109b7b8a1051496000d47fa7e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 11 Feb 2020 22:49:56 +0000
Subject: [PATCH 025/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index abcaf344..c452c22a 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,8 @@
+Version 3.3.6d6
+====
+**Bug Fix**
+* Add a SIGCHLD handler to remove completed processes that were used to perform and program "hooks" without waiting for completion. Thanks to [patrickjane](https://github.com/patrickjane) for reporting the bug and for suggesting a solution. Addresses Issue [#968](https://github.com/mikebrady/shairport-sync/issues/968).
+
Version 3.3.6d5
====
**Bug Fix**
From f7717745198fc4748d9d646cfbdfc90adb668801 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 13 Feb 2020 17:37:41 +0000
Subject: [PATCH 026/186] Compiles under gcc-10 with -fno-common
---
audio_alsa.c | 20 ++++++++++----------
common.c | 8 ++++++--
common.h | 52 ++++++++++++++++++++++++++--------------------------
mdns.h | 2 +-
player.c | 6 +++---
player.h | 6 +++---
rtsp.c | 3 ++-
rtsp.h | 4 ++--
8 files changed, 53 insertions(+), 48 deletions(-)
diff --git a/audio_alsa.c b/audio_alsa.c
index 27e35ed8..39e4d051 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -123,7 +123,7 @@ int frame_size; // in bytes for interleaved stereo
int alsa_device_initialised; // boolean to ensure the initialisation is only
// done once
-enum yndk_type precision_delay_available_status =
+yndk_type precision_delay_available_status =
YNDK_DONT_KNOW; // initially, we don't know if the device can do precision delay
snd_pcm_t *alsa_handle = NULL;
@@ -159,14 +159,14 @@ int volume_based_mute_is_active =
snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, snd_pcm_uframes_t) = snd_pcm_writei;
int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
- enum yndk_type *using_update_timestamps);
+ yndk_type *using_update_timestamps);
int standard_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
- enum yndk_type *using_update_timestamps);
+ yndk_type *using_update_timestamps);
// use this to allow the use of standard or precision delay calculations, with standard the, uh,
// standard.
int (*delay_and_status)(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
- enum yndk_type *using_update_timestamps) = standard_delay_and_status;
+ yndk_type *using_update_timestamps) = standard_delay_and_status;
// this will return true if the DAC can return precision delay information and false if not
// if it is not yet known, it will test the output device to find out
@@ -216,7 +216,7 @@ int precision_delay_available() {
do_play(silence, frames_of_silence);
pthread_cleanup_pop(1);
// now we can get the delay, and we'll note if it uses update timestamps
- enum yndk_type uses_update_timestamps;
+ yndk_type uses_update_timestamps;
snd_pcm_state_t state;
snd_pcm_sframes_t delay;
int ret = precision_delay_and_status(&state, &delay, &uses_update_timestamps);
@@ -392,7 +392,7 @@ format_record fr[] = {
// be added at the lowest possible level.
// Hence, selecting the greatest bit depth is always either beneficial or neutral.
-enum sps_format_t auto_format_check_sequence[] = {
+sps_format_t auto_format_check_sequence[] = {
SPS_FORMAT_S32, SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S24, SPS_FORMAT_S24_LE,
SPS_FORMAT_S24_BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S16, SPS_FORMAT_S16_LE,
SPS_FORMAT_S16_BE, SPS_FORMAT_S8, SPS_FORMAT_U8,
@@ -508,12 +508,12 @@ int actual_open_alsa_device(int do_auto_setup) {
}
} else { // auto format
int number_of_formats_to_try;
- enum sps_format_t *formats;
+ sps_format_t *formats;
formats = auto_format_check_sequence;
number_of_formats_to_try = sizeof(auto_format_check_sequence) / sizeof(sps_format_t);
int i = 0;
int format_found = 0;
- enum sps_format_t trial_format = SPS_FORMAT_UNKNOWN;
+ sps_format_t trial_format = SPS_FORMAT_UNKNOWN;
while ((i < number_of_formats_to_try) && (format_found == 0)) {
trial_format = formats[i];
sf = fr[trial_format].alsa_code;
@@ -1422,7 +1422,7 @@ static void start(__attribute__((unused)) int i_sample_rate,
}
int standard_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
- enum yndk_type *using_update_timestamps) {
+ yndk_type *using_update_timestamps) {
int ret = 0;
if (using_update_timestamps)
*using_update_timestamps = YNDK_NO;
@@ -1444,7 +1444,7 @@ int standard_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
}
int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
- enum yndk_type *using_update_timestamps) {
+ yndk_type *using_update_timestamps) {
snd_pcm_status_t *alsa_snd_pcm_status;
snd_pcm_status_alloca(&alsa_snd_pcm_status);
diff --git a/common.c b/common.c
index ff643ef3..3d41cb6c 100644
--- a/common.c
+++ b/common.c
@@ -87,6 +87,10 @@
void set_alsa_out_dev(char *);
#endif
+config_t config_file_stuff;
+pthread_t main_thread_id;
+uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
+
// always lock use this when accessing the fp_time_at_last_debug_message
static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -96,7 +100,7 @@ const char *sps_format_description_string_array[] = {
"unknown", "S8", "U8", "S16", "S16_LE", "S16_BE", "S24", "S24_LE",
"S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid"};
-const char *sps_format_description_string(enum sps_format_t format) {
+const char *sps_format_description_string(sps_format_t format) {
if ((format >= SPS_FORMAT_UNKNOWN) && (format <= SPS_FORMAT_AUTO))
return sps_format_description_string_array[format];
else
@@ -1381,7 +1385,7 @@ char *get_version_string() {
return version_string;
}
-int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format,
+int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t format,
int with_dither, int64_t random_number_in) {
// return the last random number used
// assuming the buffer has been assigned
diff --git a/common.h b/common.h
index 37217ab8..35b21bd9 100644
--- a/common.h
+++ b/common.h
@@ -34,24 +34,24 @@ enum dbus_session_type {
#define sps_extra_code_output_state_cannot_make_ready 32769
// yeah/no/auto
-enum yna_type { YNA_AUTO = -1, YNA_NO = 0, YNA_YES = 1 } yna_type;
+typedef enum { YNA_AUTO = -1, YNA_NO = 0, YNA_YES = 1 } yna_type;
// yeah/no/dont-care
-enum yndk_type { YNDK_DONT_KNOW = -1, YNDK_NO = 0, YNDK_YES = 1 } yndk_type;
+typedef enum { YNDK_DONT_KNOW = -1, YNDK_NO = 0, YNDK_YES = 1 } yndk_type;
-enum endian_type {
+typedef enum {
SS_LITTLE_ENDIAN = 0,
SS_PDP_ENDIAN,
SS_BIG_ENDIAN,
} endian_type;
-enum stuffing_type {
+typedef enum {
ST_basic = 0, // straight deletion or insertion of a frame in a 352-frame packet
ST_soxr, // use libsoxr to make a 352 frame packet one frame longer or shorter
ST_auto, // use soxr if compiled for it and if the soxr_index is low enough
-} s_type;
+} stuffing_type;
-enum playback_mode_type {
+typedef enum {
ST_stereo = 0,
ST_mono,
ST_reverse_stereo,
@@ -59,26 +59,26 @@ enum playback_mode_type {
ST_right_only,
} playback_mode_type;
-enum volume_control_profile_type {
+typedef enum {
VCP_standard = 0,
VCP_flat,
} volume_control_profile_type;
-enum decoders_supported_type {
+typedef enum {
decoder_hammerton = 0,
decoder_apple_alac,
} decoders_supported_type;
-enum disable_standby_mode_type {
+typedef enum {
disable_standby_off = 0,
disable_standby_auto,
disable_standby_always
-};
+} disable_standby_mode_type;
// the following enum is for the formats recognised -- currently only S16LE is recognised for input,
// so these are output only for the present
-enum sps_format_t {
+typedef enum {
SPS_FORMAT_UNKNOWN = 0,
SPS_FORMAT_S8,
SPS_FORMAT_U8,
@@ -97,7 +97,7 @@ enum sps_format_t {
SPS_FORMAT_INVALID,
} sps_format_t;
-const char *sps_format_description_string(enum sps_format_t format);
+const char *sps_format_description_string(sps_format_t format);
typedef struct {
double resend_control_first_check_time; // wait this long before asking for a missing packet to be resent
@@ -182,12 +182,12 @@ typedef struct {
int debugger_show_relative_time; // in the debug message, display the time since the last one
int debugger_show_file_and_line; // in the debug message, display the filename and line number
int statistics_requested, use_negotiated_latencies;
- enum playback_mode_type playback_mode;
+ 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;
+ stuffing_type packet_stuffing;
int soxr_delay_index;
int soxr_delay_threshold; // the soxr delay must be less or equal to this for soxr interpolation
// to be enabled under the auto setting
@@ -221,10 +221,10 @@ typedef struct {
// attenuators, lowering the volume, use all the hw attenuation
// before using
// sw attenuation
- enum volume_control_profile_type volume_control_profile;
+ volume_control_profile_type volume_control_profile;
int output_format_auto_requested; // true if the configuration requests auto configuration
- enum sps_format_t output_format;
+ sps_format_t output_format;
int output_rate_auto_requested; // true if the configuration requests auto configuration
unsigned int output_rate;
@@ -240,9 +240,9 @@ 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;
+ disable_standby_mode_type disable_standby_mode;
volatile int keep_dac_busy;
- enum yna_type use_precision_timing; // defaults to no
+ yna_type use_precision_timing; // defaults to no
#if defined(CONFIG_DBUS_INTERFACE)
enum dbus_session_type dbus_service_bus_type;
@@ -321,7 +321,7 @@ int64_t r64i();
void resetFreeUDPPort();
uint16_t nextFreeUDPPort();
-volatile int debuglev;
+extern volatile int debuglev;
void _die(const char *filename, const int linenumber, const char *format, ...);
void _warn(const char *filename, const int linenumber, const char *format, ...);
@@ -352,17 +352,17 @@ double vol2attn(double vol, long max_db, long min_db);
uint64_t get_absolute_time_in_fp(void);
// time at startup for debugging timing
-uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
+extern uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
uint32_t uatoi(const char *nptr);
// this is for allowing us to cancel the whole program
-pthread_t main_thread_id;
+extern pthread_t main_thread_id;
-shairport_cfg config;
-config_t config_file_stuff;
+extern shairport_cfg config;
+extern config_t config_file_stuff;
int config_set_lookup_bool(config_t *cfg, char *where, int *dst);
@@ -377,7 +377,7 @@ void shairport_shutdown();
extern sigset_t pselect_sigset;
-pthread_mutex_t the_conn_lock;
+extern pthread_mutex_t the_conn_lock;
#define conn_lock(arg) \
pthread_mutex_lock(&the_conn_lock); \
@@ -412,7 +412,7 @@ void pthread_cleanup_debug_mutex_unlock(void *arg);
#define config_unlock pthread_mutex_unlock(&config.lock)
-pthread_mutex_t r64_mutex;
+extern pthread_mutex_t r64_mutex;
#define r64_lock pthread_mutex_lock(&r64_mutex)
@@ -423,7 +423,7 @@ char *get_version_string(); // mallocs a string space -- remember to free it aft
void sps_nanosleep(const time_t sec,
const long nanosec); // waits for this time, even through interruptions
-int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format,
+int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t format,
int with_dither, int64_t random_number_in);
void malloc_cleanup(void *arg);
diff --git a/mdns.h b/mdns.h
index 8dc919f2..b69f4c85 100644
--- a/mdns.h
+++ b/mdns.h
@@ -2,7 +2,7 @@
#define _MDNS_H
#include "config.h"
-#include
+#include "player.h"
#include
extern int mdns_pid;
diff --git a/player.c b/player.c
index 21eb432f..5876bd33 100644
--- a/player.c
+++ b/player.c
@@ -660,7 +660,7 @@ int32_t rand_in_range(int32_t exclusive_range_limit) {
return sp >> 32;
}
-static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume,
+static inline void process_sample(int32_t sample, char **outp, sps_format_t format, int volume,
int dither, rtsp_conn_info *conn) {
/*
{
@@ -1392,7 +1392,7 @@ static inline int32_t mean_32(int32_t a, int32_t b) {
// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32
// stuff: 1 means add 1; 0 means do nothing; -1 means remove 1
-static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format,
+static int stuff_buffer_basic_32(int32_t *inptr, int length, sps_format_t l_output_format,
char *outptr, int stuff, int dither, rtsp_conn_info *conn) {
int tstuff = stuff;
char *l_outptr = outptr;
@@ -1458,7 +1458,7 @@ double longest_soxr_execution_time_us = 0.0;
int64_t packets_processed = 0;
int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
- enum sps_format_t l_output_format, char *outptr, int stuff, int dither,
+ sps_format_t l_output_format, char *outptr, int stuff, int dither,
rtsp_conn_info *conn) {
if (scratchBuffer == NULL) {
die("soxr scratchBuffer not initialised.");
diff --git a/player.h b/player.h
index 3be249dc..b6cae8a9 100644
--- a/player.h
+++ b/player.h
@@ -63,17 +63,17 @@ typedef struct audio_buffer_entry { // decoded audio packets
#define BUFFER_FRAMES 1024
-enum audio_stream_type {
+typedef enum {
ast_unknown,
ast_uncompressed, // L16/44100/2
ast_apple_lossless,
-} ast_type;
+} audio_stream_type;
typedef struct {
int encrypted;
uint8_t aesiv[16], aeskey[16];
int32_t fmtp[12];
- enum audio_stream_type type;
+ audio_stream_type type;
} stream_cfg;
typedef struct {
diff --git a/rtsp.c b/rtsp.c
index 0bf68457..6cf53927 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -89,7 +89,8 @@ enum rtsp_read_request_response {
rtsp_read_request_response_error
};
-// Mike Brady's part...
+rtsp_conn_info *playing_conn;
+rtsp_conn_info **conns;
int metadata_running = 0;
diff --git a/rtsp.h b/rtsp.h
index 71e5b22c..703c1006 100644
--- a/rtsp.h
+++ b/rtsp.h
@@ -3,8 +3,8 @@
#include "player.h"
-rtsp_conn_info *playing_conn;
-rtsp_conn_info **conns;
+extern rtsp_conn_info *playing_conn;
+extern rtsp_conn_info **conns;
void rtsp_listen_loop(void);
// void rtsp_shutdown_stream(void);
From 85ba78b61989670b00c9fb33501d606aa4e7a25f Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 13 Feb 2020 17:57:30 +0000
Subject: [PATCH 027/186] More fixes to allow gcc-10 to compile without error
---
audio_sndio.c | 2 +-
common.h | 8 ++++----
dbus-service.c | 2 ++
dbus-service.h | 2 +-
metadata_hub.c | 2 ++
metadata_hub.h | 20 ++++++++++----------
mpris-service.c | 3 +++
mpris-service.h | 4 ++--
8 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/audio_sndio.c b/audio_sndio.c
index 9fd497ac..ac5cfa38 100644
--- a/audio_sndio.c
+++ b/audio_sndio.c
@@ -65,7 +65,7 @@ struct sio_par par;
struct sndio_formats {
const char *name;
- enum sps_format_t fmt;
+ sps_format_t fmt;
unsigned int rate;
unsigned int bits;
unsigned int bps;
diff --git a/common.h b/common.h
index 35b21bd9..2c9c05bd 100644
--- a/common.h
+++ b/common.h
@@ -24,10 +24,10 @@
#endif
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
-enum dbus_session_type {
+typedef enum {
DBT_system = 0, // use the session bus
DBT_session, // use the system bus
-} dbt_type;
+} dbus_session_type;
#endif
#define sps_extra_code_output_stalled 32768
@@ -245,10 +245,10 @@ typedef struct {
yna_type use_precision_timing; // defaults to no
#if defined(CONFIG_DBUS_INTERFACE)
- enum dbus_session_type dbus_service_bus_type;
+ dbus_session_type dbus_service_bus_type;
#endif
#if defined(CONFIG_MPRIS_INTERFACE)
- enum dbus_session_type mpris_service_bus_type;
+ dbus_session_type mpris_service_bus_type;
#endif
#ifdef CONFIG_METADATA_HUB
diff --git a/dbus-service.c b/dbus-service.c
index 3efde8f1..1fe59c35 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -45,6 +45,8 @@
#include
#endif
+ShairportSync *shairportSyncSkeleton;
+
int service_is_running = 0;
ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL;
diff --git a/dbus-service.h b/dbus-service.h
index 5cfc02e2..1373e9cb 100644
--- a/dbus-service.h
+++ b/dbus-service.h
@@ -3,7 +3,7 @@
#define DBUS_SERVICE_H
#include "dbus-interface.h"
-ShairportSync *shairportSyncSkeleton;
+extern ShairportSync *shairportSyncSkeleton;
int start_dbus_service();
void stop_dbus_service();
diff --git a/metadata_hub.c b/metadata_hub.c
index fb459b6b..c09759f2 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -58,6 +58,8 @@
#include
#endif
+struct metadata_bundle metadata_store;
+
int metadata_hub_initialised = 0;
pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER;
diff --git a/metadata_hub.h b/metadata_hub.h
index ed7d947d..e7e6988a 100644
--- a/metadata_hub.h
+++ b/metadata_hub.h
@@ -5,25 +5,25 @@
#define number_of_watchers 2
-enum play_status_type {
+typedef enum {
PS_NOT_AVAILABLE = 0,
PS_STOPPED,
PS_PAUSED,
PS_PLAYING,
} play_status_type;
-enum active_state_type {
+typedef enum {
AM_INACTIVE = 0,
AM_ACTIVE,
} active_state_type;
-enum shuffle_status_type {
+typedef enum {
SS_NOT_AVAILABLE = 0,
SS_OFF,
SS_ON,
} shuffle_status_type;
-enum repeat_status_type {
+typedef enum {
RS_NOT_AVAILABLE = 0,
RS_OFF,
RS_ONE,
@@ -59,9 +59,9 @@ typedef struct metadata_bundle {
// used detect transitions between server activity being on or off
// e.g. to reease metadata when a server goes inactive, but not if it's permanently
// inactive.
- enum play_status_type play_status;
- enum shuffle_status_type shuffle_status;
- enum repeat_status_type repeat_status;
+ play_status_type play_status;
+ shuffle_status_type shuffle_status;
+ repeat_status_type repeat_status;
// the following pertain to the track playing
@@ -123,9 +123,9 @@ typedef struct metadata_bundle {
// end
- enum play_status_type
+ play_status_type
player_state; // this is the state of the actual player itself, which can be a bit noisy.
- enum active_state_type active_state;
+ active_state_type active_state;
int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the
// speaker volume control
@@ -136,7 +136,7 @@ typedef struct metadata_bundle {
} metadata_bundle;
-struct metadata_bundle metadata_store;
+extern struct metadata_bundle metadata_store;
void add_metadata_watcher(metadata_watcher fn, void *userdata);
diff --git a/mpris-service.c b/mpris-service.c
index 168ce396..76a04c76 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -39,6 +39,9 @@
#include "metadata_hub.h"
#include "mpris-service.h"
+MediaPlayer2 *mprisPlayerSkeleton;
+MediaPlayer2Player *mprisPlayerPlayerSkeleton;
+
double airplay_volume_to_mpris_volume(double sp) {
if (sp < -30.0)
sp = -30.0;
diff --git a/mpris-service.h b/mpris-service.h
index f290d79c..160e55d4 100644
--- a/mpris-service.h
+++ b/mpris-service.h
@@ -4,8 +4,8 @@
#include "mpris-interface.h"
-MediaPlayer2 *mprisPlayerSkeleton;
-MediaPlayer2Player *mprisPlayerPlayerSkeleton;
+extern MediaPlayer2 *mprisPlayerSkeleton;
+extern MediaPlayer2Player *mprisPlayerPlayerSkeleton;
int start_mpris_service();
From f0c0a2310e4d8c3d1a6fbe2ebefcd5f835c34fab Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 14 Feb 2020 16:15:28 +0000
Subject: [PATCH 028/186] Make some changes to remove new warnings from
tinysvcmdns.c in gcc-10
---
tinysvcmdns.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/tinysvcmdns.c b/tinysvcmdns.c
index 4cb6fb4c..069a6436 100644
--- a/tinysvcmdns.c
+++ b/tinysvcmdns.c
@@ -114,8 +114,8 @@ uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2) {
s = malloc(len1 + len2 + 1);
if (s) {
- strncpy((char *)s, (char *)n1, len1);
- strncpy((char *)s + len1, (char *)n2, len2);
+ memcpy((char *)s, (char *)n1, len1);
+ memcpy((char *)s + len1, (char *)n2, len2);
s[len1 + len2] = '\0';
} else {
die("can not allocate memory for \"s\" in tinysvcmdns");
@@ -200,7 +200,7 @@ uint8_t *create_label(const char *txt) {
s = malloc(len + 2);
if (s) {
s[0] = len;
- strncpy((char *)s + 1, txt, len);
+ memcpy((char *)s + 1, txt, len);
s[len + 1] = '\0';
} else {
die("can not allocate memory for \"s\" 2 in tinysvcmdns.");
@@ -222,7 +222,7 @@ uint8_t *create_nlabel(const char *name) {
if (label == NULL)
return NULL;
- strncpy((char *)label + 1, name, len);
+ memcpy((char *)label + 1, name, len);
label[len + 1] = '\0';
p = label;
@@ -1466,7 +1466,7 @@ int close_pipe(int s) {
// main loop to receive, process and send out MDNS replies
// also handles MDNS service announces
-static void main_loop(struct mdnsd *svr) {
+void* main_loop(struct mdnsd *svr) {
fd_set sockfd_set;
int max_fd = svr->sockfd;
char notify_buf[2]; // buffer for reading of notify_pipe
@@ -1568,6 +1568,7 @@ static void main_loop(struct mdnsd *svr) {
close_pipe(svr->sockfd);
svr->stop_flag = 2;
+ return NULL;
}
/////////////////////////////////////////////////////
@@ -1723,7 +1724,7 @@ struct mdnsd *mdnsd_start() {
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- if (pthread_create(&tid, &attr, (void *(*)(void *))main_loop, (void *)server) != 0) {
+ if (pthread_create(&tid, &attr, (void * (*)(void *))&main_loop, (void *)server) != 0) {
pthread_mutex_destroy(&server->data_lock);
free(server);
return NULL;
From 0902cdfe1a7b5e8c8520c1154b1ad13f8256ce22 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 14 Feb 2020 16:28:45 +0000
Subject: [PATCH 029/186] Make some changes to remove new warnings from
tinysvcmdns.c in gcc-10
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index a47a248d..7e0d7407 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.3.6d6], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d7], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 9dee8756601b80b00c045317a3fefd47e075b8ce Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 15 Feb 2020 12:35:02 +0000
Subject: [PATCH 030/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index c452c22a..3ecb0cbd 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,12 +1,19 @@
+Version 3.3.6d7
+====
+**Bug Fix**
+* Make Shairport Sync compile in the forthcoming Fedora 32. Fedora 32 uses GCC-10 which [defaults to `-fno-common`](https://gcc.gnu.org/gcc-10/porting_to.html) exposing a number of issues with Shairport Sync – multiple definitions of some `enum`s and failure to define certain variables as `extern`.
+
+ Version 3.3.6d7 is an attempt to clear up all these errors. Many thanks to [Bill Peck](https://github.com/p3ck) for his ongoing support, for bringing this issue to notice and for developing a workaround. Address issue [#973](https://github.com/mikebrady/shairport-sync/issues/973).
+
Version 3.3.6d6
====
**Bug Fix**
-* Add a SIGCHLD handler to remove completed processes that were used to perform and program "hooks" without waiting for completion. Thanks to [patrickjane](https://github.com/patrickjane) for reporting the bug and for suggesting a solution. Addresses Issue [#968](https://github.com/mikebrady/shairport-sync/issues/968).
+* Add a `SIGCHLD` handler to remove completed processes that were used to perform and program "hooks" without waiting for completion and thus prevent them from becoming zombie processes. Thanks to [patrickjane](https://github.com/patrickjane) for reporting the bug and for suggesting a solution. Addresses issue [#968](https://github.com/mikebrady/shairport-sync/issues/968).
Version 3.3.6d5
====
**Bug Fix**
-* Fix an incompatibilty with Forked Daapd that was causing Forked Daapd to lock up. Thanks to [@tomgadow](https://github.com/tomgadow) and [@ejurgensen](https://github.com/ejurgensen) for their help in finding and (hopefully) fixing this issue. Addresses Issue [#953](https://github.com/mikebrady/shairport-sync/issues/953) and [Forked Daapd Issue #870](https://github.com/ejurgensen/forked-daapd/issues/870).
+* Fix an incompatibilty with Forked Daapd that was causing Forked Daapd to lock up. Thanks to [@tomgadow](https://github.com/tomgadow) and [@ejurgensen](https://github.com/ejurgensen) for their help in finding and (hopefully) fixing this issue. Addresses issue [#953](https://github.com/mikebrady/shairport-sync/issues/953) and [Forked Daapd Issue #870](https://github.com/ejurgensen/forked-daapd/issues/870).
**Enhancements**
* Add some extra examples of using the RemoteControl interface's SetAirplayVolume feature to the shairport-sync-dbus-test-client source.
From 0c3ec38c97768a8770cb01c9a044c90a4c1876b4 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 18 Feb 2020 14:07:59 +0000
Subject: [PATCH 031/186] Add the -fno-common flag to the compiler
---
Makefile.am | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 2bbc387c..c9ccb75e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,8 +18,8 @@ if BUILD_FOR_OPENBSD
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
else
- AM_CXXFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
- AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
+ AM_CXXFLAGS = -fno-common -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
+ AM_CFLAGS = -fno-common -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
endif
endif
From 789ba1ee5e8ab17b70bd2419a08c27ce6fa604fb Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 18 Feb 2020 14:09:43 +0000
Subject: [PATCH 032/186] Treat the "mper" attribute as the 64-bit item that it
is rather than a 32-bit item as hithereto. Output it as a hexadecimal number
to correspond with the format of the track id obtained from AppleScript
---
dbus-service.c | 5 +++--
metadata_hub.c | 42 ++++++++++++++++++++++++------------------
metadata_hub.h | 2 +-
mpris-service.c | 4 ++--
4 files changed, 30 insertions(+), 23 deletions(-)
diff --git a/dbus-service.c b/dbus-service.c
index 1fe59c35..457aefed 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include "config.h"
@@ -204,7 +205,7 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
// Add in the Track ID based on the 'mper' metadata if it is non-zero
if (argc->item_id != 0) {
char trackidstring[128];
- snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
+ snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "",
argc->item_id);
GVariant *trackid = g_variant_new("o", trackidstring);
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
@@ -517,7 +518,7 @@ gboolean notify_convolution_impulse_response_file_callback(ShairportSync *skelet
#else
gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused)) ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
- char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
+ __attribute__((unused)) char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
return TRUE;
}
#endif
diff --git a/metadata_hub.c b/metadata_hub.c
index c09759f2..d52241a0 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -5,7 +5,7 @@
* then you need a metadata hub,
* where everything is stored
* This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2017--2019
+ * Copyright (c) Mike Brady 2017--2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -38,6 +38,7 @@
#include
#include
#include
+#include
#include "config.h"
@@ -303,28 +304,33 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
// all the following items of metadata are contained in one metadata packet
// they are preceded by an 'ssnc' 'mdst' item and followed by an 'ssnc 'mden' item.
- uint32_t ui;
char *cs;
int changed = 0;
if (type == 'core') {
switch (code) {
- case 'mper':
- ui = ntohl(*(uint32_t *)data);
- debug(2, "MH Item ID seen: \"%u\" of length %u.", ui, length);
- if (ui != metadata_store.item_id) {
- metadata_store.item_id = ui;
- metadata_store.item_id_changed = 1;
- metadata_store.item_id_received = 1;
- debug(2, "MH Item ID set to: \"%u\"", metadata_store.item_id);
- }
+ case 'mper': {
+ // get the 64-bit number as a uint64_t by reading two uint32_t s and combining them
+ uint64_t vl = ntohl(*(uint32_t*)data); // get the high order 32 bits
+ vl = vl << 32; // shift them into the correct location
+ uint64_t ul = ntohl(*(uint32_t*)(data+sizeof(uint32_t))); // and the low order 32 bits
+ vl = vl + ul;
+ debug(2, "MH Item ID seen: \"%" PRIx64 "\" of length %u.", vl, length);
+ if (vl != metadata_store.item_id) {
+ metadata_store.item_id = vl;
+ metadata_store.item_id_changed = 1;
+ metadata_store.item_id_received = 1;
+ debug(2, "MH Item ID set to: \"%" PRIx64 "\"", metadata_store.item_id);
+ }
+ }
break;
- case 'astm':
- ui = ntohl(*(uint32_t *)data);
- debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
- if (ui != metadata_store.songtime_in_milliseconds) {
- metadata_store.songtime_in_milliseconds = ui;
- metadata_store.songtime_in_milliseconds_changed = 1;
- debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
+ case 'astm': {
+ uint32_t ui = ntohl(*(uint32_t *)data);
+ debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
+ if (ui != metadata_store.songtime_in_milliseconds) {
+ metadata_store.songtime_in_milliseconds = ui;
+ metadata_store.songtime_in_milliseconds_changed = 1;
+ debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
+ }
}
break;
case 'asal':
diff --git a/metadata_hub.h b/metadata_hub.h
index e7e6988a..75d2f852 100644
--- a/metadata_hub.h
+++ b/metadata_hub.h
@@ -68,7 +68,7 @@ typedef struct metadata_bundle {
char *cover_art_pathname;
int cover_art_pathname_changed;
- uint32_t item_id; // seems to be a track ID -- see itemid in DACP.c
+ uint64_t item_id; // seems to be a track ID -- see itemid in DACP.c
int item_id_changed;
int item_id_received; // important for deciding if the track information should be ignored.
diff --git a/mpris-service.c b/mpris-service.c
index 76a04c76..125398a1 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -25,6 +25,7 @@
*/
#include
#include
+#include
#include "config.h"
@@ -174,8 +175,7 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
// Add in the Track ID based on the 'mper' metadata if it is non-zero
if (argc->item_id != 0) {
char trackidstring[128];
- snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
- argc->item_id);
+ snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "", argc->item_id);
GVariant *trackid = g_variant_new("o", trackidstring);
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
}
From a3cf72c8ced25801028fdc5ccf6e2dc948e566db Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 18 Feb 2020 14:11:32 +0000
Subject: [PATCH 033/186] Add the -fno-common flag to compilation. Treat the
"mper" attribute correctly as a 64 bit item and output it in hex to
correspond with AppleScript.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 7e0d7407..d6fa5ef0 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.3.6d7], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d8], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From a1cf11317144bc56170a3ba9999441aef92be62d Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 18 Feb 2020 15:03:14 +0000
Subject: [PATCH 034/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 3ecb0cbd..1796eefb 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,7 +1,24 @@
Version 3.3.6d7
====
**Bug Fix**
-* Make Shairport Sync compile in the forthcoming Fedora 32. Fedora 32 uses GCC-10 which [defaults to `-fno-common`](https://gcc.gnu.org/gcc-10/porting_to.html) exposing a number of issues with Shairport Sync – multiple definitions of some `enum`s and failure to define certain variables as `extern`.
+* Treat the `mper` (Persistent ID of a track) metadata attribute as the 64-bit item that it really is rather than a 32-bit item as hithereto. Output it as a hexadecimal number on the MPRIS and D-Bus interfaces to correspond with the format of the persistent id obtained from AppleScript. E.g:
+
+```
+tell application "Music"
+ get the {persistent ID} of the current track
+end tell
+```
+resulting in:
+```
+{"FD84B4B40FA33A85"}
+```
+**Other Changes**
+* Include the `-fno-common` flag in the compilation options to detect incorrect definitions of global variables and multiple definitions of `enum` data types.
+
+Version 3.3.6d7
+====
+**Bug Fix**
+* Make Shairport Sync compile in the forthcoming Fedora 33. Fedora 33 uses GCC-10 which [defaults to `-fno-common`](https://gcc.gnu.org/gcc-10/porting_to.html) exposing a number of issues with Shairport Sync – multiple definitions of some `enum`s and failure to define certain variables as `extern`.
Version 3.3.6d7 is an attempt to clear up all these errors. Many thanks to [Bill Peck](https://github.com/p3ck) for his ongoing support, for bringing this issue to notice and for developing a workaround. Address issue [#973](https://github.com/mikebrady/shairport-sync/issues/973).
From 28c3c4252204c14c229276722f813ee762f22d76 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 18 Feb 2020 15:27:29 +0000
Subject: [PATCH 035/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 1796eefb..608f1432 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -12,6 +12,10 @@ resulting in:
```
{"FD84B4B40FA33A85"}
```
+The MPRIS `trackid` corresponding to this would be `/org/gnome/ShairportSync/FD84B4B40FA33A85`.
+
+Thanks to to Scott Simon (https://github.com/zutroy97) for finding a related bug in the [shairport-sync-metadata-reader](https://github.com/mikebrady/shairport-sync-metadata-reader).
+
**Other Changes**
* Include the `-fno-common` flag in the compilation options to detect incorrect definitions of global variables and multiple definitions of `enum` data types.
From 4c6b0e83c5399014066923aa182e5d4b91a64d5c Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 18 Feb 2020 16:25:28 +0000
Subject: [PATCH 036/186] Remove some unwanted and unused variable declarations
from audio_dummy.c and audio_soundio.c
---
audio_dummy.c | 6 ------
audio_soundio.c | 6 ------
2 files changed, 12 deletions(-)
diff --git a/audio_dummy.c b/audio_dummy.c
index 026ef78a..5d64ecf2 100644
--- a/audio_dummy.c
+++ b/audio_dummy.c
@@ -34,17 +34,11 @@
#include
#include
-int Fs;
-long long starttime, samples_played;
-
static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { return 0; }
static void deinit(void) {}
static void start(int sample_rate, __attribute__((unused)) int sample_format) {
- Fs = sample_rate;
- starttime = 0;
- samples_played = 0;
debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
}
diff --git a/audio_soundio.c b/audio_soundio.c
index 53de2acb..119f7670 100644
--- a/audio_soundio.c
+++ b/audio_soundio.c
@@ -7,9 +7,6 @@
#include
-int Fs;
-long long starttime, samples_played;
-
struct SoundIoOutStream *outstream;
struct SoundIo *soundio;
struct SoundIoDevice *device;
@@ -129,9 +126,6 @@ static void deinit(void) {
}
static void start(int sample_rate, int sample_format) {
- Fs = sample_rate;
- starttime = 0;
- samples_played = 0;
int err;
debug(1, "soundion rate: %d, format: %d", sample_rate, sample_format);
From 1d0464adbc2b966752ad70c7a9ce2f1b3094bfbb Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 18 Feb 2020 16:45:37 +0000
Subject: [PATCH 037/186] removbe an extra declaration of a variable called
"changed" which might allow it to be used uninitalised.
---
metadata_hub.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/metadata_hub.c b/metadata_hub.c
index d52241a0..f079ea2a 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -547,7 +547,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
case 'pffr': // this is sent when the first frame has been received
case 'prsm':
metadata_hub_modify_prolog();
- int changed = (metadata_store.player_state != PS_PLAYING);
+ changed = (metadata_store.player_state != PS_PLAYING);
metadata_store.player_state = PS_PLAYING;
metadata_hub_modify_epilog(changed);
break;
From f6ce0262a231ce8398716a00b86e98d75388ebab Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 09:34:17 +0000
Subject: [PATCH 038/186] fix compaison between old and new metadata string --
check for lenghts, huh.
---
common.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common.c b/common.c
index 3d41cb6c..1f56430e 100644
--- a/common.c
+++ b/common.c
@@ -1554,7 +1554,7 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t f
int string_update_with_size(char **str, int *flag, char *s, size_t len) {
if (*str) {
if ((s) && (len)) {
- if (strncmp(*str, s, len) != 0) {
+ if ((len == strlen(*str)) && (strncmp(*str, s, len) != 0)) {
free(*str);
*str = strndup(s, len);
*flag = 1;
From d237d72c9746e76351819e19aca57463dc9aef97 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 09:36:59 +0000
Subject: [PATCH 039/186] fix comparison between old and new metadata strings
by checking they have equal lengths as well. Remove a redundant definition to
prevent use on an uninitialised copy, clean up some of the less-used backends
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index d6fa5ef0..0d617d46 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.3.6d8], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d9], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 52da14a295ad7aceababe46a35d5dddbd51db617 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 10:28:19 +0000
Subject: [PATCH 040/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 608f1432..aa0b57d8 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,14 @@
+Version 3.3.6d9
+====
+**Bug Fixes**
+* Fix a bug in the provision of metadata which would on occasion cause metadata from the previous track to be provided. Thanks to [HiFiBerry](https://github.com/hifiberry) for reporting. This bugfix addresses issue [#972](https://github.com/mikebrady/shairport-sync/issues/972).
+
+Version 3.3.6d8
+====
+**Bug Fixes**
+* Tidy up compilation of the `audio-dummy` and `audio-soundio` back ends when the `fno-common` flag is used.
+* Remove a superflous extra definition of a variable which would casue it to be used uninitialised.
+
Version 3.3.6d7
====
**Bug Fix**
From 1e5adac012524a148fd5216f4a331cb63de7fa89 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 10:28:34 +0000
Subject: [PATCH 041/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index aa0b57d8..556301f6 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,6 +1,6 @@
Version 3.3.6d9
====
-**Bug Fixes**
+**Bug Fix**
* Fix a bug in the provision of metadata which would on occasion cause metadata from the previous track to be provided. Thanks to [HiFiBerry](https://github.com/hifiberry) for reporting. This bugfix addresses issue [#972](https://github.com/mikebrady/shairport-sync/issues/972).
Version 3.3.6d8
From 98e444857bca8020231eb0c4862cbfc44064fd20 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 10:28:54 +0000
Subject: [PATCH 042/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 556301f6..3577c2fe 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -7,7 +7,7 @@ Version 3.3.6d8
====
**Bug Fixes**
* Tidy up compilation of the `audio-dummy` and `audio-soundio` back ends when the `fno-common` flag is used.
-* Remove a superflous extra definition of a variable which would casue it to be used uninitialised.
+* Remove a superflous extra definition of a variable which would cause it to be used uninitialised.
Version 3.3.6d7
====
From 2cd77c690a55cfa6a65c4eedb607a97345b82e47 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 10:29:46 +0000
Subject: [PATCH 043/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 3577c2fe..e26385e2 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -6,7 +6,7 @@ Version 3.3.6d9
Version 3.3.6d8
====
**Bug Fixes**
-* Tidy up compilation of the `audio-dummy` and `audio-soundio` back ends when the `fno-common` flag is used.
+* Tidy up compilation of the `audio-dummy` and `audio-soundio` back ends when the `-fno-common` flag is used.
* Remove a superflous extra definition of a variable which would cause it to be used uninitialised.
Version 3.3.6d7
From d64a8d1e7576127df5999cd63e8f27c93598952d Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 10:43:01 +0000
Subject: [PATCH 044/186] Update FREEBSD.md
---
FREEBSD.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/FREEBSD.md b/FREEBSD.md
index 63e57685..057a96f2 100644
--- a/FREEBSD.md
+++ b/FREEBSD.md
@@ -60,7 +60,7 @@ Next, configure the build and compile it:
```
$ autoreconf -i -f
-./configure --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
+./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
$ make
```
Add `--with-alsa` if you wish to include the ALSA back end. Omit the `--with-sndio` if you don't want the `sndio` back end. Omit the `--with-freebsd-service` if you don't want to install a FreeBSD startup script, runtime folder and user and group -- see below for more details.
From 6c72e52a07b2f63579e72a59826072d82a212b0e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 14:34:29 +0000
Subject: [PATCH 045/186] Brown bag -- this is what 3.3.6d9 was supposed to do!
---
common.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common.c b/common.c
index 1f56430e..84b31793 100644
--- a/common.c
+++ b/common.c
@@ -1554,7 +1554,7 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t f
int string_update_with_size(char **str, int *flag, char *s, size_t len) {
if (*str) {
if ((s) && (len)) {
- if ((len == strlen(*str)) && (strncmp(*str, s, len) != 0)) {
+ if ((len != strlen(*str)) || (strncmp(*str, s, len) != 0)) {
free(*str);
*str = strndup(s, len);
*flag = 1;
From d017ac3948b45a2bb8a21ba3d446e622f6c62dbe Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 14:34:51 +0000
Subject: [PATCH 046/186] Brown bag -- this is what 3.3.6d9 was supposed to do!
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 0d617d46..2d9a0496 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.3.6d9], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d10], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 6c9177a77866fc583ea4e74b2f19873228f45492 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 14:35:50 +0000
Subject: [PATCH 047/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index e26385e2..51ee6516 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,8 @@
+Version 3.3.6d10
+====
+**Bug Fix**
+* Brown-bag fix -- do what 3.3.6d9 was supposed to do!
+
Version 3.3.6d9
====
**Bug Fix**
From 4fc161cf0c4d0bbf500b5887da5704a600d7ab5e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 19 Feb 2020 20:18:09 +0000
Subject: [PATCH 048/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 51ee6516..04d192c4 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -6,7 +6,7 @@ Version 3.3.6d10
Version 3.3.6d9
====
**Bug Fix**
-* Fix a bug in the provision of metadata which would on occasion cause metadata from the previous track to be provided. Thanks to [HiFiBerry](https://github.com/hifiberry) for reporting. This bugfix addresses issue [#972](https://github.com/mikebrady/shairport-sync/issues/972).
+* Fix a bug in the provision of metadata which would on occasion cause metadata from the previous track to be provided. Thanks to [Tuomas Hämäläinen](https://github.com/tuomashamalainen) and [HiFiBerry](https://github.com/hifiberry) for reporting. This bugfix addresses issue [#972](https://github.com/mikebrady/shairport-sync/issues/972).
Version 3.3.6d8
====
@@ -38,7 +38,7 @@ Thanks to to Scott Simon (https://github.com/zutroy97) for finding a related bug
Version 3.3.6d7
====
**Bug Fix**
-* Make Shairport Sync compile in the forthcoming Fedora 33. Fedora 33 uses GCC-10 which [defaults to `-fno-common`](https://gcc.gnu.org/gcc-10/porting_to.html) exposing a number of issues with Shairport Sync – multiple definitions of some `enum`s and failure to define certain variables as `extern`.
+* Make Shairport Sync compile in the forthcoming Fedora 32. Fedora 32 uses GCC-10 which [defaults to `-fno-common`](https://gcc.gnu.org/gcc-10/porting_to.html) exposing a number of issues with Shairport Sync – multiple definitions of some `enum`s and failure to define certain variables as `extern`.
Version 3.3.6d7 is an attempt to clear up all these errors. Many thanks to [Bill Peck](https://github.com/p3ck) for his ongoing support, for bringing this issue to notice and for developing a workaround. Address issue [#973](https://github.com/mikebrady/shairport-sync/issues/973).
From d673f274cbb0b9aa06132715cd587899948956be Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 20 Feb 2020 18:24:04 +0000
Subject: [PATCH 049/186] Don't use strndup as old versions of OpenWrt don't
seem to have it
---
common.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/common.c b/common.c
index 84b31793..228dfec9 100644
--- a/common.c
+++ b/common.c
@@ -1556,7 +1556,11 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
if ((s) && (len)) {
if ((len != strlen(*str)) || (strncmp(*str, s, len) != 0)) {
free(*str);
- *str = strndup(s, len);
+ //*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
+ char *p = malloc(len + 1);
+ memcpy(p,s,len);
+ p[len] = '\0';
+ *str = p;
*flag = 1;
} else {
*flag = 0;
@@ -1569,7 +1573,11 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
}
} else { // old string is NULL
if ((s) && (len)) {
- *str = strndup(s, len);
+ //*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
+ char *p = malloc(len + 1);
+ memcpy(p,s,len);
+ p[len] = '\0';
+ *str = p;
*flag = 1;
} else {
// old string is NULL and new string is NULL or length 0
From d6c6e8c1b7f68ada55e7b3098e3b8e67fb5fe90a Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 20 Feb 2020 18:24:32 +0000
Subject: [PATCH 050/186] Don't use strndup as old versions of OpenWrt don't
seem to have it
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 2d9a0496..19f60933 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.3.6d10], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6d11], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From db12e0a8e3608e3e1dd4cc25d5719c418307a5e1 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 20 Feb 2020 21:25:16 +0000
Subject: [PATCH 051/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 04d192c4..2c275823 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,8 @@
+Version 3.3.6d11
+====
+**Bug Fix**
+* Avoid using `strndup` as it seems older versions of OpenWrt do not recognise it.
+
Version 3.3.6d10
====
**Bug Fix**
From 20ca91d016f51789651e05f55e9f8762375e3f83 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 20 Feb 2020 22:26:04 +0000
Subject: [PATCH 052/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 2c275823..d0146e3d 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,4 +1,4 @@
-Version 3.3.6d11
+Version 3.3.6d11 = Version 3.3.6
====
**Bug Fix**
* Avoid using `strndup` as it seems older versions of OpenWrt do not recognise it.
From 528b5141ac40b59bc47b1a00474966547e2e4b3c Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 21 Feb 2020 13:36:07 +0000
Subject: [PATCH 053/186] Update FREEBSD.md
---
FREEBSD.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/FREEBSD.md b/FREEBSD.md
index 057a96f2..fe3a2a5e 100644
--- a/FREEBSD.md
+++ b/FREEBSD.md
@@ -60,7 +60,7 @@ Next, configure the build and compile it:
```
$ autoreconf -i -f
-./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
+$ ./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
$ make
```
Add `--with-alsa` if you wish to include the ALSA back end. Omit the `--with-sndio` if you don't want the `sndio` back end. Omit the `--with-freebsd-service` if you don't want to install a FreeBSD startup script, runtime folder and user and group -- see below for more details.
From ebb1d69ce0a1d42794752a2c4aeffe239847ccca Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 22 Feb 2020 18:24:57 +0000
Subject: [PATCH 054/186] Remove a redundant comparison
---
common.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common.c b/common.c
index 228dfec9..78f89f9c 100644
--- a/common.c
+++ b/common.c
@@ -101,7 +101,7 @@ const char *sps_format_description_string_array[] = {
"S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid"};
const char *sps_format_description_string(sps_format_t format) {
- if ((format >= SPS_FORMAT_UNKNOWN) && (format <= SPS_FORMAT_AUTO))
+ if (format <= SPS_FORMAT_AUTO)
return sps_format_description_string_array[format];
else
return sps_format_description_string_array[SPS_FORMAT_INVALID];
From 1f526938841e979526c8e7e2148c83d31aeb7afe Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 22 Feb 2020 18:25:14 +0000
Subject: [PATCH 055/186] This is 3.3.6 with some documentation updates and a
tiny edit
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 19f60933..c1e393f7 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.3.6d11], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.7d1], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 820ee1c9f2099ebb9aa8978859d221c56999b9e3 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 23 Feb 2020 11:46:07 +0000
Subject: [PATCH 056/186] Add a call to get the time in nanoseconds.
---
common.c | 40 ++++++++++++++++++++++++++++++++++++++++
common.h | 1 +
2 files changed, 41 insertions(+)
diff --git a/common.c b/common.c
index 78f89f9c..3bdf17de 100644
--- a/common.c
+++ b/common.c
@@ -1013,6 +1013,46 @@ uint64_t get_absolute_time_in_fp() {
return time_now_fp;
}
+uint64_t get_absolute_time_in_ns() {
+ uint64_t time_now_ns;
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ struct timespec tn;
+ // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
+ clock_gettime(CLOCK_MONOTONIC, &tn);
+ uint64_t tnnsec = tn.tv_sec;
+ tnnsec = tnnsec * 1000000000;
+ uint64_t tnjnsec = tn.tv_nsec;
+ time_now_ns = tnnsec + tnjnsec;
+#endif
+
+#ifdef COMPILE_FOR_OSX
+ uint64_t time_now_mach;
+ uint64_t elapsedNano;
+ static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
+
+ time_now_mach = mach_absolute_time();
+
+ // If this is the first time we've run, get the timebase.
+ // We can use denom == 0 to indicate that sTimebaseInfo is
+ // uninitialised because it makes no sense to have a zero
+ // denominator in a fraction.
+
+ if (sTimebaseInfo.denom == 0) {
+ debug(1, "Mac initialise timebase info.");
+ (void)mach_timebase_info(&sTimebaseInfo);
+ }
+
+ // Do the maths. We hope that the multiplication doesn't
+ // overflow; the price you pay for working in fixed point.
+
+ // this gives us nanoseconds
+ time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
+#endif
+
+ return time_now_ns;
+}
+
ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout) {
// timeout is in milliseconds
void *ibuf = (void *)buf;
diff --git a/common.h b/common.h
index 2c9c05bd..96d59b7d 100644
--- a/common.h
+++ b/common.h
@@ -350,6 +350,7 @@ double vol2attn(double vol, long max_db, long min_db);
// return a monolithic (always increasing) time in nanoseconds
uint64_t get_absolute_time_in_fp(void);
+uint64_t get_absolute_time_in_ns(void);
// time at startup for debugging timing
extern uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
From 67e9b1b6cd2efd23532a618ee7e9a0651f55ce4a Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 24 Feb 2020 19:40:44 +0000
Subject: [PATCH 057/186] partial move to using a nanosecond-based system
timer. More to come.
---
activity_monitor.c | 16 +++----
audio_alsa.c | 110 +++++++++++++++++++++------------------------
audio_jack.c | 8 ++--
common.c | 75 +++++++++++++++----------------
common.h | 2 +-
dacp.c | 7 ++-
player.c | 70 ++++++++++++++---------------
rtsp.c | 8 ++--
shairport.c | 16 +++----
9 files changed, 145 insertions(+), 167 deletions(-)
diff --git a/activity_monitor.c b/activity_monitor.c
index bcc25d24..ce7edc5f 100644
--- a/activity_monitor.c
+++ b/activity_monitor.c
@@ -196,21 +196,19 @@ void *activity_monitor_thread_code(void *arg) {
} else {
state = am_timing_out;
- uint64_t time_to_wait_for_wakeup_fp =
- (uint64_t)(config.active_state_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;
+ uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(config.active_state_timeout * 1000000000);
#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;
+ uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
+ sec = time_of_wakeup_ns / 1000000000;
+ nsec = time_of_wakeup_ns % 1000000000;
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;
+ sec = time_to_wait_for_wakeup_ns / 1000000000;
+ nsec = time_to_wait_for_wakeup_ns % 1000000000;
time_for_wait.tv_sec = sec;
time_for_wait.tv_nsec = nsec;
#endif
diff --git a/audio_alsa.c b/audio_alsa.c
index 39e4d051..40287909 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -210,9 +210,6 @@ int precision_delay_available() {
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);
// now we can get the delay, and we'll note if it uses update timestamps
@@ -255,8 +252,6 @@ static uint64_t frames_played_at_measurement_start_time;
static uint64_t measurement_time;
static uint64_t frames_played_at_measurement_time;
-volatile uint64_t most_recent_write_time;
-
static uint64_t frames_sent_for_playing;
static uint64_t frame_index;
static int measurement_data_is_valid;
@@ -1350,7 +1345,6 @@ static int init(int argc, char **argv) {
// length of the queue
// if the queue gets too short, stuff it with silence
- 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);
return response;
@@ -1605,6 +1599,7 @@ int delay(long *the_delay) {
}
int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) {
+ // elapsed_time is in nanoseconds
int response = 0; // zero means okay
if (measurement_data_is_valid) {
*elapsed_time = measurement_time - measurement_start_time;
@@ -1657,7 +1652,7 @@ int do_play(void *buf, int samples) {
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();
+ measurement_time = get_absolute_time_in_ns();
frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples;
if (frame_index == start_measurement_from_this_frame) {
@@ -1744,6 +1739,7 @@ int do_close() {
// 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));
+ usleep(5000);
if ((derr = snd_pcm_hw_free(alsa_handle)))
debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr));
@@ -1946,7 +1942,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
alsa_device_init();
alsa_device_initialised = 1;
}
- int sleep_time_ms = (int)(config.disable_standby_mode_silence_scan_interval * 1000);
+ int sleep_time_us = (int)(config.disable_standby_mode_silence_scan_interval * 1000000);
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)) {
@@ -1974,57 +1970,51 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
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, NULL);
- 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.disable_standby_mode_silence_threshold * config.output_rate);
- size_t size_of_silence_buffer;
- 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;
- int frames_of_silence = 1024;
- size_of_silence_buffer = frames_of_silence * frame_size;
- void *silence = malloc(size_of_silence_buffer);
- if (silence == NULL) {
- warn("disable_standby_mode has been turned off because a memory allocation error "
- "occurred.");
- error_detected = 1;
- } else {
- int ret;
- pthread_cleanup_push(malloc_cleanup, silence);
- int use_dither = 0;
- if ((alsa_mix_ctrl == NULL) && (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);
- ret = do_play(silence, frames_of_silence);
- frame_count++;
- pthread_cleanup_pop(1); // free malloced buffer
- if (ret < 0) {
- error_count++;
- char errorstring[1024];
- strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
- debug(2, "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
- "to alsa device -- %d errors in %d trials.",
- ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
- if ((error_count > 40) && (frame_count < 100)) {
- warn("disable_standby_mode has been turned off because too many underruns "
- "occurred. Is Shairport Sync outputting to a virtual device or running in a "
- "virtual machine?");
- error_detected = 1;
- }
+ reply = delay_and_status(&state, &buffer_size, NULL);
+ 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.disable_standby_mode_silence_threshold * config.output_rate);
+ size_t size_of_silence_buffer;
+ if (buffer_size < buffer_size_threshold) {
+ int frames_of_silence = 1024;
+ size_of_silence_buffer = frames_of_silence * frame_size;
+ void *silence = malloc(size_of_silence_buffer);
+ if (silence == NULL) {
+ warn("disable_standby_mode has been turned off because a memory allocation error "
+ "occurred.");
+ error_detected = 1;
+ } else {
+ int ret;
+ pthread_cleanup_push(malloc_cleanup, silence);
+ int use_dither = 0;
+ if ((alsa_mix_ctrl == NULL) && (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);
+ ret = do_play(silence, frames_of_silence);
+ frame_count++;
+ pthread_cleanup_pop(1); // free malloced buffer
+ if (ret < 0) {
+ error_count++;
+ char errorstring[1024];
+ strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
+ debug(2, "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
+ "to alsa device -- %d errors in %d trials.",
+ ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
+ if ((error_count > 40) && (frame_count < 100)) {
+ warn("disable_standby_mode has been turned off because too many underruns "
+ "occurred. Is Shairport Sync outputting to a virtual device or running in a "
+ "virtual machine?");
+ error_detected = 1;
}
}
}
@@ -2032,7 +2022,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
}
debug_mutex_unlock(&alsa_mutex, 0);
pthread_cleanup_pop(0); // release the mutex
- usleep(sleep_time_ms * 1000); // has a cancellation point in it
+ usleep(sleep_time_us); // has a cancellation point in it
}
pthread_exit(NULL);
}
diff --git a/audio_jack.c b/audio_jack.c
index db6e1899..5caa6c68 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -395,13 +395,13 @@ int jack_delay(long *the_delay) {
// occupancy after another transfer had occurred, so we could "lose" a full transfer
// (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds)
pthread_mutex_lock(&buffer_mutex);
- int64_t time_now = get_absolute_time_in_fp();
- int64_t delta = time_now - time_of_latest_transfer;
+ int64_t time_now = get_absolute_time_in_ns();
+ int64_t delta = time_now - time_of_latest_transfer; // nanoseconds
size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame;
debug(2, "audio_occupancy_now is %d.", audio_occupancy_now);
pthread_mutex_unlock(&buffer_mutex);
- int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) >> 32;
+ int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) / 1000000000;
// debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check);
// jack_latency is set by the graph() callback, it's the average of the maximum
// latencies of all our output ports. Adjust this constant baseline delay according
@@ -456,7 +456,7 @@ int play(void *buf, int samples) {
}
#endif
}
- time_of_latest_transfer = get_absolute_time_in_fp();
+ time_of_latest_transfer = get_absolute_time_in_ns();
pthread_mutex_unlock(&buffer_mutex);
if (samples) {
warn("JACK ringbuffer overrun. Dropped %d samples.", samples);
diff --git a/common.c b/common.c
index 3bdf17de..b3942f78 100644
--- a/common.c
+++ b/common.c
@@ -89,9 +89,9 @@ void set_alsa_out_dev(char *);
config_t config_file_stuff;
pthread_t main_thread_id;
-uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
+uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
-// always lock use this when accessing the fp_time_at_last_debug_message
+// always lock use this when accessing the ns_time_at_last_debug_message
static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t the_conn_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -218,14 +218,13 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *fatal error: ");
} else {
s = b;
@@ -247,14 +246,13 @@ void _warn(const char *filename, const int linenumber, const char *format, ...)
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *warning: ");
} else {
s = b;
@@ -275,14 +273,13 @@ void _debug(const char *filename, const int linenumber, int level, const char *f
char b[1024];
b[0] = 0;
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " ");
va_list args;
va_start(args, format);
@@ -300,14 +297,13 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " ");
} else {
s = b;
@@ -1133,7 +1129,9 @@ char *str_replace(const char *string, const char *substr, const char *replacemen
/* from http://burtleburtle.net/bob/rand/smallprng.html */
-// this is not thread-safe, so we need a mutex on it to use it properly// always lock use this when accessing the fp_time_at_last_debug_message
+// this is not thread-safe, so we need a mutex on it to use it properly.
+// always lock use this when accessing the fp_time_at_last_debug_message
+
pthread_mutex_t r64_mutex = PTHREAD_MUTEX_INITIALIZER;
// typedef uint64_t u8;
@@ -1241,18 +1239,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();
+ int64_t start_time = get_absolute_time_in_ns();
int r = pthread_mutex_timedlock(mutex, &timeoutTime);
- int64_t et = get_absolute_time_in_fp() - start_time;
+ int64_t et = get_absolute_time_in_ns() - start_time;
if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
- 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\".",
- (1.0 * et) / 1000000, dally_time, debugmessage);
+ (1.0 * et) / 1000000000, dally_time, debugmessage);
else
debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
@@ -1301,7 +1298,7 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
return pthread_mutex_lock(mutex);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- uint64_t time_at_start = get_absolute_time_in_fp();
+ uint64_t time_at_start = get_absolute_time_in_ns();
char dstring[1000];
memset(dstring, 0, sizeof(dstring));
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
@@ -1310,12 +1307,10 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel);
if (result == ETIMEDOUT) {
result = pthread_mutex_lock(mutex);
- uint64_t time_delay = get_absolute_time_in_fp() - time_at_start;
- uint64_t divisor = (uint64_t)1 << 32;
- double delay = 1.0 * time_delay / divisor;
+ uint64_t time_delay = get_absolute_time_in_ns() - time_at_start;
debug(debuglevel,
"mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.",
- mutexname, dstring, (1.0 * dally_time) / 1000000, delay);
+ mutexname, dstring, (1.0 * dally_time) / 1000000, 0.000000001 * time_delay);
}
pthread_setcancelstate(oldState, NULL);
return result;
diff --git a/common.h b/common.h
index 96d59b7d..92095387 100644
--- a/common.h
+++ b/common.h
@@ -353,7 +353,7 @@ uint64_t get_absolute_time_in_fp(void);
uint64_t get_absolute_time_in_ns(void);
// time at startup for debugging timing
-extern uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
+extern uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
diff --git a/dacp.c b/dacp.c
index f3111a83..4c36de34 100644
--- a/dacp.c
+++ b/dacp.c
@@ -201,7 +201,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();
+ uint64_t start_time = get_absolute_time_in_ns();
pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
// only do this one at a time -- not sure it is necessary, but better safe than sorry
@@ -352,10 +352,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
+ uint64_t et = get_absolute_time_in_ns() - start_time; // this will be in nanoseconds
debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
- (1.0 * et) / 1000000, response.code, command);
+ (1.0 * et) / 1000000000, response.code, command);
}
*body = response.body;
*bodysize = response.size;
diff --git a/player.c b/player.c
index 5876bd33..a034346c 100644
--- a/player.c
+++ b/player.c
@@ -124,7 +124,7 @@ static void ab_resync(rtsp_conn_info *conn) {
conn->audio_buffer[i].ready = 0;
conn->audio_buffer[i].resend_request_number = 0;
conn->audio_buffer[i].resend_time = 0; // this is either zero or the time the last resend was requested.
- conn->audio_buffer[i].initialisation_time = 0; // this is either the time the packet was received or the time it was noticed the packet was missing.
+ conn->audio_buffer[i].initialisation_time = 0; // this is either the time the packet was received or the time it was noticed the packet was missing.
conn->audio_buffer[i].sequence_number = 0;
}
conn->ab_synced = 0;
@@ -564,7 +564,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
int rc = pthread_cond_signal(&conn->flowcontrol);
if (rc)
debug(1, "Error signalling flowcontrol.");
-
+
// resend checks
{
uint64_t minimum_wait_time = (uint64_t)(config.resend_control_first_check_time * (uint64_t)0x100000000);
@@ -572,7 +572,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
uint64_t minimum_remaining_time = (uint64_t)((config.resend_control_last_check_time + config.audio_backend_buffer_desired_length)* (uint64_t)0x100000000);
uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)0x100000000);
latency_time = latency_time / (uint64_t)conn->input_rate;
-
+
int x; // this is the first frame to be checked
// if we detected a first empty frame before and if it's still in the buffer!
if ((first_possibly_missing_frame >= 0) && (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read, conn->ab_write, NULL))) {
@@ -580,9 +580,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
} else {
x = conn->ab_read;
}
-
+
first_possibly_missing_frame = -1; // has not been set
-
+
int missing_frame_run_count = 0;
int start_of_missing_frame_run = -1;
int number_of_missing_frames = 0;
@@ -604,12 +604,12 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
if (too_early)
check_buf->status |= 1<<3; // too early
else
- check_buf->status &= 0xFF-(1<<3); // not too early
+ check_buf->status &= 0xFF-(1<<3); // not too early
if (too_soon_after_last_request)
check_buf->status |= 1<<4; // too soon after last request
else
check_buf->status &= 0xFF-(1<<4); // not too soon after last request
-
+
if ((!too_soon_after_last_request) && (!too_late) && (!too_early)){
if (start_of_missing_frame_run == -1) {
start_of_missing_frame_run = x;
@@ -619,7 +619,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
}
check_buf->resend_time = time_now; // setting the time to now because we are definitely going to take action
check_buf->resend_request_number++;
- debug(3,"Frame %d is missing with ab_read of %u and ab_write of %u.", x, conn->ab_read, conn->ab_write);
+ debug(3,"Frame %d is missing with ab_read of %u and ab_write of %u.", x, conn->ab_read, conn->ab_write);
}
// if (too_late) {
// debug(1,"too late to get missing frame %u.", x);
@@ -631,13 +631,13 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) {
// send a resend request
if (missing_frame_run_count > 1)
- debug(2,"request resend of %d packets starting at seqno %u.", missing_frame_run_count, start_of_missing_frame_run);
+ debug(2,"request resend of %d packets starting at seqno %u.", missing_frame_run_count, start_of_missing_frame_run);
if (config.disable_resend_requests == 0) {
debug_mutex_unlock(&conn->ab_mutex, 3);
rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn);
debug_mutex_lock(&conn->ab_mutex, 20000, 1);
conn->resend_requests++;
- }
+ }
start_of_missing_frame_run = -1;
missing_frame_run_count = 0;
}
@@ -1454,7 +1454,7 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, sps_format_t l_outp
int32_t stat_n = 0;
double stat_mean = 0.0;
double stat_M2 = 0.0;
-double longest_soxr_execution_time_us = 0.0;
+double longest_soxr_execution_time = 0.0;
int64_t packets_processed = 0;
int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
@@ -1482,7 +1482,7 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
size_t odone;
- uint64_t soxr_start_time = get_absolute_time_in_fp();
+ uint64_t soxr_start_time = get_absolute_time_in_ns();
soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, // Rates and # of chans.
inptr, length, NULL, // Input.
@@ -1499,15 +1499,15 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
// mean and variance calculations from "online_variance" algorithm at
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
- double soxr_execution_time_us =
- (((get_absolute_time_in_fp() - soxr_start_time) * 1000000) >> 32) * 1.0;
+ double soxr_execution_time =
+ (get_absolute_time_in_ns() - soxr_start_time) * 0.000000001;
// debug(1,"soxr_execution_time_us: %10.1f",soxr_execution_time_us);
- if (soxr_execution_time_us > longest_soxr_execution_time_us)
- longest_soxr_execution_time_us = soxr_execution_time_us;
+ if (soxr_execution_time > longest_soxr_execution_time)
+ longest_soxr_execution_time = soxr_execution_time;
stat_n += 1;
- double stat_delta = soxr_execution_time_us - stat_mean;
+ double stat_delta = soxr_execution_time - stat_mean;
stat_mean += stat_delta / stat_n;
- stat_M2 += stat_delta * (soxr_execution_time_us - stat_mean);
+ stat_M2 += stat_delta * (soxr_execution_time - stat_mean);
int i;
int32_t *ip, *op;
@@ -1553,15 +1553,15 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
}
if (packets_processed % 1250 == 0) {
- debug(3, "soxr_oneshot execution time in microseconds: mean, standard deviation and max "
+ debug(3, "soxr_oneshot execution time in nanoseconds: mean, standard deviation and max "
"for %" PRId32 " interpolations in the last "
- "1250 packets. %10.1f, %10.1f, %10.1f.",
+ "1250 packets. %10.6f, %10.6f, %10.6f.",
stat_n, stat_mean, stat_n <= 1 ? 0.0 : sqrtf(stat_M2 / (stat_n - 1)),
- longest_soxr_execution_time_us);
+ longest_soxr_execution_time);
stat_n = 0;
stat_mean = 0.0;
stat_M2 = 0.0;
- longest_soxr_execution_time_us = 0.0;
+ longest_soxr_execution_time = 0.0;
}
conn->amountStuffed = tstuff;
@@ -2365,11 +2365,11 @@ void *player_thread_func(void *arg) {
amount_to_stuff = 0; // no stuffing if it's been disabled
// Apply DSP here
-
+
// check the state of loudness and convolution flags here and don't change them for the frame
-
+
int do_loudness = config.loudness;
-
+
#ifdef CONFIG_CONVOLUTION
int do_convolution = 0;
@@ -2377,12 +2377,12 @@ void *player_thread_func(void *arg) {
do_convolution = 1;
// we will apply the convolution gain if convolution is enabled, even if there is no valid convolution happening
-
+
int convolution_is_enabled = 0;
if (config.convolution)
convolution_is_enabled = 1;
#endif
-
+
if (do_loudness
#ifdef CONFIG_CONVOLUTION
|| convolution_is_enabled
@@ -2533,7 +2533,7 @@ void *player_thread_func(void *arg) {
// update the watchdog
if ((config.dont_check_timeout == 0) && (config.timeout != 0)) {
- uint64_t time_now = get_absolute_time_in_fp();
+ uint64_t time_now = get_absolute_time_in_ns();
debug_mutex_lock(&conn->watchdog_mutex, 1000, 0);
conn->watchdog_bark_time = time_now;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
@@ -2620,12 +2620,8 @@ void *player_thread_func(void *arg) {
conn->frame_rate_status = 0;
if (conn->frame_rate_status) {
conn->frame_rate =
- (1.0 * frames_played) /
+ (1000000000.0 * frames_played) /
elapsed_play_time; // an IEEE double calculation with two 64-bit integers
- conn->frame_rate =
- conn->frame_rate * (uint64_t)0x100000000; // this should just change the [binary]
- // exponent in the IEEE FP representation;
- // the mantissa should be unaffected.
} else {
conn->frame_rate = 0.0;
}
@@ -2758,7 +2754,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
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;
audio_parameters audio_information;
@@ -2928,7 +2924,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
if (config.logOutputLevel) {
inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
}
-
+
#ifdef CONFIG_METADATA
// here, send the 'pvol' metadata message when the airplay volume information
// is being used by shairport sync to control the output volume
@@ -2938,7 +2934,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
if (volume_mode == vol_both) {
// normalise the maximum output to the hardware device's max output
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, (scaled_attenuation - max_db + hw_max_db) / 100.0,
- (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
+ (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
} else {
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
min_db / 100.0, max_db / 100.0);
@@ -2972,7 +2968,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
}
#endif
-
+
// here, store the volume for possible use in the future
config.airplay_volume = airplay_volume;
debug_mutex_unlock(&conn->volume_control_mutex, 3);
diff --git a/rtsp.c b/rtsp.c
index 6cf53927..dca0ecb5 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -286,7 +286,7 @@ void *player_watchdog_thread_code(void *arg) {
uint64_t last_watchdog_bark_time = conn->watchdog_bark_time;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
if (last_watchdog_bark_time != 0) {
- uint64_t time_since_last_bark = (get_absolute_time_in_fp() - last_watchdog_bark_time) >> 32;
+ uint64_t time_since_last_bark = (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000;
uint64_t ct = config.timeout; // go from int to 64-bit int
if (time_since_last_bark >= ct) {
@@ -666,7 +666,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
}
uint64_t threshold_time =
- get_absolute_time_in_fp() + ((uint64_t)15 << 32); // i.e. fifteen seconds from now
+ get_absolute_time_in_ns() + ((uint64_t)15000000000); // i.e. fifteen seconds from now
int warning_message_sent = 0;
const size_t max_read_chunk = 1024 * 1024 / 16;
@@ -679,7 +679,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
// metadata
if (warning_message_sent == 0) {
- uint64_t time_now = get_absolute_time_in_fp();
+ uint64_t time_now = get_absolute_time_in_ns();
if (time_now > threshold_time) { // it's taking too long
debug(1, "Error receiving metadata from source -- transmission seems "
"to be stalled.");
@@ -2319,7 +2319,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
rtsp_conn_info *conn = pconn;
// create the watchdog mutex, initialise the watchdog time and start the watchdog thread;
- conn->watchdog_bark_time = get_absolute_time_in_fp();
+ conn->watchdog_bark_time = get_absolute_time_in_ns();
pthread_mutex_init(&conn->watchdog_mutex, NULL);
pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn);
diff --git a/shairport.c b/shairport.c
index 5e375916..881ecd97 100644
--- a/shairport.c
+++ b/shairport.c
@@ -143,10 +143,10 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
int i;
int number_of_iterations = 0;
- uint64_t soxr_start_time = get_absolute_time_in_fp();
+ uint64_t soxr_start_time = get_absolute_time_in_ns();
uint64_t loop_until_time =
- (uint64_t)0x180000000 + soxr_start_time; // loop for a second and a half, max -- no need to be able to cancel it, do _don't even try_!
- while (get_absolute_time_in_fp() < loop_until_time) {
+ (uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be able to cancel it, do _don't even try_!
+ while (get_absolute_time_in_ns() < loop_until_time) {
number_of_iterations++;
for (i = 0; i < buffer_length; i++) {
@@ -184,11 +184,11 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
NULL, NULL); // Default configuration.
}
- double soxr_execution_time_us =
- (((get_absolute_time_in_fp() - soxr_start_time) * 1000000) >> 32) * 1.0;
+ double soxr_execution_time_ns =
+ (get_absolute_time_in_ns() - soxr_start_time) * 1.0;
// free(outbuffer);
// free(inbuffer);
- config.soxr_delay_index = (int)(0.9 + soxr_execution_time_us / (number_of_iterations * 1000));
+ config.soxr_delay_index = (int)(0.9 + soxr_execution_time_ns / (number_of_iterations * 1000000));
debug(2, "soxr_delay_index: %d.", config.soxr_delay_index);
if ((config.packet_stuffing == ST_soxr) &&
(config.soxr_delay_index > config.soxr_delay_threshold))
@@ -1393,8 +1393,8 @@ 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;
+ ns_time_at_startup = get_absolute_time_in_ns();
+ ns_time_at_last_debug_message = ns_time_at_startup;
// this is a bit weird, but necessary -- basename() may modify the argument passed in
char *basec = strdup(argv[0]);
char *bname = basename(basec);
From 32427423af836734c8997247e500c7bd942bf741 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 24 Feb 2020 19:58:35 +0000
Subject: [PATCH 058/186] update packet reception statistics to use nanosecond
timing
---
rtp.c | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/rtp.c b/rtp.c
index 6a90a5cd..6932420a 100644
--- a/rtp.c
+++ b/rtp.c
@@ -108,7 +108,7 @@ void *rtp_audio_receiver(void *arg) {
int32_t last_seqno = -1;
uint8_t packet[2048], *pktp;
- uint64_t time_of_previous_packet_fp = 0;
+ uint64_t time_of_previous_packet_ns = 0;
float longest_packet_time_interval_us = 0.0;
// mean and variance calculations from "online_variance" algorithm at
@@ -125,11 +125,11 @@ void *rtp_audio_receiver(void *arg) {
frame_count++;
- uint64_t local_time_now_fp = get_absolute_time_in_fp();
- if (time_of_previous_packet_fp) {
+ uint64_t local_time_now_ns = get_absolute_time_in_ns();
+ if (time_of_previous_packet_ns) {
float time_interval_us =
- (((local_time_now_fp - time_of_previous_packet_fp) * 1000000) >> 32) * 1.0;
- time_of_previous_packet_fp = local_time_now_fp;
+ (local_time_now_ns - time_of_previous_packet_ns) * 0.001;
+ time_of_previous_packet_ns = local_time_now_ns;
if (time_interval_us > longest_packet_time_interval_us)
longest_packet_time_interval_us = time_interval_us;
stat_n += 1;
@@ -143,11 +143,11 @@ void *rtp_audio_receiver(void *arg) {
stat_n = 0;
stat_mean = 0.0;
stat_M2 = 0.0;
- time_of_previous_packet_fp = 0;
+ time_of_previous_packet_ns = 0;
longest_packet_time_interval_us = 0.0;
}
} else {
- time_of_previous_packet_fp = local_time_now_fp;
+ time_of_previous_packet_ns = local_time_now_ns;
}
if (nread >= 0) {
@@ -270,8 +270,8 @@ void *rtp_control_receiver(void *arg) {
obfp += 2;
};
*obfp = 0;
-
-
+
+
// get raw timestamp information
// I think that a good way to understand these timestamps is that
// (1) the rtlt below is the timestamp of the frame that should be playing at the
@@ -282,19 +282,19 @@ void *rtp_control_receiver(void *arg) {
// Thus, (3) the latency can be calculated by subtracting the second from the
// first.
// There must be more to it -- there something missing.
-
+
// In addition, it seems that if the value of the short represented by the second
// pair of bytes in the packet is 7
// then an extra time lag is expected to be added, presumably by
// the AirPort Express.
-
+
// Best guess is that this delay is 11,025 frames.
-
+
uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
uint32_t rt = nctohl(&packet[16]); // raw timestamp
-
+
uint32_t fl = nctohs(&packet[2]); //
-
+
debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u,
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
//debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
From 40289ca6d38bc5969a049e307c5ad875349180f7 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 24 Feb 2020 22:23:08 +0000
Subject: [PATCH 059/186] functional with full removal of
get_absolute_time_fp() but not quite starting correctly
---
player.c | 63 ++++++++------------
player.h | 2 +-
rtp.c | 179 ++++++++++++++++++-------------------------------------
3 files changed, 85 insertions(+), 159 deletions(-)
diff --git a/player.c b/player.c
index a034346c..0e2a9956 100644
--- a/player.c
+++ b/player.c
@@ -478,7 +478,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
}
debug_mutex_lock(&conn->ab_mutex, 30000, 0);
- uint64_t time_now = get_absolute_time_in_fp();
+ uint64_t time_now = get_absolute_time_in_ns();
conn->packet_count++;
conn->packet_count_since_flush++;
conn->time_of_last_audio_packet = time_now;
@@ -567,10 +567,10 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
// resend checks
{
- uint64_t minimum_wait_time = (uint64_t)(config.resend_control_first_check_time * (uint64_t)0x100000000);
- uint64_t resend_repeat_interval = (uint64_t)(config.resend_control_check_interval_time * (uint64_t)0x100000000);
- uint64_t minimum_remaining_time = (uint64_t)((config.resend_control_last_check_time + config.audio_backend_buffer_desired_length)* (uint64_t)0x100000000);
- uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)0x100000000);
+ uint64_t minimum_wait_time = (uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000);
+ uint64_t resend_repeat_interval = (uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000);
+ uint64_t minimum_remaining_time = (uint64_t)((config.resend_control_last_check_time + config.audio_backend_buffer_desired_length)* (uint64_t)1000000000);
+ uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000);
latency_time = latency_time / (uint64_t)conn->input_rate;
int x; // this is the first frame to be checked
@@ -902,7 +902,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
(void *)conn); // undo what's been done so far
do {
// get the time
- local_time_now = get_absolute_time_in_fp(); // type okay
+ local_time_now = get_absolute_time_in_ns(); // type okay
// debug(3, "buffer_get_frame is iterating");
int rco = get_requested_connection_state_to_output();
@@ -1057,8 +1057,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (local_time_now > conn->first_packet_time_to_play) {
uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
- lateness = (lateness * 1000000) >> 32; // microseconds
- debug(3, "First packet is %" PRIu64 " microseconds late! Flushing 0.5 seconds",
+ debug(3, "First packet is %" PRIu64 " nanoseconds late! Flushing 0.5 seconds",
lateness);
do_flush(conn->first_packet_timestamp + 5 * 4410, conn);
}
@@ -1097,8 +1096,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (local_time_now > conn->first_packet_time_to_play) {
uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
- lateness = (lateness * 1000000) >> 32; // microseconds
- debug(3, "Gone past starting time by %" PRIu64 " microseconds.", lateness);
+ debug(3, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness);
have_sent_prefiller_silence = 1;
conn->ab_buffering = 0;
@@ -1123,7 +1121,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
int64_t lead_in_time = -1;
if (config.audio_backend_silent_lead_in_time >= 0)
lead_in_time =
- (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)0x100000000);
+ (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000);
// debug(1,"Lead time is %llx at fpttp
// %llx.",lead_time,conn->first_packet_time_to_play);
if ((lead_in_time < 0) || (lead_time <= lead_in_time)) {
@@ -1330,15 +1328,15 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
wait = (conn->ab_buffering || (do_wait != 0) || (!conn->ab_synced));
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 *= 2 * 352; // two full 352-frame packets
- time_to_wait_for_wakeup_fp /= 3; // two thirds of a packet time
+ uint64_t time_to_wait_for_wakeup_ns =
+ 1000000000 / conn->input_rate; // this is time period of one frame
+ time_to_wait_for_wakeup_ns *= 2 * 352; // two full 352-frame packets
+ time_to_wait_for_wakeup_ns /= 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;
- uint64_t sec = time_of_wakeup_fp >> 32;
- uint64_t nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ uint64_t time_of_wakeup_ns = local_time_now + time_to_wait_for_wakeup_ns;
+ uint64_t sec = time_of_wakeup_ns / 1000000000;
+ uint64_t nsec = time_of_wakeup_ns % 1000000000;
struct timespec time_of_wakeup;
time_of_wakeup.tv_sec = sec;
@@ -1350,8 +1348,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
debug(3, "pthread_cond_timedwait returned error code %d.", rc);
#endif
#ifdef COMPILE_FOR_OSX
- uint64_t sec = time_to_wait_for_wakeup_fp >> 32;
- uint64_t nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
+ uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
struct timespec time_to_wait;
time_to_wait.tv_sec = sec;
time_to_wait.tv_nsec = nsec;
@@ -2102,11 +2100,7 @@ void *player_thread_func(void *arg) {
rt = rt * conn->output_sample_ratio;
nt = nt * conn->output_sample_ratio;
- uint64_t local_time_now = get_absolute_time_in_fp(); // types okay
- // struct timespec tn;
- // clock_gettime(CLOCK_MONOTONIC,&tn);
- // uint64_t
- // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000;
+ uint64_t local_time_now = get_absolute_time_in_ns(); // types okay
int64_t td = 0; // td is the time difference between the reference timestamp time and the
// present time. Only used to calculate td_in_frames
@@ -2117,14 +2111,13 @@ void *player_thread_func(void *arg) {
td = local_time_now - reference_timestamp_time; // this is the positive value.
// Conversion is positive uint64_t to
// int64_t, thus okay
- td_in_frames = (td * config.output_rate) >> 32;
+ td_in_frames = (td * config.output_rate) / 1000000000;
} else {
td = reference_timestamp_time - local_time_now; // this is the absolute value, which
// should be negated. Conversion is
// positive uint64_t to int64_t, thus
// okay.
- td_in_frames = (td * config.output_rate) >>
- 32; // use the absolute td value for the present. Types okay
+ td_in_frames = (td * config.output_rate) / 1000000000 ; // use the absolute td value for the present. Types okay
td_in_frames = -td_in_frames;
td = -td; // should be okay, as the range of values should be very small w.r.t 64 bits
}
@@ -2346,8 +2339,7 @@ void *player_thread_func(void *arg) {
if ((local_time_now) && (conn->first_packet_time_to_play) &&
(local_time_now >= conn->first_packet_time_to_play)) {
- int64_t tp = (local_time_now - conn->first_packet_time_to_play) >>
- 32; // seconds int64_t from uint64_t which is always positive, so ok
+ int64_t tp = (local_time_now - conn->first_packet_time_to_play) / 1000000000; // seconds int64_t from uint64_t which is always positive, so ok
if (tp < 5)
amount_to_stuff = 0; // wait at least five seconds
@@ -2601,14 +2593,9 @@ void *player_thread_func(void *arg) {
frames_received = conn->frames_inward_frames_received_at_measurement_time -
conn->frames_inward_frames_received_at_measurement_start_time;
conn->input_frame_rate =
- (1.0 * frames_received) /
+ (1.0E9 * frames_received) /
elapsed_reception_time; // an IEEE double calculation with two 64-bit integers
- conn->input_frame_rate =
- conn->input_frame_rate * (uint64_t)0x100000000; // this should just change the
- // [binary] exponent in the IEEE FP
- // representation; the mantissa
- // should be unaffected.
- } else {
+ } else {
conn->input_frame_rate = 0.0;
}
@@ -2620,7 +2607,7 @@ void *player_thread_func(void *arg) {
conn->frame_rate_status = 0;
if (conn->frame_rate_status) {
conn->frame_rate =
- (1000000000.0 * frames_played) /
+ (1.0E9 * frames_played) /
elapsed_play_time; // an IEEE double calculation with two 64-bit integers
} else {
conn->frame_rate = 0.0;
diff --git a/player.h b/player.h
index b6cae8a9..3845ca9f 100644
--- a/player.h
+++ b/player.h
@@ -177,7 +177,7 @@ typedef struct {
// RTP stuff
// only one RTP session can be active at a time.
int rtp_running;
- uint64_t rtp_time_of_last_resend_request_error_fp;
+ uint64_t rtp_time_of_last_resend_request_error_ns;
char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it
diff --git a/rtp.c b/rtp.c
index 6932420a..70a54a36 100644
--- a/rtp.c
+++ b/rtp.c
@@ -49,7 +49,7 @@ uint64_t local_to_remote_time_jitter;
uint64_t local_to_remote_time_jitter_count;
void rtp_initialise(rtsp_conn_info *conn) {
- conn->rtp_time_of_last_resend_request_error_fp = 0;
+ conn->rtp_time_of_last_resend_request_error_ns = 0;
conn->rtp_running = 0;
// initialise the timer mutex
int rc = pthread_mutex_init(&conn->reference_time_mutex, NULL);
@@ -69,32 +69,16 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) {
// this is an attempt to compensate for clock drift since the last time ping that was used
// so, if we have a non-zero clock drift, we will calculate the drift there would
// be from the time of the last time ping
- uint64_t local_time_now_fp = get_absolute_time_in_fp();
uint64_t time_since_last_local_to_remote_time_difference_measurement =
- local_time_now_fp - conn->local_to_remote_time_difference_measurement_time;
+ get_absolute_time_in_ns() - conn->local_to_remote_time_difference_measurement_time;
- uint64_t remote_time_since_last_local_to_remote_time_difference_measurement =
- (uint64_t)(conn->local_to_remote_time_gradient *
- time_since_last_local_to_remote_time_difference_measurement);
-
- double drift;
- if (remote_time_since_last_local_to_remote_time_difference_measurement >=
- time_since_last_local_to_remote_time_difference_measurement)
- drift = (1.0 * (remote_time_since_last_local_to_remote_time_difference_measurement -
- time_since_last_local_to_remote_time_difference_measurement)) /
- (uint64_t)0x100000000;
- else
- drift = -((1.0 * (time_since_last_local_to_remote_time_difference_measurement -
- remote_time_since_last_local_to_remote_time_difference_measurement)) /
- (uint64_t)0x100000000);
-
- // double interval_ms =
- // 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32);
- // debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f
- // milliseconds with drift of %.2f
- // ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000);
- // return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000));
- return conn->local_to_remote_time_difference + (uint64_t)(drift * (uint64_t)0x100000000);
+ uint64_t result = conn->local_to_remote_time_difference;
+ if (conn->local_to_remote_time_gradient >= 1.0) {
+ result = conn->local_to_remote_time_difference + (uint64_t)((conn->local_to_remote_time_gradient - 1.0) * time_since_last_local_to_remote_time_difference_measurement);
+ } else {
+ result = conn->local_to_remote_time_difference - (uint64_t)((1.0 - conn->local_to_remote_time_gradient) * time_since_last_local_to_remote_time_difference_measurement);
+ }
+ return result;
}
void rtp_audio_receiver_cleanup_handler(__attribute__((unused)) void *arg) {
@@ -248,9 +232,6 @@ void *rtp_control_receiver(void *arg) {
ssize_t nread;
while (1) {
nread = recv(conn->control_socket, packet, sizeof(packet), 0);
- // local_time_now = get_absolute_time_in_fp();
- // clock_gettime(CLOCK_MONOTONIC,&tn);
- // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000;
if (nread >= 0) {
@@ -303,11 +284,16 @@ void *rtp_control_receiver(void *arg) {
*/
if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged
// first...
+ uint64_t ps, pn;
- remote_time_of_sync = (uint64_t)nctohl(&packet[8]) << 32;
- remote_time_of_sync += nctohl(&packet[12]);
+ ps = nctohl(&packet[8]);
+ ps = ps * 1000000000; // this many nanoseconds from the whole seconds
+ pn = nctohl(&packet[12]);
+ pn = pn * 1000000000;
+ pn = pn >> 32; // this many nanoseconds from the fractional part
+ remote_time_of_sync = ps + pn;
- // debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync);
+ // debug(1,"Remote Sync Time: " PRIu64 "",remote_time_of_sync);
sync_rtp_timestamp = nctohl(&packet[16]);
uint32_t rtp_timestamp_less_latency = nctohl(&packet[4]);
@@ -390,15 +376,8 @@ void *rtp_control_receiver(void *arg) {
conn->initial_reference_time; // here, this should never be zero
if (remote_frame_time_interval) {
conn->remote_frame_rate =
- (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) /
- remote_frame_time_interval; // an IEEE double calculation with a 32-bit
- // numerator and 64-bit denominator
- // integers
- conn->remote_frame_rate =
- conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the
- // [binary] exponent in the IEEE
- // FP representation; the
- // mantissa should be unaffected.
+ (1.0E9 * (conn->reference_timestamp - conn->initial_reference_timestamp)) /
+ remote_frame_time_interval;
} else {
conn->remote_frame_rate = 0.0; // use as a flag.
}
@@ -423,28 +402,6 @@ void *rtp_control_receiver(void *arg) {
else
conn->reference_to_previous_frame_difference =
sync_rtp_timestamp - old_reference_timestamp;
-
- // int64_t delayed_frame_difference = rtp_timestamp_less_latency -
- // old_latency_delayed_timestamp;
-
- /*
- if (old_remote_reference_time)
- debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %"
- PRId64 " and %" PRId64 ", giving rates _at source!!_ of %f and %f respectively.",
- (conn->reference_to_previous_time_difference*1000000)>>32,conn->reference_to_previous_frame_difference,delayed_frame_difference,
- (1.0*(conn->reference_to_previous_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32));
- else
- debug(1,"First sync received");
- */
-
- // debug(1,"New Reference timestamp and timestamp time...");
- // get estimated remote time now
- // remote_time_now = local_time_now + local_to_remote_time_difference;
-
- // debug(1,"Sync Time is %lld us late (remote
- // times).",((remote_time_now-remote_time_of_sync)*1000000)>>32);
- // debug(1,"Sync Time is %lld us late (local
- // times).",((local_time_now-reference_timestamp_time)*1000000)>>32);
} else {
debug(2, "Sync packet received before we got a timing packet back.");
}
@@ -520,8 +477,7 @@ void *rtp_timing_sender(void *arg) {
req.filler = 0;
req.origin = req.receive = req.transmit = 0;
- // clock_gettime(CLOCK_MONOTONIC,&dtt);
- conn->departure_time = get_absolute_time_in_fp();
+ conn->departure_time = get_absolute_time_in_ns();
socklen_t msgsize = sizeof(struct sockaddr_in);
#ifdef AF_INET6
if (conn->rtp_client_timing_socket.SAFAMILY == AF_INET6) {
@@ -596,45 +552,39 @@ void *rtp_timing_receiver(void *arg) {
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
(drand48() > config.diagnostic_drop_packet_fraction)) {
- arrival_time = get_absolute_time_in_fp();
+ arrival_time = get_absolute_time_in_ns();
// ssize_t plen = nread;
// debug(1,"Packet Received on Timing Port.");
if (packet[1] == 0xd3) { // timing reply
- /*
- char obf[4096];
- char *obfp = obf;
- int obfc;
- for (obfc=0;obfcdeparture_time;
- // uint64_t rtus = (return_time * 1000000) >> 32;
+ if (return_time < 300000000) { // must be less than 0.3 seconds
- if (((return_time * 1000000) >> 32) < 300000) {
-
- // debug(2,"Synchronisation ping return time is %f milliseconds.",(rtus*1.0)/1000);
+ // debug(1,"Synchronisation ping return time is %" PRIu64 " nanoseconds.",return_time);
// distant_receive_time =
// ((uint64_t)ntohl(*((uint32_t*)&packet[16])))<<32+ntohl(*((uint32_t*)&packet[20]));
- distant_receive_time = (uint64_t)nctohl(&packet[16]) << 32;
- distant_receive_time += nctohl(&packet[20]);
+ uint64_t ps, pn;
+
+ ps = nctohl(&packet[16]);
+ ps = ps * 1000000000; // this many nanoseconds from the whole seconds
+ pn = nctohl(&packet[20]);
+ pn = pn * 1000000000;
+ pn = pn >> 32; // this many nanoseconds from the fractional part
+ distant_receive_time = ps + pn;
// distant_transmit_time =
// ((uint64_t)ntohl(*((uint32_t*)&packet[24])))<<32+ntohl(*((uint32_t*)&packet[28]));
- distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32;
- distant_transmit_time += nctohl(&packet[28]);
+ ps = nctohl(&packet[24]);
+ ps = ps * 1000000000; // this many nanoseconds from the whole seconds
+ pn = nctohl(&packet[28]);
+ pn = pn * 1000000000;
+ pn = pn >> 32; // this many nanoseconds from the fractional part
+ distant_transmit_time = ps + pn;
uint64_t remote_processing_time = 0;
@@ -644,8 +594,8 @@ void *rtp_timing_receiver(void *arg) {
debug(1, "Yikes: distant_transmit_time is before distant_receive_time; remote "
"processing time set to zero.");
}
- // debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 "
- // uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32);
+ // debug(1,"Return trip time: %" PRIu64 " nS, remote processing time: %" PRIu64 "
+ // nS.",return_time, remote_processing_time);
uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2;
@@ -685,14 +635,13 @@ void *rtp_timing_receiver(void *arg) {
// mean and variance calculations from "online_variance" algorithm at
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
- double rtfus = 1.0 * ((return_time * 1000000) >> 32);
stat_n += 1;
- double stat_delta = rtfus - stat_mean;
+ double stat_delta = return_time - stat_mean;
stat_mean += stat_delta / stat_n;
- stat_M2 += stat_delta * (rtfus - stat_mean);
+ stat_M2 += stat_delta * (return_time - stat_mean);
// debug(1, "Timing packet return time stats: current, mean and standard deviation over
- // %d packets: %.1f, %.1f, %.1f (microseconds).",
- // stat_n,rtfus,stat_mean, sqrtf(stat_M2 / (stat_n - 1)));
+ // %d packets: %.1f, %.1f, %.1f (nanoseconds).",
+ // stat_n,return_time,stat_mean, sqrtf(stat_M2 / (stat_n - 1)));
// here, pick the record with the least dispersion, and record that it's been chosen
@@ -742,16 +691,14 @@ void *rtp_timing_receiver(void *arg) {
ltd = -(conn->local_to_remote_time_difference_measurement_time-lt);
if (ltd) {
- debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji *
- (int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32);
+ debug(1,"Jitter: %" PRId64 " nanoseconds in %" PRId64 " nanoseconds.", ji, ltd);
debug(1,"Source clock to local clock drift: %.2f ppm.",((1.0*ji)/ltd)*1000000.0);
}
// uncomment below to print jitter between client's clock and our clock
if (ji) {
- int64_t rtus = (tld*1000000)>>32;
- debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an
- adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32);
+ debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " ns with an
+ adjustment of %" PRId64 " ns",chosen, tld, ji);
}
*/
conn->local_to_remote_time_difference =
@@ -830,8 +777,8 @@ void *rtp_timing_receiver(void *arg) {
// debug(1,"local to remote time gradient is %12.2f ppm, based on %d
// samples.",conn->local_to_remote_time_gradient*1000000,sample_count);
} else {
- debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.",
- (return_time * 1000000) >> 32);
+ debug(2, "Time ping turnaround time: " PRIu64 " ns -- it looks like a timing ping was lost.",
+ return_time);
}
} else {
debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread);
@@ -1039,9 +986,6 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport
conn->local_control_port, conn->local_timing_port);
conn->reference_timestamp = 0;
- // pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
- // pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
- // pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
conn->request_sent = 0;
conn->rtp_running = 1;
@@ -1061,9 +1005,6 @@ void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time
*remote_timestamp_time = conn->remote_reference_timestamp_time;
*timestamp_time =
conn->remote_reference_timestamp_time - local_to_remote_time_difference_now(conn);
- // if ((*timestamp == 0) && (*timestamp_time == 0)) {
- // debug(1,"Reference timestamp is invalid.");
- //}
debug_mutex_unlock(&conn->reference_time_mutex, 0);
}
@@ -1088,9 +1029,8 @@ const int use_nominal_rate = 0; // specify whether to use the nominal input rate
int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn) {
int result = 1;
uint32_t fs = conn->input_rate;
- *frames = fs;
- uint64_t one_fp = (uint64_t)(0x100000000); // one second in fp form
- *time = one_fp;
+ *frames = fs; // default value to return
+ *time = 1000000000; // default value to return
if ((conn->initial_reference_time) && (conn->initial_reference_timestamp)) {
// uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp;
uint32_t local_frames =
@@ -1101,7 +1041,7 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con
} else {
double calculated_frame_rate = conn->input_rate;
if (local_time)
- calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp;
+ calculated_frame_rate = (1.0E9 * local_frames) / local_time;
else
debug(1, "sanitised_source_rate_information: local_time is zero");
if ((local_time == 0) || ((calculated_frame_rate / conn->input_rate) > 1.002) ||
@@ -1173,7 +1113,7 @@ int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) {
// here, we calculate the time interval, in terms of remote time
uint64_t offset = modulo_64_offset(conn->remote_reference_timestamp_time, remote_time);
- int reference_time_was_earlier = (offset <= (uint64_t)0x100000000 * 3600);
+ int reference_time_was_earlier = (offset <= (uint64_t)3600000000000);
if (reference_time_was_earlier) // if we haven't had a reference within the last hour, it'll be
// taken as afterwards
time_interval = remote_time - conn->remote_reference_timestamp_time;
@@ -1217,11 +1157,10 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
msgsize = sizeof(struct sockaddr_in6);
}
#endif
- uint64_t time_of_sending_fp = get_absolute_time_in_fp();
- uint64_t resend_error_backoff_time = (uint64_t)1000000 * 0.3; // 0.3 seconds
- resend_error_backoff_time = (resend_error_backoff_time << 32) / 1000000;
- if ((conn->rtp_time_of_last_resend_request_error_fp == 0) ||
- ((time_of_sending_fp - conn->rtp_time_of_last_resend_request_error_fp) >
+ uint64_t time_of_sending_ns = get_absolute_time_in_ns();
+ uint64_t resend_error_backoff_time = 300000000; // 0.3 seconds
+ if ((conn->rtp_time_of_last_resend_request_error_ns == 0) ||
+ ((time_of_sending_ns - conn->rtp_time_of_last_resend_request_error_ns) >
resend_error_backoff_time)) {
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
(drand48() > config.diagnostic_drop_packet_fraction)) {
@@ -1241,9 +1180,9 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
strerror_r(errno, em, sizeof(em));
debug(2, "Error %d using sendto to request a resend: \"%s\".",
errno, em);
- conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
+ conn->rtp_time_of_last_resend_request_error_ns = time_of_sending_ns;
} else {
- conn->rtp_time_of_last_resend_request_error_fp = 0;
+ conn->rtp_time_of_last_resend_request_error_ns = 0;
}
} else {
@@ -1251,7 +1190,7 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
3,
"Dropping resend request packet to simulate a bad network. Backing off for 0.3 "
"second.");
- conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
+ conn->rtp_time_of_last_resend_request_error_ns = time_of_sending_ns;
}
} else {
debug(1, "Suppressing a resend request due to a resend sendto error in the last 0.3 seconds.");
From 8bd6d8d45f58b12818f1ba048898c840f40322ce Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 25 Feb 2020 14:44:13 +0000
Subject: [PATCH 060/186] Fix calculation of initial delay
---
player.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/player.c b/player.c
index 0e2a9956..483fbee8 100644
--- a/player.c
+++ b/player.c
@@ -1137,8 +1137,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
resp = config.output->delay(&dac_delay);
if (resp == 0) {
int64_t gross_frame_gap =
- ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >>
- 32;
+ ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) / 1000000000;
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
// debug(1,"Exact and gross frame gaps are %" PRId64 " and %" PRId64 " frames,
// and the dac delay is %ld.", exact_frame_gap, gross_frame_gap, dac_delay);
From c2655b0388b455ddce1f21b1c72f4ce3c17161d8 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 25 Feb 2020 15:53:56 +0000
Subject: [PATCH 061/186] Use nanosecond time in audio_sndio
---
audio_sndio.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/audio_sndio.c b/audio_sndio.c
index ac5cfa38..e7aaaf42 100644
--- a/audio_sndio.c
+++ b/audio_sndio.c
@@ -247,7 +247,7 @@ static void stop() {
}
static void onmove_cb(__attribute__((unused)) void *arg, int delta) {
- time_of_last_onmove_cb = get_absolute_time_in_fp();
+ time_of_last_onmove_cb = get_absolute_time_in_ns();
at_least_one_onmove_cb_seen = 1;
played += delta;
}
@@ -258,10 +258,14 @@ static int delay(long *_delay) {
if (at_least_one_onmove_cb_seen) { // when output starts, the onmove_cb callback will be made
// calculate the difference in time between now and when the last callback occurred,
// and use it to estimate the frames that would have been output
- uint64_t time_difference = get_absolute_time_in_fp() - time_of_last_onmove_cb;
- uint64_t frame_difference = time_difference * par.rate;
- uint64_t frame_difference_big_integer = frame_difference >> 32;
- estimated_extra_frames_output = frame_difference_big_integer;
+ uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb;
+ uint64_t frame_difference = (time_difference * par.rate) / 1000000000;
+ estimated_extra_frames_output = frame_difference;
+ // sanity check -- total estimate can not exceed frames written.
+ if ((estimated_extra_frames_output + played) > written/framesize) {
+ // debug(1,"play estimate fails sanity check, possibly due to running on a VM");
+ estimated_extra_frames_output = 0; // can't make any sensible guess
+ }
// debug(1,"Frames played to last cb: %d, estimated to current time:
// %d.",played,estimated_extra_frames_output);
}
From e1028b2c896f72a6d91f612187317dffb454c6e0 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 25 Feb 2020 15:56:53 +0000
Subject: [PATCH 062/186] Hide get_absolute_time_in_fp as it is now obselete
---
common.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common.h b/common.h
index 92095387..b9e9cb52 100644
--- a/common.h
+++ b/common.h
@@ -349,7 +349,7 @@ double flat_vol2attn(double vol, long max_db, long min_db);
double vol2attn(double vol, long max_db, long min_db);
// return a monolithic (always increasing) time in nanoseconds
-uint64_t get_absolute_time_in_fp(void);
+// uint64_t get_absolute_time_in_fp(void); // obselete
uint64_t get_absolute_time_in_ns(void);
// time at startup for debugging timing
From 514a21e911c812cf110abcc5bd6eff4e9fcd2cb2 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 25 Feb 2020 16:04:55 +0000
Subject: [PATCH 063/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index d0146e3d..e8c62920 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,15 @@
+Version 3.3.7d2
+====
+**Pesky Changes**
+* The underlying timing system has been moved from 64-bit fixed-point time representation (a là NTP) to a 64-bit unsigned nanoseconds representation. This should make precisely no difference to the functionality of Shairport Sync but the transition might behabve inadeventely introduced bugs. Problem reports gratefully received.
+
+**Enhancement**
+* The timing software in the `sndio` backend does some extra sanity checking on certain time estimates, it may help a little whne running on virtual machines.
+
+Version 3.3.7d1
+====
+This is 3.3.6 with some documentation updates.
+
Version 3.3.6d11 = Version 3.3.6
====
**Bug Fix**
From db34ec8d3cbc6ac39ec3d0d525d7fbe75ae6910e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 25 Feb 2020 16:13:39 +0000
Subject: [PATCH 064/186] Move from fixed-point 64-bit timing to unsigned
nanosecond timing
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index c1e393f7..1abafedc 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.3.7d1], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.7d2], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 218a4385f49b3a4cc0856bfccd28a050e86d7767 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 24 Mar 2020 15:57:16 +0000
Subject: [PATCH 065/186] Tidy up timed mutex messages.
---
common.c | 8 ++++----
common.h | 3 ---
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/common.c b/common.c
index b3942f78..2958cfe6 100644
--- a/common.c
+++ b/common.c
@@ -1247,9 +1247,9 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
char errstr[1000];
if (r == ETIMEDOUT)
debug(debuglevel,
- "timed out waiting for a mutex, having waiting %f seconds, with a maximum "
+ "timed out waiting for a mutex, having waited %f microseconds, with a maximum "
"waiting time of %d microseconds. \"%s\".",
- (1.0 * et) / 1000000000, dally_time, debugmessage);
+ (1.0E6 * et) / 1000000000, dally_time, debugmessage);
else
debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
@@ -1309,8 +1309,8 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
result = pthread_mutex_lock(mutex);
uint64_t time_delay = get_absolute_time_in_ns() - time_at_start;
debug(debuglevel,
- "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.",
- mutexname, dstring, (1.0 * dally_time) / 1000000, 0.000000001 * time_delay);
+ "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f microseconds.",
+ mutexname, dstring, (1.0 * dally_time), 0.001 * time_delay);
}
pthread_setcancelstate(oldState, NULL);
return result;
diff --git a/common.h b/common.h
index b9e9cb52..ba20078a 100644
--- a/common.h
+++ b/common.h
@@ -421,9 +421,6 @@ extern pthread_mutex_t r64_mutex;
char *get_version_string(); // mallocs a string space -- remember to free it afterwards
-void sps_nanosleep(const time_t sec,
- const long nanosec); // waits for this time, even through interruptions
-
int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t format,
int with_dither, int64_t random_number_in);
From a6acb6062afa51a6550d7dde872269000f80aaf7 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 25 Mar 2020 10:16:21 +0000
Subject: [PATCH 066/186] If the DACP port is zero, do a zeroconf re-broswe
every two seconds in case it has been missed.
---
common.h | 1 +
dacp.c | 753 ++++++++++++++++++++++++++++------------------------
rtsp.c | 1 +
shairport.c | 1 +
4 files changed, 404 insertions(+), 352 deletions(-)
diff --git a/common.h b/common.h
index ba20078a..95f4decc 100644
--- a/common.h
+++ b/common.h
@@ -100,6 +100,7 @@ typedef enum {
const char *sps_format_description_string(sps_format_t format);
typedef struct {
+ double missing_port_dacp_scan_interval_seconds; // if no DACP port number can be found, check at these intervals
double resend_control_first_check_time; // wait this long before asking for a missing packet to be resent
double resend_control_check_interval_time; // wait this long between making requests
double resend_control_last_check_time; // if the packet is missing this close to the time of use, give up
diff --git a/dacp.c b/dacp.c
index 4c36de34..dc794d4d 100644
--- a/dacp.c
+++ b/dacp.c
@@ -1,6 +1,6 @@
/*
* DACP protocol handler. This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2017 -- 2019
+ * Copyright (c) Mike Brady 2017 -- 2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -49,7 +49,7 @@
typedef struct {
int players_connection_thread_index; // the connection thread index when a player thread is
// associated with this, zero otherwise
- int scan_enable; // set to 1 if if sacanning should be considered
+ int scan_enable; // set to 1 if scanning should be considered
char dacp_id[256]; // the DACP ID string
uint16_t port; // zero if no port discovered
short connection_family; // AF_INET6 or AF_INET
@@ -123,7 +123,7 @@ static const struct http_funcs responseFuncs = {
// static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t dacp_conversation_lock;
static pthread_mutex_t dacp_server_information_lock;
-static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
+static pthread_cond_t dacp_server_information_cv;
void addrinfo_cleanup(void *arg) {
// debug(1, "addrinfo cleanup called.");
@@ -154,7 +154,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"dacp_send_command: command is: \"%s\".",command);
if (dacp_server.port == 0) {
- debug(1, "No DACP port specified yet");
+ debug(2, "No DACP port specified yet");
result = 490; // no port specified
} else {
@@ -169,6 +169,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// 493 Client failed to send a message
// 492 Argument out of range
// 491 Client refused connection
+ // 490 No port specified
struct addrinfo hints, *res;
int sockfd;
@@ -421,9 +422,6 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
dacp_server.always_use_revision_number_1 = 1;
}
-
- mdns_dacp_monitor_set_id(dacp_server.dacp_id);
-
metadata_hub_modify_prolog();
int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
metadata_store.dacp_server_active = dacp_server.scan_enable;
@@ -513,14 +511,40 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
"flag value of %d",
ch);
metadata_hub_modify_epilog(ch);
- while (dacp_server.scan_enable == 0) {
- // debug(1, "dacp_monitor_thread_code wait for an event to possible enable scan");
- pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
- // debug(1,"dacp_monitor_thread_code wake up.");
+
+ uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
+ uint64_t sec = time_of_wakeup_ns / 1000000000;
+ uint64_t nsec = time_of_wakeup_ns % 1000000000;
+#endif
+#ifdef COMPILE_FOR_OSX
+ uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
+ uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
+#endif
+
+ struct timespec time_to_wait;
+ time_to_wait.tv_sec = sec;
+ time_to_wait.tv_nsec = nsec;
+
+ while ((dacp_server.scan_enable == 0) && (result != ETIMEDOUT)) {
+ // debug(1, "dacp_monitor_thread_code wait for an event to possibly enable scan");
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ result = pthread_cond_timedwait(&dacp_server_information_cv, &dacp_server_information_lock,
+ &time_to_wait); // this is a pthread cancellation point
+ // debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %" PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);
+
+#endif
+#ifdef COMPILE_FOR_OSX
+ result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv, &dacp_server_information_lock, &time_to_wait);
+#endif
+ }
+ if (dacp_server.scan_enable == 1) {
+ bad_result_count = 0;
+ idle_scan_count = 0;
}
- // so dacp_server.scan_enable will be true at this point
- bad_result_count = 0;
- idle_scan_count = 0;
}
always_use_revision_number_1 = dacp_server.always_use_revision_number_1; // set this while access is locked
@@ -528,360 +552,367 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
result = dacp_get_volume(&the_volume); // just want the http code
pthread_cleanup_pop(1);
- scan_index++;
- // debug(1,"DACP Scan Result: %d.", result);
-
- if ((result == 200) || (result == 400)) {
- bad_result_count = 0;
+ if (result == 490) { // 490 means no port was specified
+ if ((dacp_server.dacp_id != NULL) && (strlen(dacp_server.dacp_id) != 0)) {
+ // debug(1,"mdns_dacp_monitor_set_id");
+ mdns_dacp_monitor_set_id(dacp_server.dacp_id);
+ }
} else {
- if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
- bad_result_count++;
- }
+ scan_index++;
+ // debug(1,"DACP Scan Result: %d.", result);
- // here, do the debouncing calculations to see
- // if the dacp server and the
- // advanced dacp server are available
+ if ((result == 200) || (result == 400)) {
+ bad_result_count = 0;
+ } else {
+ if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
+ bad_result_count++;
+ }
- // -1 means we don't know because some bad statuses have been reported
- // 0 means definitely no
- // +1 means definitely yes
+ // here, do the debouncing calculations to see
+ // if the dacp server and the
+ // advanced dacp server are available
- int dacp_server_status_now = -1;
- int advanced_dacp_server_status_now = -1;
+ // -1 means we don't know because some bad statuses have been reported
+ // 0 means definitely no
+ // +1 means definitely yes
- if (bad_result_count == 0) {
- dacp_server_status_now = 1;
- if (result == 200)
- advanced_dacp_server_status_now = 1;
- else if (result == 400)
+ int dacp_server_status_now = -1;
+ int advanced_dacp_server_status_now = -1;
+
+ if (bad_result_count == 0) {
+ dacp_server_status_now = 1;
+ if (result == 200)
+ advanced_dacp_server_status_now = 1;
+ else if (result == 400)
+ advanced_dacp_server_status_now = 0;
+ } else if (bad_result_count ==
+ config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
+ // then it's gone
+ dacp_server_status_now = 0;
advanced_dacp_server_status_now = 0;
- } else if (bad_result_count ==
- config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
- // then it's gone
- dacp_server_status_now = 0;
- advanced_dacp_server_status_now = 0;
- }
-
- if (metadata_store.player_thread_active == 0)
- idle_scan_count++;
- else
- idle_scan_count = 0;
-
- debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
- idle_scan_count);
-
- /* not used
- // decide if idle for too long
- if (idle_scan_count == config.scan_max_inactive_count) {
- debug(2, "DACP server status scanning stopped.");
- dacp_server.scan_enable = 0;
- }
- */
-
- int update_needed = 0;
- metadata_hub_modify_prolog();
- if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
- if (metadata_store.dacp_server_active != dacp_server_status_now) {
- debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
- metadata_store.dacp_server_active = dacp_server_status_now;
- update_needed = 1;
}
- }
- if (advanced_dacp_server_status_now !=
- -1) { // if advanced_dacp_server_status_now is actually known...
- if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
- debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
- metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
- update_needed = 1;
- }
- }
- metadata_hub_modify_epilog(update_needed);
+ if (metadata_store.player_thread_active == 0)
+ idle_scan_count++;
+ else
+ idle_scan_count = 0;
- // pthread_mutex_unlock(&dacp_server_information_lock);
- // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
- // dacp_server.ip_string, dacp_server.port, scan_index);
+ debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
+ idle_scan_count);
- if (result == 200) {
- metadata_hub_modify_prolog();
- int diff = metadata_store.speaker_volume != the_volume;
- if (diff)
- metadata_store.speaker_volume = the_volume;
- metadata_hub_modify_epilog(diff);
-
- ssize_t le;
- char *response = NULL;
- int32_t item_size;
- char command[1024] = "";
- if (always_use_revision_number_1 != 0) // for forked-daapd
- revision_number = 1;
- snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
- revision_number);
- // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
- result = dacp_send_command(command, &response, &le);
- // debug(1,"Response to \"%s\" is %d.",command,result);
- // remember: unless the revision_number you pass in is 1,
- // response will be 200 only if there's something new to report.
- if (result == 200) {
- // if (0) {
- char *sp = response;
- if (le >= 8) {
- // here start looking for the contents of the status update
- if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
- // here, we know that we are receiving playerstatusupdates, so set a flag
- metadata_hub_modify_prolog();
- // debug(1, "playstatusupdate release track metadata");
- // metadata_hub_reset_track_metadata();
- // metadata_store.playerstatusupdates_are_received = 1;
- sp -= item_size; // drop down into the array -- don't skip over it
- le -= 8;
- // char typestring[5];
- // we need to acquire the metadata data structure and possibly update it
- while (le >= 8) {
- uint32_t type = dacp_tlv_crawl(&sp, &item_size);
- le -= item_size + 8;
- char *t;
- // char u;
- // char *st;
- int32_t r;
- uint32_t ui;
- // uint64_t v;
- // int i;
-
- switch (type) {
- case 'cmsr': // revision number
- t = sp - item_size;
- revision_number = ntohl(*(uint32_t *)(t));
- // debug(1,"New revision number received: %d", revision_number);
- break;
- case 'caps': // play status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 2:
- if (metadata_store.play_status != PS_STOPPED) {
- metadata_store.play_status = PS_STOPPED;
- debug(2, "Play status is \"stopped\".");
- }
- break;
- case 3:
- if (metadata_store.play_status != PS_PAUSED) {
- metadata_store.play_status = PS_PAUSED;
- debug(2, "Play status is \"paused\".");
- }
- break;
- case 4:
- if (metadata_store.play_status != PS_PLAYING) {
- metadata_store.play_status = PS_PLAYING;
- debug(2, "Play status changed to \"playing\".");
- }
- break;
- default:
- debug(1, "Unrecognised play status %d received.", r);
- break;
- }
- break;
- case 'cash': // shuffle status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 0:
- if (metadata_store.shuffle_status != SS_OFF) {
- metadata_store.shuffle_status = SS_OFF;
- debug(2, "Shuffle status is \"off\".");
- }
- break;
- case 1:
- if (metadata_store.shuffle_status != SS_ON) {
- metadata_store.shuffle_status = SS_ON;
- debug(2, "Shuffle status is \"on\".");
- }
- break;
- default:
- debug(1, "Unrecognised shuffle status %d received.", r);
- break;
- }
- break;
- case 'carp': // repeat status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 0:
- if (metadata_store.repeat_status != RS_OFF) {
- metadata_store.repeat_status = RS_OFF;
- debug(2, "Repeat status is \"none\".");
- }
- break;
- case 1:
- if (metadata_store.repeat_status != RS_ONE) {
- metadata_store.repeat_status = RS_ONE;
- debug(2, "Repeat status is \"one\".");
- }
- break;
- case 2:
- if (metadata_store.repeat_status != RS_ALL) {
- metadata_store.repeat_status = RS_ALL;
- debug(2, "Repeat status is \"all\".");
- }
- break;
- default:
- debug(1, "Unrecognised repeat status %d received.", r);
- break;
- }
- break;
- case 'cann': // track name
- debug(2, "DACP Track Name seen");
- if (string_update_with_size(&metadata_store.track_name,
- &metadata_store.track_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
- }
- break;
- case 'cana': // artist name
- debug(2, "DACP Artist Name seen");
- if (string_update_with_size(&metadata_store.artist_name,
- &metadata_store.artist_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
- }
- break;
- case 'canl': // album name
- debug(2, "DACP Album Name seen");
- if (string_update_with_size(&metadata_store.album_name,
- &metadata_store.album_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
- }
- break;
- case 'cang': // genre
- debug(2, "DACP Genre seen");
- if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
- sp - item_size, item_size)) {
- debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
- }
- break;
- case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
- // see reference above)
- debug(2, "DACP Composite ID seen");
- if (memcmp(metadata_store.item_composite_id, sp - item_size,
- sizeof(metadata_store.item_composite_id)) != 0) {
- memcpy(metadata_store.item_composite_id, sp - item_size,
- sizeof(metadata_store.item_composite_id));
- char st[33];
- char *pt = st;
- int it;
- for (it = 0; it < 16; it++) {
- snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
- pt += 2;
- }
- *pt = 0;
- debug(2, "Item composite ID changed to 0x%s.", st);
- metadata_store.item_composite_id_changed = 1;
- }
- break;
- case 'astm':
- t = sp - item_size;
- ui = ntohl(*(uint32_t *)(t));
- debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
- if (ui != metadata_store.songtime_in_milliseconds) {
- metadata_store.songtime_in_milliseconds = ui;
- metadata_store.songtime_in_milliseconds_changed = 1;
- debug(2, "DACP Song Time set to: \"%u\"",
- metadata_store.songtime_in_milliseconds);
- }
- break;
-
- /*
- case 'mstt':
- case 'cant':
- case 'cast':
- case 'cmmk':
- case 'caas':
- case 'caar':
- t = sp - item_size;
- r = ntohl(*(uint32_t *)(t));
- printf(" %d", r);
- printf(" (0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- printf(")");
- break;
- case 'asai':
- t = sp - item_size;
- s = ntohl(*(uint32_t *)(t));
- s = s << 32;
- t += 4;
- v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
- s += v;
- printf(" %lu", s);
- printf(" (0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- printf(")");
- break;
- */
- default:
- /*
- printf(" 0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- */
- break;
- }
- // printf("\n");
- }
-
- // finished possibly writing to the metadata hub
- metadata_hub_modify_epilog(
- 1); // should really see if this can be made responsive to changes
- } else {
- debug(1, "Status Update not found.\n");
+ /* not used
+ // decide if idle for too long
+ if (idle_scan_count == config.scan_max_inactive_count) {
+ debug(2, "DACP server status scanning stopped.");
+ dacp_server.scan_enable = 0;
}
- } else {
- debug(1, "Can't find any content in playerstatusupdate request");
+ */
+
+ int update_needed = 0;
+ metadata_hub_modify_prolog();
+ if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
+ if (metadata_store.dacp_server_active != dacp_server_status_now) {
+ debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
+ metadata_store.dacp_server_active = dacp_server_status_now;
+ update_needed = 1;
}
- } /* else {
- if (result != 403)
- debug(1, "Unexpected response %d to playerstatusupdate request", result);
- } */
+ }
+ if (advanced_dacp_server_status_now !=
+ -1) { // if advanced_dacp_server_status_now is actually known...
+ if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
+ debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
+ metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
+ update_needed = 1;
+ }
+ }
+
+ metadata_hub_modify_epilog(update_needed);
+
+ // pthread_mutex_unlock(&dacp_server_information_lock);
+ // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
+ // dacp_server.ip_string, dacp_server.port, scan_index);
+
+ if (result == 200) {
+ metadata_hub_modify_prolog();
+ int diff = metadata_store.speaker_volume != the_volume;
+ if (diff)
+ metadata_store.speaker_volume = the_volume;
+ metadata_hub_modify_epilog(diff);
+
+ ssize_t le;
+ char *response = NULL;
+ int32_t item_size;
+ char command[1024] = "";
+ if (always_use_revision_number_1 != 0) // for forked-daapd
+ revision_number = 1;
+ snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
+ revision_number);
+ // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
+ result = dacp_send_command(command, &response, &le);
+ // debug(1,"Response to \"%s\" is %d.",command,result);
+ // remember: unless the revision_number you pass in is 1,
+ // response will be 200 only if there's something new to report.
+ if (result == 200) {
+ // if (0) {
+ char *sp = response;
+ if (le >= 8) {
+ // here start looking for the contents of the status update
+ if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
+ // here, we know that we are receiving playerstatusupdates, so set a flag
+ metadata_hub_modify_prolog();
+ // debug(1, "playstatusupdate release track metadata");
+ // metadata_hub_reset_track_metadata();
+ // metadata_store.playerstatusupdates_are_received = 1;
+ sp -= item_size; // drop down into the array -- don't skip over it
+ le -= 8;
+ // char typestring[5];
+ // we need to acquire the metadata data structure and possibly update it
+ while (le >= 8) {
+ uint32_t type = dacp_tlv_crawl(&sp, &item_size);
+ le -= item_size + 8;
+ char *t;
+ // char u;
+ // char *st;
+ int32_t r;
+ uint32_t ui;
+ // uint64_t v;
+ // int i;
+
+ switch (type) {
+ case 'cmsr': // revision number
+ t = sp - item_size;
+ revision_number = ntohl(*(uint32_t *)(t));
+ // debug(1,"New revision number received: %d", revision_number);
+ break;
+ case 'caps': // play status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 2:
+ if (metadata_store.play_status != PS_STOPPED) {
+ metadata_store.play_status = PS_STOPPED;
+ debug(2, "Play status is \"stopped\".");
+ }
+ break;
+ case 3:
+ if (metadata_store.play_status != PS_PAUSED) {
+ metadata_store.play_status = PS_PAUSED;
+ debug(2, "Play status is \"paused\".");
+ }
+ break;
+ case 4:
+ if (metadata_store.play_status != PS_PLAYING) {
+ metadata_store.play_status = PS_PLAYING;
+ debug(2, "Play status changed to \"playing\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised play status %d received.", r);
+ break;
+ }
+ break;
+ case 'cash': // shuffle status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 0:
+ if (metadata_store.shuffle_status != SS_OFF) {
+ metadata_store.shuffle_status = SS_OFF;
+ debug(2, "Shuffle status is \"off\".");
+ }
+ break;
+ case 1:
+ if (metadata_store.shuffle_status != SS_ON) {
+ metadata_store.shuffle_status = SS_ON;
+ debug(2, "Shuffle status is \"on\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised shuffle status %d received.", r);
+ break;
+ }
+ break;
+ case 'carp': // repeat status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 0:
+ if (metadata_store.repeat_status != RS_OFF) {
+ metadata_store.repeat_status = RS_OFF;
+ debug(2, "Repeat status is \"none\".");
+ }
+ break;
+ case 1:
+ if (metadata_store.repeat_status != RS_ONE) {
+ metadata_store.repeat_status = RS_ONE;
+ debug(2, "Repeat status is \"one\".");
+ }
+ break;
+ case 2:
+ if (metadata_store.repeat_status != RS_ALL) {
+ metadata_store.repeat_status = RS_ALL;
+ debug(2, "Repeat status is \"all\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised repeat status %d received.", r);
+ break;
+ }
+ break;
+ case 'cann': // track name
+ debug(2, "DACP Track Name seen");
+ if (string_update_with_size(&metadata_store.track_name,
+ &metadata_store.track_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
+ }
+ break;
+ case 'cana': // artist name
+ debug(2, "DACP Artist Name seen");
+ if (string_update_with_size(&metadata_store.artist_name,
+ &metadata_store.artist_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
+ }
+ break;
+ case 'canl': // album name
+ debug(2, "DACP Album Name seen");
+ if (string_update_with_size(&metadata_store.album_name,
+ &metadata_store.album_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
+ }
+ break;
+ case 'cang': // genre
+ debug(2, "DACP Genre seen");
+ if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
+ sp - item_size, item_size)) {
+ debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
+ }
+ break;
+ case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
+ // see reference above)
+ debug(2, "DACP Composite ID seen");
+ if (memcmp(metadata_store.item_composite_id, sp - item_size,
+ sizeof(metadata_store.item_composite_id)) != 0) {
+ memcpy(metadata_store.item_composite_id, sp - item_size,
+ sizeof(metadata_store.item_composite_id));
+ char st[33];
+ char *pt = st;
+ int it;
+ for (it = 0; it < 16; it++) {
+ snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
+ pt += 2;
+ }
+ *pt = 0;
+ debug(2, "Item composite ID changed to 0x%s.", st);
+ metadata_store.item_composite_id_changed = 1;
+ }
+ break;
+ case 'astm':
+ t = sp - item_size;
+ ui = ntohl(*(uint32_t *)(t));
+ debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
+ if (ui != metadata_store.songtime_in_milliseconds) {
+ metadata_store.songtime_in_milliseconds = ui;
+ metadata_store.songtime_in_milliseconds_changed = 1;
+ debug(2, "DACP Song Time set to: \"%u\"",
+ metadata_store.songtime_in_milliseconds);
+ }
+ break;
+
+ /*
+ case 'mstt':
+ case 'cant':
+ case 'cast':
+ case 'cmmk':
+ case 'caas':
+ case 'caar':
+ t = sp - item_size;
+ r = ntohl(*(uint32_t *)(t));
+ printf(" %d", r);
+ printf(" (0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ printf(")");
+ break;
+ case 'asai':
+ t = sp - item_size;
+ s = ntohl(*(uint32_t *)(t));
+ s = s << 32;
+ t += 4;
+ v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
+ s += v;
+ printf(" %lu", s);
+ printf(" (0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ printf(")");
+ break;
+ */
+ default:
+ /*
+ printf(" 0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ */
+ break;
+ }
+ // printf("\n");
+ }
+
+ // finished possibly writing to the metadata hub
+ metadata_hub_modify_epilog(
+ 1); // should really see if this can be made responsive to changes
+ } else {
+ debug(1, "Status Update not found.\n");
+ }
+ } else {
+ debug(1, "Can't find any content in playerstatusupdate request");
+ }
+ } /* else {
+ if (result != 403)
+ debug(1, "Unexpected response %d to playerstatusupdate request", result);
+ } */
+ if (response) {
+ free(response);
+ response = NULL;
+ };
+ };
+ /*
+ strcpy(command,"nowplayingartwork?mw=320&mh=320");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
if (response) {
free(response);
response = NULL;
- };
- };
- /*
- strcpy(command,"nowplayingartwork?mw=320&mh=320");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
+ }
+ strcpy(command,"getproperty?properties=dmcp.volume");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ strcpy(command,"setproperty?dmcp.volume=100.000000");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ */
+ if (metadata_store.player_thread_active)
+ sleep(config.scan_interval_when_active);
+ else
+ sleep(config.scan_interval_when_inactive);
}
- strcpy(command,"getproperty?properties=dmcp.volume");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- strcpy(command,"setproperty?dmcp.volume=100.000000");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- */
- if (metadata_store.player_thread_active)
- sleep(config.scan_interval_when_active);
- else
- sleep(config.scan_interval_when_inactive);
}
debug(1, "DACP monitor thread exiting -- should never happen.");
pthread_exit(NULL);
@@ -889,6 +920,22 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
void dacp_monitor_start() {
int rc;
+
+#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(&dacp_server_information_cv, &attr);
+ if (rc)
+ debug(1, "Error initialising the DACP Server Information Condition Variable");
+ pthread_condattr_destroy(&attr);
+#endif
+#ifdef COMPILE_FOR_OSX
+ rc = pthread_cond_init(&dacp_server_information_cv, NULL);
+#endif
+
+
+
pthread_mutexattr_t mta;
rc = pthread_mutexattr_init(&mta);
@@ -947,6 +994,8 @@ void dacp_monitor_stop() {
pthread_mutex_destroy(&dacp_server_information_lock);
debug(3, "DACP Conversation Lock Mutex Destroyed");
pthread_mutex_destroy(&dacp_conversation_lock);
+ pthread_cond_destroy(&dacp_server_information_cv);
+ debug(1, "DACP Server Information Condition Variable destroyed.");
}
}
diff --git a/rtsp.c b/rtsp.c
index dca0ecb5..4013acb9 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -2335,6 +2335,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
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(&conn->flowcontrol, &attr);
+ pthread_condattr_destroy(&attr);
#endif
#ifdef COMPILE_FOR_OSX
rc = pthread_cond_init(&conn->flowcontrol, NULL);
diff --git a/shairport.c b/shairport.c
index 881ecd97..8f80ebdb 100644
--- a/shairport.c
+++ b/shairport.c
@@ -405,6 +405,7 @@ int parse_options(int argc, char **argv) {
config.resend_control_first_check_time = 0.10; // wait this many seconds before requesting the resending of a missing packet
config.resend_control_check_interval_time = 0.25; // wait this many seconds before again requesting the resending of a missing packet
config.resend_control_last_check_time = 0.10; // give up if the packet is still missing this close to when it's needed
+ config.missing_port_dacp_scan_interval_seconds = 2.0; // check at this interval if no DACP port number is known
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
From ff3cef44714b1b298bc28849d3ed8e1cf79a5443 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 25 Mar 2020 10:17:39 +0000
Subject: [PATCH 067/186] If the DACP port is zero, do a zeroconf re-broswe
every two seconds in case it has been missed.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 1abafedc..3e041734 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.3.7d2], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.7d3], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 31f5e8b9ca9173244b1e64d13945acebcd0a5d1e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 25 Mar 2020 10:31:19 +0000
Subject: [PATCH 068/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index e8c62920..9aff68db 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,9 @@
+Version 3.3.7d3
+====
+
+**Enhancement**
+* While a player is active, the DACP port number to which to send remote commands should be broadcast over ZEROCONF/Bonjour. However, if that information is not available, Shairport Sync will now check for it every two seconds.
+
Version 3.3.7d2
====
**Pesky Changes**
From 1b7e23c093b6159b1f05a91e80a7c9ae4a216012 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 25 Mar 2020 10:40:05 +0000
Subject: [PATCH 069/186] Add ability to set the missing DACP port scan
interval.
---
scripts/shairport-sync.conf | 2 +-
shairport.c | 12 ++++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 9c875156..170fafc2 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -54,7 +54,7 @@ general =
// resend_control_first_check_time = 0.10; // Use this optional advanced setting to set the wait time in seconds before deciding a packet is missing.
// resend_control_check_interval_time = 0.25; // Use this optional advanced setting to set the time in seconds between requests for a missing packet.
// resend_control_last_check_time = 0.10; // Use this optional advanced setting to set the latest time, in seconds, by which the last check should be done before the estimated time of a missing packet's transfer to the output buffer.
-//
+// missing_port_dacp_scan_interval_seconds = 2.0; // Use this optional advanced setting to set the time interval between scans for a DACP port number if no port number has been provided by the player for remote control commands
};
// Advanced parameters for controlling how Shairport Sync stays active and how it runs a session
diff --git a/shairport.c b/shairport.c
index 8f80ebdb..ec5a259a 100644
--- a/shairport.c
+++ b/shairport.c
@@ -806,6 +806,18 @@ int parse_options(int argc, char **argv) {
dvalue, config.resend_control_last_check_time);
}
+ if (config_lookup_float(config.cfg, "general.missing_port_dacp_scan_interval_seconds",
+ &dvalue)) {
+ if ((dvalue >= 0.0) && (dvalue <= 300.0))
+ config.missing_port_dacp_scan_interval_seconds = dvalue;
+ else
+ warn("Invalid general missing_port_dacp_scan_interval_seconds setting \"%d\". It should "
+ "be "
+ "between 0.0 and 300.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.missing_port_dacp_scan_interval_seconds);
+ }
+
/* Get the default latency. Deprecated! */
if (config_lookup_int(config.cfg, "latencies.default", &value))
config.userSuppliedLatency = value;
From eef6bbf9839ff5c0434ff23fdcf8587bf1d17411 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 26 Mar 2020 17:03:53 +0000
Subject: [PATCH 070/186] Remove leading zeroes from the DACP ID provided by
mDNS
---
mdns_avahi.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/mdns_avahi.c b/mdns_avahi.c
index caec909d..575eba08 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -96,6 +96,8 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
char *dacpid = strstr(name, "iTunes_Ctrl_");
if (dacpid) {
dacpid += strlen("iTunes_Ctrl_");
+ while (*dacpid == '0')
+ dacpid++; // remove any leading zeroes
if (strcmp(dacpid, dbs->dacp_id) == 0) {
debug(3, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
#ifdef CONFIG_DACP_CLIENT
From 1f9be7d6aa0484726d68f806b05293018aa6addf Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 26 Mar 2020 17:04:25 +0000
Subject: [PATCH 071/186] Remove leading zeroes from the DACP ID provided by
mDNS
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 3e041734..ebc1122f 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.3.7d3], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.7d4], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 4eaf23aa22277e603f3e55487c9fa3c0130d672e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 26 Mar 2020 17:11:31 +0000
Subject: [PATCH 072/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 9aff68db..0d769e66 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,6 +1,10 @@
+Version 3.3.7d4
+====
+**Bug Fix**
+* The DACP ID looks like a 64-bit number expressed in hexadecimal. It is normally quoted with leading zeroes removed, but in the `_dacp._tcp` service string, leading zeros are not removed from the DACP ID. The bug fix removes those leading zeroes.
+
Version 3.3.7d3
====
-
**Enhancement**
* While a player is active, the DACP port number to which to send remote commands should be broadcast over ZEROCONF/Bonjour. However, if that information is not available, Shairport Sync will now check for it every two seconds.
@@ -10,7 +14,7 @@ Version 3.3.7d2
* The underlying timing system has been moved from 64-bit fixed-point time representation (a là NTP) to a 64-bit unsigned nanoseconds representation. This should make precisely no difference to the functionality of Shairport Sync but the transition might behabve inadeventely introduced bugs. Problem reports gratefully received.
**Enhancement**
-* The timing software in the `sndio` backend does some extra sanity checking on certain time estimates, it may help a little whne running on virtual machines.
+* The timing software in the `sndio` backend does some extra sanity checking on certain time estimates, it may help a little when running on virtual machines.
Version 3.3.7d1
====
From aacc4e802bdaffbfbe4aaffa5dc5a3f9f43cab7c Mon Sep 17 00:00:00 2001
From: Guillaume Revaillot
Date: Thu, 26 Mar 2020 16:13:36 +0100
Subject: [PATCH 073/186] Allow to specify pulseaudio server instead of using
default one.
Add "server" entry to pa config section, allowing to connect to a remote or a local system
pulseaudio instance instead of letting pulseaudio choose.
---
audio_pa.c | 8 +++++++-
common.h | 1 +
scripts/shairport-sync.conf | 1 +
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/audio_pa.c b/audio_pa.c
index e6a5b576..951860be 100644
--- a/audio_pa.c
+++ b/audio_pa.c
@@ -85,6 +85,11 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
if (config.cfg != NULL) {
const char *str;
+ /* Get the PulseAudio server name. */
+ if (config_lookup_string(config.cfg, "pa.server", &str)) {
+ config.pa_server = (char *)str;
+ }
+
/* Get the Application Name. */
if (config_lookup_string(config.cfg, "pa.application_name", &str)) {
config.pa_application_name = (char *)str;
@@ -126,7 +131,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
// Start the mainloop
if (pa_threaded_mainloop_start(mainloop) != 0)
die("could not start the pulseaudio threaded mainloop");
- if (pa_context_connect(context, NULL, 0, NULL) != 0)
+
+ if (pa_context_connect(context, config.pa_server, 0, NULL) != 0)
die("failed to connect to the pulseaudio context -- the error message is \"%s\".",
pa_strerror(pa_context_errno(context)));
diff --git a/common.h b/common.h
index 95f4decc..d5aad230 100644
--- a/common.h
+++ b/common.h
@@ -114,6 +114,7 @@ typedef struct {
// on host %h"
#ifdef CONFIG_PA
+ char *pa_server; // the pulseaudio server address that Shairport Sync will play on.
char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in
// the Sound Preferences in most desktop Linuxes.
// Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 170fafc2..992c4b95 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -118,6 +118,7 @@ sndio =
// --with-pa
pa =
{
+// server = "host"; // Set this to override the default pulseaudio server that should be used.
// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active.
};
From 13794352b272f2c09b12ade4247bd4dd027bc3d0 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 31 Mar 2020 14:40:18 +0100
Subject: [PATCH 074/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 0d769e66..962a33f2 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,6 @@
+**New Feature**
+* For the PulseAudio backend `pa`, add a new `server` entry to `pa` section of the configuration file, allowing you to specify a connection to a remote or a local system PulseAudio instance instead of letting PulseAudio choose. Thanks to [Guillaume Revaillot](https://github.com/grevaillot) for this new feature.
+
Version 3.3.7d4
====
**Bug Fix**
From ca72b570df0aac111ef36488650f9ec586db8e28 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 15 Apr 2020 14:57:39 +0100
Subject: [PATCH 075/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 962a33f2..a1d93710 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -14,7 +14,7 @@ Version 3.3.7d3
Version 3.3.7d2
====
**Pesky Changes**
-* The underlying timing system has been moved from 64-bit fixed-point time representation (a là NTP) to a 64-bit unsigned nanoseconds representation. This should make precisely no difference to the functionality of Shairport Sync but the transition might behabve inadeventely introduced bugs. Problem reports gratefully received.
+* The underlying timing system has been moved from 64-bit fixed-point time representation (a là NTP) to a 64-bit unsigned nanoseconds representation. This should make precisely no difference to the functionality of Shairport Sync but the transition might inadvertently have introduced bugs. Problem reports gratefully received.
**Enhancement**
* The timing software in the `sndio` backend does some extra sanity checking on certain time estimates, it may help a little when running on virtual machines.
From 0ad9d3d85eef97b81c51593d994d593fc2f71d2c Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 15 Apr 2020 14:59:16 +0100
Subject: [PATCH 076/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index a1d93710..f72f53f6 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -4,7 +4,7 @@
Version 3.3.7d4
====
**Bug Fix**
-* The DACP ID looks like a 64-bit number expressed in hexadecimal. It is normally quoted with leading zeroes removed, but in the `_dacp._tcp` service string, leading zeros are not removed from the DACP ID. The bug fix removes those leading zeroes.
+* The DACP ID looks like a 64-bit number expressed in hexadecimal. It is normally quoted with leading zeroes removed, but in the `_dacp._tcp` service string, leading zeros are not removed from the DACP ID. The bug fix removes those leading zeroes. Thanks to [julianc1969](https://github.com/juliandc1969) for tenaciously tracking down this bug!
Version 3.3.7d3
====
From f0f6276d64b3728309bb440f97fc0704c5948fe9 Mon Sep 17 00:00:00 2001
From: Tucker Kern
Date: Mon, 27 Apr 2020 15:22:06 -0600
Subject: [PATCH 077/186] Use signed integers when calculating backend latency
adjustment
---
player.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/player.c b/player.c
index 483fbee8..ee1d1a4f 100644
--- a/player.c
+++ b/player.c
@@ -1048,7 +1048,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
uint64_t should_be_time;
frame_to_local_time(conn->first_packet_timestamp + conn->latency +
- (uint32_t)(config.audio_backend_latency_offset *
+ (int32_t)(config.audio_backend_latency_offset *
conn->input_rate), // this will go modulo 2^32
&should_be_time,
conn);
@@ -1069,7 +1069,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
uint64_t should_be_time;
frame_to_local_time(conn->first_packet_timestamp + conn->latency +
- (uint32_t)(config.audio_backend_latency_offset *
+ (int32_t)(config.audio_backend_latency_offset *
conn->input_rate), // this should go modulo 2^32
&should_be_time,
conn);
@@ -1304,7 +1304,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
uint64_t time_to_play;
frame_to_local_time(curframe->given_timestamp + conn->latency +
- (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) -
+ (int32_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,
From 72d715352951efe3b4cecd412ec7528659ad18f2 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 14 May 2020 19:51:32 +0100
Subject: [PATCH 078/186] Fix calculation of latency -- honour the signs of the
offset and account for the DAC buffer size.
---
audio.c | 12 +++---------
common.h | 1 +
dacp.c | 2 +-
player.h | 1 +
shairport.c | 4 ++++
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/audio.c b/audio.c
index 8b2762f4..f2195279 100644
--- a/audio.c
+++ b/audio.c
@@ -147,10 +147,10 @@ void parse_general_audio_options(void) {
/* Get the desired buffer size setting in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
- if ((dvalue < 0) || (dvalue > 1.5)) {
+ if (dvalue < 0) {
die("Invalid audio_backend_buffer_desired_length_in_seconds value: \"%f\". It "
- "should be between 0 and "
- "1.5, default is %.3f seconds",
+ "should be 0.0 or greater."
+ " The default is %.3f seconds",
dvalue, config.audio_backend_buffer_desired_length);
} else {
config.audio_backend_buffer_desired_length = dvalue;
@@ -190,13 +190,7 @@ void parse_general_audio_options(void) {
/* Get the latency offset in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds",
&dvalue)) {
- if ((dvalue < -1.75) || (dvalue > 1.75)) {
- die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It "
- "should be between -1.75 and +1.75, default is 0 seconds",
- dvalue);
- } else {
config.audio_backend_latency_offset = dvalue;
- }
}
/* Get the desired length of the silent lead-in. */
diff --git a/common.h b/common.h
index d5aad230..dd267550 100644
--- a/common.h
+++ b/common.h
@@ -215,6 +215,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.
+ uint32_t minimum_free_buffer_headroom; // when effective latency is calculated, ensure this number of buffers are unallocated
double active_state_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
diff --git a/dacp.c b/dacp.c
index dc794d4d..b8e41d67 100644
--- a/dacp.c
+++ b/dacp.c
@@ -1248,7 +1248,7 @@ int dacp_get_volume(int32_t *the_actual_volume) {
} else {
debug(1, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
}
- } else {
+ } else if (http_response != 400){
debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response);
}
if (the_actual_volume) {
diff --git a/player.h b/player.h
index 3845ca9f..483d4fee 100644
--- a/player.h
+++ b/player.h
@@ -87,6 +87,7 @@ typedef struct {
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 unachievable_audio_backend_latency_offset_notified; // set when a latency warning is issued
int fd;
int authorized; // set if a password is required and has been supplied
diff --git a/shairport.c b/shairport.c
index ec5a259a..867938ed 100644
--- a/shairport.c
+++ b/shairport.c
@@ -407,6 +407,10 @@ int parse_options(int argc, char **argv) {
config.resend_control_last_check_time = 0.10; // give up if the packet is still missing this close to when it's needed
config.missing_port_dacp_scan_interval_seconds = 2.0; // check at this interval if no DACP port number is known
+ config.minimum_free_buffer_headroom = 125; // leave approximately one second's worth of buffers free after calculating the effective latency.
+ // e.g. if we have 1024 buffers or 352 frames = 8.17 seconds and we have a nominal latency of 2.0 seconds
+ // then we can add an offset of 5.17 seconds and still leave a second's worth of buffers for unexpected circumstances
+
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
config.scan_interval_when_active =
From 14d75679bedab7fe4bd9ae63fb037d6cc7e8d407 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 14 May 2020 20:45:52 +0100
Subject: [PATCH 079/186] Fix calculation of latency -- honour the signs of the
offset and account for the DAC buffer size.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index ebc1122f..7f148707 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.3.7d4], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.7d5], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From a4edc649f8f4255a7d84495f2a72771424813a9d Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 16 May 2020 12:23:08 +0100
Subject: [PATCH 080/186] Add code to do calculations involving the
audio_backend_latency_offset_in_seconds and audio_backend_silent_lead_in_time
settings correctly. Clean up the audio_backend_silent_lead_in_time setting by
adding an "auto" setting.
---
audio.c | 28 ++++++-
common.h | 1 +
player.c | 158 +++++++++++++++++++++++++++---------
scripts/shairport-sync.conf | 6 +-
shairport.c | 7 +-
5 files changed, 155 insertions(+), 45 deletions(-)
diff --git a/audio.c b/audio.c
index f2195279..4d5df37b 100644
--- a/audio.c
+++ b/audio.c
@@ -126,6 +126,7 @@ void parse_general_audio_options(void) {
* are set before any options are chosen */
int value;
double dvalue;
+ const char *str = 0;
if (config.cfg != NULL) {
/* Get the desired buffer size setting (deprecated). */
@@ -193,14 +194,37 @@ void parse_general_audio_options(void) {
config.audio_backend_latency_offset = dvalue;
}
+
+ /* Check if the length of the silent lead-in ia set to \"auto\". */
+ if (config_lookup_string(config.cfg, "general.audio_backend_silent_lead_in_time", &str)) {
+ if (strcasecmp(str, "auto") == 0) {
+ config.audio_backend_silent_lead_in_time_auto = 1;
+ } else {
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ warn("Invalid audio_backend_silent_lead_in_time \"%s\". It should be \"auto\" or the lead-in time in seconds. "
+ "It remains set to \"auto\". Note: numbers should not be placed in quotes.",
+ str);
+ else
+ warn("Invalid output rate \"%s\". It should be \"auto\" or the lead-in time in seconds. "
+ "It remains set to %f. Note: numbers should not be placed in quotes.",
+ str, config.audio_backend_silent_lead_in_time);
+ }
+ }
+
/* Get the desired length of the silent lead-in. */
if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", &dvalue)) {
if ((dvalue < 0.0) || (dvalue > 4)) {
- die("Invalid audio_backend_silent_lead_in_time \"%f\". It "
- "must be between 0.0 and 4.0 seconds. Omit setting to use the default value",
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. The setting remains at \"auto\".",
dvalue);
+ else
+ warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. It remains set to %f.",
+ dvalue, config.audio_backend_silent_lead_in_time);
} else {
config.audio_backend_silent_lead_in_time = dvalue;
+ config.audio_backend_silent_lead_in_time_auto = 0;
}
}
}
diff --git a/common.h b/common.h
index dd267550..d2d90421 100644
--- a/common.h
+++ b/common.h
@@ -214,6 +214,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
+ int audio_backend_silent_lead_in_time_auto; // true if the lead-in time should be from as soon as packets are received
double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
uint32_t minimum_free_buffer_headroom; // when effective latency is calculated, ensure this number of buffers are unallocated
double active_state_timeout; // the amount of time from when play ends to when the system leaves
diff --git a/player.c b/player.c
index ee1d1a4f..fb16a3e8 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 -- 2019
+ * and related work, copyright (c) Mike Brady 2014 -- 2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -660,6 +660,28 @@ int32_t rand_in_range(int32_t exclusive_range_limit) {
return sp >> 32;
}
+int get_and_check_effective_latency(rtsp_conn_info *conn, uint32_t *effective_latency, double offset_time) {
+// check that the overall effective latency remains positive and is not greater than the capacity of the buffers
+// return 0 if okay, -1 if the latency would be negative, +1 if it would be too large
+
+ int result = 0;
+ if (offset_time >= 0.0) {
+ uint32_t latency_addition = (uint32_t)(offset_time * conn->input_rate);
+ // keep about one second of buffers back
+ if ((*effective_latency + latency_addition) <= (conn->max_frames_per_packet * (BUFFER_FRAMES - config.minimum_free_buffer_headroom)))
+ *effective_latency += latency_addition;
+ else
+ result = 1;
+ } else {
+ uint32_t latency_reduction = (uint32_t)((-offset_time) * conn->input_rate);
+ if (latency_reduction <= *effective_latency)
+ *effective_latency -= latency_reduction;
+ else
+ result = -1;
+ }
+ return result;
+}
+
static inline void process_sample(int32_t sample, char **outp, sps_format_t format, int volume,
int dither, rtsp_conn_info *conn) {
/*
@@ -898,6 +920,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
int wait;
long dac_delay = 0; // long because alsa returns a long
+ int have_sent_prefiller_silence = 0; // set to true when we have sent at least one silent frame to the DAC
+
pthread_cleanup_push(buffer_get_frame_cleanup_handler,
(void *)conn); // undo what's been done so far
do {
@@ -989,8 +1013,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
notified_buffer_empty = 0; // at least one buffer now -- diagnostic only.
if (conn->ab_buffering) { // if we are getting packets but not yet forwarding them to the
// player
- int have_sent_prefiller_silence = 1; // set true when we have sent some silent frames to
- // the DAC
+// int have_sent_prefiller_silence = 1; // set to true when we have sent at least one silent frame to the DAC
/*
int64_t reference_timestamp;
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
@@ -1007,7 +1030,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
conn->first_packet_timestamp =
curframe->given_timestamp; // we will keep buffering until we are
// supposed to start playing this
- have_sent_prefiller_silence = 0;
+ // have_sent_prefiller_silence = 0;
// debug(1, "First packet timestamp is %" PRId64 ".", conn->first_packet_timestamp);
@@ -1047,9 +1070,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// frame should be played"
uint64_t should_be_time;
- frame_to_local_time(conn->first_packet_timestamp + conn->latency +
- (int32_t)(config.audio_backend_latency_offset *
- conn->input_rate), // this will go modulo 2^32
+ uint32_t effective_latency = conn->latency;
+
+ get_and_check_effective_latency(conn, &effective_latency,config.audio_backend_latency_offset);
+ // we are ignoring the returned status because it will be captured on subsequent frames, below.
+
+ frame_to_local_time(conn->first_packet_timestamp + effective_latency, // this will go modulo 2^32
&should_be_time,
conn);
@@ -1067,14 +1093,34 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (conn->first_packet_time_to_play != 0) {
// recalculate conn->first_packet_time_to_play -- the latency might change
- uint64_t should_be_time;
- frame_to_local_time(conn->first_packet_timestamp + conn->latency +
- (int32_t)(config.audio_backend_latency_offset *
- conn->input_rate), // this should go modulo 2^32
- &should_be_time,
- conn);
+ uint64_t should_be_time;
+ uint32_t effective_latency = conn->latency;
- conn->first_packet_time_to_play = should_be_time;
+ switch (get_and_check_effective_latency(conn, &effective_latency,config.audio_backend_latency_offset)) {
+ case -1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Negative latency! A latency of %d frames requested by the player, when combined with an audio_backend_latency_offset of %f seconds, would make the overall latency negative. The audio_backend_latency_offset setting is ignored.", conn->latency, config.audio_backend_latency_offset);
+ config.audio_backend_latency_offset = 0; //set it to zero
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+// effective_latency = 0 ;
+ break;
+ case 1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("An audio_backend_latency_offset of %f seconds may exceed the frame buffering capacity -- the setting is ignored.", config.audio_backend_latency_offset);
+ config.audio_backend_latency_offset = 0; // set it to zero;
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ break;
+ default:
+ break;
+ }
+
+ frame_to_local_time(conn->first_packet_timestamp + effective_latency, // this will go modulo 2^32
+ &should_be_time,
+ conn);
+
+ conn->first_packet_time_to_play = should_be_time;
// we want the frames of silence sent at the start to be fairly large in case the output
// device's minimum buffer size is large. But they can't be greater than the silent
@@ -1083,21 +1129,35 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// setting
// In fact, if should be some fraction of them, to allow for adjustment.
- int64_t max_dac_delay = conn->latency;
- if (config.audio_backend_silent_lead_in_time >= 0)
+ double adjustment_interval_seconds = 0.025;
+ int64_t minimum_adjustments_to_make = 3;
+ double initial_silence_seconds = 0.2;
+
+ int64_t adjustment_interval_frames = (int64_t)(adjustment_interval_seconds * conn->input_rate);
+ int64_t initial_silence_frames = (int64_t)(initial_silence_seconds * conn->input_rate);
+
+ int64_t max_dac_delay = effective_latency;
+ if (config.audio_backend_silent_lead_in_time_auto == 0)
max_dac_delay =
(int64_t)(config.audio_backend_silent_lead_in_time * conn->input_rate);
- max_dac_delay = max_dac_delay / 4;
+ // should check that the audio_backend_silent_lead_in_time is greater than the desired_buffer_size
- // debug(1,"max_dac_delay is %" PRIu64 " frames.", max_dac_delay);
+ int64_t filler_size = adjustment_interval_frames;
- int64_t filler_size = max_dac_delay;
+ if (have_sent_prefiller_silence == 0) {
+ int64_t delay_before_adjustments = max_dac_delay - minimum_adjustments_to_make * adjustment_interval_frames;
+ if (delay_before_adjustments > initial_silence_frames)
+ filler_size = initial_silence_frames;
+ else
+ filler_size = max_dac_delay / (minimum_adjustments_to_make + 1);
+ debug(1,"Initial filler size: %" PRId64 " frames.", filler_size);
+ }
if (local_time_now > conn->first_packet_time_to_play) {
uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
debug(3, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness);
- have_sent_prefiller_silence = 1;
+ // have_sent_prefiller_silence = 1;
conn->ab_buffering = 0;
// we've gone past the time...
@@ -1117,16 +1177,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
} else {
// do some calculations
int64_t lead_time = conn->first_packet_time_to_play - local_time_now;
- // an audio_backend_silent_lead_in_time of less than zero means start filling ASAP
- int64_t lead_in_time = -1;
- if (config.audio_backend_silent_lead_in_time >= 0)
- lead_in_time =
- (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000);
- // debug(1,"Lead time is %llx at fpttp
- // %llx.",lead_time,conn->first_packet_time_to_play);
- if ((lead_in_time < 0) || (lead_time <= lead_in_time)) {
- // debug(1,"Lead time is %" PRIx64 ", lead-in time is %" PRIx64 " at fpttp
- // %llx.",lead_time,conn->first_packet_time_to_play);
+ if ((config.audio_backend_silent_lead_in_time_auto == 1) || (lead_time <= (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) {
+ debug(1,"Lead time: %" PRId64 ".", lead_time);
// debug(1,"Checking");
if (config.output->delay) {
@@ -1235,7 +1287,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
void *silence;
if (lead_time != 0) {
- int64_t frame_gap = (lead_time * config.output_rate) >> 32;
+ int64_t frame_gap = (lead_time * config.output_rate) / 1000000000;
// debug(1,"%d frames needed.",frame_gap);
while (frame_gap > 0) {
ssize_t fs = config.output_rate / 10;
@@ -1303,12 +1355,40 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (have_timestamp_timing_information(conn)) { // if we have a reference time
uint64_t time_to_play;
- frame_to_local_time(curframe->given_timestamp + conn->latency +
- (int32_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);
+ uint32_t effective_latency = conn->latency;
+
+ switch (get_and_check_effective_latency(conn, &effective_latency,config.audio_backend_latency_offset - config.audio_backend_buffer_desired_length)) {
+ case -1:
+ // this means that the latency is negative, i.e. the packet must be played before its time. This is
+ // clearly a mistake, and can arise if the combination of a large negative latency offset and the desired buffer length are greater than the
+ // latency chosen by the player. For example if the latency is 88200 frames (2 seconds), the audio_backend_latency_offset is -1.9 and the audio_backend_buffer_desired_length is 0.2
+ // the effective latency is -0.1, so the frame must be played 0.1 seconds before it is time-tagged for, and in reality before it arrives at the player.
+
+ // to deal with the problem, rather than block the packets, we'll just let 'em go...
+
+ //
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Negative latency! A latency of %d frames requested by the player, when combined with an audio_backend_latency_offset of %f seconds an audio_backend_buffer_desired_length of %f seconds, would make the overall latency negative. No latency is used. Synchronisation may fail.", conn->latency, config.audio_backend_latency_offset, config.audio_backend_buffer_desired_length);
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ time_to_play = local_time_now; // pretend the frame should be played now...
+ break;
+ case 1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Latency too long! An audio_backend_latency_offset of %f seconds combined with an audio_backend_buffer_desired_length of %f seconds may exceed the frame buffering capacity.", config.audio_backend_latency_offset, config.audio_backend_buffer_desired_length);
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ time_to_play = local_time_now; // pretend the frame should be played now...
+ break;
+ default:
+ frame_to_local_time(curframe->given_timestamp + effective_latency, // this will go modulo 2^32
+ &time_to_play,
+ conn);
+ break;
+ }
+
+
+
if (local_time_now >= time_to_play) {
do_wait = 0;
@@ -1320,7 +1400,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (notified_buffer_empty == 0) {
debug(3, "Buffers exhausted.");
notified_buffer_empty = 1;
- reset_input_flow_metrics(conn);
+ // reset_input_flow_metrics(conn); // don't do a full flush parameters reset
+ conn->initial_reference_time = 0;
+ conn->initial_reference_timestamp = 0;
}
do_wait = 1;
}
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 992c4b95..fd20b33f 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -43,10 +43,10 @@ general =
// If you build Shairport Sync with the flag --with-apple-alac, the Apple ALAC decoder will be chosen by default.
// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically.
-// 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.2; // 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_latency_offset_in_seconds = 0.0; // This is added to the latency requested by the player to delay or advance the output by a fixed amount. Use it, for example, to compensate for a fixed delay in the audio back end. E.g. if the output device, e.g. a soundbar, takes 100 ms to process audio, set this to -0.1 to deliver the audio to the output device 100 ms early, allowing it time to process the audio and output it perfectly in sync.
+// audio_backend_buffer_desired_length_in_seconds = 0.2; // 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.2 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.
+// audio_backend_silent_lead_in_time = "auto"; // This optional advanced setting, either "auto" or a positive number, sets the length of the period of silence that precedes the start of the audio. The default is "auto" -- the silent lead-in starts as soon as the player starts sending packets. 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".
// mpris_service_bus = "system"; // The Shairport Sync mpris interface, if selected at compilation, will appear
diff --git a/shairport.c b/shairport.c
index 867938ed..ead93b2d 100644
--- a/shairport.c
+++ b/shairport.c
@@ -388,7 +388,7 @@ int parse_options(int argc, char **argv) {
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
- config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set
+ config.audio_backend_silent_lead_in_time_auto = 1; // start outputting silence as soon as packets start arriving
config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if
// nothing else comes in first.
config.fixedLatencyOffset = 11025; // this sounds like it works properly.
@@ -1788,7 +1788,10 @@ int main(int argc, char **argv) {
debug(1, "audio_backend_buffer_interpolation_threshold_in_seconds is %f seconds.",
config.audio_backend_buffer_interpolation_threshold_in_seconds);
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.",
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ debug(1, "audio backend silence lead-in time is \"auto\".");
+ else
+ debug(1, "audio backend silence lead-in time is %f seconds.",
config.audio_backend_silent_lead_in_time);
debug(1, "zeroconf regtype is \"%s\".", config.regtype);
debug(1, "decoders_supported field is %d.", config.decoders_supported);
From 07d956c014217ea7391854ab25ad88a28b9d0e00 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 16 May 2020 12:25:27 +0100
Subject: [PATCH 081/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index f72f53f6..a72ed3c0 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -4,7 +4,7 @@
Version 3.3.7d4
====
**Bug Fix**
-* The DACP ID looks like a 64-bit number expressed in hexadecimal. It is normally quoted with leading zeroes removed, but in the `_dacp._tcp` service string, leading zeros are not removed from the DACP ID. The bug fix removes those leading zeroes. Thanks to [julianc1969](https://github.com/juliandc1969) for tenaciously tracking down this bug!
+* The DACP ID looks like a 64-bit number expressed in hexadecimal. It is normally quoted with leading zeroes removed, but in the `_dacp._tcp` service string, leading zeros are not removed from the DACP ID. The bug fix removes those leading zeroes. Thanks to [julianc1969](https://github.com/juliandc1969) for tracking down this bug so tenaciously!
Version 3.3.7d3
====
From e8831f6850378676f9e65e9876f6526309256b4b Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 16 May 2020 12:35:31 +0100
Subject: [PATCH 082/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index a72ed3c0..a1a91eb7 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,10 @@
+Version 3.3.7d5
+====
+**Bug Fixes and Enhancements**
+* Added code to do calculations involving the `audio_backend_latency_offset_in_seconds` and `audio_backend_silent_lead_in_time` settings correctly. Many thanks to [Tucker Kern](https://github.com/mill1000) for discovering a number of bugs associated with this and for [proposing a solution](https://github.com/mikebrady/shairport-sync/pull/1001). This prompted a closer investigation and a number of further improvements were made, and a few "hostages to fortune" removed.
+* Cleaned up the `audio_backend_silent_lead_in_time` setting by adding an `"auto"` setting.
+* Improved synchronisation accuracy with short silence lead-ins.
+
**New Feature**
* For the PulseAudio backend `pa`, add a new `server` entry to `pa` section of the configuration file, allowing you to specify a connection to a remote or a local system PulseAudio instance instead of letting PulseAudio choose. Thanks to [Guillaume Revaillot](https://github.com/grevaillot) for this new feature.
From 18749bc3159c8f67c32c90563333e11c2024952e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 16 May 2020 12:49:00 +0100
Subject: [PATCH 083/186] Add brief message about finding ALSA devices.
---
scripts/shairport-sync.conf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index fd20b33f..e4137638 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -84,7 +84,7 @@ sessioncontrol =
// --with-alsa
alsa =
{
-// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
+// output_device = "default"; // the name of the alsa output device. Use "shairport-sync -h" to discover the names of ALSA hardware devices. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
// mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software.
// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this.
// output_rate = "auto"; // can be "auto", 44100, 88200, 176400 or 352800, but the device must have the capability.
From bca704d5b71997a740e15f7a30602068d0217647 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 16 May 2020 12:54:18 +0100
Subject: [PATCH 084/186] Quieten a few debug messages.
---
player.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/player.c b/player.c
index fb16a3e8..f36417c5 100644
--- a/player.c
+++ b/player.c
@@ -1151,7 +1151,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
filler_size = initial_silence_frames;
else
filler_size = max_dac_delay / (minimum_adjustments_to_make + 1);
- debug(1,"Initial filler size: %" PRId64 " frames.", filler_size);
+ // debug(1,"Initial filler size: %" PRId64 " frames.", filler_size);
}
if (local_time_now > conn->first_packet_time_to_play) {
@@ -1178,7 +1178,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// do some calculations
int64_t lead_time = conn->first_packet_time_to_play - local_time_now;
if ((config.audio_backend_silent_lead_in_time_auto == 1) || (lead_time <= (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) {
- debug(1,"Lead time: %" PRId64 ".", lead_time);
+ // debug(1,"Lead time: %" PRId64 ".", lead_time);
// debug(1,"Checking");
if (config.output->delay) {
From 012dd26c77c6abf89a73076bc085ad97cf3639de Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 17 May 2020 12:48:25 +0100
Subject: [PATCH 085/186] Add the ability to set the silent lead-in parameters
initial_period, minimum number of adjustments and adjustment interval, and
give them sensible defaults.
---
common.h | 4 +++
player.c | 12 +++-----
scripts/shairport-sync.conf | 4 +++
shairport.c | 58 ++++++++++++++++++++++++++++++++-----
4 files changed, 63 insertions(+), 15 deletions(-)
diff --git a/common.h b/common.h
index d2d90421..ec9f35a1 100644
--- a/common.h
+++ b/common.h
@@ -212,6 +212,10 @@ typedef struct {
// buffer
double disable_standby_mode_silence_scan_interval; // check the threshold this often
+ double lead_in_silence_initial_period; // the size of the first block of silence to send to the DAC
+ int lead_in_silence_minimum_adjustments_to_make; // make at least this number of checks and timing adjustments
+ double lead_in_silence_adjustment_interval; // make checks and adjustments at this interval
+
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
int audio_backend_silent_lead_in_time_auto; // true if the lead-in time should be from as soon as packets are received
diff --git a/player.c b/player.c
index f36417c5..2d8f635e 100644
--- a/player.c
+++ b/player.c
@@ -1129,12 +1129,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// setting
// In fact, if should be some fraction of them, to allow for adjustment.
- double adjustment_interval_seconds = 0.025;
- int64_t minimum_adjustments_to_make = 3;
- double initial_silence_seconds = 0.2;
-
- int64_t adjustment_interval_frames = (int64_t)(adjustment_interval_seconds * conn->input_rate);
- int64_t initial_silence_frames = (int64_t)(initial_silence_seconds * conn->input_rate);
+ int64_t adjustment_interval_frames = (int64_t)(config.lead_in_silence_adjustment_interval * conn->input_rate);
+ int64_t initial_silence_frames = (int64_t)(config.lead_in_silence_initial_period * conn->input_rate);
int64_t max_dac_delay = effective_latency;
if (config.audio_backend_silent_lead_in_time_auto == 0)
@@ -1146,11 +1142,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
int64_t filler_size = adjustment_interval_frames;
if (have_sent_prefiller_silence == 0) {
- int64_t delay_before_adjustments = max_dac_delay - minimum_adjustments_to_make * adjustment_interval_frames;
+ int64_t delay_before_adjustments = max_dac_delay - config.lead_in_silence_minimum_adjustments_to_make * adjustment_interval_frames;
if (delay_before_adjustments > initial_silence_frames)
filler_size = initial_silence_frames;
else
- filler_size = max_dac_delay / (minimum_adjustments_to_make + 1);
+ filler_size = max_dac_delay / (config.lead_in_silence_minimum_adjustments_to_make + 1);
// debug(1,"Initial filler size: %" PRId64 " frames.", filler_size);
}
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index e4137638..33db5d91 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -47,6 +47,10 @@ general =
// audio_backend_buffer_desired_length_in_seconds = 0.2; // 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.2 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 = "auto"; // This optional advanced setting, either "auto" or a positive number, sets the length of the period of silence that precedes the start of the audio. The default is "auto" -- the silent lead-in starts as soon as the player starts sending packets. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation.
+// audio_backend_silent_lead_in_initial_period = 0.5; // This optional advanced setting sets the length, in seconds, of the first "block" of silence sent to the DAC -- some DACs need a fairly substantial block to prevent lots of initial underrun.
+// audio_backend_silent_lead_in_minimum_adjustments_to_make = 3; // This optional advanced setting sets the minimum number of times Shairport Sync will readjust the length of the silent lead in to ensure that the audio starts at precisely the correct time.
+// audio_backend_silent_lead_in_adjustment_interval = 0.1; // This optional advanced setting sets the interval, in seconds, between adjustments of the silent lead in to ensure that the audio starts at precisely the correct time.
+
// 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".
// mpris_service_bus = "system"; // The Shairport Sync mpris interface, if selected at compilation, will appear
diff --git a/shairport.c b/shairport.c
index ead93b2d..60bfb4a0 100644
--- a/shairport.c
+++ b/shairport.c
@@ -751,9 +751,9 @@ int parse_options(int argc, char **argv) {
* range set by the mixer. */
if (config_lookup_int(config.cfg, "general.volume_range_db", &value)) {
if ((value < 30) || (value > 150))
- die("Invalid volume range \"%sd\". It should be between 30 and 150 dB. Zero means use "
- "the mixer's native range",
- value);
+ die("Invalid volume range %d dB. It should be between 30 and 150 dB. Zero means use "
+ "the mixer's native range. The setting reamins at %d.",
+ value, config.volume_range_db);
else
config.volume_range_db = value;
}
@@ -779,7 +779,7 @@ int parse_options(int argc, char **argv) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_first_check_time = dvalue;
else
- warn("Invalid general resend_control_first_check_time setting \"%d\". It should "
+ warn("Invalid general resend_control_first_check_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
@@ -791,7 +791,7 @@ int parse_options(int argc, char **argv) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_check_interval_time = dvalue;
else
- warn("Invalid general resend_control_check_interval_time setting \"%d\". It should "
+ warn("Invalid general resend_control_check_interval_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
@@ -803,7 +803,7 @@ int parse_options(int argc, char **argv) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_last_check_time = dvalue;
else
- warn("Invalid general resend_control_last_check_time setting \"%d\". It should "
+ warn("Invalid general resend_control_last_check_time setting \"%f\". It should "
"be "
"between 0.0 and 3.0, "
"inclusive. The setting remains at %f seconds.",
@@ -815,7 +815,7 @@ int parse_options(int argc, char **argv) {
if ((dvalue >= 0.0) && (dvalue <= 300.0))
config.missing_port_dacp_scan_interval_seconds = dvalue;
else
- warn("Invalid general missing_port_dacp_scan_interval_seconds setting \"%d\". It should "
+ warn("Invalid general missing_port_dacp_scan_interval_seconds setting \"%f\". It should "
"be "
"between 0.0 and 300.0, "
"inclusive. The setting remains at %f seconds.",
@@ -826,6 +826,40 @@ int parse_options(int argc, char **argv) {
if (config_lookup_int(config.cfg, "latencies.default", &value))
config.userSuppliedLatency = value;
+ /* Get the lead-in silence settings. */
+ if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_initial_period",
+ &dvalue)) {
+ if ((dvalue >= 0.0) && (dvalue <= 3.0))
+ config.lead_in_silence_initial_period = dvalue;
+ else
+ warn("Invalid general audio_backend_silent_lead_in_initial_period setting \"%f\". It should "
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.lead_in_silence_initial_period);
+ }
+
+ if (config_lookup_int(config.cfg, "general.audio_backend_silent_lead_in_minimum_adjustments_to_make", &value)) {
+ if (value < 0)
+ die("Invalid audio_backend_silent_lead_in_minimum_adjustments_to_make range \"%d\". It must be positive. The setting remains at %d.",
+ value, config.lead_in_silence_minimum_adjustments_to_make);
+ else
+ config.lead_in_silence_minimum_adjustments_to_make = value;
+ }
+
+ if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_adjustment_interval",
+ &dvalue)) {
+ if ((dvalue >= 0.0) && (dvalue <= 3.0))
+ config.lead_in_silence_adjustment_interval = dvalue;
+ else
+ warn("Invalid general audio_backend_silent_lead_in_adjustment_interval setting \"%f\". It should "
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.lead_in_silence_adjustment_interval);
+ }
+
+
#ifdef CONFIG_METADATA
/* Get the metadata setting. */
config.metadata_enabled = 1; // if metadata support is included, then enable it by default
@@ -1498,6 +1532,9 @@ int main(int argc, char **argv) {
config.output_rate_auto_requested = 1; // default auto select format
config.decoders_supported =
1 << decoder_hammerton; // David Hammerton's decoder supported by default
+ config.lead_in_silence_initial_period = 0.5; // the size of the first block of silence to send to the DAC
+ config.lead_in_silence_minimum_adjustments_to_make = 3; // make at least this number of checks and timing adjustments
+ config.lead_in_silence_adjustment_interval = 0.025; // make checks and adjustments at this interval
#ifdef CONFIG_APPLE_ALAC
config.decoders_supported += 1 << decoder_apple_alac;
config.use_apple_decoder = 1; // use the ALAC decoder by default if support has been included
@@ -1793,6 +1830,13 @@ int main(int argc, char **argv) {
else
debug(1, "audio backend silence lead-in time is %f seconds.",
config.audio_backend_silent_lead_in_time);
+
+ debug(1, "audio backend silent lead-in initial period is %f seconds.",
+ config.lead_in_silence_initial_period);
+ debug(1, "audio backend silent lead-in minimum adjustments to make is %d.", config.lead_in_silence_minimum_adjustments_to_make);
+ debug(1, "audio backend silent lead-in adjustment interval is %f seconds.",
+ config.lead_in_silence_adjustment_interval);
+
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);
From 54d761ff7d0f08b04bb8a8b45588c08a8daf8ca0 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 18 May 2020 16:55:09 +0100
Subject: [PATCH 086/186] Clean up some of the wording and layout of the
configuration file Clean up and improve the way lead-in sillence is used to
give a more accurate sync time to the first packet of audio. Allow very good
sync to occur with 0.3 seconds of lead-in silence. Improve the use of the
buffer offset. Format some other files.
---
alac.h | 1 -
audio.c | 22 +-
audio_alsa.c | 260 +++++-----
audio_jack.c | 53 +--
audio_sndio.c | 6 +-
audio_soundio.c | 5 +-
common.c | 10 +-
common.h | 21 +-
dacp.c | 33 +-
dbus-service.c | 65 +--
mdns_avahi.c | 2 +-
mdns_dns_sd.c | 2 +-
mdns_external.c | 2 +-
metadata_hub.c | 89 ++--
mpris-service.c | 17 +-
player.c | 766 +++++++++++++++---------------
player.h | 9 +-
rtp.c | 47 +-
rtsp.c | 75 +--
scripts/shairport-sync.conf | 45 +-
shairport-sync-dbus-test-client.c | 26 +-
shairport.c | 247 ++++------
tinysvcmdns.c | 7 +-
23 files changed, 907 insertions(+), 903 deletions(-)
diff --git a/alac.h b/alac.h
index 7cdd507d..51db2c60 100644
--- a/alac.h
+++ b/alac.h
@@ -27,7 +27,6 @@
*
*/
-
#ifndef __ALAC__DECOMP_H
#define __ALAC__DECOMP_H
diff --git a/audio.c b/audio.c
index 4d5df37b..23b1760d 100644
--- a/audio.c
+++ b/audio.c
@@ -191,17 +191,17 @@ void parse_general_audio_options(void) {
/* Get the latency offset in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds",
&dvalue)) {
- config.audio_backend_latency_offset = dvalue;
+ config.audio_backend_latency_offset = dvalue;
}
-
/* Check if the length of the silent lead-in ia set to \"auto\". */
if (config_lookup_string(config.cfg, "general.audio_backend_silent_lead_in_time", &str)) {
if (strcasecmp(str, "auto") == 0) {
config.audio_backend_silent_lead_in_time_auto = 1;
} else {
if (config.audio_backend_silent_lead_in_time_auto == 1)
- warn("Invalid audio_backend_silent_lead_in_time \"%s\". It should be \"auto\" or the lead-in time in seconds. "
+ warn("Invalid audio_backend_silent_lead_in_time \"%s\". It should be \"auto\" or the "
+ "lead-in time in seconds. "
"It remains set to \"auto\". Note: numbers should not be placed in quotes.",
str);
else
@@ -214,14 +214,16 @@ void parse_general_audio_options(void) {
/* Get the desired length of the silent lead-in. */
if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", &dvalue)) {
if ((dvalue < 0.0) || (dvalue > 4)) {
- if (config.audio_backend_silent_lead_in_time_auto == 1)
- warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
- "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. The setting remains at \"auto\".",
- dvalue);
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
+ "The setting remains at \"auto\".",
+ dvalue);
else
- warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
- "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. It remains set to %f.",
- dvalue, config.audio_backend_silent_lead_in_time);
+ warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
+ "It remains set to %f.",
+ dvalue, config.audio_backend_silent_lead_in_time);
} else {
config.audio_backend_silent_lead_in_time = dvalue;
config.audio_backend_silent_lead_in_time_auto = 0;
diff --git a/audio_alsa.c b/audio_alsa.c
index 40287909..69722d12 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -337,8 +337,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.",
+ debug(1,
+ "Error %d (\"%s\") freeing the output device hardware while "
+ "closing it.",
derr, snd_strerror(derr));
if ((derr = snd_pcm_close(alsa_handle)))
@@ -353,7 +354,10 @@ void actual_close_alsa_device() {
// The lowest rate that the DAC is capable of is chosen.
unsigned int auto_speed_output_rates[] = {
- 44100, 88200, 176400, 352800,
+ 44100,
+ 88200,
+ 176400,
+ 352800,
};
// This array is of all the formats known to Shairport Sync, in order of the SPS_FORMAT definitions,
@@ -702,9 +706,10 @@ int actual_open_alsa_device(int do_auto_setup) {
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);
}
@@ -864,120 +869,117 @@ int prepare_mixer() {
// do any alsa device initialisation (general case)
// 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 use
- if (alsa_mix_ctrl == NULL) {
- audio_alsa.volume = NULL;
- audio_alsa.parameters = NULL;
- audio_alsa.mute = NULL;
- } else {
- debug(2, "alsa: hardware mixer prepare");
- int oldState;
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ if (alsa_mix_ctrl == NULL) {
+ audio_alsa.volume = NULL;
+ audio_alsa.parameters = NULL;
+ audio_alsa.mute = NULL;
+ } else {
+ debug(2, "alsa: hardware mixer prepare");
+ 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_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)
- 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_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)
+ 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(3, "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);
- */
+ 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.");
}
- }
- 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.");
+ debug(3, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
+ (1.0 * alsa_mix_maxdb) / 100.0);
} else {
- // debug(1, "Has mixer but not using hardware mute.");
+ // 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);
+ */
}
- close_mixer();
}
- debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
- pthread_cleanup_pop(0);
- pthread_setcancelstate(oldState, NULL);
+ 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_mixer_mutex, 3); // release the mutex
+ pthread_cleanup_pop(0);
+ pthread_setcancelstate(oldState, NULL);
+ }
return response;
}
-int alsa_device_init() {
- return prepare_mixer();
-}
-
+int alsa_device_init() { return prepare_mixer(); }
static int init(int argc, char **argv) {
// for debugging
@@ -1135,8 +1137,10 @@ static int init(int argc, char **argv) {
"\"S16\", \"S24\", \"S24_LE\", \"S24_BE\", "
"\"S24_3LE\", \"S24_3BE\" or "
"\"S32\", \"S32_LE\", \"S32_BE\". It remains set to \"%s\".",
- str, config.output_format_auto_requested == 1 ? "auto" : sps_format_description_string(
- config.output_format));
+ str,
+ config.output_format_auto_requested == 1
+ ? "auto"
+ : sps_format_description_string(config.output_format));
}
}
@@ -1286,9 +1290,9 @@ static int init(int argc, char **argv) {
warn("Invalid use_precision_timing option choice \"%s\". It should be "
"\"yes\", \"auto\" or \"no\". "
"It remains set to \"%s\".",
- config.use_precision_timing == YNA_NO ? "no" : config.use_precision_timing == YNA_AUTO
- ? "auto"
- : "yes");
+ config.use_precision_timing == YNA_NO
+ ? "no"
+ : config.use_precision_timing == YNA_AUTO ? "auto" : "yes");
}
}
@@ -1519,14 +1523,16 @@ int precision_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);
- debug(2, "DAC seems to have stalled with time_now: %lx,%lx"
- ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
- ", stall_monitor_error_threshold %" PRIX64 ".",
+ debug(2,
+ "DAC seems to have stalled with time_now: %lx,%lx"
+ ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
+ ", stall_monitor_error_threshold %" PRIX64 ".",
tn.tv_sec, tn.tv_nsec, update_timestamp.tv_sec, update_timestamp.tv_nsec,
stall_monitor_start_time, stall_monitor_error_threshold);
ret = sps_extra_code_output_stalled;
@@ -1692,8 +1698,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.",
+ 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;
@@ -2007,8 +2014,9 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
error_count++;
char errorstring[1024];
strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
- debug(2, "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
- "to alsa device -- %d errors in %d trials.",
+ debug(2,
+ "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
+ "to alsa device -- %d errors in %d trials.",
ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
if ((error_count > 40) && (frame_count < 100)) {
warn("disable_standby_mode has been turned off because too many underruns "
@@ -2021,8 +2029,8 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
}
}
debug_mutex_unlock(&alsa_mutex, 0);
- pthread_cleanup_pop(0); // release the mutex
- usleep(sleep_time_us); // has a cancellation point in it
+ pthread_cleanup_pop(0); // release the mutex
+ usleep(sleep_time_us); // has a cancellation point in it
}
pthread_exit(NULL);
}
diff --git a/audio_jack.c b/audio_jack.c
index 5caa6c68..21c3e543 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -89,14 +89,9 @@ typedef struct soxr_quality {
const char *name;
} soxr_quality_t;
-static soxr_quality_t soxr_quality_table[] = {
- { SOXR_VHQ, "very high" },
- { SOXR_HQ, "high" },
- { SOXR_MQ, "medium" },
- { SOXR_LQ, "low" },
- { SOXR_QQ, "quick" },
- { -1, NULL }
-};
+static soxr_quality_t soxr_quality_table[] = {{SOXR_VHQ, "very high"}, {SOXR_HQ, "high"},
+ {SOXR_MQ, "medium"}, {SOXR_LQ, "low"},
+ {SOXR_QQ, "quick"}, {-1, NULL}};
static int parse_soxr_quality_name(const char *name) {
for (soxr_quality_t *s = soxr_quality_table; s->name != NULL; ++s) {
@@ -107,9 +102,9 @@ static int parse_soxr_quality_name(const char *name) {
return -1;
}
-static soxr_t soxr = NULL;
+static soxr_t soxr = NULL;
static soxr_quality_spec_t quality_spec;
-static soxr_io_spec_t io_spec;
+static soxr_io_spec_t io_spec;
#endif
static inline sample_t sample_conv(short sample) {
@@ -122,8 +117,7 @@ static inline sample_t sample_conv(short sample) {
return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX));
}
-static void deinterleave(const char *interleaved_input_buffer,
- sample_t *jack_output_buffer[],
+static void deinterleave(const char *interleaved_input_buffer, sample_t *jack_output_buffer[],
jack_nframes_t offset, jack_nframes_t nframes) {
jack_nframes_t f;
// We're dealing with 16bit audio here:
@@ -271,7 +265,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
io_spec = soxr_io_spec(SOXR_INT16_I, SOXR_FLOAT32_I);
} else
#endif
- if (sample_rate != 44100) {
+ if (sample_rate != 44100) {
die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync."
" Must be 44100 Hz.",
sample_rate);
@@ -353,8 +347,7 @@ void jack_deinit() {
#endif
}
-void jack_start(int i_sample_rate,
- __attribute__((unused)) int i_sample_format) {
+void jack_start(int i_sample_rate, __attribute__((unused)) int i_sample_format) {
// Nothing to do, JACK client has already been set up at jack_init().
// Also, we have no say over the sample rate or sample format of JACK,
// We convert the 16bit samples to float, and die if the sample rate is != 44k1 without soxr.
@@ -365,13 +358,7 @@ void jack_start(int i_sample_rate,
soxr_delete(soxr);
}
soxr_error_t e = NULL;
- soxr = soxr_create(i_sample_rate,
- sample_rate,
- NPORTS,
- &e,
- &io_spec,
- &quality_spec,
- NULL);
+ soxr = soxr_create(i_sample_rate, sample_rate, NPORTS, &e, &io_spec, &quality_spec, NULL);
if (!soxr) {
die("Unable to create soxr resampler for JACK: %s", e);
}
@@ -428,13 +415,7 @@ int play(void *buf, int samples) {
size_t i_done, o_done;
soxr_error_t e;
while (samples > 0 && thisbuf > 0) {
- e = soxr_process(soxr,
- (soxr_in_t)in,
- samples,
- &i_done,
- (soxr_out_t)out,
- thisbuf,
- &o_done);
+ e = soxr_process(soxr, (soxr_in_t)in, samples, &i_done, (soxr_out_t)out, thisbuf, &o_done);
if (e)
die("Error during soxr process: %s", e);
@@ -445,13 +426,13 @@ int play(void *buf, int samples) {
}
} else {
#endif
- j = 0;
- for (j = 0; j < thisbuf && samples > 0; ++j) {
- for (c = 0; c < NPORTS; ++c)
- out[j * NPORTS + c] = sample_conv(*in++);
- --samples;
- }
- jack_ringbuffer_write_advance(jackbuf, j * jack_sample_size * NPORTS);
+ j = 0;
+ for (j = 0; j < thisbuf && samples > 0; ++j) {
+ for (c = 0; c < NPORTS; ++c)
+ out[j * NPORTS + c] = sample_conv(*in++);
+ --samples;
+ }
+ jack_ringbuffer_write_advance(jackbuf, j * jack_sample_size * NPORTS);
#ifdef CONFIG_SOXR
}
#endif
diff --git a/audio_sndio.c b/audio_sndio.c
index e7aaaf42..f8582c8a 100644
--- a/audio_sndio.c
+++ b/audio_sndio.c
@@ -260,12 +260,12 @@ static int delay(long *_delay) {
// and use it to estimate the frames that would have been output
uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb;
uint64_t frame_difference = (time_difference * par.rate) / 1000000000;
- estimated_extra_frames_output = frame_difference;
+ estimated_extra_frames_output = frame_difference;
// sanity check -- total estimate can not exceed frames written.
- if ((estimated_extra_frames_output + played) > written/framesize) {
+ if ((estimated_extra_frames_output + played) > written / framesize) {
// debug(1,"play estimate fails sanity check, possibly due to running on a VM");
estimated_extra_frames_output = 0; // can't make any sensible guess
- }
+ }
// debug(1,"Frames played to last cb: %d, estimated to current time:
// %d.",played,estimated_extra_frames_output);
}
diff --git a/audio_soundio.c b/audio_soundio.c
index 119f7670..501c6db2 100644
--- a/audio_soundio.c
+++ b/audio_soundio.c
@@ -24,8 +24,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 2958cfe6..4aba7e1a 100644
--- a/common.c
+++ b/common.c
@@ -1578,9 +1578,11 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t f
return previous_random_number;
}
-// This will check the incoming string "s" of length "len" with the existing NUL-terminated string "str" and update "flag" accordingly.
+// This will check the incoming string "s" of length "len" with the existing NUL-terminated string
+// "str" and update "flag" accordingly.
-// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings are stored.
+// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings
+// are stored.
// If the strings are different, the str is free'd and replaced by a pointer
// to a newly strdup'd string and the flag is set
@@ -1593,7 +1595,7 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
free(*str);
//*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
char *p = malloc(len + 1);
- memcpy(p,s,len);
+ memcpy(p, s, len);
p[len] = '\0';
*str = p;
*flag = 1;
@@ -1610,7 +1612,7 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
if ((s) && (len)) {
//*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
char *p = malloc(len + 1);
- memcpy(p,s,len);
+ memcpy(p, s, len);
p[len] = '\0';
*str = p;
*flag = 1;
diff --git a/common.h b/common.h
index ec9f35a1..e7efc72e 100644
--- a/common.h
+++ b/common.h
@@ -100,10 +100,13 @@ typedef enum {
const char *sps_format_description_string(sps_format_t format);
typedef struct {
- double missing_port_dacp_scan_interval_seconds; // if no DACP port number can be found, check at these intervals
- double resend_control_first_check_time; // wait this long before asking for a missing packet to be resent
+ double missing_port_dacp_scan_interval_seconds; // if no DACP port number can be found, check at
+ // these intervals
+ double resend_control_first_check_time; // wait this long before asking for a missing packet to be
+ // resent
double resend_control_check_interval_time; // wait this long between making requests
- double resend_control_last_check_time; // if the packet is missing this close to the time of use, give up
+ double resend_control_last_check_time; // if the packet is missing this close to the time of use,
+ // give up
pthread_mutex_t lock;
config_t *cfg;
int endianness;
@@ -114,7 +117,7 @@ typedef struct {
// on host %h"
#ifdef CONFIG_PA
- char *pa_server; // the pulseaudio server address that Shairport Sync will play on.
+ char *pa_server; // the pulseaudio server address that Shairport Sync will play on.
char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in
// the Sound Preferences in most desktop Linuxes.
// Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
@@ -212,15 +215,13 @@ typedef struct {
// buffer
double disable_standby_mode_silence_scan_interval; // check the threshold this often
- double lead_in_silence_initial_period; // the size of the first block of silence to send to the DAC
- int lead_in_silence_minimum_adjustments_to_make; // make at least this number of checks and timing adjustments
- double lead_in_silence_adjustment_interval; // make checks and adjustments at this interval
-
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
- int audio_backend_silent_lead_in_time_auto; // true if the lead-in time should be from as soon as packets are received
+ int audio_backend_silent_lead_in_time_auto; // true if the lead-in time should be from as soon as
+ // packets are received
double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
- uint32_t minimum_free_buffer_headroom; // when effective latency is calculated, ensure this number of buffers are unallocated
+ uint32_t minimum_free_buffer_headroom; // when effective latency is calculated, ensure this number
+ // of buffers are unallocated
double active_state_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
diff --git a/dacp.c b/dacp.c
index b8e41d67..82fc0dbb 100644
--- a/dacp.c
+++ b/dacp.c
@@ -154,7 +154,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"dacp_send_command: command is: \"%s\".",command);
if (dacp_server.port == 0) {
- debug(2, "No DACP port specified yet");
+ debug(3, "No DACP port specified yet");
result = 490; // no port specified
} else {
@@ -222,18 +222,16 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
// debug(2, "dacp_send_command: open socket %d.",sockfd);
-
// This is for limiting the time to be spent waiting for a response.
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500000;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
+ debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
debug(1, "dacp_send_command: error %d setting send timeout.", errno);
-
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
@@ -418,7 +416,8 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
// which return immediately with a 403 code if there are no changes.
dacp_server.always_use_revision_number_1 = 0;
char *p = strstr(conn->UserAgent, "forked-daapd");
- if ((p != 0) && (p == conn->UserAgent)) {// must exist and be at the start of the UserAgent string
+ if ((p != 0) &&
+ (p == conn->UserAgent)) { // must exist and be at the start of the UserAgent string
dacp_server.always_use_revision_number_1 = 1;
}
@@ -505,14 +504,15 @@ 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,
+ debug(3,
"setting metadata_store.dacp_server_active and "
"metadata_store.advanced_dacp_server_active to 0 with an update "
"flag value of %d",
ch);
metadata_hub_modify_epilog(ch);
- uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
+ uint64_t time_to_wait_for_wakeup_ns =
+ (uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
@@ -534,11 +534,13 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
result = pthread_cond_timedwait(&dacp_server_information_cv, &dacp_server_information_lock,
&time_to_wait); // this is a pthread cancellation point
- // debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %" PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);
+ // debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %"
+ // PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);
#endif
#ifdef COMPILE_FOR_OSX
- result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv, &dacp_server_information_lock, &time_to_wait);
+ result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv,
+ &dacp_server_information_lock, &time_to_wait);
#endif
}
if (dacp_server.scan_enable == 1) {
@@ -547,12 +549,13 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
}
}
- always_use_revision_number_1 = dacp_server.always_use_revision_number_1; // set this while access is locked
+ always_use_revision_number_1 =
+ dacp_server.always_use_revision_number_1; // set this while access is locked
result = dacp_get_volume(&the_volume); // just want the http code
pthread_cleanup_pop(1);
- if (result == 490) { // 490 means no port was specified
+ if (result == 490) { // 490 means no port was specified
if ((dacp_server.dacp_id != NULL) && (strlen(dacp_server.dacp_id) != 0)) {
// debug(1,"mdns_dacp_monitor_set_id");
mdns_dacp_monitor_set_id(dacp_server.dacp_id);
@@ -597,8 +600,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
else
idle_scan_count = 0;
- debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
- idle_scan_count);
+ debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result,
+ bad_result_count, idle_scan_count);
/* not used
// decide if idle for too long
@@ -934,8 +937,6 @@ void dacp_monitor_start() {
rc = pthread_cond_init(&dacp_server_information_cv, NULL);
#endif
-
-
pthread_mutexattr_t mta;
rc = pthread_mutexattr_init(&mta);
@@ -1248,7 +1249,7 @@ int dacp_get_volume(int32_t *the_actual_volume) {
} else {
debug(1, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
}
- } else if (http_response != 400){
+ } else if (http_response != 400) {
debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response);
}
if (the_actual_volume) {
diff --git a/dbus-service.c b/dbus-service.c
index 457aefed..3c4e6daf 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -24,10 +24,10 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
+#include
#include
#include
#include
-#include
#include "config.h"
@@ -69,8 +69,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairport_sync_remote_control_set_client(shairportSyncRemoteControlSkeleton, argc->client_ip);
-
- // although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their term).
+ // although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their
+ // term).
if (argc->dacp_server_active) {
shairport_sync_remote_control_set_available(shairportSyncRemoteControlSkeleton, TRUE);
} else {
@@ -166,7 +166,6 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairportSyncAdvancedRemoteControlSkeleton, response);
}
-
switch (argc->shuffle_status) {
case SS_NOT_AVAILABLE:
new_status = FALSE;
@@ -188,8 +187,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
// only set this if it's different
if (current_status != new_status) {
debug(3, "Shuffle State should be changed");
- shairport_sync_advanced_remote_control_set_shuffle(
- shairportSyncAdvancedRemoteControlSkeleton, new_status);
+ shairport_sync_advanced_remote_control_set_shuffle(shairportSyncAdvancedRemoteControlSkeleton,
+ new_status);
}
// Build the metadata array
@@ -369,8 +368,9 @@ static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
}
static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleton,
- GDBusMethodInvocation *invocation, const gdouble volume,
- __attribute__((unused)) gpointer user_data) {
+ GDBusMethodInvocation *invocation,
+ const gdouble volume,
+ __attribute__((unused)) gpointer user_data) {
debug(2, "Set airplay volume to %.6f.", volume);
char command[256] = "";
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume);
@@ -379,8 +379,6 @@ static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleto
return TRUE;
}
-
-
gboolean notify_elapsed_time_callback(ShairportSyncDiagnostics *skeleton,
__attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_elapsed_time_callback\" called.");
@@ -408,7 +406,7 @@ gboolean notify_delta_time_callback(ShairportSyncDiagnostics *skeleton,
}
gboolean notify_file_and_line_callback(ShairportSyncDiagnostics *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_file_and_line_callback\" called.");
if (shairport_sync_diagnostics_get_file_and_line(skeleton)) {
config.debugger_show_file_and_line = 1;
@@ -463,12 +461,13 @@ gboolean notify_disable_standby_callback(ShairportSync *skeleton,
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_convolution_callback\" called.");
if (shairport_sync_get_convolution(skeleton)) {
debug(1, ">> activating convolution");
config.convolution = 1;
- config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ config.convolver_valid =
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
} else {
debug(1, ">> deactivating convolution");
config.convolution = 0;
@@ -477,7 +476,7 @@ gboolean notify_convolution_callback(ShairportSync *skeleton,
}
#else
gboolean notify_convolution_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
warn(">> Convolution support is not built in to this build of Shairport Sync.");
return TRUE;
}
@@ -485,7 +484,7 @@ gboolean notify_convolution_callback(__attribute__((unused)) ShairportSync *skel
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_gain_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
gdouble th = shairport_sync_get_convolution_gain(skeleton);
if ((th <= 0.0) && (th >= -100.0)) {
@@ -499,34 +498,38 @@ gboolean notify_convolution_gain_callback(ShairportSync *skeleton,
}
#else
gboolean notify_convolution_gain_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
warn(">> Convolution support is not built in to this build of Shairport Sync.");
return TRUE;
}
#endif
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_impulse_response_file_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused))
+ gpointer user_data) {
char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
if (config.convolution_ir_file)
free(config.convolution_ir_file);
config.convolution_ir_file = strdup(th);
- debug(1, ">> setting configuration impulse response filter file to \"%s\".", config.convolution_ir_file);
- config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ debug(1, ">> setting configuration impulse response filter file to \"%s\".",
+ config.convolution_ir_file);
+ config.convolver_valid =
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
return TRUE;
}
#else
-gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
- __attribute__((unused)) char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
+gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused))
+ ShairportSync *skeleton,
+ __attribute__((unused))
+ gpointer user_data) {
+ __attribute__((unused)) char *th =
+ (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
return TRUE;
}
#endif
-
-
gboolean notify_loudness_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_loudness_callback\" called.");
if (shairport_sync_get_loudness(skeleton)) {
debug(1, ">> activating loudness");
@@ -781,7 +784,6 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
return TRUE;
}
-
static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name,
__attribute__((unused)) gpointer user_data) {
@@ -823,8 +825,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
G_CALLBACK(notify_convolution_gain_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::convolution-impulse-response-file",
G_CALLBACK(notify_convolution_impulse_response_file_callback), NULL);
- g_signal_connect(shairportSyncSkeleton, "notify::loudness",
- G_CALLBACK(notify_loudness_callback), NULL);
+ g_signal_connect(shairportSyncSkeleton, "notify::loudness", G_CALLBACK(notify_loudness_callback),
+ NULL);
g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold",
G_CALLBACK(notify_loudness_threshold_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::drift-tolerance",
@@ -879,7 +881,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-set-airplay-volume",
G_CALLBACK(on_handle_set_airplay_volume), NULL);
-
g_signal_connect(shairportSyncAdvancedRemoteControlSkeleton, "handle-set-volume",
G_CALLBACK(on_handle_set_volume), NULL);
@@ -975,9 +976,11 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
shairport_sync_set_convolution(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
}
if (config.convolution_ir_file)
- shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), config.convolution_ir_file);
+ shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton),
+ config.convolution_ir_file);
// else
-// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), NULL);
+// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton),
+// NULL);
#endif
shairport_sync_set_version(SHAIRPORT_SYNC(shairportSyncSkeleton), PACKAGE_VERSION);
diff --git a/mdns_avahi.c b/mdns_avahi.c
index 575eba08..65bd6477 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -97,7 +97,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
if (dacpid) {
dacpid += strlen("iTunes_Ctrl_");
while (*dacpid == '0')
- dacpid++; // remove any leading zeroes
+ dacpid++; // remove any leading zeroes
if (strcmp(dacpid, dbs->dacp_id) == 0) {
debug(3, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
#ifdef CONFIG_DACP_CLIENT
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 2ae23c61..974aa85b 100644
--- a/mdns_external.c
+++ b/mdns_external.c
@@ -25,8 +25,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 f079ea2a..b2f370ac 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -35,10 +35,10 @@
#include
#include
#include
+#include
#include
#include
#include
-#include
#include "config.h"
@@ -173,39 +173,39 @@ char *metadata_write_image_file(const char *buf, int len) {
// it will return a path to the image file allocated with malloc.
// free it if you don't need it.
- char *path = NULL; // this will be what is returned
- if (strcmp(config.cover_art_cache_dir,"") != 0) { // an empty string means do not write the file
+ char *path = NULL; // this will be what is returned
+ if (strcmp(config.cover_art_cache_dir, "") != 0) { // an empty string means do not write the file
uint8_t img_md5[16];
// uint8_t ap_md5[16];
- #ifdef CONFIG_OPENSSL
+#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, buf, len);
MD5_Final(img_md5, &ctx);
- #endif
+#endif
- #ifdef CONFIG_MBEDTLS
- #if MBEDTLS_VERSION_MINOR >= 7
+#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 *)buf, len);
mbedtls_md5_finish_ret(&tctx, img_md5);
- #else
+#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
+#endif
+#endif
- #ifdef CONFIG_POLARSSL
+#ifdef CONFIG_POLARSSL
md5_context tctx;
md5_starts(&tctx);
md5_update(&tctx, (const unsigned char *)buf, len);
md5_finish(&tctx, img_md5);
- #endif
+#endif
char img_md5_str[33];
memset(img_md5_str, 0, sizeof(img_md5_str));
@@ -232,8 +232,8 @@ char *metadata_write_image_file(const char *buf, int len) {
// if it exists, we're done
char *prefix = "cover-";
- size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 +
- strlen(ext);
+ size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) +
+ 1 + strlen(ext);
path = malloc(pl + 1);
snprintf(path, pl + 1, "%s/%s%s.%s", config.cover_art_cache_dir, prefix, img_md5_str, ext);
@@ -309,30 +309,28 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
if (type == 'core') {
switch (code) {
case 'mper': {
- // get the 64-bit number as a uint64_t by reading two uint32_t s and combining them
- uint64_t vl = ntohl(*(uint32_t*)data); // get the high order 32 bits
- vl = vl << 32; // shift them into the correct location
- uint64_t ul = ntohl(*(uint32_t*)(data+sizeof(uint32_t))); // and the low order 32 bits
- vl = vl + ul;
- debug(2, "MH Item ID seen: \"%" PRIx64 "\" of length %u.", vl, length);
- if (vl != metadata_store.item_id) {
- metadata_store.item_id = vl;
- metadata_store.item_id_changed = 1;
- metadata_store.item_id_received = 1;
- debug(2, "MH Item ID set to: \"%" PRIx64 "\"", metadata_store.item_id);
- }
- }
- break;
- case 'astm': {
- uint32_t ui = ntohl(*(uint32_t *)data);
- debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
- if (ui != metadata_store.songtime_in_milliseconds) {
- metadata_store.songtime_in_milliseconds = ui;
- metadata_store.songtime_in_milliseconds_changed = 1;
- debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
- }
+ // get the 64-bit number as a uint64_t by reading two uint32_t s and combining them
+ uint64_t vl = ntohl(*(uint32_t *)data); // get the high order 32 bits
+ vl = vl << 32; // shift them into the correct location
+ uint64_t ul = ntohl(*(uint32_t *)(data + sizeof(uint32_t))); // and the low order 32 bits
+ vl = vl + ul;
+ debug(2, "MH Item ID seen: \"%" PRIx64 "\" of length %u.", vl, length);
+ if (vl != metadata_store.item_id) {
+ metadata_store.item_id = vl;
+ metadata_store.item_id_changed = 1;
+ metadata_store.item_id_received = 1;
+ debug(2, "MH Item ID set to: \"%" PRIx64 "\"", metadata_store.item_id);
}
- break;
+ } break;
+ case 'astm': {
+ uint32_t ui = ntohl(*(uint32_t *)data);
+ debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
+ if (ui != metadata_store.songtime_in_milliseconds) {
+ metadata_store.songtime_in_milliseconds = ui;
+ metadata_store.songtime_in_milliseconds_changed = 1;
+ debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
+ }
+ } break;
case 'asal':
cs = strndup(data, length);
if (string_update(&metadata_store.album_name, &metadata_store.album_name_changed, cs)) {
@@ -467,7 +465,8 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_hub_modify_prolog();
debug(2, "MH Picture received, length %u bytes.", length);
char uri[2048];
- if ((length > 16) && (strcmp(config.cover_art_cache_dir,"")!=0)) { // if it's okay to write the file
+ if ((length > 16) &&
+ (strcmp(config.cover_art_cache_dir, "") != 0)) { // if it's okay to write the file
char *pathname = metadata_write_image_file(data, length);
snprintf(uri, sizeof(uri), "file://%s", pathname);
free(pathname);
@@ -526,14 +525,16 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
break;
case 'pbeg':
metadata_hub_modify_prolog();
- changed = ((metadata_store.player_state != PS_PLAYING) || (metadata_store.player_thread_active == 0));
+ changed = ((metadata_store.player_state != PS_PLAYING) ||
+ (metadata_store.player_thread_active == 0));
metadata_store.player_state = PS_PLAYING;
metadata_store.player_thread_active = 1;
metadata_hub_modify_epilog(changed);
break;
case 'pend':
metadata_hub_modify_prolog();
- changed = ((metadata_store.player_state != PS_STOPPED) || (metadata_store.player_thread_active == 1));
+ changed = ((metadata_store.player_state != PS_STOPPED) ||
+ (metadata_store.player_thread_active == 1));
metadata_store.player_state = PS_STOPPED;
metadata_store.player_thread_active = 0;
metadata_hub_modify_epilog(changed);
@@ -554,10 +555,10 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
case 'pvol': {
metadata_hub_modify_prolog();
// Note: it's assumed that the config.airplay volume has already been correctly set.
- //int32_t actual_volume;
- //int gv = dacp_get_volume(&actual_volume);
- //metadata_hub_modify_prolog();
- //if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
+ // int32_t actual_volume;
+ // int gv = dacp_get_volume(&actual_volume);
+ // metadata_hub_modify_prolog();
+ // if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
// metadata_store.speaker_volume = actual_volume;
// changed = 1;
//}
diff --git a/mpris-service.c b/mpris-service.c
index 125398a1..8258baf6 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -23,9 +23,9 @@
* 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"
@@ -48,12 +48,12 @@ double airplay_volume_to_mpris_volume(double sp) {
sp = -30.0;
if (sp > 0.0)
sp = 0.0;
- sp = (sp/30.0)+1;
+ sp = (sp / 30.0) + 1;
return sp;
}
double mpris_volume_to_airplay_volume(double sp) {
- sp = (sp-1.0)*30.0;
+ sp = (sp - 1.0) * 30.0;
if (sp < -30.0)
sp = -30.0;
if (sp > 0.0)
@@ -64,7 +64,8 @@ double mpris_volume_to_airplay_volume(double sp) {
void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
// debug(1, "MPRIS metadata watcher called");
char response[100];
- media_player2_player_set_volume(mprisPlayerPlayerSkeleton, airplay_volume_to_mpris_volume(argc->airplay_volume));
+ media_player2_player_set_volume(mprisPlayerPlayerSkeleton,
+ airplay_volume_to_mpris_volume(argc->airplay_volume));
switch (argc->repeat_status) {
case RS_NOT_AVAILABLE:
strcpy(response, "Not Available");
@@ -175,7 +176,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
// Add in the Track ID based on the 'mper' metadata if it is non-zero
if (argc->item_id != 0) {
char trackidstring[128];
- snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "", argc->item_id);
+ snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "",
+ argc->item_id);
GVariant *trackid = g_variant_new("o", trackidstring);
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
}
@@ -276,8 +278,8 @@ static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocati
}
static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton,
- GDBusMethodInvocation *invocation, const gdouble volume,
- __attribute__((unused)) gpointer user_data) {
+ GDBusMethodInvocation *invocation, const gdouble volume,
+ __attribute__((unused)) gpointer user_data) {
double ap_volume = mpris_volume_to_airplay_volume(volume);
debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume);
char command[256] = "";
@@ -334,7 +336,6 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume),
NULL);
-
add_metadata_watcher(mpris_metadata_watcher, NULL);
debug(1, "MPRIS service started at \"%s\" on the %s bus.", name,
diff --git a/player.c b/player.c
index 2d8f635e..344662c3 100644
--- a/player.c
+++ b/player.c
@@ -91,6 +91,14 @@
#include "activity_monitor.h"
+// make the first audio packet deliberately early to bias the sync error of
+// the very first packet, making the error more likely to be too early
+// rather than too late. It it's too early,
+// a delay exactly compensating for it can be sent just before the
+// first packet. This should exactly compensate for the error.
+
+int64_t first_frame_early_bias = 8;
+
// default buffer size
// needs to be a power of 2 because of the way BUFIDX(seqno) works
//#define BUFFER_FRAMES 512
@@ -123,8 +131,11 @@ static void ab_resync(rtsp_conn_info *conn) {
for (i = 0; i < BUFFER_FRAMES; i++) {
conn->audio_buffer[i].ready = 0;
conn->audio_buffer[i].resend_request_number = 0;
- conn->audio_buffer[i].resend_time = 0; // this is either zero or the time the last resend was requested.
- conn->audio_buffer[i].initialisation_time = 0; // this is either the time the packet was received or the time it was noticed the packet was missing.
+ conn->audio_buffer[i].resend_time =
+ 0; // this is either zero or the time the last resend was requested.
+ conn->audio_buffer[i].initialisation_time =
+ 0; // this is either the time the packet was received or the time it was noticed the packet
+ // was missing.
conn->audio_buffer[i].sequence_number = 0;
}
conn->ab_synced = 0;
@@ -323,16 +334,18 @@ int audio_packet_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_c
}
if (outsize > maximum_possible_outsize) {
- 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, maximum_possible_outsize, 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;
}
@@ -486,9 +499,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;
@@ -523,7 +537,8 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
abuf->ready = 0; // to be sure, to be sure
abuf->resend_request_number = 0;
- abuf->initialisation_time = time_now; // this represents when the packet was noticed to be missing
+ abuf->initialisation_time =
+ time_now; // this represents when the packet was noticed to be missing
abuf->status = 1 << 0; // signifying missing
abuf->resend_time = 0;
abuf->given_timestamp = 0;
@@ -534,7 +549,8 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
// rtp_request_resend(ab_write, gap);
// resend_requests++;
conn->ab_write = SUCCESSOR(seqno);
- } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // older than expected but not too late
+ } else if (seq_order(conn->ab_read, seqno,
+ conn->ab_read)) { // older than expected but not too late
conn->late_packets++;
abuf = conn->audio_buffer + BUFIDX(seqno);
} else { // too late.
@@ -567,15 +583,21 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
// resend checks
{
- uint64_t minimum_wait_time = (uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000);
- uint64_t resend_repeat_interval = (uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000);
- uint64_t minimum_remaining_time = (uint64_t)((config.resend_control_last_check_time + config.audio_backend_buffer_desired_length)* (uint64_t)1000000000);
- uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000);
- latency_time = latency_time / (uint64_t)conn->input_rate;
+ uint64_t minimum_wait_time =
+ (uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000);
+ uint64_t resend_repeat_interval =
+ (uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000);
+ uint64_t minimum_remaining_time = (uint64_t)(
+ (config.resend_control_last_check_time + config.audio_backend_buffer_desired_length) *
+ (uint64_t)1000000000);
+ uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000);
+ latency_time = latency_time / (uint64_t)conn->input_rate;
- int x; // this is the first frame to be checked
+ int x; // this is the first frame to be checked
// if we detected a first empty frame before and if it's still in the buffer!
- if ((first_possibly_missing_frame >= 0) && (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read, conn->ab_write, NULL))) {
+ if ((first_possibly_missing_frame >= 0) &&
+ (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read,
+ conn->ab_write, NULL))) {
x = first_possibly_missing_frame;
} else {
x = conn->ab_read;
@@ -583,8 +605,8 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
first_possibly_missing_frame = -1; // has not been set
- int missing_frame_run_count = 0;
- int start_of_missing_frame_run = -1;
+ int missing_frame_run_count = 0;
+ int start_of_missing_frame_run = -1;
int number_of_missing_frames = 0;
while (x != conn->ab_write) {
abuf_t *check_buf = conn->audio_buffer + BUFIDX(x);
@@ -592,46 +614,57 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
if (first_possibly_missing_frame < 0)
first_possibly_missing_frame = x;
number_of_missing_frames++;
- // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%" PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x, check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time);
- int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) || ((check_buf->initialisation_time - (time_now - latency_time)) < minimum_remaining_time));
+ // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%"
+ // PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x,
+ // check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time);
+ int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) ||
+ ((check_buf->initialisation_time - (time_now - latency_time)) <
+ minimum_remaining_time));
int too_early = ((time_now - check_buf->initialisation_time) < minimum_wait_time);
- int too_soon_after_last_request = ((check_buf->resend_time != 0) && ((time_now - check_buf->resend_time) < resend_repeat_interval)); // time_now can never be less than the time_tag
+ int too_soon_after_last_request =
+ ((check_buf->resend_time != 0) &&
+ ((time_now - check_buf->resend_time) <
+ resend_repeat_interval)); // time_now can never be less than the time_tag
if (too_late)
- check_buf->status |= 1<<2; // too late
+ check_buf->status |= 1 << 2; // too late
else
- check_buf->status &= 0xFF-(1<<2); // not too late
+ check_buf->status &= 0xFF - (1 << 2); // not too late
if (too_early)
- check_buf->status |= 1<<3; // too early
+ check_buf->status |= 1 << 3; // too early
else
- check_buf->status &= 0xFF-(1<<3); // not too early
+ check_buf->status &= 0xFF - (1 << 3); // not too early
if (too_soon_after_last_request)
- check_buf->status |= 1<<4; // too soon after last request
+ check_buf->status |= 1 << 4; // too soon after last request
else
- check_buf->status &= 0xFF-(1<<4); // not too soon after last request
+ check_buf->status &= 0xFF - (1 << 4); // not too soon after last request
- if ((!too_soon_after_last_request) && (!too_late) && (!too_early)){
+ if ((!too_soon_after_last_request) && (!too_late) && (!too_early)) {
if (start_of_missing_frame_run == -1) {
start_of_missing_frame_run = x;
missing_frame_run_count = 1;
} else {
missing_frame_run_count++;
}
- check_buf->resend_time = time_now; // setting the time to now because we are definitely going to take action
+ check_buf->resend_time = time_now; // setting the time to now because we are
+ // definitely going to take action
check_buf->resend_request_number++;
- debug(3,"Frame %d is missing with ab_read of %u and ab_write of %u.", x, conn->ab_read, conn->ab_write);
+ debug(3, "Frame %d is missing with ab_read of %u and ab_write of %u.", x,
+ conn->ab_read, conn->ab_write);
}
// if (too_late) {
// debug(1,"too late to get missing frame %u.", x);
// }
}
- //if (number_of_missing_frames != 0)
- // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame);
+ // if (number_of_missing_frames != 0)
+ // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame
+ // = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame);
x = (x + 1) & 0xffff;
if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) {
- // send a resend request
- if (missing_frame_run_count > 1)
- debug(2,"request resend of %d packets starting at seqno %u.", missing_frame_run_count, start_of_missing_frame_run);
+ // send a resend request
+ if (missing_frame_run_count > 1)
+ debug(2, "request resend of %d packets starting at seqno %u.",
+ missing_frame_run_count, start_of_missing_frame_run);
if (config.disable_resend_requests == 0) {
debug_mutex_unlock(&conn->ab_mutex, 3);
rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn);
@@ -660,26 +693,29 @@ int32_t rand_in_range(int32_t exclusive_range_limit) {
return sp >> 32;
}
-int get_and_check_effective_latency(rtsp_conn_info *conn, uint32_t *effective_latency, double offset_time) {
-// check that the overall effective latency remains positive and is not greater than the capacity of the buffers
-// return 0 if okay, -1 if the latency would be negative, +1 if it would be too large
+int get_and_check_effective_latency(rtsp_conn_info *conn, uint32_t *effective_latency,
+ double offset_time) {
+ // check that the overall effective latency remains positive and is not greater than the capacity
+ // of the buffers return 0 if okay, -1 if the latency would be negative, +1 if it would be too
+ // large
- int result = 0;
- if (offset_time >= 0.0) {
- uint32_t latency_addition = (uint32_t)(offset_time * conn->input_rate);
- // keep about one second of buffers back
- if ((*effective_latency + latency_addition) <= (conn->max_frames_per_packet * (BUFFER_FRAMES - config.minimum_free_buffer_headroom)))
- *effective_latency += latency_addition;
- else
- result = 1;
- } else {
- uint32_t latency_reduction = (uint32_t)((-offset_time) * conn->input_rate);
- if (latency_reduction <= *effective_latency)
- *effective_latency -= latency_reduction;
- else
- result = -1;
- }
- return result;
+ int result = 0;
+ if (offset_time >= 0.0) {
+ uint32_t latency_addition = (uint32_t)(offset_time * conn->input_rate);
+ // keep about one second of buffers back
+ if ((*effective_latency + latency_addition) <=
+ (conn->max_frames_per_packet * (BUFFER_FRAMES - config.minimum_free_buffer_headroom)))
+ *effective_latency += latency_addition;
+ else
+ result = 1;
+ } else {
+ uint32_t latency_reduction = (uint32_t)((-offset_time) * conn->input_rate);
+ if (latency_reduction <= *effective_latency)
+ *effective_latency -= latency_reduction;
+ else
+ result = -1;
+ }
+ return result;
}
static inline void process_sample(int32_t sample, char **outp, sps_format_t format, int volume,
@@ -920,7 +956,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
int wait;
long dac_delay = 0; // long because alsa returns a long
- int have_sent_prefiller_silence = 0; // set to true when we have sent at least one silent frame to the DAC
+ int have_sent_prefiller_silence =
+ 0; // set to true when we have sent at least one silent frame to the DAC
pthread_cleanup_push(buffer_get_frame_cleanup_handler,
(void *)conn); // undo what's been done so far
@@ -958,6 +995,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
conn->first_packet_time_to_play = 0;
conn->time_since_play_started = 0;
conn->flush_requested = 0;
+ have_sent_prefiller_silence = 0;
+ dac_delay = 0;
}
debug_mutex_unlock(&conn->flush_mutex, 0);
@@ -990,9 +1029,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_request_number = 0;
@@ -1013,29 +1053,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
notified_buffer_empty = 0; // at least one buffer now -- diagnostic only.
if (conn->ab_buffering) { // if we are getting packets but not yet forwarding them to the
// player
-// int have_sent_prefiller_silence = 1; // set to true when we have sent at least one silent frame to the DAC
- /*
- int64_t reference_timestamp;
- uint64_t reference_timestamp_time, remote_reference_timestamp_time;
- get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
- &remote_reference_timestamp_time, conn);
- reference_timestamp *= conn->output_sample_ratio;
- */
- if (conn->first_packet_timestamp == 0) { // if this is the very first packet
- // debug(1,"First frame seen, time %u, with %d
- // frames...",curframe->timestamp,seq_diff(ab_read, ab_write));
-
+ if (conn->first_packet_timestamp == 0) { // if this is the very first packet
if (have_timestamp_timing_information(conn)) { // if we have a reference time
// debug(1,"First frame seen with timestamp...");
conn->first_packet_timestamp =
curframe->given_timestamp; // we will keep buffering until we are
// supposed to start playing this
- // have_sent_prefiller_silence = 0;
-
-// debug(1, "First packet timestamp is %" PRId64 ".", conn->first_packet_timestamp);
-
-// say we have started playing here
#ifdef CONFIG_METADATA
+ // say we have started receiving frames here
debug(2, "pffr");
send_ssnc_metadata(
'pffr', NULL, 0,
@@ -1046,9 +1071,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// We will send packets of silence from now until that time and then we will send the
// first packet, which will be followed by the subsequent packets.
- // we will get a fix every second or so, which will be stored as a pair consisting of
- // the time when the packet with a particular timestamp should be played, neglecting
- // latencies, etc.
+ // every second or so, we get a reference on when a particular packet should be
+ // played.
// It probably won't be the timestamp of our first packet, however, so we might have
// to do some calculations.
@@ -1061,29 +1085,30 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// that the audio back end has a latency of 100 ms, we would
// ask for the first packet to be emitted 100 ms earlier than it should, i.e. -4410
// frames, so that when it got through the audio back end,
- // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e.
- // -4410 frames.
- // debug(1, "Output sample ratio is %d", conn->output_sample_ratio);
+ // if would be in sync. To do this, we would give it a latency offset of -0.1 sec,
+ // i.e. -4410 frames.
+
+ uint64_t should_be_time;
+ uint32_t effective_latency = conn->latency;
+
+ get_and_check_effective_latency(conn, &effective_latency,
+ config.audio_backend_latency_offset);
+ // we are ignoring the returned status because it will be captured on subsequent
+ // frames, below.
// what we are asking for here is "what is the local time at which time the calculated
// frame should be played"
- uint64_t should_be_time;
- uint32_t effective_latency = conn->latency;
-
- get_and_check_effective_latency(conn, &effective_latency,config.audio_backend_latency_offset);
- // we are ignoring the returned status because it will be captured on subsequent frames, below.
-
- frame_to_local_time(conn->first_packet_timestamp + effective_latency, // this will go modulo 2^32
- &should_be_time,
- conn);
+ frame_to_local_time(conn->first_packet_timestamp +
+ effective_latency, // this will go modulo 2^32
+ &should_be_time, conn);
conn->first_packet_time_to_play = should_be_time;
if (local_time_now > conn->first_packet_time_to_play) {
uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
- debug(3, "First packet is %" PRIu64 " nanoseconds late! Flushing 0.5 seconds",
+ debug(2, "First packet is %" PRIu64 " nanoseconds late! Flushing 0.5 seconds",
lateness);
do_flush(conn->first_packet_timestamp + 5 * 4410, conn);
}
@@ -1093,192 +1118,120 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (conn->first_packet_time_to_play != 0) {
// recalculate conn->first_packet_time_to_play -- the latency might change
- uint64_t should_be_time;
- uint32_t effective_latency = conn->latency;
+ uint64_t should_be_time;
+ uint32_t effective_latency = conn->latency;
- switch (get_and_check_effective_latency(conn, &effective_latency,config.audio_backend_latency_offset)) {
- case -1:
- if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
- warn("Negative latency! A latency of %d frames requested by the player, when combined with an audio_backend_latency_offset of %f seconds, would make the overall latency negative. The audio_backend_latency_offset setting is ignored.", conn->latency, config.audio_backend_latency_offset);
- config.audio_backend_latency_offset = 0; //set it to zero
- conn->unachievable_audio_backend_latency_offset_notified = 1;
- };
-// effective_latency = 0 ;
- break;
- case 1:
- if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
- warn("An audio_backend_latency_offset of %f seconds may exceed the frame buffering capacity -- the setting is ignored.", config.audio_backend_latency_offset);
- config.audio_backend_latency_offset = 0; // set it to zero;
- conn->unachievable_audio_backend_latency_offset_notified = 1;
- };
- break;
- default:
- break;
- }
-
- frame_to_local_time(conn->first_packet_timestamp + effective_latency, // this will go modulo 2^32
- &should_be_time,
- conn);
-
- conn->first_packet_time_to_play = should_be_time;
-
- // we want the frames of silence sent at the start to be fairly large in case the output
- // device's minimum buffer size is large. But they can't be greater than the silent
- // lead_in time
- // which is either the agreed latency or the silent lead-in time specified by the
- // setting
- // In fact, if should be some fraction of them, to allow for adjustment.
-
- int64_t adjustment_interval_frames = (int64_t)(config.lead_in_silence_adjustment_interval * conn->input_rate);
- int64_t initial_silence_frames = (int64_t)(config.lead_in_silence_initial_period * conn->input_rate);
-
- int64_t max_dac_delay = effective_latency;
- if (config.audio_backend_silent_lead_in_time_auto == 0)
- max_dac_delay =
- (int64_t)(config.audio_backend_silent_lead_in_time * conn->input_rate);
-
- // should check that the audio_backend_silent_lead_in_time is greater than the desired_buffer_size
-
- int64_t filler_size = adjustment_interval_frames;
-
- if (have_sent_prefiller_silence == 0) {
- int64_t delay_before_adjustments = max_dac_delay - config.lead_in_silence_minimum_adjustments_to_make * adjustment_interval_frames;
- if (delay_before_adjustments > initial_silence_frames)
- filler_size = initial_silence_frames;
- else
- filler_size = max_dac_delay / (config.lead_in_silence_minimum_adjustments_to_make + 1);
- // debug(1,"Initial filler size: %" PRId64 " frames.", filler_size);
+ switch (get_and_check_effective_latency(conn, &effective_latency,
+ config.audio_backend_latency_offset)) {
+ case -1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Negative latency! A latency of %d frames requested by the player, when "
+ "combined with an audio_backend_latency_offset of %f seconds, would make the "
+ "overall latency negative. The audio_backend_latency_offset setting is "
+ "ignored.",
+ conn->latency, config.audio_backend_latency_offset);
+ config.audio_backend_latency_offset = 0; // set it to zero
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ break;
+ case 1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("An audio_backend_latency_offset of %f seconds may exceed the frame buffering "
+ "capacity -- the setting is ignored.",
+ config.audio_backend_latency_offset);
+ config.audio_backend_latency_offset = 0; // set it to zero;
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ break;
+ default:
+ break;
}
+ frame_to_local_time(conn->first_packet_timestamp +
+ effective_latency, // this will go modulo 2^32
+ &should_be_time, conn);
+
+ conn->first_packet_time_to_play = should_be_time;
+
if (local_time_now > conn->first_packet_time_to_play) {
uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
- debug(3, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness);
+ debug(2, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness);
// have_sent_prefiller_silence = 1;
conn->ab_buffering = 0;
-
- // we've gone past the time...
- // debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp
- // of %llx and dac_delay of %d and %d packets;
- // flush.",(((tn-conn->first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read,
- // ab_write));
-
- /*
- if (config.output->flush)
- config.output->flush();
- ab_resync(conn);
- conn->first_packet_timestamp = 0;
- conn->first_packet_time_to_play = 0;
- conn->time_since_play_started = 0;
- */
} else {
// do some calculations
int64_t lead_time = conn->first_packet_time_to_play - local_time_now;
- if ((config.audio_backend_silent_lead_in_time_auto == 1) || (lead_time <= (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) {
- // debug(1,"Lead time: %" PRId64 ".", lead_time);
-
- // debug(1,"Checking");
- if (config.output->delay) {
- // conn->first_packet_time_to_play is definitely later than local_time_now
+ if ((config.audio_backend_silent_lead_in_time_auto == 1) ||
+ (lead_time <=
+ (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) {
+ debug(3, "Lead time: %" PRId64 ".", lead_time);
+ if (config.output->delay) { // if the output device has a delay function
+ debug(3, "Checking");
int resp = 0;
dac_delay = 0;
if (have_sent_prefiller_silence != 0)
resp = config.output->delay(&dac_delay);
if (resp == 0) {
int64_t gross_frame_gap =
- ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) / 1000000000;
+ ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) /
+ 1000000000;
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
- // debug(1,"Exact and gross frame gaps are %" PRId64 " and %" PRId64 " frames,
- // and the dac delay is %ld.", exact_frame_gap, gross_frame_gap, dac_delay);
- if (exact_frame_gap < 0) {
- // we've gone past the time...
- // debug(1,"Run past time.");
+ int64_t frames_needed_to_maintain_desired_buffer =
+ (int64_t)(config.audio_backend_buffer_desired_length * config.output_rate) -
+ dac_delay;
+ // below, remember that exact_frame_gap and
+ // frames_needed_to_maintain_desired_buffer could both be negative
+ int64_t fs = frames_needed_to_maintain_desired_buffer;
- // this might happen if a big clock adjustment was made at just the wrong
- // time.
+ // if there isn't enough time to have the desired buffer size
+ if (exact_frame_gap <= frames_needed_to_maintain_desired_buffer) {
+ fs = conn->max_frames_per_packet * 2;
+ }
- 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();
- ab_resync(conn);
- conn->first_packet_timestamp = 0;
- conn->first_packet_time_to_play = 0;
- } else {
- int64_t fs = filler_size;
- if (fs > (max_dac_delay - dac_delay))
- fs = max_dac_delay - dac_delay;
- if (fs < 0) {
- // this could happen if the dac delay mysteriously grows between samples,
- // which could happen in a transition between having no interpolation and
- // having interpolated buffer numbers.
-
- // this will happen benignly if standby is being prevented, because a
- // thread in the alsa back end will be stuffing frames of silence in there
- // to keep it busy
-
- debug(3,
- "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",
- max_dac_delay, dac_delay);
- fs = 0;
+ // if we are very close to the end of buffering, i.e. within to frame-lengths,
+ // add the remaining silence needed and end buffering
+ if (exact_frame_gap <= conn->max_frames_per_packet * 2) {
+ fs = exact_frame_gap;
+ if (fs > first_frame_early_bias)
+ fs = fs - first_frame_early_bias; // deliberately make the first packet a tiny bit early so that the player may compensate for it at the last minute
+ conn->ab_buffering = 0;
+ }
+ void *silence;
+ if (fs > 0) {
+ silence = malloc(conn->output_bytes_per_frame * fs);
+ if (silence == NULL)
+ debug(1, "Failed to allocate %d byte silence buffer.", fs);
+ else {
+ // generate frames of silence with dither if necessary
+ conn->previous_random_number =
+ generate_zero_frames(silence, fs, config.output_format,
+ conn->enable_dither, conn->previous_random_number);
+ config.output->play(silence, fs);
+ debug(3, "Sent %" PRId64 " frames of silence", fs);
+ free(silence);
+ have_sent_prefiller_silence = 1;
}
- if ((exact_frame_gap <= fs) ||
- (exact_frame_gap <= conn->max_frames_per_packet * 2)) {
- fs = exact_frame_gap;
- // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is
- // %d,
- // with %d packets, ab_read is %04x, ab_write is
- // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read,
- // ab_write),ab_read,ab_write);
- conn->ab_buffering = 0;
- }
- void *silence;
- // if (fs==0)
- // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld
- // and
- // dac_delay of %lld.",gross_frame_gap,dac_delay);
- // the fs (number of frames of silence to play) can be zero in the DAC doesn't
- // start
- // outputting frames for a while -- it could get loaded up but not start
- // responding
- // for many milliseconds.
- if (fs > 0) {
- silence = malloc(conn->output_bytes_per_frame * fs);
- if (silence == NULL)
- debug(1, "Failed to allocate %d byte silence buffer.", fs);
- else {
-
- conn->previous_random_number = generate_zero_frames(
- silence, fs, config.output_format, conn->enable_dither,
- conn->previous_random_number);
-
- // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d
- // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read,
- // conn->ab_write, conn->ab_read));
- config.output->play(silence, fs);
- // debug(1,"Sent %" PRId64 " frames of silence",fs);
- free(silence);
- }
- }
- have_sent_prefiller_silence =
- 1; // even if we haven't sent silence because it's zero frames long...
}
} else {
- if ((resp == sps_extra_code_output_stalled) &&
- (conn->unfixable_error_reported == 0)) {
- conn->unfixable_error_reported = 1;
- if (config.cmd_unfixable) {
- command_execute(config.cmd_unfixable, "output_device_stalled", 1);
- } else {
- warn(
- "an unrecoverable error, \"output_device_stalled\", has been detected.",
- conn->connection_number);
+
+ if (resp == sps_extra_code_output_stalled) {
+ if (conn->unfixable_error_reported == 0) {
+ conn->unfixable_error_reported = 1;
+ if (config.cmd_unfixable) {
+ command_execute(config.cmd_unfixable, "output_device_stalled", 1);
+ } else {
+ warn("an unrecoverable error, \"output_device_stalled\", has been "
+ "detected.",
+ conn->connection_number);
+ }
}
+ } else {
+ debug(2, "Unexpected response to getting dac delay: %d.", resp);
}
}
} else {
// no delay function on back end -- just send the prefiller silence
- // debug(1,"Back end has no delay function.");
+ // debug(2,"Back end has no delay function.");
// send the appropriate prefiller here...
void *silence;
@@ -1351,40 +1304,50 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (have_timestamp_timing_information(conn)) { // if we have a reference time
uint64_t time_to_play;
- uint32_t effective_latency = conn->latency;
-
- switch (get_and_check_effective_latency(conn, &effective_latency,config.audio_backend_latency_offset - config.audio_backend_buffer_desired_length)) {
- case -1:
- // this means that the latency is negative, i.e. the packet must be played before its time. This is
- // clearly a mistake, and can arise if the combination of a large negative latency offset and the desired buffer length are greater than the
- // latency chosen by the player. For example if the latency is 88200 frames (2 seconds), the audio_backend_latency_offset is -1.9 and the audio_backend_buffer_desired_length is 0.2
- // the effective latency is -0.1, so the frame must be played 0.1 seconds before it is time-tagged for, and in reality before it arrives at the player.
-
- // to deal with the problem, rather than block the packets, we'll just let 'em go...
-
- //
- if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
- warn("Negative latency! A latency of %d frames requested by the player, when combined with an audio_backend_latency_offset of %f seconds an audio_backend_buffer_desired_length of %f seconds, would make the overall latency negative. No latency is used. Synchronisation may fail.", conn->latency, config.audio_backend_latency_offset, config.audio_backend_buffer_desired_length);
- conn->unachievable_audio_backend_latency_offset_notified = 1;
- };
- time_to_play = local_time_now; // pretend the frame should be played now...
- break;
- case 1:
- if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
- warn("Latency too long! An audio_backend_latency_offset of %f seconds combined with an audio_backend_buffer_desired_length of %f seconds may exceed the frame buffering capacity.", config.audio_backend_latency_offset, config.audio_backend_buffer_desired_length);
- conn->unachievable_audio_backend_latency_offset_notified = 1;
- };
- time_to_play = local_time_now; // pretend the frame should be played now...
- break;
- default:
- frame_to_local_time(curframe->given_timestamp + effective_latency, // this will go modulo 2^32
- &time_to_play,
- conn);
- break;
- }
+ uint32_t effective_latency = conn->latency;
+ switch (get_and_check_effective_latency(conn, &effective_latency,
+ config.audio_backend_latency_offset -
+ config.audio_backend_buffer_desired_length)) {
+ case -1:
+ // this means that the latency is negative, i.e. the packet must be played before its
+ // time. This is clearly a mistake, and can arise if the combination of a large negative
+ // latency offset and the desired buffer length are greater than the latency chosen by the
+ // player. For example if the latency is 88200 frames (2 seconds), the
+ // audio_backend_latency_offset is -1.9 and the audio_backend_buffer_desired_length is 0.2
+ // the effective latency is -0.1, so the frame must be played 0.1 seconds before it is
+ // time-tagged for, and in reality before it arrives at the player.
+ // to deal with the problem, rather than block the packets, we'll just let 'em go...
+ //
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Negative latency! A latency of %d frames requested by the player, when combined "
+ "with an audio_backend_latency_offset of %f seconds an "
+ "audio_backend_buffer_desired_length of %f seconds, would make the overall "
+ "latency negative. No latency is used. Synchronisation may fail.",
+ conn->latency, config.audio_backend_latency_offset,
+ config.audio_backend_buffer_desired_length);
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ time_to_play = local_time_now; // pretend the frame should be played now...
+ break;
+ case 1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Latency too long! An audio_backend_latency_offset of %f seconds combined with an "
+ "audio_backend_buffer_desired_length of %f seconds may exceed the frame "
+ "buffering capacity.",
+ config.audio_backend_latency_offset, config.audio_backend_buffer_desired_length);
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ time_to_play = local_time_now; // pretend the frame should be played now...
+ break;
+ default:
+ frame_to_local_time(curframe->given_timestamp +
+ effective_latency, // this will go modulo 2^32
+ &time_to_play, conn);
+ break;
+ }
if (local_time_now >= time_to_play) {
do_wait = 0;
@@ -1398,7 +1361,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
notified_buffer_empty = 1;
// reset_input_flow_metrics(conn); // don't do a full flush parameters reset
conn->initial_reference_time = 0;
- conn->initial_reference_timestamp = 0;
+ conn->initial_reference_timestamp = 0;
}
do_wait = 1;
}
@@ -1406,9 +1369,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (wait) {
uint64_t time_to_wait_for_wakeup_ns =
- 1000000000 / conn->input_rate; // this is time period of one frame
- time_to_wait_for_wakeup_ns *= 2 * 352; // two full 352-frame packets
- time_to_wait_for_wakeup_ns /= 3; // two thirds of a packet time
+ 1000000000 / conn->input_rate; // this is time period of one frame
+ time_to_wait_for_wakeup_ns *= 2 * 352; // two full 352-frame packets
+ time_to_wait_for_wakeup_ns /= 3; // two thirds of a packet time
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
uint64_t time_of_wakeup_ns = local_time_now + time_to_wait_for_wakeup_ns;
@@ -1574,8 +1537,7 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
// mean and variance calculations from "online_variance" algorithm at
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
- double soxr_execution_time =
- (get_absolute_time_in_ns() - soxr_start_time) * 0.000000001;
+ double soxr_execution_time = (get_absolute_time_in_ns() - soxr_start_time) * 0.000000001;
// debug(1,"soxr_execution_time_us: %10.1f",soxr_execution_time_us);
if (soxr_execution_time > longest_soxr_execution_time)
longest_soxr_execution_time = soxr_execution_time;
@@ -1628,9 +1590,10 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
}
if (packets_processed % 1250 == 0) {
- debug(3, "soxr_oneshot execution time in nanoseconds: mean, standard deviation and max "
- "for %" PRId32 " interpolations in the last "
- "1250 packets. %10.6f, %10.6f, %10.6f.",
+ debug(3,
+ "soxr_oneshot execution time in nanoseconds: mean, standard deviation and max "
+ "for %" PRId32 " interpolations in the last "
+ "1250 packets. %10.6f, %10.6f, %10.6f.",
stat_n, stat_mean, stat_n <= 1 ? 0.0 : sqrtf(stat_M2 / (stat_n - 1)),
longest_soxr_execution_time);
stat_n = 0;
@@ -1826,6 +1789,7 @@ void *player_thread_func(void *arg) {
stats_t statistics[trend_interval];
int number_of_statistics, oldest_statistic, newest_statistic;
int at_least_one_frame_seen = 0;
+ int at_least_one_frame_seen_this_session = 0;
int64_t tsum_of_sync_errors, tsum_of_corrections, tsum_of_insertions_and_deletions,
tsum_of_drifts;
int64_t previous_sync_error = 0, previous_correction = 0;
@@ -1920,17 +1884,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.");
@@ -2040,8 +2004,11 @@ void *player_thread_func(void *arg) {
// debug(3, "Play frame %d.", play_number);
conn->play_number_after_flush++;
if (inframe->given_timestamp == 0) {
- debug(1, "Player has supplied a silent frame, (possibly frame %u) for play number %d, status 0x%X after %u resend requests.",
- SUCCESSOR(conn->last_seqno_read), play_number, inframe->status, inframe->resend_request_number);
+ debug(1,
+ "Player has supplied a silent frame, (possibly frame %u) for play number %d, "
+ "status 0x%X after %u resend requests.",
+ SUCCESSOR(conn->last_seqno_read), play_number, inframe->status,
+ inframe->resend_request_number);
conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
0xffff); // manage the packet out of sequence minder
@@ -2162,8 +2129,6 @@ void *player_thread_func(void *arg) {
// If it's late, we remove an audio frame from this frame to bring a subsequent frame
// forward in time
- at_least_one_frame_seen = 1;
-
// now, go back as far as the total latency less, say, 100 ms, and check the presence of
// frames from then onwards
@@ -2194,7 +2159,8 @@ void *player_thread_func(void *arg) {
// should be negated. Conversion is
// positive uint64_t to int64_t, thus
// okay.
- td_in_frames = (td * config.output_rate) / 1000000000 ; // use the absolute td value for the present. Types okay
+ td_in_frames = (td * config.output_rate) /
+ 1000000000; // use the absolute td value for the present. Types okay
td_in_frames = -td_in_frames;
td = -td; // should be okay, as the range of values should be very small w.r.t 64 bits
}
@@ -2213,8 +2179,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...
}
@@ -2306,7 +2273,48 @@ void *player_thread_func(void *arg) {
(int64_t)(config.audio_backend_latency_offset *
config.output_rate)); // int64_t from int64_t - int32_t, so okay
- // debug(1,"%" PRId64 "",sync_error,inbuflength);
+ if (at_least_one_frame_seen_this_session == 0) {
+ at_least_one_frame_seen_this_session = 1;
+
+ // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", sync_error);
+
+ // this is a sneaky attempt to make a final adjustment to the timing of the first packet
+
+ // the very first packet generally has a first_frame_early_bias subtracted from its timing
+ // to make it more likely that it will be early than late,
+ // making it possible to compensate for it be adding a few frames of silence.
+
+ // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", sync_error);
+
+ // remove the bias when reporting the error to make it the true error
+
+ debug(2,"first frame sync error (positive --> late): %" PRId64 " frames, %.3f mS at %d frames per second output.", sync_error+first_frame_early_bias, (1000.0*(sync_error+first_frame_early_bias))/config.output_rate, config.output_rate);
+
+ // if the packet is early, add the frames needed to put it in sync.
+ if (sync_error < 0) {
+ size_t final_adjustment_length_sized = -sync_error;
+ char *final_adjustment_silence = malloc(conn->output_bytes_per_frame * final_adjustment_length_sized);
+ if (final_adjustment_silence) {
+
+ conn->previous_random_number =
+ generate_zero_frames(final_adjustment_silence, final_adjustment_length_sized, config.output_format,
+ conn->enable_dither, conn->previous_random_number);
+ int final_adjustment = -sync_error;
+ final_adjustment = final_adjustment - first_frame_early_bias;
+ debug(2, "final sync adjustment: %d frames.", final_adjustment);
+ config.output->play(final_adjustment_silence, final_adjustment_length_sized);
+ free(final_adjustment_silence);
+ } else {
+ warn("Failed to allocate memory for a final_adjustment_silence buffer of %d frames for a "
+ "sync error of %d frames.",
+ final_adjustment_length_sized, sync_error);
+ }
+ sync_error = 0; // say the error was fixed!
+ }
+ }
+
+
+ at_least_one_frame_seen = 1;
// not too sure if abs() is implemented for int64_t, so we'll do it manually
int64_t abs_sync_error = sync_error;
@@ -2416,7 +2424,9 @@ void *player_thread_func(void *arg) {
if ((local_time_now) && (conn->first_packet_time_to_play) &&
(local_time_now >= conn->first_packet_time_to_play)) {
- int64_t tp = (local_time_now - conn->first_packet_time_to_play) / 1000000000; // seconds int64_t from uint64_t which is always positive, so ok
+ int64_t tp =
+ (local_time_now - conn->first_packet_time_to_play) /
+ 1000000000; // seconds int64_t from uint64_t which is always positive, so ok
if (tp < 5)
amount_to_stuff = 0; // wait at least five seconds
@@ -2435,17 +2445,18 @@ void *player_thread_func(void *arg) {
// Apply DSP here
- // check the state of loudness and convolution flags here and don't change them for the frame
+ // check the state of loudness and convolution flags here and don't change them for
+ // the frame
int do_loudness = config.loudness;
-
#ifdef CONFIG_CONVOLUTION
int do_convolution = 0;
if ((config.convolution) && (config.convolver_valid))
do_convolution = 1;
- // we will apply the convolution gain if convolution is enabled, even if there is no valid convolution happening
+ // we will apply the convolution gain if convolution is enabled, even if there is no
+ // valid convolution happening
int convolution_is_enabled = 0;
if (config.convolution)
@@ -2456,7 +2467,7 @@ void *player_thread_func(void *arg) {
#ifdef CONFIG_CONVOLUTION
|| convolution_is_enabled
#endif
- ) {
+ ) {
int32_t *tbuf32 = (int32_t *)conn->tbuf;
float fbuf_l[inbuflength];
float fbuf_r[inbuflength];
@@ -2511,7 +2522,7 @@ void *player_thread_func(void *arg) {
((config.packet_stuffing == ST_auto) &&
(config.soxr_delay_index >
config.soxr_delay_threshold)) // if the CPU is deemed too slow
- ) {
+ ) {
#endif
play_samples =
stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format,
@@ -2672,7 +2683,7 @@ void *player_thread_func(void *arg) {
conn->input_frame_rate =
(1.0E9 * frames_received) /
elapsed_reception_time; // an IEEE double calculation with two 64-bit integers
- } else {
+ } else {
conn->input_frame_rate = 0.0;
}
@@ -2704,42 +2715,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 */
@@ -2754,10 +2764,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,
@@ -2776,10 +2785,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,
@@ -2879,13 +2887,15 @@ 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.",
+ 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.",
+ debug(2,
+ "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
+ "software mute is enabled.",
volume_mode, airplay_volume);
}
@@ -2990,14 +3000,15 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
}
#ifdef CONFIG_METADATA
- // here, send the 'pvol' metadata message when the airplay volume information
- // is being used by shairport sync to control the output volume
+ // here, send the 'pvol' metadata message when the airplay volume information
+ // is being used by shairport sync to control the output volume
char *dv = malloc(128); // will be freed in the metadata thread
if (dv) {
memset(dv, 0, 128);
if (volume_mode == vol_both) {
// normalise the maximum output to the hardware device's max output
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, (scaled_attenuation - max_db + hw_max_db) / 100.0,
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume,
+ (scaled_attenuation - max_db + hw_max_db) / 100.0,
(min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
} else {
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
@@ -3007,32 +3018,31 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
}
#endif
-
if (config.output->mute)
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 "
- "is disabled.",
+ 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);
}
}
#ifdef CONFIG_METADATA
- else {
- // here, send the 'pvol' metadata message when the airplay volume information
- // is being used by shairport sync to control the output volume
- char *dv = malloc(128); // will be freed in the metadata thread
- if (dv) {
- memset(dv, 0, 128);
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
- send_ssnc_metadata('pvol', dv, strlen(dv), 1);
- }
+ else {
+ // here, send the 'pvol' metadata message when the airplay volume information
+ // is being used by shairport sync to control the output volume
+ char *dv = malloc(128); // will be freed in the metadata thread
+ if (dv) {
+ memset(dv, 0, 128);
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
+ send_ssnc_metadata('pvol', dv, strlen(dv), 1);
+ }
}
#endif
-
// here, store the volume for possible use in the future
config.airplay_volume = airplay_volume;
debug_mutex_unlock(&conn->volume_control_mutex, 3);
diff --git a/player.h b/player.h
index 483d4fee..1db4b36f 100644
--- a/player.h
+++ b/player.h
@@ -43,10 +43,11 @@ typedef struct audio_buffer_entry { // decoded audio packets
uint16_t resend_request_number;
signed short *data;
seq_t sequence_number;
- uint64_t initialisation_time; // the time the packet was added or the time it was noticed the packet was missing
- uint64_t resend_time; // time of last resend request or zero
- uint32_t given_timestamp; // for debugging and checking
- int length; // the length of the decoded data
+ uint64_t initialisation_time; // the time the packet was added or the time it was noticed the
+ // packet was missing
+ uint64_t resend_time; // time of last resend request or zero
+ uint32_t given_timestamp; // for debugging and checking
+ int length; // the length of the decoded data
} abuf_t;
// default buffer size
diff --git a/rtp.c b/rtp.c
index 70a54a36..f7e12c50 100644
--- a/rtp.c
+++ b/rtp.c
@@ -74,9 +74,13 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) {
uint64_t result = conn->local_to_remote_time_difference;
if (conn->local_to_remote_time_gradient >= 1.0) {
- result = conn->local_to_remote_time_difference + (uint64_t)((conn->local_to_remote_time_gradient - 1.0) * time_since_last_local_to_remote_time_difference_measurement);
+ result = conn->local_to_remote_time_difference +
+ (uint64_t)((conn->local_to_remote_time_gradient - 1.0) *
+ time_since_last_local_to_remote_time_difference_measurement);
} else {
- result = conn->local_to_remote_time_difference - (uint64_t)((1.0 - conn->local_to_remote_time_gradient) * time_since_last_local_to_remote_time_difference_measurement);
+ result = conn->local_to_remote_time_difference -
+ (uint64_t)((1.0 - conn->local_to_remote_time_gradient) *
+ time_since_last_local_to_remote_time_difference_measurement);
}
return result;
}
@@ -111,8 +115,7 @@ void *rtp_audio_receiver(void *arg) {
uint64_t local_time_now_ns = get_absolute_time_in_ns();
if (time_of_previous_packet_ns) {
- float time_interval_us =
- (local_time_now_ns - time_of_previous_packet_ns) * 0.001;
+ float time_interval_us = (local_time_now_ns - time_of_previous_packet_ns) * 0.001;
time_of_previous_packet_ns = local_time_now_ns;
if (time_interval_us > longest_packet_time_interval_us)
longest_packet_time_interval_us = time_interval_us;
@@ -121,8 +124,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;
@@ -290,7 +294,7 @@ void *rtp_control_receiver(void *arg) {
ps = ps * 1000000000; // this many nanoseconds from the whole seconds
pn = nctohl(&packet[12]);
pn = pn * 1000000000;
- pn = pn >> 32; // this many nanoseconds from the fractional part
+ pn = pn >> 32; // this many nanoseconds from the fractional part
remote_time_of_sync = ps + pn;
// debug(1,"Remote Sync Time: " PRIu64 "",remote_time_of_sync);
@@ -354,9 +358,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);
}
@@ -573,7 +578,7 @@ void *rtp_timing_receiver(void *arg) {
ps = ps * 1000000000; // this many nanoseconds from the whole seconds
pn = nctohl(&packet[20]);
pn = pn * 1000000000;
- pn = pn >> 32; // this many nanoseconds from the fractional part
+ pn = pn >> 32; // this many nanoseconds from the fractional part
distant_receive_time = ps + pn;
// distant_transmit_time =
@@ -583,7 +588,7 @@ void *rtp_timing_receiver(void *arg) {
ps = ps * 1000000000; // this many nanoseconds from the whole seconds
pn = nctohl(&packet[28]);
pn = pn * 1000000000;
- pn = pn >> 32; // this many nanoseconds from the fractional part
+ pn = pn >> 32; // this many nanoseconds from the fractional part
distant_transmit_time = ps + pn;
uint64_t remote_processing_time = 0;
@@ -777,7 +782,9 @@ void *rtp_timing_receiver(void *arg) {
// debug(1,"local to remote time gradient is %12.2f ppm, based on %d
// samples.",conn->local_to_remote_time_gradient*1000000,sample_count);
} else {
- debug(2, "Time ping turnaround time: " PRIu64 " ns -- it looks like a timing ping was lost.",
+ debug(2,
+ "Time ping turnaround time: %" PRIu64
+ " ns -- it looks like a timing ping was lost.",
return_time);
}
} else {
@@ -1029,7 +1036,7 @@ const int use_nominal_rate = 0; // specify whether to use the nominal input rate
int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn) {
int result = 1;
uint32_t fs = conn->input_rate;
- *frames = fs; // default value to return
+ *frames = fs; // default value to return
*time = 1000000000; // default value to return
if ((conn->initial_reference_time) && (conn->initial_reference_timestamp)) {
// uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp;
@@ -1178,22 +1185,20 @@ 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(2, "Error %d using sendto to request a resend: \"%s\".",
- errno, em);
+ debug(2, "Error %d using sendto to request a resend: \"%s\".", errno, em);
conn->rtp_time_of_last_resend_request_error_ns = time_of_sending_ns;
} else {
conn->rtp_time_of_last_resend_request_error_ns = 0;
}
} else {
- debug(
- 3,
- "Dropping resend request packet to simulate a bad network. Backing off for 0.3 "
- "second.");
+ debug(3, "Dropping resend request packet to simulate a bad network. Backing off for 0.3 "
+ "second.");
conn->rtp_time_of_last_resend_request_error_ns = time_of_sending_ns;
}
} else {
- debug(1, "Suppressing a resend request due to a resend sendto error in the last 0.3 seconds.");
+ debug(1,
+ "Suppressing a resend request due to a resend sendto error in the last 0.3 seconds.");
}
} else {
// if (!request_sent) {
diff --git a/rtsp.c b/rtsp.c
index 4013acb9..a1252133 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -286,15 +286,17 @@ void *player_watchdog_thread_code(void *arg) {
uint64_t last_watchdog_bark_time = conn->watchdog_bark_time;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
if (last_watchdog_bark_time != 0) {
- uint64_t time_since_last_bark = (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000;
+ uint64_t time_since_last_bark =
+ (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000;
uint64_t ct = config.timeout; // go from int to 64-bit int
if (time_since_last_bark >= ct) {
conn->watchdog_barks++;
if (conn->watchdog_barks == 1) {
// debuglev = 3; // tell us everything.
- 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);
@@ -571,7 +573,7 @@ int msg_handle_line(rtsp_message **pmsg, char *line) {
}
fail:
- debug(3,"msg_handle_line fail");
+ debug(3, "msg_handle_line fail");
msg_free(pmsg);
*pmsg = NULL;
return 0;
@@ -614,7 +616,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
if (errno == EINTR)
continue;
if (errno == EAGAIN) {
- debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!", conn->connection_number);
+ debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!",
+ conn->connection_number);
continue;
}
if (errno != ECONNRESET) {
@@ -627,15 +630,15 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
goto shutdown;
}
-/* // this outputs the message received
- {
- void *pt = malloc(nread+1);
- memset(pt, 0, nread+1);
- memcpy(pt, buf + inbuf, nread);
- debug(1, "Incoming string on port: \"%s\"",pt);
- free(pt);
- }
-*/
+ /* // this outputs the message received
+ {
+ void *pt = malloc(nread+1);
+ memset(pt, 0, nread+1);
+ memcpy(pt, buf + inbuf, nread);
+ debug(1, "Incoming string on port: \"%s\"",pt);
+ free(pt);
+ }
+ */
inbuf += nread;
@@ -644,7 +647,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
msg_size = msg_handle_line(the_packet, buf);
if (!(*the_packet)) {
- debug(1,"Connection %d: rtsp_read_request can't find an RTSP header.", conn->connection_number);
+ debug(1, "Connection %d: rtsp_read_request can't find an RTSP header.",
+ conn->connection_number);
reply = rtsp_read_request_response_bad_packet;
goto shutdown;
}
@@ -851,9 +855,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,
@@ -994,8 +999,9 @@ 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 DACP-ID \"%s\" from %s to %s with UDP ports Control: "
- "%d, Timing: %d and Audio: %d.",
+ debug(1,
+ "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: "
+ "%d, Timing: %d and Audio: %d.",
conn->connection_number, conn->dacp_id, &conn->client_ip_string,
&conn->self_ip_string, conn->local_control_port, conn->local_timing_port,
conn->local_audio_port);
@@ -1590,7 +1596,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
@@ -1888,7 +1894,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
unsigned int i = 0;
unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]);
- char* found;
+ char *found;
while ((found = strsep(&pfmtp, " \t")) != NULL && i < max_param) {
conn->stream.fmtp[i++] = atoi(found);
}
@@ -2370,8 +2376,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);
@@ -2420,8 +2427,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"
@@ -2474,10 +2482,11 @@ static void *rtsp_conversation_thread_func(void *pconn) {
if (reply == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno, (char *)errorstring);
+ debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno,
+ (char *)errorstring);
} else if (reply != (ssize_t)strlen(response_text)) {
- debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.", strlen(response_text),
- reply);
+ debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.",
+ strlen(response_text), reply);
}
} else {
debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.",
@@ -2690,7 +2699,7 @@ void rtsp_listen_loop(void) {
debug(2, "Connection %d: new connection from %s:%u to self at %s:%u.",
conn->connection_number, remote_ip4, rport, ip4, tport);
}
- #ifdef AF_INET6
+#ifdef AF_INET6
if (local_info->SAFAMILY == AF_INET6) {
// IPv6:
@@ -2707,7 +2716,7 @@ void rtsp_listen_loop(void) {
debug(2, "Connection %d: new connection from [%s]:%u to self at [%s]:%u.",
conn->connection_number, remote_ip6, rport, ip6, tport);
}
- #endif
+#endif
} else {
debug(1, "Error figuring out Shairport Sync's own IP number.");
@@ -2735,8 +2744,8 @@ void rtsp_listen_loop(void) {
pthread_cleanup_pop(1); // should never happen
} else {
warn("could not establish a service on port %d -- program terminating. Is another instance of "
- "Shairport Sync running on this device?",
- config.port);
+ "Shairport Sync running on this device?",
+ config.port);
}
// debug(1, "Oops -- fell out of the RTSP select loop");
}
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 33db5d91..09098fbc 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -18,11 +18,20 @@ general =
// interpolation = "auto"; // aka "stuffing". Default is "auto". Alternatives are "basic" or "soxr". Choose "soxr" only if you have a reasonably fast processor and Shairport Sync has been built with "soxr" support.
// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one.
// mdns_backend = "avahi"; // Run "shairport-sync -h" to get a list of all mdns_backends. The default is the first one.
+// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically.
// port = 5000; // Listen for service requests on this port
// udp_port_base = 6001; // start allocating UDP ports from this port number when needed
// udp_port_range = 10; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state.
+// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
+
// drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it
// resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it
+
+// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
+// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose
+// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple.
+// If you build Shairport Sync with the flag --with-apple-alac, the Apple ALAC decoder will be chosen by default.
+
// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to.
// volume_range_db = 60 ; // use this advanced setting to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range.
// volume_max_db = 0.0 ; // use this advanced setting, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use.
@@ -36,25 +45,23 @@ general =
// 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".
-// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
-// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
-// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose
-// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple.
-// If you build Shairport Sync with the flag --with-apple-alac, the Apple ALAC decoder will be chosen by default.
-// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically.
-
-// audio_backend_latency_offset_in_seconds = 0.0; // This is added to the latency requested by the player to delay or advance the output by a fixed amount. Use it, for example, to compensate for a fixed delay in the audio back end. E.g. if the output device, e.g. a soundbar, takes 100 ms to process audio, set this to -0.1 to deliver the audio to the output device 100 ms early, allowing it time to process the audio and output it perfectly in sync.
-// audio_backend_buffer_desired_length_in_seconds = 0.2; // 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.2 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
+// audio_backend_latency_offset_in_seconds = 0.0; // This is added to the latency requested by the player to delay or advance the output by a fixed amount.
+// Use it, for example, to compensate for a fixed delay in the audio back end.
+// E.g. if the output device, e.g. a soundbar, takes 100 ms to process audio, set this to -0.1 to deliver the audio
+// to the output device 100 ms early, allowing it time to process the audio and output it perfectly in sync.
+// audio_backend_buffer_desired_length_in_seconds = 0.2; // 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.2 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 = "auto"; // This optional advanced setting, either "auto" or a positive number, sets the length of the period of silence that precedes the start of the audio. The default is "auto" -- the silent lead-in starts as soon as the player starts sending packets. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation.
-// audio_backend_silent_lead_in_initial_period = 0.5; // This optional advanced setting sets the length, in seconds, of the first "block" of silence sent to the DAC -- some DACs need a fairly substantial block to prevent lots of initial underrun.
-// audio_backend_silent_lead_in_minimum_adjustments_to_make = 3; // This optional advanced setting sets the minimum number of times Shairport Sync will readjust the length of the silent lead in to ensure that the audio starts at precisely the correct time.
-// audio_backend_silent_lead_in_adjustment_interval = 0.1; // This optional advanced setting sets the interval, in seconds, between adjustments of the silent lead in to ensure that the audio starts at precisely the correct time.
+// audio_backend_silent_lead_in_time = "auto"; // This optional advanced setting, either "auto" or a positive number, sets the length of the period of silence that precedes the start of the audio.
+// The default is "auto" -- the silent lead-in starts as soon as the player starts sending packets.
+// 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".
// mpris_service_bus = "system"; // The Shairport Sync mpris interface, if selected at compilation, will appear
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
+
// resend_control_first_check_time = 0.10; // Use this optional advanced setting to set the wait time in seconds before deciding a packet is missing.
// resend_control_check_interval_time = 0.25; // Use this optional advanced setting to set the time in seconds between requests for a missing packet.
// resend_control_last_check_time = 0.10; // Use this optional advanced setting to set the latest time, in seconds, by which the last check should be done before the estimated time of a missing packet's transfer to the output buffer.
@@ -64,14 +71,14 @@ general =
// 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" state starts when play begins and ends when the active_state_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed.
// run_this_before_entering_active_state = "/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_state = "/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_state_timeout = 10.0; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins.
+// 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
+
// 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!
@@ -91,15 +98,19 @@ alsa =
// output_device = "default"; // the name of the alsa output device. Use "shairport-sync -h" to discover the names of ALSA hardware devices. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
// mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software.
// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this.
+
// output_rate = "auto"; // can be "auto", 44100, 88200, 176400 or 352800, but the device must have the capability.
// output_format = "auto"; // can be "auto", "U8", "S8", "S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE" or "S32_BE" but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
-// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no".
+
+// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no" This is really meant for troubleshooting.
+
// period_size = ; // Use this optional advanced setting to set the alsa period size near to this value
// buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value
// use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes"
// use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players.
// maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms.
// use_precision_timing = "auto"; // Use this optional advanced setting to control how Shairport Sync gathers timing information. When set to "auto", if the output device is a real hardware device, precision timing will be used. Choose "no" for more compatible standard timing, choose "yes" to force the use of precision timing, which may cause problems.
+
// disable_standby_mode = "never"; // This setting prevents the DAC from entering the standby mode. Some DACs make small "popping" noises when they go in and out of standby mode. Settings can be: "always", "auto" or "never". Default is "never", but only for backwards compatibility. The "auto" setting prevents entry to standby mode while Shairport Sync is in the "active" mode. You can use "yes" instead of "always" and "no" instead of "never".
// disable_standby_mode_silence_threshold = 0.040; // Use this optional advanced setting to control how little audio should remain in the output buffer before the disable_standby code should start sending silence to the output device.
// disable_standby_mode_silence_scan_interval = 0.004; // Use this optional advanced setting to control how often the amount of audio remaining in the output buffer should be checked.
diff --git a/shairport-sync-dbus-test-client.c b/shairport-sync-dbus-test-client.c
index d0b15bb2..2433fd69 100644
--- a/shairport-sync-dbus-test-client.c
+++ b/shairport-sync-dbus-test-client.c
@@ -186,11 +186,10 @@ int main(int argc, char *argv[]) {
g_signal_connect(proxy4, "notify::volume", G_CALLBACK(notify_volume_callback),
"ShairportSync.AdvancedRemoteControl");
-
-
g_print("Starting test...\n");
- g_print("Using the RemoteControl interface, play for five seconds, pause for five seconds and then resume play...\n");
+ g_print("Using the RemoteControl interface, play for five seconds, pause for five seconds and "
+ "then resume play...\n");
g_print("Play...\n");
shairport_sync_remote_control_call_play(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
sleep(5);
@@ -200,24 +199,31 @@ int main(int argc, char *argv[]) {
g_print("Play...\n");
shairport_sync_remote_control_call_play(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
sleep(5);
- g_print("Using the RemoteControl interface, set AirPlay Volume (range -30 to 0) to -30, -20, -10, 0 and -15 for five seconds each...\n");
+ g_print("Using the RemoteControl interface, set AirPlay Volume (range -30 to 0) to -30, -20, "
+ "-10, 0 and -15 for five seconds each...\n");
g_print("Set AirPlay Volume (range -30 to 0) to -30\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -30, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -30,
+ NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -20\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -20, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -20,
+ NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -10\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -10, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -10,
+ NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -0\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), 0, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), 0,
+ NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -15\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -15, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -15,
+ NULL, NULL, 0);
sleep(5);
- g_print("Using the AdvancedRemoteControl interface, set Volume to 20%%, 100%%, 40%% and 60%% for five seconds each...\n");
+ g_print("Using the AdvancedRemoteControl interface, set Volume to 20%%, 100%%, 40%% and 60%% for "
+ "five seconds each...\n");
g_print("Set Volume to 20%%\n");
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 20, NULL, NULL, 0);
diff --git a/shairport.c b/shairport.c
index 60bfb4a0..e9cd70d3 100644
--- a/shairport.c
+++ b/shairport.c
@@ -145,7 +145,8 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
int number_of_iterations = 0;
uint64_t soxr_start_time = get_absolute_time_in_ns();
uint64_t loop_until_time =
- (uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be able to cancel it, do _don't even try_!
+ (uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be
+ // able to cancel it, do _don't even try_!
while (get_absolute_time_in_ns() < loop_until_time) {
number_of_iterations++;
@@ -184,8 +185,7 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
NULL, NULL); // Default configuration.
}
- double soxr_execution_time_ns =
- (get_absolute_time_in_ns() - soxr_start_time) * 1.0;
+ double soxr_execution_time_ns = (get_absolute_time_in_ns() - soxr_start_time) * 1.0;
// free(outbuffer);
// free(inbuffer);
config.soxr_delay_index = (int)(0.9 + soxr_execution_time_ns / (number_of_iterations * 1000000));
@@ -388,7 +388,8 @@ int parse_options(int argc, char **argv) {
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
- config.audio_backend_silent_lead_in_time_auto = 1; // start outputting silence as soon as packets start arriving
+ config.audio_backend_silent_lead_in_time_auto =
+ 1; // start outputting silence as soon as packets start arriving
config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if
// nothing else comes in first.
config.fixedLatencyOffset = 11025; // this sounds like it works properly.
@@ -399,17 +400,23 @@ int parse_options(int argc, char **argv) {
// automatically.
config.volume_range_hw_priority =
0; // if combining software and hardware volume control, give the software priority
-// i.e. when reducing volume, reduce the sw first before reducing the software.
-// this is because some hw mixers mute at the bottom of their range, and they don't always advertise
-// this fact
- config.resend_control_first_check_time = 0.10; // wait this many seconds before requesting the resending of a missing packet
- config.resend_control_check_interval_time = 0.25; // wait this many seconds before again requesting the resending of a missing packet
- config.resend_control_last_check_time = 0.10; // give up if the packet is still missing this close to when it's needed
- config.missing_port_dacp_scan_interval_seconds = 2.0; // check at this interval if no DACP port number is known
+ // i.e. when reducing volume, reduce the sw first before reducing the software.
+ // this is because some hw mixers mute at the bottom of their range, and they don't always
+ // advertise this fact
+ config.resend_control_first_check_time =
+ 0.10; // wait this many seconds before requesting the resending of a missing packet
+ config.resend_control_check_interval_time =
+ 0.25; // wait this many seconds before again requesting the resending of a missing packet
+ config.resend_control_last_check_time =
+ 0.10; // give up if the packet is still missing this close to when it's needed
+ config.missing_port_dacp_scan_interval_seconds =
+ 2.0; // check at this interval if no DACP port number is known
- config.minimum_free_buffer_headroom = 125; // leave approximately one second's worth of buffers free after calculating the effective latency.
- // e.g. if we have 1024 buffers or 352 frames = 8.17 seconds and we have a nominal latency of 2.0 seconds
- // then we can add an offset of 5.17 seconds and still leave a second's worth of buffers for unexpected circumstances
+ config.minimum_free_buffer_headroom = 125; // leave approximately one second's worth of buffers
+ // free after calculating the effective latency.
+ // e.g. if we have 1024 buffers or 352 frames = 8.17 seconds and we have a nominal latency of 2.0
+ // seconds then we can add an offset of 5.17 seconds and still leave a second's worth of buffers
+ // for unexpected circumstances
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
@@ -419,9 +426,10 @@ int parse_options(int argc, char **argv) {
1; // number of seconds between DACP server scans when playing nothing
config.scan_max_bad_response_count =
5; // number of successive bad results to ignore before giving up
- //config.scan_max_inactive_count =
- // (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before stopping if
- // not made active again (not used)
+ // config.scan_max_inactive_count =
+ // (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before
+ // stopping if
+ // not made active again (not used)
#endif
// config_setting_t *setting;
@@ -772,42 +780,38 @@ int parse_options(int argc, char **argv) {
die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"");
}
-
/* Get the resend control settings. */
- if (config_lookup_float(config.cfg, "general.resend_control_first_check_time",
- &dvalue)) {
+ if (config_lookup_float(config.cfg, "general.resend_control_first_check_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_first_check_time = dvalue;
else
warn("Invalid general resend_control_first_check_time setting \"%f\". It should "
- "be "
- "between 0.0 and 3.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.resend_control_first_check_time);
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_first_check_time);
}
- if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time",
- &dvalue)) {
+ if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_check_interval_time = dvalue;
else
warn("Invalid general resend_control_check_interval_time setting \"%f\". It should "
- "be "
- "between 0.0 and 3.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.resend_control_check_interval_time);
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_check_interval_time);
}
- if (config_lookup_float(config.cfg, "general.resend_control_last_check_time",
- &dvalue)) {
+ if (config_lookup_float(config.cfg, "general.resend_control_last_check_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_last_check_time = dvalue;
else
warn("Invalid general resend_control_last_check_time setting \"%f\". It should "
- "be "
- "between 0.0 and 3.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.resend_control_last_check_time);
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_last_check_time);
}
if (config_lookup_float(config.cfg, "general.missing_port_dacp_scan_interval_seconds",
@@ -816,50 +820,16 @@ int parse_options(int argc, char **argv) {
config.missing_port_dacp_scan_interval_seconds = dvalue;
else
warn("Invalid general missing_port_dacp_scan_interval_seconds setting \"%f\". It should "
- "be "
- "between 0.0 and 300.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.missing_port_dacp_scan_interval_seconds);
+ "be "
+ "between 0.0 and 300.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.missing_port_dacp_scan_interval_seconds);
}
/* Get the default latency. Deprecated! */
if (config_lookup_int(config.cfg, "latencies.default", &value))
config.userSuppliedLatency = value;
- /* Get the lead-in silence settings. */
- if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_initial_period",
- &dvalue)) {
- if ((dvalue >= 0.0) && (dvalue <= 3.0))
- config.lead_in_silence_initial_period = dvalue;
- else
- warn("Invalid general audio_backend_silent_lead_in_initial_period setting \"%f\". It should "
- "be "
- "between 0.0 and 3.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.lead_in_silence_initial_period);
- }
-
- if (config_lookup_int(config.cfg, "general.audio_backend_silent_lead_in_minimum_adjustments_to_make", &value)) {
- if (value < 0)
- die("Invalid audio_backend_silent_lead_in_minimum_adjustments_to_make range \"%d\". It must be positive. The setting remains at %d.",
- value, config.lead_in_silence_minimum_adjustments_to_make);
- else
- config.lead_in_silence_minimum_adjustments_to_make = value;
- }
-
- if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_adjustment_interval",
- &dvalue)) {
- if ((dvalue >= 0.0) && (dvalue <= 3.0))
- config.lead_in_silence_adjustment_interval = dvalue;
- else
- warn("Invalid general audio_backend_silent_lead_in_adjustment_interval setting \"%f\". It should "
- "be "
- "between 0.0 and 3.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.lead_in_silence_adjustment_interval);
- }
-
-
#ifdef CONFIG_METADATA
/* Get the metadata setting. */
config.metadata_enabled = 1; // if metadata support is included, then enable it by default
@@ -1014,7 +984,8 @@ int parse_options(int argc, char **argv) {
if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) {
config.convolution_ir_file = strdup(str);
- config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ config.convolver_valid =
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
}
if (config.convolution && config.convolution_ir_file == NULL) {
@@ -1210,7 +1181,7 @@ int parse_options(int argc, char **argv) {
poptFreeContext(optCon);
-// here, we are finally finished reading the options
+ // here, we are finally finished reading the options
#ifdef CONFIG_LIBDAEMON
if ((daemonisewith) && (daemonisewithout))
@@ -1224,8 +1195,9 @@ int parse_options(int argc, char **argv) {
#else
/* Check if we are called with -d or --daemon or -j or justDaemoniseNoPIDFile options*/
if ((daemonisewith != 0) || (daemonisewithout != 0)) {
- fprintf(stderr, "%s was built without libdaemon, so does not support daemonisation using the "
- "-d, --daemon, -j or --justDaemoniseNoPIDFile options\n",
+ fprintf(stderr,
+ "%s was built without libdaemon, so does not support daemonisation using the "
+ "-d, --daemon, -j or --justDaemoniseNoPIDFile options\n",
config.appName);
exit(EXIT_FAILURE);
}
@@ -1248,8 +1220,6 @@ int parse_options(int argc, char **argv) {
char hostname[100];
gethostname(hostname, 100);
-
-
char *i0;
if (raw_service_name == NULL)
i0 = strdup("%H"); // this is the default it the Service Name wasn't specified
@@ -1320,25 +1290,24 @@ const char *pid_file_proc(void) {
}
#endif
-
void exit_function() {
// the following is to ensure that if libdaemon has been included
// that most of this code will be skipped when the parent process is exiting
// exec
#ifdef CONFIG_LIBDAEMON
- if (this_is_the_daemon_process) { //this is the daemon that is exiting
+ if (this_is_the_daemon_process) { // this is the daemon that is exiting
#endif
- debug(1, "exit function called...");
+ debug(1, "exit function called...");
-/*
-Actually, there is no terminate_mqtt() function.
-#ifdef CONFIG_MQTT
- if (config.mqtt_enabled) {
- terminate_mqtt();
- }
-#endif
-*/
+ /*
+ Actually, there is no terminate_mqtt() function.
+ #ifdef CONFIG_MQTT
+ if (config.mqtt_enabled) {
+ terminate_mqtt();
+ }
+ #endif
+ */
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
@@ -1349,62 +1318,61 @@ Actually, there is no stop_mpris_service() function.
#endif
*/
#ifdef CONFIG_DBUS_INTERFACE
- stop_dbus_service();
+ stop_dbus_service();
#endif
- if (g_main_loop) {
- debug(2, "Stopping DBUS Loop Thread");
- g_main_loop_quit(g_main_loop);
- pthread_join(dbus_thread, NULL);
- }
+ if (g_main_loop) {
+ debug(2, "Stopping DBUS Loop Thread");
+ g_main_loop_quit(g_main_loop);
+ pthread_join(dbus_thread, NULL);
+ }
#endif
#ifdef CONFIG_DACP_CLIENT
- debug(2, "Stopping DACP Monitor");
- dacp_monitor_stop();
+ debug(2, "Stopping DACP Monitor");
+ dacp_monitor_stop();
#endif
#ifdef CONFIG_METADATA_HUB
- debug(2, "Stopping metadata hub");
- metadata_hub_stop();
+ debug(2, "Stopping metadata hub");
+ metadata_hub_stop();
#endif
#ifdef CONFIG_METADATA
- metadata_stop(); // close down the metadata pipe
+ metadata_stop(); // close down the metadata pipe
#endif
- activity_monitor_stop(0);
+ activity_monitor_stop(0);
- if ((config.output) && (config.output->deinit)) {
- debug(2, "Deinitialise the audio backend.");
- config.output->deinit();
- }
+ if ((config.output) && (config.output->deinit)) {
+ debug(2, "Deinitialise the audio backend.");
+ config.output->deinit();
+ }
#ifdef CONFIG_SOXR
- // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down
- pthread_join(soxr_time_check_thread, NULL);
+ // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down
+ pthread_join(soxr_time_check_thread, NULL);
#endif
+ if (conns)
+ free(conns); // make sure the connections have been deleted first
- if (conns)
- free(conns); // make sure the connections have been deleted first
-
- if (config.service_name)
- free(config.service_name);
+ if (config.service_name)
+ free(config.service_name);
#ifdef CONFIG_CONVOLUTION
- if (config.convolution_ir_file)
- free(config.convolution_ir_file);
+ if (config.convolution_ir_file)
+ free(config.convolution_ir_file);
#endif
- if (config.regtype)
- free(config.regtype);
+ if (config.regtype)
+ free(config.regtype);
#ifdef CONFIG_LIBDAEMON
- daemon_retval_send(0);
- daemon_pid_file_remove();
- daemon_signal_done();
- if (config.computed_piddir)
- free(config.computed_piddir);
+ daemon_retval_send(0);
+ daemon_pid_file_remove();
+ daemon_signal_done();
+ if (config.computed_piddir)
+ free(config.computed_piddir);
}
#endif
@@ -1421,7 +1389,8 @@ Actually, there is no stop_mpris_service() function.
void handle_sigchld(__attribute__((unused)) int sig) {
int saved_errno = errno;
- while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}
+ while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {
+ }
errno = saved_errno;
}
@@ -1500,7 +1469,7 @@ int main(int argc, char **argv) {
// config.userSuppliedLatency = 0; // zero means none supplied
config.debugger_show_file_and_line =
- 1; // by default, log the file and line of the originating message
+ 1; // by default, log the file and line of the originating message
config.debugger_show_relative_time =
1; // by default, log the time back to the previous debug message
config.resyncthreshold = 0.05; // 50 ms
@@ -1532,9 +1501,6 @@ int main(int argc, char **argv) {
config.output_rate_auto_requested = 1; // default auto select format
config.decoders_supported =
1 << decoder_hammerton; // David Hammerton's decoder supported by default
- config.lead_in_silence_initial_period = 0.5; // the size of the first block of silence to send to the DAC
- config.lead_in_silence_minimum_adjustments_to_make = 3; // make at least this number of checks and timing adjustments
- config.lead_in_silence_adjustment_interval = 0.025; // make checks and adjustments at this interval
#ifdef CONFIG_APPLE_ALAC
config.decoders_supported += 1 << decoder_apple_alac;
config.use_apple_decoder = 1; // use the ALAC decoder by default if support has been included
@@ -1766,8 +1732,9 @@ int main(int argc, char **argv) {
/* Print out options */
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);
#if CONFIG_LIBDAEMON
@@ -1790,8 +1757,8 @@ int main(int argc, char **argv) {
debug(1, "mdns backend \"%s\".", config.mdns_name);
debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency);
debug(1, "interpolation setting is \"%s\".",
- config.packet_stuffing == ST_basic ? "basic" : config.packet_stuffing == ST_soxr ? "soxr"
- : "auto");
+ config.packet_stuffing == ST_basic ? "basic"
+ : config.packet_stuffing == ST_soxr ? "soxr" : "auto");
debug(1, "interpolation soxr_delay_threshold is %d.", config.soxr_delay_threshold);
debug(1, "resync time is %f seconds.", config.resyncthreshold);
debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption);
@@ -1805,8 +1772,9 @@ int main(int argc, char **argv) {
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, "volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used "
- "first) is %d.",
+ debug(1,
+ "volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used "
+ "first) 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);
@@ -1828,15 +1796,8 @@ int main(int argc, char **argv) {
if (config.audio_backend_silent_lead_in_time_auto == 1)
debug(1, "audio backend silence lead-in time is \"auto\".");
else
- debug(1, "audio backend silence lead-in time is %f seconds.",
- config.audio_backend_silent_lead_in_time);
-
- debug(1, "audio backend silent lead-in initial period is %f seconds.",
- config.lead_in_silence_initial_period);
- debug(1, "audio backend silent lead-in minimum adjustments to make is %d.", config.lead_in_silence_minimum_adjustments_to_make);
- debug(1, "audio backend silent lead-in adjustment interval is %f seconds.",
- config.lead_in_silence_adjustment_interval);
-
+ debug(1, "audio backend silence lead-in time is %f seconds.",
+ config.audio_backend_silent_lead_in_time);
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);
diff --git a/tinysvcmdns.c b/tinysvcmdns.c
index 069a6436..9e605e07 100644
--- a/tinysvcmdns.c
+++ b/tinysvcmdns.c
@@ -1466,7 +1466,7 @@ int close_pipe(int s) {
// main loop to receive, process and send out MDNS replies
// also handles MDNS service announces
-void* main_loop(struct mdnsd *svr) {
+void *main_loop(struct mdnsd *svr) {
fd_set sockfd_set;
int max_fd = svr->sockfd;
char notify_buf[2]; // buffer for reading of notify_pipe
@@ -1724,7 +1724,7 @@ struct mdnsd *mdnsd_start() {
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- if (pthread_create(&tid, &attr, (void * (*)(void *))&main_loop, (void *)server) != 0) {
+ if (pthread_create(&tid, &attr, (void *(*)(void *)) & main_loop, (void *)server) != 0) {
pthread_mutex_destroy(&server->data_lock);
free(server);
return NULL;
@@ -1737,7 +1737,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 89e99f28a58db9c3baf49e9d8f563db9bc88c678 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 18 May 2020 17:00:56 +0100
Subject: [PATCH 087/186] Improve initial sync calculations, even with short
(0.3 sec) silent lead-in times.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 7f148707..026345ef 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.3.7d5], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.7d6], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 923a4f0a3cecacc64adf5839a8c7b57ea7615957 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 18 May 2020 17:25:09 +0100
Subject: [PATCH 088/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index a1a91eb7..58a7fb0b 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,9 @@
+Version 3.3.7d6
+====
+**Bug Fixes and Enhancements**
+* Clean up and improve the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchrnoisation, certainly to under a millisecond in normal usage. Sync should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as much as -1.7 seconds.
+* Removed a bug which would affect initial synchronisation if a flush command was received from the player at an inopportune time.
+
Version 3.3.7d5
====
**Bug Fixes and Enhancements**
From 034dd53af5c3dfd3420fddfb010ea2c5bb6a20bc Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 23 May 2020 10:18:23 +0100
Subject: [PATCH 089/186] Calculate the delay and sync error modulo the output
frame numbers. Duh! A long-standing bug.
---
player.c | 90 +++++++++++++++++++++++++-------------------------------
player.h | 2 +-
2 files changed, 41 insertions(+), 51 deletions(-)
diff --git a/player.c b/player.c
index 344662c3..94dccd02 100644
--- a/player.c
+++ b/player.c
@@ -249,6 +249,30 @@ static inline seq_t seq_sum(seq_t a, seq_t b) {
return r;
}
+// This orders u and v by picking the smaller of the two modulo differences
+// in unsigned modulo arithmetic and setting the sign of the result accordingly.
+
+// (Think of a modulo ring starting at 0 and circling around finally to
+// modulo-2, modulo-1 to 0 again. The ordering of u and v is always based on
+// the shorter way around the ring, taking the 0, 1, 2, ... direction as positive.)
+
+// The result will always fit in an int64_t,
+// Due to the asymmetry of 2's complement representation, however,
+// if the difference is equal to modulo / 2, it is ambiguous as to whether
+// u is "before" v or v is "before" u.
+// and will be returned as - modulo / 2.
+
+int64_t int64_mod_difference(const uint64_t u, const uint64_t v, const uint64_t modulo) {
+ int64_t response;
+ uint64_t diff = (u - v) % modulo;
+ uint64_t invdiff = (v - u) % modulo;
+ if (diff < invdiff)
+ response = diff;
+ else
+ response = -invdiff;
+ return response;
+}
+
void reset_input_flow_metrics(rtsp_conn_info *conn) {
conn->play_number_after_flush = 0;
conn->packet_count_since_flush = 0;
@@ -1793,7 +1817,7 @@ void *player_thread_func(void *arg) {
int64_t tsum_of_sync_errors, tsum_of_corrections, tsum_of_insertions_and_deletions,
tsum_of_drifts;
int64_t previous_sync_error = 0, previous_correction = 0;
- int64_t minimum_dac_queue_size = INT64_MAX;
+ uint64_t minimum_dac_queue_size = UINT64_MAX;
int32_t minimum_buffer_occupancy = INT32_MAX;
int32_t maximum_buffer_occupancy = INT32_MIN;
@@ -1808,7 +1832,7 @@ void *player_thread_func(void *arg) {
conn->buffer_occupancy = 0;
int play_samples = 0;
- int64_t current_delay;
+ uint64_t current_delay;
int play_number = 0;
conn->play_number_after_flush = 0;
// int last_timestamp = 0; // for debugging only
@@ -2136,35 +2160,12 @@ void *player_thread_func(void *arg) {
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
&remote_reference_timestamp_time, conn); // types okay
- int64_t rt, nt;
- rt = reference_timestamp; // uint32_t to int64_t
+ uint64_t nt;
nt = inframe->given_timestamp; // uint32_t to int64_t
- rt = rt * conn->output_sample_ratio;
nt = nt * conn->output_sample_ratio;
uint64_t local_time_now = get_absolute_time_in_ns(); // types okay
- int64_t td = 0; // td is the time difference between the reference timestamp time and the
- // present time. Only used to calculate td_in_frames
- int64_t td_in_frames = 0; // td_in_frames is the number of frames between between the
- // reference timestamp time and the present time
-
- if (local_time_now >= reference_timestamp_time) {
- td = local_time_now - reference_timestamp_time; // this is the positive value.
- // Conversion is positive uint64_t to
- // int64_t, thus okay
- td_in_frames = (td * config.output_rate) / 1000000000;
- } else {
- td = reference_timestamp_time - local_time_now; // this is the absolute value, which
- // should be negated. Conversion is
- // positive uint64_t to int64_t, thus
- // okay.
- td_in_frames = (td * config.output_rate) /
- 1000000000; // use the absolute td value for the present. Types okay
- td_in_frames = -td_in_frames;
- td = -td; // should be okay, as the range of values should be very small w.r.t 64 bits
- }
-
// This is the timing error for the next audio frame in the DAC, if applicable
int64_t sync_error = 0;
@@ -2202,15 +2203,16 @@ void *player_thread_func(void *arg) {
// If any of these are false, we don't do any synchronisation stuff
int resp = -1; // use this as a flag -- if negative, we can't rely on a real known delay
- current_delay = -1; // use this as a failure flag
if (config.output->delay) {
long l_delay;
resp = config.output->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);
+ if (l_delay >= 0)
+ current_delay = l_delay;
+ else {
+ debug(2, "Underrun of %ld frames reported, but ignored.", l_delay);
current_delay =
0; // could get a negative value if there was underrun, but ignore it.
}
@@ -2244,29 +2246,17 @@ void *player_thread_func(void *arg) {
uint32_t should_be_frame_32;
local_time_to_frame(local_time_now, &should_be_frame_32, conn);
- int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio;
+ // int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio;
- // int64_t absolute_difference_in_frames = td_in_frames + rt - should_be_frame;
- // if (absolute_difference_in_frames < 0)
- // absolute_difference_in_frames = -absolute_difference_in_frames;
+ int64_t delay = int64_mod_difference(should_be_frame_32 * conn->output_sample_ratio, nt - current_delay, UINT32_MAX * conn->output_sample_ratio);
- // if (absolute_difference_in_frames > 10 * conn->output_sample_ratio)
- // debug(1, "Difference between old and new frame number is %" PRId64 " frames.",
- // td_in_frames + rt - should_be_frame);
- // this is the actual delay, including the latency we actually want, which will
- // fluctuate a good bit about a potentially rising or falling trend.
+ //int64_t delay = should_be_frame - (nt - current_delay); // all int64_t
- // int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t
- // cut over to the new calculation method
- int64_t delay = should_be_frame - (nt - current_delay); // all int64_t
+ // the original frame numbers are unsigned 32-bit integers that roll over modulo 2^32
+ // hence these delay figures will be unsigned numbers that roll over modulo 2^32 * conn->output_sample_ratio;
+ // therefore, calculating the delay must be done in the light of possible rollover
- // td_in_frames + rt is the frame number that should be output at local_time_now.
- // This is the timing error for the next audio frame in the DAC.
-
- // if positive, it means that the packet will be late -- the delay is longer than
- // requested
- // if negative, the packet will be early -- the delay is less than expected.
sync_error =
delay - ((int64_t)conn->latency * conn->output_sample_ratio +
@@ -2724,7 +2714,7 @@ void *player_thread_func(void *arg) {
"%*" PRIu64 "," /* late packets */
"%*" PRIu64 "," /* too late packets */
"%*" PRIu64 "," /* resend requests */
- "%*" PRId64 "," /* min DAC queue size */
+ "%*" PRIu64 "," /* min DAC queue size */
"%*" PRId32 "," /* min buffer occupancy */
"%*" PRId32 "," /* max buffer occupancy */
"%*.2f," /* source nominal frame rate */
@@ -2757,7 +2747,7 @@ void *player_thread_func(void *arg) {
"%*" PRIu64 "," /* late packets */
"%*" PRIu64 "," /* too late packets */
"%*" PRIu64 "," /* resend requests */
- "%*" PRId64 "," /* min DAC queue size */
+ "%*" PRIu64 "," /* min DAC queue size */
"%*" PRId32 "," /* min buffer occupancy */
"%*" PRId32 "," /* max buffer occupancy */
"%*.2f," /* source nominal frame rate */
@@ -2797,7 +2787,7 @@ void *player_thread_func(void *arg) {
inform("No frames received in the last sampling interval.");
}
}
- minimum_dac_queue_size = INT64_MAX; // hack reset
+ minimum_dac_queue_size = UINT64_MAX; // hack reset
maximum_buffer_occupancy = INT32_MIN; // can't be less than this
minimum_buffer_occupancy = INT32_MAX; // can't be more than this
at_least_one_frame_seen = 0;
diff --git a/player.h b/player.h
index 1db4b36f..55b4426b 100644
--- a/player.h
+++ b/player.h
@@ -258,7 +258,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;
+ uint64_t dac_buffer_queue_minimum_length;
} rtsp_conn_info;
uint32_t modulo_32_offset(uint32_t from, uint32_t to);
From 4f5ff881a5b50630c0ed61bf4536179fd2d5d532 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 23 May 2020 13:54:21 +0100
Subject: [PATCH 090/186] Tidy up some of the modulo arithmetic stuff,
including tidying up how to deal with out-of-order packet arrival.
---
player.c | 141 +++++++++++++++++--------------------------------------
1 file changed, 44 insertions(+), 97 deletions(-)
diff --git a/player.c b/player.c
index 94dccd02..f472959e 100644
--- a/player.c
+++ b/player.c
@@ -143,6 +143,7 @@ static void ab_resync(rtsp_conn_info *conn) {
conn->ab_buffering = 1;
}
+
// given starting and ending points as unsigned 16-bit integers running modulo 2^16, returns the
// position of x in the interval in *pos
// returns true if x is actually within the buffer
@@ -173,94 +174,47 @@ int position_in_modulo_uint16_t_buffer(uint16_t x, uint16_t start, uint16_t end,
return response;
}
-// given starting and ending points as unsigned 32-bit integers running modulo 2^32, returns the
-// position of x in the interval in *pos
-// returns true if x is actually within the buffer
+static inline seq_t SUCCESSOR(seq_t x) {
+ return x + 1;
+}
-int position_in_modulo_uint32_t_buffer(uint32_t x, uint32_t start, uint32_t end, uint32_t *pos) {
- int response = 0; // not in the buffer
- if (start <= end) {
- if (x < start) {
- if (pos)
- *pos = UINT32_MAX - start + 1 + x;
- } else {
- if (pos)
- *pos = x - start;
- if (x < end)
- response = 1;
- }
- } else if ((x >= start)) { // && (x <= UINT32_MAX)) { // always true
- response = 1;
- if (pos)
- *pos = x - start;
- } else {
- if (pos)
- *pos = UINT32_MAX - start + 1 + x;
- if (x < end) {
- response = 1;
- }
- }
+// a minus b
+int16_t seq_diff(seq_t a, seq_t b) {
+ int16_t response;
+ seq_t diff = a - b;
+ seq_t invdiff = b - a;
+ if (diff < invdiff)
+ response = diff;
+ else
+ response = -invdiff;
return response;
}
-// this is used.
-static inline seq_t SUCCESSOR(seq_t x) {
- uint32_t p = x & 0xffff;
- p += 1;
- p = p & 0xffff;
- return p;
-}
-
-// used in seq_diff and seq_order
-
-// anything with ORDINATE in it must be protected by the ab_mutex
-static inline int32_t ORDINATE(seq_t x, seq_t base) {
- int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay
- int32_t q = base; // int32_t from seq_t, i.e. uint16_t, so okay
- int32_t t = (p + 0x10000 - q) & 0xffff;
- // we definitely will get a positive number in t at this point, but it might be a
- // positive alias of a negative number, i.e. x might actually be "before" ab_read
- // So, if the result is greater than 32767, we will assume its an
- // alias and subtract 65536 from it
- if (t >= 32767) {
- // debug(1,"OOB: %u, ab_r: %u, ab_w: %u",x,ab_read,ab_write);
- t -= 65536;
- }
- return t;
-}
-
-// wrapped number between two seq_t.
-int32_t seq_diff(seq_t a, seq_t b, seq_t base) {
- int32_t diff = ORDINATE(b, base) - ORDINATE(a, base);
- return diff;
-}
-
-// the sequence numbers will wrap pretty often.
-// this returns true if the second arg is after the first
-static inline int seq_order(seq_t a, seq_t b, seq_t base) {
- int32_t d = ORDINATE(b, base) - ORDINATE(a, base);
- return d > 0;
-}
-
static inline seq_t seq_sum(seq_t a, seq_t b) {
- // uint32_t p = a & 0xffff;
- // uint32_t q = b & 0x0ffff;
- uint32_t r = (a + b) & 0xffff;
- return r;
+ return a + b;
}
// This orders u and v by picking the smaller of the two modulo differences
// in unsigned modulo arithmetic and setting the sign of the result accordingly.
-// (Think of a modulo ring starting at 0 and circling around finally to
-// modulo-2, modulo-1 to 0 again. The ordering of u and v is always based on
-// the shorter way around the ring, taking the 0, 1, 2, ... direction as positive.)
+// if u - v gives the smaller modulo difference, then that modulo difference is returned
+// otherwise the negative of the v - u modulo difference is returned.
-// The result will always fit in an int64_t,
+// (Think of a modulo ring starting at 0 and circling around clockwise finally to
+// modulo-2, modulo-1 to 0 again. To determine the ordering of the two numbers,
+// Find the shorter path between them around the ring.
+// If you go from v to u in the positive (0, 1, 2 ... or clockwise) direction,
+// then u is greater than (or "after") v.)
+
+// The result will always fit in an int64_t as it must be less or equal to half way
+// around the ring.
// Due to the asymmetry of 2's complement representation, however,
// if the difference is equal to modulo / 2, it is ambiguous as to whether
-// u is "before" v or v is "before" u.
-// and will be returned as - modulo / 2.
+// u is "after" or "before" (i.e. greater or less than) v.
+// and will be returned as -modulo / 2, that is, "before".
+// If that ever happens and there is a real ambiguity in the application,
+// the modulo chosen is too small.
+
int64_t int64_mod_difference(const uint64_t u, const uint64_t v, const uint64_t modulo) {
int64_t response;
@@ -539,8 +493,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
conn->ab_read = seqno;
conn->ab_synced = 1;
}
- if (conn->ab_write ==
- seqno) { // if this is the expected packet (which could be the first packet...)
+ int16_t write_point_gap = seq_diff(seqno,conn->ab_write); // this is the difference between
+ // the incoming packet number and the packet number that was expected.
+ if (write_point_gap == 0) { // if this is the expected packet (which could be the first packet...)
if (conn->input_frame_rate_starting_point_is_valid == 0) {
if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) {
conn->frames_inward_measurement_start_time = time_now;
@@ -552,12 +507,10 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
conn->frames_inward_frames_received_at_measurement_time = actual_timestamp;
abuf = conn->audio_buffer + BUFIDX(seqno);
conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space
- } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
- int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read);
- if (gap <= 0)
- debug(1, "Unexpected gap size: %d.", gap);
+ } else if (write_point_gap > 0) { // newer than expected
+ // initialise the frames in between
int i;
- for (i = 0; i < gap; i++) {
+ for (i = 0; i < write_point_gap; i++) {
abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
abuf->ready = 0; // to be sure, to be sure
abuf->resend_request_number = 0;
@@ -568,13 +521,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
abuf->given_timestamp = 0;
abuf->sequence_number = 0;
}
- // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write);
abuf = conn->audio_buffer + BUFIDX(seqno);
- // rtp_request_resend(ab_write, gap);
- // resend_requests++;
conn->ab_write = SUCCESSOR(seqno);
- } else if (seq_order(conn->ab_read, seqno,
- conn->ab_read)) { // older than expected but not too late
+ } else if (seq_diff(seqno,conn->ab_read) > 0) { // older than expected but still not too late
conn->late_packets++;
abuf = conn->audio_buffer + BUFIDX(seqno);
} else { // too late.
@@ -1035,11 +984,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (curframe->sequence_number != conn->ab_read) {
// some kind of sync problem has occurred.
if (BUFIDX(curframe->sequence_number) == BUFIDX(conn->ab_read)) {
- // it looks like some kind of aliasing has happened
- if (seq_order(conn->ab_read, curframe->sequence_number, conn->ab_read)) {
- conn->ab_read = curframe->sequence_number;
- debug(1, "Aliasing of buffer index -- reset.");
- }
+ // it looks like aliasing has happened
+ // jump to the new incoming stuff...
+ conn->ab_read = curframe->sequence_number;
+ debug(1, "Aliasing of buffer index -- reset.");
} else {
debug(1, "Inconsistent sequence numbers detected");
}
@@ -1789,9 +1737,9 @@ 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)(
+ conn->dac_buffer_queue_minimum_length = (uint64_t)(
config.audio_backend_buffer_interpolation_threshold_in_seconds * config.output_rate);
- debug(3, "dac_buffer_queue_minimum_length is %" PRId64 " frames.",
+ debug(3, "dac_buffer_queue_minimum_length is %" PRIu64 " frames.",
conn->dac_buffer_queue_minimum_length);
conn->session_corrections = 0;
@@ -2033,8 +1981,7 @@ void *player_thread_func(void *arg) {
"status 0x%X after %u resend requests.",
SUCCESSOR(conn->last_seqno_read), play_number, inframe->status,
inframe->resend_request_number);
- conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
- 0xffff); // manage the packet out of sequence minder
+ conn->last_seqno_read = SUCCESSOR(conn->last_seqno_read); // manage the packet out of sequence minder
void *silence = malloc(conn->output_bytes_per_frame * conn->max_frames_per_packet *
conn->output_sample_ratio);
@@ -2189,7 +2136,7 @@ void *player_thread_func(void *arg) {
}
conn->buffer_occupancy =
- seq_diff(conn->ab_read, conn->ab_write, conn->ab_read); // int32_t from int32
+ seq_diff(conn->ab_write, conn->ab_read); // int32_t from int16_t
if (conn->buffer_occupancy < minimum_buffer_occupancy)
minimum_buffer_occupancy = conn->buffer_occupancy;
From 1945cd0b7fa31718b8c0530d591ecadaa60ba77e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 23 May 2020 14:26:52 +0100
Subject: [PATCH 091/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 58a7fb0b..2c5108ed 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,6 +1,8 @@
Version 3.3.7d6
====
**Bug Fixes and Enhancements**
+* Fix a bug calculating the instantaneous synchronisation error. This could occasionally cause Shairport Sync to lose synchronisation and maybe even to mute for a few seconds before regaining it. It was caused by not doing modulo arithmetic properly, and it's been there for a while.
+* Clean up some other uses of modulo arithmetic.
* Clean up and improve the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchrnoisation, certainly to under a millisecond in normal usage. Sync should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as much as -1.7 seconds.
* Removed a bug which would affect initial synchronisation if a flush command was received from the player at an inopportune time.
From 759b87f2a291939b46a4cd00e7630764d8143927 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 23 May 2020 20:31:54 +0100
Subject: [PATCH 092/186] Move the statistics array from the stack into the
heap.
---
player.c | 54 +++++++++++++++++++++++++-----------------------------
player.h | 8 ++++++++
2 files changed, 33 insertions(+), 29 deletions(-)
diff --git a/player.c b/player.c
index f472959e..19fed7b3 100644
--- a/player.c
+++ b/player.c
@@ -1579,10 +1579,6 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
}
#endif
-typedef struct stats { // statistics for running averages
- int64_t sync_error, correction, drift;
-} stats_t;
-
void player_thread_initial_cleanup_handler(__attribute__((unused)) void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
debug(3, "Connection %d: player thread main loop exit via player_thread_initial_cleanup_handler.",
@@ -1649,6 +1645,11 @@ void player_thread_cleanup_handler(void *arg) {
free(conn->tbuf);
conn->tbuf = NULL;
}
+
+ if (conn->statistics) {
+ free(conn->statistics);
+ conn->statistics = NULL;
+ }
free_audio_buffers(conn);
if (conn->stream.type == ast_apple_lossless)
terminate_decoders(conn);
@@ -1756,9 +1757,10 @@ void *player_thread_func(void *arg) {
conn->connection_state_to_output = get_requested_connection_state_to_output();
// this is about half a minute
//#define trend_interval 3758
+
+// this is about 8 seconds
#define trend_interval 1003
- stats_t statistics[trend_interval];
int number_of_statistics, oldest_statistic, newest_statistic;
int at_least_one_frame_seen = 0;
int at_least_one_frame_seen_this_session = 0;
@@ -1853,9 +1855,6 @@ void *player_thread_func(void *arg) {
// we need an intermediate "transition" buffer
- // 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));
@@ -1884,6 +1883,10 @@ void *player_thread_func(void *arg) {
int sync_error_out_of_bounds =
0; // number of times in a row that there's been a serious sync error
+ conn->statistics = malloc(sizeof(stats_t)*trend_interval);
+ if (conn->statistics == NULL)
+ die("Failed to allocate a statistics buffer");
+
conn->framesProcessedInThisEpoch = 0;
conn->framesGeneratedInThisEpoch = 0;
conn->correctionsRequestedInThisEpoch = 0;
@@ -2565,34 +2568,34 @@ void *player_thread_func(void *arg) {
// valid samples and the number of times sync wasn't checked due to non availability of a
// delay figure.
// for the present, stats are only updated when sync has been checked
- if (sync_error != -1) {
+ if (config.output->delay != NULL) {
if (number_of_statistics == trend_interval) {
// here we remove the oldest statistical data and take it from the summaries as well
- tsum_of_sync_errors -= statistics[oldest_statistic].sync_error;
- tsum_of_drifts -= statistics[oldest_statistic].drift;
- if (statistics[oldest_statistic].correction > 0)
- tsum_of_insertions_and_deletions -= statistics[oldest_statistic].correction;
+ tsum_of_sync_errors -= conn->statistics[oldest_statistic].sync_error;
+ tsum_of_drifts -= conn->statistics[oldest_statistic].drift;
+ if (conn->statistics[oldest_statistic].correction > 0)
+ tsum_of_insertions_and_deletions -= conn->statistics[oldest_statistic].correction;
else
- tsum_of_insertions_and_deletions += statistics[oldest_statistic].correction;
- tsum_of_corrections -= statistics[oldest_statistic].correction;
+ tsum_of_insertions_and_deletions += conn->statistics[oldest_statistic].correction;
+ tsum_of_corrections -= conn->statistics[oldest_statistic].correction;
oldest_statistic = (oldest_statistic + 1) % trend_interval;
number_of_statistics--;
}
- statistics[newest_statistic].sync_error = sync_error;
- statistics[newest_statistic].correction = conn->amountStuffed;
+ conn->statistics[newest_statistic].sync_error = sync_error;
+ conn->statistics[newest_statistic].correction = conn->amountStuffed;
if (number_of_statistics == 0)
- statistics[newest_statistic].drift = 0;
+ conn->statistics[newest_statistic].drift = 0;
else
- statistics[newest_statistic].drift =
+ conn->statistics[newest_statistic].drift =
sync_error - previous_sync_error - previous_correction;
previous_sync_error = sync_error;
previous_correction = conn->amountStuffed;
tsum_of_sync_errors += sync_error;
- tsum_of_drifts += statistics[newest_statistic].drift;
+ tsum_of_drifts += conn->statistics[newest_statistic].drift;
if (conn->amountStuffed > 0) {
tsum_of_insertions_and_deletions += conn->amountStuffed;
} else {
@@ -3034,17 +3037,10 @@ int player_play(rtsp_conn_info *conn) {
if (pt == NULL)
die("Couldn't allocate space for pthread_t");
conn->player_thread = pt;
- size_t size = (PTHREAD_STACK_MIN + 256 * 1024);
- pthread_attr_t tattr;
- pthread_attr_init(&tattr);
- int rc = pthread_attr_setstacksize(&tattr, size);
- if (rc)
- debug(1, "Error setting stack size for player_thread: %s", strerror(errno));
- // finished initialising.
- rc = pthread_create(pt, &tattr, player_thread_func, (void *)conn);
+ int rc = pthread_create(pt, NULL, player_thread_func, (void *)conn);
if (rc)
debug(1, "Error creating player_thread: %s", strerror(errno));
- pthread_attr_destroy(&tattr);
+
#ifdef CONFIG_METADATA
debug(2, "pbeg");
send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points
diff --git a/player.h b/player.h
index 55b4426b..f79f8806 100644
--- a/player.h
+++ b/player.h
@@ -50,6 +50,10 @@ typedef struct audio_buffer_entry { // decoded audio packets
int length; // the length of the decoded data
} abuf_t;
+typedef struct stats { // statistics for running averages
+ int64_t sync_error, correction, drift;
+} stats_t;
+
// default buffer size
// This needs to be a power of 2 because of the way BUFIDX(seqno) works.
// 512 is the minimum for normal operation -- it gives 512*352/44100 or just over 4 seconds of
@@ -110,6 +114,10 @@ typedef struct {
int32_t *sbuf;
char *outbuf;
+ // for generating running statistics...
+
+ stats_t *statistics;
+
// for holding the output rate information until printed out at the end of a session
double frame_rate;
int frame_rate_status;
From 5b24dddecd8031739c53939aa762c7b8a7a1d224 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 23 May 2020 20:33:50 +0100
Subject: [PATCH 093/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 2c5108ed..0cfb96da 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -2,9 +2,10 @@ Version 3.3.7d6
====
**Bug Fixes and Enhancements**
* Fix a bug calculating the instantaneous synchronisation error. This could occasionally cause Shairport Sync to lose synchronisation and maybe even to mute for a few seconds before regaining it. It was caused by not doing modulo arithmetic properly, and it's been there for a while.
-* Clean up some other uses of modulo arithmetic.
* Clean up and improve the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchrnoisation, certainly to under a millisecond in normal usage. Sync should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as much as -1.7 seconds.
* Removed a bug which would affect initial synchronisation if a flush command was received from the player at an inopportune time.
+* Clean up some other uses of modulo arithmetic.
+* Clean up allocation of memory for gathering running statistics -- use the heap instead of the stack.
Version 3.3.7d5
====
From 9280e1430baee3db257bdb8468fb584d83ffed39 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 24 May 2020 09:45:03 +0100
Subject: [PATCH 094/186] Clean up statistics display for backends with no
sync. Flush pipe and stdout backends when play stops.
---
audio_pipe.c | 6 ++++--
audio_stdout.c | 5 ++++-
player.c | 19 +++++++++----------
3 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/audio_pipe.c b/audio_pipe.c
index 4ec9b240..c2e71642 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -86,8 +86,10 @@ static int play(void *buf, int samples) {
static void stop(void) {
// Don't close the pipe just because a play session has stopped.
- // if (fd > 0)
- // close(fd);
+ // Flush it instead
+ if (fd > 0)
+ if (fsync(fd) != 0)
+ debug(1, "Error flushing pipe when play stopped: \"%s\".", strerror(errno));
}
static int init(int argc, char **argv) {
diff --git a/audio_stdout.c b/audio_stdout.c
index 00ded166..aca660b1 100644
--- a/audio_stdout.c
+++ b/audio_stdout.c
@@ -56,7 +56,10 @@ static int play(void *buf, int samples) {
}
static void stop(void) {
- // don't close stdout
+ // Flush stdout when play stops instead
+ if (fd > 0)
+ if (fsync(fd) != 0)
+ debug(1, "Error flushing STDOUT when play stopped: \"%s\".", strerror(errno));
}
static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
diff --git a/player.c b/player.c
index 19fed7b3..a750415a 100644
--- a/player.c
+++ b/player.c
@@ -1927,8 +1927,7 @@ void *player_thread_func(void *arg) {
"source clock drift sample count");
}
} else {
- inform("sync error in milliseconds, "
- "total packets, "
+ inform("total packets, "
"missing packets, "
"late packets, "
"too late packets, "
@@ -2094,7 +2093,8 @@ void *player_thread_func(void *arg) {
die("Shairport Sync only supports 16 bit input");
}
- inbuflength *= conn->output_sample_ratio;
+
+ at_least_one_frame_seen = 1;
// We have a frame of data. We need to see if we want to add or remove a frame from it to
// keep in sync.
@@ -2106,6 +2106,7 @@ void *player_thread_func(void *arg) {
// now, go back as far as the total latency less, say, 100 ms, and check the presence of
// frames from then onwards
+ inbuflength *= conn->output_sample_ratio;
uint32_t reference_timestamp;
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
@@ -2147,6 +2148,9 @@ void *player_thread_func(void *arg) {
if (conn->buffer_occupancy > maximum_buffer_occupancy)
maximum_buffer_occupancy = conn->buffer_occupancy;
+
+
+
// here, we want to check (a) if we are meant to do synchronisation,
// (b) if we have a delay procedure, (c) if we can get the delay.
@@ -2252,10 +2256,6 @@ void *player_thread_func(void *arg) {
sync_error = 0; // say the error was fixed!
}
}
-
-
- at_least_one_frame_seen = 1;
-
// not too sure if abs() is implemented for int64_t, so we'll do it manually
int64_t abs_sync_error = sync_error;
if (abs_sync_error < 0)
@@ -2713,8 +2713,7 @@ void *player_thread_func(void *arg) {
conn->local_to_remote_time_gradient_sample_count);
}
} else {
- inform("%*.2f," /* Sync error in milliseconds */
- "%*d," /* total packets */
+ inform("%*d," /* total packets */
"%*" PRIu64 "," /* missing packets */
"%*" PRIu64 "," /* late packets */
"%*" PRIu64 "," /* too late packets */
@@ -2725,7 +2724,7 @@ 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,
+ 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,
From 5b8a8c879eb6cd7faa056f7573ad60da1614ea3e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 24 May 2020 10:05:42 +0100
Subject: [PATCH 095/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 0cfb96da..df6fb29b 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -2,10 +2,12 @@ Version 3.3.7d6
====
**Bug Fixes and Enhancements**
* Fix a bug calculating the instantaneous synchronisation error. This could occasionally cause Shairport Sync to lose synchronisation and maybe even to mute for a few seconds before regaining it. It was caused by not doing modulo arithmetic properly, and it's been there for a while.
-* Clean up and improve the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchrnoisation, certainly to under a millisecond in normal usage. Sync should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as much as -1.7 seconds.
-* Removed a bug which would affect initial synchronisation if a flush command was received from the player at an inopportune time.
-* Clean up some other uses of modulo arithmetic.
-* Clean up allocation of memory for gathering running statistics -- use the heap instead of the stack.
+* Clean up and improve the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchronisation, usually to under a millisecond, and often to within 20 or 30 microseconds in normal usage. Sync should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as great as -1.7 seconds.
+* Remove a bug which would affect initial synchronisation if a `FLUSH` command was received from the player at an inopportune time.
+* Clean up some confused uses of modulo arithmetic.
+* Clean up allocation of memory for gathering running statistics – use the heap instead of the stack.
+* Clean up the display of statistics for backends that do not implement active synchronisation, e.g. the `pipe` and `STDOUT` back ends.
+* Add code to flush output to the `pipe` or to `STDOUT` when playing stops.
Version 3.3.7d5
====
From 5e65976eed8a861e03240717467f5c7939e92b92 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 24 May 2020 10:06:06 +0100
Subject: [PATCH 096/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index df6fb29b..4861e8fc 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,7 +1,7 @@
Version 3.3.7d6
====
**Bug Fixes and Enhancements**
-* Fix a bug calculating the instantaneous synchronisation error. This could occasionally cause Shairport Sync to lose synchronisation and maybe even to mute for a few seconds before regaining it. It was caused by not doing modulo arithmetic properly, and it's been there for a while.
+* Fix a bug calculating the instantaneous synchronisation error. This bug could occasionally cause Shairport Sync to lose synchronisation and maybe even to mute for a few seconds before regaining it. It was caused by not doing modulo arithmetic properly, and it's been there for a while.
* Clean up and improve the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchronisation, usually to under a millisecond, and often to within 20 or 30 microseconds in normal usage. Sync should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as great as -1.7 seconds.
* Remove a bug which would affect initial synchronisation if a `FLUSH` command was received from the player at an inopportune time.
* Clean up some confused uses of modulo arithmetic.
From d8039d1d88e1f7bad374c8a71e9e4359bab463d3 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 24 May 2020 10:07:04 +0100
Subject: [PATCH 097/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 4861e8fc..6fa28671 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,13 +1,13 @@
Version 3.3.7d6
====
**Bug Fixes and Enhancements**
-* Fix a bug calculating the instantaneous synchronisation error. This bug could occasionally cause Shairport Sync to lose synchronisation and maybe even to mute for a few seconds before regaining it. It was caused by not doing modulo arithmetic properly, and it's been there for a while.
-* Clean up and improve the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchronisation, usually to under a millisecond, and often to within 20 or 30 microseconds in normal usage. Sync should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as great as -1.7 seconds.
-* Remove a bug which would affect initial synchronisation if a `FLUSH` command was received from the player at an inopportune time.
-* Clean up some confused uses of modulo arithmetic.
-* Clean up allocation of memory for gathering running statistics – use the heap instead of the stack.
-* Clean up the display of statistics for backends that do not implement active synchronisation, e.g. the `pipe` and `STDOUT` back ends.
-* Add code to flush output to the `pipe` or to `STDOUT` when playing stops.
+* Fixed a bug calculating the instantaneous synchronisation error. This bug could occasionally cause Shairport Sync to lose synchronisation and maybe even to mute for a few seconds before regaining it. It was caused by not doing modulo arithmetic properly, and it's been there for a while.
+* Cleaned up and improve the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchronisation, usually to under a millisecond, and often to within 20 or 30 microseconds in normal usage. Sync should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as great as -1.7 seconds.
+* Removed a bug which would affect initial synchronisation if a `FLUSH` command was received from the player at an inopportune time.
+* Cleaned up some confused uses of modulo arithmetic.
+* Cleaned up allocation of memory for gathering running statistics – use the heap instead of the stack.
+* Cleaned up the display of statistics for backends that do not implement active synchronisation, e.g. the `pipe` and `STDOUT` back ends.
+* Added code to flush output to the `pipe` or to `STDOUT` when playing stops.
Version 3.3.7d5
====
From b0164820add2810ddd3646e9113fe13d342d7e2a Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 24 May 2020 10:18:15 +0100
Subject: [PATCH 098/186] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 6fa28671..9ce1afad 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -78,7 +78,7 @@ resulting in:
```
The MPRIS `trackid` corresponding to this would be `/org/gnome/ShairportSync/FD84B4B40FA33A85`.
-Thanks to to Scott Simon (https://github.com/zutroy97) for finding a related bug in the [shairport-sync-metadata-reader](https://github.com/mikebrady/shairport-sync-metadata-reader).
+Thanks to to [Scott Simon](https://github.com/zutroy97) for finding a related bug in the [shairport-sync-metadata-reader](https://github.com/mikebrady/shairport-sync-metadata-reader).
**Other Changes**
* Include the `-fno-common` flag in the compilation options to detect incorrect definitions of global variables and multiple definitions of `enum` data types.
From c1f973dde7c662e9b871a69a4401c9725e578e5f Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 24 May 2020 19:47:04 +0100
Subject: [PATCH 099/186] Remove the fsync calls from stdout and pipe backends.
Slight edits to man file.
---
README.md | 30 +-
audio_pipe.c | 4 -
audio_stdout.c | 5 +-
man/shairport-sync.7 | 4 +-
man/shairport-sync.7.xml | 607 +++++++++----------
man/shairport-sync.html | 1219 +-------------------------------------
shairport.c | 2 +-
7 files changed, 347 insertions(+), 1524 deletions(-)
diff --git a/README.md b/README.md
index 48bd955c..29acc4ca 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ For more about the motivation behind Shairport Sync, please see the wiki at http
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.
+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”.
@@ -42,7 +42,7 @@ What else?
* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/) interface, partially complete and very functional, including access to metadata and artwork, and some limited remote control.
* An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), an often-used protocol in home automation projects.
* A native D-Bus interface, including access to metadata and artwork, some limited remote control and some system settings.
-
+
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/master/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.
@@ -65,7 +65,7 @@ See [here](https://github.com/mikebrady/shairport-sync/blob/master/INSTALL.md) f
Shairport Sync may already be available as a package in your Linux distribution (search for `shairport-sync` – the package named `shairport` is a different program). Packages are available on recent versions of Debian, Ubuntu, Arch, OpenWrt and possibly more:
-**Ubuntu:** A `shairport-sync` installer package is available for Ubuntu. Additionally, a Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson.
+**Ubuntu:** A `shairport-sync` installer package is available for Ubuntu. Additionally, a Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson.
**Debian:** shairport-sync is in the Debian archive.
@@ -75,7 +75,7 @@ Shairport Sync may already be available as a package in your Linux distribution
To build and install the latest version of Shairport Sync, an [Arch Linux build and installation guide](https://github.com/mikebrady/shairport-sync-for-arch-linux) is available, based on original work by [Elia Cereda](https://github.com/EliaCereda).
-**Mac OS X:** A HomeBrew package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command:
+**Mac OS X:** A HomeBrew package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command:
```
$brew install shairport-sync
```
@@ -91,7 +91,7 @@ If you wish to build and install the latest version of Shairport Sync on Debian,
**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 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 startup script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
@@ -123,13 +123,13 @@ 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**
+**Building**
To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions.
@@ -179,7 +179,7 @@ $ autoreconf -i -f
(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-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.
@@ -199,9 +199,9 @@ $ autoreconf -i -f
**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.)
+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.
+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 is to look at the first few lines of the `init` manual. Enter the command:
@@ -363,7 +363,7 @@ Note: Shairport Sync can take configuration settings from command line options.
**Raspberry Pi**
-The Raspberry Pi Models A and B have a built-in audio DAC that is connected to the device's headphone jack. Apart from a loud click when used for the first time after power-up, it is now quite adequate for casual listening.
+The Raspberry Pi Models A and B have a built-in audio DAC that is connected to the device's headphone jack. Apart from a loud click when used for the first time after power-up, it is now quite adequate for casual listening.
To get the benefits of improvements in the Pi's software and firmware, you should update to the Raspian release of October 2018 or later, as a number of improvements have been made to the built-in DAC.
@@ -403,7 +403,7 @@ The System V init script at `/etc/init.d/shairport-sync` has a bare minimum :
Examples
--------
-Here are some examples of complete configuration files.
+Here are some examples of complete configuration files.
```
general = {
@@ -516,7 +516,7 @@ The UDP metadata format is very simple - the first four bytes are 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.
+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.
@@ -524,7 +524,7 @@ Problems can arise when you are trying to synchronise with speaker systems — t
Resynchronisation
-------------
-Shairport Sync actively maintains synchronisation with the source.
+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.
@@ -535,7 +535,7 @@ Playback synchronisation is allowed to wander — to "drift" — a small amount
Some Statistics
---------------
-If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file:
+If you turn on the `general` `statistics` setting, a heading like this will be output to the log file (or to `STDERR` if the `-u` command line option is chosen):
```
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, source nominal frames per second, source actual frames per second, output frames per second, source clock drift in ppm, source clock drift sample count, rough calculated correction in ppm
```
diff --git a/audio_pipe.c b/audio_pipe.c
index c2e71642..2abef94b 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -86,10 +86,6 @@ static int play(void *buf, int samples) {
static void stop(void) {
// Don't close the pipe just because a play session has stopped.
- // Flush it instead
- if (fd > 0)
- if (fsync(fd) != 0)
- debug(1, "Error flushing pipe when play stopped: \"%s\".", strerror(errno));
}
static int init(int argc, char **argv) {
diff --git a/audio_stdout.c b/audio_stdout.c
index aca660b1..8b37883e 100644
--- a/audio_stdout.c
+++ b/audio_stdout.c
@@ -56,10 +56,7 @@ static int play(void *buf, int samples) {
}
static void stop(void) {
- // Flush stdout when play stops instead
- if (fd > 0)
- if (fsync(fd) != 0)
- debug(1, "Error flushing STDOUT when play stopped: \"%s\".", strerror(errno));
+ // Do nothing when play stops
}
static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
diff --git a/man/shairport-sync.7 b/man/shairport-sync.7
index 9048b551..f85bce3a 100644
--- a/man/shairport-sync.7
+++ b/man/shairport-sync.7
@@ -326,10 +326,10 @@ The maximum packet size for any UDP metadata. This must be between 500 or 65000.
\fB"DIAGNOSTICS" SETTINGS\f1
.TP
\fBstatistics=\f1\fI"setting"\f1\fB;\f1
-Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
+Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information to the system log (or to \fISTDERR\f1 if the \fB-u\f1 command line option is chosen). The default is to disable statistics.
.TP
\fBlog_verbosity=\f1\fI0\f1\fB;\f1
-Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
+Use this to specify how much debugging information should sent to the system log (or to \fISTDERR\f1 if the \fB-u\f1 command line option is chosen). The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
.SH OPTIONS
This section is about the command-line options available in shairport-sync.
diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml
index 93b24146..39311a86 100644
--- a/man/shairport-sync.7.xml
+++ b/man/shairport-sync.7.xml
@@ -7,7 +7,7 @@
Copyright (c) Mike Brady 2014-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
@@ -15,10 +15,10 @@
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
@@ -61,61 +61,61 @@
-
Shairport Sync plays
- audio streamed from iTunes
- or from an AirPlay device to an ALSA-compatible audio output device (available on
- Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
+
Shairport Sync plays
+ audio streamed from iTunes
+ or from an AirPlay device to an ALSA-compatible audio output device (available on
+ Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
Linux), to a PulseAudio output stream or to Jack Audio.
-
+
Shairport Sync offers full audio synchronisation.
- Full audio synchronisation means that audio is played on the output device at exactly
+ Full audio synchronisation means that audio is played on the output device at exactly
the time specified by the audio source.
This means that if many devices are playing the same stream at the same
time, all the outputs will stay in synchrony with one another.
- This allows multiple devices to play the same source without getting out of step with
+ This allows multiple devices to play the same source without getting out of step with
one another, enabling, for example, simultaneous multi-room operation.
-
-
Shairport Sync can stream synchronised audio 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
+
+
Shairport Sync can stream synchronised audio 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.
-
Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
+
Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
or socket.
-
Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
- D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
+
Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
+ D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
metadata, including cover art, and can offer remote control of the audio source.
-
-
Settings can be made using the configuration file (recommended for all new
+
+
Settings can be made using the configuration file (recommended for all new
installations) or by using command-line options.
-
+
The name of the Shairport Sync executable is shairport-sync.
Both names are used in these man pages.
-
+
-
-
+
+
You should use the configuration file for setting up Shairport Sync.
This file is usually shairport-sync.conf and is generally located in the
- System Configuration Directory, which is normally the /etc directory in
+ System Configuration Directory, which is normally the /etc directory in
Linux or the /usr/local/etc directory in BSD unixes.
You may need to have root privileges to modify it.
-
-
(Note: Shairport Sync may have been compiled to use a different configuration
- directory. You can determine which by performing the command $ shairport-sync
- -V. One of the items in the output string is the value of the
+
+
(Note: Shairport Sync may have been compiled to use a different configuration
+ directory. You can determine which by performing the command $ shairport-sync
+ -V. One of the items in the output string is the value of the
sysconfdir,
i.e. the System Configuration Directory.)
-
-
+
+
Within the configuration file, settings are organised into groups, for
example, there is a "general" group of
standard settings, and there is an "alsa" group with settings that pertain to the ALSA
back end. Here is an example of a typical configuration file:
-
+
general = {
name = "Mike's Boombox";
interpolation = "soxr";
@@ -127,20 +127,20 @@
output_device = "hw:0";
mixer_control_name = "PCM";
};
-
-
Most settings have sensible default values, so -- as in the example above -- users
+
+
Most settings have sensible default values, so -- as in the example above -- users
generally only need to set (1) the service name, (2) a password (if desired) and
- (3) the output device. If the output device has a mixer that can be used for volume
- control, then (4) the volume control's name should be specified. It is highly
- desirable to use the output device's mixer for volume control, if available --
- response time is reduced to zero and the processor load is reduced. In the example
+ (3) the output device. If the output device has a mixer that can be used for volume
+ control, then (4) the volume control's name should be specified. It is highly
+ desirable to use the output device's mixer for volume control, if available --
+ response time is reduced to zero and the processor load is reduced. In the example
above, "soxr" interpolation was also enabled.
-
-
A sample configuration file with all possible settings, but with all of them
- commented out, is installed at shairport-sync.conf.sample, within the
- System Configuration Directory -- /etc in Linux,
+
+
A sample configuration file with all possible settings, but with all of them
+ commented out, is installed at shairport-sync.conf.sample, within the
+ System Configuration Directory -- /etc in Linux,
/usr/local/etc in BSD unixes.
-
+
To retain backwards compatibility with previous versions of shairport-sync
you can use still use command line options, but any new features, etc. will
be available only via configuration file settings.
@@ -157,25 +157,25 @@
Use this service_name to identify this player in iTunes, etc.
The following substitutions are allowed:
%h for the computer's hostname,
- %H for the computer's hostname with the first letter capitalised (ASCII
+ %H for the computer's hostname with the first letter capitalised (ASCII
only),
%v for the shairport-sync version number, e.g. "3.0.1" and
- %V for the shairport-sync version string, e.g.
+ %V for the shairport-sync version string, e.g.
"3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
-
The default is "%H", which is replaced by the hostname with the first letter
+
The default is "%H", which is replaced by the hostname with the first letter
capitalised.