From 5adb76c37d0d927eaf0385047704111690f2031d Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 10:51:33 +0100 Subject: [PATCH 01/42] Include lightweight tags (that GitHub uses to mark releases) when forming the version number and version string. Duh. --- Makefile.am | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index bc6710d8..7b896b3b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,14 +37,14 @@ endif endif endif -# include information generated by 'git describe --dirty' if requested +# include information generated by 'git describe --tags --dirty' if requested if USE_GIT_VERSION common.c: gitversion.h gitversion.h: .git/index printf "// Do not edit!\n" > gitversion.h - printf "// This file is automatically generated by 'git describe --dirty', if available.\n" >> gitversion.h + printf "// This file is automatically generated by 'git describe --tags --dirty', if available.\n" >> gitversion.h printf " char git_version_string[] = \"" >> gitversion.h - git describe --dirty | tr -d '[[:space:]]' >> gitversion.h + git describe --tags --dirty | tr -d '[[:space:]]' >> gitversion.h printf "\";\n" >> gitversion.h CLEANFILES += gitversion.h endif From 4509db877aae804e6f397ae45e0cc7e4cf065995 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 10:55:23 +0100 Subject: [PATCH 02/42] Remove superseded files. --- BUILDFORAP1.md | 3 --- BUILDFORAP2.md | 3 --- FEDORA.md | 3 --- FREEBSD.md | 3 --- MOREINFO.md | 3 --- UPDATING.md | 3 --- 6 files changed, 18 deletions(-) delete mode 100644 BUILDFORAP1.md delete mode 100644 BUILDFORAP2.md delete mode 100644 FEDORA.md delete mode 100644 FREEBSD.md delete mode 100644 MOREINFO.md delete mode 100644 UPDATING.md diff --git a/BUILDFORAP1.md b/BUILDFORAP1.md deleted file mode 100644 index e173e594..00000000 --- a/BUILDFORAP1.md +++ /dev/null @@ -1,3 +0,0 @@ -# Build Instructions for AirPlay 1 - -This guide has been superseded by the general building guide at [BUILD.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILD.md). diff --git a/BUILDFORAP2.md b/BUILDFORAP2.md deleted file mode 100644 index cf856c48..00000000 --- a/BUILDFORAP2.md +++ /dev/null @@ -1,3 +0,0 @@ -# Building Shairport Sync for AirPlay 2 - -This guide has been superseded by the general building guide at [BUILD.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILD.md). diff --git a/FEDORA.md b/FEDORA.md deleted file mode 100644 index 7a8fd57a..00000000 --- a/FEDORA.md +++ /dev/null @@ -1,3 +0,0 @@ -# Fedora Installation Guide - -For the present, this guide has been superseded by the general building guide at [BUILD.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILD.md). diff --git a/FREEBSD.md b/FREEBSD.md deleted file mode 100644 index 8a95a4ab..00000000 --- a/FREEBSD.md +++ /dev/null @@ -1,3 +0,0 @@ -# Shairport Sync on FreeBSD - -This guide has been superseded by the general building guide at [BUILD.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILD.md). diff --git a/MOREINFO.md b/MOREINFO.md deleted file mode 100644 index 147d195d..00000000 --- a/MOREINFO.md +++ /dev/null @@ -1,3 +0,0 @@ -# More Information - -Information in this document has been updated and moved to [ADVANCED TOPICS](https://github.com/mikebrady/shairport-sync/blob/development/ADVANCED%20TOPICS/README.md). diff --git a/UPDATING.md b/UPDATING.md deleted file mode 100644 index d0ebae46..00000000 --- a/UPDATING.md +++ /dev/null @@ -1,3 +0,0 @@ -### Updating Shairport Sync - -This guide has been superseded by the general building guide at [BUILD.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILD.md). From 5f086497f39534a85e3a267d3227515027cfdc11 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 11:03:44 +0100 Subject: [PATCH 03/42] Update to reflect removal of superseded documents. --- CAR INSTALL.md | 2 +- RELEASENOTES-DEVELOPMENT.md | 7 +------ RELEASENOTES.md | 4 ---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/CAR INSTALL.md b/CAR INSTALL.md index c2259a5f..84513289 100644 --- a/CAR INSTALL.md +++ b/CAR INSTALL.md @@ -240,7 +240,7 @@ From this point on, if you reboot the machine, it will connect to the network it 4. Reboot and do Normal Updating - You can perform updates in the normal way -- see [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md). When you are finished, you need to undo the temporary changes you made to the setup, as follows: + You can perform updates in the normal way. When you are finished, you need to undo the temporary changes you made to the setup, as follows: 5. If you had temporarily re-enabled services that are normally disabled, then it's time to disable them again: `# systemctl disable dhcpcd.service` diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index bb6aa59d..ede8ab49 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -433,7 +433,7 @@ Version-4.1-dev-102-g9bf45574 Version-4.1-dev-100-g8330f30b ==== #### Enhancement -* Updated information on the statistics provided -- see [MOREINFO.md](https://github.com/mikebrady/shairport-sync/blob/development/MOREINFO.md#statistics). +* Updated information on the statistics provided -- see [MOREINFO.md](https://github.com/mikebrady/shairport-sync/blob/development/MOREINFO.md#statistics). (Note: this has moved to [STATISTICS.md](ADVANCED%20TOPICS/Statistics.md).) Version 4.1-dev-95-ga7a02083 ==== @@ -1690,9 +1690,6 @@ To retain the present behaviour of Shairport Sync, *you must add an extra parame The enhancements and bug fixes in 2.8.5 were made in versions 2.8.4.1 to 2.8.4.8 inclusive. Please read below for the full list. -For advice on updating an installation you built yourself, -please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page. - Version 2.8.4.8 – Development Version ---- **Enhancements** @@ -1749,8 +1746,6 @@ Version 2.8.4.1 – Development Version Version 2.8.4 – Stable Version ---- -This release includes important bug fixes and minor enhancements and is recommended for all users. No settings need to be changed. For advice on updating an installation you built yourself, please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page. - The following is a summary of the bug fixes and enhancements since version 2.8.3. **Bug Fixes** diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9fdc0628..275ef76a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -557,9 +557,6 @@ To retain the present behaviour of Shairport Sync, *you must add an extra parame The enhancements and bug fixes in 2.8.5 were made in versions 2.8.4.1 to 2.8.4.8 inclusive. Please read below for the full list. -For advice on updating an installation you built yourself, -please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page. - Version 2.8.4.8 – Development Version ---- **Enhancements** @@ -616,7 +613,6 @@ Version 2.8.4.1 – Development Version Version 2.8.4 – Stable Version ---- -This release includes important bug fixes and minor enhancements and is recommended for all users. No settings need to be changed. For advice on updating an installation you built yourself, please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page. The following is a summary of the bug fixes and enhancements since version 2.8.3. From 65daef30c2d31b0ac726da2c131639d59329fbaf Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:11:53 +0100 Subject: [PATCH 04/42] Add a section on adjusting sync to compensate for amplifier delays. Update the troubleshooting guide to point to it. --- ADVANCED TOPICS/AdjustingSync.md | 25 +++++++++++++++++++++++++ ADVANCED TOPICS/README.md | 1 + TROUBLESHOOTING.md | 10 ++-------- 3 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 ADVANCED TOPICS/AdjustingSync.md diff --git a/ADVANCED TOPICS/AdjustingSync.md b/ADVANCED TOPICS/AdjustingSync.md new file mode 100644 index 00000000..cc4627f8 --- /dev/null +++ b/ADVANCED TOPICS/AdjustingSync.md @@ -0,0 +1,25 @@ +# Adjusting Synchronisation on Shairport Sync ("SPS") + +Sometimes, a timing difference can be heard, where the audio coming from the SPS-powered device is slightly ahead or slightly behind another device playing in synchrony. This can sometimes be heard as an irritating "echo". + +This is usually due to audio amplifier delays. + +* If your audio output device (including the amplifier in a TV) includes any digital processing component, it probably delays audio while amplifying it. + +* If your output device is a HDMI-connected device such as a TV or an AV Receiver (AVR), it will almost certainly delay audio by anything up to several hundred milliseconds. + +In these circumstances, if the output from the SPS device is amplified by a conventional analog-only HiFi amplifier – which has almost no delay – it will be early by comparison with audio coming the other device. + +Conversely, if the output from the SPS device is passed through an AVR, then it could be late by comparison with audio amplified by a conventional audio amplifier. + +The fix for this is to get Shairport Sync to compensate for delays by providing audio to the output device _slightly late_ or _slightly early_, so that when audio emerges from the amplifier, it is in exact synchrony with audio from the other devices. + +The setting to look for is in the `general` section of the Shairport Sync configuration file and is called `audio_backend_latency_offset_in_seconds`. By default it is `0.0` seconds. + +To delay the output from the SPS device by 100 milliseconds (0.1 seconds), set the `audio_backend_latency_offset_in_seconds` to `0.1`, so that audio is provided to your output device 100 milliseconds later than nominal synchronisation time. + +Similarly, to get the output from the SPS device 50 milliseconds (0.05 seconds) early, set the `audio_backend_latency_offset_in_seconds` to `-0.05`, so that audio is provided to your output device 50 milliseconds earlier than nominal synchronisation time. + +Latency adjustments should be small, not more than about ± 250 milliseconds. + +Remember to uncomment the line by removing the initial `//` and then restart Shairport Sync (or reboot the device) for the changed setting to take effect. diff --git a/ADVANCED TOPICS/README.md b/ADVANCED TOPICS/README.md index b5c3c232..19db0266 100644 --- a/ADVANCED TOPICS/README.md +++ b/ADVANCED TOPICS/README.md @@ -1,6 +1,7 @@ # Advanced Topics Here you will find links to some advanced features and things you can do with Shairport Sync. * [Finish Setting Up](InitialConfiguration.md). +* [Adjusting Sync](AdjustingSync.md) – advance or delay the timing of the output from Shairport Sync to compensate for amplifier delays. * [Get The Best](GetTheBest.md) from your system. * [Metadata](Metadata.md). * [Events](Events.md). diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 2bccf048..ad09ccb1 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -15,14 +15,8 @@ If you are using the default ALSA backend, don't forget to check two simple thin You can use `alsamixer` for both of theses checks. A muted output has the letter(s) `M` as its value. Select it and type `M` again to unmute. -### Audio is Delayed! -If the audio from your Shairport Sync device is delayed slightly by comparison with audio from other devices, it may be that the output device being fed by Shairport Sync is introducing a delay while it processes the audio. If your output device include any digital processing component, it probably delays the audio while it processing occurs. - -For instance, if your output device is a HDMI-connected device such as a TV or an AV Receiver, it will almost certainly delay audio by anything up to several hundred milliseconds. - -The fix for this is to ask Shairport Sync to provide the audio to the output device _slightly ahead of time_, so that by the time the output device has processed it, the audio emerges at exactly the right time. The setting to look for is in the `general` section of the Shairport Sync configuration file and is called `audio_backend_latency_offset_in_seconds`. By default it is `0.0` seconds. - -For example, if your output device is delaying audio by 100 milliseconds (0.1 seconds), set the `audio_backend_latency_offset_in_seconds` to `-0.1`, so that audio is provided to your output device 0.1 seconds early. Remember to uncomment the line by removing the initial `//` and then restart Shairport Sync (or reboot the device) for the changed setting to take effect. +### Sync is slightly off! +Please see [Adjusting Sync](./ADVANCED%20TOPICS/AdjustingSync.md). ### WiFi adapter running in power-saving / low-power mode From aa3345ac23a757a5659a3d27ab6def96b9fca83c Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:18:14 +0100 Subject: [PATCH 05/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index ede8ab49..35d8074e 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,11 @@ +Version 4.1-dev-701-g65daef30 +==== +**Bug Fix** +* Fix a bug in the generation of version information from git tags. The fix is to use lightweight tags as well as annotated tags. GitHub marks releases with lightweight tags, so this should make version and release information correspond better. + +**Enhancement** +* Add an new Advanced Topic -- [Adjusting Sync](ADVANCED%20TOPICS/AdjustincSync.md) explaining how to compensate for amplifier delays such as might be found on TVs or AVRs. + Version 4.1-dev-694-g234c00ad ==== **Bug Fix** From e4bb1447afb3ca99e6c80c31f224f583a0f37c5a Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 15:00:04 +0100 Subject: [PATCH 06/42] Update BUILD.md --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 3d5bc4a9..15c5253f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -194,4 +194,4 @@ With AirPlay 2, you can follow the steps in [ADDINGTOHOME.md](ADDINGTOHOME.md) t ### Wait, there's more... Instead of using default values for everything, you can use the configuration file to get finer control over the setup, particularly the output device and mixer control -- see [Finish Setting Up](ADVANCED%20TOPICS/InitialConfiguration.md). -Please take a look at [Advanced Topics](ADVANCED%20TOPICS/README.md) for some ideas about what else you can do to enhance the operation of Shairport Sync. +Please take a look at [Advanced Topics](ADVANCED%20TOPICS/README.md) for some ideas about what else you can do to enhance the operation of Shairport Sync. For example, you can adjust synchronisation to compensate for delays in your system. From 76388d1325e675c6617c379448869e2ef35c44a2 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 20:50:42 +0100 Subject: [PATCH 07/42] Automake seems to get confused about where common.c is, due perhaps to its dependence on gitversion.h. This seems to fix it. --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 7b896b3b..65ced9de 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,7 +39,7 @@ endif # include information generated by 'git describe --tags --dirty' if requested if USE_GIT_VERSION -common.c: gitversion.h +$(top_srcdir)/common.c: gitversion.h gitversion.h: .git/index printf "// Do not edit!\n" > gitversion.h printf "// This file is automatically generated by 'git describe --tags --dirty', if available.\n" >> gitversion.h From 5b279b2e45b0a599c4755d4b414c3b0e63a1870f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 20:52:07 +0100 Subject: [PATCH 08/42] Use xsltproc instead of xmlmantohtml, which seems to be broken. --- man/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/Makefile.am b/man/Makefile.am index e40bfb85..062b1dd5 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -6,6 +6,6 @@ all-local: shairport-sync.html shairport-sync.7: shairport-sync.7.xml xmltoman $< > $@ -shairport-sync.html: shairport-sync.7.xml - xmlmantohtml $< > $@ +shairport-sync.html: xmltoman.xsl shairport-sync.7.xml + xsltproc $^ > $@ endif From e16d9a45d1e2916dff0bf867bf5b3775837c6adb Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 20:52:55 +0100 Subject: [PATCH 09/42] Update for AP2 and other things, and fix a few errors. --- man/shairport-sync.7.xml | 867 +++------------------------------------ 1 file changed, 61 insertions(+), 806 deletions(-) diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index e5dc1c56..d7ebaffb 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -4,7 +4,7 @@ - + - shairport-sync [-djvuw] + shairport-sync [-djvw] [-a name] [-A latency] [-B command] @@ -91,8 +91,9 @@

Within the configuration file, settings are organised into groups, for example, there is a "general" group of - standard settings, and there is an "alsa" group with settings that pertain to the ALSA - back end. Here is an example of a typical configuration file:

+ standard settings, and there is an alsa group with settings + that pertain to the ALSA back end. + Here is an example of a typical configuration file:

