]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Squashed commit of the following:
authorMike Brady <mikebradydublin@icloud.com>
Thu, 20 Feb 2020 21:32:35 +0000 (21:32 +0000)
committerMike Brady <mikebradydublin@icloud.com>
Thu, 20 Feb 2020 21:32:35 +0000 (21:32 +0000)
    Don't use strndup as old versions of OpenWrt don't seem to have it
    Update FREEBSD.md
    Fix comparison between old and new metadata strings by checking they have equal lengths as well.
    Remove a redundant definition to prevent use on an uninitialised copy, clean up some of the less-used backends
    Remove some unwanted and unused variable declarations from audio_dummy.c and audio_soundio.c
    Add the -fno-common flag to compilation.
    Treat the "mper" attribute as the 64-bit item that it is rather than a 32-bit item as hithereto. Output it as a hexadecimal number to correspond with the format of the track id obtained from AppleScript
    Make changes to that it compiles under gcc-10 with -fno-common
    Add a SIGCHLD handler to reap zombie processes generated after script invocations where wait_for_completion is set to "no".
    Update and rename CONTRIBUTING.md to REPORTING ISSUES.md
    Add a few more commands and clarify some text.
    Change from talking about a server to talking about a client. Technically, e.g. iTunes is a client of Shairport Sync.
    Store the UserAgent so as to recognise forked-daapd so as to always send a revision_number of 1 when asking for playerstatus of forked-daapd.
    Add or update some of the copyright notices
    Add the ability to set the volume directly to the D-Bus RemoteControl interface and to the MPRIS interface. Both use the recently-discovered ability to set the device_volume.
    Merge in Pieter De Gendt's work on resampling on the jack backend.
    Merge pull request #939 from pdgendt/feature/jack-soxr-resampling
    Add SetAirplayVolume to the D-Bus RemoteControl interface.
    Add SetVolume to the MPRIS interface.
    Hook up the Volume property in the MPRIS interface.
    Modify RemoteCommand in the D-Bus interface to return the HTTP status and response.
    Change the type of airplay_volume from int to double in the metadata hub.
    Add a few sample commands in the D-Bus document.

35 files changed:
Makefile.am
README.md
alac.h
apple_alac.cpp
apple_alac.h
audio.c
audio_alsa.c
audio_dummy.c
audio_jack.c
audio_sndio.c
audio_soundio.c
common.c
common.h
configure.ac
dacp.c
dacp.h
dbus-service.c
dbus-service.h
documents/sample dbus commands
mdns.h
metadata_hub.c
metadata_hub.h
mpris-service.c
mpris-service.h
org.gnome.ShairportSync.xml
org.mpris.MediaPlayer2.xml
player.c
player.h
rtsp.c
rtsp.h
scripts/shairport-sync.conf
shairport-sync-dbus-test-client.c
shairport-sync-mpris-test-client.c
shairport.c
tinysvcmdns.c

index 2bbc387c99dd1b78c11633349dcfc50974d87bbe..c9ccb75ebccc8f0cb84cb7f262e0b115900f0c3f 100644 (file)
@@ -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
 
index b0aea26032e3e54629fc900acffe401c0336c643..48bd955c13484db517f304584f161a31022ec389 100644 (file)
--- 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 33a35928ade743326405e5193f144e01fef34b90..7cdd507dfcb0321af7767ec9466ca5fb8b2e8547 100644 (file)
--- 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
 
index 6d543b2e3be46dc1bbf92bcf56b7c8967e849453..485c5bfec8c354c4223182fb350bc77867ce9312 100644 (file)
@@ -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
index eb8bcf6c917eaaa390517f0812baf5b7bba94190..be11e86ea4bac2de6c12fc4eb4a7a0562e0436fd 100644 (file)
@@ -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 c71ac939a99a65ebe9e04f50acd034af16a223c6..8b2762f459c6bf609232502218b90ed42aa205c3 100644 (file)
--- 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
index 27e35ed827ea17b6d2bf4ee46898a8e093b436ae..39e4d05148c6267424c53c2981a727197c418954 100644 (file)
@@ -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);
 
index 026ef78aa4aa0dee4f0325ecc8e6a1d67e1852d3..5d64ecf238758a82ba689ca332ae7e78d161064a 100644 (file)
 #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);
 }
 
index 0aff5025af4f689697167b2e7d70d3678cfcedce..db6e18994ee3fc413ca1e63fb822d6f482a07ace 100644 (file)
 #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;
 }
index 9fd497ace45eedf908a50a8a85930b35bf6f49fc..ac5cfa380ee346658625a8978b9c6171c2b67bb6 100644 (file)
@@ -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;
index 53de2acbc111a392288cc296f1b70eae636418ca..119f7670eec7e878f42ef53ae50e1dd8b49a7ff8 100644 (file)
@@ -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);
index ff643ef38b1c29ea4e97c20237da991aebc2cc67..228dfec978d81513fdba69631c59c771d46d173c 100644 (file)
--- a/common.c
+++ b/common.c
 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
