summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am4
-rw-r--r--README.md4
-rw-r--r--alac.h30
-rw-r--r--apple_alac.cpp26
-rw-r--r--apple_alac.h25
-rw-r--r--audio.c2
-rw-r--r--audio_alsa.c20
-rw-r--r--audio_dummy.c6
-rw-r--r--audio_jack.c174
-rw-r--r--audio_sndio.c2
-rw-r--r--audio_soundio.c6
-rw-r--r--common.c22
-rw-r--r--common.h63
-rw-r--r--configure.ac2
-rw-r--r--dacp.c594
-rw-r--r--dacp.h2
-rw-r--r--dbus-service.c87
-rw-r--r--dbus-service.h2
-rw-r--r--documents/sample dbus commands15
-rw-r--r--mdns.h2
-rw-r--r--metadata_hub.c46
-rw-r--r--metadata_hub.h24
-rw-r--r--mpris-service.c68
-rw-r--r--mpris-service.h4
-rw-r--r--org.gnome.ShairportSync.xml7
-rwxr-xr-xorg.mpris.MediaPlayer2.xml3
-rw-r--r--player.c6
-rw-r--r--player.h7
-rw-r--r--rtsp.c17
-rw-r--r--rtsp.h4
-rw-r--r--scripts/shairport-sync.conf2
-rw-r--r--shairport-sync-dbus-test-client.c80
-rw-r--r--shairport-sync-mpris-test-client.c26
-rw-r--r--shairport.c46
-rw-r--r--tinysvcmdns.c13
35 files changed, 977 insertions, 464 deletions
diff --git a/Makefile.am b/Makefile.am
index 2bbc387..c9ccb75 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
diff --git a/README.md b/README.md
index b0aea26..48bd955 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.
diff --git a/alac.h b/alac.h
index 33a3592..7cdd507 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 6d543b2..485c5bf 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 <string.h>
// these are headers for the ALAC decoder, utilities and endian utilities
diff --git a/apple_alac.h b/apple_alac.h
index eb8bcf6..be11e86 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 c71ac93..8b2762f 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/audio_alsa.c b/audio_alsa.c
index 27e35ed..39e4d05 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/audio_dummy.c b/audio_dummy.c
index 026ef78..5d64ecf 100644
--- a/audio_dummy.c
+++ b/audio_dummy.c
@@ -34,17 +34,11 @@
#include <sys/time.h>
#include <unistd.h>
-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_jack.c b/audio_jack.c
index 0aff502..db6e189 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -29,10 +29,18 @@
#include <jack/jack.h>
#include <jack/ringbuffer.h>
-// Two-channel, 16bit audio:
-static const int bytes_per_frame = 4;
-// Four seconds buffer -- should be plenty
-#define buffer_size (44100 * 4 * bytes_per_frame)
+#ifdef CONFIG_SOXR
+#include <soxr.h>
+#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;
static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -62,7 +70,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 +83,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_quality {
+ int 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 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->quality;
+ }
+ }
+ 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 +122,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 +143,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 +151,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 +167,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;
}
@@ -175,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
@@ -183,6 +220,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_quality = -1; // don't resample by default
+#endif
// Now the options specific to the backend, from the "jack" stanza:
if (config.cfg != NULL) {
@@ -193,13 +233,25 @@ 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_quality", &str)) {
+ debug(1, "SOXR quality %s", 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);
@@ -213,6 +265,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_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
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 +345,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_quality >= 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 +401,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 +412,54 @@ 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);
+
+ 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);
+ }
+ } 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/audio_sndio.c b/audio_sndio.c
index 9fd497a..ac5cfa3 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/audio_soundio.c b/audio_soundio.c
index 53de2ac..119f767 100644
--- a/audio_soundio.c
+++ b/audio_soundio.c
@@ -7,9 +7,6 @@
#include <soundio/soundio.h>
-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);
diff --git a/common.c b/common.c
index ff643ef..228dfec 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
@@ -1550,9 +1554,13 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma
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);
+ //*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;
@@ -1565,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
diff --git a/common.h b/common.h
index 34cd10b..2c9c05b 100644
--- a/common.h
+++ b/common.h
@@ -24,34 +24,34 @@
#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
#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,15 +240,15 @@ 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;
+ 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
@@ -270,6 +270,9 @@ typedef struct {
#ifdef CONFIG_JACK
char *jack_client_name;
char *jack_autoconnect_pattern;
+#ifdef CONFIG_SOXR
+ int jack_soxr_resample_quality;
+#endif
#endif
} shairport_cfg;
@@ -318,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, ...);
@@ -349,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);
@@ -374,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); \
@@ -409,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)
@@ -420,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/configure.ac b/configure.ac
index df0db32..3bbd67b 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.5], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.6], [mikebradydublin@icloud.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
diff --git a/dacp.c b/dacp.c
index 79447af..f3111a8 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
@@ -150,12 +151,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 +214,25 @@ 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);
+ 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 +246,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);
@@ -289,10 +296,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);
@@ -399,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();
@@ -466,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
@@ -487,7 +509,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 +522,10 @@ 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;
-
}
-
+
+ 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);
@@ -514,25 +538,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 +571,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 +588,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,284 +598,291 @@ 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] = "";
+ 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) {
- 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);
- 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;
+ // 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 '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);
- }
+ default:
+ debug(1, "Unrecognised play status %d received.", r);
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 '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 '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);
+ case 1:
+ if (metadata_store.shuffle_status != SS_ON) {
+ metadata_store.shuffle_status = SS_ON;
+ debug(2, "Shuffle status is \"on\".");
}
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);
+ 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 '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;
+ case 1:
+ if (metadata_store.repeat_status != RS_ONE) {
+ metadata_store.repeat_status = RS_ONE;
+ debug(2, "Repeat status is \"one\".");
}
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);
+ case 2:
+ if (metadata_store.repeat_status != RS_ALL) {
+ metadata_store.repeat_status = RS_ALL;
+ debug(2, "Repeat status is \"all\".");
}
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 repeat status %d received.", r);
break;
}
- // printf("\n");
+ 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;
}
-
- // 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");
+ // 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;
- };
- };
- /*
- 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));
+ } 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;
- }
- */
- 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);
@@ -981,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
}
@@ -991,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/dacp.h b/dacp.h
index ce1e127..ea44aac 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 0c85a98..457aefe 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -1,6 +1,33 @@
+/*
+ * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <inttypes.h>
#include "config.h"
@@ -19,6 +46,8 @@
#include <FFTConvolver/convolver.h>
#endif
+ShairportSync *shairportSyncSkeleton;
+
int service_is_running = 0;
ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL;
@@ -30,7 +59,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);
@@ -38,8 +67,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 {
@@ -135,7 +166,7 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairportSyncAdvancedRemoteControlSkeleton, response);
}
-
+
switch (argc->shuffle_status) {
case SS_NOT_AVAILABLE:
new_status = FALSE;
@@ -150,10 +181,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");
@@ -174,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);
@@ -217,7 +248,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);
@@ -337,6 +368,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.");
@@ -474,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
@@ -715,11 +759,29 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
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);
+ int reply = 0;
+ char *client_reply = NULL;
+ ssize_t reply_size = 0;
+ 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);
+ p += 2;
+ q++;
+ }
+ }
+ *p = '\0';
+ }
+ shairport_sync_complete_remote_command(skeleton, invocation, reply, client_reply_hex);
return TRUE;
}
+
static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name,
__attribute__((unused)) gpointer user_data) {
@@ -814,6 +876,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);
@@ -914,7 +979,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/dbus-service.h b/dbus-service.h
index 5cfc02e..1373e9c 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/documents/sample dbus commands b/documents/sample dbus commands
index 5b173d4..96219c1 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/mdns.h b/mdns.h
index 8dc919f..b69f4c8 100644
--- a/mdns.h
+++ b/mdns.h
@@ -2,7 +2,7 @@
#define _MDNS_H
#include "config.h"
-#include <player.h>
+#include "player.h"
#include <stdint.h>
extern int mdns_pid;
diff --git a/metadata_hub.c b/metadata_hub.c
index 0b5aa41..f079ea2 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--2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -38,6 +38,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include <inttypes.h>
#include "config.h"
@@ -58,6 +59,8 @@
#include <openssl/md5.h>
#endif
+struct metadata_bundle metadata_store;
+
int metadata_hub_initialised = 0;
pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER;
@@ -301,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':
@@ -539,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;
diff --git a/metadata_hub.h b/metadata_hub.h
index 5b565bb..75d2f85 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,16 +59,16 @@ 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
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.
@@ -123,20 +123,20 @@ 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
- 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
} 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 37e7bf8..125398a 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -1,5 +1,31 @@
+/*
+ * This file is part of Shairport Sync.
+ * Copyright (c) Mike Brady 2018 -- 2020
+ * 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 <stdio.h>
#include <string.h>
+#include <inttypes.h>
#include "config.h"
@@ -14,10 +40,31 @@
#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;
+ 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");
@@ -128,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);
}
@@ -229,6 +275,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 +312,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 +331,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/mpris-service.h b/mpris-service.h
index f290d79..160e55d 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();
diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml
index 15317fa..10eb547 100644
--- a/org.gnome.ShairportSync.xml
+++ b/org.gnome.ShairportSync.xml
@@ -13,6 +13,8 @@
<property name="DriftTolerance" type="d" access="readwrite" />
<method name="RemoteCommand">
<arg name="command" type="s" direction="in" />
+ <arg name="reply" type="i" direction="out" />
+ <arg name="response" type="s" direction="out" />
</method>
<property name="VolumeControlProfile" type="s" access="readwrite" />
<property name="Interpolation" type="s" access="readwrite" />
@@ -43,8 +45,11 @@
<method name='VolumeDown'/>
<property name='PlayerState' type='s' access='read'/>
<property name='ProgressString' type='s' access='read'/>
- <property name='Server' type='s' access='read'/>
+ <property name='Client' type='s' access='read'/>
<property name='AirplayVolume' type='d' access='read'/>
+ <method name="SetAirplayVolume">
+ <arg name="volume" type="d" direction="in" />
+ </method>
<property name='Metadata' type='a{sv}' access='read'>
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
</property>
diff --git a/org.mpris.MediaPlayer2.xml b/org.mpris.MediaPlayer2.xml
index ae34e1e..64ee2cc 100755
--- a/org.mpris.MediaPlayer2.xml
+++ b/org.mpris.MediaPlayer2.xml
@@ -41,6 +41,9 @@
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
</property>
<property name='Volume' type='d' access='read'/>
+ <method name='SetVolume'>
+ <arg name='volume' type='d' direction='in' />
+ </method>
<property name='Position' type='x' access='read'/>
<property name='MinimumRate' type='d' access='read'/>
<property name='MaximumRate' type='d' access='read'/>
diff --git a/player.c b/player.c
index 21eb432..5876bd3 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 9246f44..b6cae8a 100644
--- a/player.h
+++ b/player.h
@@ -63,22 +63,23 @@ 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 {
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 757bcd3..6cf5392 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;
@@ -626,16 +627,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 +1890,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 +1931,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 +2269,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)
diff --git a/rtsp.h b/rtsp.h
index 71e5b22..703c100 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);
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index bec0d4f..9c87515 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 = <number>; // 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.
diff --git a/shairport-sync-dbus-test-client.c b/shairport-sync-dbus-test-client.c
index bf968c4..d0b15bb 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 <popt.h>
#include <stdio.h>
@@ -40,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);
@@ -162,28 +186,63 @@ 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("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(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(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(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(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(5);
+
+ 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(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(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(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);
-
- 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);
+ SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 50, NULL, NULL, 0);
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, 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("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);
@@ -201,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);
diff --git a/shairport-sync-mpris-test-client.c b/shairport-sync-mpris-test-client.c
index c15c71b..7af29ae 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 <popt.h>
#include <stdio.h>
diff --git a/shairport.c b/shairport.c
index d2a1a49..5e37591 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!");
diff --git a/tinysvcmdns.c b/tinysvcmdns.c
index 4cb6fb4..069a643 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;