general = {

name = "Mike's Boombox";

From 86a52964f9431b79fae25c802d27726fd3d16dbb Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 22:39:15 +0100 Subject: [PATCH 13/42] Don't automatically try to build the man file -- assume it's already there. Add a separate Makefile for the man directory. Only generate the man file (not the html file) by default. Specify the man file in this directory for installation during installation of shairport sync --- Makefile.am | 3 ++- configure.ac | 16 +--------------- man/Makefile.am | 13 ------------- 3 files changed, 3 insertions(+), 29 deletions(-) delete mode 100644 man/Makefile.am diff --git a/Makefile.am b/Makefile.am index 65ced9de..0d3ec66f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,7 @@ -SUBDIRS = man ARFLAGS = cr +man_MANS = $(top_srcdir)/man/shairport-sync.7 + lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread lib_tinyhttp_a_CFLAGS = -pthread lib_dbus_interface_a_CFLAGS = -pthread diff --git a/configure.ac b/configure.ac index 16d10eb9..b65aad38 100644 --- a/configure.ac +++ b/configure.ac @@ -444,20 +444,6 @@ if test "x${with_systemd}" = xyes ; then [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) fi -# Look for xmltoman -AC_CHECK_PROGS([XMLTOMAN], [xmltoman]) -if test -z "$XMLTOMAN"; then - AC_MSG_WARN([xmltoman not found - not installing man page]) -fi -AM_CONDITIONAL([HAVE_XMLTOMAN], [test -n "$XMLTOMAN"]) - -# Look for xmltoman -AC_CHECK_PROGS([XSLTPROC], [xsltproc]) -if test -z "$XSLTPROC"; then - AC_MSG_WARN([xsltproc not found - not creating html version of man]) -fi -AM_CONDITIONAL([HAVE_XSLTPROC], [test -n "$XSLTPROC"]) - # Checks for header files. AC_CHECK_HEADERS([getopt_long.h]) AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h mach/mach.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h syslog.h unistd.h]) @@ -482,6 +468,6 @@ AC_FUNC_FORK AC_CHECK_FUNCS([atexit clock_gettime gethostname inet_ntoa memchr memmove memset mkfifo pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul]) # Note -- there are AC_CONFIG_FILES directives further back, conditional on Avahi -AC_CONFIG_FILES([Makefile man/Makefile]) +AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([scripts/shairport-sync],[chmod +x scripts/shairport-sync]) AC_OUTPUT diff --git a/man/Makefile.am b/man/Makefile.am deleted file mode 100644 index c7d4dfba..00000000 --- a/man/Makefile.am +++ /dev/null @@ -1,13 +0,0 @@ -man_MANS = shairport-sync.7 - -if HAVE_XMLTOMAN -shairport-sync.7: shairport-sync.7.xml - xmltoman $< > $@ -endif - -if HAVE_XSLTPROC -all-local: shairport-sync.html - -shairport-sync.html: xmltoman.xsl shairport-sync.7.xml - xsltproc $^ > $@ -endif From 404a4d6ccafcfa3a90f4aca740106b04c26c201f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 22:40:17 +0100 Subject: [PATCH 14/42] The man file to be installed --- man/shairport-sync.7 | 165 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 man/shairport-sync.7 diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 new file mode 100644 index 00000000..0f80efef --- /dev/null +++ b/man/shairport-sync.7 @@ -0,0 +1,165 @@ +.TH shairport-sync 7 User Manuals +.SH NAME +shairport-sync \- AirPlay and AirPlay 2 Audio Player +.SH SYNOPSIS +\fBshairport-sync [-djvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--get-cover-art]\fB [--logOutputLevel]\fB [--log-to-syslog]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB + +shairport-sync -k\fB + +shairport-sync -h\fB + +shairport-sync -V\fB +\f1 +.SH DESCRIPTION +Shairport Sync plays audio streamed from an AirPlay or an AirPlay 2 device. AirPlay 2 support is limited, and AirPlay 2 from iTunes for Windows is not supported. Please see \fBhttps://github.com/mikebrady/shairport-sync\f1 for details. + +Settings can be made using the configuration file (recommended for all new installations) or by using command-line options. + +The name of the Shairport Sync executable is \fBshairport-sync\f1. Both names are used in these man pages. +.SH CONFIGURATION FILE SETTINGS +You should use the configuration file for setting up Shairport Sync. This file is usually \fIshairport-sync.conf\f1 and is generally located in the System Configuration Directory, which is normally the \fI/etc\f1 directory in Linux or the \fI/usr/local/etc\f1 directory in BSD unixes. You may need to have root privileges to modify it. + +(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command \fI$ shairport-sync -V\f1. One of the items in the output string is the value of the \fBsysconfdir\f1, i.e. the System Configuration Directory.) + +Within the configuration file, settings are organised into groups, for example, there is a "general" group of standard settings, and there is an \fBalsa\f1 group with settings that pertain to the \fBALSA\f1 back end. Here is an example of a typical configuration file: + +\fBgeneral = {\f1 + +\fBname = "Mike's Boombox";\f1 + +\fB};\f1 + +\fB\f1 + +\fBalsa = {\f1 + +\fBoutput_device = "hw:0";\f1 + +\fBmixer_control_name = "PCM";\f1 + +\fB};\f1 + +Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name and (2) the output device. If the \fBname\f1 setting is omitted, the service name is derived from the system's hostname. By default, the \fBALSA\f1 backend will be chosen if included in the build. If the (alsa) output device has a mixer that can be used for volume control, then (3) the mixer name should be specified. It is important to do this if the mixer exists. Otherwise, the maximum output from the output device will be whatever setting the mixer happens to have, which will be a matter of chance and which could be very low or even silent. + +A sample configuration file with all possible settings, but with all of them commented out, is installed at \fIshairport-sync.conf.sample\f1, within the System Configuration Directory -- \fI/etc\f1 in Linux, \fI/usr/local/etc\f1 in BSD unixes. + +The sample configuration file includes extensive documentation of the settings. and is also available at \fBhttps://github.com/mikebrady/shairport-sync/blob/master/scripts/shairport-sync.conf\f1. Please refer to it for the most up-to-date information on configuration file settings. + +New features, etc. will generally be available only via configuration file settings. +.SH OPTIONS +Many command-line options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages. + +There are two kinds of command-line options for shairport-sync: regular \fBprogram options\f1 and \fBaudio backend options\f1. Program options are always listed first, followed by any audio backend options, preceded by a \fB--\f1 symbol. +.SH PROGRAM OPTIONS +Program Options are used by shairport-sync itself. +.TP +\fB-a \f1\fIservice name\f1\fB | --name=\f1\fIservice name\f1 +Use this \fIservice name\f1 to identify this player in iTunes, etc. + +The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc". + +The default is "%H", which is replaced by the hostname with the first letter capitalised. +.TP +\fB-B \f1\fIprogram\f1\fB | --on-start=\f1\fIprogram\f1 +Execute \fIprogram\f1 when playback is about to begin. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have the appropriate shebang (\fI#!/bin/sh\f1) in the headline. + +If you want shairport-sync to wait until the command has completed before starting to play, select the \fB-w\f1 option as well. +.TP +\fB-c \f1\fIfilename\f1\fB | --configfile=\f1\fIfilename\f1 +Read configuration settings from \fIfilename\f1. The default is to read them from the \fIshairport-sync.conf\f1 in the System Configuration Directory -- \fI/etc\f1 in Linux, \fI/usr/local/etc\f1 in BSD unixes. For information about configuration settings, see the "Configuration File Settings" section above. +.TP +\fB-d | --daemon\f1 +Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time. See also the \fB-j\f1 option. Only available if shairport-sync has been compiled with libdaemon support. +.TP +\fB-E \f1\fIprogram\f1\fB | --on-stop=\f1\fIprogram\f1 +Execute \fIprogram\f1 when playback has ended. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have the appropriate shebang (\fI#!/bin/sh\f1) in the headline. + +If you want shairport-sync to wait until the command has completed before continuing, select the \fB-w\f1 option as well. +.TP +\fB--get-coverart\f1 +This option requires the \fB--meta-dir\f1 option to be set, and enables shairport-sync to request cover art from the source and to transmit it through the metadata pipe. + +Please note that cover art data may be very large, and may place too great a burden on your network. +.TP +\fB-h | --help\f1 +Print brief help message and exit. +.TP +\fB-j\f1 +Instruct shairport-sync to demonise itself. Unlike the \fB-d\f1 option, it will not write a Process ID (PID) to a file -- it will just (hence the "j") demonise itself. Only available if shairport-sync has been compiled with libdaemon support. +.TP +\fB-k | --kill\f1 +Kill the shairport-sync daemon and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option. Only available if shairport-sync has been compiled with libdaemon support.) +.TP +\fB--logOutputLevel\f1 +Use this to log the volume level when the volume is changed. It may be useful if you are trying to determine a suitable value for the maximum volume level. Not available as a configuration file setting. +.TP +\fB--log-to-syslog\f1 +Warnings, error messages and messages are sent, by default, to \fISTDERR\f1. Use this option to route these messages to the \fBsyslog\f1 instead. This is intended for use when Shairport Sync is operating as a daemon. +.TP +\fB-L | --latency=\f1\fIlatency\f1 +Use this to set the \fIdefault latency\f1, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the \fIdefault latency\f1 is 88,200 frames, where there are 44,100 frames to the second. + +Please note that this feature is deprecated and will be removed in a future version of shairport-sync. +.TP +\fB--meta-dir=\f1\fIdirectory\f1 +Listen for metadata coming from the source and send it, along with metadata from shairport-sync itself, to a pipe called \fIshairport-sync-metadata\f1 in the \fIdirectory\f1 you specify. If you add the \fB--get-cover-art\f1 then cover art will be sent through the pipe too. See \fBhttps://github.com/mikebrady/shairport-sync-metadata-reader\f1 for a sample metadata reader. +.TP +\fB-m \f1\fImdnsbackend\f1\fB | --mdns=\f1\fImdnsbackend\f1 +Force the use of the specified mDNS backend to advertise the player on the network. The default is to try all mDNS backends until one works. +.TP +\fB-o \f1\fIoutputbackend\f1\fB | --output=\f1\fIoutputbackend\f1 +Force the use of the specified output backend to play the audio. The default is to try the first one. +.TP +\fB-p \f1\fIport\f1\fB | --port=\f1\fIport\f1 +Listen for play requests on \fIport\f1. The default is to use port 5000 for AirPlay and 7000 for AirPlay 2. +.TP +\fB--password=\f1\fIsecret\f1 +Require the password \fIsecret\f1 to be able to connect and stream to the service. (This only works for AirPlay and not for AirPlay 2.) +.TP +\fB-r \f1\fIthreshold\f1\fB | --resync=\f1\fIthreshold\f1 +Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation. This setting is deprecated and will be removed in a future version of shairport-sync. +.TP +\fB--statistics\f1 +Print some performance information \fISTDERR\f1, or to \fBsyslog\f1 if the \fB-log-to-syslog\f1 command line option is also chosen. +.TP +\fB-S \f1\fImode\f1\fB | --stuffing=\f1\fImode\f1 +Interpolate ("stuff") the audio stream using the \fImode\f1. "Stuffing" refers to the process of adding or removing frames of audio to or from the stream sent to the output device in order to keep it synchronised with the player. The \fBbasic\f1 mode is normally almost completely inaudible. The alternative mode, \fBsoxr\f1, is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled. The default setting, \fBauto\f1, allows Shairport Sync to choose \fBsoxr\f1 mode if the system is powerful enough. +.TP +\fB-t \f1\fItimeout\f1\fB | --timeout=\f1\fItimeout\f1 +Exit play mode if the stream disappears for more than \fItimeout\f1 seconds. + +When shairport-sync plays an audio stream, it starts a play session and will return a busy signal to any other sources that attempt to use it. If the audio stream disappears for longer than \fItimeout\f1 seconds, the play session will be terminated. If you specify a timeout time of \fB0\f1, shairport-sync will never signal that it is busy and will not prevent other sources from "barging in" on an existing play session. The default value is 120 seconds. +.TP +\fB--tolerance=\f1\fIframes\f1 +Allow playback to be up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections. This setting is deprecated and will be removed in a future version of shairport-sync. +.TP +\fB-V | --version\f1 +Print version information and exit. +.TP +\fB-v | --verbose\f1 +Print debug information to the \fISTDERR\f1, or to \fBsyslog\f1 if the \fB-log-to-syslog\f1 command line option is also chosen. Repeat up to three times (i.e. \fB-vv\f1 or \fB-vvv\f1) for more detail. +.TP +\fB-w | --wait-cmd\f1 +Wait for commands specified using \fB-B\f1 or \fB-E\f1 to complete before continuing execution. +.SH AUDIO BACKEND OPTIONS +Audio Backend Options are command-line options that are passed to the chosen audio backend. They are always preceded by the \fB--\f1 symbol to introduce them and to separate them from any preceding program options. In this way, option letters can be used as program options and reused as audio backend options without ambiguity. + +Audio backends are listed with their corresponding Audio Backend Options in the help text provided by the help (\fB-h\f1 or \fB--help\f1) option. +.SH EXAMPLES +Here is a slightly contrived example: + +shairport-sync \fB-a "Joe's Stereo"\f1 \fB--\f1 \fB-d hw:1,0\f1 \fB-m hw:1\f1 \fB-c PCM\f1 + +The program will be visible as "Joe's Stereo" ( \fB-a "Joe's Stereo"\f1 ). The audio backend options following the \fB--\f1 separator specify that the audio will be output on output 0 of soundcard 1 ( \fB-d hw:1,0\f1 ) and will take advantage of the same sound card's mixer ( \fB-m hw:1\f1 ) using the level control named "PCM" ( \fB-c "PCM"\f1 ). + +The example above is slightly contrived in order to show the use of the \fB-m\f1 option. Typically, output 0 is the default output of a card, so the output device could be written \fB-d hw:1\f1 and then the mixer option would be unnecessary, giving the following, simpler, command: + +shairport-sync \fB-a "Joe's Stereo"\f1 \fB--\f1 \fB-d hw:1\f1 \fB-c PCM\f1 +.SH CREDITS +Mike Brady developed shairport-sync from the original Shairport by James Laird. + +shairport-sync can be found at \fBhttps://github.com/mikebrady/shairport-sync.\f1 + +Shairport can be found at \fBhttps://github.com/abrasive/shairport.\f1 +.SH COMMENTS +This man page was written using \fBxml2man(1)\f1 by Oliver Kurth. From 491cbf6b5c3ffb1b2d8890eee6cc984b3180039d Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 10 Oct 2022 22:44:03 +0100 Subject: [PATCH 15/42] xmltoman is no longer needed unless you are changing the man entry -- the man file is part of the repository. --- BUILD.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BUILD.md b/BUILD.md index 15c5253f..6db01e3f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -37,7 +37,7 @@ Okay, now let's get the tools and libraries for building and installing Shairpor ``` # apt update # apt upgrade # this is optional but recommended -# apt install --no-install-recommends build-essential git xmltoman autoconf automake libtool \ +# apt install --no-install-recommends build-essential git autoconf automake libtool \ libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev \ libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev xxd ``` @@ -45,7 +45,7 @@ If you are building classic Shairport Sync, the list of packages is shorter: ``` # apt update # apt upgrade # this is optional but recommended -# apt-get install --no-install-recommends build-essential git xmltoman autoconf automake libtool \ +# apt-get install --no-install-recommends build-essential git autoconf automake libtool \ libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev ``` ### Fedora @@ -53,7 +53,7 @@ For AirPlay 2 operation, _before you install the libraries_, please ensure the y ``` # yum update # yum install make automake gcc gcc-c++ \ - git xmltoman autoconf automake avahi-devel libconfig-devel openssl-devel popt-devel soxr-devel \ + git autoconf automake avahi-devel libconfig-devel openssl-devel popt-devel soxr-devel \ ffmpeg ffmpeg-devel libplist-devel libsodium-devel libgcrypt-dev libuuid-devel vim-common \ alsa-lib-devel ``` @@ -61,7 +61,7 @@ If you are building classic Shairport Sync, the list of packages is shorter: ``` # yum update # yum install make automake gcc gcc-c++ \ - git xmltoman autoconf automake avahi-devel libconfig-devel openssl-devel popt-devel soxr-devel \ + git autoconf automake avahi-devel libconfig-devel openssl-devel popt-devel soxr-devel \ alsa-lib-devel ``` ### Arch Linux From f775267d0274b54d021698035384d817365ad7a1 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:45:47 +0100 Subject: [PATCH 16/42] Remove the useless main_thread_id and fix the MPRIS quit handler. --- common.c | 1 - common.h | 5 +---- mpris-service.c | 3 ++- shairport.c | 8 +++----- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/common.c b/common.c index dbbbef60..63ea1cd4 100644 --- a/common.c +++ b/common.c @@ -112,7 +112,6 @@ void set_alsa_out_dev(char *); config_t config_file_stuff; int type_of_exit_cleanup; -pthread_t main_thread_id; uint64_t ns_time_at_startup, ns_time_at_last_debug_message; // always lock use this when accessing the ns_time_at_last_debug_message diff --git a/common.h b/common.h index 0d5ae2e7..a3f21126 100644 --- a/common.h +++ b/common.h @@ -37,7 +37,7 @@ typedef enum { typedef enum { TOE_normal, TOE_emergency, - TOE_dbus // a dbus request was made -- don't wait for the dbus thread to exit + TOE_dbus // a request was made on a D-Bus interface (the native D-Bus or MPRIS interfaces)-- don't wait for the dbus thread to exit } type_of_exit_type; #define sps_extra_code_output_stalled 32768 @@ -401,9 +401,6 @@ extern uint64_t ns_time_at_startup, ns_time_at_last_debug_message; uint32_t uatoi(const char *nptr); -// this is for allowing us to cancel the whole program -extern pthread_t main_thread_id; - extern shairport_cfg config; extern config_t config_file_stuff; extern int type_of_exit_cleanup; // normal, emergency, dbus requested... diff --git a/mpris-service.c b/mpris-service.c index 9851f0b7..8416df95 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -229,7 +229,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused) static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { debug(1, "quit requested (MPRIS interface)."); - pthread_cancel(main_thread_id); + type_of_exit_cleanup = TOE_dbus; // request an exit cleanup that is compatible with dbus + exit(EXIT_SUCCESS); media_player2_complete_quit(skeleton, invocation); return TRUE; } diff --git a/shairport.c b/shairport.c index 9f3ce855..f4c7f25d 100644 --- a/shairport.c +++ b/shairport.c @@ -599,6 +599,9 @@ int parse_options(int argc, char **argv) { 1); // allow autoconversion from int/float to int/float // make config.cfg point to it config.cfg = &config_file_stuff; + + config_write(config.cfg, stderr); + /* Get the Service Name. */ if (config_lookup_string(config.cfg, "general.name", &str)) { raw_service_name = (char *)str; @@ -1734,7 +1737,6 @@ int main(int argc, char **argv) { pid = getpid(); config.log_fd = -1; conns = NULL; // no connections active - memset((void *)&main_thread_id, 0, sizeof(main_thread_id)); ns_time_at_startup = get_absolute_time_in_ns(); ns_time_at_last_debug_message = ns_time_at_startup; // this is a bit weird, but necessary -- basename() may modify the argument passed in @@ -2053,10 +2055,6 @@ int main(int argc, char **argv) { exit(1); } - main_thread_id = pthread_self(); - if (!main_thread_id) - debug(1, "Main thread is set up to be NULL!"); - // make sure the program can create files that group and world can read umask(S_IWGRP | S_IWOTH); From 2f6bdb7975c098ce14c5babdd8bacc8c8beca0b5 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:46:17 +0100 Subject: [PATCH 17/42] man text updates --- man/shairport-sync.7 | 6 +++--- man/shairport-sync.7.xml | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 0f80efef..34dbbb2a 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -152,14 +152,14 @@ shairport-sync \fB-a "Joe's Stereo"\f1 \fB--\f1 \fB-d hw:1,0\f1 \fB-m hw:1\f1 \f The program will be visible as "Joe's Stereo" ( \fB-a "Joe's Stereo"\f1 ). The audio backend options following the \fB--\f1 separator specify that the audio will be output on output 0 of soundcard 1 ( \fB-d hw:1,0\f1 ) and will take advantage of the same sound card's mixer ( \fB-m hw:1\f1 ) using the level control named "PCM" ( \fB-c "PCM"\f1 ). -The example above is slightly contrived in order to show the use of the \fB-m\f1 option. Typically, output 0 is the default output of a card, so the output device could be written \fB-d hw:1\f1 and then the mixer option would be unnecessary, giving the following, simpler, command: +The example above is slightly contrived: Firstly, output 0 is the default output of a card, so the output device could be written \fB-d hw:1\f1. Secondly, when a mixer name is given ( \fB-c "PCM"\f1 ), the default is that the mixer is on the output device, so the \fB-m hw:1\f1 is unnecessary here. These simplifications give the following command: shairport-sync \fB-a "Joe's Stereo"\f1 \fB--\f1 \fB-d hw:1\f1 \fB-c PCM\f1 .SH CREDITS -Mike Brady developed shairport-sync from the original Shairport by James Laird. +Mike Brady (\fBhttps://github.com/mikebrady\f1) developed shairport-sync from Shairport by James Wah (\fBhttps://github.com/abrasive\f1). shairport-sync can be found at \fBhttps://github.com/mikebrady/shairport-sync.\f1 -Shairport can be found at \fBhttps://github.com/abrasive/shairport.\f1 +Shairport can be found at \fBhttps://github.com/abrasive/shairport\f1 .SH COMMENTS This man page was written using \fBxml2man(1)\f1 by Oliver Kurth. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index f85e9fdf..5a4e1b52 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -444,9 +444,11 @@ ( -d hw:1,0 ) and will take advantage of the same sound card's mixer ( -m hw:1 ) using the level control named "PCM" ( -c "PCM" ).

-

The example above is slightly contrived in order to show the use of the -m - option. Typically, output 0 is the default output of a card, so the output device could - be written -d hw:1 and then the mixer option would be unnecessary, giving the following, simpler, command:

+

The example above is slightly contrived: Firstly, output 0 is the default output of a card, + so the output device could be written -d hw:1. + Secondly, when a mixer name is given ( -c "PCM" ), + the default is that the mixer is on the output device, so the -m hw:1 is unnecessary here. + These simplifications give the following command:

shairport-sync -a "Joe's Stereo" -- @@ -457,11 +459,11 @@
-

Mike Brady developed shairport-sync from the original Shairport by James Laird.

+

Mike Brady () developed shairport-sync from Shairport by James Wah ().

shairport-sync can be found at

Shairport can be found at -

+

From abf6257fd85ad98f3b4fe00f9c942032cde72ab3 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:47:39 +0100 Subject: [PATCH 18/42] remove errant config_write call. --- shairport.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shairport.c b/shairport.c index f4c7f25d..2da76203 100644 --- a/shairport.c +++ b/shairport.c @@ -599,9 +599,7 @@ int parse_options(int argc, char **argv) { 1); // allow autoconversion from int/float to int/float // make config.cfg point to it config.cfg = &config_file_stuff; - - config_write(config.cfg, stderr); - + /* Get the Service Name. */ if (config_lookup_string(config.cfg, "general.name", &str)) { raw_service_name = (char *)str; From 3cbf7739955e63b7e42cc7d0bcd5bde7fba71e1e Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 13:04:27 +0100 Subject: [PATCH 19/42] Add a new command line option "--displayConfig" to give version string, command line and active configuration file name and settings. Clean up and reorganise the help messages and fix a few mistakes. --- audio_pipe.c | 2 +- audio_soundio.c | 4 +- shairport.c | 204 ++++++++++++++++++++++++++---------------------- 3 files changed, 112 insertions(+), 98 deletions(-) diff --git a/audio_pipe.c b/audio_pipe.c index 83b9666f..001f1385 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -141,7 +141,7 @@ static void deinit(void) { close(fd); } -static void help(void) { printf(" specify the pathname of the pipe to write to.\n"); } +static void help(void) { printf(" Provide the pipe's pathname. The default is \"%s\".\n", default_pipe_name); } audio_output audio_pipe = {.name = "pipe", .help = &help, diff --git a/audio_soundio.c b/audio_soundio.c index 83347b5b..a34851c6 100644 --- a/audio_soundio.c +++ b/audio_soundio.c @@ -208,10 +208,8 @@ static void flush(void) { debug(1, "libsoundio output flushed\n"); } -static void help(void) { printf(" There are no options for libsoundio.\n"); } - audio_output audio_soundio = {.name = "soundio", - .help = &help, + .help = NULL, .init = &init, .deinit = &deinit, .prepare = NULL, diff --git a/shairport.c b/shairport.c index 2da76203..2d9c71cd 100644 --- a/shairport.c +++ b/shairport.c @@ -130,11 +130,11 @@ int killOption = 0; int daemonisewith = 0; int daemonisewithout = 0; int log_to_syslog_selected = 0; +int display_config_selected = 0; int log_to_syslog_select_is_first_command_line_argument = 0; -// static int shutting_down = 0; char configuration_file_path[4096 + 1]; -char actual_configuration_file_path[4096 + 1]; +char *config_file_real_path = NULL; char first_backend_name[256]; @@ -270,91 +270,62 @@ void usage(char *progname) { } else { #endif - + // clang-format off + printf("Please use the configuration file for settings where possible.\n"); + printf("Many more settings are available in the configuration file.\n"); + printf("\n"); printf("Usage: %s [options...]\n", progname); printf(" or: %s [options...] -- [audio output-specific options]\n", progname); printf("\n"); printf("Options:\n"); - printf(" -h, --help show this help.\n"); -#ifdef CONFIG_LIBDAEMON - printf(" -d, --daemon daemonise.\n"); - printf(" -j, --justDaemoniseNoPIDFile daemonise without a PID file.\n"); - printf(" -k, --kill kill the existing shairport daemon.\n"); -#endif - printf(" -V, --version show version information.\n"); - printf(" -c, --configfile=FILE read configuration settings from FILE. Default is " - "/etc/shairport-sync.conf.\n"); - - printf("\n"); - printf( - "The following general options are for backward compatibility. These and all new options " - "have settings in the configuration file, by default /etc/shairport-sync.conf:\n"); - printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n"); - printf(" -p, --port=PORT set RTSP listening port.\n"); - printf(" -a, --name=NAME set advertised name.\n"); - printf( - " -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown " - "device.\n"); + printf(" -h, --help Show this help.\n"); + printf(" -V, --version Show version information -- the version string.\n"); + printf(" --displayConfig Output version string, command line, configuration file and active settings to stderr.\n"); + printf(" --statistics Print some interesting statistics. More will be printed if -v / -vv / -vvv are also chosen.\n"); + printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n"); + printf(" -c, --configfile=FILE Read configuration settings from FILE. Default is %s.\n", configuration_file_path); + printf(" -a, --name=NAME Set service name. Default is the hostname with first letter capitalised.\n"); + printf(" --password=PASSWORD Require PASSWORD to connect. Default is no password. (Classic AirPlay only.)\n"); + printf(" -p, --port=PORT Set RTSP listening port. Default 5000; 7000 for AirPlay 2./\n"); + printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown device.\n"); printf(" The default is to set it automatically.\n"); - printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, " - "where \n"); - printf(" \"basic\" inserts or deletes audio frames from " - "packet frames with low processor overhead, and \n"); - printf( - " \"soxr\" uses libsoxr to minimally resample packet frames -- " - "moderate processor overhead.\n"); - printf(" \"auto\" (default) chooses basic or soxr depending on " - "processor capability.\n"); - printf( - " \"soxr\" option only available if built with soxr support.\n"); - printf(" -B, --on-start=PROGRAM run PROGRAM when playback is about to begin.\n"); - printf(" -E, --on-stop=PROGRAM run PROGRAM when playback has ended.\n"); - printf( - " For -B and -E options, specify the full path to the program, " - "e.g. /usr/bin/logger.\n"); - printf(" Executable scripts work, but must have the appropriate " - "shebang " - "(#!/bin/sh) in the headline.\n"); - printf( - " -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n"); - printf(" -o, --output=BACKEND select audio output method.\n"); - printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n"); - printf(" if no mdns provider is specified,\n"); - printf(" shairport tries them all until one works.\n"); - printf( - " -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. " - "Set to 0 to " - "stop resyncing.\n"); - printf( - " -t, --timeout=SECONDS go back to idle mode from play mode after a break in " - "communications of this many seconds (default 120). Set to 0 never to exit play mode.\n"); - printf(" --statistics print some interesting statistics -- output to the logfile " - "if running as a daemon.\n"); - printf(" --tolerance=TOLERANCE [Deprecated] allow a synchronization error of TOLERANCE " - "frames (default " - "88) before trying to correct it.\n"); - printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a " - "password.\n"); - printf(" --logOutputLevel log the output level setting -- useful for setting maximum " - "volume.\n"); -#ifdef CONFIG_METADATA - printf(" -M, --metadata-enable ask for metadata from the source and process it.\n"); - printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. " - "--metadata-pipename=/tmp/%s-metadata.\n", - config.appName); - printf(" The default is /tmp/%s-metadata.\n", config.appName); - printf( - " -g, --get-coverart Include cover art in the metadata to be gathered and sent.\n"); + printf(" -S, --stuffing=MODE Set how to adjust current latency to match desired latency, where:\n"); + printf(" \"basic\" inserts or deletes audio frames from packet frames with low processor overhead, and\n"); + printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- moderate processor overhead.\n"); + printf(" The default \"auto\" setting chooses basic or soxr depending on processor capability.\n"); + printf(" The \"soxr\" option is only available if built with soxr support.\n"); + printf(" -B, --on-start=PROGRAM Run PROGRAM when playback is about to begin.\n"); + printf(" -E, --on-stop=PROGRAM Run PROGRAM when playback has ended.\n"); + printf(" For -B and -E options, specify the full path to the program and arguments, e.g. \"/usr/bin/logger\".\n"); + printf(" Executable scripts work, but the file must be marked executable have the appropriate shebang (#!/bin/sh) on the first line.\n"); + printf(" -w, --wait-cmd Wait until the -B or -E programs finish before continuing.\n"); + printf(" -o, --output=BACKEND Select audio backend. They are listed at the end of this text. The first one is the default.\n"); + printf(" -m, --mdns=BACKEND Use the mDNS backend named BACKEND to advertise the AirPlay service through Bonjour/ZeroConf.\n"); + printf(" They are listed at the end of this text.\n"); + printf(" If no mdns backend is specified, they are tried in order until one works.\n"); + printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. Set to 0 to stop resyncing.\n"); + printf(" -t, --timeout=SECONDS Go back to idle mode from play mode after a break in communications of this many seconds (default 120). Set to 0 never to exit play mode.\n"); + printf(" --tolerance=TOLERANCE [Deprecated] Allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n"); + printf(" --logOutputLevel Log the output level setting -- a debugging option, useful for determining the optimum maximum volume.\n"); +#ifdef CONFIG_LIBDAEMON + printf(" -d, --daemon Daemonise.\n"); + printf(" -j, --justDaemoniseNoPIDFile Daemonise without a PID file.\n"); + printf(" -k, --kill Kill the existing shairport daemon.\n"); #endif - printf(" --log-to-syslog send debug and statistics information through syslog\n"); - printf( - " If used, this should be the first command line argument.\n"); - printf(" -u, --use-stderr [Deprecated] This setting is not needed -- stderr is now " - "used by default.\n"); +#ifdef CONFIG_METADATA + printf(" -M, --metadata-enable Ask for metadata from the source and process it. Much more flexibility with configuration file settings.\n"); + printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. --metadata-pipename=/tmp/%s-metadata.\n", config.appName); + printf(" The default is /tmp/%s-metadata.\n", config.appName); + printf(" -g, --get-coverart Include cover art in the metadata to be gathered and sent.\n"); +#endif + printf(" --log-to-syslog Send debug and statistics information through syslog\n"); + printf(" If used, this should be the first command line argument.\n"); + printf(" -u, --use-stderr [Deprecated] This setting is not needed -- stderr is now used by default and syslog is selected using --log-to-syslog.\n"); printf("\n"); mdns_ls_backends(); printf("\n"); audio_ls_outputs(); + // clang-format on #ifdef CONFIG_AIRPLAY_2 } @@ -380,6 +351,7 @@ int parse_options(int argc, char **argv) { {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL}, {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL}, {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL, NULL}, + {"displayConfig", 0, POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL}, {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL, NULL}, {"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL, NULL}, {"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL, NULL}, @@ -587,14 +559,13 @@ int parse_options(int argc, char **argv) { config_init(&config_file_stuff); - char *config_file_real_path = realpath(config.configfile, NULL); + config_file_real_path = realpath(config.configfile, NULL); if (config_file_real_path == NULL) { debug(2, "can't resolve the configuration file \"%s\".", config.configfile); } else { debug(2, "looking for configuration file at full path \"%s\"", config_file_real_path); /* Read the file. If there is an error, report it and exit. */ if (config_read_file(&config_file_stuff, config_file_real_path)) { - free(config_file_real_path); config_set_auto_convert(&config_file_stuff, 1); // allow autoconversion from int/float to int/float // make config.cfg point to it @@ -1655,6 +1626,8 @@ void exit_function() { #endif if (config.cfg) config_destroy(config.cfg); + if (config_file_real_path) + free(config_file_real_path); if (config.appName) free(config.appName); @@ -1701,6 +1674,40 @@ void termHandler(__attribute__((unused)) int k) { exit(EXIT_SUCCESS); } +void display_config(int argc, char **argv) { + if (log_to_syslog_selected != 0) { + warn("The \"--display-config\" option has a limitation: it can only output to STDERR. To route its output to the system log, please redirect STDERR to the system log."); + fprintf(stderr, "NOTE: the \"--display-config\" option has a limitation: it can only output to STDERR. To route its output to the system log, please redirect STDERR to the system log.\n\n"); + } + fprintf(stderr, ">> Display Config Start.\n"); + char *version_string = get_version_string(); + if (version_string) { + fprintf(stderr, "\nVersion String:\n%s\n", version_string); + free(version_string); + } else { + fprintf(stderr, "Can't print version string!\n"); + } + if (argc != 0) { + fprintf(stderr, "\nCommand Line:\n"); + int i; + for (i = 0; i < argc; i++) { + fprintf(stderr, argv[i]); + if (i == argc-1) + fprintf(stderr, "\n"); + else + fprintf(stderr, " "); + } + } + + if (config.cfg == NULL) + fprintf(stderr, "\nNo configuration file.\n"); + else { + fprintf(stderr, "\nConfiguration File:\n%s\n\nConfiguration File Settings:\n",config_file_real_path); + config_write(config.cfg,stderr); + } + fprintf(stderr, "\n>> Display Config End.\n"); +} + int main(int argc, char **argv) { memset(&config, 0, sizeof(config)); // also clears all strings, BTW /* Check if we are called with -V or --version parameter */ @@ -1708,6 +1715,22 @@ int main(int argc, char **argv) { print_version(); exit(EXIT_SUCCESS); } + + // this is a bit weird, but necessary -- basename() may modify the argument passed in + char *basec = strdup(argv[0]); + char *bname = basename(basec); + config.appName = strdup(bname); + if (config.appName == NULL) + die("can not allocate memory for the app name!"); + free(basec); + + strcpy(configuration_file_path, SYSCONFDIR); + // strcat(configuration_file_path, "/shairport-sync"); // thinking about adding a special + // shairport-sync directory + strcat(configuration_file_path, "/"); + strcat(configuration_file_path, config.appName); + strcat(configuration_file_path, ".conf"); + config.configfile = configuration_file_path; #ifdef CONFIG_AIRPLAY_2 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 10, 0) @@ -1737,13 +1760,6 @@ int main(int argc, char **argv) { conns = NULL; // no connections active ns_time_at_startup = get_absolute_time_in_ns(); ns_time_at_last_debug_message = ns_time_at_startup; - // this is a bit weird, but necessary -- basename() may modify the argument passed in - char *basec = strdup(argv[0]); - char *bname = basename(basec); - config.appName = strdup(bname); - if (config.appName == NULL) - die("can not allocate memory for the app name!"); - free(basec); #ifdef CONFIG_LIBDAEMON daemon_set_verbosity(LOG_DEBUG); @@ -1791,14 +1807,6 @@ int main(int argc, char **argv) { config.output_name = first_backend_name; } - strcpy(configuration_file_path, SYSCONFDIR); - // strcat(configuration_file_path, "/shairport-sync"); // thinking about adding a special - // shairport-sync directory - strcat(configuration_file_path, "/"); - strcat(configuration_file_path, config.appName); - strcat(configuration_file_path, ".conf"); - config.configfile = configuration_file_path; - // config.statistics_requested = 0; // don't print stats in the log // config.userSuppliedLatency = 0; // zero means none supplied @@ -1879,6 +1887,14 @@ int main(int argc, char **argv) { config.service_name); config.service_name[50] = '\0'; // truncate it and carry on... } + + if (display_config_selected != 0) { + display_config(argc, argv); + if (argc == 2) { + fprintf(stderr, ">> Goodbye!\n"); + exit(EXIT_SUCCESS); + } + } /* Check if we are called with -k or --kill option */ if (killOption != 0) { From 86539bc5cedf6b01c480fb95329d1d44ece08edb Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 15:48:55 +0100 Subject: [PATCH 20/42] Update man page. --- man/shairport-sync.7 | 72 +++++++++------- man/shairport-sync.7.xml | 174 ++++++++++++++++++++++----------------- 2 files changed, 139 insertions(+), 107 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 34dbbb2a..1dc2dd42 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -2,26 +2,30 @@ .SH NAME shairport-sync \- AirPlay and AirPlay 2 Audio Player .SH SYNOPSIS -\fBshairport-sync [-djvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--get-cover-art]\fB [--logOutputLevel]\fB [--log-to-syslog]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB +\fBshairport-sync [-djvw]\fB [-a \fB\fIservice-name\fB | --name=\fB\fIservice-name\fB]\fB [-B \fB\fIcommand\fB | --onstart=\fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB | --configfile=\fB\fIconfigurationfile\fB]\fB [-d | --daemon]\fB [-E \fB\fIcommand\fB | --onstop=\fB\fIcommand\fB]\fB [-g | --get-cover-art]\fB [-j | --justDaemoniseNoPIDFile]\fB [--logOutputLevel]\fB [--log-to-syslog]\fB [-L \fB\fIlatency\fB | --latency=\fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB | --mdns=\fB\fIbackend\fB]\fB [-M | --metadata-enable]\fB [-o \fB\fIbackend\fB | --output=\fB\fIbackend\fB]\fB [-p \fB\fIport\fB | --port=\fB\fIport\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB | --resync=\fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB | --stuffing=\fB\fImode\fB]\fB [-t \fB\fItimeout\fB | --timeout=\fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-v | --verbose]\fB [-w | --wait-cmd]\fB [-- \fB\fIaudio_backend_options\fB]\fB -shairport-sync -k\fB +shairport-sync --displayConfig\fB shairport-sync -h\fB +shairport-sync -k\fB + shairport-sync -V\fB \f1 .SH DESCRIPTION -Shairport Sync plays audio streamed from an AirPlay or an AirPlay 2 device. AirPlay 2 support is limited, and AirPlay 2 from iTunes for Windows is not supported. Please see \fBhttps://github.com/mikebrady/shairport-sync\f1 for details. +Shairport Sync plays AirPlay audio. It can be built to stream either from "classic" AirPlay (aka "AirPlay 1") or from AirPlay 2 devices. -Settings can be made using the configuration file (recommended for all new installations) or by using command-line options. +AirPlay 2 support is limited, and AirPlay 2 from iTunes for Windows is not supported. For AirPlay 2 operation, a companion program called \fBnqptp\f1 must be installed. -The name of the Shairport Sync executable is \fBshairport-sync\f1. Both names are used in these man pages. +Please see \fBhttps://github.com/mikebrady/shairport-sync\f1 for details. + +The name of the Shairport Sync executable is \fBshairport-sync\f1. .SH CONFIGURATION FILE SETTINGS -You should use the configuration file for setting up Shairport Sync. This file is usually \fIshairport-sync.conf\f1 and is generally located in the System Configuration Directory, which is normally the \fI/etc\f1 directory in Linux or the \fI/usr/local/etc\f1 directory in BSD unixes. You may need to have root privileges to modify it. +You should use the configuration file for setting up Shairport Sync because -- apart from a few special-purpose commands -- it has a much richer set of options than are available on the command line. This file is usually \fIshairport-sync.conf\f1 and is generally located in the System Configuration Directory, which is normally the \fI/etc\f1 directory in Linux or the \fI/usr/local/etc\f1 directory in BSD unixes. You may need to have root privileges to modify it. -(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command \fI$ shairport-sync -V\f1. One of the items in the output string is the value of the \fBsysconfdir\f1, i.e. the System Configuration Directory.) +(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command \fI$ shairport-sync -V\f1. The last item in the output string is the value of the \fBsysconfdir\f1, i.e. the System Configuration Directory.) -Within the configuration file, settings are organised into groups, for example, there is a "general" group of standard settings, and there is an \fBalsa\f1 group with settings that pertain to the \fBALSA\f1 back end. Here is an example of a typical configuration file: +Within the configuration file, settings are organised into groups, for example, there is a \fBgeneral\f1 group of standard settings, and there is an \fBalsa\f1 group with settings that pertain to the \fBALSA\f1 back end. Here is an example of a typical configuration file: \fBgeneral = {\f1 @@ -39,17 +43,15 @@ Within the configuration file, settings are organised into groups, for example, \fB};\f1 -Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name and (2) the output device. If the \fBname\f1 setting is omitted, the service name is derived from the system's hostname. By default, the \fBALSA\f1 backend will be chosen if included in the build. If the (alsa) output device has a mixer that can be used for volume control, then (3) the mixer name should be specified. It is important to do this if the mixer exists. Otherwise, the maximum output from the output device will be whatever setting the mixer happens to have, which will be a matter of chance and which could be very low or even silent. +Users generally only need to set (1) the service name and (2) the output device. If the \fBname\f1 setting is omitted, the service name is derived from the system's hostname. By default, the \fBALSA\f1 backend will be chosen if included in the build. If the (alsa) output device has a mixer that can be used for volume control, then (3) the mixer name should be specified. It is important to do this if the mixer exists. Otherwise, the maximum output from the output device will be whatever setting the mixer happens to have, which will be a matter of chance and which could be very low or even silent. A sample configuration file with all possible settings, but with all of them commented out, is installed at \fIshairport-sync.conf.sample\f1, within the System Configuration Directory -- \fI/etc\f1 in Linux, \fI/usr/local/etc\f1 in BSD unixes. The sample configuration file includes extensive documentation of the settings. and is also available at \fBhttps://github.com/mikebrady/shairport-sync/blob/master/scripts/shairport-sync.conf\f1. Please refer to it for the most up-to-date information on configuration file settings. - -New features, etc. will generally be available only via configuration file settings. .SH OPTIONS -Many command-line options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages. - There are two kinds of command-line options for shairport-sync: regular \fBprogram options\f1 and \fBaudio backend options\f1. Program options are always listed first, followed by any audio backend options, preceded by a \fB--\f1 symbol. + +See the EXAMPLES section for sample usages. .SH PROGRAM OPTIONS Program Options are used by shairport-sync itself. .TP @@ -71,20 +73,25 @@ Read configuration settings from \fIfilename\f1. The default is to read them fro \fB-d | --daemon\f1 Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time. See also the \fB-j\f1 option. Only available if shairport-sync has been compiled with libdaemon support. .TP +\fB--displayConfig\f1 +This will display information relating to the configuration of Shairport Sync. It can be very useful for debugging. The information displayed is the version string (which indicates the build options used when \fBshairport-sync\f1 was built), the contents of the command line that invoked Shairport Sync, the name of the configuration file and the active settings therein. + +If this is the only option on the command line, \fBshairport-sync\f1 will terminate after displaying the information. + +Due to a limitation, \fB--displayConfig\f1 always outputs to \fISTDERR\f1, irrespective of the setting of \fB--log-to-syslog\f1. +.TP \fB-E \f1\fIprogram\f1\fB | --on-stop=\f1\fIprogram\f1 Execute \fIprogram\f1 when playback has ended. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have the appropriate shebang (\fI#!/bin/sh\f1) in the headline. If you want shairport-sync to wait until the command has completed before continuing, select the \fB-w\f1 option as well. .TP -\fB--get-coverart\f1 -This option requires the \fB--meta-dir\f1 option to be set, and enables shairport-sync to request cover art from the source and to transmit it through the metadata pipe. - -Please note that cover art data may be very large, and may place too great a burden on your network. +\fB-g | --get-coverart\f1 +This option requires the \fB-M | --metadata-enable\f1 option to be set, and enables shairport-sync to request cover art from the source and to process it as metadata. .TP \fB-h | --help\f1 Print brief help message and exit. .TP -\fB-j\f1 +\fB-j | justDaemoniseNoPIDFile\f1 Instruct shairport-sync to demonise itself. Unlike the \fB-d\f1 option, it will not write a Process ID (PID) to a file -- it will just (hence the "j") demonise itself. Only available if shairport-sync has been compiled with libdaemon support. .TP \fB-k | --kill\f1 @@ -95,17 +102,22 @@ Use this to log the volume level when the volume is changed. It may be useful if .TP \fB--log-to-syslog\f1 Warnings, error messages and messages are sent, by default, to \fISTDERR\f1. Use this option to route these messages to the \fBsyslog\f1 instead. This is intended for use when Shairport Sync is operating as a daemon. + +See also \fB--displayConfig\f1. .TP \fB-L | --latency=\f1\fIlatency\f1 Use this to set the \fIdefault latency\f1, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the \fIdefault latency\f1 is 88,200 frames, where there are 44,100 frames to the second. Please note that this feature is deprecated and will be removed in a future version of shairport-sync. .TP -\fB--meta-dir=\f1\fIdirectory\f1 -Listen for metadata coming from the source and send it, along with metadata from shairport-sync itself, to a pipe called \fIshairport-sync-metadata\f1 in the \fIdirectory\f1 you specify. If you add the \fB--get-cover-art\f1 then cover art will be sent through the pipe too. See \fBhttps://github.com/mikebrady/shairport-sync-metadata-reader\f1 for a sample metadata reader. +\fB-M | --metadata-enable\f1 +Ask the client to send metadata. It will be sent, along with metadata generated by shairport-sync itself, to a pipe and will also be sent as UDP packets. If you add the \fB-g | --get-cover-art\f1 then cover art included, where available. See \fBhttps://github.com/mikebrady/shairport-sync-metadata-reader\f1 for a sample metadata reader. +.TP +\fB--metadata-pipename=\f1\fIpathname\f1 +Specify the path name for the metadata pipe. Note that \fBshairport-sync\f1 will need write permission on that directory and pipe. The default is \fI/tmp/shairport-sync-metadata\f1. If you rename the \fBshairport-sync\f1 executable, the default pipe name will change accordingly. .TP \fB-m \f1\fImdnsbackend\f1\fB | --mdns=\f1\fImdnsbackend\f1 -Force the use of the specified mDNS backend to advertise the player on the network. The default is to try all mDNS backends until one works. +Force the use of the specified mDNS backend to advertise the player on the network. The default is to try all mDNS backends in order until one works. .TP \fB-o \f1\fIoutputbackend\f1\fB | --output=\f1\fIoutputbackend\f1 Force the use of the specified output backend to play the audio. The default is to try the first one. @@ -120,10 +132,10 @@ Require the password \fIsecret\f1 to be able to connect and stream to the servic Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation. This setting is deprecated and will be removed in a future version of shairport-sync. .TP \fB--statistics\f1 -Print some performance information \fISTDERR\f1, or to \fBsyslog\f1 if the \fB-log-to-syslog\f1 command line option is also chosen. +Print some performance information to \fISTDERR\f1, or to \fBsyslog\f1 if the \fB-log-to-syslog\f1 command line option is also chosen. .TP \fB-S \f1\fImode\f1\fB | --stuffing=\f1\fImode\f1 -Interpolate ("stuff") the audio stream using the \fImode\f1. "Stuffing" refers to the process of adding or removing frames of audio to or from the stream sent to the output device in order to keep it synchronised with the player. The \fBbasic\f1 mode is normally almost completely inaudible. The alternative mode, \fBsoxr\f1, is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled. The default setting, \fBauto\f1, allows Shairport Sync to choose \fBsoxr\f1 mode if the system is powerful enough. +Interpolate ("stuff") the audio stream using the \fImode\f1. "Stuffing" refers to the process of adding or removing frames of audio to or from the stream sent to the output device in order to keep it synchronised with the player. The \fBbasic\f1 mode is normally almost completely inaudible. The alternative mode, \fBsoxr\f1, is even less obtrusive but requires much more processing power. For this mode, support for \fBlibsoxr\f1, the SoX Resampler Library, must be selected when \fBshairport-sync\f1 is built. The default setting, \fBauto\f1, allows Shairport Sync to choose \fBsoxr\f1 mode if the system is powerful enough. .TP \fB-t \f1\fItimeout\f1\fB | --timeout=\f1\fItimeout\f1 Exit play mode if the stream disappears for more than \fItimeout\f1 seconds. @@ -137,7 +149,7 @@ Allow playback to be up to \fIframes\f1 out of exact synchronization before atte Print version information and exit. .TP \fB-v | --verbose\f1 -Print debug information to the \fISTDERR\f1, or to \fBsyslog\f1 if the \fB-log-to-syslog\f1 command line option is also chosen. Repeat up to three times (i.e. \fB-vv\f1 or \fB-vvv\f1) for more detail. +Print debug information to the \fISTDERR\f1, or to \fBsyslog\f1 if the \fB-log-to-syslog\f1 command line option is also chosen. Repeat up to three times (i.e. \fB-vv\f1 or \fB-vvv\f1) for more detail. You should use \fB-vvv\f1 very sparingly -- it is really noisy. .TP \fB-w | --wait-cmd\f1 Wait for commands specified using \fB-B\f1 or \fB-E\f1 to complete before continuing execution. @@ -148,18 +160,14 @@ Audio backends are listed with their corresponding Audio Backend Options in the .SH EXAMPLES Here is a slightly contrived example: -shairport-sync \fB-a "Joe's Stereo"\f1 \fB--\f1 \fB-d hw:1,0\f1 \fB-m hw:1\f1 \fB-c PCM\f1 +shairport-sync \fB-a "Joe's Stereo"\f1 \fB-o alsa\f1 \fB--\f1 \fB-d hw:1,0\f1 \fB-m hw:1\f1 \fB-c PCM\f1 -The program will be visible as "Joe's Stereo" ( \fB-a "Joe's Stereo"\f1 ). The audio backend options following the \fB--\f1 separator specify that the audio will be output on output 0 of soundcard 1 ( \fB-d hw:1,0\f1 ) and will take advantage of the same sound card's mixer ( \fB-m hw:1\f1 ) using the level control named "PCM" ( \fB-c "PCM"\f1 ). +The program will be visible as "Joe's Stereo" ( \fB-a "Joe's Stereo"\f1 ). The program option \fB-o alsa\f1 specifies that the \fBalsa\f1 backend be used, thus that audio should be output into the \fBALSA\f1 audio subsystem. The audio backend options following the \fB--\f1 separator are passed to the \fBalsa\f1 backend and specify that the audio will be output on subdevice 0 of soundcard 1 ( \fB-d hw:1,0\f1 ) and will take advantage of the same sound card's mixer ( \fB-m hw:1\f1 ) using the level control named "PCM" ( \fB-c "PCM"\f1 ). -The example above is slightly contrived: Firstly, output 0 is the default output of a card, so the output device could be written \fB-d hw:1\f1. Secondly, when a mixer name is given ( \fB-c "PCM"\f1 ), the default is that the mixer is on the output device, so the \fB-m hw:1\f1 is unnecessary here. These simplifications give the following command: +The example above is slightly contrived: Firstly, if the \fBalsa\f1 backend has been included in the build, it will be the default, so it doesn't need to be specified and the \fB-o alsa\f1 option could be omitted. Secondly, subdevice 0 is the default for a soundcard, so the output device could simply be written \fB-d hw:1\f1. Thirdly, when a mixer name is given ( \fB-c "PCM"\f1 ), the default is that the mixer is on the output device, so the \fB-m hw:1\f1 is unnecessary here. Using these defaults and simplifications gives the following command: shairport-sync \fB-a "Joe's Stereo"\f1 \fB--\f1 \fB-d hw:1\f1 \fB-c PCM\f1 .SH CREDITS -Mike Brady (\fBhttps://github.com/mikebrady\f1) developed shairport-sync from Shairport by James Wah (\fBhttps://github.com/abrasive\f1). - -shairport-sync can be found at \fBhttps://github.com/mikebrady/shairport-sync.\f1 - -Shairport can be found at \fBhttps://github.com/abrasive/shairport\f1 +Mike Brady (\fBhttps://github.com/mikebrady\f1) developed Shairport Sync from Shairport by James Wah (\fBhttps://github.com/abrasive\f1). .SH COMMENTS This man page was written using \fBxml2man(1)\f1 by Oliver Kurth. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 5a4e1b52..76e99111 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -35,49 +35,56 @@ - shairport-sync [-djvw] - [-a name] - [-A latency] - [-B command] - [-c configurationfile] - [-E command] - [--get-cover-art] + shairport-sync [-djvw] + [-a service-name | --name=service-name] + [-B command | --onstart=command] + [-c configurationfile | --configfile=configurationfile] + [-d | --daemon] + [-E command | --onstop=command] + [-g | --get-cover-art] + [-j | --justDaemoniseNoPIDFile] [--logOutputLevel] [--log-to-syslog] - [-L latency] - [-m backend] - [--meta-dir=directory] - [-o backend] + [-L latency | --latency=latency] + [-m backend | --mdns=backend] + [-M | --metadata-enable] + [-o backend | --output=backend] + [-p port | --port=port] [--password=secret] - [-r threshold] + [-r threshold | --resync=threshold] [--statistics] - [-S mode] - [-t timeout] + [-S mode | --stuffing=mode] + [-t timeout | --timeout=timeout] [--tolerance=frames] + [-v | --verbose] + [-w | --wait-cmd] [-- audio_backend_options] - shairport-sync -k + shairport-sync --displayConfig shairport-sync -h + shairport-sync -k shairport-sync -V -

Shairport Sync plays audio streamed from an AirPlay or an AirPlay 2 device. - AirPlay 2 support is limited, and AirPlay 2 from iTunes for Windows is not supported. +

Shairport Sync plays AirPlay audio. + It can be built to stream either from "classic" AirPlay (aka "AirPlay 1") + or from AirPlay 2 devices.

- Please see for details.

+

AirPlay 2 support is limited, and AirPlay 2 from iTunes for Windows is not supported. + For AirPlay 2 operation, a companion program called nqptp must be installed.

-

Settings can be made using the configuration file (recommended for all new - installations) or by using command-line options.

- -

The name of the Shairport Sync executable is shairport-sync. - Both names are used in these man pages.

+

Please see for details.

+

The name of the Shairport Sync executable is shairport-sync.

+
-

You should use the configuration file for setting up Shairport Sync. +

You should use the configuration file for setting up Shairport Sync because -- + apart from a few special-purpose commands -- it has a much richer set of options + than are available on the command line. This file is usually shairport-sync.conf and is generally located in the System Configuration Directory, which is normally the /etc directory in Linux or the /usr/local/etc directory in BSD unixes. @@ -85,12 +92,11 @@

(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command $ shairport-sync - -V. One of the items in the output string is the value of the - sysconfdir, - i.e. the System Configuration Directory.)

+ -V. The last item in the output string is the value of the + sysconfdir, i.e. the System Configuration Directory.)

