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:
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:
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.