index 34cd10bae9224e71d1883043227bba93c3f88f32..2c9c05bdb4a8321048948c1ab0dd9281f12857af 100644 (file)
--- a/common.h
+++ b/common.h
 #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);
index df0db32d87116af410de8efd2ec070493d4a6d02..3bbd67bf09775726f5f7a76171d02bcf7c6c935b 100644 (file)
@@ -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 79447afb0e9fcb6dd0a9a6b45767263ced9d0d0c..f3111a8382a3d9b65c3ea47906c53ff16b4f8cae 100644 (file)
--- 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 ce1e127842311755871cdf3a22b3e0a8fb4cfaa8..ea44aacb8431a00e00c6a6218c95dfa6d4365700 100644 (file)
--- 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);
index 0c85a98171a3f909bc60a3efd147cab835beaba9..457aefed9a9d747b9c9c1de797be681e905ef652 100644 (file)
@@ -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);
index 5cfc02e2dcfac67a49cf8fcf0924e6eab7d9cde0..1373e9cb237357d33dd2b9680fe63977b5ab822d 100644 (file)
@@ -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();
index 5b173d4a97463188092600f023c0ca495628c069..96219c195d0d0f25d20e2c6872eb0128e7c579cc 100644 (file)
@@ -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 8dc919f26c7f71d0819aff1aa35e1eb7ecf49174..b69f4c85d14aa22d932f6705c5b8428f332ab0df 100644 (file)
--- 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;
index 0b5aa412273ae17f5ba8330ebf2c06ce9cc5d1bb..f079ea2af76719186dbaa0c4a9994e3a021d5731 100644 (file)
@@ -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;
index 5b565bbad53fb64fbdbc0ac991c55d4e03a97313..75d2f85274d27c5ac046a2c8a267773c0d9c337c 100644 (file)
@@ -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);
 
index 37e7bf8a5ae5abf92c2c00fe3eed84d48a252128..125398a1a71d69f077ce9a99e3ed8220ee8a707f 100644 (file)
@@ -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"
 
 #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);
 
index f290d79c0bbc1b3aa1e19b0adfe9c5b198b4056e..160e55d4d9e79ba2e01fd25ea98d5c191fed7856 100644 (file)
@@ -4,8 +4,8 @@
 
 #include "mpris-interface.h"
 
-MediaPlayer2 *mprisPlayerSkeleton;
-MediaPlayer2Player *mprisPlayerPlayerSkeleton;
+extern MediaPlayer2 *mprisPlayerSkeleton;
+extern MediaPlayer2Player *mprisPlayerPlayerSkeleton;
 
 int start_mpris_service();
 
index 15317faa02ff663435fa91ce59ca425f45fe2607..10eb5477b2b9ba7ed0b1cf85c7acf731842e4c1a 100644 (file)
@@ -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" />
                <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>
index ae34e1e51f8b3f0a21f28caa006bc6d2f9e73347..64ee2cca1242a983fce5136bf2f6490d4820a369 100755 (executable)
@@ -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'/>
index 21eb432f1d981fd3a0bc937942369d9a7ed00dbf..5876bd334a2ccfef2323a7fa9da81f277e0e4339 100644 (file)
--- 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.");
index 9246f4485ba3318736bd5b0dc5b08630058dcd6a..b6cae8a967a0f1caee0b835663e07650f9fb52b1 100644 (file)
--- 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 757bcd36dbc01ff2580474ab631452056d081b9e..6cf5392762409e440498c2e753daca008d1997c5 100644 (file)
--- 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 71e5b22cb1a17292f3feada858b982127bab930a..703c100682a78a76d4f7f94a6fd50932329dfba1 100644 (file)
--- 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);
index bec0d4fc55bbf5cd4e3281faa1fcdc50c703c4f7..9c875156c9c43eadc4b40ed17578df21328b6b29 100644 (file)
@@ -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.
index bf968c456dea0bd494f5f6ec03d25a1bc1637760..d0b15bb29f116cffaa1928167da9519ed73f8035 100644 (file)
@@ -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);
index c15c71bc8df750d57242c11965f505270ccc8355..7af29aea850e866e688b3644086f7511dcfd8ee9 100644 (file)
@@ -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>
index d2a1a4949f460c53a80268550ed38c51243f39f7..5e375916c3802f8a77e4faf09363ec09db609dc3 100644 (file)
@@ -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!");
index 4cb6fb4c470d759f5d0655e16180a69e4921ebcb..069a6436c20ac88c4a2959a5787c11c57bac78d1 100644 (file)
@@ -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;