Within the configuration file, settings are organised into groups, for - example, there is a "general" group of + example, there is a general group of standard settings, and there is an alsa group with settings that pertain to the ALSA back end. Here is an example of a typical configuration file:

@@ -104,8 +110,7 @@

mixer_control_name = "PCM";

};

-

Most settings have sensible default values, so -- as in the example above -- users - generally only need to set (1) the service name and +

Users generally only need to set (1) the service name and (2) the output device. If the name setting is omitted, the service name is derived from the system's hostname. @@ -128,21 +133,17 @@ . Please refer to it for the most up-to-date information on configuration file settings.

-

New features, etc. will generally be available only via configuration file settings.

- -
-

Many command-line options take sensible default values, so you can normally - ignore most of them. See the EXAMPLES section for typical usages.

-

There are two kinds of command-line options for shairport-sync: regular program options and audio backend options. Program options are always listed first, followed by any audio backend options, preceded by a -- symbol.

+ +

See the EXAMPLES section for sample usages.

Program Options are used by shairport-sync itself.

@@ -185,7 +186,7 @@ Read configuration settings from filename. The default is to read them from the shairport-sync.conf in the System Configuration Directory -- /etc in Linux, /usr/local/etc in BSD unixes. - For information about configuration settings, see the "Configuration File Settings" + For information about configuration settings, see the "Configuration File Settings" section above.

@@ -197,11 +198,26 @@ Process ID (PID) to a file, usually at /var/run/shairport-sync/shairport-sync.pid, which is used by the -k, -D and -R options to locate - the daemon at a later time. See also the -j option. Only available if + the daemon at a later time. See also the -j option. Only available if shairport-sync has been compiled with libdaemon support.

+ + @@ -282,22 +297,33 @@ + + @@ -342,7 +368,7 @@ @@ -355,10 +381,10 @@ stream sent to the output device in order to keep it synchronised with the player. The basic mode is normally almost completely inaudible. - The alternative mode, soxr, is even less obtrusive but + The alternative mode, soxr, is even less obtrusive but requires much more processing power. For this mode, support for - libsoxr, the SoX Resampler Library, must be selected when - shairport-sync is compiled. + libsoxr, the SoX Resampler Library, must be selected when + shairport-sync is built. The default setting, auto, allows Shairport Sync to choose soxr mode if the system is powerful enough. @@ -368,13 +394,13 @@ @@ -425,13 +451,14 @@ options and reused as audio backend options without ambiguity.

Audio backends are listed with their corresponding Audio Backend Options in the help text provided by the help (-h or --help) option.

-
+

Here is a slightly contrived example:

shairport-sync -a "Joe's Stereo" + -o alsa -- -d hw:1,0 -m hw:1 @@ -439,16 +466,17 @@

The program will be visible as "Joe's Stereo" ( -a "Joe's Stereo" ). - The audio backend options following the -- separator specify - that the audio will be output on output 0 of soundcard 1 + The program option -o alsa specifies that the alsa backend be used, thus that audio should be output into the ALSA audio subsystem. + The audio backend options following the -- separator are passed to the alsa backend and specify + that the audio will be output on subdevice 0 of soundcard 1 ( -d hw:1,0 ) and will take advantage of the same sound card's mixer ( -m hw:1 ) using the level control named "PCM" ( -c "PCM" ).

-

The example above is slightly contrived: Firstly, output 0 is the default output of a card, - so the output device could be written -d hw:1. - Secondly, when a mixer name is given ( -c "PCM" ), - the default is that the mixer is on the output device, so the -m hw:1 is unnecessary here. - These simplifications give the following command:

+

The example above is slightly contrived: + Firstly, if the alsa backend has been included in the build, it will be the default, so it doesn't need to be specified and the -o alsa option could be omitted. + Secondly, subdevice 0 is the default for a soundcard, so the output device could simply be written -d hw:1. + Thirdly, when a mixer name is given ( -c "PCM" ), the default is that the mixer is on the output device, so the -m hw:1 is unnecessary here. + Using these defaults and simplifications gives the following command:

shairport-sync -a "Joe's Stereo" -- @@ -459,11 +487,7 @@
-

Mike Brady () developed shairport-sync from Shairport by James Wah ().

-

shairport-sync can be found at -

-

Shairport can be found at -

+

Mike Brady () developed Shairport Sync from Shairport by James Wah ().

From 703b82c2b9c953dff05706863463c56241441556 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 16:01:32 +0100 Subject: [PATCH 21/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 35d8074e..d81dc259 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,13 @@ +Version 4.1-dev-717-g86539bc5 +==== +**Enhancements** +* A new command-line-only option is included: `--displayConfig`. This prints configuration information to `STDERR` and should be useful when debugging issues. +* Help text is reordered and updated. +* The `man` page is updated. +* The `man` contents are no longer automatically built when Shairport Sync is built. This is okay because the contents are normally static. The `man` folder has a new `Makefile`. +* The `xmltoman` application is not now needed when building Shairport Sync. +* When updating the `man` page, `xsltproc` is now used instead of `xmlmantohtml`. + Version 4.1-dev-701-g65daef30 ==== **Bug Fix** From bde261e38d7f214d1ed9e462b20b1c78e61ef6a5 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 16:06:43 +0100 Subject: [PATCH 22/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index d81dc259..93bb79ba 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,5 +1,9 @@ Version 4.1-dev-717-g86539bc5 ==== +**Pesky Things You Can't Ignore** + +If you are updating from a previous version, after you have pulled the update, please redo the `autoreconf -fi` and the `./configure...` steps (and the do a `make clean` for good measure) before `make`ing the executable -- there have been many changes to the build process. + **Enhancements** * A new command-line-only option is included: `--displayConfig`. This prints configuration information to `STDERR` and should be useful when debugging issues. * Help text is reordered and updated. From 11495fd76015037d3f7fb84de7b3df24f0b1d991 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 21:10:53 +0100 Subject: [PATCH 23/42] Correct a hacky and incorrect kludge so that the Makefile works on Linux and FreeBSD. --- Makefile.am | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index 0d3ec66f..bc1393d4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,15 +32,15 @@ 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 = -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)\" + AM_CXXFLAGS = -I$(srcdir) -fno-common -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" + AM_CFLAGS = -I$(srcdir) -fno-common -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\" endif endif endif # include information generated by 'git describe --tags --dirty' if requested if USE_GIT_VERSION -$(top_srcdir)/common.c: gitversion.h +common.c: gitversion.h gitversion.h: .git/index printf "// Do not edit!\n" > gitversion.h printf "// This file is automatically generated by 'git describe --tags --dirty', if available.\n" >> gitversion.h From 93c1f1ae3759c71550dbd6ced09640ecac1db15b Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 21:11:36 +0100 Subject: [PATCH 24/42] Remove the cause of a warning from clang on FreeBSD. --- shairport.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/shairport.c b/shairport.c index 2d9c71cd..03023a0c 100644 --- a/shairport.c +++ b/shairport.c @@ -1690,13 +1690,10 @@ void display_config(int argc, char **argv) { if (argc != 0) { fprintf(stderr, "\nCommand Line:\n"); int i; - for (i = 0; i < argc; i++) { - fprintf(stderr, argv[i]); - if (i == argc-1) - fprintf(stderr, "\n"); - else - fprintf(stderr, " "); + for (i = 0; i < argc - 1; i++) { + fprintf(stderr, "%s ", argv[i]); } + fprintf(stderr, "%s\n", argv[argc]); } if (config.cfg == NULL) From f214790016c4488d424144166c7ea1f9b2d10f6e Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 21:22:11 +0100 Subject: [PATCH 25/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 93bb79ba..66266778 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,12 @@ +Version 4.1-dev-721-g93c1f1ae +==== +**Pesky Things You Can't Ignore** + +If you are updating from a previous version, after you have pulled the update, please redo the `autoreconf -fi` and the `./configure...` steps (and the do a `make clean` for good measure) before `make`ing the executable -- there have been many changes to the build process. + +**Bug Fix** +* Correction to `Makefile.am` to make it work both on FreeBSD and Linux both in the source directory and in a subsidiary build directory. + Version 4.1-dev-717-g86539bc5 ==== **Pesky Things You Can't Ignore** From 16ca1c9fa59f9c179fd87834b0c016cd5642a5a9 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Tue, 11 Oct 2022 21:24:07 +0100 Subject: [PATCH 26/42] Update check_classic_mac_basic.yml --- .github/workflows/check_classic_mac_basic.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_classic_mac_basic.yml b/.github/workflows/check_classic_mac_basic.yml index 9489f3fb..abe75591 100644 --- a/.github/workflows/check_classic_mac_basic.yml +++ b/.github/workflows/check_classic_mac_basic.yml @@ -2,7 +2,7 @@ name: Classic on macOS with brew on: push: - branches: [ "danger" ] + branches: [ "development" ] jobs: build: From 6e9ebe6e926432da80fed4c7b9868951d1542418 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 12 Oct 2022 13:52:05 +0100 Subject: [PATCH 27/42] Improve the displayConfig output and route it through the standard logging system instead of STDERR. Include the man Makefile. --- man/Makefile | 11 ++++ man/shairport-sync.7 | 4 +- man/shairport-sync.7.xml | 7 +-- shairport.c | 118 ++++++++++++++++++++++++++++++++------- 4 files changed, 114 insertions(+), 26 deletions(-) create mode 100644 man/Makefile diff --git a/man/Makefile b/man/Makefile new file mode 100644 index 00000000..0c30fe4b --- /dev/null +++ b/man/Makefile @@ -0,0 +1,11 @@ +shairport-sync.7: shairport-sync.7.xml + xmltoman shairport-sync.7.xml > shairport-sync.7 + +shairport-sync.html: shairport-sync.7.xml + xsltproc xmltoman.xsl shairport-sync.7.xml > shairport-sync.html + +all: shairport-sync.7 shairport-sync.html + +clean: + rm shairport-sync.7 + rm shairport-sync.html diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 1dc2dd42..8bbb915b 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -74,11 +74,9 @@ Read configuration settings from \fIfilename\f1. The default is to read them fro Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time. See also the \fB-j\f1 option. Only available if shairport-sync has been compiled with libdaemon support. .TP \fB--displayConfig\f1 -This will display information relating to the configuration of Shairport Sync. It can be very useful for debugging. The information displayed is the version string (which indicates the build options used when \fBshairport-sync\f1 was built), the contents of the command line that invoked Shairport Sync, the name of the configuration file and the active settings therein. +This logs information relating to the configuration of Shairport Sync. It can be very useful for debugging. The information logged is some host OS information, the Shairport Sync version string (which indicates the build options used when \fBshairport-sync\f1 was built), the contents of the command line that invoked Shairport Sync, the name of the configuration file and the active settings therein. If this is the only option on the command line, \fBshairport-sync\f1 will terminate after displaying the information. - -Due to a limitation, \fB--displayConfig\f1 always outputs to \fISTDERR\f1, irrespective of the setting of \fB--log-to-syslog\f1. .TP \fB-E \f1\fIprogram\f1\fB | --on-stop=\f1\fIprogram\f1 Execute \fIprogram\f1 when playback has ended. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have the appropriate shebang (\fI#!/bin/sh\f1) in the headline. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index 76e99111..c9a197de 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -206,15 +206,14 @@ diff --git a/shairport.c b/shairport.c index 03023a0c..439e5b6a 100644 --- a/shairport.c +++ b/shairport.c @@ -280,7 +280,7 @@ void usage(char *progname) { printf("Options:\n"); printf(" -h, --help Show this help.\n"); printf(" -V, --version Show version information -- the version string.\n"); - printf(" --displayConfig Output version string, command line, configuration file and active settings to stderr.\n"); + printf(" --displayConfig Output OS information, version string, command line, configuration file and active settings to the log.\n"); printf(" --statistics Print some interesting statistics. More will be printed if -v / -vv / -vvv are also chosen.\n"); printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n"); printf(" -c, --configfile=FILE Read configuration settings from FILE. Default is %s.\n", configuration_file_path); @@ -570,7 +570,7 @@ int parse_options(int argc, char **argv) { 1); // allow autoconversion from int/float to int/float // make config.cfg point to it config.cfg = &config_file_stuff; - + /* Get the Service Name. */ if (config_lookup_string(config.cfg, "general.name", &str)) { raw_service_name = (char *)str; @@ -1674,37 +1674,117 @@ void termHandler(__attribute__((unused)) int k) { exit(EXIT_SUCCESS); } -void display_config(int argc, char **argv) { - if (log_to_syslog_selected != 0) { - warn("The \"--display-config\" option has a limitation: it can only output to STDERR. To route its output to the system log, please redirect STDERR to the system log."); - fprintf(stderr, "NOTE: the \"--display-config\" option has a limitation: it can only output to STDERR. To route its output to the system log, please redirect STDERR to the system log.\n\n"); +void _display_config(const char *filename, const int linenumber, int argc, char **argv) { + _inform(filename, linenumber, ">> Display Config Start."); + + // see the man entry on popen + FILE *fp; + int status; + char result[1024]; + + fp = popen("uname -a 2>/dev/null", "r"); + if (fp != NULL) { + if (fgets(result, 1024, fp) != NULL) { + _inform(filename, linenumber, ""); + _inform(filename, linenumber, "From \"uname -a\":"); + if (result[strlen(result) - 1] <= ' ') + result[strlen(result) - 1] = '\0'; // remove the last character if it's not printable + _inform(filename, linenumber, " %s", result); + } + status = pclose(fp); + if (status == -1) { + debug(1, "Error on pclose"); + } } - fprintf(stderr, ">> Display Config Start.\n"); + + fp = popen("(cat /etc/os-release | grep PRETTY_NAME | sed 's/PRETTY_NAME=//' | sed 's/\"//g') " + "2>/dev/null", + "r"); + if (fp != NULL) { + if (fgets(result, 1024, fp) != NULL) { + _inform(filename, linenumber, ""); + _inform(filename, linenumber, "From /etc/os-release:"); + if (result[strlen(result) - 1] <= ' ') + result[strlen(result) - 1] = '\0'; // remove the last character if it's not printable + _inform(filename, linenumber, " %s", result); + } + status = pclose(fp); + if (status == -1) { + debug(1, "Error on pclose"); + } + } + + fp = popen("cat /sys/firmware/devicetree/base/model 2>/dev/null", "r"); + if (fp != NULL) { + if (fgets(result, 1024, fp) != NULL) { + _inform(filename, linenumber, ""); + _inform(filename, linenumber, "From /sys/firmware/devicetree/base/model:"); + _inform(filename, linenumber, " %s", result); + } + status = pclose(fp); + if (status == -1) { + debug(1, "Error on pclose"); + } + } + char *version_string = get_version_string(); if (version_string) { - fprintf(stderr, "\nVersion String:\n%s\n", version_string); + _inform(filename, linenumber, ""); + _inform(filename, linenumber, "Shairport Sync Version String:"); + _inform(filename, linenumber, " %s", version_string); free(version_string); } else { - fprintf(stderr, "Can't print version string!\n"); + debug(1, "Can't print version string!\n"); } if (argc != 0) { - fprintf(stderr, "\nCommand Line:\n"); + char *obfp = result; int i; for (i = 0; i < argc - 1; i++) { - fprintf(stderr, "%s ", argv[i]); + snprintf(obfp, strlen(argv[i]) + 2, "%s ", argv[i]); + obfp += strlen(argv[i]) + 1; } - fprintf(stderr, "%s\n", argv[argc]); + snprintf(obfp, strlen(argv[i]) + 1, "%s", argv[i]); + obfp += strlen(argv[i]); + *obfp = 0; + + _inform(filename, linenumber, ""); + _inform(filename, linenumber, "Command Line:"); + _inform(filename, linenumber, " %s", result); } - + if (config.cfg == NULL) - fprintf(stderr, "\nNo configuration file.\n"); + _inform(filename, linenumber, "No configuration file."); else { - fprintf(stderr, "\nConfiguration File:\n%s\n\nConfiguration File Settings:\n",config_file_real_path); - config_write(config.cfg,stderr); + int configpipe[2]; + if (pipe(configpipe) == 0) { + FILE *cw; + cw = fdopen(configpipe[1], "w"); + _inform(filename, linenumber, ""); + _inform(filename, linenumber, "Configuration File:"); + _inform(filename, linenumber, " %s", config_file_real_path); + _inform(filename, linenumber, ""); + _inform(filename, linenumber, "Configuration File Settings:"); + config_write(config.cfg, cw); + fclose(cw); + FILE *cr; + cr = fdopen(configpipe[0], "r"); + while (fgets(result, 1024, cr) != NULL) { + // replace funny character at the end, if it's there + if (result[strlen(result) - 1] <= ' ') + result[strlen(result) - 1] = '\0'; // remove the last character if it's not printable + _inform(filename, linenumber, " %s", result); + } + fclose(cr); + } else { + debug(1, "Error making pipe.\n"); + } } - fprintf(stderr, "\n>> Display Config End.\n"); + _inform(filename, linenumber, ""); + _inform(filename, linenumber, ">> Display Config End."); } +#define display_config(argc, argv) _display_config(__FILE__, __LINE__, argc, argv) + int main(int argc, char **argv) { memset(&config, 0, sizeof(config)); // also clears all strings, BTW /* Check if we are called with -V or --version parameter */ @@ -1712,7 +1792,7 @@ int main(int argc, char **argv) { print_version(); exit(EXIT_SUCCESS); } - + // this is a bit weird, but necessary -- basename() may modify the argument passed in char *basec = strdup(argv[0]); char *bname = basename(basec); @@ -1884,7 +1964,7 @@ int main(int argc, char **argv) { config.service_name); config.service_name[50] = '\0'; // truncate it and carry on... } - + if (display_config_selected != 0) { display_config(argc, argv); if (argc == 2) { From 62fca43f498879a025465f583180b08c9184853f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 12 Oct 2022 14:21:43 +0100 Subject: [PATCH 28/42] Fix a bug when displayConfig was exiting when the soxr timer thread hadn't been started. Also quieten a cryptic dbus message. --- dbus-service.c | 7 ++++--- shairport.c | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/dbus-service.c b/dbus-service.c index c461e12a..c6cc2ac7 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -1129,10 +1129,11 @@ int start_dbus_service() { void stop_dbus_service() { debug(2, "stopping dbus service"); - if (ownerID) + if (ownerID) { g_bus_unown_name(ownerID); - else - debug(1, "Zero OwnerID for \"org.gnome.ShairportSync\"."); + } else if (service_is_running != 0) { + debug(1, "Zero OwnerID for running \"org.gnome.ShairportSync\" dbus service."); + } service_is_running = 0; } diff --git a/shairport.c b/shairport.c index 439e5b6a..d9fe93a5 100644 --- a/shairport.c +++ b/shairport.c @@ -171,6 +171,7 @@ int has_fltp_capable_aac_decoder(void) { #ifdef CONFIG_SOXR pthread_t soxr_time_check_thread; +int soxr_time_check_thread_started = 0; void *soxr_time_check(__attribute__((unused)) void *arg) { const int buffer_length = 352; int32_t inbuffer[buffer_length * 2]; @@ -1541,6 +1542,7 @@ void exit_function() { #ifdef CONFIG_DBUS_INTERFACE debug(2, "Stopping D-Bus service"); stop_dbus_service(); + debug(2, "Stopping D-Bus service done"); #endif if (g_main_loop) { debug(2, "Stopping D-Bus Loop Thread"); @@ -1551,35 +1553,48 @@ void exit_function() { // so don't wait for it if (type_of_exit_cleanup != TOE_dbus) pthread_join(dbus_thread, NULL); + debug(2, "Stopping D-Bus Loop Thread Done"); } #endif #ifdef CONFIG_DACP_CLIENT debug(2, "Stopping DACP Monitor"); dacp_monitor_stop(); + debug(2, "Stopping DACP Monitor Done"); #endif #ifdef CONFIG_METADATA_HUB debug(2, "Stopping metadata hub"); metadata_hub_stop(); + debug(2, "Stopping metadata done"); #endif #ifdef CONFIG_METADATA debug(2, "Stopping metadata"); metadata_stop(); // close down the metadata pipe + debug(2, "Stopping metadata done"); #endif debug(2, "Stopping the activity monitor."); activity_monitor_stop(0); + debug(2, "Stopping the activity monitor done."); + if ((config.output) && (config.output->deinit)) { debug(2, "Deinitialise the audio backend."); config.output->deinit(); + debug(2, "Deinitialise the audio backend done."); + } #ifdef CONFIG_SOXR // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down - debug(2, "Waiting for SoXr timecheck to terminate..."); - pthread_join(soxr_time_check_thread, NULL); + if (soxr_time_check_thread_started != 0) { + debug(1, "Waiting for SoXr timecheck to terminate..."); + pthread_join(soxr_time_check_thread, NULL); + soxr_time_check_thread_started = 0; + debug(1, "Waiting for SoXr timecheck to terminate done"); + } + #endif if (conns) @@ -2354,6 +2369,7 @@ int main(int argc, char **argv) { #ifdef CONFIG_SOXR pthread_create(&soxr_time_check_thread, NULL, &soxr_time_check, NULL); + soxr_time_check_thread_started = 1; #endif /* From 5e6e634497ffd5e55e4856849d94d758cc855407 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 12 Oct 2022 14:24:18 +0100 Subject: [PATCH 29/42] Tiny format fix. --- shairport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shairport.c b/shairport.c index d9fe93a5..1c586479 100644 --- a/shairport.c +++ b/shairport.c @@ -1983,7 +1983,7 @@ int main(int argc, char **argv) { if (display_config_selected != 0) { display_config(argc, argv); if (argc == 2) { - fprintf(stderr, ">> Goodbye!\n"); + inform(">> Goodbye!"); exit(EXIT_SUCCESS); } } From 15b60fe895e6cc25a1d25895cd586cd7817aec5b Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 12 Oct 2022 14:30:42 +0100 Subject: [PATCH 30/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 66266778..abdb230f 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,8 @@ +Version 4.1-dev-726-g5e6e6344 +==== +**Enhancement** +* Enhance `--displayConfig` to log information about the OS as well as about Shairport Sync itself, and use Shairport Sync's standard logging -- it's not stuck on `STDERR` anymore. + Version 4.1-dev-721-g93c1f1ae ==== **Pesky Things You Can't Ignore** From 7497b8e2dacc6173301e364dbd635d936f9d95f0 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 13 Oct 2022 12:02:52 +0100 Subject: [PATCH 31/42] Add -X as a quick alternative to --displayConfig. --- man/shairport-sync.7 | 12 ++++++------ man/shairport-sync.7.xml | 30 +++++++++++++++--------------- shairport.c | 4 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/man/shairport-sync.7 b/man/shairport-sync.7 index 8bbb915b..345e75de 100644 --- a/man/shairport-sync.7 +++ b/man/shairport-sync.7 @@ -4,7 +4,7 @@ shairport-sync \- AirPlay and AirPlay 2 Audio Player .SH SYNOPSIS \fBshairport-sync [-djvw]\fB [-a \fB\fIservice-name\fB | --name=\fB\fIservice-name\fB]\fB [-B \fB\fIcommand\fB | --onstart=\fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB | --configfile=\fB\fIconfigurationfile\fB]\fB [-d | --daemon]\fB [-E \fB\fIcommand\fB | --onstop=\fB\fIcommand\fB]\fB [-g | --get-cover-art]\fB [-j | --justDaemoniseNoPIDFile]\fB [--logOutputLevel]\fB [--log-to-syslog]\fB [-L \fB\fIlatency\fB | --latency=\fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB | --mdns=\fB\fIbackend\fB]\fB [-M | --metadata-enable]\fB [-o \fB\fIbackend\fB | --output=\fB\fIbackend\fB]\fB [-p \fB\fIport\fB | --port=\fB\fIport\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB | --resync=\fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB | --stuffing=\fB\fImode\fB]\fB [-t \fB\fItimeout\fB | --timeout=\fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-v | --verbose]\fB [-w | --wait-cmd]\fB [-- \fB\fIaudio_backend_options\fB]\fB -shairport-sync --displayConfig\fB +shairport-sync -X | --displayConfig\fB shairport-sync -h\fB @@ -73,11 +73,6 @@ Read configuration settings from \fIfilename\f1. The default is to read them fro \fB-d | --daemon\f1 Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time. See also the \fB-j\f1 option. Only available if shairport-sync has been compiled with libdaemon support. .TP -\fB--displayConfig\f1 -This logs information relating to the configuration of Shairport Sync. It can be very useful for debugging. The information logged is some host OS information, the Shairport Sync version string (which indicates the build options used when \fBshairport-sync\f1 was built), the contents of the command line that invoked Shairport Sync, the name of the configuration file and the active settings therein. - -If this is the only option on the command line, \fBshairport-sync\f1 will terminate after displaying the information. -.TP \fB-E \f1\fIprogram\f1\fB | --on-stop=\f1\fIprogram\f1 Execute \fIprogram\f1 when playback has ended. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have the appropriate shebang (\fI#!/bin/sh\f1) in the headline. @@ -151,6 +146,11 @@ Print debug information to the \fISTDERR\f1, or to \fBsyslog\f1 if the \fB-log-t .TP \fB-w | --wait-cmd\f1 Wait for commands specified using \fB-B\f1 or \fB-E\f1 to complete before continuing execution. +.TP +\fB-X | --displayConfig\f1 +This logs information relating to the configuration of Shairport Sync. It can be very useful for debugging. The information logged is some host OS information, the Shairport Sync version string (which indicates the build options used when \fBshairport-sync\f1 was built), the contents of the command line that invoked Shairport Sync, the name of the configuration file and the active settings therein. + +If this is the only option on the command line, \fBshairport-sync\f1 will terminate after displaying the information. .SH AUDIO BACKEND OPTIONS Audio Backend Options are command-line options that are passed to the chosen audio backend. They are always preceded by the \fB--\f1 symbol to introduce them and to separate them from any preceding program options. In this way, option letters can be used as program options and reused as audio backend options without ambiguity. diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml index c9a197de..43dd4b15 100644 --- a/man/shairport-sync.7.xml +++ b/man/shairport-sync.7.xml @@ -60,7 +60,7 @@ [-w | --wait-cmd] [-- audio_backend_options] - shairport-sync --displayConfig + shairport-sync -X | --displayConfig shairport-sync -h shairport-sync -k shairport-sync -V @@ -203,20 +203,6 @@

- - + +

Audio Backend Options are command-line options that are passed to the chosen audio backend. They are always preceded by the -- symbol to introduce them and to separate them from diff --git a/shairport.c b/shairport.c index 1c586479..11ffcf6e 100644 --- a/shairport.c +++ b/shairport.c @@ -281,7 +281,7 @@ void usage(char *progname) { printf("Options:\n"); printf(" -h, --help Show this help.\n"); printf(" -V, --version Show version information -- the version string.\n"); - printf(" --displayConfig Output OS information, version string, command line, configuration file and active settings to the log.\n"); + printf(" -X, --displayConfig Output OS information, version string, command line, configuration file and active settings to the log.\n"); printf(" --statistics Print some interesting statistics. More will be printed if -v / -vv / -vvv are also chosen.\n"); printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n"); printf(" -c, --configfile=FILE Read configuration settings from FILE. Default is %s.\n", configuration_file_path); @@ -352,7 +352,7 @@ int parse_options(int argc, char **argv) { {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL, NULL}, {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL, NULL}, {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL, NULL}, - {"displayConfig", 0, POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL}, + {"displayConfig", 'X', POPT_ARG_NONE, &display_config_selected, 0, NULL, NULL}, {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL, NULL}, {"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL, NULL}, {"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL, NULL}, From abc0e09324b5e0fddc088bc2579673539c3242e2 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 14 Oct 2022 10:18:37 +0100 Subject: [PATCH 32/42] Display command line at the start if debug is enabled. --- shairport.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/shairport.c b/shairport.c index 11ffcf6e..9bc5456a 100644 --- a/shairport.c +++ b/shairport.c @@ -2173,6 +2173,24 @@ int main(int argc, char **argv) { } else { debug(1, "can't print the version information!"); } + + // print command line + + if (argc != 0) { + char result[1024]; + char *obfp = result; + int i; + for (i = 0; i < argc - 1; i++) { + snprintf(obfp, strlen(argv[i]) + 2, "%s ", argv[i]); + obfp += strlen(argv[i]) + 1; + } + snprintf(obfp, strlen(argv[i]) + 1, "%s", argv[i]); + obfp += strlen(argv[i]); + *obfp = 0; + debug(1,"Command Line: \"%s\".", result); + } + + debug(1, "log verbosity is %d.", debuglev); From 63e0dfdac0b57d64f4eb1cc0c137c8cc3ca9ad23 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 14 Oct 2022 12:26:14 +0100 Subject: [PATCH 33/42] If no logging options are chosen and if a process has been libdaemonised, automatically direct its logs to the syslog, i.e. the daemon_log. --- shairport.c | 167 ++++++++++++++++++++++++++-------------------------- 1 file changed, 82 insertions(+), 85 deletions(-) diff --git a/shairport.c b/shairport.c index 9bc5456a..04b0c914 100644 --- a/shairport.c +++ b/shairport.c @@ -130,6 +130,9 @@ int killOption = 0; int daemonisewith = 0; int daemonisewithout = 0; int log_to_syslog_selected = 0; +#ifdef CONFIG_LIBDAEMON +int log_to_default = 1; // needed if libdaemon used +#endif int display_config_selected = 0; int log_to_syslog_select_is_first_command_line_argument = 0; @@ -442,6 +445,9 @@ int parse_options(int argc, char **argv) { inform("Suggestion: make \"--log-to-syslog\" the first command line argument to ensure " "messages go to the syslog right from the beginning."); } +#ifdef CONFIG_LIBDAEMON + log_to_default = 0; // a specific log output modality has been selected. +#endif log_to_syslog(); } @@ -797,6 +803,9 @@ int parse_options(int argc, char **argv) { /* Get the diagnostics output default. */ if (config_lookup_string(config.cfg, "diagnostics.log_output_to", &str)) { +#ifdef CONFIG_LIBDAEMON + log_to_default = 0; // a specific log output modality has been selected. +#endif if (strcasecmp(str, "syslog") == 0) log_to_syslog(); else if (strcasecmp(str, "stdout") == 0) { @@ -1471,7 +1480,7 @@ int parse_options(int argc, char **argv) { char temp_pid_dir[4096]; strcpy(temp_pid_dir, "/var/run/"); strcat(temp_pid_dir, config.appName); - debug(1, "default pid filename is \"%s\".", temp_pid_dir); + debug(3, "Default PID directory is \"%s\".", temp_pid_dir); char *use_this_pid_dir = temp_pid_dir; #endif // debug(1,"config.piddir \"%s\".",config.piddir); @@ -1501,7 +1510,7 @@ char pid_file_path_string[4096] = "\0"; const char *pid_file_proc(void) { snprintf(pid_file_path_string, sizeof(pid_file_path_string), "%s/%s.pid", config.computed_piddir, daemon_pid_file_ident ? daemon_pid_file_ident : "unknown"); - // debug(1,"pid_file_path_string \"%s\".",pid_file_path_string); + debug(1, "PID file: \"%s\".", pid_file_path_string); return pid_file_path_string; } #endif @@ -1553,37 +1562,35 @@ void exit_function() { // so don't wait for it if (type_of_exit_cleanup != TOE_dbus) pthread_join(dbus_thread, NULL); - debug(2, "Stopping D-Bus Loop Thread Done"); + debug(2, "Stopping D-Bus Loop Thread Done"); } #endif #ifdef CONFIG_DACP_CLIENT debug(2, "Stopping DACP Monitor"); dacp_monitor_stop(); - debug(2, "Stopping DACP Monitor Done"); + debug(2, "Stopping DACP Monitor Done"); #endif #ifdef CONFIG_METADATA_HUB debug(2, "Stopping metadata hub"); metadata_hub_stop(); - debug(2, "Stopping metadata done"); + debug(2, "Stopping metadata done"); #endif #ifdef CONFIG_METADATA debug(2, "Stopping metadata"); metadata_stop(); // close down the metadata pipe - debug(2, "Stopping metadata done"); + debug(2, "Stopping metadata done"); #endif debug(2, "Stopping the activity monitor."); activity_monitor_stop(0); debug(2, "Stopping the activity monitor done."); - if ((config.output) && (config.output->deinit)) { debug(2, "Deinitialise the audio backend."); config.output->deinit(); debug(2, "Deinitialise the audio backend done."); - } #ifdef CONFIG_SOXR @@ -1594,7 +1601,7 @@ void exit_function() { soxr_time_check_thread_started = 0; debug(1, "Waiting for SoXr timecheck to terminate done"); } - + #endif if (conns) @@ -1650,12 +1657,12 @@ void exit_function() { #ifdef CONFIG_LIBDAEMON if (this_is_the_daemon_process) { // this is the daemon that is exiting - debug(1, "libdaemon daemon exit"); + debug(1, "libdaemon daemon process exit"); } else { if (config.daemonise) - debug(1, "libdaemon parent exit"); + debug(1, "libdaemon parent process exit"); else - debug(1, "exit_function libdaemon exit"); + debug(1, "normal exit"); } #else mdns_unregister(); // once the dacp handler is done and all player threrads are done it should @@ -1997,23 +2004,18 @@ int main(int argc, char **argv) { /* Check if the new function daemon_pid_file_kill_wait() is available, if it is, use it. */ if ((ret = daemon_pid_file_kill_wait(SIGTERM, 5)) < 0) { if (errno == ENOENT) - daemon_log(LOG_WARNING, "Failed to kill %s daemon: PID file not found.", config.appName); + warn("Failed to kill the %s daemon. The PID file was not found.", config.appName); + // daemon_log(LOG_WARNING, "Failed to kill %s daemon: PID file not found.", config.appName); else - daemon_log(LOG_WARNING, "Failed to kill %s daemon: \"%s\", errno %u.", config.appName, - strerror(errno), errno); - } else { - // debug(1,"Successfully killed the %s daemon.", config.appName); - if (daemon_pid_file_remove() == 0) - debug(2, "killed the %s daemon.", config.appName); - else - daemon_log(LOG_WARNING, - "killed the %s daemon, but cannot remove old PID file: \"%s\", errno %u.", - config.appName, strerror(errno), errno); + warn("Failed to kill the %s daemon. Error: \"%s\", errno %u.", config.appName, + strerror(errno), errno); + // daemon_log(LOG_WARNING, "Failed to kill %s daemon: \"%s\", errno %u.", config.appName, + // strerror(errno), errno); } return ret < 0 ? 1 : 0; #else - fprintf(stderr, "%s was built without libdaemon, so does not support the -k or --kill option\n", - config.appName); + warn("%s was built without libdaemon, so it does not support the -k or --kill option.", + config.appName); return 1; #endif } @@ -2021,7 +2023,8 @@ int main(int argc, char **argv) { #ifdef CONFIG_LIBDAEMON /* If we are going to daemonise, check that the daemon is not running already.*/ if ((config.daemonise) && ((pid = daemon_pid_file_is_running()) >= 0)) { - daemon_log(LOG_ERR, "The %s daemon is already running as PID %u", config.appName, pid); + warn("The %s deamon is already running with process ID (PID) %u.", config.appName, pid); + // daemon_log(LOG_ERR, "The %s daemon is already running as PID %u", config.appName, pid); return 1; } @@ -2030,8 +2033,7 @@ int main(int argc, char **argv) { if (config.daemonise) { /* Prepare for return value passing from the initialization procedure of the daemon process */ if (daemon_retval_init() < 0) { - daemon_log(LOG_ERR, "Failed to create pipe."); - return 1; + die("Failed to create pipe."); } /* Do the fork */ @@ -2046,43 +2048,38 @@ int main(int argc, char **argv) { /* Wait for 20 seconds for the return value passed from the daemon process */ if ((ret = daemon_retval_wait(20)) < 0) { - daemon_log(LOG_ERR, "Could not receive return value from daemon process: %s", - strerror(errno)); - return 255; + die("Could not receive return value from daemon process: %s", strerror(errno)); } switch (ret) { case 0: break; case 1: - daemon_log( - LOG_ERR, - "the %s daemon failed to launch: could not close open file descriptors after forking.", - config.appName); + warn("The %s daemon failed to launch: could not close open file descriptors after forking.", + config.appName); break; case 2: - daemon_log(LOG_ERR, "the %s daemon failed to launch: could not create PID file.", - config.appName); + warn("The %s daemon failed to launch: could not create PID file.", config.appName); break; case 3: - daemon_log(LOG_ERR, - "the %s daemon failed to launch: could not create or access PID directory.", - config.appName); + warn("The %s daemon failed to launch: could not create or access PID directory.", + config.appName); break; default: - daemon_log(LOG_ERR, "the %s daemon failed to launch, error %i.", config.appName, ret); + warn("The %s daemon failed to launch, error %i.", config.appName, ret); } return ret; } else { /* pid == 0 means we are the daemon */ - this_is_the_daemon_process = 1; // + this_is_the_daemon_process = 1; + if (log_to_default != 0) // if a specific logging mode has not been selected + log_to_syslog(); // automatically send logs to the daemon_log /* Close FDs */ if (daemon_close_all(-1) < 0) { - daemon_log(LOG_ERR, "Failed to close all file descriptors: %s", strerror(errno)); + warn("Failed to close all file descriptors while daemonising. Error: %s", strerror(errno)); /* Send the error condition to the parent process */ daemon_retval_send(1); - daemon_signal_done(); return 0; } @@ -2090,19 +2087,20 @@ int main(int argc, char **argv) { /* Create the PID file if required */ if (config.daemonise_store_pid) { /* Create the PID directory if required -- we don't really care about the result */ - printf("PID directory is \"%s\".", config.computed_piddir); + debug(1, "PID directory is \"%s\".", config.computed_piddir); int result = mkpath(config.computed_piddir, 0700); if ((result != 0) && (result != -EEXIST)) { // error creating or accessing the PID file directory + warn("Failed to create the directory \"%s\" for the PID file. Error: %s.", + config.computed_piddir, strerror(errno)); daemon_retval_send(3); - daemon_signal_done(); return 0; } if (daemon_pid_file_create() < 0) { - daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno)); - + // daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno)); + warn("Failed to create the PID file. Error: %s.", strerror(errno)); daemon_retval_send(2); daemon_signal_done(); return 0; @@ -2129,10 +2127,10 @@ int main(int argc, char **argv) { apfh = apfh >> 32; uint32_t apf32 = apf; uint32_t apfh32 = apfh; - debug(1, "startup in AirPlay 2 mode, with features 0x%" PRIx32 ",0x%" PRIx32 " on device \"%s\".", + debug(1, "Startup in AirPlay 2 mode, with features 0x%" PRIx32 ",0x%" PRIx32 " on device \"%s\".", apf32, apfh32, config.airplay_device_id); #else - debug(1, "startup in classic Airplay (aka \"AirPlay 1\") mode."); + debug(1, "Startup in classic Airplay (aka \"AirPlay 1\") mode."); #endif // control-c (SIGINT) cleanly @@ -2168,12 +2166,12 @@ int main(int argc, char **argv) { char *version_dbs = get_version_string(); if (version_dbs) { - debug(1, "software version: \"%s\"", version_dbs); + debug(1, "Version String: \"%s\"", version_dbs); free(version_dbs); } else { - debug(1, "can't print the version information!"); + debug(1, "Can't print the version information!"); } - + // print command line if (argc != 0) { @@ -2187,34 +2185,7 @@ int main(int argc, char **argv) { snprintf(obfp, strlen(argv[i]) + 1, "%s", argv[i]); obfp += strlen(argv[i]); *obfp = 0; - debug(1,"Command Line: \"%s\".", result); - } - - - - debug(1, "log verbosity is %d.", debuglev); - - config.output = audio_get_output(config.output_name); - if (!config.output) { - die("Invalid audio backend \"%s\" selected!", - config.output_name == NULL ? "" : config.output_name); - } - config.output->init(argc - audio_arg, argv + audio_arg); - - // pthread_cleanup_push(main_cleanup_handler, NULL); - - // daemon_log(LOG_NOTICE, "startup"); - - switch (config.endianness) { - case SS_LITTLE_ENDIAN: - debug(2, "The processor is running little-endian."); - break; - case SS_BIG_ENDIAN: - debug(2, "The processor is running big-endian."); - break; - case SS_PDP_ENDIAN: - debug(2, "The processor is running pdp-endian."); - break; + debug(1, "Command Line: \"%s\".", result); } #ifdef CONFIG_AIRPLAY_2 @@ -2246,8 +2217,35 @@ int main(int argc, char **argv) { /* Tell Libgcrypt that initialization has completed. */ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + debug(1, "libgcrypt initialised."); + #endif + debug(1, "Log Verbosity is %d.", debuglev); + + config.output = audio_get_output(config.output_name); + if (!config.output) { + die("Invalid audio backend \"%s\" selected!", + config.output_name == NULL ? "" : config.output_name); + } + config.output->init(argc - audio_arg, argv + audio_arg); + + // pthread_cleanup_push(main_cleanup_handler, NULL); + + // daemon_log(LOG_NOTICE, "startup"); + + switch (config.endianness) { + case SS_LITTLE_ENDIAN: + debug(2, "The processor is running little-endian."); + break; + case SS_BIG_ENDIAN: + debug(2, "The processor is running big-endian."); + break; + case SS_PDP_ENDIAN: + debug(2, "The processor is running pdp-endian."); + break; + } + /* Mess around with the latency options */ // Basically, we expect the source to set the latency and add a fixed offset of 11025 frames to // it, which sounds right @@ -2271,7 +2269,7 @@ int main(int argc, char **argv) { } /* Print out options */ - debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off"); + debug(1, "disable_resend_requests is %s.", config.disable_resend_requests ? "on" : "off"); debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped " "deliberately.", @@ -2300,9 +2298,8 @@ int main(int argc, char **argv) { debug(1, "mdns backend \"%s\".", strnull(config.mdns_name)); debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); debug(1, "interpolation setting is \"%s\".", - config.packet_stuffing == ST_basic ? "basic" - : config.packet_stuffing == ST_soxr ? "soxr" - : "auto"); + config.packet_stuffing == ST_basic ? "basic" + : config.packet_stuffing == ST_soxr ? "soxr" : "auto"); debug(1, "interpolation soxr_delay_threshold is %d.", config.soxr_delay_threshold); debug(1, "resync time is %f seconds.", config.resyncthreshold); debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption); From 4bb2b1dcd13fea3fafda767b4cbf39b817a114a2 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 14 Oct 2022 12:31:55 +0100 Subject: [PATCH 34/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index abdb230f..3ea17190 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,9 @@ +Version 4.1-dev-730-g63e0dfda +==== +**Minor Debugging Ehnancements** +* Improve debugging of a Shairport Sync daemon process created with `libdaemon`. +* List the command line when Shairport Sync starts with a verbosity of 1 or more. + Version 4.1-dev-726-g5e6e6344 ==== **Enhancement** From b0bf6668f3ed6aaeefd1b72e74d2761ba3f2dd9f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 14 Oct 2022 12:34:06 +0100 Subject: [PATCH 35/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 3ea17190..214796f9 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,6 +1,6 @@ Version 4.1-dev-730-g63e0dfda ==== -**Minor Debugging Ehnancements** +**Minor Debugging Enhancements** * Improve debugging of a Shairport Sync daemon process created with `libdaemon`. * List the command line when Shairport Sync starts with a verbosity of 1 or more. From da66b9cfc98b795b67cca9d975a6f823caab471e Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 14 Oct 2022 14:52:48 +0100 Subject: [PATCH 36/42] Update docker-build-on-push.yaml --- .github/workflows/docker-build-on-push.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build-on-push.yaml b/.github/workflows/docker-build-on-push.yaml index 1d273cad..17a621a5 100644 --- a/.github/workflows/docker-build-on-push.yaml +++ b/.github/workflows/docker-build-on-push.yaml @@ -9,7 +9,8 @@ name: Build and push docker (commit) on: push: branches: - - '**' + - master + - development env: DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 @@ -72,4 +73,4 @@ jobs: push: ${{ env.IMAGE_TAG_BASE != '' }} tags: ${{ secrets.DOCKER_IMAGE_NAME }}:${{ env.IMAGE_TAG_BASE }}-classic build-args: | - SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }} \ No newline at end of file + SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }} From 7004c81a28b42cd33a5cdc9c3f4e2c1d852c63c5 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sat, 15 Oct 2022 13:31:32 +0100 Subject: [PATCH 37/42] Check for the existence of a session key when starting to play AP2, and drop the connection if not. --- rtsp.c | 328 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 167 insertions(+), 161 deletions(-) diff --git a/rtsp.c b/rtsp.c index 62da82e5..6fb60824 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1501,6 +1501,7 @@ int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) { {404, "Not Found"}, {451, "Unavailable"}, {456, "Header Field Not Valid for Resource"}, + {470, "Connection Authorization Required"}, {500, "Internal Server Error"}, {501, "Not Implemented"}}; // 451 is really "Unavailable For Legal Reasons"! @@ -3148,187 +3149,192 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) plist_t streams_array = plist_new_array(); // to hold the ports and stuff plist_t stream0dict = plist_new_dict(); - // more stuff - // set up a UDP control stream and thread and a UDP or TCP audio stream and thread - // bind a new UDP port and get a socket - conn->local_ap2_control_port = 0; // any port - err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_ap2_control_port, - &conn->ap2_control_socket); - if (err) { - die("Error %d: could not find a UDP port to use as an ap2_control port", err); - } - debug(2, "Connection %d: UDP control port opened: %u.", conn->connection_number, - conn->local_ap2_control_port); - - pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn); - - // get the session key + // get the session key -- it must have one plist_t item = plist_dict_get_item(stream0, "shk"); // session key - uint64_t item_value = 0; + uint64_t item_value = 0; // the length plist_get_data_val(item, (char **)&conn->session_key, &item_value); + if (item_value != 0) { - // get the DACP-ID and Active Remote for remote control stuff + // more stuff + // set up a UDP control stream and thread and a UDP or TCP audio stream and thread - char *ar = msg_get_header(req, "Active-Remote"); - if (ar) { - debug(3, "Connection %d: SETUP AP2 -- Active-Remote string seen: \"%s\".", - conn->connection_number, ar); - // get the active remote - if (conn->dacp_active_remote) // this is in case SETUP was previously called - free(conn->dacp_active_remote); - conn->dacp_active_remote = strdup(ar); -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); -#endif - } else { - debug(1, "Connection %d: SETUP AP2 no Active-Remote information the SETUP Record.", - conn->connection_number); - if (conn->dacp_active_remote) { // this is in case SETUP was previously called - free(conn->dacp_active_remote); - conn->dacp_active_remote = NULL; - } - } - - ar = msg_get_header(req, "DACP-ID"); - if (ar) { - debug(3, "Connection %d: SETUP AP2 -- DACP-ID string seen: \"%s\".", - conn->connection_number, ar); - if (conn->dacp_id) // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = strdup(ar); -#ifdef CONFIG_METADATA - send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); -#endif - } else { - debug(1, "Connection %d: SETUP AP2 doesn't include DACP-ID string information.", - conn->connection_number); - if (conn->dacp_id) { // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = NULL; - } - } - - // now, get the type of the stream. - item = plist_dict_get_item(stream0, "type"); - item_value = 0; - plist_get_uint_val(item, &item_value); - - switch (item_value) { - case 96: { - debug(1, "Connection %d. AP2 Realtime Audio Stream.", conn->connection_number); - debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req); - // get_play_lock(conn); - conn->airplay_stream_type = realtime_stream; // bind a new UDP port and get a socket - conn->local_realtime_audio_port = 0; // any port + conn->local_ap2_control_port = 0; // any port err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_realtime_audio_port, - &conn->realtime_audio_socket); + conn->self_scope_id, &conn->local_ap2_control_port, + &conn->ap2_control_socket); if (err) { - die("Error %d: could not find a UDP port to use as a realtime_audio port", err); + die("Error %d: could not find a UDP port to use as an ap2_control port", err); } - debug(2, "Connection %d: UDP realtime audio port opened: %u.", conn->connection_number, - conn->local_realtime_audio_port); + debug(2, "Connection %d: UDP control port opened: %u.", conn->connection_number, + conn->local_ap2_control_port); - pthread_create(&conn->rtp_realtime_audio_thread, NULL, &rtp_realtime_audio_receiver, - (void *)conn); + pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn); - plist_dict_set_item(stream0dict, "type", plist_new_uint(96)); - plist_dict_set_item(stream0dict, "dataPort", - plist_new_uint(conn->local_realtime_audio_port)); + // get the DACP-ID and Active Remote for remote control stuff - conn->stream.type = ast_apple_lossless; - debug(3, "An ALAC stream has been detected."); - - // Set reasonable connection defaults - conn->stream.fmtp[0] = 96; - conn->stream.fmtp[1] = 352; - conn->stream.fmtp[2] = 0; - conn->stream.fmtp[3] = 16; - conn->stream.fmtp[4] = 40; - conn->stream.fmtp[5] = 10; - conn->stream.fmtp[6] = 14; - conn->stream.fmtp[7] = 2; - conn->stream.fmtp[8] = 255; - conn->stream.fmtp[9] = 0; - conn->stream.fmtp[10] = 0; - conn->stream.fmtp[11] = 44100; - - // set the parameters of the player (as distinct from the parameters of the decoder -- - // that's done later). - conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. - conn->input_rate = conn->stream.fmtp[11]; - conn->input_num_channels = conn->stream.fmtp[7]; - conn->input_bit_depth = conn->stream.fmtp[3]; - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - debug(2, "Realtime Stream Play"); - activity_monitor_signify_activity(1); - player_prepare_to_play(conn); - player_play(conn); - - conn->rtp_running = 1; // hack! - } break; - case 103: { - debug(1, "Connection %d. AP2 Buffered Audio Stream.", conn->connection_number); - debug_log_rtsp_message(2, "Buffered Audio Stream SETUP incoming message", req); - // get_play_lock(conn); - conn->airplay_stream_type = buffered_stream; - // get needed stuff - - // bind a new TCP port and get a socket - conn->local_buffered_audio_port = 0; // any port - err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_buffered_audio_port, - &conn->buffered_audio_socket); - if (err) { - die("SETUP on Connection %d: Error %d: could not find a TCP port to use as a " - "buffered_audio port", - conn->connection_number, err); + char *ar = msg_get_header(req, "Active-Remote"); + if (ar) { + debug(3, "Connection %d: SETUP AP2 -- Active-Remote string seen: \"%s\".", + conn->connection_number, ar); + // get the active remote + if (conn->dacp_active_remote) // this is in case SETUP was previously called + free(conn->dacp_active_remote); + conn->dacp_active_remote = strdup(ar); + #ifdef CONFIG_METADATA + send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); + #endif + } else { + debug(1, "Connection %d: SETUP AP2 no Active-Remote information the SETUP Record.", + conn->connection_number); + if (conn->dacp_active_remote) { // this is in case SETUP was previously called + free(conn->dacp_active_remote); + conn->dacp_active_remote = NULL; + } } - debug(2, "Connection %d: TCP Buffered Audio port opened: %u.", conn->connection_number, - conn->local_buffered_audio_port); + ar = msg_get_header(req, "DACP-ID"); + if (ar) { + debug(3, "Connection %d: SETUP AP2 -- DACP-ID string seen: \"%s\".", + conn->connection_number, ar); + if (conn->dacp_id) // this is in case SETUP was previously called + free(conn->dacp_id); + conn->dacp_id = strdup(ar); + #ifdef CONFIG_METADATA + send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); + #endif + } else { + debug(1, "Connection %d: SETUP AP2 doesn't include DACP-ID string information.", + conn->connection_number); + if (conn->dacp_id) { // this is in case SETUP was previously called + free(conn->dacp_id); + conn->dacp_id = NULL; + } + } - // hack. - conn->max_frames_per_packet = 352; // number of audio frames per packet. - conn->input_rate = 44100; // we are stuck with this for the moment. - conn->input_num_channels = 2; - conn->input_bit_depth = 16; - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - activity_monitor_signify_activity(1); - player_prepare_to_play( - conn); // get capabilities of DAC before creating the buffered audio thread + // now, get the type of the stream. + item = plist_dict_get_item(stream0, "type"); + item_value = 0; + plist_get_uint_val(item, &item_value); - pthread_create(&conn->rtp_buffered_audio_thread, NULL, &rtp_buffered_audio_processor, - (void *)conn); + switch (item_value) { + case 96: { + debug(1, "Connection %d. AP2 Realtime Audio Stream.", conn->connection_number); + debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req); + // get_play_lock(conn); + conn->airplay_stream_type = realtime_stream; + // bind a new UDP port and get a socket + conn->local_realtime_audio_port = 0; // any port + err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, + conn->self_scope_id, &conn->local_realtime_audio_port, + &conn->realtime_audio_socket); + if (err) { + die("Error %d: could not find a UDP port to use as a realtime_audio port", err); + } + debug(2, "Connection %d: UDP realtime audio port opened: %u.", conn->connection_number, + conn->local_realtime_audio_port); - plist_dict_set_item(stream0dict, "type", plist_new_uint(103)); - plist_dict_set_item(stream0dict, "dataPort", - plist_new_uint(conn->local_buffered_audio_port)); - plist_dict_set_item(stream0dict, "audioBufferSize", - plist_new_uint(conn->ap2_audio_buffer_size)); + pthread_create(&conn->rtp_realtime_audio_thread, NULL, &rtp_realtime_audio_receiver, + (void *)conn); - // this should be cancelled by an activity_monitor_signify_activity(1) - // call in the SETRATEANCHORI handler, which should come up right away - activity_monitor_signify_activity(0); - player_play(conn); + plist_dict_set_item(stream0dict, "type", plist_new_uint(96)); + plist_dict_set_item(stream0dict, "dataPort", + plist_new_uint(conn->local_realtime_audio_port)); - conn->rtp_running = 1; // hack! - } break; - default: - debug(1, "SETUP on Connection %d: Unhandled stream type %" PRIu64 ".", - conn->connection_number, item_value); - debug_log_rtsp_message(1, "Unhandled stream type incoming message", req); + conn->stream.type = ast_apple_lossless; + debug(3, "An ALAC stream has been detected."); + + // Set reasonable connection defaults + conn->stream.fmtp[0] = 96; + conn->stream.fmtp[1] = 352; + conn->stream.fmtp[2] = 0; + conn->stream.fmtp[3] = 16; + conn->stream.fmtp[4] = 40; + conn->stream.fmtp[5] = 10; + conn->stream.fmtp[6] = 14; + conn->stream.fmtp[7] = 2; + conn->stream.fmtp[8] = 255; + conn->stream.fmtp[9] = 0; + conn->stream.fmtp[10] = 0; + conn->stream.fmtp[11] = 44100; + + // set the parameters of the player (as distinct from the parameters of the decoder -- + // that's done later). + conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. + conn->input_rate = conn->stream.fmtp[11]; + conn->input_num_channels = conn->stream.fmtp[7]; + conn->input_bit_depth = conn->stream.fmtp[3]; + conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); + debug(2, "Realtime Stream Play"); + activity_monitor_signify_activity(1); + player_prepare_to_play(conn); + player_play(conn); + + conn->rtp_running = 1; // hack! + } break; + case 103: { + debug(1, "Connection %d. AP2 Buffered Audio Stream.", conn->connection_number); + debug_log_rtsp_message(2, "Buffered Audio Stream SETUP incoming message", req); + // get_play_lock(conn); + conn->airplay_stream_type = buffered_stream; + // get needed stuff + + // bind a new TCP port and get a socket + conn->local_buffered_audio_port = 0; // any port + err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, + conn->self_scope_id, &conn->local_buffered_audio_port, + &conn->buffered_audio_socket); + if (err) { + die("SETUP on Connection %d: Error %d: could not find a TCP port to use as a " + "buffered_audio port", + conn->connection_number, err); + } + + debug(2, "Connection %d: TCP Buffered Audio port opened: %u.", conn->connection_number, + conn->local_buffered_audio_port); + + // hack. + conn->max_frames_per_packet = 352; // number of audio frames per packet. + conn->input_rate = 44100; // we are stuck with this for the moment. + conn->input_num_channels = 2; + conn->input_bit_depth = 16; + conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); + activity_monitor_signify_activity(1); + player_prepare_to_play( + conn); // get capabilities of DAC before creating the buffered audio thread + + pthread_create(&conn->rtp_buffered_audio_thread, NULL, &rtp_buffered_audio_processor, + (void *)conn); + + plist_dict_set_item(stream0dict, "type", plist_new_uint(103)); + plist_dict_set_item(stream0dict, "dataPort", + plist_new_uint(conn->local_buffered_audio_port)); + plist_dict_set_item(stream0dict, "audioBufferSize", + plist_new_uint(conn->ap2_audio_buffer_size)); + + // this should be cancelled by an activity_monitor_signify_activity(1) + // call in the SETRATEANCHORI handler, which should come up right away + activity_monitor_signify_activity(0); + player_play(conn); + + conn->rtp_running = 1; // hack! + } break; + default: + debug(1, "SETUP on Connection %d: Unhandled stream type %" PRIu64 ".", + conn->connection_number, item_value); + debug_log_rtsp_message(1, "Unhandled stream type incoming message", req); + } + + plist_dict_set_item(stream0dict, "controlPort", plist_new_uint(conn->local_ap2_control_port)); + + plist_array_append_item(streams_array, stream0dict); + plist_dict_set_item(setupResponsePlist, "streams", streams_array); + resp->respcode = 200; + } else { + warn("this stream can not be played because a session key is missing."); } - - plist_dict_set_item(stream0dict, "controlPort", plist_new_uint(conn->local_ap2_control_port)); - - plist_array_append_item(streams_array, stream0dict); - plist_dict_set_item(setupResponsePlist, "streams", streams_array); - resp->respcode = 200; } else if (conn->airplay_stream_category == remote_control_stream) { debug(2, "Connection %d (RC): SETUP: Remote Control Stream received from %s.", conn->connection_number, conn->client_ip_string); @@ -4303,7 +4309,7 @@ static void handle_get_parameter(__attribute__((unused)) rtsp_conn_info *conn, r if ((req->content) && (req->contentlength == strlen("volume\r\n")) && strstr(req->content, "volume") == req->content) { - debug(2, "Connection %d: Current volume (%.6f) requested", conn->connection_number, + debug(1, "Connection %d: Current volume (%.6f) requested", conn->connection_number, config.airplay_volume); char *p = malloc(128); // will be automatically deallocated with the response is deleted if (p) { From a1a81dda72f4e6a52be821ce6f2b046005d5ce6f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sat, 15 Oct 2022 13:40:05 +0100 Subject: [PATCH 38/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 214796f9..8ce197c1 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,8 @@ +Version 4.1-dev-735-g6a55774f +==== +**Bug Fix** +* Very occasionally, and for as-yet unknown reasons, an AirPlay 2 session may not include an important parameter called a "session key". This was causing Shairport Sync to crash. With this update, Shairport Sync will now simply drop the entire connection if a session doesn't include a "session key". Addresses the crashing issue reported in [#1551](https://github.com/mikebrady/shairport-sync/issues/1551). Big thanks to [Mike](https://github.com/xska2) for his huge assistance in tracking this down. + Version 4.1-dev-730-g63e0dfda ==== **Minor Debugging Enhancements** From 1046a076c404ba559c74c41c9000bf5222b2e435 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:15:26 +0100 Subject: [PATCH 39/42] Change how a missing session key is dealt with: instead of dropping the AirPlay connection, simply skip the audio. Hopefully this will be less disruptive for users. --- rtp.c | 109 ++++++++++-------- rtsp.c | 351 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 233 insertions(+), 227 deletions(-) diff --git a/rtp.c b/rtp.c index 7b53df74..88e05d0d 100644 --- a/rtp.c +++ b/rtp.c @@ -1671,40 +1671,44 @@ int32_t decipher_player_put_packet(uint8_t *ciphered_audio_alt, ssize_t nread, // %u, Csrc Count: %u, Marker: %u, Payload Type: %u, Sequence Number: %u, Timestamp: %u, // SSRC: %u.", version, padding, extension, csrc_count, marker, payload_type, // sequence_number, timestamp, ssrc); + + if (conn->session_key != NULL) { + unsigned char nonce[12]; + memset(nonce, 0, sizeof(nonce)); + memcpy(nonce + 4, ciphered_audio_alt + nread - 8, + 8); // front-pad the 8-byte nonce received to get the 12-byte nonce expected - unsigned char nonce[12]; - memset(nonce, 0, sizeof(nonce)); - memcpy(nonce + 4, ciphered_audio_alt + nread - 8, - 8); // front-pad the 8-byte nonce received to get the 12-byte nonce expected + // https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305/ietf_chacha20-poly1305_construction + // Note: the eight-byte nonce must be front-padded out to 12 bytes. - // https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305/ietf_chacha20-poly1305_construction - // Note: the eight-byte nonce must be front-padded out to 12 bytes. + unsigned char m[4096]; + unsigned long long new_payload_length = 0; + int response = crypto_aead_chacha20poly1305_ietf_decrypt( + m, // m + &new_payload_length, // mlen_p + NULL, // nsec, + ciphered_audio_alt + + 10, // the ciphertext starts 10 bytes in and is followed by the MAC tag, + nread - (8 + 10), // clen -- the last 8 bytes are the nonce + ciphered_audio_alt + 2, // authenticated additional data + 8, // authenticated additional data length + nonce, + conn->session_key); // *k + if (response != 0) { + debug(1, "Error decrypting an audio packet."); + } + // now pass it in to the regular processing chain - unsigned char m[4096]; - unsigned long long new_payload_length = 0; - int response = crypto_aead_chacha20poly1305_ietf_decrypt( - m, // m - &new_payload_length, // mlen_p - NULL, // nsec, - ciphered_audio_alt + - 10, // the ciphertext starts 10 bytes in and is followed by the MAC tag, - nread - (8 + 10), // clen -- the last 8 bytes are the nonce - ciphered_audio_alt + 2, // authenticated additional data - 8, // authenticated additional data length - nonce, - conn->session_key); // *k - if (response != 0) { - debug(1, "Error decrypting an audio packet."); + unsigned long long max_int = INT_MAX; // put in the right format + if (new_payload_length > max_int) + debug(1, "Madly long payload length!"); + int plen = new_payload_length; // + // debug(1," Write packet to buffer %d, timestamp %u.", sequence_number, timestamp); + player_put_packet(1, sequence_number, timestamp, m, plen, + conn); // the '1' means is original format + } else { + debug(2, "No session key, so the audio packet can not be deciphered -- skipped."); } - // now pass it in to the regular processing chain - - unsigned long long max_int = INT_MAX; // put in the right format - if (new_payload_length > max_int) - debug(1, "Madly long payload length!"); - int plen = new_payload_length; // - // debug(1," Write packet to buffer %d, timestamp %u.", sequence_number, timestamp); - player_put_packet(1, sequence_number, timestamp, m, plen, - conn); // the '1' means is original format return sequence_number; } else { debug(1, "packet was too small -- ignored"); @@ -2828,28 +2832,33 @@ void *rtp_buffered_audio_processor(void *arg) { if ((((flush_requested != 0) && (seq_no == flushUntilSeq)) || ((flush_requested == 0) && (new_buffer_needed))) && (too_soon_after_connection == 0)) { - - unsigned char nonce[12]; - memset(nonce, 0, sizeof(nonce)); - memcpy(nonce + 4, packet + nread - 8, - 8); // front-pad the 8-byte nonce received to get the 12-byte nonce expected - - // https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305/ietf_chacha20-poly1305_construction - // Note: the eight-byte nonce must be front-padded out to 12 bytes. unsigned long long new_payload_length = 0; - int response = crypto_aead_chacha20poly1305_ietf_decrypt( - m + 7, // m - &new_payload_length, // mlen_p - NULL, // nsec, - packet + 12, // the ciphertext starts 12 bytes in and is followed by the MAC tag, - nread - (8 + 12), // clen -- the last 8 bytes are the nonce - packet + 4, // authenticated additional data - 8, // authenticated additional data length - nonce, - conn->session_key); // *k - if (response != 0) { - debug(1, "Error decrypting audio packet %u -- packet length %d.", seq_no, nread); + int response = -1; // guess that there is a problem + if (conn->session_key != NULL) { + unsigned char nonce[12]; + memset(nonce, 0, sizeof(nonce)); + memcpy(nonce + 4, packet + nread - 8, + 8); // front-pad the 8-byte nonce received to get the 12-byte nonce expected + + // https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305/ietf_chacha20-poly1305_construction + // Note: the eight-byte nonce must be front-padded out to 12 bytes. + + response = crypto_aead_chacha20poly1305_ietf_decrypt( + m + 7, // m + &new_payload_length, // mlen_p + NULL, // nsec, + packet + 12, // the ciphertext starts 12 bytes in and is followed by the MAC tag, + nread - (8 + 12), // clen -- the last 8 bytes are the nonce + packet + 4, // authenticated additional data + 8, // authenticated additional data length + nonce, + conn->session_key); // *k + if (response != 0) + debug(1, "Error decrypting audio packet %u -- packet length %d.", seq_no, nread); } else { + debug(2, "No session key, so the audio packet can not be deciphered -- skipped."); + } + if (response == 0) { // now pass it in to the regular processing chain unsigned long long max_int = INT_MAX; // put in the right format diff --git a/rtsp.c b/rtsp.c index 6fb60824..6c109c18 100644 --- a/rtsp.c +++ b/rtsp.c @@ -3155,186 +3155,183 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) plist_t item = plist_dict_get_item(stream0, "shk"); // session key uint64_t item_value = 0; // the length plist_get_data_val(item, (char **)&conn->session_key, &item_value); - if (item_value != 0) { - // more stuff - // set up a UDP control stream and thread and a UDP or TCP audio stream and thread + // more stuff + // set up a UDP control stream and thread and a UDP or TCP audio stream and thread - // bind a new UDP port and get a socket - conn->local_ap2_control_port = 0; // any port - err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_ap2_control_port, - &conn->ap2_control_socket); - if (err) { - die("Error %d: could not find a UDP port to use as an ap2_control port", err); - } - debug(2, "Connection %d: UDP control port opened: %u.", conn->connection_number, - conn->local_ap2_control_port); - - pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn); - - // get the DACP-ID and Active Remote for remote control stuff - - char *ar = msg_get_header(req, "Active-Remote"); - if (ar) { - debug(3, "Connection %d: SETUP AP2 -- Active-Remote string seen: \"%s\".", - conn->connection_number, ar); - // get the active remote - if (conn->dacp_active_remote) // this is in case SETUP was previously called - free(conn->dacp_active_remote); - conn->dacp_active_remote = strdup(ar); - #ifdef CONFIG_METADATA - send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); - #endif - } else { - debug(1, "Connection %d: SETUP AP2 no Active-Remote information the SETUP Record.", - conn->connection_number); - if (conn->dacp_active_remote) { // this is in case SETUP was previously called - free(conn->dacp_active_remote); - conn->dacp_active_remote = NULL; - } - } - - ar = msg_get_header(req, "DACP-ID"); - if (ar) { - debug(3, "Connection %d: SETUP AP2 -- DACP-ID string seen: \"%s\".", - conn->connection_number, ar); - if (conn->dacp_id) // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = strdup(ar); - #ifdef CONFIG_METADATA - send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); - #endif - } else { - debug(1, "Connection %d: SETUP AP2 doesn't include DACP-ID string information.", - conn->connection_number); - if (conn->dacp_id) { // this is in case SETUP was previously called - free(conn->dacp_id); - conn->dacp_id = NULL; - } - } - - // now, get the type of the stream. - item = plist_dict_get_item(stream0, "type"); - item_value = 0; - plist_get_uint_val(item, &item_value); - - switch (item_value) { - case 96: { - debug(1, "Connection %d. AP2 Realtime Audio Stream.", conn->connection_number); - debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req); - // get_play_lock(conn); - conn->airplay_stream_type = realtime_stream; - // bind a new UDP port and get a socket - conn->local_realtime_audio_port = 0; // any port - err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_realtime_audio_port, - &conn->realtime_audio_socket); - if (err) { - die("Error %d: could not find a UDP port to use as a realtime_audio port", err); - } - debug(2, "Connection %d: UDP realtime audio port opened: %u.", conn->connection_number, - conn->local_realtime_audio_port); - - pthread_create(&conn->rtp_realtime_audio_thread, NULL, &rtp_realtime_audio_receiver, - (void *)conn); - - plist_dict_set_item(stream0dict, "type", plist_new_uint(96)); - plist_dict_set_item(stream0dict, "dataPort", - plist_new_uint(conn->local_realtime_audio_port)); - - conn->stream.type = ast_apple_lossless; - debug(3, "An ALAC stream has been detected."); - - // Set reasonable connection defaults - conn->stream.fmtp[0] = 96; - conn->stream.fmtp[1] = 352; - conn->stream.fmtp[2] = 0; - conn->stream.fmtp[3] = 16; - conn->stream.fmtp[4] = 40; - conn->stream.fmtp[5] = 10; - conn->stream.fmtp[6] = 14; - conn->stream.fmtp[7] = 2; - conn->stream.fmtp[8] = 255; - conn->stream.fmtp[9] = 0; - conn->stream.fmtp[10] = 0; - conn->stream.fmtp[11] = 44100; - - // set the parameters of the player (as distinct from the parameters of the decoder -- - // that's done later). - conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. - conn->input_rate = conn->stream.fmtp[11]; - conn->input_num_channels = conn->stream.fmtp[7]; - conn->input_bit_depth = conn->stream.fmtp[3]; - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - debug(2, "Realtime Stream Play"); - activity_monitor_signify_activity(1); - player_prepare_to_play(conn); - player_play(conn); - - conn->rtp_running = 1; // hack! - } break; - case 103: { - debug(1, "Connection %d. AP2 Buffered Audio Stream.", conn->connection_number); - debug_log_rtsp_message(2, "Buffered Audio Stream SETUP incoming message", req); - // get_play_lock(conn); - conn->airplay_stream_type = buffered_stream; - // get needed stuff - - // bind a new TCP port and get a socket - conn->local_buffered_audio_port = 0; // any port - err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, - conn->self_scope_id, &conn->local_buffered_audio_port, - &conn->buffered_audio_socket); - if (err) { - die("SETUP on Connection %d: Error %d: could not find a TCP port to use as a " - "buffered_audio port", - conn->connection_number, err); - } - - debug(2, "Connection %d: TCP Buffered Audio port opened: %u.", conn->connection_number, - conn->local_buffered_audio_port); - - // hack. - conn->max_frames_per_packet = 352; // number of audio frames per packet. - conn->input_rate = 44100; // we are stuck with this for the moment. - conn->input_num_channels = 2; - conn->input_bit_depth = 16; - conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); - activity_monitor_signify_activity(1); - player_prepare_to_play( - conn); // get capabilities of DAC before creating the buffered audio thread - - pthread_create(&conn->rtp_buffered_audio_thread, NULL, &rtp_buffered_audio_processor, - (void *)conn); - - plist_dict_set_item(stream0dict, "type", plist_new_uint(103)); - plist_dict_set_item(stream0dict, "dataPort", - plist_new_uint(conn->local_buffered_audio_port)); - plist_dict_set_item(stream0dict, "audioBufferSize", - plist_new_uint(conn->ap2_audio_buffer_size)); - - // this should be cancelled by an activity_monitor_signify_activity(1) - // call in the SETRATEANCHORI handler, which should come up right away - activity_monitor_signify_activity(0); - player_play(conn); - - conn->rtp_running = 1; // hack! - } break; - default: - debug(1, "SETUP on Connection %d: Unhandled stream type %" PRIu64 ".", - conn->connection_number, item_value); - debug_log_rtsp_message(1, "Unhandled stream type incoming message", req); - } - - plist_dict_set_item(stream0dict, "controlPort", plist_new_uint(conn->local_ap2_control_port)); - - plist_array_append_item(streams_array, stream0dict); - plist_dict_set_item(setupResponsePlist, "streams", streams_array); - resp->respcode = 200; - } else { - warn("this stream can not be played because a session key is missing."); + // bind a new UDP port and get a socket + conn->local_ap2_control_port = 0; // any port + err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, + conn->self_scope_id, &conn->local_ap2_control_port, + &conn->ap2_control_socket); + if (err) { + die("Error %d: could not find a UDP port to use as an ap2_control port", err); } + debug(2, "Connection %d: UDP control port opened: %u.", conn->connection_number, + conn->local_ap2_control_port); + + pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn); + + // get the DACP-ID and Active Remote for remote control stuff + + char *ar = msg_get_header(req, "Active-Remote"); + if (ar) { + debug(3, "Connection %d: SETUP AP2 -- Active-Remote string seen: \"%s\".", + conn->connection_number, ar); + // get the active remote + if (conn->dacp_active_remote) // this is in case SETUP was previously called + free(conn->dacp_active_remote); + conn->dacp_active_remote = strdup(ar); +#ifdef CONFIG_METADATA + send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); +#endif + } else { + debug(1, "Connection %d: SETUP AP2 no Active-Remote information the SETUP Record.", + conn->connection_number); + if (conn->dacp_active_remote) { // this is in case SETUP was previously called + free(conn->dacp_active_remote); + conn->dacp_active_remote = NULL; + } + } + + ar = msg_get_header(req, "DACP-ID"); + if (ar) { + debug(3, "Connection %d: SETUP AP2 -- DACP-ID string seen: \"%s\".", + conn->connection_number, ar); + if (conn->dacp_id) // this is in case SETUP was previously called + free(conn->dacp_id); + conn->dacp_id = strdup(ar); +#ifdef CONFIG_METADATA + send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); +#endif + } else { + debug(1, "Connection %d: SETUP AP2 doesn't include DACP-ID string information.", + conn->connection_number); + if (conn->dacp_id) { // this is in case SETUP was previously called + free(conn->dacp_id); + conn->dacp_id = NULL; + } + } + + // now, get the type of the stream. + item = plist_dict_get_item(stream0, "type"); + item_value = 0; + plist_get_uint_val(item, &item_value); + + switch (item_value) { + case 96: { + debug(1, "Connection %d. AP2 Realtime Audio Stream.", conn->connection_number); + debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req); + // get_play_lock(conn); + conn->airplay_stream_type = realtime_stream; + // bind a new UDP port and get a socket + conn->local_realtime_audio_port = 0; // any port + err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, + conn->self_scope_id, &conn->local_realtime_audio_port, + &conn->realtime_audio_socket); + if (err) { + die("Error %d: could not find a UDP port to use as a realtime_audio port", err); + } + debug(2, "Connection %d: UDP realtime audio port opened: %u.", conn->connection_number, + conn->local_realtime_audio_port); + + pthread_create(&conn->rtp_realtime_audio_thread, NULL, &rtp_realtime_audio_receiver, + (void *)conn); + + plist_dict_set_item(stream0dict, "type", plist_new_uint(96)); + plist_dict_set_item(stream0dict, "dataPort", + plist_new_uint(conn->local_realtime_audio_port)); + + conn->stream.type = ast_apple_lossless; + debug(3, "An ALAC stream has been detected."); + + // Set reasonable connection defaults + conn->stream.fmtp[0] = 96; + conn->stream.fmtp[1] = 352; + conn->stream.fmtp[2] = 0; + conn->stream.fmtp[3] = 16; + conn->stream.fmtp[4] = 40; + conn->stream.fmtp[5] = 10; + conn->stream.fmtp[6] = 14; + conn->stream.fmtp[7] = 2; + conn->stream.fmtp[8] = 255; + conn->stream.fmtp[9] = 0; + conn->stream.fmtp[10] = 0; + conn->stream.fmtp[11] = 44100; + + // set the parameters of the player (as distinct from the parameters of the decoder -- + // that's done later). + conn->max_frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. + conn->input_rate = conn->stream.fmtp[11]; + conn->input_num_channels = conn->stream.fmtp[7]; + conn->input_bit_depth = conn->stream.fmtp[3]; + conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); + debug(2, "Realtime Stream Play"); + activity_monitor_signify_activity(1); + player_prepare_to_play(conn); + player_play(conn); + + conn->rtp_running = 1; // hack! + } break; + case 103: { + debug(1, "Connection %d. AP2 Buffered Audio Stream.", conn->connection_number); + debug_log_rtsp_message(2, "Buffered Audio Stream SETUP incoming message", req); + // get_play_lock(conn); + conn->airplay_stream_type = buffered_stream; + // get needed stuff + + // bind a new TCP port and get a socket + conn->local_buffered_audio_port = 0; // any port + err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, + conn->self_scope_id, &conn->local_buffered_audio_port, + &conn->buffered_audio_socket); + if (err) { + die("SETUP on Connection %d: Error %d: could not find a TCP port to use as a " + "buffered_audio port", + conn->connection_number, err); + } + + debug(2, "Connection %d: TCP Buffered Audio port opened: %u.", conn->connection_number, + conn->local_buffered_audio_port); + + // hack. + conn->max_frames_per_packet = 352; // number of audio frames per packet. + conn->input_rate = 44100; // we are stuck with this for the moment. + conn->input_num_channels = 2; + conn->input_bit_depth = 16; + conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); + activity_monitor_signify_activity(1); + player_prepare_to_play( + conn); // get capabilities of DAC before creating the buffered audio thread + + pthread_create(&conn->rtp_buffered_audio_thread, NULL, &rtp_buffered_audio_processor, + (void *)conn); + + plist_dict_set_item(stream0dict, "type", plist_new_uint(103)); + plist_dict_set_item(stream0dict, "dataPort", + plist_new_uint(conn->local_buffered_audio_port)); + plist_dict_set_item(stream0dict, "audioBufferSize", + plist_new_uint(conn->ap2_audio_buffer_size)); + + // this should be cancelled by an activity_monitor_signify_activity(1) + // call in the SETRATEANCHORI handler, which should come up right away + activity_monitor_signify_activity(0); + player_play(conn); + + conn->rtp_running = 1; // hack! + } break; + default: + debug(1, "SETUP on Connection %d: Unhandled stream type %" PRIu64 ".", + conn->connection_number, item_value); + debug_log_rtsp_message(1, "Unhandled stream type incoming message", req); + } + + plist_dict_set_item(stream0dict, "controlPort", plist_new_uint(conn->local_ap2_control_port)); + + plist_array_append_item(streams_array, stream0dict); + plist_dict_set_item(setupResponsePlist, "streams", streams_array); + resp->respcode = 200; + } else if (conn->airplay_stream_category == remote_control_stream) { debug(2, "Connection %d (RC): SETUP: Remote Control Stream received from %s.", conn->connection_number, conn->client_ip_string); From 80316d20bd3f27ecae4c9088b42b760a6b602147 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:31:46 +0100 Subject: [PATCH 40/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 8ce197c1..20c9b4de 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,8 @@ +Version 4.1-dev-738-g9f7584eb +==== +**Enhancement** +* It seems that this missing session key issue discovered and discussed below is a transient problem: some client apps omit the session key occasionally but include it the rest of the time. So, to make the problem a bit less intrusive for users, the way a missing session key is dealt with has been changed. The new arrangement is that instead of dropping the AirPlay connection completely as noted below, the audio is simply skipped. From the user's perspective, the music simply won't play, but the AirPlay connection won't be dropped. When they start it again, the session key will hopefully be present and the audio will play. Let's hope that this will be less disruptive for users and that this issue goes away as clients are updated. Thanks again to [Mike](https://github.com/xska2) for his help with this in [#1551](https://github.com/mikebrady/shairport-sync/issues/1551). + Version 4.1-dev-735-g6a55774f ==== **Bug Fix** From 69337bb9887add6d32654a3a2526392b22c9ad56 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:34:08 +0100 Subject: [PATCH 41/42] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 20c9b4de..2963de68 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -48,7 +48,7 @@ Version 4.1-dev-701-g65daef30 * Fix a bug in the generation of version information from git tags. The fix is to use lightweight tags as well as annotated tags. GitHub marks releases with lightweight tags, so this should make version and release information correspond better. **Enhancement** -* Add an new Advanced Topic -- [Adjusting Sync](ADVANCED%20TOPICS/AdjustincSync.md) explaining how to compensate for amplifier delays such as might be found on TVs or AVRs. +* Add an new Advanced Topic -- [Adjusting Sync](ADVANCED%20TOPICS/AdjustingSync.md) explaining how to compensate for amplifier delays such as might be found on TVs or AVRs. Version 4.1-dev-694-g234c00ad ==== From 5aa860ff9869ffcea4747cfdd3a5e69569a57b80 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:35:58 +0100 Subject: [PATCH 42/42] Update AdjustingSync.md --- ADVANCED TOPICS/AdjustingSync.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ADVANCED TOPICS/AdjustingSync.md b/ADVANCED TOPICS/AdjustingSync.md index cc4627f8..26c2cae9 100644 --- a/ADVANCED TOPICS/AdjustingSync.md +++ b/ADVANCED TOPICS/AdjustingSync.md @@ -2,13 +2,13 @@ Sometimes, a timing difference can be heard, where the audio coming from the SPS-powered device is slightly ahead or slightly behind another device playing in synchrony. This can sometimes be heard as an irritating "echo". -This is usually due to audio amplifier delays. +This is usually due to audio amplifier delays: * If your audio output device (including the amplifier in a TV) includes any digital processing component, it probably delays audio while amplifying it. * If your output device is a HDMI-connected device such as a TV or an AV Receiver (AVR), it will almost certainly delay audio by anything up to several hundred milliseconds. -In these circumstances, if the output from the SPS device is amplified by a conventional analog-only HiFi amplifier – which has almost no delay – it will be early by comparison with audio coming the other device. +In these circumstances, if the output from the SPS device is amplified by a conventional analog-only HiFi amplifier – which has almost no delay – it will be early by comparison with audio coming from the other device. Conversely, if the output from the SPS device is passed through an AVR, then it could be late by comparison with audio amplified by a conventional audio amplifier. @@ -16,7 +16,7 @@ The fix for this is to get Shairport Sync to compensate for delays by providing The setting to look for is in the `general` section of the Shairport Sync configuration file and is called `audio_backend_latency_offset_in_seconds`. By default it is `0.0` seconds. -To delay the output from the SPS device by 100 milliseconds (0.1 seconds), set the `audio_backend_latency_offset_in_seconds` to `0.1`, so that audio is provided to your output device 100 milliseconds later than nominal synchronisation time. +For example, to delay the output from the SPS device by 100 milliseconds (0.1 seconds), set the `audio_backend_latency_offset_in_seconds` to `0.1`, so that audio is provided to your output device 100 milliseconds later than nominal synchronisation time. Similarly, to get the output from the SPS device 50 milliseconds (0.05 seconds) early, set the `audio_backend_latency_offset_in_seconds` to `-0.05`, so that audio is provided to your output device 50 milliseconds earlier than nominal synchronisation time.