From 9edf20d2b3448fb4ab2c8a9c7afc8559d00e7ee8 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 23 Feb 2020 09:57:30 +0000
Subject: [PATCH 001/180] Update OPENBSD.md
---
OPENBSD.md | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/OPENBSD.md b/OPENBSD.md
index 219d7402..a26598bd 100644
--- a/OPENBSD.md
+++ b/OPENBSD.md
@@ -10,7 +10,15 @@ This build was done on a default build of `OpenBSD 6.2 GENERIC.MP#134 amd64`. Fo
```
https://ftp.openbsd.org/pub/OpenBSD
```
-Next, although it may not always be necessary, [update the packages](https://unix.stackexchange.com/questions/23579/how-to-apply-updates-on-openbsd-netbsd-and-freebsd).
+You might have a [closer or faster](https://www.openbsd.org/ftp.html) repository.
+
+Next, although it may not always be necessary, apply any outstanding system updates and [update the packages](https://unix.stackexchange.com/questions/23579/how-to-apply-updates-on-openbsd-netbsd-and-freebsd).
+
+First, update the system:
+```
+syspatch
+```
+Now update the packages:
```
# pkg_add -Uu
```
@@ -31,7 +39,7 @@ Install the following packages (e.g. using `pkg_add` in superuser mode) needed t
```
# pkg_add autoconf automake popt libconfig git
```
-Add the relevant shell variable definitions for Autoconf and Automake -- they could be placed in the user's `.profile` file to be automatically executed at login:
+Note the versions of `autoconf` and `automake` you choose (`2.69` and `1.15` at the time of writing) and add them as shell variable definitions for -- they could be placed in the user's `.profile` file to be automatically executed at login:
```
export AUTOCONF_VERSION=2.69
export AUTOMAKE_VERSION=1.15
@@ -59,5 +67,5 @@ There is no make install yet -- you're on your own.
Using the `sndio` backend
----
-The `sndio` back end does not have a hardware volume control facility.
+The `sndio` back end does not yet have a hardware volume control facility.
You should set the volume to maximum before use, using, for example, the `mixerctl` command.
From 2a14b92218a3f89a411ad580dd194317ebb0ceb5 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 23 Feb 2020 09:59:10 +0000
Subject: [PATCH 002/180] Update OPENBSD.md
---
OPENBSD.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/OPENBSD.md b/OPENBSD.md
index a26598bd..8a61808f 100644
--- a/OPENBSD.md
+++ b/OPENBSD.md
@@ -6,17 +6,17 @@ Unlike FreeBSD, it seems that OpenBSD does not use the directory `/usr/local/etc
General
----
-This build was done on a default build of `OpenBSD 6.2 GENERIC.MP#134 amd64`. Following [this guide](https://www.openbsd.org/faq/faq15.html), `/etc/installurl` was created with the contents:
+This build was done on a default build of `OpenBSD 6.2 GENERIC.MP#134 amd64`. Following [this guide](https://www.openbsd.org/faq/faq15.html), `/etc/installurl` was created with the to refer to the standard repository:
```
https://ftp.openbsd.org/pub/OpenBSD
```
-You might have a [closer or faster](https://www.openbsd.org/ftp.html) repository.
+You might have a [closer or faster](https://www.openbsd.org/ftp.html) repository to use instead.
Next, although it may not always be necessary, apply any outstanding system updates and [update the packages](https://unix.stackexchange.com/questions/23579/how-to-apply-updates-on-openbsd-netbsd-and-freebsd).
First, update the system:
```
-syspatch
+# syspatch
```
Now update the packages:
```
From f33a4e03948eadde467998298a287895c060296e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 23 Feb 2020 10:02:29 +0000
Subject: [PATCH 003/180] Update OPENBSD.md
---
OPENBSD.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/OPENBSD.md b/OPENBSD.md
index 8a61808f..9c0a597f 100644
--- a/OPENBSD.md
+++ b/OPENBSD.md
@@ -1,12 +1,12 @@
Shairport Sync on OpenBSD
----
-This is an initial note about installing Shairport Sync on OpenBSD. Shairport Sync compiles and runs natively on OpenBSD using the `sndio` back end.
+This is a note about installing Shairport Sync on OpenBSD. Shairport Sync compiles and runs natively on OpenBSD using the `sndio` back end.
Unlike FreeBSD, it seems that OpenBSD does not use the directory `/usr/local/etc` as a system configuration directory ("`sysconfdir`") but follows the same practice as Linux in using `/etc` as the default `sysconfdir`.
General
----
-This build was done on a default build of `OpenBSD 6.2 GENERIC.MP#134 amd64`. Following [this guide](https://www.openbsd.org/faq/faq15.html), `/etc/installurl` was created with the to refer to the standard repository:
+This build was done on a default build of `OpenBSD 6.6 GENERIC.MP#5 amd64`. Following [this guide](https://www.openbsd.org/faq/faq15.html), `/etc/installurl` was created with the to refer to the standard repository:
```
https://ftp.openbsd.org/pub/OpenBSD
```
@@ -39,10 +39,10 @@ Install the following packages (e.g. using `pkg_add` in superuser mode) needed t
```
# pkg_add autoconf automake popt libconfig git
```
-Note the versions of `autoconf` and `automake` you choose (`2.69` and `1.15` at the time of writing) and add them as shell variable definitions for -- they could be placed in the user's `.profile` file to be automatically executed at login:
+Note the versions of `autoconf` and `automake` you choose (`2.69` and `1.16` at the time of writing) and add them as shell variable definitions for -- they could be placed in the user's `.profile` file to be automatically executed at login:
```
export AUTOCONF_VERSION=2.69
-export AUTOMAKE_VERSION=1.15
+export AUTOMAKE_VERSION=1.16
```
Now, download Shairport Sync from GitHub:
```
@@ -61,7 +61,7 @@ $ ./shairport-sync -V
```
This will execute the application and it will return its version information and terminate, for example:
```
-3.2-libdaemon-OpenSSL-Avahi-sndio-sysconfdir:/etc
+3.3.6-libdaemon-OpenSSL-Avahi-sndio-sysconfdir:/etc
```
There is no make install yet -- you're on your own.
From 3d89c688a40e40cf01d288058ca21c1b402ae369 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 23 Feb 2020 10:05:55 +0000
Subject: [PATCH 004/180] Update OPENBSD.md
---
OPENBSD.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/OPENBSD.md b/OPENBSD.md
index 9c0a597f..067de9f6 100644
--- a/OPENBSD.md
+++ b/OPENBSD.md
@@ -52,7 +52,8 @@ $ cd shairport-sync
Next, configure and compile shairport-sync:
```
$ autoreconf -fi
-$ ./configure --sysconfdir=/etc --with-avahi --with-ssl=openssl --with-libdaemon --with-sndio --with-os=openbsd
+$ ./configure --sysconfdir=/etc --with-avahi --with-ssl=openssl --with-libdaemon \
+ --with-sndio --with-os=openbsd
$ make
```
The application is called `shairport-sync`. Check that it's running correctly by executing the following command:
From 38afb49f517a71f00f9fe1b02dc571bd8ddfb5f4 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 23 Feb 2020 10:09:11 +0000
Subject: [PATCH 005/180] Update OPENBSD.md
---
OPENBSD.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/OPENBSD.md b/OPENBSD.md
index 067de9f6..28f163a0 100644
--- a/OPENBSD.md
+++ b/OPENBSD.md
@@ -1,6 +1,6 @@
Shairport Sync on OpenBSD
----
-This is a note about installing Shairport Sync on OpenBSD. Shairport Sync compiles and runs natively on OpenBSD using the `sndio` back end.
+This is a note about installing Shairport Sync on OpenBSD. Shairport Sync compiles and runs natively on OpenBSD using the `sndio` back end. For information on `sndio` please follow [this link](http://www.sndio.org).
Unlike FreeBSD, it seems that OpenBSD does not use the directory `/usr/local/etc` as a system configuration directory ("`sysconfdir`") but follows the same practice as Linux in using `/etc` as the default `sysconfdir`.
From d66189ed62e71723eed7a111491e755da67b1440 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 23 Feb 2020 10:57:09 +0000
Subject: [PATCH 006/180] Update OPENBSD.md
---
OPENBSD.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/OPENBSD.md b/OPENBSD.md
index 28f163a0..16465e6b 100644
--- a/OPENBSD.md
+++ b/OPENBSD.md
@@ -1,6 +1,6 @@
Shairport Sync on OpenBSD
----
-This is a note about installing Shairport Sync on OpenBSD. Shairport Sync compiles and runs natively on OpenBSD using the `sndio` back end. For information on `sndio` please follow [this link](http://www.sndio.org).
+This is a note about installing Shairport Sync on OpenBSD. Shairport Sync compiles and runs natively on OpenBSD using the `sndio` back end. For general information on `sndio` please follow [this link](http://www.sndio.org).
Unlike FreeBSD, it seems that OpenBSD does not use the directory `/usr/local/etc` as a system configuration directory ("`sysconfdir`") but follows the same practice as Linux in using `/etc` as the default `sysconfdir`.
From 4c6628abe47cc3f1cb4ced4ba0112ca124df6be9 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 27 Feb 2020 09:48:22 +0000
Subject: [PATCH 007/180] Update TROUBLESHOOTING.md
---
TROUBLESHOOTING.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index fd500e60..966c0c71 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -4,9 +4,9 @@ The installation and setup of Shairport Sync is straightforward on recent Linux
In this brief document will be listed some problems and some solutions, some provided by other users.
-1. Before starting, ensure that your software is up-to-date.
-2. Set the `interpolation` in the `general` section of the configuration file to `basic` as the `soxr` setting can cause lower-powered devices to bog down at critical times, e.g. see [this report](https://github.com/mikebrady/shairport-sync/issues/631#issuecomment-366305203).
-
+1. Before starting, ensure that your software is up-to-date. This document always refers to the most recent version of Shairport Sync -- see [here](https://github.com/mikebrady/shairport-sync/releases) for information about the most recent release.
+2. If you have set `interpolation` in the `general` section of the configuration file to to `soxr`, comment it out or set it to `auto` or `basic` as the `soxr` setting can cause lower-powered devices to bog down at critical times, e.g. see [this report](https://github.com/mikebrady/shairport-sync/issues/631#issuecomment-366305203).
+3. Ensure the volume setting of your output device is set to some reasonable value and ensure it is not muted. For ALSA devices, the `alsamixer` command-line tool is very good for this. For other sound systems, please consult the relevant documentation.
### WiFi adapter running in power-saving / low-power mode
From 719bdbf33ee11282110283d6cc0510b189964d71 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 19 May 2020 16:13:32 +0100
Subject: [PATCH 008/180] Update REPORTING ISSUES.md
---
REPORTING ISSUES.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/REPORTING ISSUES.md b/REPORTING ISSUES.md
index 5c502091..61895508 100644
--- a/REPORTING ISSUES.md
+++ b/REPORTING ISSUES.md
@@ -16,7 +16,7 @@ If your system uses `systemd`, as most recent Linuxes do, try using:
```
$ sudo systemctl status shairport-sync
```
-You might get something lke this:
+You might get something like this:
```
● shairport-sync.service - Shairport Sync - AirPlay Audio Receiver
Loaded: loaded (/lib/systemd/system/shairport-sync.service; disabled; vendor preset: enabled)
From 0606a76bd88b68c1d9af29fb9ac4120d7a215650 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 21 May 2020 10:27:36 +0100
Subject: [PATCH 009/180] Update Fedora Version Number
---
shairport-sync.spec | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/shairport-sync.spec b/shairport-sync.spec
index 7755c8fe..ed467df2 100644
--- a/shairport-sync.spec
+++ b/shairport-sync.spec
@@ -1,5 +1,5 @@
Name: shairport-sync
-Version: 3.3.5
+Version: 3.3.6
Release: 1%{?dist}
Summary: AirTunes emulator. Multi-Room with Audio Synchronisation
# MIT licensed except for tinysvcmdns under BSD,
From d48d9698d5604a773a5822424cf21e7117c14e65 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 21 May 2020 11:00:52 +0100
Subject: [PATCH 010/180] Update FEDORA.md
---
FEDORA.md | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/FEDORA.md b/FEDORA.md
index bb57d58b..37bcbd46 100644
--- a/FEDORA.md
+++ b/FEDORA.md
@@ -4,12 +4,13 @@ Fedora Installation Guide
Install the toolchain and pre-requisites, if necessary:
```
# yum install make automake gcc gcc-c++ kernel-devel
-# yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-devel openssl-devel popt-devel soxr-devel
+# yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel \
+ libdaemon-devel openssl-devel popt-devel soxr-devel rpmbuild
```
-Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.6:
+Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. Alternatively, download a tarball of the `master` branch, as in this example:
```
-$ wget -O shairport-sync-2.6.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.6.tar.gz
-$ rpmbuild -ta shairport-sync-2.6.tar.gz
+$ wget -O shairport-sync-master.tar.gz https://github.com/mikebrady/shairport-sync/archive/master.tar.gz
+$ rpmbuild -ta shairport-sync-master.tar.gz
```
The `-ta` means "build all from this tarball".
From 15e3dbe4590703768849b15a452d4101b1550a2e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 21 May 2020 15:20:31 +0100
Subject: [PATCH 011/180] Update FEDORA.md
---
FEDORA.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/FEDORA.md b/FEDORA.md
index 37bcbd46..289a3bfb 100644
--- a/FEDORA.md
+++ b/FEDORA.md
@@ -16,9 +16,9 @@ The `-ta` means "build all from this tarball".
The RPM will be built in a directory and will have a pathname like, for example, `~/rpmbuild/RPMS/i686/shairport-sync-2.6-1.fc22.i686.rpm` You should then install it with (for this example):
```
-# rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.6-1.fc22.i686.rpm
+$ sudo rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.6-1.fc22.i686.rpm
```
-You may have to manually create the directory `/var/shairport-sync` for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see ("Configuring Shairport Sync")[https://github.com/mikebrady/shairport-sync/blob/master/README.md#configuring-shairport-sync]), enable and start the service with:
+You may have to manually create the directory `/var/shairport-sync` beforehand for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see ("Configuring Shairport Sync")[https://github.com/mikebrady/shairport-sync/blob/master/README.md#configuring-shairport-sync]), enable and start the service with:
```
# systemctl enable shairport-sync.service
# systemctl start shairport-sync.service
From 058387aa2664c43d95832946d45515d6e8da8c35 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 21 May 2020 15:22:05 +0100
Subject: [PATCH 012/180] Update FEDORA.md
---
FEDORA.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/FEDORA.md b/FEDORA.md
index 289a3bfb..0ee75612 100644
--- a/FEDORA.md
+++ b/FEDORA.md
@@ -18,7 +18,7 @@ The RPM will be built in a directory and will have a pathname like, for example,
```
$ sudo rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.6-1.fc22.i686.rpm
```
-You may have to manually create the directory `/var/shairport-sync` beforehand for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see ("Configuring Shairport Sync")[https://github.com/mikebrady/shairport-sync/blob/master/README.md#configuring-shairport-sync]), enable and start the service with:
+You may have to manually create the directory `/var/shairport-sync` beforehand for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see [Configuring Shairport Sync](https://github.com/mikebrady/shairport-sync/blob/master/README.md#configuring-shairport-sync)), enable and start the service with:
```
# systemctl enable shairport-sync.service
# systemctl start shairport-sync.service
From 0f16ec4af72eee2feaad9c755b254000ce3c63a5 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 21 May 2020 15:23:46 +0100
Subject: [PATCH 013/180] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 48bd955c..4722fc01 100644
--- a/README.md
+++ b/README.md
@@ -314,7 +314,7 @@ For the ALSA backend you may need to (c) specify the output device to use and (d
Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card.
-Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room` asd give the service the password `secret`, then you should do the following:
+Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room` and give the service the password `secret`, then you should do the following:
```
general =
From 88c4a25a0271f461220881b6500ef3fbb08e9d32 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 23 May 2020 16:14:13 +0100
Subject: [PATCH 014/180] Update FEDORA.md
---
FEDORA.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/FEDORA.md b/FEDORA.md
index 0ee75612..7690dbd6 100644
--- a/FEDORA.md
+++ b/FEDORA.md
@@ -1,6 +1,9 @@
Fedora Installation Guide
-----
-
+Update everything...
+```
+# yum update
+```
Install the toolchain and pre-requisites, if necessary:
```
# yum install make automake gcc gcc-c++ kernel-devel
From 5304ad25476ae283c1ccf6fe63bb0bdfd11570a4 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 30 May 2020 18:42:36 +0100
Subject: [PATCH 015/180] Correct a typo.
Thanks to [agneevX](https://github.com/agneevX).
---
INSTALL.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/INSTALL.md b/INSTALL.md
index 2d21b9c5..714eb0fd 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -34,7 +34,7 @@ Remove it as follows:
Do this until no more copies of `shairport-sync` are found.
### Remove Old Startup Scripts
-You should also remove the startup script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
+You should also remove the startup script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed if necessary.
### Reboot after Cleaning Up
If you removed any installations of Shairport Sync or any of its startup script files in the last two steps, you should reboot.
From aa1e632e0dc6f84de5e46bc2589a0a1d05c98ee4 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 1 Jun 2020 09:49:48 +0100
Subject: [PATCH 016/180] Update INSTALL.md
---
INSTALL.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/INSTALL.md b/INSTALL.md
index 714eb0fd..964be275 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -34,7 +34,7 @@ Remove it as follows:
Do this until no more copies of `shairport-sync` are found.
### Remove Old Startup Scripts
-You should also remove the startup script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed if necessary.
+You should also remove the startup script files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed if necessary.
### Reboot after Cleaning Up
If you removed any installations of Shairport Sync or any of its startup script files in the last two steps, you should reboot.
From a15fa1b756f1575c02e3f82b55bc8498493a791d Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Mon, 1 Jun 2020 09:51:27 +0100
Subject: [PATCH 017/180] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4722fc01..d73a9877 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ If you wish to build and install the latest version of Shairport Sync on Debian,
You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
-You should also remove the startup script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
+You should also remove the startup script files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
If you removed any installations of Shairport Sync or any of its startup script files, you should reboot.
From a77a6e5aaea3f6fad0c283cde5b4b1fe8006c7a5 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Fri, 12 Jun 2020 09:49:27 +0100
Subject: [PATCH 018/180] Update REPORTING ISSUES.md
---
REPORTING ISSUES.md | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/REPORTING ISSUES.md b/REPORTING ISSUES.md
index 61895508..ce0db535 100644
--- a/REPORTING ISSUES.md
+++ b/REPORTING ISSUES.md
@@ -2,15 +2,15 @@
Reporting Issues and Making Pull Requests
====
-Pull Requests
-----
-If you would like to contribute to the development of Shairport Sync, please make you changes to the `development` branch and make a pull request.
-
-Changes and additions in the development branch make their way eventually to the `master` branch.
-
Issue Reports
----
-Issue reports are welcome, but before you report an issue, please have a look though the existing [issues](https://github.com/mikebrady/shairport-sync/issues), both open and closed, and check for hints in the [TROUBLESHOOTING](TROUBLESHOOTING.md) page. It would be great to give some details of the device and version of Linux or FreeBSD in use along with the version of Shairport Sync you are using (use `$ shairport-sync -V` to get this). Then, if possible, some diagnostic information from the log or logfile would be useful.
+Issue reports are welcome, but before you report an issue, please have a look though the existing [issues](https://github.com/mikebrady/shairport-sync/issues), both open and closed, and check for hints in the [TROUBLESHOOTING](TROUBLESHOOTING.md) page.
+
+It would be great to give some details of the device and version of Linux or FreeBSD in use along with the version of Shairport Sync you are using. Use:
+```
+$ shairport-sync -V
+```
+Then, if possible, some diagnostic information from the log or logfile would be useful.
If your system uses `systemd`, as most recent Linuxes do, try using:
```
@@ -41,3 +41,10 @@ code or log file entries
```
\`\`\`
+Pull Requests
+----
+If you would like to contribute to the development of Shairport Sync, please make you changes to the `development` branch and make a pull request.
+
+Changes and additions in the development branch make their way eventually to the `master` branch.
+
+
From 0fe89a9e9157ce3fcafd160979248393f1f7a905 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 3 Jul 2020 16:15:04 +0100
Subject: [PATCH 019/180] Add initial support for Docker Hub / GitHub Actions
integration.
---
.github/workflows/docker.yml | 69 ++++++++++++++++++++++++
Dockerfile | 101 +++++++++++++++++++++++++++++++++++
docker/start.sh | 13 +++++
3 files changed, 183 insertions(+)
create mode 100644 .github/workflows/docker.yml
create mode 100644 Dockerfile
create mode 100755 docker/start.sh
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 00000000..4160499e
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,69 @@
+name: Docker
+
+# Thanks to Ryan Govostes for this workflow
+
+on:
+ push:
+ # Publish `master` as Docker `latest` image.
+ branches:
+ - master
+
+ # Publish `1.2.3` tags as releases.
+ tags:
+ - '*'
+
+ # Run a test build for any PRs.
+ pull_request:
+
+env:
+ IMAGE_NAME: mikebrady/shairport-sync
+# DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
+ DOCKER_PLATFORMS: linux/arm/v6
+ DOCKER_HUB_USER: mikebrady
+
+jobs:
+ # Build the container image for multiple architectures
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ # This unusual setup is due to limitations with `docker buildx`.
+ # See: https://github.com/crazy-max/ghaction-docker-buildx/issues/134
+ - name: Prepare
+ id: prepare
+ run: |
+ # Map git ref branch or tag name to Docker tag version
+ VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
+ [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
+ [ "$VERSION" == "master" ] && VERSION=latest && SHAIRPORT_SYNC_BRANCH=master
+
+ # If tagged with a version, build the same version of Shairport Sync
+ [ "$VERSION" != "latest" ] && SHAIRPORT_SYNC_BRANCH=tags/$VERSION
+
+ # Output build arguments for downstream steps
+ echo ::set-output name=buildx_args::\
+ --platform ${DOCKER_PLATFORMS} \
+ --build-arg SHAIRPORT_SYNC_BRANCH=${SHAIRPORT_SYNC_BRANCH} \
+ --tag ${IMAGE_NAME}:${VERSION} \
+ .
+
+ - uses: actions/checkout@v2
+ - uses: crazy-max/ghaction-docker-buildx@v1.2.1
+
+ - name: Build
+ run: |
+ docker buildx build \
+ --output type=image,push=false \
+ ${{ steps.prepare.outputs.buildx_args }}
+
+ - name: Log into registry
+ if: github.event_name == 'push'
+ run: echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login --username $DOCKER_HUB_USER --password-stdin
+
+ - name: Push to registry
+ if: github.event_name == 'push'
+ run: |
+ docker buildx build \
+ --output type=image,push=true \
+ ${{ steps.prepare.outputs.buildx_args }}
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..1ce42121
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,101 @@
+FROM alpine AS builder-base
+# General Build System:
+RUN apk -U add \
+ git \
+ build-base \
+ autoconf \
+ automake \
+ libtool \
+ dbus \
+ su-exec \
+ alsa-lib-dev \
+ libdaemon-dev \
+ popt-dev \
+ mbedtls-dev \
+ soxr-dev \
+ avahi-dev \
+ libconfig-dev \
+ libsndfile-dev \
+ mosquitto-dev \
+ xmltoman
+
+# ALAC Build System:
+FROM builder-base AS builder-alac
+
+RUN git clone https://github.com/mikebrady/alac
+WORKDIR alac
+RUN autoreconf -fi
+RUN ./configure
+RUN make
+RUN make install
+
+# Shairport Sync Build System:
+FROM builder-base AS builder-sps
+
+# This may be modified by the Github Action Workflow.
+ARG SHAIRPORT_SYNC_BRANCH=master
+
+COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
+COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
+COPY --from=builder-alac /usr/local/include /usr/local/include
+
+RUN git clone https://github.com/mikebrady/shairport-sync
+WORKDIR shairport-sync
+RUN git checkout "$SHAIRPORT_SYNC_BRANCH"
+RUN autoreconf -fi
+RUN ./configure \
+ --with-alsa \
+ --with-dummy \
+ --with-pipe \
+ --with-stdout \
+ --with-avahi \
+ --with-ssl=mbedtls \
+ --with-soxr \
+ --sysconfdir=/etc \
+ --with-dbus-interface \
+ --with-mpris-interface \
+ --with-mqtt-client \
+ --with-apple-alac \
+ --with-convolution
+RUN make -j $(nproc)
+RUN make install
+
+# Shairport Sync Runtime System:
+FROM alpine
+
+RUN apk add \
+ alsa-lib \
+ dbus \
+ popt \
+ glib \
+ mbedtls \
+ soxr \
+ avahi \
+ libconfig \
+ libsndfile \
+ mosquitto-libs \
+ su-exec \
+ libgcc \
+ libgc++
+
+RUN rm -rf /lib/apk/db/*
+
+COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
+COPY --from=builder-sps /etc/shairport-sync* /etc/
+COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-dbus.conf /etc/dbus-1/system.d/
+COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-mpris.conf /etc/dbus-1/system.d/
+COPY --from=builder-sps /usr/local/bin/shairport-sync /usr/local/bin/shairport-sync
+
+# Create non-root user for running the container -- running as the user 'shairport-sync' also allows
+# Shairport Sync to provide the D-Bus and MPRIS interfaces within the container
+
+RUN addgroup shairport-sync
+RUN adduser -D shairport-sync -G shairport-sync
+
+# Add the shairport-sync user to the pre-existing audio group, which has ID 29, for access to the ALSA stuff
+RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio
+
+COPY docker/* /
+
+ENTRYPOINT [ "/start.sh" ]
+
diff --git a/docker/start.sh b/docker/start.sh
new file mode 100755
index 00000000..40d408d3
--- /dev/null
+++ b/docker/start.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+set -e
+
+rm -rf /var/run/dbus.pid
+#mkdir -p /var/run/dbus
+
+dbus-uuidgen --ensure
+dbus-daemon --system
+
+avahi-daemon --daemonize --no-chroot
+
+shairport-sync $@
From db813e049fe2133457d0603109ba56cde4b67a72 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 3 Jul 2020 16:26:16 +0100
Subject: [PATCH 020/180] Add initial support for Docker Hub / GitHub Actions
integration.
---
.github/workflows/docker.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 4160499e..e2e6bc8e 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -1,6 +1,6 @@
name: Docker
-# Thanks to Ryan Govostes for this workflow
+# Thanks to Ryan Govostes for this workflow.
on:
push:
From a90e5b4cf8bcb5c436d35c5010e3566d2ca3d8d4 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 3 Jul 2020 16:41:22 +0100
Subject: [PATCH 021/180] Push images for x86, x86-64, arm32 (v6) and arm64
architectures to Docker Hub using GitHub Actions
---
.github/workflows/docker.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index e2e6bc8e..f6f983e3 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -18,7 +18,7 @@ on:
env:
IMAGE_NAME: mikebrady/shairport-sync
# DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
- DOCKER_PLATFORMS: linux/arm/v6
+ DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm64
DOCKER_HUB_USER: mikebrady
jobs:
From 37cd5ab33c107441c254c8eeeadea98e098d8dd0 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 3 Jul 2020 17:10:09 +0100
Subject: [PATCH 022/180] Update RELEASENOTES.md
---
RELEASENOTES.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 973433a5..3bb0a374 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -1,5 +1,8 @@
Please see the [Release Notes for 3.3](https://github.com/mikebrady/shairport-sync/releases/tag/3.3).
+**Enhancements**
+* Docker Hub integration. From now on, prebuilt Docker images should be available on the Docker Hub at https://hub.docker.com/r/mikebrady/shairport-sync.
+
Version 3.3.6
====
**New Features**
From 94122c9eea9d5087bd716aee3d834b73950f9112 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 3 Jul 2020 17:13:50 +0100
Subject: [PATCH 023/180] Don't build Docker images for a pull request.
---
.github/workflows/docker.yml | 3 ---
1 file changed, 3 deletions(-)
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index f6f983e3..281b631e 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -12,9 +12,6 @@ on:
tags:
- '*'
- # Run a test build for any PRs.
- pull_request:
-
env:
IMAGE_NAME: mikebrady/shairport-sync
# DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
From 2bb559aa71643b00026ee125b8025769755f0a8e Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 3 Jul 2020 18:34:25 +0100
Subject: [PATCH 024/180] Update README.md
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index d73a9877..a6821f31 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,8 @@ Building And Installing
---------------------
See [here](https://github.com/mikebrady/shairport-sync/blob/master/INSTALL.md) for simple installation instructions for modern Linux systems like Rapsberry Pis.
+**Docker:** Shairport Sync is available as a prebuilt Docker image at https://hub.docker.com/r/mikebrady/shairport-sync. Alternatively, you can roll your own using the Dockerfile provided. Note that, at present, the Shairport Sync Docker image does not work on a Mac, due to limitations in the Docker system.
+
Shairport Sync may already be available as a package in your Linux distribution (search for `shairport-sync` – the package named `shairport` is a different program). Packages are available on recent versions of Debian, Ubuntu, Arch, OpenWrt and possibly more:
**Ubuntu:** A `shairport-sync` installer package is available for Ubuntu. Additionally, a Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson.
From b2caafe34e875b0947cfd72f30f8aecfd07a489a Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 4 Jul 2020 10:28:02 +0100
Subject: [PATCH 025/180] Only allow steps of workflow to execute if running
out of the mikebrady/shairport-sync GitHub repository
---
.github/workflows/docker.yml | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 281b631e..cdd0b6f6 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -2,6 +2,9 @@ name: Docker
# Thanks to Ryan Govostes for this workflow.
+# Using 'if:' conditions, the script will not execute the steps
+# unless it is running out of the 'mikebrady/shairport-sync' GitHub repository.
+
on:
push:
# Publish `master` as Docker `latest` image.
@@ -28,6 +31,7 @@ jobs:
# See: https://github.com/crazy-max/ghaction-docker-buildx/issues/134
- name: Prepare
id: prepare
+ if: github.repository == 'mikebrady/shairport-sync'
run: |
# Map git ref branch or tag name to Docker tag version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
@@ -48,17 +52,18 @@ jobs:
- uses: crazy-max/ghaction-docker-buildx@v1.2.1
- name: Build
+ if: github.repository == 'mikebrady/shairport-sync'
run: |
docker buildx build \
--output type=image,push=false \
${{ steps.prepare.outputs.buildx_args }}
- name: Log into registry
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && github.repository == 'mikebrady/shairport-sync'
run: echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login --username $DOCKER_HUB_USER --password-stdin
- name: Push to registry
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && github.repository == 'mikebrady/shairport-sync'
run: |
docker buildx build \
--output type=image,push=true \
From 8e46117c7cfe3c1fb97661b883b28786f8a46a6a Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Jul 2020 09:02:25 +0100
Subject: [PATCH 026/180] Remove the Dockerfile and GitHub actions workflow.
---
.github/workflows/docker.yml | 71 ------------------------
Dockerfile | 101 -----------------------------------
docker/start.sh | 13 -----
3 files changed, 185 deletions(-)
delete mode 100644 .github/workflows/docker.yml
delete mode 100644 Dockerfile
delete mode 100755 docker/start.sh
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
deleted file mode 100644
index cdd0b6f6..00000000
--- a/.github/workflows/docker.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-name: Docker
-
-# Thanks to Ryan Govostes for this workflow.
-
-# Using 'if:' conditions, the script will not execute the steps
-# unless it is running out of the 'mikebrady/shairport-sync' GitHub repository.
-
-on:
- push:
- # Publish `master` as Docker `latest` image.
- branches:
- - master
-
- # Publish `1.2.3` tags as releases.
- tags:
- - '*'
-
-env:
- IMAGE_NAME: mikebrady/shairport-sync
-# DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
- DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm64
- DOCKER_HUB_USER: mikebrady
-
-jobs:
- # Build the container image for multiple architectures
- build:
- runs-on: ubuntu-latest
-
- steps:
- # This unusual setup is due to limitations with `docker buildx`.
- # See: https://github.com/crazy-max/ghaction-docker-buildx/issues/134
- - name: Prepare
- id: prepare
- if: github.repository == 'mikebrady/shairport-sync'
- run: |
- # Map git ref branch or tag name to Docker tag version
- VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
- [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
- [ "$VERSION" == "master" ] && VERSION=latest && SHAIRPORT_SYNC_BRANCH=master
-
- # If tagged with a version, build the same version of Shairport Sync
- [ "$VERSION" != "latest" ] && SHAIRPORT_SYNC_BRANCH=tags/$VERSION
-
- # Output build arguments for downstream steps
- echo ::set-output name=buildx_args::\
- --platform ${DOCKER_PLATFORMS} \
- --build-arg SHAIRPORT_SYNC_BRANCH=${SHAIRPORT_SYNC_BRANCH} \
- --tag ${IMAGE_NAME}:${VERSION} \
- .
-
- - uses: actions/checkout@v2
- - uses: crazy-max/ghaction-docker-buildx@v1.2.1
-
- - name: Build
- if: github.repository == 'mikebrady/shairport-sync'
- run: |
- docker buildx build \
- --output type=image,push=false \
- ${{ steps.prepare.outputs.buildx_args }}
-
- - name: Log into registry
- if: github.event_name == 'push' && github.repository == 'mikebrady/shairport-sync'
- run: echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login --username $DOCKER_HUB_USER --password-stdin
-
- - name: Push to registry
- if: github.event_name == 'push' && github.repository == 'mikebrady/shairport-sync'
- run: |
- docker buildx build \
- --output type=image,push=true \
- ${{ steps.prepare.outputs.buildx_args }}
-
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 1ce42121..00000000
--- a/Dockerfile
+++ /dev/null
@@ -1,101 +0,0 @@
-FROM alpine AS builder-base
-# General Build System:
-RUN apk -U add \
- git \
- build-base \
- autoconf \
- automake \
- libtool \
- dbus \
- su-exec \
- alsa-lib-dev \
- libdaemon-dev \
- popt-dev \
- mbedtls-dev \
- soxr-dev \
- avahi-dev \
- libconfig-dev \
- libsndfile-dev \
- mosquitto-dev \
- xmltoman
-
-# ALAC Build System:
-FROM builder-base AS builder-alac
-
-RUN git clone https://github.com/mikebrady/alac
-WORKDIR alac
-RUN autoreconf -fi
-RUN ./configure
-RUN make
-RUN make install
-
-# Shairport Sync Build System:
-FROM builder-base AS builder-sps
-
-# This may be modified by the Github Action Workflow.
-ARG SHAIRPORT_SYNC_BRANCH=master
-
-COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
-COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
-COPY --from=builder-alac /usr/local/include /usr/local/include
-
-RUN git clone https://github.com/mikebrady/shairport-sync
-WORKDIR shairport-sync
-RUN git checkout "$SHAIRPORT_SYNC_BRANCH"
-RUN autoreconf -fi
-RUN ./configure \
- --with-alsa \
- --with-dummy \
- --with-pipe \
- --with-stdout \
- --with-avahi \
- --with-ssl=mbedtls \
- --with-soxr \
- --sysconfdir=/etc \
- --with-dbus-interface \
- --with-mpris-interface \
- --with-mqtt-client \
- --with-apple-alac \
- --with-convolution
-RUN make -j $(nproc)
-RUN make install
-
-# Shairport Sync Runtime System:
-FROM alpine
-
-RUN apk add \
- alsa-lib \
- dbus \
- popt \
- glib \
- mbedtls \
- soxr \
- avahi \
- libconfig \
- libsndfile \
- mosquitto-libs \
- su-exec \
- libgcc \
- libgc++
-
-RUN rm -rf /lib/apk/db/*
-
-COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
-COPY --from=builder-sps /etc/shairport-sync* /etc/
-COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-dbus.conf /etc/dbus-1/system.d/
-COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-mpris.conf /etc/dbus-1/system.d/
-COPY --from=builder-sps /usr/local/bin/shairport-sync /usr/local/bin/shairport-sync
-
-# Create non-root user for running the container -- running as the user 'shairport-sync' also allows
-# Shairport Sync to provide the D-Bus and MPRIS interfaces within the container
-
-RUN addgroup shairport-sync
-RUN adduser -D shairport-sync -G shairport-sync
-
-# Add the shairport-sync user to the pre-existing audio group, which has ID 29, for access to the ALSA stuff
-RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio
-
-COPY docker/* /
-
-ENTRYPOINT [ "/start.sh" ]
-
diff --git a/docker/start.sh b/docker/start.sh
deleted file mode 100755
index 40d408d3..00000000
--- a/docker/start.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-
-set -e
-
-rm -rf /var/run/dbus.pid
-#mkdir -p /var/run/dbus
-
-dbus-uuidgen --ensure
-dbus-daemon --system
-
-avahi-daemon --daemonize --no-chroot
-
-shairport-sync $@
From bdc2dca3ec485f8afec8a3b8d2d06550dd49a0ac Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Jul 2020 17:09:15 +0100
Subject: [PATCH 027/180] 3.3.7rc0
---
FREEBSD.md | 2 +-
README.md | 30 +-
activity_monitor.c | 20 +-
alac.h | 1 -
audio.c | 42 +-
audio_alsa.c | 364 +++--
audio_dummy.c | 2 +-
audio_jack.c | 61 +-
audio_pa.c | 8 +-
audio_pipe.c | 50 +-
audio_sndio.c | 14 +-
audio_soundio.c | 5 +-
audio_stdout.c | 2 +-
common.c | 303 ++--
common.h | 44 +-
dacp.c | 775 ++++++-----
dbus-service.c | 69 +-
man/shairport-sync.7 | 20 +-
man/shairport-sync.7.xml | 634 ++++-----
man/shairport-sync.html | 1219 +----------------
mdns_avahi.c | 12 +-
mdns_dns_sd.c | 2 +-
mdns_external.c | 2 +-
metadata_hub.c | 201 +--
metadata_hub.h | 16 +-
mpris-service.c | 19 +-
mqtt.c | 14 +-
player.c | 1596 +++++++++++-----------
player.h | 27 +-
rtp.c | 400 +++---
rtsp.c | 452 ++++--
scripts/shairport-sync-dbus-policy.conf | 6 +-
scripts/shairport-sync-mpris-policy.conf | 5 +-
scripts/shairport-sync.conf | 49 +-
shairport-sync-dbus-test-client.c | 26 +-
shairport.c | 342 +++--
tinysvcmdns.c | 7 +-
37 files changed, 3154 insertions(+), 3687 deletions(-)
diff --git a/FREEBSD.md b/FREEBSD.md
index 057a96f2..fe3a2a5e 100644
--- a/FREEBSD.md
+++ b/FREEBSD.md
@@ -60,7 +60,7 @@ Next, configure the build and compile it:
```
$ autoreconf -i -f
-./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
+$ ./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
$ make
```
Add `--with-alsa` if you wish to include the ALSA back end. Omit the `--with-sndio` if you don't want the `sndio` back end. Omit the `--with-freebsd-service` if you don't want to install a FreeBSD startup script, runtime folder and user and group -- see below for more details.
diff --git a/README.md b/README.md
index a6821f31..fde314f9 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ For more about the motivation behind Shairport Sync, please see the wiki at http
Synchronisation, Latency, "Stuffing"
---------
-The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on.
+The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on.
As mentioned previously, Shairport Sync implements full audio synchronisation when used with `alsa`, `sndio` or PulseAudio systems. This is done by monitoring the timestamps present in data coming from the audio source and the timing information from the audio system, e.g. `alsa`. To maintain the latency required for exact synchronisation, if the output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”.
@@ -42,7 +42,7 @@ What else?
* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/) interface, partially complete and very functional, including access to metadata and artwork, and some limited remote control.
* An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), an often-used protocol in home automation projects.
* A native D-Bus interface, including access to metadata and artwork, some limited remote control and some system settings.
-
+
Heritage
-------
Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0.
@@ -67,7 +67,7 @@ See [here](https://github.com/mikebrady/shairport-sync/blob/master/INSTALL.md) f
Shairport Sync may already be available as a package in your Linux distribution (search for `shairport-sync` – the package named `shairport` is a different program). Packages are available on recent versions of Debian, Ubuntu, Arch, OpenWrt and possibly more:
-**Ubuntu:** A `shairport-sync` installer package is available for Ubuntu. Additionally, a Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson.
+**Ubuntu:** A `shairport-sync` installer package is available for Ubuntu. Additionally, a Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson.
**Debian:** shairport-sync is in the Debian archive.
@@ -77,7 +77,7 @@ Shairport Sync may already be available as a package in your Linux distribution
To build and install the latest version of Shairport Sync, an [Arch Linux build and installation guide](https://github.com/mikebrady/shairport-sync-for-arch-linux) is available, based on original work by [Elia Cereda](https://github.com/EliaCereda).
-**Mac OS X:** A HomeBrew package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command:
+**Mac OS X:** A HomeBrew package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command:
```
$brew install shairport-sync
```
@@ -93,7 +93,7 @@ If you wish to build and install the latest version of Shairport Sync on Debian,
**Remove Old Versions of Shairport Sync and its Startup Scripts**
-You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
+You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
You should also remove the startup script files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
@@ -125,13 +125,13 @@ If PulseAudio in not installed, you'll get something like this:
```
$ pactl info
-bash: pactl: command not found
-$
+$
```
If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend.
If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it.
-**Building**
+**Building**
To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions.
@@ -181,7 +181,7 @@ $ autoreconf -i -f
(Don't worry -- there's a recommended set of configuration options further down.)
-- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality.
+- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality.
- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed.
- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout).
- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe.
@@ -201,9 +201,9 @@ $ autoreconf -i -f
**Determine if it's a `systemd` or a "System V" installation:**
-If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.)
+If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.)
-There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system.
+There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system.
The easiest way is to look at the first few lines of the `init` manual. Enter the command:
@@ -365,7 +365,7 @@ Note: Shairport Sync can take configuration settings from command line options.
**Raspberry Pi**
-The Raspberry Pi Models A and B have a built-in audio DAC that is connected to the device's headphone jack. Apart from a loud click when used for the first time after power-up, it is now quite adequate for casual listening.
+The Raspberry Pi Models A and B have a built-in audio DAC that is connected to the device's headphone jack. Apart from a loud click when used for the first time after power-up, it is now quite adequate for casual listening.
To get the benefits of improvements in the Pi's software and firmware, you should update to the Raspian release of October 2018 or later, as a number of improvements have been made to the built-in DAC.
@@ -405,7 +405,7 @@ The System V init script at `/etc/init.d/shairport-sync` has a bare minimum :
Examples
--------
-Here are some examples of complete configuration files.
+Here are some examples of complete configuration files.
```
general = {
@@ -518,7 +518,7 @@ The UDP metadata format is very simple - the first four bytes are the metadata *
Latency
-------
-Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer.
+Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer.
Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them.
@@ -526,7 +526,7 @@ Problems can arise when you are trying to synchronise with speaker systems — t
Resynchronisation
-------------
-Shairport Sync actively maintains synchronisation with the source.
+Shairport Sync actively maintains synchronisation with the source.
If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above.
* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting.
@@ -537,7 +537,7 @@ Playback synchronisation is allowed to wander — to "drift" — a small amount
Some Statistics
---------------
-If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file:
+If you turn on the `general` `statistics` setting, a heading like this will be output to the log file (or to `STDERR` if the `-u` command line option is chosen):
```
sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy, source nominal frames per second, source actual frames per second, output frames per second, source clock drift in ppm, source clock drift sample count, rough calculated correction in ppm
```
diff --git a/activity_monitor.c b/activity_monitor.c
index bcc25d24..735fd03a 100644
--- a/activity_monitor.c
+++ b/activity_monitor.c
@@ -62,7 +62,7 @@ void going_active(int block) {
command_execute(config.cmd_active_start, "", block);
#ifdef CONFIG_METADATA
debug(2, "abeg"); // active mode begin
- send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
+ send_ssnc_metadata('abeg', NULL, 0, 1); // contains cancellation points
#endif
#ifdef CONFIG_DBUS_INTERFACE
@@ -89,7 +89,7 @@ void going_inactive(int block) {
command_execute(config.cmd_active_stop, "", block);
#ifdef CONFIG_METADATA
debug(2, "aend"); // active mode end
- send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
+ send_ssnc_metadata('aend', NULL, 0, 1); // contains cancellation points
#endif
#ifdef CONFIG_DBUS_INTERFACE
@@ -196,21 +196,19 @@ void *activity_monitor_thread_code(void *arg) {
} else {
state = am_timing_out;
- uint64_t time_to_wait_for_wakeup_fp =
- (uint64_t)(config.active_state_timeout * 1000000); // resolution of microseconds
- time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32;
- time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000;
+ uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(config.active_state_timeout * 1000000000);
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
- uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp;
- sec = time_of_wakeup_fp >> 32;
- nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
+ sec = time_of_wakeup_ns / 1000000000;
+ nsec = time_of_wakeup_ns % 1000000000;
time_for_wait.tv_sec = sec;
time_for_wait.tv_nsec = nsec;
#endif
+
#ifdef COMPILE_FOR_OSX
- sec = time_to_wait_for_wakeup_fp >> 32;
- nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ sec = time_to_wait_for_wakeup_ns / 1000000000;
+ nsec = time_to_wait_for_wakeup_ns % 1000000000;
time_for_wait.tv_sec = sec;
time_for_wait.tv_nsec = nsec;
#endif
diff --git a/alac.h b/alac.h
index 7cdd507d..51db2c60 100644
--- a/alac.h
+++ b/alac.h
@@ -27,7 +27,6 @@
*
*/
-
#ifndef __ALAC__DECOMP_H
#define __ALAC__DECOMP_H
diff --git a/audio.c b/audio.c
index 8b2762f4..23b1760d 100644
--- a/audio.c
+++ b/audio.c
@@ -126,6 +126,7 @@ void parse_general_audio_options(void) {
* are set before any options are chosen */
int value;
double dvalue;
+ const char *str = 0;
if (config.cfg != NULL) {
/* Get the desired buffer size setting (deprecated). */
@@ -147,10 +148,10 @@ void parse_general_audio_options(void) {
/* Get the desired buffer size setting in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
- if ((dvalue < 0) || (dvalue > 1.5)) {
+ if (dvalue < 0) {
die("Invalid audio_backend_buffer_desired_length_in_seconds value: \"%f\". It "
- "should be between 0 and "
- "1.5, default is %.3f seconds",
+ "should be 0.0 or greater."
+ " The default is %.3f seconds",
dvalue, config.audio_backend_buffer_desired_length);
} else {
config.audio_backend_buffer_desired_length = dvalue;
@@ -190,23 +191,42 @@ void parse_general_audio_options(void) {
/* Get the latency offset in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds",
&dvalue)) {
- if ((dvalue < -1.75) || (dvalue > 1.75)) {
- die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It "
- "should be between -1.75 and +1.75, default is 0 seconds",
- dvalue);
+ config.audio_backend_latency_offset = dvalue;
+ }
+
+ /* Check if the length of the silent lead-in ia set to \"auto\". */
+ if (config_lookup_string(config.cfg, "general.audio_backend_silent_lead_in_time", &str)) {
+ if (strcasecmp(str, "auto") == 0) {
+ config.audio_backend_silent_lead_in_time_auto = 1;
} else {
- config.audio_backend_latency_offset = dvalue;
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ warn("Invalid audio_backend_silent_lead_in_time \"%s\". It should be \"auto\" or the "
+ "lead-in time in seconds. "
+ "It remains set to \"auto\". Note: numbers should not be placed in quotes.",
+ str);
+ else
+ warn("Invalid output rate \"%s\". It should be \"auto\" or the lead-in time in seconds. "
+ "It remains set to %f. Note: numbers should not be placed in quotes.",
+ str, config.audio_backend_silent_lead_in_time);
}
}
/* Get the desired length of the silent lead-in. */
if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", &dvalue)) {
if ((dvalue < 0.0) || (dvalue > 4)) {
- die("Invalid audio_backend_silent_lead_in_time \"%f\". It "
- "must be between 0.0 and 4.0 seconds. Omit setting to use the default value",
- dvalue);
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
+ "The setting remains at \"auto\".",
+ dvalue);
+ else
+ warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
+ "It remains set to %f.",
+ dvalue, config.audio_backend_silent_lead_in_time);
} else {
config.audio_backend_silent_lead_in_time = dvalue;
+ config.audio_backend_silent_lead_in_time_auto = 0;
}
}
}
diff --git a/audio_alsa.c b/audio_alsa.c
index 39e4d051..69722d12 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -210,9 +210,6 @@ int precision_delay_available() {
generate_zero_frames(silence, frames_of_silence, config.output_format,
use_dither, // i.e. with dither
dither_random_number_store);
- // debug(1,"Play %d frames of silence with most_recent_write_time of
- // %" PRIx64 ".",
- // frames_of_silence,most_recent_write_time);
do_play(silence, frames_of_silence);
pthread_cleanup_pop(1);
// now we can get the delay, and we'll note if it uses update timestamps
@@ -255,8 +252,6 @@ static uint64_t frames_played_at_measurement_start_time;
static uint64_t measurement_time;
static uint64_t frames_played_at_measurement_time;
-volatile uint64_t most_recent_write_time;
-
static uint64_t frames_sent_for_playing;
static uint64_t frame_index;
static int measurement_data_is_valid;
@@ -342,8 +337,9 @@ void actual_close_alsa_device() {
if (alsa_handle) {
int derr;
if ((derr = snd_pcm_hw_free(alsa_handle)))
- debug(1, "Error %d (\"%s\") freeing the output device hardware while "
- "closing it.",
+ debug(1,
+ "Error %d (\"%s\") freeing the output device hardware while "
+ "closing it.",
derr, snd_strerror(derr));
if ((derr = snd_pcm_close(alsa_handle)))
@@ -358,7 +354,10 @@ void actual_close_alsa_device() {
// The lowest rate that the DAC is capable of is chosen.
unsigned int auto_speed_output_rates[] = {
- 44100, 88200, 176400, 352800,
+ 44100,
+ 88200,
+ 176400,
+ 352800,
};
// This array is of all the formats known to Shairport Sync, in order of the SPS_FORMAT definitions,
@@ -707,9 +706,10 @@ int actual_open_alsa_device(int do_auto_setup) {
buffer_size);
}
*/
- debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend "
- "buffer "
- "length (%ld) you have chosen.",
+ debug(1,
+ "The alsa buffer is smaller (%lu bytes) than the desired backend "
+ "buffer "
+ "length (%ld) you have chosen.",
actual_buffer_length, config.audio_backend_buffer_desired_length);
}
@@ -869,120 +869,117 @@ int prepare_mixer() {
// do any alsa device initialisation (general case)
// at present, this is only needed if a hardware mixer is being used
// if there's a hardware mixer, it needs to be initialised before use
- if (alsa_mix_ctrl == NULL) {
- audio_alsa.volume = NULL;
- audio_alsa.parameters = NULL;
- audio_alsa.mute = NULL;
- } else {
- debug(2, "alsa: hardware mixer prepare");
- int oldState;
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ if (alsa_mix_ctrl == NULL) {
+ audio_alsa.volume = NULL;
+ audio_alsa.parameters = NULL;
+ audio_alsa.mute = NULL;
+ } else {
+ debug(2, "alsa: hardware mixer prepare");
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
- if (alsa_mix_dev == NULL)
- alsa_mix_dev = alsa_out_dev;
+ if (alsa_mix_dev == NULL)
+ alsa_mix_dev = alsa_out_dev;
- // Now, start trying to initialise the alsa device with the settings
- // obtained
- pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
- if (open_mixer() == 1) {
- if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv,
- &alsa_mix_maxv) < 0)
- debug(1, "Can't read mixer's [linear] min and max volumes.");
- else {
- if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
- &alsa_mix_maxdb) == 0) {
+ // Now, start trying to initialise the alsa device with the settings
+ // obtained
+ pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
+ if (open_mixer() == 1) {
+ if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
+ 0)
+ debug(1, "Can't read mixer's [linear] min and max volumes.");
+ else {
+ if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
+ &alsa_mix_maxdb) == 0) {
- audio_alsa.volume = &volume; // insert the volume function now we
- // know it can do dB stuff
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
- // For instance, the Raspberry Pi does this
- debug(1, "Lowest dB value is a mute");
- mixer_volume_setting_gives_mute = 1;
- alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be
- // necessary -- it's
- // always
- // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
- // debug(1, "Try minimum volume + 1 as lowest true attenuation
- // value");
- if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
- &alsa_mix_mindb) != 0)
- debug(1, "Can't get dB value corresponding to a minimum volume "
- "+ 1.");
- }
- debug(3, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
- (1.0 * alsa_mix_maxdb) / 100.0);
- } else {
- // use the linear scale and do the db conversion ourselves
- warn("The hardware mixer specified -- \"%s\" -- does not have "
- "a dB volume scale.",
- alsa_mix_ctrl);
-
- if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
- warn("Cannot open control \"%s\"", alsa_mix_dev);
- response = -1;
- }
- if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
- debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
- elem_id = NULL;
- response = -2;
- } else {
- snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
- snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
-
- if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
- debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume "
- "from %f to %f.",
- alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0,
- (1.0 * alsa_mix_maxdb) / 100.0);
- has_softvol = 1;
- audio_alsa.volume = &volume; // insert the volume function now
- // we know it can do dB stuff
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- } else {
- debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl);
- }
- }
- /*
- debug(1, "Min and max volumes are %d and
- %d.",alsa_mix_minv,alsa_mix_maxv);
- alsa_mix_maxdb = 0;
- if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
- alsa_mix_mindb =
- -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
- else if (alsa_mix_maxv!=0)
- alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
- audio_alsa.volume = &linear_volume; // insert the linear volume
- function
- audio_alsa.parameters = ¶meters; // likewise the parameters
- stuff
- debug(1,"Max and min dB calculated are %d and
- %d.",alsa_mix_maxdb,alsa_mix_mindb);
- */
+ audio_alsa.volume = &volume; // insert the volume function now we
+ // know it can do dB stuff
+ audio_alsa.parameters = ¶meters; // likewise the parameters stuff
+ if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
+ // For instance, the Raspberry Pi does this
+ debug(1, "Lowest dB value is a mute");
+ mixer_volume_setting_gives_mute = 1;
+ alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be
+ // necessary -- it's
+ // always
+ // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
+ // debug(1, "Try minimum volume + 1 as lowest true attenuation
+ // value");
+ if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
+ &alsa_mix_mindb) != 0)
+ debug(1, "Can't get dB value corresponding to a minimum volume "
+ "+ 1.");
}
- }
- if (((config.alsa_use_hardware_mute == 1) &&
- (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
- mixer_volume_setting_gives_mute) {
- audio_alsa.mute = &mute; // insert the mute function now we know it
- // can do muting stuff
- // debug(1, "Has mixer and mute ability we will use.");
+ debug(3, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
+ (1.0 * alsa_mix_maxdb) / 100.0);
} else {
- // debug(1, "Has mixer but not using hardware mute.");
+ // use the linear scale and do the db conversion ourselves
+ warn("The hardware mixer specified -- \"%s\" -- does not have "
+ "a dB volume scale.",
+ alsa_mix_ctrl);
+
+ if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
+ warn("Cannot open control \"%s\"", alsa_mix_dev);
+ response = -1;
+ }
+ if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
+ debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
+ elem_id = NULL;
+ response = -2;
+ } else {
+ snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
+ snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
+
+ if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
+ debug(1,
+ "alsa: hardware mixer \"%s\" selected, with dB volume "
+ "from %f to %f.",
+ alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
+ has_softvol = 1;
+ audio_alsa.volume = &volume; // insert the volume function now
+ // we know it can do dB stuff
+ audio_alsa.parameters = ¶meters; // likewise the parameters stuff
+ } else {
+ debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl);
+ }
+ }
+ /*
+ debug(1, "Min and max volumes are %d and
+ %d.",alsa_mix_minv,alsa_mix_maxv);
+ alsa_mix_maxdb = 0;
+ if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
+ alsa_mix_mindb =
+ -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
+ else if (alsa_mix_maxv!=0)
+ alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
+ audio_alsa.volume = &linear_volume; // insert the linear volume
+ function
+ audio_alsa.parameters = ¶meters; // likewise the parameters
+ stuff
+ debug(1,"Max and min dB calculated are %d and
+ %d.",alsa_mix_maxdb,alsa_mix_mindb);
+ */
}
- close_mixer();
}
- debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
- pthread_cleanup_pop(0);
- pthread_setcancelstate(oldState, NULL);
+ if (((config.alsa_use_hardware_mute == 1) &&
+ (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
+ mixer_volume_setting_gives_mute) {
+ audio_alsa.mute = &mute; // insert the mute function now we know it
+ // can do muting stuff
+ // debug(1, "Has mixer and mute ability we will use.");
+ } else {
+ // debug(1, "Has mixer but not using hardware mute.");
+ }
+ close_mixer();
}
+ debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
+ pthread_cleanup_pop(0);
+ pthread_setcancelstate(oldState, NULL);
+ }
return response;
}
-int alsa_device_init() {
- return prepare_mixer();
-}
-
+int alsa_device_init() { return prepare_mixer(); }
static int init(int argc, char **argv) {
// for debugging
@@ -1140,8 +1137,10 @@ static int init(int argc, char **argv) {
"\"S16\", \"S24\", \"S24_LE\", \"S24_BE\", "
"\"S24_3LE\", \"S24_3BE\" or "
"\"S32\", \"S32_LE\", \"S32_BE\". It remains set to \"%s\".",
- str, config.output_format_auto_requested == 1 ? "auto" : sps_format_description_string(
- config.output_format));
+ str,
+ config.output_format_auto_requested == 1
+ ? "auto"
+ : sps_format_description_string(config.output_format));
}
}
@@ -1291,9 +1290,9 @@ static int init(int argc, char **argv) {
warn("Invalid use_precision_timing option choice \"%s\". It should be "
"\"yes\", \"auto\" or \"no\". "
"It remains set to \"%s\".",
- config.use_precision_timing == YNA_NO ? "no" : config.use_precision_timing == YNA_AUTO
- ? "auto"
- : "yes");
+ config.use_precision_timing == YNA_NO
+ ? "no"
+ : config.use_precision_timing == YNA_AUTO ? "auto" : "yes");
}
}
@@ -1350,7 +1349,6 @@ static int init(int argc, char **argv) {
// length of the queue
// if the queue gets too short, stuff it with silence
- most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code
pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL);
return response;
@@ -1525,14 +1523,16 @@ int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) ||
((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) {
- debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64
- ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64
- ", stall_monitor_error_threshold %" PRIX64 ".",
+ debug(2,
+ "DAC seems to have stalled with time_now_ns: %" PRIX64
+ ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64
+ ", stall_monitor_error_threshold %" PRIX64 ".",
time_now_ns, update_timestamp_ns, stall_monitor_start_time,
stall_monitor_error_threshold);
- debug(2, "DAC seems to have stalled with time_now: %lx,%lx"
- ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
- ", stall_monitor_error_threshold %" PRIX64 ".",
+ debug(2,
+ "DAC seems to have stalled with time_now: %lx,%lx"
+ ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
+ ", stall_monitor_error_threshold %" PRIX64 ".",
tn.tv_sec, tn.tv_nsec, update_timestamp.tv_sec, update_timestamp.tv_nsec,
stall_monitor_start_time, stall_monitor_error_threshold);
ret = sps_extra_code_output_stalled;
@@ -1605,6 +1605,7 @@ int delay(long *the_delay) {
}
int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) {
+ // elapsed_time is in nanoseconds
int response = 0; // zero means okay
if (measurement_data_is_valid) {
*elapsed_time = measurement_time - measurement_start_time;
@@ -1657,7 +1658,7 @@ int do_play(void *buf, int samples) {
if ((frame_index == start_measurement_from_this_frame) ||
((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) {
- measurement_time = get_absolute_time_in_fp();
+ measurement_time = get_absolute_time_in_ns();
frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples;
if (frame_index == start_measurement_from_this_frame) {
@@ -1697,8 +1698,9 @@ int do_play(void *buf, int samples) {
}
}
} else {
- debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* "
- "%d for play.",
+ debug(1,
+ "alsa: device status returns fault status %d and SND_PCM_STATE_* "
+ "%d for play.",
ret, state);
frame_index = 0;
measurement_data_is_valid = 0;
@@ -1744,6 +1746,7 @@ int do_close() {
// debug(1,"alsa: do_close() -- closing the output device");
if ((derr = snd_pcm_drop(alsa_handle)))
debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr));
+ usleep(5000);
if ((derr = snd_pcm_hw_free(alsa_handle)))
debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr));
@@ -1946,7 +1949,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
alsa_device_init();
alsa_device_initialised = 1;
}
- int sleep_time_ms = (int)(config.disable_standby_mode_silence_scan_interval * 1000);
+ int sleep_time_us = (int)(config.disable_standby_mode_silence_scan_interval * 1000000);
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0);
// check possible state transitions here
if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) {
@@ -1974,65 +1977,60 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
int reply;
long buffer_size = 0;
snd_pcm_state_t state;
- uint64_t present_time = get_absolute_time_in_fp();
- if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) {
- reply = delay_and_status(&state, &buffer_size, NULL);
- if (reply != 0) {
- buffer_size = 0;
- char errorstring[1024];
- strerror_r(-reply, (char *)errorstring, sizeof(errorstring));
- debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply,
- (char *)errorstring);
- }
- long buffer_size_threshold =
- (long)(config.disable_standby_mode_silence_threshold * config.output_rate);
- size_t size_of_silence_buffer;
- if (buffer_size < buffer_size_threshold) {
- uint64_t sleep_time_in_fp = sleep_time_ms;
- sleep_time_in_fp = sleep_time_in_fp << 32;
- sleep_time_in_fp = sleep_time_in_fp / 1000;
- int frames_of_silence = 1024;
- size_of_silence_buffer = frames_of_silence * frame_size;
- void *silence = malloc(size_of_silence_buffer);
- if (silence == NULL) {
- warn("disable_standby_mode has been turned off because a memory allocation error "
- "occurred.");
- error_detected = 1;
- } else {
- int ret;
- pthread_cleanup_push(malloc_cleanup, silence);
- int use_dither = 0;
- if ((alsa_mix_ctrl == NULL) && (config.ignore_volume_control == 0) &&
- (config.airplay_volume != 0.0))
- use_dither = 1;
- dither_random_number_store =
- generate_zero_frames(silence, frames_of_silence, config.output_format,
- use_dither, // i.e. with dither
- dither_random_number_store);
- ret = do_play(silence, frames_of_silence);
- frame_count++;
- pthread_cleanup_pop(1); // free malloced buffer
- if (ret < 0) {
- error_count++;
- char errorstring[1024];
- strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
- debug(2, "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
- "to alsa device -- %d errors in %d trials.",
- ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
- if ((error_count > 40) && (frame_count < 100)) {
- warn("disable_standby_mode has been turned off because too many underruns "
- "occurred. Is Shairport Sync outputting to a virtual device or running in a "
- "virtual machine?");
- error_detected = 1;
- }
+ reply = delay_and_status(&state, &buffer_size, NULL);
+ if (reply != 0) {
+ buffer_size = 0;
+ char errorstring[1024];
+ strerror_r(-reply, (char *)errorstring, sizeof(errorstring));
+ debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply,
+ (char *)errorstring);
+ }
+ long buffer_size_threshold =
+ (long)(config.disable_standby_mode_silence_threshold * config.output_rate);
+ size_t size_of_silence_buffer;
+ if (buffer_size < buffer_size_threshold) {
+ int frames_of_silence = 1024;
+ size_of_silence_buffer = frames_of_silence * frame_size;
+ void *silence = malloc(size_of_silence_buffer);
+ if (silence == NULL) {
+ warn("disable_standby_mode has been turned off because a memory allocation error "
+ "occurred.");
+ error_detected = 1;
+ } else {
+ int ret;
+ pthread_cleanup_push(malloc_cleanup, silence);
+ int use_dither = 0;
+ if ((alsa_mix_ctrl == NULL) && (config.ignore_volume_control == 0) &&
+ (config.airplay_volume != 0.0))
+ use_dither = 1;
+ dither_random_number_store =
+ generate_zero_frames(silence, frames_of_silence, config.output_format,
+ use_dither, // i.e. with dither
+ dither_random_number_store);
+ ret = do_play(silence, frames_of_silence);
+ frame_count++;
+ pthread_cleanup_pop(1); // free malloced buffer
+ if (ret < 0) {
+ error_count++;
+ char errorstring[1024];
+ strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
+ debug(2,
+ "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
+ "to alsa device -- %d errors in %d trials.",
+ ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
+ if ((error_count > 40) && (frame_count < 100)) {
+ warn("disable_standby_mode has been turned off because too many underruns "
+ "occurred. Is Shairport Sync outputting to a virtual device or running in a "
+ "virtual machine?");
+ error_detected = 1;
}
}
}
}
}
debug_mutex_unlock(&alsa_mutex, 0);
- pthread_cleanup_pop(0); // release the mutex
- usleep(sleep_time_ms * 1000); // has a cancellation point in it
+ pthread_cleanup_pop(0); // release the mutex
+ usleep(sleep_time_us); // has a cancellation point in it
}
pthread_exit(NULL);
}
diff --git a/audio_dummy.c b/audio_dummy.c
index 5d64ecf2..5d16bf95 100644
--- a/audio_dummy.c
+++ b/audio_dummy.c
@@ -39,7 +39,7 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
static void deinit(void) {}
static void start(int sample_rate, __attribute__((unused)) int sample_format) {
- debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
+ debug(1, "dummy audio output started at %d frames per second.", sample_rate);
}
static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {
diff --git a/audio_jack.c b/audio_jack.c
index db6e1899..21c3e543 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -89,14 +89,9 @@ typedef struct soxr_quality {
const char *name;
} soxr_quality_t;
-static soxr_quality_t soxr_quality_table[] = {
- { SOXR_VHQ, "very high" },
- { SOXR_HQ, "high" },
- { SOXR_MQ, "medium" },
- { SOXR_LQ, "low" },
- { SOXR_QQ, "quick" },
- { -1, NULL }
-};
+static soxr_quality_t soxr_quality_table[] = {{SOXR_VHQ, "very high"}, {SOXR_HQ, "high"},
+ {SOXR_MQ, "medium"}, {SOXR_LQ, "low"},
+ {SOXR_QQ, "quick"}, {-1, NULL}};
static int parse_soxr_quality_name(const char *name) {
for (soxr_quality_t *s = soxr_quality_table; s->name != NULL; ++s) {
@@ -107,9 +102,9 @@ static int parse_soxr_quality_name(const char *name) {
return -1;
}
-static soxr_t soxr = NULL;
+static soxr_t soxr = NULL;
static soxr_quality_spec_t quality_spec;
-static soxr_io_spec_t io_spec;
+static soxr_io_spec_t io_spec;
#endif
static inline sample_t sample_conv(short sample) {
@@ -122,8 +117,7 @@ static inline sample_t sample_conv(short sample) {
return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX));
}
-static void deinterleave(const char *interleaved_input_buffer,
- sample_t *jack_output_buffer[],
+static void deinterleave(const char *interleaved_input_buffer, sample_t *jack_output_buffer[],
jack_nframes_t offset, jack_nframes_t nframes) {
jack_nframes_t f;
// We're dealing with 16bit audio here:
@@ -271,7 +265,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
io_spec = soxr_io_spec(SOXR_INT16_I, SOXR_FLOAT32_I);
} else
#endif
- if (sample_rate != 44100) {
+ if (sample_rate != 44100) {
die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync."
" Must be 44100 Hz.",
sample_rate);
@@ -353,8 +347,7 @@ void jack_deinit() {
#endif
}
-void jack_start(int i_sample_rate,
- __attribute__((unused)) int i_sample_format) {
+void jack_start(int i_sample_rate, __attribute__((unused)) int i_sample_format) {
// Nothing to do, JACK client has already been set up at jack_init().
// Also, we have no say over the sample rate or sample format of JACK,
// We convert the 16bit samples to float, and die if the sample rate is != 44k1 without soxr.
@@ -365,13 +358,7 @@ void jack_start(int i_sample_rate,
soxr_delete(soxr);
}
soxr_error_t e = NULL;
- soxr = soxr_create(i_sample_rate,
- sample_rate,
- NPORTS,
- &e,
- &io_spec,
- &quality_spec,
- NULL);
+ soxr = soxr_create(i_sample_rate, sample_rate, NPORTS, &e, &io_spec, &quality_spec, NULL);
if (!soxr) {
die("Unable to create soxr resampler for JACK: %s", e);
}
@@ -395,13 +382,13 @@ int jack_delay(long *the_delay) {
// occupancy after another transfer had occurred, so we could "lose" a full transfer
// (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds)
pthread_mutex_lock(&buffer_mutex);
- int64_t time_now = get_absolute_time_in_fp();
- int64_t delta = time_now - time_of_latest_transfer;
+ int64_t time_now = get_absolute_time_in_ns();
+ int64_t delta = time_now - time_of_latest_transfer; // nanoseconds
size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame;
debug(2, "audio_occupancy_now is %d.", audio_occupancy_now);
pthread_mutex_unlock(&buffer_mutex);
- int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) >> 32;
+ int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) / 1000000000;
// debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check);
// jack_latency is set by the graph() callback, it's the average of the maximum
// latencies of all our output ports. Adjust this constant baseline delay according
@@ -428,13 +415,7 @@ int play(void *buf, int samples) {
size_t i_done, o_done;
soxr_error_t e;
while (samples > 0 && thisbuf > 0) {
- e = soxr_process(soxr,
- (soxr_in_t)in,
- samples,
- &i_done,
- (soxr_out_t)out,
- thisbuf,
- &o_done);
+ e = soxr_process(soxr, (soxr_in_t)in, samples, &i_done, (soxr_out_t)out, thisbuf, &o_done);
if (e)
die("Error during soxr process: %s", e);
@@ -445,18 +426,18 @@ int play(void *buf, int samples) {
}
} else {
#endif
- j = 0;
- for (j = 0; j < thisbuf && samples > 0; ++j) {
- for (c = 0; c < NPORTS; ++c)
- out[j * NPORTS + c] = sample_conv(*in++);
- --samples;
- }
- jack_ringbuffer_write_advance(jackbuf, j * jack_sample_size * NPORTS);
+ j = 0;
+ for (j = 0; j < thisbuf && samples > 0; ++j) {
+ for (c = 0; c < NPORTS; ++c)
+ out[j * NPORTS + c] = sample_conv(*in++);
+ --samples;
+ }
+ jack_ringbuffer_write_advance(jackbuf, j * jack_sample_size * NPORTS);
#ifdef CONFIG_SOXR
}
#endif
}
- time_of_latest_transfer = get_absolute_time_in_fp();
+ time_of_latest_transfer = get_absolute_time_in_ns();
pthread_mutex_unlock(&buffer_mutex);
if (samples) {
warn("JACK ringbuffer overrun. Dropped %d samples.", samples);
diff --git a/audio_pa.c b/audio_pa.c
index e6a5b576..951860be 100644
--- a/audio_pa.c
+++ b/audio_pa.c
@@ -85,6 +85,11 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
if (config.cfg != NULL) {
const char *str;
+ /* Get the PulseAudio server name. */
+ if (config_lookup_string(config.cfg, "pa.server", &str)) {
+ config.pa_server = (char *)str;
+ }
+
/* Get the Application Name. */
if (config_lookup_string(config.cfg, "pa.application_name", &str)) {
config.pa_application_name = (char *)str;
@@ -126,7 +131,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
// Start the mainloop
if (pa_threaded_mainloop_start(mainloop) != 0)
die("could not start the pulseaudio threaded mainloop");
- if (pa_context_connect(context, NULL, 0, NULL) != 0)
+
+ if (pa_context_connect(context, config.pa_server, 0, NULL) != 0)
die("failed to connect to the pulseaudio context -- the error message is \"%s\".",
pa_strerror(pa_context_errno(context)));
diff --git a/audio_pipe.c b/audio_pipe.c
index 4ec9b240..6dedc464 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -42,7 +42,6 @@
static int fd = -1;
char *pipename = NULL;
-int warned = 0;
static void start(__attribute__((unused)) int sample_rate,
__attribute__((unused)) int sample_format) {
@@ -51,47 +50,46 @@ static void start(__attribute__((unused)) int sample_rate,
// we check that it's not a "real" error though. From the "man 2 open" page:
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
// open for reading."
- fd = open(pipename, O_WRONLY | O_NONBLOCK);
- if ((fd == -1) && (errno != ENXIO) && (warned == 0)) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "pipe: start -- error %d (\"%s\") opening the pipe named \"%s\".", errno,
- (char *)errorstring, pipename);
- warn("Error %d opening the pipe named \"%s\".", errno, pipename);
- warned = 1;
- }
+
+ fd = try_to_open_pipe_for_writing(pipename);
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ if ((fd == -1) && (errno != ENXIO)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "audio_pipe start -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, pipename);
+ warn("can not open audio pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, pipename);
+ }
}
static int play(void *buf, int samples) {
// if the file is not open, try to open it.
char errorstring[1024];
if (fd == -1) {
- fd = open(pipename, O_WRONLY | O_NONBLOCK);
+ fd = try_to_open_pipe_for_writing(pipename);
}
// if it's got a reader, write to it.
if (fd > 0) {
- int rc = non_blocking_write(fd, buf, samples * 4);
- if ((rc < 0) && (warned == 0)) {
+ //int rc = non_blocking_write(fd, buf, samples * 4);
+ int rc = write(fd, buf, samples * 4);
+ if ((rc < 0) && (errno != EPIPE)) {
strerror_r(errno, (char *)errorstring, 1024);
- warn("Error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
- warned = 1;
+ debug(1, "audio_pip play: error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
}
- } else if ((fd == -1) && (errno != ENXIO) && (warned == 0)) {
- strerror_r(errno, (char *)errorstring, 1024);
- warn("Error %d opening the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
- warned = 1;
}
- return warned;
+ return 0;
}
static void stop(void) {
// Don't close the pipe just because a play session has stopped.
- // if (fd > 0)
- // close(fd);
+
}
static int init(int argc, char **argv) {
- debug(1, "pipe init");
+ // debug(1, "pipe init");
// const char *str;
// int value;
// double dvalue;
@@ -122,10 +120,12 @@ static int init(int argc, char **argv) {
pipename = strdup(argv[0]);
// here, create the pipe
+ mode_t oldumask = umask(000);
if (mkfifo(pipename, 0644) && errno != EEXIST)
- die("Could not create output pipe \"%s\"", pipename);
+ die("Could not create audio pipe \"%s\"", pipename);
+ umask(oldumask);
- debug(1, "Pipename is \"%s\"", pipename);
+ debug(1, "audio pipe name is \"%s\"", pipename);
return 0;
}
diff --git a/audio_sndio.c b/audio_sndio.c
index ac5cfa38..f8582c8a 100644
--- a/audio_sndio.c
+++ b/audio_sndio.c
@@ -247,7 +247,7 @@ static void stop() {
}
static void onmove_cb(__attribute__((unused)) void *arg, int delta) {
- time_of_last_onmove_cb = get_absolute_time_in_fp();
+ time_of_last_onmove_cb = get_absolute_time_in_ns();
at_least_one_onmove_cb_seen = 1;
played += delta;
}
@@ -258,10 +258,14 @@ static int delay(long *_delay) {
if (at_least_one_onmove_cb_seen) { // when output starts, the onmove_cb callback will be made
// calculate the difference in time between now and when the last callback occurred,
// and use it to estimate the frames that would have been output
- uint64_t time_difference = get_absolute_time_in_fp() - time_of_last_onmove_cb;
- uint64_t frame_difference = time_difference * par.rate;
- uint64_t frame_difference_big_integer = frame_difference >> 32;
- estimated_extra_frames_output = frame_difference_big_integer;
+ uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb;
+ uint64_t frame_difference = (time_difference * par.rate) / 1000000000;
+ estimated_extra_frames_output = frame_difference;
+ // sanity check -- total estimate can not exceed frames written.
+ if ((estimated_extra_frames_output + played) > written / framesize) {
+ // debug(1,"play estimate fails sanity check, possibly due to running on a VM");
+ estimated_extra_frames_output = 0; // can't make any sensible guess
+ }
// debug(1,"Frames played to last cb: %d, estimated to current time:
// %d.",played,estimated_extra_frames_output);
}
diff --git a/audio_soundio.c b/audio_soundio.c
index 119f7670..501c6db2 100644
--- a/audio_soundio.c
+++ b/audio_soundio.c
@@ -24,8 +24,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m
int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer);
int fill_count = fill_bytes / outstream->bytes_per_frame;
- debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
- "outstream->bytes_per_frame: %d",
+ debug(3,
+ "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
+ "outstream->bytes_per_frame: %d",
frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame);
if (frame_count_min > fill_count) {
diff --git a/audio_stdout.c b/audio_stdout.c
index 00ded166..8b37883e 100644
--- a/audio_stdout.c
+++ b/audio_stdout.c
@@ -56,7 +56,7 @@ static int play(void *buf, int samples) {
}
static void stop(void) {
- // don't close stdout
+ // Do nothing when play stops
}
static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
diff --git a/common.c b/common.c
index 228dfec9..39c4d62e 100644
--- a/common.c
+++ b/common.c
@@ -40,6 +40,8 @@
#include
#include
#include
+#include
+#include
#ifdef COMPILE_FOR_OSX
#include
@@ -88,10 +90,11 @@ void set_alsa_out_dev(char *);
#endif
config_t config_file_stuff;
+int emergency_exit;
pthread_t main_thread_id;
-uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
+uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
-// always lock use this when accessing the fp_time_at_last_debug_message
+// always lock use this when accessing the ns_time_at_last_debug_message
static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t the_conn_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -101,7 +104,7 @@ const char *sps_format_description_string_array[] = {
"S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid"};
const char *sps_format_description_string(sps_format_t format) {
- if ((format >= SPS_FORMAT_UNKNOWN) && (format <= SPS_FORMAT_AUTO))
+ if (format <= SPS_FORMAT_AUTO)
return sps_format_description_string_array[format];
else
return sps_format_description_string_array[SPS_FORMAT_INVALID];
@@ -120,7 +123,7 @@ static void (*sps_log)(int prio, const char *t, ...) = daemon_log;
static void (*sps_log)(int prio, const char *t, ...) = syslog;
#endif
-void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
+void do_sps_log_to_stderr(__attribute__((unused)) int prio, const char *t, ...) {
char s[1024];
va_list args;
va_start(args, t);
@@ -129,7 +132,86 @@ void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
fprintf(stderr, "%s\n", s);
}
-void log_to_stderr() { sps_log = do_sps_log; }
+void do_sps_log_to_stdout(__attribute__((unused)) int prio, const char *t, ...) {
+ char s[1024];
+ va_list args;
+ va_start(args, t);
+ vsnprintf(s, sizeof(s), t, args);
+ va_end(args);
+ fprintf(stdout, "%s\n", s);
+}
+
+int create_log_file(const char* path) {
+ int fd = -1;
+ if (path != NULL) {
+ char *dirc = strdup(path);
+ if (dirc) {
+ char *dname = dirname(dirc);
+ // create the directory, if necessary
+ int result = 0;
+ if (dname) {
+ char *pdir = realpath(dname, NULL); // will return a NULL if the directory doesn't exist
+ if (pdir == NULL) {
+ mode_t oldumask = umask(000);
+ result = mkpath(dname, 0777);
+ umask(oldumask);
+ } else {
+ free(pdir);
+ }
+ if ((result == 0) || (result == -EEXIST)) {
+ // now open the file
+ fd = open(path, O_WRONLY | O_NONBLOCK | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if ((fd == -1) && (errno == EEXIST))
+ fd = open(path, O_WRONLY | O_APPEND | O_NONBLOCK);
+
+ if (fd >= 0) {
+ // now we switch to blocking mode
+ int flags = fcntl(fd, F_GETFL);
+ if (flags == -1) {
+// strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+// debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
+// (char *)errorstring, pathname);
+ } else {
+ flags = fcntl(fd, F_SETFL,flags & ~O_NONBLOCK);
+// if (flags == -1) {
+// strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+// debug(1, "create_log_file -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
+// (char *)errorstring, pathname);
+ }
+ }
+ }
+ }
+ free(dirc);
+ }
+ }
+ return fd;
+}
+
+void do_sps_log_to_fd(__attribute__((unused)) int prio, const char *t, ...) {
+ char s[1024];
+ va_list args;
+ va_start(args, t);
+ vsnprintf(s, sizeof(s), t, args);
+ va_end(args);
+ if (config.log_fd == -1)
+ config.log_fd = create_log_file(config.log_file_path);
+ if (config.log_fd >= 0) {
+ dprintf(config.log_fd, "%s\n", s);
+ } else if (errno != ENXIO) { // maybe there is a pipe there but not hooked up
+ fprintf(stderr, "%s\n", s);
+ }
+}
+
+void log_to_stderr() { sps_log = do_sps_log_to_stderr; }
+void log_to_stdout() { sps_log = do_sps_log_to_stdout; }
+void log_to_file() { sps_log = do_sps_log_to_fd; }
+void log_to_syslog() {
+#ifdef CONFIG_LIBDAEMON
+ sps_log = daemon_log;
+#else
+ sps_log = syslog;
+#endif
+}
shairport_cfg config;
@@ -202,10 +284,10 @@ char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss
insertion_point = insertion_point + strlen(insertion_point);
space_remaining = space_remaining - strlen(insertion_point);
}
-
if (prefix) {
snprintf(insertion_point, space_remaining, "%s", prefix);
insertion_point = insertion_point + strlen(insertion_point);
+ space_remaining = space_remaining - strlen(insertion_point);
}
return insertion_point;
}
@@ -218,17 +300,17 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *fatal error: ");
} else {
- s = b;
+ strncpy(b, "fatal error: ", sizeof(b));
+ s = b+strlen(b);
}
va_list args;
va_start(args, format);
@@ -236,7 +318,8 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
va_end(args);
sps_log(LOG_ERR, "%s", b);
pthread_setcancelstate(oldState, NULL);
- abort(); // exit() doesn't always work, by heaven.
+ emergency_exit = 1;
+ exit(EXIT_FAILURE);
}
void _warn(const char *filename, const int linenumber, const char *format, ...) {
@@ -247,17 +330,17 @@ void _warn(const char *filename, const int linenumber, const char *format, ...)
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *warning: ");
} else {
- s = b;
+ strncpy(b, "warning: ", sizeof(b));
+ s = b+strlen(b);
}
va_list args;
va_start(args, format);
@@ -275,14 +358,13 @@ void _debug(const char *filename, const int linenumber, int level, const char *f
char b[1024];
b[0] = 0;
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " ");
va_list args;
va_start(args, format);
@@ -300,14 +382,13 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " ");
} else {
s = b;
@@ -1013,43 +1094,77 @@ uint64_t get_absolute_time_in_fp() {
return time_now_fp;
}
-ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout) {
- // timeout is in milliseconds
- void *ibuf = (void *)buf;
- size_t bytes_remaining = count;
- int rc = 1;
- struct pollfd ufds[1];
- while ((bytes_remaining > 0) && (rc > 0)) {
- // check that we can do some writing
- ufds[0].fd = fd;
- ufds[0].events = POLLOUT;
- rc = poll(ufds, 1, timeout);
- if (rc < 0) {
- // debug(1, "non-blocking write error waiting for pipe to become ready for writing...");
- } else if (rc == 0) {
- // warn("non-blocking write timeout waiting for pipe to become ready for writing");
- rc = -1;
- errno = -ETIMEDOUT;
- } else { // rc > 0, implying it might be ready
- ssize_t bytes_written = write(fd, ibuf, bytes_remaining);
- if (bytes_written == -1) {
- // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno));
- rc = bytes_written; // to imitate the return from write()
- } else {
- ibuf += bytes_written;
- bytes_remaining -= bytes_written;
- }
- }
+uint64_t get_absolute_time_in_ns() {
+ uint64_t time_now_ns;
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ struct timespec tn;
+ // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
+ clock_gettime(CLOCK_MONOTONIC, &tn);
+ uint64_t tnnsec = tn.tv_sec;
+ tnnsec = tnnsec * 1000000000;
+ uint64_t tnjnsec = tn.tv_nsec;
+ time_now_ns = tnnsec + tnjnsec;
+#endif
+
+#ifdef COMPILE_FOR_OSX
+ uint64_t time_now_mach;
+ uint64_t elapsedNano;
+ static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
+
+ time_now_mach = mach_absolute_time();
+
+ // If this is the first time we've run, get the timebase.
+ // We can use denom == 0 to indicate that sTimebaseInfo is
+ // uninitialised because it makes no sense to have a zero
+ // denominator in a fraction.
+
+ if (sTimebaseInfo.denom == 0) {
+ debug(1, "Mac initialise timebase info.");
+ (void)mach_timebase_info(&sTimebaseInfo);
}
- if (rc > 0)
- return count - bytes_remaining; // this is just to mimic a normal write/3.
- else
- return rc;
- // return write(fd,buf,count);
+
+ // Do the maths. We hope that the multiplication doesn't
+ // overflow; the price you pay for working in fixed point.
+
+ // this gives us nanoseconds
+ time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
+#endif
+
+ return time_now_ns;
}
-ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
- return non_blocking_write_with_timeout(fd, buf, count, 5000); // default is 5 seconds.
+int try_to_open_pipe_for_writing(const char* pathname) {
+ // tries to open the pipe in non-blocking mode first.
+ // if it succeeds, it sets it to blocking.
+ // if not, it returns -1.
+
+ int fdis = open(pathname, O_WRONLY | O_NONBLOCK); // open it in non blocking mode first
+
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ // This is checked by the caller.
+
+ if (fdis >= 0) {
+ // now we switch to blocking mode
+ int flags = fcntl(fdis, F_GETFL);
+ if (flags == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "try_to_open_pipe -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
+ (char *)errorstring, pathname);
+ } else {
+ flags = fcntl(fdis, F_SETFL,flags & ~O_NONBLOCK);
+ if (flags == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "try_to_open_pipe -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
+ (char *)errorstring, pathname);
+ }
+ }
+ }
+ return fdis;
}
/* from
@@ -1093,7 +1208,9 @@ char *str_replace(const char *string, const char *substr, const char *replacemen
/* from http://burtleburtle.net/bob/rand/smallprng.html */
-// this is not thread-safe, so we need a mutex on it to use it properly// always lock use this when accessing the fp_time_at_last_debug_message
+// this is not thread-safe, so we need a mutex on it to use it properly.
+// always lock use this when accessing the fp_time_at_last_debug_message
+
pthread_mutex_t r64_mutex = PTHREAD_MUTEX_INITIALIZER;
// typedef uint64_t u8;
@@ -1201,18 +1318,17 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
timeoutTime.tv_sec = time_then;
timeoutTime.tv_nsec = time_then_nsec;
- int64_t start_time = get_absolute_time_in_fp();
+ uint64_t start_time = get_absolute_time_in_ns();
int r = pthread_mutex_timedlock(mutex, &timeoutTime);
- int64_t et = get_absolute_time_in_fp() - start_time;
+ uint64_t et = get_absolute_time_in_ns() - start_time;
if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
- et = (et * 1000000) >> 32; // microseconds
char errstr[1000];
if (r == ETIMEDOUT)
debug(debuglevel,
- "timed out waiting for a mutex, having waiting %f seconds, with a maximum "
+ "timed out waiting for a mutex, having waited %f microseconds, with a maximum "
"waiting time of %d microseconds. \"%s\".",
- (1.0 * et) / 1000000, dally_time, debugmessage);
+ (1.0E6 * et) / 1000000000, dally_time, debugmessage);
else
debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
@@ -1261,7 +1377,7 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
return pthread_mutex_lock(mutex);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- uint64_t time_at_start = get_absolute_time_in_fp();
+ uint64_t time_at_start = get_absolute_time_in_ns();
char dstring[1000];
memset(dstring, 0, sizeof(dstring));
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
@@ -1270,12 +1386,10 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel);
if (result == ETIMEDOUT) {
result = pthread_mutex_lock(mutex);
- uint64_t time_delay = get_absolute_time_in_fp() - time_at_start;
- uint64_t divisor = (uint64_t)1 << 32;
- double delay = 1.0 * time_delay / divisor;
+ uint64_t time_delay = get_absolute_time_in_ns() - time_at_start;
debug(debuglevel,
- "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.",
- mutexname, dstring, (1.0 * dally_time) / 1000000, delay);
+ "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f microseconds.",
+ mutexname, dstring, (1.0 * dally_time), 0.001 * time_delay);
}
pthread_setcancelstate(oldState, NULL);
return result;
@@ -1313,6 +1427,9 @@ char *get_version_string() {
if (version_string) {
strcpy(version_string, PACKAGE_VERSION);
+#ifdef CONFIG_APPLE_ALAC
+ strcat(version_string, "-alac");
+#endif
#ifdef CONFIG_LIBDAEMON
strcat(version_string, "-libdaemon");
#endif
@@ -1543,9 +1660,11 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t f
return previous_random_number;
}
-// This will check the incoming string "s" of length "len" with the existing NUL-terminated string "str" and update "flag" accordingly.
+// This will check the incoming string "s" of length "len" with the existing NUL-terminated string
+// "str" and update "flag" accordingly.
-// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings are stored.
+// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings
+// are stored.
// If the strings are different, the str is free'd and replaced by a pointer
// to a newly strdup'd string and the flag is set
@@ -1558,7 +1677,7 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
free(*str);
//*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
char *p = malloc(len + 1);
- memcpy(p,s,len);
+ memcpy(p, s, len);
p[len] = '\0';
*str = p;
*flag = 1;
@@ -1575,7 +1694,7 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
if ((s) && (len)) {
//*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
char *p = malloc(len + 1);
- memcpy(p,s,len);
+ memcpy(p, s, len);
p[len] = '\0';
*str = p;
*flag = 1;
@@ -1586,3 +1705,13 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
}
return *flag;
}
+
+// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
+void* memdup(const void* mem, size_t size) {
+ void* out = malloc(size);
+
+ if(out != NULL)
+ memcpy(out, mem, size);
+
+ return out;
+}
diff --git a/common.h b/common.h
index 2c9c05bd..1f5d1c87 100644
--- a/common.h
+++ b/common.h
@@ -100,9 +100,13 @@ typedef enum {
const char *sps_format_description_string(sps_format_t format);
typedef struct {
- double resend_control_first_check_time; // wait this long before asking for a missing packet to be resent
+ double missing_port_dacp_scan_interval_seconds; // if no DACP port number can be found, check at
+ // these intervals
+ double resend_control_first_check_time; // wait this long before asking for a missing packet to be
+ // resent
double resend_control_check_interval_time; // wait this long between making requests
- double resend_control_last_check_time; // if the packet is missing this close to the time of use, give up
+ double resend_control_last_check_time; // if the packet is missing this close to the time of use,
+ // give up
pthread_mutex_t lock;
config_t *cfg;
int endianness;
@@ -113,6 +117,7 @@ typedef struct {
// on host %h"
#ifdef CONFIG_PA
+ char *pa_server; // the pulseaudio server address that Shairport Sync will play on.
char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in
// the Sound Preferences in most desktop Linuxes.
// Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
@@ -177,6 +182,8 @@ typedef struct {
char *pidfile;
#endif
+ int log_fd; // file descriptor of the file or pipe to log stuff to.
+ char *log_file_path; // path to file or pipe to log to, if any
int logOutputLevel; // log output level
int debugger_show_elapsed_time; // in the debug message, display the time since startup
int debugger_show_relative_time; // in the debug message, display the time since the last one
@@ -212,7 +219,11 @@ typedef struct {
double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any
// fixed latency there might be in the audio path
+ int audio_backend_silent_lead_in_time_auto; // true if the lead-in time should be from as soon as
+ // packets are received
double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
+ uint32_t minimum_free_buffer_headroom; // when effective latency is calculated, ensure this number
+ // of buffers are unallocated
double active_state_timeout; // the amount of time from when play ends to when the system leaves
// into the "active" mode.
uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
@@ -274,7 +285,10 @@ typedef struct {
int jack_soxr_resample_quality;
#endif
#endif
-
+ void *gradients; // a linked list of the clock gradients discovered for all DACP IDs
+ // can't use IP numbers as they might be given to different devices
+ // can't get hold of MAC addresses.
+ // can't define the nvll linked list struct here
} shairport_cfg;
// accessors to config for multi-thread access
@@ -286,7 +300,12 @@ uint16_t nctohs(const uint8_t *p); // read 2 characters from *p and do ntohs on
void memory_barrier();
-void log_to_stderr(); // call this to director logging to stderr;
+void log_to_stderr(); // call this to direct logging to stderr;
+void log_to_stdout(); // call this to direct logging to stdout;
+void log_to_syslog(); // call this to direct logging to the system log;
+void log_to_file(); // call this to direct logging to a file or (pre-existing) pipe;
+
+
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
@@ -294,10 +313,7 @@ int get_requested_connection_state_to_output();
void set_requested_connection_state_to_output(int v);
-ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count,
- int timeout); // timeout in milliseconds
-
-ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places
+int try_to_open_pipe_for_writing(const char* pathname); // open it without blocking if it's not hooked up
/* from
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
@@ -349,10 +365,11 @@ double flat_vol2attn(double vol, long max_db, long min_db);
double vol2attn(double vol, long max_db, long min_db);
// return a monolithic (always increasing) time in nanoseconds
-uint64_t get_absolute_time_in_fp(void);
+// uint64_t get_absolute_time_in_fp(void); // obselete
+uint64_t get_absolute_time_in_ns(void);
// time at startup for debugging timing
-extern uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
+extern uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
@@ -363,6 +380,7 @@ extern pthread_t main_thread_id;
extern shairport_cfg config;
extern config_t config_file_stuff;
+extern int emergency_exit;
int config_set_lookup_bool(config_t *cfg, char *where, int *dst);
@@ -420,9 +438,6 @@ extern pthread_mutex_t r64_mutex;
char *get_version_string(); // mallocs a string space -- remember to free it afterwards
-void sps_nanosleep(const time_t sec,
- const long nanosec); // waits for this time, even through interruptions
-
int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t format,
int with_dither, int64_t random_number_in);
@@ -430,4 +445,7 @@ void malloc_cleanup(void *arg);
int string_update_with_size(char **str, int *flag, char *s, size_t len);
+// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
+void* memdup(const void* mem, size_t size);
+
#endif // _COMMON_H
diff --git a/dacp.c b/dacp.c
index f3111a83..c6b9968f 100644
--- a/dacp.c
+++ b/dacp.c
@@ -1,6 +1,6 @@
/*
* DACP protocol handler. This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2017 -- 2019
+ * Copyright (c) Mike Brady 2017 -- 2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -49,7 +49,7 @@
typedef struct {
int players_connection_thread_index; // the connection thread index when a player thread is
// associated with this, zero otherwise
- int scan_enable; // set to 1 if if sacanning should be considered
+ int scan_enable; // set to 1 if scanning should be considered
char dacp_id[256]; // the DACP ID string
uint16_t port; // zero if no port discovered
short connection_family; // AF_INET6 or AF_INET
@@ -123,7 +123,7 @@ static const struct http_funcs responseFuncs = {
// static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t dacp_conversation_lock;
static pthread_mutex_t dacp_server_information_lock;
-static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
+static pthread_cond_t dacp_server_information_cv;
void addrinfo_cleanup(void *arg) {
// debug(1, "addrinfo cleanup called.");
@@ -154,7 +154,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"dacp_send_command: command is: \"%s\".",command);
if (dacp_server.port == 0) {
- debug(1, "No DACP port specified yet");
+ debug(3, "No DACP port specified yet");
result = 490; // no port specified
} else {
@@ -169,6 +169,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// 493 Client failed to send a message
// 492 Argument out of range
// 491 Client refused connection
+ // 490 No port specified
struct addrinfo hints, *res;
int sockfd;
@@ -201,7 +202,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
response.code = 498; // Bad Address information for the DACP server
} else {
- uint64_t start_time = get_absolute_time_in_fp();
+ uint64_t start_time = get_absolute_time_in_ns();
pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
// only do this one at a time -- not sure it is necessary, but better safe than sorry
@@ -221,18 +222,16 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
// debug(2, "dacp_send_command: open socket %d.",sockfd);
-
// This is for limiting the time to be spent waiting for a response.
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500000;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
+ debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
debug(1, "dacp_send_command: error %d setting send timeout.", errno);
-
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
@@ -352,10 +351,9 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
}
pthread_cleanup_pop(1); // this should free the addrinfo
// freeaddrinfo(res);
- uint64_t et = get_absolute_time_in_fp() - start_time;
- et = (et * 1000000) >> 32; // microseconds
+ uint64_t et = get_absolute_time_in_ns() - start_time; // this will be in nanoseconds
debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
- (1.0 * et) / 1000000, response.code, command);
+ (1.0 * et) / 1000000000, response.code, command);
}
*body = response.body;
*bodysize = response.size;
@@ -418,13 +416,11 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
// which return immediately with a 403 code if there are no changes.
dacp_server.always_use_revision_number_1 = 0;
char *p = strstr(conn->UserAgent, "forked-daapd");
- if ((p != 0) && (p == conn->UserAgent)) {// must exist and be at the start of the UserAgent string
+ if ((p != 0) &&
+ (p == conn->UserAgent)) { // must exist and be at the start of the UserAgent string
dacp_server.always_use_revision_number_1 = 1;
}
-
- mdns_dacp_monitor_set_id(dacp_server.dacp_id);
-
metadata_hub_modify_prolog();
int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
metadata_store.dacp_server_active = dacp_server.scan_enable;
@@ -508,381 +504,418 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
(metadata_store.advanced_dacp_server_active != 0);
metadata_store.dacp_server_active = 0;
metadata_store.advanced_dacp_server_active = 0;
- debug(2,
+ debug(3,
"setting metadata_store.dacp_server_active and "
"metadata_store.advanced_dacp_server_active to 0 with an update "
"flag value of %d",
ch);
metadata_hub_modify_epilog(ch);
- while (dacp_server.scan_enable == 0) {
- // debug(1, "dacp_monitor_thread_code wait for an event to possible enable scan");
- pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
- // debug(1,"dacp_monitor_thread_code wake up.");
+
+ uint64_t time_to_wait_for_wakeup_ns =
+ (uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
+ uint64_t sec = time_of_wakeup_ns / 1000000000;
+ uint64_t nsec = time_of_wakeup_ns % 1000000000;
+#endif
+#ifdef COMPILE_FOR_OSX
+ uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
+ uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
+#endif
+
+ struct timespec time_to_wait;
+ time_to_wait.tv_sec = sec;
+ time_to_wait.tv_nsec = nsec;
+
+ while ((dacp_server.scan_enable == 0) && (result != ETIMEDOUT)) {
+ // debug(1, "dacp_monitor_thread_code wait for an event to possibly enable scan");
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ result = pthread_cond_timedwait(&dacp_server_information_cv, &dacp_server_information_lock,
+ &time_to_wait); // this is a pthread cancellation point
+ // debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %"
+ // PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);
+
+#endif
+#ifdef COMPILE_FOR_OSX
+ result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv,
+ &dacp_server_information_lock, &time_to_wait);
+#endif
+ }
+ if (dacp_server.scan_enable == 1) {
+ bad_result_count = 0;
+ idle_scan_count = 0;
}
- // so dacp_server.scan_enable will be true at this point
- bad_result_count = 0;
- idle_scan_count = 0;
}
- always_use_revision_number_1 = dacp_server.always_use_revision_number_1; // set this while access is locked
+ always_use_revision_number_1 =
+ dacp_server.always_use_revision_number_1; // set this while access is locked
result = dacp_get_volume(&the_volume); // just want the http code
pthread_cleanup_pop(1);
- scan_index++;
- // debug(1,"DACP Scan Result: %d.", result);
-
- if ((result == 200) || (result == 400)) {
- bad_result_count = 0;
+ if (result == 490) { // 490 means no port was specified
+ if (((char *)dacp_server.dacp_id != NULL) && (strlen(dacp_server.dacp_id) != 0)) {
+ // debug(1,"mdns_dacp_monitor_set_id");
+ mdns_dacp_monitor_set_id(dacp_server.dacp_id);
+ }
} else {
- if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
- bad_result_count++;
- }
+ scan_index++;
+ // debug(1,"DACP Scan Result: %d.", result);
- // here, do the debouncing calculations to see
- // if the dacp server and the
- // advanced dacp server are available
+ if ((result == 200) || (result == 400)) {
+ bad_result_count = 0;
+ } else {
+ if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
+ bad_result_count++;
+ }
- // -1 means we don't know because some bad statuses have been reported
- // 0 means definitely no
- // +1 means definitely yes
+ // here, do the debouncing calculations to see
+ // if the dacp server and the
+ // advanced dacp server are available
- int dacp_server_status_now = -1;
- int advanced_dacp_server_status_now = -1;
+ // -1 means we don't know because some bad statuses have been reported
+ // 0 means definitely no
+ // +1 means definitely yes
- if (bad_result_count == 0) {
- dacp_server_status_now = 1;
- if (result == 200)
- advanced_dacp_server_status_now = 1;
- else if (result == 400)
+ int dacp_server_status_now = -1;
+ int advanced_dacp_server_status_now = -1;
+
+ if (bad_result_count == 0) {
+ dacp_server_status_now = 1;
+ if (result == 200)
+ advanced_dacp_server_status_now = 1;
+ else if (result == 400)
+ advanced_dacp_server_status_now = 0;
+ } else if (bad_result_count ==
+ config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
+ // then it's gone
+ dacp_server_status_now = 0;
advanced_dacp_server_status_now = 0;
- } else if (bad_result_count ==
- config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
- // then it's gone
- dacp_server_status_now = 0;
- advanced_dacp_server_status_now = 0;
- }
-
- if (metadata_store.player_thread_active == 0)
- idle_scan_count++;
- else
- idle_scan_count = 0;
-
- debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
- idle_scan_count);
-
- /* not used
- // decide if idle for too long
- if (idle_scan_count == config.scan_max_inactive_count) {
- debug(2, "DACP server status scanning stopped.");
- dacp_server.scan_enable = 0;
- }
- */
-
- int update_needed = 0;
- metadata_hub_modify_prolog();
- if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
- if (metadata_store.dacp_server_active != dacp_server_status_now) {
- debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
- metadata_store.dacp_server_active = dacp_server_status_now;
- update_needed = 1;
}
- }
- if (advanced_dacp_server_status_now !=
- -1) { // if advanced_dacp_server_status_now is actually known...
- if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
- debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
- metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
- update_needed = 1;
- }
- }
- metadata_hub_modify_epilog(update_needed);
+ if (metadata_store.player_thread_active == 0)
+ idle_scan_count++;
+ else
+ idle_scan_count = 0;
- // pthread_mutex_unlock(&dacp_server_information_lock);
- // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
- // dacp_server.ip_string, dacp_server.port, scan_index);
+ debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result,
+ bad_result_count, idle_scan_count);
- if (result == 200) {
- metadata_hub_modify_prolog();
- int diff = metadata_store.speaker_volume != the_volume;
- if (diff)
- metadata_store.speaker_volume = the_volume;
- metadata_hub_modify_epilog(diff);
-
- ssize_t le;
- char *response = NULL;
- int32_t item_size;
- char command[1024] = "";
- if (always_use_revision_number_1 != 0) // for forked-daapd
- revision_number = 1;
- snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
- revision_number);
- // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
- result = dacp_send_command(command, &response, &le);
- // debug(1,"Response to \"%s\" is %d.",command,result);
- // remember: unless the revision_number you pass in is 1,
- // response will be 200 only if there's something new to report.
- if (result == 200) {
- // if (0) {
- char *sp = response;
- if (le >= 8) {
- // here start looking for the contents of the status update
- if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
- // here, we know that we are receiving playerstatusupdates, so set a flag
- metadata_hub_modify_prolog();
- // debug(1, "playstatusupdate release track metadata");
- // metadata_hub_reset_track_metadata();
- // metadata_store.playerstatusupdates_are_received = 1;
- sp -= item_size; // drop down into the array -- don't skip over it
- le -= 8;
- // char typestring[5];
- // we need to acquire the metadata data structure and possibly update it
- while (le >= 8) {
- uint32_t type = dacp_tlv_crawl(&sp, &item_size);
- le -= item_size + 8;
- char *t;
- // char u;
- // char *st;
- int32_t r;
- uint32_t ui;
- // uint64_t v;
- // int i;
-
- switch (type) {
- case 'cmsr': // revision number
- t = sp - item_size;
- revision_number = ntohl(*(uint32_t *)(t));
- // debug(1,"New revision number received: %d", revision_number);
- break;
- case 'caps': // play status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 2:
- if (metadata_store.play_status != PS_STOPPED) {
- metadata_store.play_status = PS_STOPPED;
- debug(2, "Play status is \"stopped\".");
- }
- break;
- case 3:
- if (metadata_store.play_status != PS_PAUSED) {
- metadata_store.play_status = PS_PAUSED;
- debug(2, "Play status is \"paused\".");
- }
- break;
- case 4:
- if (metadata_store.play_status != PS_PLAYING) {
- metadata_store.play_status = PS_PLAYING;
- debug(2, "Play status changed to \"playing\".");
- }
- break;
- default:
- debug(1, "Unrecognised play status %d received.", r);
- break;
- }
- break;
- case 'cash': // shuffle status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 0:
- if (metadata_store.shuffle_status != SS_OFF) {
- metadata_store.shuffle_status = SS_OFF;
- debug(2, "Shuffle status is \"off\".");
- }
- break;
- case 1:
- if (metadata_store.shuffle_status != SS_ON) {
- metadata_store.shuffle_status = SS_ON;
- debug(2, "Shuffle status is \"on\".");
- }
- break;
- default:
- debug(1, "Unrecognised shuffle status %d received.", r);
- break;
- }
- break;
- case 'carp': // repeat status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 0:
- if (metadata_store.repeat_status != RS_OFF) {
- metadata_store.repeat_status = RS_OFF;
- debug(2, "Repeat status is \"none\".");
- }
- break;
- case 1:
- if (metadata_store.repeat_status != RS_ONE) {
- metadata_store.repeat_status = RS_ONE;
- debug(2, "Repeat status is \"one\".");
- }
- break;
- case 2:
- if (metadata_store.repeat_status != RS_ALL) {
- metadata_store.repeat_status = RS_ALL;
- debug(2, "Repeat status is \"all\".");
- }
- break;
- default:
- debug(1, "Unrecognised repeat status %d received.", r);
- break;
- }
- break;
- case 'cann': // track name
- debug(2, "DACP Track Name seen");
- if (string_update_with_size(&metadata_store.track_name,
- &metadata_store.track_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
- }
- break;
- case 'cana': // artist name
- debug(2, "DACP Artist Name seen");
- if (string_update_with_size(&metadata_store.artist_name,
- &metadata_store.artist_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
- }
- break;
- case 'canl': // album name
- debug(2, "DACP Album Name seen");
- if (string_update_with_size(&metadata_store.album_name,
- &metadata_store.album_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
- }
- break;
- case 'cang': // genre
- debug(2, "DACP Genre seen");
- if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
- sp - item_size, item_size)) {
- debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
- }
- break;
- case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
- // see reference above)
- debug(2, "DACP Composite ID seen");
- if (memcmp(metadata_store.item_composite_id, sp - item_size,
- sizeof(metadata_store.item_composite_id)) != 0) {
- memcpy(metadata_store.item_composite_id, sp - item_size,
- sizeof(metadata_store.item_composite_id));
- char st[33];
- char *pt = st;
- int it;
- for (it = 0; it < 16; it++) {
- snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
- pt += 2;
- }
- *pt = 0;
- debug(2, "Item composite ID changed to 0x%s.", st);
- metadata_store.item_composite_id_changed = 1;
- }
- break;
- case 'astm':
- t = sp - item_size;
- ui = ntohl(*(uint32_t *)(t));
- debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
- if (ui != metadata_store.songtime_in_milliseconds) {
- metadata_store.songtime_in_milliseconds = ui;
- metadata_store.songtime_in_milliseconds_changed = 1;
- debug(2, "DACP Song Time set to: \"%u\"",
- metadata_store.songtime_in_milliseconds);
- }
- break;
-
- /*
- case 'mstt':
- case 'cant':
- case 'cast':
- case 'cmmk':
- case 'caas':
- case 'caar':
- t = sp - item_size;
- r = ntohl(*(uint32_t *)(t));
- printf(" %d", r);
- printf(" (0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- printf(")");
- break;
- case 'asai':
- t = sp - item_size;
- s = ntohl(*(uint32_t *)(t));
- s = s << 32;
- t += 4;
- v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
- s += v;
- printf(" %lu", s);
- printf(" (0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- printf(")");
- break;
- */
- default:
- /*
- printf(" 0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- */
- break;
- }
- // printf("\n");
- }
-
- // finished possibly writing to the metadata hub
- metadata_hub_modify_epilog(
- 1); // should really see if this can be made responsive to changes
- } else {
- debug(1, "Status Update not found.\n");
+ /* not used
+ // decide if idle for too long
+ if (idle_scan_count == config.scan_max_inactive_count) {
+ debug(2, "DACP server status scanning stopped.");
+ dacp_server.scan_enable = 0;
}
- } else {
- debug(1, "Can't find any content in playerstatusupdate request");
+ */
+
+ int update_needed = 0;
+ metadata_hub_modify_prolog();
+ if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
+ if (metadata_store.dacp_server_active != dacp_server_status_now) {
+ debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
+ metadata_store.dacp_server_active = dacp_server_status_now;
+ update_needed = 1;
}
- } /* else {
- if (result != 403)
- debug(1, "Unexpected response %d to playerstatusupdate request", result);
- } */
+ }
+ if (advanced_dacp_server_status_now !=
+ -1) { // if advanced_dacp_server_status_now is actually known...
+ if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
+ debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
+ metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
+ update_needed = 1;
+ }
+ }
+
+ metadata_hub_modify_epilog(update_needed);
+
+ // pthread_mutex_unlock(&dacp_server_information_lock);
+ // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
+ // dacp_server.ip_string, dacp_server.port, scan_index);
+
+ if (result == 200) {
+ metadata_hub_modify_prolog();
+ int diff = metadata_store.speaker_volume != the_volume;
+ if (diff)
+ metadata_store.speaker_volume = the_volume;
+ metadata_hub_modify_epilog(diff);
+
+ ssize_t le;
+ char *response = NULL;
+ int32_t item_size;
+ char command[1024] = "";
+ if (always_use_revision_number_1 != 0) // for forked-daapd
+ revision_number = 1;
+ snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
+ revision_number);
+ // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
+ result = dacp_send_command(command, &response, &le);
+ // debug(1,"Response to \"%s\" is %d.",command,result);
+ // remember: unless the revision_number you pass in is 1,
+ // response will be 200 only if there's something new to report.
+ if (result == 200) {
+ // if (0) {
+ char *sp = response;
+ if (le >= 8) {
+ // here start looking for the contents of the status update
+ if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
+ // here, we know that we are receiving playerstatusupdates, so set a flag
+ metadata_hub_modify_prolog();
+ // debug(1, "playstatusupdate release track metadata");
+ // metadata_hub_reset_track_metadata();
+ // metadata_store.playerstatusupdates_are_received = 1;
+ sp -= item_size; // drop down into the array -- don't skip over it
+ le -= 8;
+ // char typestring[5];
+ // we need to acquire the metadata data structure and possibly update it
+ while (le >= 8) {
+ uint32_t type = dacp_tlv_crawl(&sp, &item_size);
+ le -= item_size + 8;
+ char *t;
+ // char u;
+ // char *st;
+ int32_t r;
+ uint32_t ui;
+ // uint64_t v;
+ // int i;
+
+ switch (type) {
+ case 'cmsr': // revision number
+ t = sp - item_size;
+ revision_number = ntohl(*(uint32_t *)(t));
+ // debug(1,"New revision number received: %d", revision_number);
+ break;
+ case 'caps': // play status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 2:
+ if (metadata_store.play_status != PS_STOPPED) {
+ metadata_store.play_status = PS_STOPPED;
+ debug(2, "Play status is \"stopped\".");
+ }
+ break;
+ case 3:
+ if (metadata_store.play_status != PS_PAUSED) {
+ metadata_store.play_status = PS_PAUSED;
+ debug(2, "Play status is \"paused\".");
+ }
+ break;
+ case 4:
+ if (metadata_store.play_status != PS_PLAYING) {
+ metadata_store.play_status = PS_PLAYING;
+ debug(2, "Play status changed to \"playing\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised play status %d received.", r);
+ break;
+ }
+ break;
+ case 'cash': // shuffle status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 0:
+ if (metadata_store.shuffle_status != SS_OFF) {
+ metadata_store.shuffle_status = SS_OFF;
+ debug(2, "Shuffle status is \"off\".");
+ }
+ break;
+ case 1:
+ if (metadata_store.shuffle_status != SS_ON) {
+ metadata_store.shuffle_status = SS_ON;
+ debug(2, "Shuffle status is \"on\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised shuffle status %d received.", r);
+ break;
+ }
+ break;
+ case 'carp': // repeat status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 0:
+ if (metadata_store.repeat_status != RS_OFF) {
+ metadata_store.repeat_status = RS_OFF;
+ debug(2, "Repeat status is \"none\".");
+ }
+ break;
+ case 1:
+ if (metadata_store.repeat_status != RS_ONE) {
+ metadata_store.repeat_status = RS_ONE;
+ debug(2, "Repeat status is \"one\".");
+ }
+ break;
+ case 2:
+ if (metadata_store.repeat_status != RS_ALL) {
+ metadata_store.repeat_status = RS_ALL;
+ debug(2, "Repeat status is \"all\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised repeat status %d received.", r);
+ break;
+ }
+ break;
+ case 'cann': // track name
+ debug(2, "DACP Track Name seen");
+ if (string_update_with_size(&metadata_store.track_name,
+ &metadata_store.track_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
+ }
+ break;
+ case 'cana': // artist name
+ debug(2, "DACP Artist Name seen");
+ if (string_update_with_size(&metadata_store.artist_name,
+ &metadata_store.artist_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
+ }
+ break;
+ case 'canl': // album name
+ debug(2, "DACP Album Name seen");
+ if (string_update_with_size(&metadata_store.album_name,
+ &metadata_store.album_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
+ }
+ break;
+ case 'cang': // genre
+ debug(2, "DACP Genre seen");
+ if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
+ sp - item_size, item_size)) {
+ debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
+ }
+ break;
+ case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
+ // see reference above)
+ debug(2, "DACP Composite ID seen");
+ if (memcmp(metadata_store.item_composite_id, sp - item_size,
+ sizeof(metadata_store.item_composite_id)) != 0) {
+ memcpy(metadata_store.item_composite_id, sp - item_size,
+ sizeof(metadata_store.item_composite_id));
+ char st[33];
+ char *pt = st;
+ int it;
+ for (it = 0; it < 16; it++) {
+ snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
+ pt += 2;
+ }
+ *pt = 0;
+ debug(2, "Item composite ID changed to 0x%s.", st);
+ metadata_store.item_composite_id_changed = 1;
+ }
+ break;
+ case 'astm':
+ t = sp - item_size;
+ ui = ntohl(*(uint32_t *)(t));
+ debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
+ if (ui != metadata_store.songtime_in_milliseconds) {
+ metadata_store.songtime_in_milliseconds = ui;
+ metadata_store.songtime_in_milliseconds_changed = 1;
+ debug(2, "DACP Song Time set to: \"%u\"",
+ metadata_store.songtime_in_milliseconds);
+ }
+ break;
+
+ /*
+ case 'mstt':
+ case 'cant':
+ case 'cast':
+ case 'cmmk':
+ case 'caas':
+ case 'caar':
+ t = sp - item_size;
+ r = ntohl(*(uint32_t *)(t));
+ printf(" %d", r);
+ printf(" (0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ printf(")");
+ break;
+ case 'asai':
+ t = sp - item_size;
+ s = ntohl(*(uint32_t *)(t));
+ s = s << 32;
+ t += 4;
+ v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
+ s += v;
+ printf(" %lu", s);
+ printf(" (0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ printf(")");
+ break;
+ */
+ default:
+ /*
+ printf(" 0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ */
+ break;
+ }
+ // printf("\n");
+ }
+
+ // finished possibly writing to the metadata hub
+ metadata_hub_modify_epilog(
+ 1); // should really see if this can be made responsive to changes
+ } else {
+ debug(1, "Status Update not found.\n");
+ }
+ } else {
+ debug(1, "Can't find any content in playerstatusupdate request");
+ }
+ } /* else {
+ if (result != 403)
+ debug(1, "Unexpected response %d to playerstatusupdate request", result);
+ } */
+ if (response) {
+ free(response);
+ response = NULL;
+ };
+ };
+ /*
+ strcpy(command,"nowplayingartwork?mw=320&mh=320");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
if (response) {
free(response);
response = NULL;
- };
- };
- /*
- strcpy(command,"nowplayingartwork?mw=320&mh=320");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
+ }
+ strcpy(command,"getproperty?properties=dmcp.volume");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ strcpy(command,"setproperty?dmcp.volume=100.000000");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ */
+ if (metadata_store.player_thread_active)
+ sleep(config.scan_interval_when_active);
+ else
+ sleep(config.scan_interval_when_inactive);
}
- strcpy(command,"getproperty?properties=dmcp.volume");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- strcpy(command,"setproperty?dmcp.volume=100.000000");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- */
- if (metadata_store.player_thread_active)
- sleep(config.scan_interval_when_active);
- else
- sleep(config.scan_interval_when_inactive);
}
debug(1, "DACP monitor thread exiting -- should never happen.");
pthread_exit(NULL);
@@ -890,6 +923,20 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
void dacp_monitor_start() {
int rc;
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ pthread_condattr_t attr;
+ pthread_condattr_init(&attr);
+ pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
+ rc = pthread_cond_init(&dacp_server_information_cv, &attr);
+ if (rc)
+ debug(1, "Error initialising the DACP Server Information Condition Variable");
+ pthread_condattr_destroy(&attr);
+#endif
+#ifdef COMPILE_FOR_OSX
+ rc = pthread_cond_init(&dacp_server_information_cv, NULL);
+#endif
+
pthread_mutexattr_t mta;
rc = pthread_mutexattr_init(&mta);
@@ -948,6 +995,8 @@ void dacp_monitor_stop() {
pthread_mutex_destroy(&dacp_server_information_lock);
debug(3, "DACP Conversation Lock Mutex Destroyed");
pthread_mutex_destroy(&dacp_conversation_lock);
+ pthread_cond_destroy(&dacp_server_information_cv);
+ debug(3, "DACP Server Information Condition Variable destroyed.");
}
}
@@ -1200,7 +1249,7 @@ int dacp_get_volume(int32_t *the_actual_volume) {
} else {
debug(1, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
}
- } else {
+ } else if (http_response != 400) {
debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response);
}
if (the_actual_volume) {
diff --git a/dbus-service.c b/dbus-service.c
index 457aefed..bbd5dcd4 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -24,10 +24,10 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
+#include
#include
#include
#include
-#include
#include "config.h"
@@ -69,8 +69,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairport_sync_remote_control_set_client(shairportSyncRemoteControlSkeleton, argc->client_ip);
-
- // although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their term).
+ // although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their
+ // term).
if (argc->dacp_server_active) {
shairport_sync_remote_control_set_available(shairportSyncRemoteControlSkeleton, TRUE);
} else {
@@ -166,7 +166,6 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairportSyncAdvancedRemoteControlSkeleton, response);
}
-
switch (argc->shuffle_status) {
case SS_NOT_AVAILABLE:
new_status = FALSE;
@@ -188,8 +187,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
// only set this if it's different
if (current_status != new_status) {
debug(3, "Shuffle State should be changed");
- shairport_sync_advanced_remote_control_set_shuffle(
- shairportSyncAdvancedRemoteControlSkeleton, new_status);
+ shairport_sync_advanced_remote_control_set_shuffle(shairportSyncAdvancedRemoteControlSkeleton,
+ new_status);
}
// Build the metadata array
@@ -369,8 +368,9 @@ static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
}
static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleton,
- GDBusMethodInvocation *invocation, const gdouble volume,
- __attribute__((unused)) gpointer user_data) {
+ GDBusMethodInvocation *invocation,
+ const gdouble volume,
+ __attribute__((unused)) gpointer user_data) {
debug(2, "Set airplay volume to %.6f.", volume);
char command[256] = "";
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume);
@@ -379,8 +379,6 @@ static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleto
return TRUE;
}
-
-
gboolean notify_elapsed_time_callback(ShairportSyncDiagnostics *skeleton,
__attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_elapsed_time_callback\" called.");
@@ -408,7 +406,7 @@ gboolean notify_delta_time_callback(ShairportSyncDiagnostics *skeleton,
}
gboolean notify_file_and_line_callback(ShairportSyncDiagnostics *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_file_and_line_callback\" called.");
if (shairport_sync_diagnostics_get_file_and_line(skeleton)) {
config.debugger_show_file_and_line = 1;
@@ -463,12 +461,13 @@ gboolean notify_disable_standby_callback(ShairportSync *skeleton,
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_convolution_callback\" called.");
if (shairport_sync_get_convolution(skeleton)) {
debug(1, ">> activating convolution");
config.convolution = 1;
- config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ config.convolver_valid =
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
} else {
debug(1, ">> deactivating convolution");
config.convolution = 0;
@@ -477,7 +476,7 @@ gboolean notify_convolution_callback(ShairportSync *skeleton,
}
#else
gboolean notify_convolution_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
warn(">> Convolution support is not built in to this build of Shairport Sync.");
return TRUE;
}
@@ -485,7 +484,7 @@ gboolean notify_convolution_callback(__attribute__((unused)) ShairportSync *skel
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_gain_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
gdouble th = shairport_sync_get_convolution_gain(skeleton);
if ((th <= 0.0) && (th >= -100.0)) {
@@ -499,34 +498,38 @@ gboolean notify_convolution_gain_callback(ShairportSync *skeleton,
}
#else
gboolean notify_convolution_gain_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
warn(">> Convolution support is not built in to this build of Shairport Sync.");
return TRUE;
}
#endif
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_impulse_response_file_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused))
+ gpointer user_data) {
char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
if (config.convolution_ir_file)
free(config.convolution_ir_file);
config.convolution_ir_file = strdup(th);
- debug(1, ">> setting configuration impulse response filter file to \"%s\".", config.convolution_ir_file);
- config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ debug(1, ">> setting configuration impulse response filter file to \"%s\".",
+ config.convolution_ir_file);
+ config.convolver_valid =
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
return TRUE;
}
#else
-gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
- __attribute__((unused)) char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
+gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused))
+ ShairportSync *skeleton,
+ __attribute__((unused))
+ gpointer user_data) {
+ __attribute__((unused)) char *th =
+ (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
return TRUE;
}
#endif
-
-
gboolean notify_loudness_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_loudness_callback\" called.");
if (shairport_sync_get_loudness(skeleton)) {
debug(1, ">> activating loudness");
@@ -781,7 +784,6 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
return TRUE;
}
-
static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name,
__attribute__((unused)) gpointer user_data) {
@@ -823,8 +825,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
G_CALLBACK(notify_convolution_gain_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::convolution-impulse-response-file",
G_CALLBACK(notify_convolution_impulse_response_file_callback), NULL);
- g_signal_connect(shairportSyncSkeleton, "notify::loudness",
- G_CALLBACK(notify_loudness_callback), NULL);
+ g_signal_connect(shairportSyncSkeleton, "notify::loudness", G_CALLBACK(notify_loudness_callback),
+ NULL);
g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold",
G_CALLBACK(notify_loudness_threshold_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::drift-tolerance",
@@ -879,7 +881,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-set-airplay-volume",
G_CALLBACK(on_handle_set_airplay_volume), NULL);
-
g_signal_connect(shairportSyncAdvancedRemoteControlSkeleton, "handle-set-volume",
G_CALLBACK(on_handle_set_volume), NULL);
@@ -975,9 +976,11 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
shairport_sync_set_convolution(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
}
if (config.convolution_ir_file)
- shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), config.convolution_ir_file);
+ shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton),
+ config.convolution_ir_file);
// else
-// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), NULL);
+// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton),
+// NULL);
#endif
shairport_sync_set_version(SHAIRPORT_SYNC(shairportSyncSkeleton), PACKAGE_VERSION);
@@ -1047,14 +1050,14 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
__attribute__((unused)) const gchar *name,
__attribute__((unused)) gpointer user_data) {
- warn("Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", name,
+ warn("could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", name,
(config.dbus_service_bus_type == DBT_session) ? "session" : "system");
}
static void on_dbus_name_lost(__attribute__((unused)) GDBusConnection *connection,
__attribute__((unused)) const gchar *name,
__attribute__((unused)) gpointer user_data) {
- // debug(1, "Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus --
+ // debug(1, "could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus --
// will try adding the process "
// "number to the end of it.",
// name, (config.dbus_service_bus_type == DBT_session) ? "session" : "system");
diff --git a/man/shairport-sync.7 b/man/shairport-sync.7
index 9048b551..7446662c 100644
--- a/man/shairport-sync.7
+++ b/man/shairport-sync.7
@@ -35,8 +35,6 @@ Within the configuration file, settings are organised into groups, for example,
\fBname = "Mike's Boombox";\f1
-\fBinterpolation = "soxr";\f1
-
\fBpassword = "secret";\f1
\fBoutput_backend = "alsa";\f1
@@ -53,7 +51,7 @@ 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, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specified. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.
+Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the 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.
@@ -67,7 +65,7 @@ These are the settings available within the \fBgeneral\f1 group:
\fBname=\f1\fI"service_name"\f1\fB;\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 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.3.6" and \fB%V\f1 for the shairport-sync version string, e.g. "3.3.6-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
The default is "%H", which is replaced by the hostname with the first letter capitalised.
.TP
@@ -75,7 +73,7 @@ The default is "%H", which is replaced by the hostname with the first letter cap
Require the password \fIpassword\f1 to connect to the service. If you leave this setting commented out, no password is needed.
.TP
\fBinterpolation=\f1\fI"mode"\f1\fB;\f1
-Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly in synchrony with the player. The default mode, "basic", is normally almost completely inaudible. 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.
+Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly synchronised with the player. The "basic" mode is normally almost completely inaudible. 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. The default setting is "auto", which will choose "soxr" if support for it has been compiled into the build of Shairport Synce and if the CPU is fast enough. Otherwise, "basic" stuffing will be chosen.
.TP
\fBoutput_backend=\f1\fI"backend"\f1\fB;\f1
shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends -- the default is the first on this list. Only the "alsa", "sndio" and "pa" backends support synchronisation.
@@ -218,14 +216,14 @@ Specify the \fIname\f1 of the mixer control to be used by shairport-sync to cont
By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
.TP
\fBoutput_rate=\f1\fIframe rate\f1\fB;\f1
-Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available.
+Use this setting to specify the frame rate to output to the ALSA device. Allowable values are "auto" (default), 44100, 88200, 176400 and 352800. The device must have the capability to accept the rate you specify. There is no particular reason to use anything other than 44100 if it is available, and if "auto" is selected, the lowest of these rates available, starting at 44100, will be selected.
.TP
\fBoutput_format=\f1\fI"format"\f1\fB;\f1
-Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify.
+Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "auto" (default), "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify.
"S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16".
-If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodified except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
+If you are using a hardware mixer, S16 is fine, as audio will pass through Shairport Sync unmodified except for interpolation, but any of the higher-resolution formats are okay too. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, in order to get the lowest possible levels of dither. The "auto" setting will cause Shairport Sync to choose the highest resolution available.
.TP
\fBdisable_synchronization=\f1\fI"no"\f1\fB;\f1
This is an advanced setting and is for debugging only. Set to \fI"yes"\f1 to disable synchronization. Default is \fI"no"\f1. If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to audio buffer overflow or underflow.
@@ -326,10 +324,10 @@ The maximum packet size for any UDP metadata. This must be between 500 or 65000.
\fB"DIAGNOSTICS" SETTINGS\f1
.TP
\fBstatistics=\f1\fI"setting"\f1\fB;\f1
-Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
+Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information to the system log (or to \fISTDERR\f1 if the \fB-u\f1 command line option is chosen). The default is to disable statistics.
.TP
\fBlog_verbosity=\f1\fI0\f1\fB;\f1
-Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
+Use this to specify how much debugging information should sent to the system log (or to \fISTDERR\f1 if the \fB-u\f1 command line option is chosen). The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
.SH OPTIONS
This section is about the command-line options available in shairport-sync.
@@ -425,7 +423,7 @@ If you are running shairport-sync from the command line and want logs to appear
Print version information and exit.
.TP
\fB-v | --verbose\f1
-Print debug information. Repeat up to three times to get more detail.
+Print debug information to the system log, or or to \fISTDERR\f1 if the \fB-u\f1 command line option is also chosen. Repeat up to three times to get 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.
diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml
index 93b24146..e5dc1c56 100644
--- a/man/shairport-sync.7.xml
+++ b/man/shairport-sync.7.xml
@@ -7,7 +7,7 @@
Copyright (c) Mike Brady 2014-2019
All rights reserved.
-
+
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
@@ -15,10 +15,10 @@
copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
-
+
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
-
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -61,64 +61,63 @@
- Shairport Sync plays
- audio streamed from iTunes
- or from an AirPlay device to an ALSA-compatible audio output device (available on
- Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
+
Shairport Sync plays
+ audio streamed from iTunes
+ or from an AirPlay device to an ALSA-compatible audio output device (available on
+ Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
Linux), to a PulseAudio output stream or to Jack Audio.
-
+
Shairport Sync offers full audio synchronisation.
- Full audio synchronisation means that audio is played on the output device at exactly
+ Full audio synchronisation means that audio is played on the output device at exactly
the time specified by the audio source.
This means that if many devices are playing the same stream at the same
time, all the outputs will stay in synchrony with one another.
- This allows multiple devices to play the same source without getting out of step with
+ This allows multiple devices to play the same source without getting out of step with
one another, enabling, for example, simultaneous multi-room operation.
-
- Shairport Sync can stream synchronised audio to a unix
- pipe or to standard output, or to audio systems that do not provide timing
- information. This could perhaps be described as partial audio synchronisation, where
- synchronised audio is provided by Shairport Sync, but what happens to it in the
- subsequent processing chain, before it reaches the listener's ear, is outside the
+
+
Shairport Sync can stream synchronised audio to a unix
+ pipe or to standard output, or to audio systems that do not provide timing
+ information. This could perhaps be described as partial audio synchronisation, where
+ synchronised audio is provided by Shairport Sync, but what happens to it in the
+ subsequent processing chain, before it reaches the listener's ear, is outside the
control of shairport-sync.
- Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
+
Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
or socket.
- Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
- D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
+
Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
+ D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
metadata, including cover art, and can offer remote control of the audio source.
-
- Settings can be made using the configuration file (recommended for all new
+
+
Settings can be made using the configuration file (recommended for all new
installations) or by using command-line options.
-
+
The name of the Shairport Sync executable is shairport-sync .
Both names are used in these man pages.
-
+
-
-
+
+
You should use the configuration file for setting up Shairport Sync.
This file is usually shairport-sync.conf and is generally located in the
- System Configuration Directory, which is normally the /etc directory in
+ System Configuration Directory, which is normally the /etc directory in
Linux or the /usr/local/etc directory in BSD unixes.
You may need to have root privileges to modify it.
-
- (Note: Shairport Sync may have been compiled to use a different configuration
- directory. You can determine which by performing the command $ shairport-sync
- -V . One of the items in the output string is the value of the
+
+
(Note: Shairport Sync may have been compiled to use a different configuration
+ directory. You can determine which by performing the command $ shairport-sync
+ -V . One of the items in the output string is the value of the
sysconfdir ,
i.e. the System Configuration Directory.)
-
-
+
+
Within the configuration file, settings are organised into groups , for
example, there is a "general" group of
standard settings, and there is an "alsa" group with settings that pertain to the ALSA
back end. Here is an example of a typical configuration file:
-
+
general = {
name = "Mike's Boombox";
-
interpolation = "soxr";
password = "secret";
output_backend = "alsa";
};
@@ -127,20 +126,20 @@
output_device = "hw:0";
mixer_control_name = "PCM";
};
-
- Most settings have sensible default values, so -- as in the example above -- users
+
+
Most settings have sensible default values, so -- as in the example above -- users
generally only need to set (1) the service name, (2) a password (if desired) and
- (3) the output device. If the output device has a mixer that can be used for volume
- control, then (4) the volume control's name should be specified. It is highly
- desirable to use the output device's mixer for volume control, if available --
- response time is reduced to zero and the processor load is reduced. In the example
- above, "soxr" interpolation was also enabled.
-
- A sample configuration file with all possible settings, but with all of them
- commented out, is installed at shairport-sync.conf.sample , within the
- System Configuration Directory -- /etc in Linux,
+ (3) the output device. If the output device has a mixer that can be used for volume
+ control, then (4) 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 shairport-sync.conf.sample , within the
+ System Configuration Directory -- /etc in Linux,
/usr/local/etc in BSD unixes.
-
+
To retain backwards compatibility with previous versions of shairport-sync
you can use still use command line options, but any new features, etc. will
be available only via configuration file settings.
@@ -157,88 +156,92 @@
Use this service_name to identify this player in iTunes, etc.
The following substitutions are allowed:
%h for the computer's hostname,
- %H for the computer's hostname with the first letter capitalised (ASCII
+ %H for the computer's hostname with the first letter capitalised (ASCII
only),
- %v for the shairport-sync version number, e.g. "3.0.1" and
- %V for the shairport-sync version string, e.g.
- "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
- The default is "%H", which is replaced by the hostname with the first letter
+ %v for the shairport-sync version number, e.g. "3.3.6" and
+ %V for the shairport-sync version string, e.g.
+ "3.3.6-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
+ The default is "%H", which is replaced by the hostname with the first letter
capitalised.
-
+
password= "password" ;
- Require the password password to connect to the service. If you
+ Require the password password to connect to the service. If you
leave this setting commented out, no password is needed.
interpolation= "mode" ;
- Interpolate, or "stuff", the audio stream using the mode .
+ Interpolate, or "stuff", the audio stream using the mode .
Interpolation here refers to the
process of adding or removing frames of audio to or from the
- stream sent to the output device to keep it exactly in synchrony
+ stream sent to the output device to keep it exactly synchronised
with the player.
- The default mode, "basic", is normally almost completely inaudible.
+ The "basic" mode is normally almost completely inaudible.
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.
+ The default setting is "auto", which will choose "soxr" if support for it has been
+ compiled into the build of Shairport Synce and if the CPU is fast enough. Otherwise,
+ "basic" stuffing will be chosen.
+
-
+
output_backend= "backend" ;
- shairport-sync has a number of modules of code ("backends") through which
- audio is output. Normally, the first audio backend that works is selected. This
- setting forces the selection of the specific audio backend . Perform the
- command shairport-sync -h to get a list of available audio backends -- the
- default is the first on this list. Only the "alsa", "sndio" and "pa" backends support
+ shairport-sync has a number of modules of code ("backends") through which
+ audio is output. Normally, the first audio backend that works is selected. This
+ setting forces the selection of the specific audio backend . Perform the
+ command shairport-sync -h to get a list of available audio backends -- the
+ default is the first on this list. Only the "alsa", "sndio" and "pa" backends support
synchronisation.
mdns_backend= "backend" ;
- shairport-sync has a number of modules of code ("backends") for
- interacting with the mDNS service to be used to advertise itself. Normally, the first
- mDNS backend that works is selected. This setting forces the selection of the specific
- mDNS backend . The default is "avahi". Perform the command
+ shairport-sync has a number of modules of code ("backends") for
+ interacting with the mDNS service to be used to advertise itself. Normally, the first
+ mDNS backend that works is selected. This setting forces the selection of the specific
+ mDNS backend . The default is "avahi". Perform the command
shairport-sync -h to get a list of available mDNS modules.
port= portnumber ;
- Use this to specify the portnumber shairport-sync uses to
+ Use this to specify the portnumber shairport-sync uses to
listen for service requests from iTunes, etc. The default is port 5000.
udp_port_base= portnumber ;
- When shairport-sync starts to play audio, it establises three UDP
- connections to the audio source. Use this setting to specify the starting
- portnumber for these three ports. It will pick the first three unused ports
+ When shairport-sync starts to play audio, it establises three UDP
+ connections to the audio source. Use this setting to specify the starting
+ portnumber for these three ports. It will pick the first three unused ports
starting from portnumber . The default is port 6001.
udp_port_range= range ;
Use this in conjunction with the previous setting to specify the
- range of ports that can be checked for availability. Only three ports are
+ range of ports that can be checked for availability. Only three ports are
needed.
- The default is 100, thus 100 ports will be checked from port 6001 upwards until three
+ The default is 100, thus 100 ports will be checked from port 6001 upwards until three
are found.
drift_tolerance_in_seconds= seconds ;
- Allow playback to drift up to seconds out of exact
+ Allow playback to drift up to seconds out of exact
synchronization before attempting to correct it.
- The default is 0.002 seconds, i.e. 2 milliseconds. The smaller the tolerance, the more
+ The default is 0.002 seconds, i.e. 2 milliseconds. 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 statistics setting
- to monitor correction levels. Corrections should not greatly exceed net corrections.
+ Overcorrection is when more corrections (insertions and deletions) are made than are
+ strictly necessary to keep the stream in sync. Use the statistics setting
+ to monitor correction levels. Corrections should not greatly exceed net corrections.
This setting replaces the deprecated drift setting.
@@ -256,71 +259,71 @@
ignore_volume_control= "choice" ;
- Set this choice to "yes" if you want the volume to
+ Set this choice to "yes" if you want the volume to
be at 100% no matter what the source's volume control is set to.
- This might be useful if you want to set the volume on the output device, independently
+ This might be useful if you want to set the volume on the output device, independently
of the setting at the source. The default is "no" .
-
+
volume_range_db= dBvalue ;
- Use this dBvalue to reduce or increase the attenuation range,
+ Use this dBvalue to reduce or increase the attenuation range,
in decibels, between the minimum and maximum volume.
- For example, if a mixer has a minimum volume of -80 dB and a maximum of +20 dB, you
+
For example, if a mixer has a minimum volume of -80 dB and a maximum of +20 dB, you
might wish to use only 60 dB of the 100 dB available.
- This might be because the sound becomes inaudible at the lowest setting and unbearably
+ This might be because the sound becomes inaudible at the lowest setting and unbearably
loud at the highest setting --
indeed, many domestic HiFi systems have a volume control range of just 60 to 80dB.
- Another potential use might be where the range specified by the mixer does not
+
Another potential use might be where the range specified by the mixer does not
match the capabilities of the device.
- For example, the Raspberry Pi's DAC that feeds the built-in audio jack claims a range
+ For example, the Raspberry Pi's DAC that feeds the built-in audio jack claims a range
of 106 dB but has a useful range of only about 30 dB.
The setting allows you to specify the maximum range from highest to lowest.
- The range suggested for the Raspberry Pi's built-in audio DAC, which feeds the
+ The range suggested for the Raspberry Pi's built-in audio DAC, which feeds the
headphone jack, is 30.
- Using it in this case gives the volume control a much more useful range of
+ Using it in this case gives the volume control a much more useful range of
settings.
As a third example, you can actually extend the range provided by a mixer.
Many cheaper DACs have hardware mixers that offer a restricted attenuation range.
- If you specify a volume range greater than the range of the mixer, software
+ If you specify a volume range greater than the range of the mixer, software
attenuation and hardware attenuation will be combined to give the specified range.
If you omit this setting, the native range of the mixer is used.
volume_max_db= dBvalue ;
- Specify the maximum output level to be used with the hardware mixer, if
- used. If no hardware mixed is used, this setting specifies the maximum setting
- permissible in the software mixer, which has an attenuation range from 0.0 dB down to
+ Specify the maximum output level to be used with the hardware mixer, if
+ used. If no hardware mixed is used, this setting specifies the maximum setting
+ permissible in the software mixer, which has an attenuation range from 0.0 dB down to
-96.3 dB.
-
+
volume_control_profile= "choice" ;
- Use this advanced setting to specify how the airplay volume is transferred
- to the mixer volume. The "standard" profile, which is the default, makes
- the volume change more quickly at lower volumes and slower at higher volumes. Choose
- the "flat" profile to makes the volume change at the same rate at all
+ Use this advanced setting to specify how the airplay volume is transferred
+ to the mixer volume. The "standard" profile, which is the default, makes
+ the volume change more quickly at lower volumes and slower at higher volumes. Choose
+ the "flat" profile to makes the volume change at the same rate at all
volume levels.
-
+
volume_range_combined_hardware_priority=
"choice" ;
- Use this advanced setting to specify how to combine the hardware
- attenuator with software attenuation to provide a greater attenuation range than the
- hardware attenuator alone can provide. Choosing "yes" means that when
+ Use this advanced setting to specify how to combine the hardware
+ attenuator with software attenuation to provide a greater attenuation range than the
+ hardware attenuator alone can provide. Choosing "yes" means that when
attenuation is required, the hardware attenuator will be used in preference.
- If more attenuation than it can provide is needed, the hardware attenuator is set to
+ If more attenuation than it can provide is needed, the hardware attenuator is set to
its greatest attenuation and software attenuation is added.
- For example, if 40 dB of attenuation is required and the hardware attenuator
- offers a maximum of 30 dB, then the hardware attenuator will be set to give 30 dB
- attenuation and 10 dB of software attenuation will be added.
- Unfortunately, certain hardware attenuators will mute at their greatest
- attenuation, so can't be combined with software attenuation in this way. Choosing
- "no" means that software attenuation is used to bring the remaining
+
For example, if 40 dB of attenuation is required and the hardware attenuator
+ offers a maximum of 30 dB, then the hardware attenuator will be set to give 30 dB
+ attenuation and 10 dB of software attenuation will be added.
+ Unfortunately, certain hardware attenuators will mute at their greatest
+ attenuation, so can't be combined with software attenuation in this way. Choosing
+ "no" means that software attenuation is used to bring the remaining
attenuation required into the range offered by the hardware attenuator.
This is the default.
@@ -329,71 +332,71 @@
run_this_when_volume_is_set=
"/full/path/to/application/and/args" ;
- Here you can specify a program and its arguments that will be run when the
+ Here you can specify a program and its arguments that will be run when the
volume is set or changed. Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
+ The application must be marked as executable and, if it is a script, its first line
must begin with the standard shebang #!/bin/... as appropriate.
- The desired AirPlay volume is appended to the end of the command line -- leave a
- space at the end of the command line you specify here if you want it treated as an
+
The desired AirPlay volume is appended to the end of the command line -- leave a
+ space at the end of the command line you specify here if you want it treated as an
extra argument.
AirPlay volume goes from 0.0 to -30.0 and -144.0 means "mute".
regtype= "regTypeString" ;
- Use this advanced setting to set the service type and transport to be
+ Use this advanced setting to set the service type and transport to be
advertised by Zeroconf/Bonjour. Default is "_raop._tcp" .
-
+
playback_mode= "mode" ;
- The mode can be "stereo", "mono", "reverse stereo", "both left"
- or "both right". Default is "stereo". Note that dither will be added to the signal in
+ The mode can be "stereo", "mono", "reverse stereo", "both left"
+ or "both right". Default is "stereo". Note that dither will be added to the signal in
the mono mode.
-
+
alac_decoder= "decodername" ;
- This can be "hammerton" or "apple". This advanced setting allows you to
- choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio
- Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the
- configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be
+ This can be "hammerton" or "apple". This advanced setting allows you to
+ choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio
+ Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the
+ configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be
present for this to work.
interface= "name" ;
- Use this advanced setting if you want to confine Shairport Sync to the
+ Use this advanced setting if you want to confine Shairport Sync to the
named interface. Leave it commented out to get the default behaviour.
-
+
audio_backend_latency_offset_in_seconds=
offset_in_seconds ;
- Set this offset_in_seconds to compensate for a fixed delay in
- the audio back end. For example, if the output device delays by 100 ms, set this to
+ Set this offset_in_seconds to compensate for a fixed delay in
+ the audio back end. For example, if the output device delays by 100 ms, set this to
-0.1.
audio_backend_buffer_desired_length_in_seconds=
length_in_seconds ;
- Use this length_in_seconds to set the desired length of the
+ Use this length_in_seconds to set the desired length of the
queue of audio frames in the backend's output buffer.
- The default is 0.15 seconds for the ALSA backend, 0.35 seconds for the PA backend
+
The default is 0.15 seconds for the ALSA backend, 0.35 seconds for the PA backend
and one second for all other backends.
If this value is set too small, underflow may occur on low-powered machines.
- If set too large, the response times to the volume control may become excessive, or it
+ If set too large, the response times to the volume control may become excessive, or it
may exceed the backend's buffer size.
- It may need to be larger on low-powered machines that are also performing other tasks,
+ It may need to be larger on low-powered machines that are also performing other tasks,
such as processing metadata.
audio_backend_buffer_interpolation_threshold_in_seconds=
time_in_seconds ;
- This is an advanced feature. If the length of the audio backend buffer
- size drops below this, it's a sign that shairport sync can not process frames of audio
+ This is an advanced feature. If the length of the audio backend buffer
+ size drops below this, it's a sign that shairport sync can not process frames of audio
quickly enough. It this threshold is reached, shairport sync will stop using
time-consuming interpolation like soxr to avoid underruns.
@@ -401,25 +404,25 @@
audio_backend_silent_lead_in_time=
lead_in_time_in_seconds ;
- This is an advanced setting. Use the lead_in_time_in_seconds to
- set the desired length of the period of silence (a "silent lead-in") played before a
+ This is an advanced setting. Use the lead_in_time_in_seconds to
+ set the desired length of the period of silence (a "silent lead-in") played before a
play session begins.
- The purpose of this silent lead-in is to give the backend sufficient time to
- prepare for operation and to make an estimate (and, importantly, to correct the
- estimate) of the exact time at which to begin playing audio to achieve initial
- synchronisation. The value can be from 0.0 up to a maximum of either 4.0 seconds. The
- actual duration will be close to the setting but can not exceed the latency set by the
+
The purpose of this silent lead-in is to give the backend sufficient time to
+ prepare for operation and to make an estimate (and, importantly, to correct the
+ estimate) of the exact time at which to begin playing audio to achieve initial
+ synchronisation. The value can be from 0.0 up to a maximum of either 4.0 seconds. The
+ actual duration will be close to the setting but can not exceed the latency set by the
client, usually 2 seconds or a little more.
- If the value chosen is too short for synchronised backends such as the ALSA, sndio
+
If the value chosen is too short for synchronised backends such as the ALSA, sndio
or PA backends, then audio will not be synchronised correctly at the start of play.
- The default is to have a silent lead-in of approximately the same time as the latency
+ The default is to have a silent lead-in of approximately the same time as the latency
set by the client.
dbus_service_bus=
"bus_name" ;
- If shairport sync is compiled with the D-Bus interface, it can offer it on
+ If shairport sync is compiled with the D-Bus interface, it can offer it on
the "system" or the "session" D-Bus "bus".
Use this to specify which. The default is to use the "system" bus.
@@ -427,7 +430,7 @@
mpris_service_bus=
"bus_name" ;
- If shairport sync is compiled with the MPRIS interface, it can offer the
+ If shairport sync is compiled with the MPRIS interface, it can offer the
service on the "system" or the "session" D-Bus "bus".
Use this to specify which. The default is to use the "system" bus.
@@ -435,207 +438,210 @@
"SESSIONCONTROL" SETTINGS
- run_this_before_play_begins= "/path/to/application and
+ run_this_before_play_begins= "/path/to/application and
args" ;
- Here you can specify a program and its arguments that will be run just
+ Here you can specify a program and its arguments that will be run just
before a play session begins. Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
appropriate.
- run_this_after_play_ends= "/path/to/application and
+ run_this_after_play_ends= "/path/to/application and
args" ;
- Here you can specify a program and its arguments that will be run just
+ Here you can specify a program and its arguments that will be run just
after a play session ends. Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
appropriate.
- run_this_before_entering_active_state= "/path/to/application and
+ run_this_before_entering_active_state= "/path/to/application and
args" ;
- Here you can specify a program and its arguments that will be run just
+ Here you can specify a program and its arguments that will be run just
before shairport-sync goes active.
-
- Shairport Sync goes "active" when a play session starts. When the play
- session ends, the system will stay active until the time
+
+
Shairport Sync goes "active" when a play session starts. When the play
+ session ends, the system will stay active until the time
specified in the active_state_timeout setting elapses.
- If a new play session starts before that, the system will remain active. Otherwise,
+ If a new play session starts before that, the system will remain active. Otherwise,
the system will go inactive.
-
+
Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
appropriate.
- run_this_after_exiting_active_state= "/path/to/application and
+ run_this_after_exiting_active_state= "/path/to/application and
args" ;
- Here you can specify a program and its arguments that will be run just
+ Here you can specify a program and its arguments that will be run just
after shairport-sync goes inactive (see the previous entry for an explanation
of the idea).
Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
appropriate.
active_state_timeout= seconds ;
- After a play session has ended, the system will remain active for
- seconds seconds. If a new play session starts before this time has elapsed,
- the system will remain active. However, if no new session starts in the interval, the
+ After a play session has ended, the system will remain active for
+ seconds seconds. If a new play session starts before this time has elapsed,
+ the system will remain active. However, if no new session starts in the interval, the
system will go inactive at the end of it. The default is 10 seconds.
-
+
- run_this_if_an_unfixable_error_is_detected= "/path/to/application
+ run_this_if_an_unfixable_error_is_detected= "/path/to/application
and args" ;
- Here you can specify a program and its arguments that will be run if the
+ Here you can specify a program and its arguments that will be run if the
system detects an unfixable error. At present, there are two types of
- unfixable errors. One is where a play session cannot be terminated.
- The second is if an output device has "stalled" -- that is, if an output device
+ unfixable errors. One is where a play session cannot be terminated.
+ The second is if an output device has "stalled" -- that is, if an output device
refuses to accept any more output frames.
Although the first problem could, in principle, be fixed by restarting
Shairport Sync, it is usually caused by a malfunctioning output device.
Typically, the most reliable way to recover from either of these errors
is to reboot the entire machine.
Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
appropriate.
wait_for_completion= "choice" ;
- Set choice to "yes" to make shairport-sync wait until the
- programs specified in the run_this_... settings have
+ Set choice to "yes" to make shairport-sync wait until the
+ programs specified in the run_this_... settings have
completed execution before continuing. The default is "no".
allow_session_interruption= "choice" ;
- If choice is set to "yes", then another source will be able to
+ If choice is set to "yes", then another source will be able to
interrupt an existing play session and start a new one.
- When set to "no" (the default), other devices attempting to interrupt a session will
+ When set to "no" (the default), other devices attempting to interrupt a session will
fail, receiving a busy signal.
session_timeout= seconds ;
- If a play session has been established and the source disappears without
+ If a play session has been established and the source disappears without
warning (such as a device going out of range of a network)
- then wait for the number of seconds specified before ending the session.
- Once the session has terminated, other devices can use it. The default is 120
+ then wait for the number of seconds specified before ending the session.
+ Once the session has terminated, other devices can use it. The default is 120
seconds.
-
+
"ALSA" SETTINGS
-
These settings are for the ALSA back end, used to communicate with audio output
- devices in the ALSA system. (By the way, you can use tools such as
+
These settings are for the ALSA back end, used to communicate with audio output
+ devices in the ALSA system. (By the way, you can use tools such as
alsamixer or aplay to discover what devices are available.)
- Use these settings to select the output device and the mixer control to be used to
+ Use these settings to select the output device and the mixer control to be used to
control the output volume.
- You can additionally set the desired size of the output buffer and you can adjust
+ You can additionally set the desired size of the output buffer and you can adjust
overall latency. Here are the alsa group settings:
output_device= "output_device" ;
- Use the output device called output_device . The default is the
+ Use the output device called output_device . The default is the
device called "default" .
mixer_control_name= "name" ;
- Specify the name of the mixer control to be used by
+ Specify the name of the mixer control to be used by
shairport-sync to control the volume.
The mixer control must be on the mixer device, which by default is the output device.
- If you do not specify a mixer control name, shairport-sync will adjust the volume in
+ If you do not specify a mixer control name, shairport-sync will adjust the volume in
software.
mixer_device= "mixer_device" ;
- By default, the mixer is assumed to be output_device. Use this setting to
+ By default, the mixer is assumed to be output_device. Use this setting to
specify a device other than the output device.
-
+
output_rate= frame rate ;
- Use this setting to specify the frame rate to output to the ALSA device.
- Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have
- the capability to accept the format you specify. There is no particular reason to use
- anything other than 44100 if it is available.
+ Use this setting to specify the frame rate to output to the ALSA device.
+ Allowable values are "auto" (default), 44100, 88200, 176400 and 352800. The device must have
+ the capability to accept the rate you specify. There is no particular reason to use
+ anything other than 44100 if it is available, and if "auto" is selected, the lowest
+ of these rates available, starting at 44100, will be selected.
output_format= "format" ;
- Use this setting to specify the format that should be used to send data to
- the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE"
- or "S32". The device must have the capability to accept the format you
- specify.
"S" means signed; "U" means unsigned; BE means big-endian and LE means
- little-endian. Except where stated (using *LE or *BE), endianness matches that of the
- processor. The default is "S16".
If you are using a hardware mixer, the best
- setting is S16, as audio will pass through Shairport Sync unmodified except for
- interpolation. If you are using the software mixer, use 32- or 24-bit, if your device
- is capable of it, to get the lowest possible levels of dither.
+ Use this setting to specify the format that should be used to send data to
+ the ALSA device. Allowable values are "auto" (default), "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE"
+ or "S32". The device must have the capability to accept the format you
+ specify.
"S" means signed; "U" means unsigned; BE means big-endian and LE means
+ little-endian. Except where stated (using *LE or *BE), endianness matches that of the
+ processor. The default is "S16".
If you are using a hardware mixer, S16 is fine,
+ as audio will pass through Shairport Sync unmodified except for
+ interpolation, but any of the higher-resolution formats are okay too.
+ If you are using the software mixer, use 32- or 24-bit, if your device
+ is capable of it, in order to get the lowest possible levels of dither.
+ The "auto" setting will cause Shairport Sync to choose the highest resolution available.
disable_synchronization= "no" ;
- This is an advanced setting and is for debugging only. Set to
+ This is an advanced setting and is for debugging only. Set to
"yes" to disable synchronization. Default is "no" .
- If you use it to disable synchronisation, then sooner or later you'll experience audio
+ If you use it to disable synchronisation, then sooner or later you'll experience audio
glitches due to audio buffer overflow or underflow.
period_size= number ;
- Use this optional advanced setting to set the alsa period size near to
+ Use this optional advanced setting to set the alsa period size near to
this value.
buffer_size= number ;
- Use this optional advanced setting to set the alsa buffer size near to
+ Use this optional advanced setting to set the alsa buffer size near to
this value.
use_mmap_if_available= "yes" ;
- Use this optional advanced setting to control whether MMAP-based output
+ Use this optional advanced setting to control whether MMAP-based output
is used to communicate with the DAC. Default is "yes" .
-
+
mute_using_playback_switch= "no" ;
- This is an advanced setting and the default is "no" . If it is set to
- "yes" , hardware mute will be used where it is available.
+
This is an advanced setting and the default is "no" . If it is set to
+ "yes" , hardware mute will be used where it is available.
Set it to "no" to prevent the hardware mute being used.
- If Shairport Sync is sharing the output device with other applications, it is best
+
If Shairport Sync is sharing the output device with other applications, it is best
to leave this set to "no" for compatibility with those applications.
Another motivation for this is to allow the ALSA function call
- "snd_mixer_selem_set_playback_switch_all" to be avoided. It is incorrectly implemented
+ "snd_mixer_selem_set_playback_switch_all" to be avoided. It is incorrectly implemented
on certain soundcards, including the emulated card in VMWare Fusion 8.5.
maximum_stall_time= seconds ;
- If an output device fails to accept any audio frames for more than the
+ If an output device fails to accept any audio frames for more than the
time, in seconds, specified here (0.2 seconds by default),
it is considered to have malfunctioned. It will result in the
- run_this_if_an_unfixable_error_is_detected program,
+ run_this_if_an_unfixable_error_is_detected program,
if any, being called.
Implemented for the ALSA back end only.
@@ -644,52 +650,52 @@
disable_standby_mode= "never" ;
- Shairport Sync has a "Disable Standby" feature to eliminate certain
+
Shairport Sync has a "Disable Standby" feature to eliminate certain
faint-but-annoying audible pops and clicks. When activsted, it prevents
- an output device from entering standby mode and thus it minimises standby/busy
- transitions, which can sometimes be heard. Use this setting to control when the
- Disable Standby feature is active: "never" means it will never be activated, "always"
- means it will be active as soon as shairport-sync starts running, and "auto"
- means it will be active while shairport-sync is in the "active" state.
- Shairport Sync goes "active" when a play session starts. When the play
- session ends, the system will stay active until the time
+ an output device from entering standby mode and thus it minimises standby/busy
+ transitions, which can sometimes be heard. Use this setting to control when the
+ Disable Standby feature is active: "never" means it will never be activated, "always"
+ means it will be active as soon as shairport-sync starts running, and "auto"
+ means it will be active while shairport-sync is in the "active" state.
+ Shairport Sync goes "active" when a play session starts. When the play
+ session ends, the system will stay active until the time
specified in the active_state_timeout setting elapses.
- If a new play session starts before that, the system will remain active. Otherwise,
+ If a new play session starts before that, the system will remain active. Otherwise,
the system will go inactive.
"SNDIO" SETTINGS
-
These settings are for the SNDIO back end, used to communicate with audio output
+
These settings are for the SNDIO back end, used to communicate with audio output
devices in the SNDIO system.
device= "snd/0" ;
- Use this optional setting to specify the name of the output device, e.g.
+ Use this optional setting to specify the name of the output device, e.g.
"snd/0" . The default is to use the SNDIO system's default.
rate= 44100 ;
- Use this optional setting to specify the output rate in frames per second.
+ Use this optional setting to specify the output rate in frames per second.
Valid rates are 44100, 88200, 176400 or 352800.
- The output device must have the capability to accept data at the specified rate. The
+ The output device must have the capability to accept data at the specified rate. The
default is 44100.
format= "S16" ;
- Use this optional setting to specify the output format. Allowable values
+ Use this optional setting to specify the output format. Allowable values
are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32".
- The device must have the capability to accept the format you specify.
"S" means
+ The device must have the capability to accept the format you specify.
"S" means
signed; "U" means unsigned; BE means big-endian and LE means little-endian.
- Except where stated (using *LE or *BE), endianness matches that of the processor. The
+ Except where stated (using *LE or *BE), endianness matches that of the processor. The
default is "S16".
- Since the SNDIO backend does not use a hardware mixer for volume control, dither will
+ Since the SNDIO backend does not use a hardware mixer for volume control, dither will
be introduced into the output if it is less than full volume.
Thus, (unless you are ignoring the volume control setting),
- consider using 32- or 24-bit output if your device is capable of it, to get the lowest
+ consider using 32- or 24-bit output if your device is capable of it, to get the lowest
possible levels of dither.
Please note that 32- or 24-bit has not been extensively tested on
SNDIO.
@@ -697,13 +703,13 @@
round= value ;
- Use this optional advanced setting to specify the period size of the SNDIO
+ Use this optional advanced setting to specify the period size of the SNDIO
channel. If omitted, a SNDIO system default value will be used.
bufsiz= value ;
- Use this optional advanced setting to specify the buffer size of the SNDIO
+ Use this optional advanced setting to specify the buffer size of the SNDIO
channel. If omitted, a SNDIO system default value will be used.
@@ -712,113 +718,114 @@
application_name= "Shairport Sync" ;
- Use this to set the name to appear in the Sounds "Applications" tab when
+ Use this to set the name to appear in the Sounds "Applications" tab when
Shairport Sync is active. The default is the name "Shairport Sync".
"PIPE" SETTINGS
-
These settings are for the PIPE backend, used to route audio to a named unix pipe.
- The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per
+
These settings are for the PIPE backend, used to route audio to a named unix pipe.
+ The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per
second, interleaved stereo.
name= "/path/to/pipe" ;
- Use this to specify the name and location of the pipe. The pipe will be
+ Use this to specify the name and location of the pipe. The pipe will be
created and opened when shairport-sync starts up and will be closed upon shutdown.
- Frames of audio will be sent to the pipe in packets of 352 frames and will be
+ Frames of audio will be sent to the pipe in packets of 352 frames and will be
discarded if the pipe has not have a reader attached.
The sender will wait for up to five seconds for a packet to be written before
discarding it.
-
+
"STDOUT" SETTINGS
There are no settings for the STDOUT backend.
-
+
"AO" SETTINGS
There are no configuration file settings for the AO backend.
"METADATA" SETTINGS
- shairport-sync can process metadata provided by the source, such as Track Number,
+
shairport-sync can process metadata provided by the source, such as Track Number,
Album Name, cover art, etc. and can provide additional metadata such as volume level,
- pause/resume, etc. It sends the metadata to a pipe, by default
+ pause/resume, etc. It sends the metadata to a pipe, by default
/tmp/shairport-sync-metadata .
- To process metadata, shairport-sync must have been compiled with metadata support
+ To process metadata, shairport-sync must have been compiled with metadata support
included.
- You can check that this is so by running the command $ shairport-sync -V ;
+ You can check that this is so by running the command $ shairport-sync -V ;
the identification string will contain the word metadata .
- Please note that different sources provide different levels of metadata. Some
+
Please note that different sources provide different levels of metadata. Some
provide a lot; some provide almost none.
- The metadata group of settings allow you to enable metadata handling and
+
The metadata group of settings allow you to enable metadata handling and
to control certain aspects of it:
enabled= "choice" ;
- Set the choice to "yes" to enable shairport-sync to look for
- metadata from the audio source and to forward it, along with metadata generated by
+ Set the choice to "yes" to enable shairport-sync to look for
+ metadata from the audio source and to forward it, along with metadata generated by
shairport-sync itself, to the metadata pipe. The default is "no".
include_cover_art= "choice" ;
- Set the choice to "yes" to enable shairport-sync to look for
+ Set the choice to "yes" to enable shairport-sync to look for
cover art from the audio source and to include it in the feed to the metadata pipe.
You must also enable metadata (see above).
- One reason for not including cover art is that the images can sometimes be very large
+ One reason for not including cover art is that the images can sometimes be very large
and may delay transmission of subsequent metadata through the pipe.
The default is "no".
pipe_name= "filepathname" ;
- Specify the absolute path name of the pipe through which metadata should
+ Specify the absolute path name of the pipe through which metadata should
be sent The default is /tmp/shairport-sync-metadata .
-
+
socket_address= "hostnameOrIP" ;
- If hostnameOrIP is set to a host name or and IP address, UDP
+ If hostnameOrIP is set to a host name or and IP address, UDP
packets containing metadata will be sent to this address.
- May be a multicast address. Additionally, socket-port must be non-zero and
+ May be a multicast address. Additionally, socket-port must be non-zero and
enabled must be set to "yes".
socket_port= port ;
- If socket_address is set, use port to specify the
+ If socket_address is set, use port to specify the
port to send UDP packets to. Must not be zero.
socket_msglength= 65000 ;
- The maximum packet size for any UDP metadata. This must be between 500 or
+ The maximum packet size for any UDP metadata. This must be between 500 or
65000. The default is 500.
"DIAGNOSTICS" SETTINGS
statistics= "setting" ;
- Use this setting to enable ("yes") or disable ("no") the output
- of some statistical information on the console or in the log. The default is to
+ Use this setting to enable ("yes") or disable ("no") the output
+ of some statistical information to the system log (or to STDERR if the -u command line option is chosen). The default is to
disable statistics.
log_verbosity= 0 ;
- Use this to specify how much debugging information should be output or
- logged. The value 0 means no debug information, 3 means most
+ Use this to specify how much debugging information should sent to the system log
+ (or to STDERR if the -u command line option is chosen).
+ The value 0 means no debug information, 3 means most
debug information. The default is 0 .
-
+
This section is about the command-line options available in shairport-sync.
- Note: if you are setting up shairport-sync for the first time or are updating an
- existing installation, you are encouraged to use the configuration file settings
+
Note: if you are setting up shairport-sync for the first time or are updating an
+ existing installation, you are encouraged to use the configuration file settings
described above. Most of the command-line options described below
- simply replicate the configuration settings and are retained to provide backward
+ simply replicate the configuration settings and are retained to provide backward
compatibility with older installations of shairport-sync.
-
+
Many command-line options take sensible default values, so you can normally
ignore most of them. See the EXAMPLES section for typical usages.
@@ -827,25 +834,25 @@
Program options are
always listed first, followed by any audio backend options, preceded by
a -- symbol.
-
+
These command-line options are used by shairport-sync itself.
-
+
- -a service name | --name= service
+ -a service name | --name= service
name
Use this service name to identify this player in iTunes, etc.
-
+
The following substitutions are allowed:
%h for the computer's hostname,
- %H for the computer's hostname with the first letter capitalised (ASCII
+ %H for the computer's hostname with the first letter capitalised (ASCII
only),
%v for the shairport-sync version number, e.g. "3.0.1" and
- %V for the shairport-sync version string, e.g.
- "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
- The default is "%H", which is replaced by the hostname with the first letter
+ %V for the shairport-sync version string, e.g.
+ "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
+ The default is "%H", which is replaced by the hostname with the first letter
capitalised.
@@ -853,11 +860,11 @@
-B program | --on-start= program
- Execute program when playback is about to begin. Specify the
+ Execute program when playback is about to begin. Specify the
full path to the program, e.g. /usr/bin/logger .
Executable scripts can be used, but they must have the appropriate shebang
(#!/bin/sh in the headline.
-
+
If you want shairport-sync to wait until the command has
completed before starting to play, select the -w option as well.
@@ -866,10 +873,10 @@
-c filename | --configfile= filename
- Read configuration settings from filename . The default is to read them from
- the shairport-sync.conf in the System Configuration Directory --
+ 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.
@@ -889,7 +896,7 @@
-E program | --on-stop= program
- Execute program when playback has ended. Specify the
+ Execute program when playback has ended. Specify the
full path to the program, e.g. /usr/bin/logger .
Executable scripts can be used, but they must have the appropriate shebang
(#!/bin/sh in the headline.
@@ -919,8 +926,8 @@
-j
- Instruct shairport-sync to demonise itself. Unlike the -d option, it will
- not write a Process ID (PID) to a file -- it will just (hence the "j") demonise
+ Instruct shairport-sync to demonise itself. Unlike the -d option, it will
+ not write a Process ID (PID) to a file -- it will just (hence the "j") demonise
itself. Only available if shaiport-sync has been compiled with libdaemon support.
@@ -937,8 +944,8 @@
--logOutputLevel
- 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
+ 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.
@@ -947,12 +954,12 @@
-L | --latency= latency
- Use this to set the default latency , in frames, for audio coming from an
- unidentified source or from an iTunes Version 9 or earlier source. The standard value
+ Use this to set the default latency , in frames, for audio coming from an
+ unidentified source or from an iTunes Version 9 or earlier source. The standard value
for the default latency 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
+
Please note that this feature is deprecated and will be removed in a future version
of shairport-sync.
@@ -962,8 +969,8 @@
Listen for metadata coming from the source and send it, along with metadata from
shairport-sync itself, to a pipe called shairport-sync-metadata
- in the directory you specify. If you add the --get-cover-art
- then cover art will be sent through the pipe too. See directory you specify. If you add the --get-cover-art
+ then cover art will be sent through the pipe too. See
for a sample metadata reader.
@@ -979,7 +986,7 @@
- -o outputbackend |
+ -o outputbackend |
--output= outputbackend
Force the use of the specified output backend to play the audio.
@@ -998,7 +1005,7 @@
--password= secret
- Require the password secret to be able to connect and stream to the
+ Require the password secret to be able to connect and stream to the
service.
@@ -1010,7 +1017,7 @@
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 0 to disable resynchronisation. This setting is
+ milliseconds. Specify 0 to disable resynchronisation. This setting is
deprecated and will be removed in a future version of shairport-sync.
@@ -1056,13 +1063,13 @@
--tolerance= frames
- Allow playback to be up to frames out of exact synchronization before
+ Allow playback to be up to frames 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
+ 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 --statistics option
- to monitor correction levels. Corrections should not greatly exceed net corrections.
+ Overcorrection is when more corrections (insertions and deletions) are made than are
+ strictly necessary to keep the stream in sync. Use the --statistics 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.
@@ -1085,7 +1092,8 @@
-v | --verbose
- Print debug information. Repeat up to three times to get more detail.
+ Print debug information to the system log, or or to STDERR if the -u command line option is also chosen.
+ Repeat up to three times to get more detail.
@@ -1098,23 +1106,23 @@
- These command-line options are passed to the chosen audio backend. The audio
+
These command-line options are passed to the chosen audio backend. The audio
backend options are
preceded by a -- symbol to introduce them and to separate them from any
program options. In this way, option letters can be used as program
options and also as audio backend options without ambiguity.
-
+
In the ALSA backend, audio is sent to an output device
which you can specify using the -d option.
- The output level (the "volume") is controlled using a level control associated with a
+ The output level (the "volume") is controlled using a level control associated with a
mixer.
By default, the mixer is implemented in shairport-sync itself in software.
- To use a hardware level control on a mixer on the sound card, specify the name of the
+ To use a hardware level control on a mixer on the sound card, specify the name of the
mixer control with the -c option.
- If the mixer is not associated with the output device, then you need to specify where
+ If the mixer is not associated with the output device, then you need to specify where
the mixer is to be found with the -m option.
-
+
-c controlname
@@ -1128,7 +1136,7 @@
-d device
- Use the specified output device . You may specify a card, e.g.
+ Use the specified output device . You may specify a card, e.g.
hw:0 , in which case the default output device on the card will be chosen.
Alternatively, you can specify a specific device on a card, e.g. hw:0,0 .
The default is the device named default .
@@ -1138,7 +1146,7 @@
-m mixer
- Use the specified hardware mixer for volume control. Use this to specify
+ Use the specified hardware mixer for volume control. Use this to specify
where the mixer is to be found. For example, if the mixer is associated with a card,
as is often the case, specify the card, e.g. hw:0 .
If (unusually) the mixer is associated with a specific device on a card,
@@ -1152,9 +1160,9 @@
-t devicetype
- This option is deprecated and is ignored. For your information, its functionality has
+ This option is deprecated and is ignored. For your information, its functionality has
been automatically incorporated in the -c option
- -- if you specify a mixer name with the -c option, it is assumed that the mixer is
+ -- if you specify a mixer name with the -c option, it is assumed that the mixer is
implemented in hardware.
@@ -1168,7 +1176,7 @@
--
-d hw:1,0
-m hw:1
- -c PCM
+ -c PCM
The program will run in daemon mode ( -d ), will be visible as
"Joe's Stereo" ( -a "Joe's Stereo" ) and will use the SoX Resampler
@@ -1178,17 +1186,17 @@
( -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
+
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:
shairport-sync -d
-a "Joe's Stereo"
-S soxr
--
-d hw:1
- -c PCM
+ -c PCM
-
+
diff --git a/man/shairport-sync.html b/man/shairport-sync.html
index c16a56ea..5d51b3c0 100644
--- a/man/shairport-sync.html
+++ b/man/shairport-sync.html
@@ -1,1199 +1,28 @@
shairport-sync
Synchronised Audio Player for iTunes / AirPlay
-
-
- Synopsis
-
-
- shairport-sync [-djvuw]
- [-a name ]
- [-A latency ]
- [-B command ]
- [-c configurationfile ]
- [-E command ]
- [--get-cover-art]
- [--logOutputLevel]
- [-L latency ]
- [-m backend ]
- [--meta-dir= directory ]
- [-o backend ]
- [--password= secret ]
- [-r threshold ]
- [--statistics]
- [-S mode ]
- [-t timeout ]
- [--tolerance= frames ]
- [-- audio_backend_options ]
-
-
- shairport-sync -k
-
- shairport-sync -h
-
- shairport-sync -V
-
-
-
-
- Description
-
- Shairport Sync plays
- audio streamed from iTunes
- or from an AirPlay device to an ALSA-compatible audio output device (available on
- Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
- Linux), to a PulseAudio output stream or to Jack Audio.
-
- Shairport Sync offers full audio synchronisation.
- Full audio synchronisation means that audio is played on the output device at exactly
- the time specified by the audio source.
- This means that if many devices are playing the same stream at the same
- time, all the outputs will stay in synchrony with one another.
- This allows multiple devices to play the same source without getting out of step with
- one another, enabling, for example, simultaneous multi-room operation.
-
-
- Shairport Sync can stream synchronised audio to a unix
- pipe or to standard output, or to audio systems that do not provide timing
- information. This could perhaps be described as partial audio synchronisation, where
- synchronised audio is provided by Shairport Sync, but what happens to it in the
- subsequent processing chain, before it reaches the listener's ear, is outside the
- control of shairport-sync.
- Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
- or socket.
- Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
- D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
- metadata, including cover art, and can offer remote control of the audio source.
-
- Settings can be made using the configuration file (recommended for all new
- 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.
-
-
-
-
-
- Configuration File Settings
-
- You should use the configuration file for setting up Shairport Sync.
- This file is usually shairport-sync.conf and is generally located in the
- System Configuration Directory, which is normally the /etc directory in
- Linux or the /usr/local/etc directory in BSD unixes.
- You may need to have root privileges to modify it.
-
- (Note: Shairport Sync may have been compiled to use a different configuration
- directory. You can determine which by performing the command $ shairport-sync
- -V . One of the items in the output string is the value of the
- sysconfdir ,
- i.e. the System Configuration Directory.)
-
-
- Within the configuraton file, settings are organised into groups, for
- example, there is a "general" group of
- standard settings, and there is an "alsa" group with settings that pertain to the ALSA
- back end. Here is an example of a typical configuration file:
-
- general = {
-
name = "Mike's Boombox";
-
interpolation = "soxr";
-
password = "secret";
-
output_backend = "alsa";
- };
-
- alsa = {
-
output_device = "hw:0";
-
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, (2) a password (if desired) and
- (3) the output device. If the output device has a mixer that can be used for volume
- control, then (4) the volume control's name should be specified. It is highly
- desirable to use the output device's mixer for volume control, if available --
- response time is reduced to zero and the processor load is reduced. In the example
- above, "soxr" interpolation was also enabled.
-
- A sample configuration file with all possible settings, but with all of them
- commented out, is installed at shairport-sync.conf.sample , within the
- System Configuration Directory -- /etc in Linux,
- /usr/local/etc in BSD unixes.
-
- To retain backwards compatibility with previous versions of shairport-sync
- you can use still use command line options, but any new features, etc. will
- be available only via configuration file settings.
-
- The configuration file is processed using the libconfig library
- -- see http://www.hyperrealm.com/libconfig/libconfig_manual.html .
-
- "GENERAL" SETTINGS
- These are the settings available within the general group:
-
-
- name= "service_name" ;
-
- Use this service_name to identify this player in iTunes, etc.
- The following substitutions are allowed:
- %h for the computer's hostname,
- %H for the computer's hostname with the first letter capitalised (ASCII
- only),
- %v for the shairport-sync version number, e.g. "3.0.1" and
- %V for the shairport-sync version string, e.g.
- "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
- The default is "%H", which is replaced by the hostname with the first letter
- capitalised.
-
-
-
-
- password= "password" ;
- Require the password password to connect to the service. If you
- leave this setting commented out, no password is needed.
-
-
-
- interpolation= "mode" ;
- Interpolate, or "stuff", the audio stream using the mode .
- Interpolation here refers to the
- process of adding or removing frames of audio to or from the
- stream sent to the output device to keep it exactly in synchrony
- with the player.
- The default mode, "basic", is normally almost completely inaudible.
- 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.
-
-
-
-
- output_backend= "backend" ;
- shairport-sync has a number of modules of code ("backends") through which
- audio is output. Normally, the first audio backend that works is selected. This
- setting forces the selection of the specific audio backend . Perform the
- command shairport-sync -h to get a list of available audio backends -- the
- default is the first on this list. Only the "alsa", "sndio" and "pa" backends support
- synchronisation.
-
-
-
- mdns_backend= "backend" ;
- shairport-sync has a number of modules of code ("backends") for
- interacting with the mDNS service to be used to advertise itself. Normally, the first
- mDNS backend that works is selected. This setting forces the selection of the specific
- mDNS backend . The default is "avahi". Perform the command
- shairport-sync -h to get a list of available mDNS modules.
-
-
-
- port= portnumber ;
- Use this to specify the portnumber shairport-sync uses to
- listen for service requests from iTunes, etc. The default is port 5000.
-
-
-
- udp_port_base= portnumber ;
- When shairport-sync starts to play audio, it establises three UDP
- connections to the audio source. Use this setting to specify the starting
- portnumber for these three ports. It will pick the first three unused ports
- starting from portnumber . The default is port 6001.
-
-
-
- udp_port_range= range ;
- Use this in conjunction with the prevous setting to specify the
- range of ports that can be checked for availability. Only three ports are
- needed.
- The default is 100, thus 100 ports will be checked from port 6001 upwards until three
- are found.
-
-
-
- drift_tolerance_in_seconds= seconds ;
- Allow playback to drift up to seconds out of exact
- synchronization before attempting to correct it.
- The default is 0.002 seconds, i.e. 2 milliseconds. 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 statistics setting
- to monitor correction levels. Corrections should not greatly exceed net corrections.
- This setting replaces the deprecated drift setting.
-
-
-
-
- resync_threshold_in_seconds= threshold ;
- Resynchronise if timings differ by more than threshold seconds.
- 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 0.050 seconds, i.e. 50
- milliseconds. Specify 0.0 to disable resynchronisation.
- This setting replaces the deprecated resync_threshold setting.
-
-
-
-
- ignore_volume_control= "choice" ;
- Set this choice to "yes" if you want the volume to
- be at 100% no matter what the source's volume control is set to.
- This might be useful if you want to set the volume on the output device, independently
- of the setting at the source. The default is "no" .
-
-
-
- volume_range_db= dBvalue ;
- Use this dBvalue to reduce or increase the attenuation range,
- in decibels, between the minimum and maximum volume.
- For example, if a mixer has a minimum volume of -80 dB and a maximum of +20 dB, you
- might wish to use only 60 dB of the 100 dB available.
- This might be because the sound becomes inaudible at the lowest setting and unbearably
- loud at the highest setting --
- indeed, many domestic HiFi systems have a volume control range of just 60 to 80dB.
- Another potential use might be where the range specified by the mixer does not
- match the capabilities of the device.
- For example, the Raspberry Pi's DAC that feeds the built-in audio jack claims a range
- of 106 dB but has a useful range of only about 30 dB.
- The setting allows you to specify the maximum range from highest to lowest.
- The range suggested for the Raspberry Pi's built-in audio DAC, which feeds the
- headphone jack, is 30.
- Using it in this case gives the volume control a much more useful range of
- settings.
- As a third example, you can actually extend the range provided by a mixer.
- Many cheaper DACs have hardware mixers that offer a restricted attenuation range.
- If you specify a volume range greater than the range of the mixer, software
- attenuation and hardware attenuation will be combined to give the specified range.
- If you omit this setting, the native range of the mixer is used.
-
-
-
- volume_max_db= dBvalue ;
- Specify the maximum output level to be used with the hardware mixer, if
- used. If no hardware mixed is used, this setting specifies the maximum setting
- permissible in the software mixer, which has an attenuation range from 0.0 dB down to
- -96.3 dB.
-
-
-
-
- volume_control_profile= "choice" ;
- Use this advanced setting to specify how the airplay volume is transferred
- to the mixer volume. The "standard" profile, which is the default, makes
- the volume change more quickly at lower volumes and slower at higher volumes. Choose
- the "flat" profile to makes the volume change at the same rate at all
- volume levels.
-
-
-
-
- volume_range_combined_hardware_priority=
- "choice" ;
- Use this advanced setting to specify how to combine the hardware
- attenuator with software attenuation to provide a greater attenuation range than the
- hardware attenuator alone can provide. Choosing "yes" means that when
- attenuation is required, the hardware attenuator will be used in preference.
- If more attenuation than it can provide is needed, the hardware attenuator is set to
- its greatest attenuation and software attenuation is added.
- For example, if 40 dB of attenuation is required and the hardware attenuator
- offers a maximum of 30 dB, then the hardware attenuator will be set to give 30 dB
- attenuation and 10 dB of software attenuation will be added.
- Unfortunately, certain hardware attenuators will mute at their greatest
- attenuation, so can't be combined with software attenuation in this way. Choosing
- "no" means that software attenuation is used to bring the remaining
- attenuation required into the range offered by the hardware attenuator.
- This is the default.
-
-
-
-
- run_this_when_volume_is_set=
- "/full/path/to/application/and/args" ;
- Here you can specify a program and its arguments that will be run when the
- volume is set or changed. Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as appropriate.
- The desired AirPlay volume is appended to the end of the command line -- leave a
- space at the end of the command line you specify here if you want it treated as an
- extra argument.
- AirPlay volume goes from 0.0 to -30.0 and -144.0 means "mute".
-
-
-
- regtype= "regTypeString" ;
- Use this advanced setting to set the service type and transport to be
- advertised by Zeroconf/Bonjour. Default is "_raop._tcp" .
-
-
-
- playback_mode= "mode" ;
- The mode can be "stereo", "mono", "reverse stereo", "both left"
- or "both right". Default is "stereo". Note that dither will be added to the signal in
- the mono mode.
-
-
-
- alac_decoder= "decodername" ;
- This can be "hammerton" or "apple". This advanced setting allows you to
- choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio
- Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the
- configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be
- present for this to work.
-
-
-
- interface= "name" ;
- Use this advanced setting if you want to confine Shairport Sync to the
- named interface. Leave it commented out to get the default bahaviour.
-
-
-
- audio_backend_latency_offset_in_seconds=
- offset_in_seconds ;
- Set this offset_in_seconds to compensate for a fixed delay in
- the audio back end. For example, if the output device delays by 100 ms, set this to
- -0.1.
-
-
-
- audio_backend_buffer_desired_length_in_seconds=
- length_in_seconds ;
- Use this length_in_seconds to set the desired length of the
- queue of audio frames in the backend's output buffer.
- The default is 0.15 seconds for the ALSA backend, 0.35 seconds for the PA backend
- and one second for all other backends.
- If this value is set too small, underflow may occur on low-powered machines.
- If set too large, the response times to the volume control may become excessive, or it
- may exceed the backend's buffer size.
- It may need to be larger on low-powered machines that are also performing other tasks,
- such as processing metadata.
-
-
-
- audio_backend_buffer_interpolation_threshold_in_seconds=
- time_in_seconds ;
- This is an advanced feature. If the length of the audio backend buffer
- size drops below this, it's a sign that shairport sync can not process frames of audio
- quickly enough. It this threshold is reached, shairport sync will stop using
- time-consuming interpolation like soxr to avoid underruns.
-
-
-
- audio_backend_silent_lead_in_time=
- lead_in_time_in_seconds ;
- This is an advanced setting. Use the lead_in_time_in_seconds to
- set the desired length of the period of silence (a "silent lead-in") played before a
- play session begins.
- The purpose of this silent lead-in is to give the backend sufficient time to
- prepare for operation and to make an estimate (and, importantly, to correct the
- estimate) of the exact time at which to begin playing audio to achieve initial
- synchronisation. The value can be from 0.0 up to a maximum of either 4.0 seconds. The
- actual duration will be close to the setting but can not exceed the latency set by the
- client, usually 2 seconds or a little more.
- If the value chosen is too short for synchronised backends such as the ALSA, sndio
- or PA backends, then audio will not be synchronised correctly at the start of play.
- The default is to have a silent lead-in of approximately the same time as the latency
- set by the client.
-
-
-
- dbus_service_bus=
- "bus_name" ;
- If shairport sync is compiled with the D-Bus interface, it can offer it on
- the "system" or the "session" D-Bus "bus".
- Use this to specify which. The default is to use the "system" bus.
-
-
-
- mpris_service_bus=
- "bus_name" ;
- If shairport sync is compiled with the MPRIS interface, it can offer the
- service on the "system" or the "session" D-Bus "bus".
- Use this to specify which. The default is to use the "system" bus.
-
-
- "SESSIONCONTROL" SETTINGS
-
-
- run_this_before_play_begins= "/path/to/application and
- args" ;
- Here you can specify a program and its arguments that will be run just
- before a play session begins. Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
- appropriate.
-
-
-
- run_this_after_play_ends= "/path/to/application and
- args" ;
- Here you can specify a program and its arguments that will be run just
- after a play session ends. Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
- appropriate.
-
-
-
- run_this_before_entering_active_state= "/path/to/application and
- args" ;
- Here you can specify a program and its arguments that will be run just
- before shairport-sync goes active.
-
- Shairport Sync goes "active" when a play session starts. When the play
- session ends, the system will stay active until the time
- specified in the active_state_timeout setting elapses.
- If a new play session starts before that, the system will remain active. Otherwise,
- the system will go inactive.
-
-
- Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
- appropriate.
-
-
-
- run_this_after_exiting_active_state= "/path/to/application and
- args" ;
- Here you can specify a program and its arguments that will be run just
- after shairport-sync goes inactive (see the previous entry for an explanation
- of the idea).
- Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
- appropriate.
-
-
-
- active_state_timeout= seconds ;
- After a play session has ended, the system will remain active for
- seconds seconds. If a new play session starts before this time has elapsed,
- the system will remain active. However, if no new session starts in the interval, the
- system will go inactive at the end of it. The default is 10 seconds.
-
-
-
- run_this_if_an_unfixable_error_is_detected= "/path/to/application
- and args" ;
- Here you can specify a program and its arguments that will be run if the
- system detects an unfixable error. At present, there are two types of
- unfixable errors. One is where a play session cannot be terminated.
- The second is if an output device has "stalled" -- that is, if an output device
- refuses to accept any more output frames.
- Although the first problem could, in principle, be fixed by restarting
- Shairport Sync, it is usually caused by a malfunctioning output device.
- Typically, the most reliable way to recover from either of these errors
- is to reboot the entire machine.
- Be careful to include the full path to the application.
- The application must be marked as executable and, if it is a script, its first line
- must begin with the standard shebang #!/bin/... as
- appropriate.
-
-
-
- wait_for_completion= "choice" ;
- Set choice to "yes" to make shairport-sync wait until the
- programs specified in the run_this_... settings have
- completed execution before continuing. The default is "no".
-
-
-
- allow_session_interruption= "choice" ;
- If choice is set to "yes", then another source will be able to
- interrupt an existing play session and start a new one.
- When set to "no" (the default), other devices attempting to interrupt a session will
- fail, receiving a busy signal.
-
-
-
- session_timeout= seconds ;
- If a play session has been established and the source disappears without
- warning (such as a device going out of range of a network)
- then wait for the number of seconds specified before ending the session.
- Once the session has terminated, other devices can use it. The default is 120
- seconds.
-
-
-
- "ALSA" SETTINGS
- These settings are for the ALSA back end, used to communicate with audio output
- devices in the ALSA system. (By the way, you can use tools such as
- alsamixer or aplay to discover what devices are available.)
- Use these settings to select the output device and the mixer control to be used to
- control the output volume.
- You can additionally set the desired size of the output buffer and you can adjust
- overall latency. Here are the alsa group settings:
-
-
- output_device= "output_device" ;
- Use the output device called output_device . The default is the
- device called "default" .
-
-
-
- mixer_control_name= "name" ;
- Specify the name of the mixer control to be used by
- shairport-sync to control the volume.
- The mixer control must be on the mixer device, which by default is the output device.
- If you do not specify a mixer control name, shairport-sync will adjust the volume in
- software.
-
-
-
- mixer_device= "mixer_device" ;
- By default, the mixer is assumed to be output_device. Use this setting to
- specify a device other than the output device.
-
-
-
- output_rate= frame rate ;
- Use this setting to specify the frame rate to output to the ALSA device.
- Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have
- the capability to accept the format you specify. There is no particular reason to use
- anything other than 44100 if it is available.
-
-
-
-
- output_format= "format" ;
- Use this setting to specify the format that should be used to send data to
- the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE"
- or "S32". The device must have the capability to accept the format you
- specify.
"S" means signed; "U" means unsigned; BE means big-endian and LE means
- little-endian. Except where stated (using *LE or *BE), endianness matches that of the
- processor. The default is "S16".
If you are using a hardware mixer, the best
- setting is S16, as audio will pass through Shairport Sync unmodifed except for
- interpolation. If you are using the software mixer, use 32- or 24-bit, if your device
- is capable of it, to get the lowest possible levels of dither.
-
-
-
-
- disable_synchronization= "no" ;
- This is an advanced setting and is for debugging only. Set to
- "yes" to disable synchronization. Default is "no" .
- If you use it to disable synchronisation, then sooner or later you'll experience audio
- glitches due to audio buffer overflow or underflow.
-
-
-
-
- period_size= number ;
- Use this optional advanced setting to set the alsa period size near to
- this value.
-
-
-
- buffer_size= number ;
- Use this optional advanced setting to set the alsa buffer size near to
- this value.
-
-
-
- use_mmap_if_available= "yes" ;
- Use this optional advanced setting to control whether MMAP-based output
- is used to communicate with the DAC. Default is "yes" .
-
-
-
- mute_using_playback_switch= "no" ;
-
- This is an advanced setting and the default is "no" . If it is set to
- "yes" , hardware mute will be used where it is available.
- Set it to "no" to prevent the hardware mute being used.
- If Shairport Sync is sharing the output device with other applications, it is best
- to leave this set to "no" for compatibility with those applications.
- Another motivation for this is to allow the ALSA function call
- "snd_mixer_selem_set_playback_switch_all" to be avoided. It is incorrectly implemented
- on certain soundcards, including the emulated card in VMWare Fusion 8.5.
-
-
-
-
- maximum_stall_time= seconds ;
- If an output device fails to accept any audio frames for more than the
- time, in seconds, specified here (0.2 seconds by default),
- it is considered to have malfunctioned. It will result in the
- run_this_if_an_unfixable_error_is_detected program,
- if any, being called.
- Implemented for the ALSA back end only.
-
-
-
-
- disable_standby_mode= "never" ;
-
- Shairport Sync has a "Disable Standby" feature to eliminate certain
- faint-but-annoying audible pops and clicks. When activsted, it prevents
- an output device from entering standby mode and thus it minimises standby/busy
- transitions, which can sometimes be heard. Use this setting to control when the
- Disable Standby feature is active: "never" means it will never be activated, "always"
- means it will be active as soon as shairport-sync starts running, and "auto"
- means it will be active while shairport-sync is in the "active" state.
- Shairport Sync goes "active" when a play session starts. When the play
- session ends, the system will stay active until the time
- specified in the active_state_timeout setting elapses.
- If a new play session starts before that, the system will remain active. Otherwise,
- the system will go inactive.
-
-
-
-
- "SNDIO" SETTINGS
- These settings are for the SNDIO back end, used to communicate with audio output
- devices in the SNDIO system.
-
-
- device= "snd/0" ;
- Use this optional setting to specify the name of the output device, e.g.
- "snd/0" . The default is to use the SNDIO system's default.
-
-
-
- rate= 44100 ;
- Use this optional setting to specify the output rate in frames per second.
- Valid rates are 44100, 88200, 176400 or 352800.
- The output device must have the capability to accept data at the specified rate. The
- default is 44100.
-
-
-
- format= "S16" ;
- Use this optional setting to specify the output format. Allowable values
- are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32".
- The device must have the capability to accept the format you specify.
"S" means
- signed; "U" means unsigned; BE means big-endian and LE means little-endian.
- Except where stated (using *LE or *BE), endianness matches that of the processor. The
- default is "S16".
- Since the SNDIO backend does not use a hardware mixer for volume control, dither will
- be introduced into the output if it is less than full volume.
- Thus, (unless you are ignoring the volume control setting),
- consider using 32- or 24-bit output if your device is capable of it, to get the lowest
- possible levels of dither.
- Please note that 32- or 24-bit has not been extensively tested on
- SNDIO.
-
-
-
- round= value ;
- Use this optional advanced setting to specify the period size of the SNDIO
- channel. If omitted, a SNDIO system default value will be used.
-
-
-
- bufsiz= value ;
- Use this optional advanced setting to specify the buffer size of the SNDIO
- channel. If omitted, a SNDIO system default value will be used.
-
-
- "PA" SETTINGS
- These settings are for the new PulseAudio backend.
-
-
- application_name= "Shairport Sync" ;
- Use this to set the name to appear in the Sounds "Applications" tab when
- Shairport Sync is active. The default is the name "Shairport Sync".
-
-
- "PIPE" SETTINGS
- These settings are for the PIPE backend, used to route audio to a named unix pipe.
- The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per
- second, interleaved stereo.
-
-
- name= "/path/to/pipe" ;
- Use this to specify the name and location of the pipe. The pipe will be
- created and opened when shairport-sync starts up and will be closed upon shutdown.
- Frames of audio will be sent to the pipe in packets of 352 frames and will be
- discarded if the pipe has not have a reader attached.
- The sender will wait for up to five seconds for a packet to be written before
- discarding it.
-
-
- "STDOUT" SETTINGS
- There are no settings for the STDOUT backend.
-
- "AO" SETTINGS
- There are no configuration file settings for the AO backend.
-
- "METADATA" SETTINGS
- shairport-sync can process metadata provided by the source, such as Track Number,
- Album Name, cover art, etc. and can provide additional metadata such as volume level,
- pause/resume, etc. It sends the metadata to a pipe, by default
- /tmp/shairport-sync-metadata .
- To process metadata, shairport-sync must have been compiled with metadata support
- included.
- You can check that this is so by running the command $ shairport-sync -V ;
- the identification string will contain the word metadata .
- Please note that different sources provide different levels of metadata. Some
- provide a lot; some provide almost none.
- The metadata group of settings allow you to enable metadata handling and
- to control certain aspects of it:
-
-
- enabled= "choice" ;
- Set the choice to "yes" to enable shairport-sync to look for
- metadata from the audio source and to forward it, along with metadata generated by
- shairport-sync itself, to the metadata pipe. The default is "no".
-
-
-
- include_cover_art= "choice" ;
- Set the choice to "yes" to enable shairport-sync to look for
- cover art from the audio source and to include it in the feed to the metadata pipe.
- You must also enable metadata (see above).
- One reason for not including cover art is that the images can sometimes be very large
- and may delay transmission of subsequent metadata through the pipe.
- The default is "no".
-
-
-
- pipe_name= "filepathname" ;
- Specify the absolute path name of the pipe through which metadata should
- be sent The default is /tmp/shairport-sync-metadata .
-
-
-
- socket_address= "hostnameOrIP" ;
- If hostnameOrIP is set to a host name or and IP address, UDP
- packets containing metadata will be sent to this address.
- May be a multicast address. Additionally, socket-port must be non-zero and
- enabled must be set to "yes".
-
-
-
- socket_port= port ;
- If socket_address is set, use port to specify the
- port to send UDP packets to. Must not be zero.
-
-
-
- socket_msglength= 65000 ;
- The maximum packet size for any UDP metadata. This must be between 500 or
- 65000. The default is 500.
-
-
- "DIAGNOSTICS" SETTINGS
-
- statistics= "setting" ;
- Use this setting to enable ("yes") or disable ("no") the output
- of some statistical information on the console or in the log. The default is to
- disable statistics.
-
-
-
- log_verbosity= 0 ;
- Use this to specify how much debugging information should be output or
- logged. The value 0 means no debug information, 3 means most
- debug information. The default is 0 .
-
-
-
-
-
- Options
-
- This section is about the command-line options available in shairport-sync.
- Note: if you are setting up shairport-sync for the first time or are updating an
- existing installation, you are encouraged to use the configuration file settings
- described above. Most of the command-line options described below
- simply replicate the configuration settings and are retained to provide backward
- compatibility with older installations of shairport-sync.
-
- 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.
-
- Program Options
-
- These command-line options are used by shairport-sync itself.
-
-
-
-
- -a service name | --name= service
- name
-
- Use this service name to identify this player in iTunes, etc.
-
- The following substitutions are allowed:
- %h for the computer's hostname,
- %H for the computer's hostname with the first letter capitalised (ASCII
- only),
- %v for the shairport-sync version number, e.g. "3.0.1" and
- %V for the shairport-sync version string, e.g.
- "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
- The default is "%H", which is replaced by the hostname with the first letter
- capitalised.
-
-
-
-
- -B program | --on-start= program
-
- Execute program when playback is about to begin. Specify the
- full path to the program, e.g. /usr/bin/logger .
- Executable scripts can be used, but they must have the appropriate shebang
- (#!/bin/sh in the headline.
-
- If you want shairport-sync to wait until the command has
- completed before starting to play, select the -w option as well.
-
-
-
-
- -c filename | --configfile= filename
-
- 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"
- section above.
-
-
-
-
- -d | --daemon
-
- Instruct shairport-sync to demonise itself. It will write its
- 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
- shaiport-sync has been compiled with libdaemon support.
-
-
-
-
- -E program | --on-stop= program
-
- Execute program when playback has ended. Specify the
- full path to the program, e.g. /usr/bin/logger .
- Executable scripts can be used, but they must have the appropriate shebang
- (#!/bin/sh in the headline.
- If you want shairport-sync to wait until the command has
- completed before continuing, select the -w option as well.
-
-
-
-
- --get-coverart
-
- This option requires the --meta-dir 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.
-
-
-
-
- -h | --help
-
- Print brief help message and exit.
-
-
-
-
- -j
-
- Instruct shairport-sync to demonise itself. Unlike the -d option, it will
- not write a Process ID (PID) to a file -- it will just (hence the "j") demonise
- itself. Only available if shaiport-sync has been compiled with libdaemon support.
-
-
-
-
- -k | --kill
-
- Kill the shairport-sync daemon and exit. (Requires that the daemon has
- written its PID to an agreed file -- see the -d option. Only available if
- shaiport-sync has been compiled with libdaemon support.)
-
-
-
-
- --logOutputLevel
-
- 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.
-
-
-
-
-
- -L | --latency= latency
-
- Use this to set the default latency , in frames, for audio coming from an
- unidentified source or from an iTunes Version 9 or earlier source. The standard value
- for the default latency 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.
-
-
-
-
- --meta-dir= directory
-
- Listen for metadata coming from the source and send it, along with metadata from
- shairport-sync itself, to a pipe called shairport-sync-metadata
- in the directory you specify. If you add the --get-cover-art
- then cover art will be sent through the pipe too. See https://github.com/mikebrady/shairport-sync-metadata-reader
- for a sample metadata reader.
-
-
-
-
- -m mdnsbackend | --mdns= mdnsbackend
-
- 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.
-
-
-
-
- -o outputbackend |
- --output= outputbackend
-
- Force the use of the specified output backend to play the audio.
- The default is to try the first one.
-
-
-
-
- -p port | --port= port
-
- Listen for play requests on port . The default is to use port
- 5000.
-
-
-
-
- --password= secret
-
- Require the password secret to be able to connect and stream to the
- service.
-
-
-
-
- -r threshold | --resync= threshold
-
- Resynchronise if timings differ by more than threshold 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 0 to disable resynchronisation. This setting is
- deprecated and will be removed in a future version of shairport-sync.
-
-
-
-
- --statistics
-
- Print some statistics in the standard output, or in the logfile if in daemon mode.
-
-
-
-
- -S mode | --stuffing= mode
-
- Stuff the audio stream using the mode . "Stuffing" refers to the
- process of adding or removing frames of audio to or from the
- stream sent to the output device to keep it exactly in synchrony
- with the player.
- The default mode, basic , is normally almost completely inaudible.
- 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.
-
-
-
-
- -t timeout | --timeout= timeout
-
- Exit play mode if the stream disappears for more than timeout
- 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 timeout seconds, the play session will be terminated.
- If you specify a timeout time of 0 ,
- 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.
-
-
-
-
- --tolerance= frames
-
- Allow playback to be up to frames 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 --statistics 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.
-
-
-
-
- -u
-
- If you are running shairport-sync from the command line and want logs to appear there,
- use this option. Otherwise, logs may go to the system log.
-
-
-
-
- -V | --version
-
- Print version information and exit.
-
-
-
-
- -v | --verbose
-
- Print debug information. Repeat up to three times to get more detail.
-
-
-
-
- -w | --wait-cmd
-
- Wait for commands specified using -B or -E to complete before
- continuing execution.
-
-
-
- Audio Backend Options
-
- These command-line options are passed to the chosen audio backend. The audio
- backend options are
- preceded by a -- symbol to introduce them and to separate them from any
- program options. In this way, option letters can be used as program
- options and also as audio backend options without ambiguity.
-
- In the ALSA backend, audio is sent to an output device
- which you can specify using the -d option.
- The output level (the "volume") is controlled using a level control associated with a
- mixer.
- By default, the mixer is implemented in shairport-sync itself in software.
- To use a hardware level control on a mixer on the sound card, specify the name of the
- mixer control with the -c option.
- If the mixer is not associated with the output device, then you need to specify where
- the mixer is to be found with the -m option.
-
-
-
-
- -c controlname
-
- Use the level control called controlname on the hardware mixer for
- controlling volume.
- This is needed if the mixer type is specified, using the -t option,
- to be hardware . There is no default.
-
-
-
-
- -d device
-
- Use the specified output device . You may specify a card, e.g.
- hw:0 , in which case the default output device on the card will be chosen.
- Alternatively, you can specify a specific device on a card, e.g. hw:0,0 .
- The default is the device named default .
-
-
-
-
- -m mixer
-
- Use the specified hardware mixer for volume control. Use this to specify
- where the mixer is to be found. For example, if the mixer is associated with a card,
- as is often the case, specify the card, e.g. hw:0 .
- If (unusually) the mixer is associated with a specific device on a card,
- specify the device, e.g. hw:0,1 .
- The default is the device named in the -d option,
- if given, or the device named default .
-
-
-
-
- -t devicetype
-
-
- This option is deprecated and is ignored. For your information, its functionality has
- been automatically incorporated in the -c option
- -- if you specify a mixer name with the -c option, it is assumed that the mixer is
- implemented in hardware.
-
-
-
-
- Examples
-
- Here is a slightly contrived example:
- shairport-sync -d
- -a "Joe's Stereo"
- -S soxr
- --
- -d hw:1,0
- -m hw:1
- -c PCM
-
-
- The program will run in daemon mode ( -d ), will be visible as
- "Joe's Stereo" ( -a "Joe's Stereo" ) and will use the SoX Resampler
- Library-based stuffing ( -S soxr ).
- The audio backend options following the -- separator specify
- that the audio will be output on output 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 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:
- shairport-sync -d
- -a "Joe's Stereo"
- -S soxr
- --
- -d hw:1
- -c PCM
-
-
-
-
-
-
- Credits
-
- Mike Brady developed shairport-sync from the original Shairport by James Laird.
- shairport-sync can be found at
- https://github.com/mikebrady/shairport-sync.
- Shairport can be found at
- https://github.com/abrasive/shairport.
-
-
-
- Comments
-
- This man page was written using xml2man by Oliver Kurth.
-
-
-
-
+Synopsis
+
+
+
+
+
+Description
+
+Configuration File Settings
+
http://www.hyperrealm.com/libconfig/libconfig_manual.html
+Options
+
Program Options
+
+
https://github.com/mikebrady/shairport-sync-metadata-reader
Audio Backend Options
+
+
Examples
+
+
+
+Credits
+
https://github.com/mikebrady/shairport-sync.
https://github.com/abrasive/shairport.
+Comments
+xml2man
+
diff --git a/mdns_avahi.c b/mdns_avahi.c
index caec909d..f3c1f1c9 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -96,6 +96,8 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
char *dacpid = strstr(name, "iTunes_Ctrl_");
if (dacpid) {
dacpid += strlen("iTunes_Ctrl_");
+ while (*dacpid == '0')
+ dacpid++; // remove any leading zeroes
if (strcmp(dacpid, dbs->dacp_id) == 0) {
debug(3, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
#ifdef CONFIG_DACP_CLIENT
@@ -340,17 +342,17 @@ static int avahi_register(char *srvname, int srvport) {
static void avahi_unregister(void) {
// debug(1, "avahi_unregister.");
if (tpoll) {
- debug(1, "avahi: stop the threaded poll.");
+ debug(2, "avahi: stop the threaded poll.");
avahi_threaded_poll_stop(tpoll);
if (client) {
- debug(1, "avahi: free the client.");
+ debug(2, "avahi: free the client.");
avahi_client_free(client);
client = NULL;
} else {
debug(1, "avahi attempting to unregister a NULL client");
}
- debug(1, "avahi: free the threaded poll.");
+ debug(2, "avahi: free the threaded poll.");
avahi_threaded_poll_free(tpoll);
tpoll = NULL;
} else {
@@ -358,7 +360,7 @@ static void avahi_unregister(void) {
}
if (service_name) {
- debug(1, "avahi: free the service name.");
+ debug(2, "avahi: free the service name.");
free(service_name);
} else
debug(1, "avahi attempt to free NULL service name");
@@ -412,7 +414,7 @@ void avahi_dacp_monitor_stop() {
}
avahi_threaded_poll_unlock(tpoll);
free(dbs->dacp_id);
- debug(1, "avahi_dacp_monitor_stop Avahi DACP monitor successfully stopped");
+ debug(2, "avahi_dacp_monitor_stop Avahi DACP monitor successfully stopped");
}
mdns_backend mdns_avahi = {.name = "avahi",
diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c
index 95bae443..cf178f1d 100644
--- a/mdns_dns_sd.c
+++ b/mdns_dns_sd.c
@@ -24,8 +24,8 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-#include "mdns.h"
#include "common.h"
+#include "mdns.h"
#include
#include
#include
diff --git a/mdns_external.c b/mdns_external.c
index 2ae23c61..974aa85b 100644
--- a/mdns_external.c
+++ b/mdns_external.c
@@ -25,8 +25,8 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-#include "mdns.h"
#include "common.h"
+#include "mdns.h"
#include
#include
#include
diff --git a/metadata_hub.c b/metadata_hub.c
index f079ea2a..190895c0 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -35,10 +35,10 @@
#include
#include
#include
+#include
#include
#include
#include
-#include
#include "config.h"
@@ -92,13 +92,6 @@ void add_metadata_watcher(metadata_watcher fn, void *userdata) {
}
}
-/*
-void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) {
- // debug(1, "metadata_hub_unlock_hub_mutex_cleanup called.");
- pthread_rwlock_unlock(&metadata_hub_re_lock);
-}
-*/
-
void run_metadata_watchers(void) {
int i;
for (i = 0; i < number_of_watchers; i++) {
@@ -129,83 +122,112 @@ void run_metadata_watchers(void) {
metadata_store.songtime_in_milliseconds_changed = 0;
}
-void metadata_hub_modify_prolog(void) {
+void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) {
+ debug(1, "metadata_hub_unlock_hub_mutex_cleanup called.");
+ metadata_hub_modify_epilog(0);
+}
+
+char *last_metadata_hub_modify_prolog_file = NULL;
+int last_metadata_hub_modify_prolog_line;
+int metadata_hub_re_lock_access_is_delayed;
+
+void _metadata_hub_modify_prolog(const char *filename, const int linenumber) {
// always run this before changing an entry or a sequence of entries in the metadata_hub
// debug(1, "locking metadata hub for writing");
if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) {
- debug(2, "Metadata_hub write lock is already taken -- must wait.");
+ if (last_metadata_hub_modify_prolog_file)
+ debug(2, "Metadata_hub write lock at \"%s:%d\" is already taken at \"%s:%d\" -- must wait.", filename, linenumber, last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line);
+ else
+ debug(2, "Metadata_hub write lock is already taken by unknown -- must wait.");
+ metadata_hub_re_lock_access_is_delayed = 0;
pthread_rwlock_wrlock(&metadata_hub_re_lock);
- debug(2, "Okay -- acquired the metadata_hub write lock.");
+ debug(2, "Okay -- acquired the metadata_hub write lock at \"%s:%d\".", filename, linenumber);
} else {
+ if (last_metadata_hub_modify_prolog_file) {
+ free(last_metadata_hub_modify_prolog_file);
+ }
+ last_metadata_hub_modify_prolog_file = strdup(filename);
+ last_metadata_hub_modify_prolog_line = linenumber;
debug(3, "Metadata_hub write lock acquired.");
}
+ metadata_hub_re_lock_access_is_delayed = 0;
}
-void metadata_hub_modify_epilog(int modified) {
+void _metadata_hub_modify_epilog(int modified, const char *filename, const int linenumber) {
metadata_store.dacp_server_has_been_active =
metadata_store.dacp_server_active; // set the scanner_has_been_active now.
if (modified) {
run_metadata_watchers();
}
+ if (metadata_hub_re_lock_access_is_delayed) {
+ if (last_metadata_hub_modify_prolog_file) {
+ debug(1, "Metadata_hub write lock taken at \"%s:%d\" is freed at \"%s:%d\".", last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line, filename, linenumber);
+ free(last_metadata_hub_modify_prolog_file);
+ last_metadata_hub_modify_prolog_file = NULL;
+ } else {
+ debug(1, "Metadata_hub write lock taken at an unknown place is freed at \"%s:%d\".", filename, linenumber);
+ }
+ }
pthread_rwlock_unlock(&metadata_hub_re_lock);
debug(3, "Metadata_hub write lock unlocked.");
}
-void metadata_hub_read_prolog(void) {
+/*
+void _metadata_hub_read_prolog(const char *filename, const int linenumber) {
// always run this before reading an entry or a sequence of entries in the metadata_hub
// debug(1, "locking metadata hub for reading");
if (pthread_rwlock_tryrdlock(&metadata_hub_re_lock) != 0) {
- debug(2, "Metadata_hub read lock is already taken -- must wait.");
+ debug(1, "Metadata_hub read lock is already taken -- must wait.");
pthread_rwlock_rdlock(&metadata_hub_re_lock);
- debug(2, "Okay -- acquired the metadata_hub read lock.");
+ debug(1, "Okay -- acquired the metadata_hub read lock.");
}
}
-void metadata_hub_read_epilog(void) {
+void _metadata_hub_read_epilog(const char *filename, const int linenumber) {
// always run this after reading an entry or a sequence of entries in the metadata_hub
// debug(1, "unlocking metadata hub for reading");
pthread_rwlock_unlock(&metadata_hub_re_lock);
}
-
+*/
char *metadata_write_image_file(const char *buf, int len) {
// warning -- this removes all files from the directory apart from this one, if it exists
// it will return a path to the image file allocated with malloc.
// free it if you don't need it.
- char *path = NULL; // this will be what is returned
- if (strcmp(config.cover_art_cache_dir,"") != 0) { // an empty string means do not write the file
+ char *path = NULL; // this will be what is returned
+ if (strcmp(config.cover_art_cache_dir, "") != 0) { // an empty string means do not write the file
uint8_t img_md5[16];
// uint8_t ap_md5[16];
- #ifdef CONFIG_OPENSSL
+#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, buf, len);
MD5_Final(img_md5, &ctx);
- #endif
+#endif
- #ifdef CONFIG_MBEDTLS
- #if MBEDTLS_VERSION_MINOR >= 7
+#ifdef CONFIG_MBEDTLS
+#if MBEDTLS_VERSION_MINOR >= 7
mbedtls_md5_context tctx;
mbedtls_md5_starts_ret(&tctx);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len);
mbedtls_md5_finish_ret(&tctx, img_md5);
- #else
+#else
mbedtls_md5_context tctx;
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
mbedtls_md5_finish(&tctx, img_md5);
- #endif
- #endif
+#endif
+#endif
- #ifdef CONFIG_POLARSSL
+#ifdef CONFIG_POLARSSL
md5_context tctx;
md5_starts(&tctx);
md5_update(&tctx, (const unsigned char *)buf, len);
md5_finish(&tctx, img_md5);
- #endif
+#endif
char img_md5_str[33];
memset(img_md5_str, 0, sizeof(img_md5_str));
@@ -232,8 +254,8 @@ char *metadata_write_image_file(const char *buf, int len) {
// if it exists, we're done
char *prefix = "cover-";
- size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 +
- strlen(ext);
+ size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) +
+ 1 + strlen(ext);
path = malloc(pl + 1);
snprintf(path, pl + 1, "%s/%s%s.%s", config.cover_art_cache_dir, prefix, img_md5_str, ext);
@@ -303,40 +325,47 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
// all the following items of metadata are contained in one metadata packet
// they are preceded by an 'ssnc' 'mdst' item and followed by an 'ssnc 'mden' item.
+ // we don't set "changed" for them individually; instead we set it when the 'mden' token
+ // comes in if the metadata_packet_item_changed is set.
+
+ int changed = 0;
+ int metadata_packet_item_changed = 0;
+ metadata_hub_modify_prolog();
+ pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL);
char *cs;
- int changed = 0;
if (type == 'core') {
switch (code) {
case 'mper': {
- // get the 64-bit number as a uint64_t by reading two uint32_t s and combining them
- uint64_t vl = ntohl(*(uint32_t*)data); // get the high order 32 bits
- vl = vl << 32; // shift them into the correct location
- uint64_t ul = ntohl(*(uint32_t*)(data+sizeof(uint32_t))); // and the low order 32 bits
- vl = vl + ul;
- debug(2, "MH Item ID seen: \"%" PRIx64 "\" of length %u.", vl, length);
- if (vl != metadata_store.item_id) {
- metadata_store.item_id = vl;
- metadata_store.item_id_changed = 1;
- metadata_store.item_id_received = 1;
- debug(2, "MH Item ID set to: \"%" PRIx64 "\"", metadata_store.item_id);
- }
- }
- break;
- case 'astm': {
- uint32_t ui = ntohl(*(uint32_t *)data);
- debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
- if (ui != metadata_store.songtime_in_milliseconds) {
- metadata_store.songtime_in_milliseconds = ui;
- metadata_store.songtime_in_milliseconds_changed = 1;
- debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
- }
+ // get the 64-bit number as a uint64_t by reading two uint32_t s and combining them
+ uint64_t vl = ntohl(*(uint32_t *)data); // get the high order 32 bits
+ vl = vl << 32; // shift them into the correct location
+ uint64_t ul = ntohl(*(uint32_t *)(data + sizeof(uint32_t))); // and the low order 32 bits
+ vl = vl + ul;
+ debug(2, "MH Item ID seen: \"%" PRIx64 "\" of length %u.", vl, length);
+ if (vl != metadata_store.item_id) {
+ metadata_store.item_id = vl;
+ metadata_store.item_id_changed = 1;
+ metadata_store.item_id_received = 1;
+ debug(2, "MH Item ID set to: \"%" PRIx64 "\"", metadata_store.item_id);
+ metadata_packet_item_changed = 1;
}
- break;
+ } break;
+ case 'astm': {
+ uint32_t ui = ntohl(*(uint32_t *)data);
+ debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
+ if (ui != metadata_store.songtime_in_milliseconds) {
+ metadata_store.songtime_in_milliseconds = ui;
+ metadata_store.songtime_in_milliseconds_changed = 1;
+ debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
+ metadata_packet_item_changed = 1;
+ }
+ } break;
case 'asal':
cs = strndup(data, length);
if (string_update(&metadata_store.album_name, &metadata_store.album_name_changed, cs)) {
debug(2, "MH Album name set to: \"%s\"", metadata_store.album_name);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -344,6 +373,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.artist_name, &metadata_store.artist_name_changed, cs)) {
debug(2, "MH Artist name set to: \"%s\"", metadata_store.artist_name);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -352,6 +382,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
if (string_update(&metadata_store.album_artist_name,
&metadata_store.album_artist_name_changed, cs)) {
debug(2, "MH Album Artist name set to: \"%s\"", metadata_store.album_artist_name);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -359,6 +390,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.comment, &metadata_store.comment_changed, cs)) {
debug(2, "MH Comment set to: \"%s\"", metadata_store.comment);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -366,6 +398,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.genre, &metadata_store.genre_changed, cs)) {
debug(2, "MH Genre set to: \"%s\"", metadata_store.genre);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -373,6 +406,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.track_name, &metadata_store.track_name_changed, cs)) {
debug(2, "MH Track Name set to: \"%s\"", metadata_store.track_name);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -380,6 +414,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.composer, &metadata_store.composer_changed, cs)) {
debug(2, "MH Composer set to: \"%s\"", metadata_store.composer);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -396,6 +431,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
if (string_update(&metadata_store.song_album_artist,
&metadata_store.song_album_artist_changed, cs)) {
debug(2, "MH Song Album Artist set to: \"%s\"", metadata_store.song_album_artist);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -403,6 +439,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.sort_name, &metadata_store.sort_name_changed, cs)) {
debug(2, "MH Sort Name set to: \"%s\"", metadata_store.sort_name);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -410,6 +447,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.sort_artist, &metadata_store.sort_artist_changed, cs)) {
debug(2, "MH Sort Artist set to: \"%s\"", metadata_store.sort_artist);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -417,6 +455,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.sort_album, &metadata_store.sort_album_changed, cs)) {
debug(2, "MH Sort Album set to: \"%s\"", metadata_store.sort_album);
+ metadata_packet_item_changed = 1;
}
free(cs);
break;
@@ -424,6 +463,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
cs = strndup(data, length);
if (string_update(&metadata_store.sort_composer, &metadata_store.sort_composer_changed, cs)) {
debug(2, "MH Sort Composer set to: \"%s\"", metadata_store.sort_composer);
+ metadata_packet_item_changed = 1;
}
free(cs);
default:
@@ -456,43 +496,45 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
break;
case 'mdst':
debug(2, "MH Metadata stream processing start.");
- metadata_hub_modify_prolog();
+ metadata_packet_item_changed = 0;
break;
case 'mden':
debug(2, "MH Metadata stream processing end.");
- metadata_hub_modify_epilog(1);
- debug(2, "MH Metadata stream processing epilog complete.");
+ changed = metadata_packet_item_changed;
break;
case 'PICT':
- metadata_hub_modify_prolog();
debug(2, "MH Picture received, length %u bytes.", length);
+
char uri[2048];
if ((length > 16) && (strcmp(config.cover_art_cache_dir,"")!=0)) { // if it's okay to write the file
+ // make this uncancellable
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
char *pathname = metadata_write_image_file(data, length);
snprintf(uri, sizeof(uri), "file://%s", pathname);
free(pathname);
+ pthread_setcancelstate(oldState, NULL);
+
} else {
uri[0] = '\0';
}
if (string_update(&metadata_store.cover_art_pathname,
&metadata_store.cover_art_pathname_changed,
uri)) // if the picture's file path is different from the stored one...
- metadata_hub_modify_epilog(1);
+ changed = 1;
else
- metadata_hub_modify_epilog(0);
+ changed = 0;
+// pthread_cleanup_pop(0); // don't remove the lock -- it'll have been done
break;
case 'clip':
- metadata_hub_modify_prolog();
cs = strndup(data, length);
if (string_update(&metadata_store.client_ip, &metadata_store.client_ip_changed, cs)) {
changed = 1;
debug(2, "MH Client IP set to: \"%s\"", metadata_store.client_ip);
}
free(cs);
- metadata_hub_modify_epilog(changed);
break;
case 'prgr':
- metadata_hub_modify_prolog();
cs = strndup(data, length);
if (string_update(&metadata_store.progress_string, &metadata_store.progress_string_changed,
cs)) {
@@ -500,64 +542,50 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
debug(2, "MH Progress String set to: \"%s\"", metadata_store.progress_string);
}
free(cs);
- metadata_hub_modify_epilog(changed);
break;
case 'svip':
- metadata_hub_modify_prolog();
cs = strndup(data, length);
if (string_update(&metadata_store.server_ip, &metadata_store.server_ip_changed, cs)) {
changed = 1;
debug(2, "MH Server IP set to: \"%s\"", metadata_store.server_ip);
}
free(cs);
- metadata_hub_modify_epilog(changed);
break;
case 'abeg':
- metadata_hub_modify_prolog();
changed = (metadata_store.active_state != AM_ACTIVE);
metadata_store.active_state = AM_ACTIVE;
- metadata_hub_modify_epilog(changed);
break;
case 'aend':
- metadata_hub_modify_prolog();
changed = (metadata_store.active_state != AM_INACTIVE);
metadata_store.active_state = AM_INACTIVE;
- metadata_hub_modify_epilog(changed);
break;
case 'pbeg':
- metadata_hub_modify_prolog();
- changed = ((metadata_store.player_state != PS_PLAYING) || (metadata_store.player_thread_active == 0));
+ changed = ((metadata_store.player_state != PS_PLAYING) ||
+ (metadata_store.player_thread_active == 0));
metadata_store.player_state = PS_PLAYING;
metadata_store.player_thread_active = 1;
- metadata_hub_modify_epilog(changed);
break;
case 'pend':
- metadata_hub_modify_prolog();
- changed = ((metadata_store.player_state != PS_STOPPED) || (metadata_store.player_thread_active == 1));
+ changed = ((metadata_store.player_state != PS_STOPPED) ||
+ (metadata_store.player_thread_active == 1));
metadata_store.player_state = PS_STOPPED;
metadata_store.player_thread_active = 0;
- metadata_hub_modify_epilog(changed);
break;
case 'pfls':
- metadata_hub_modify_prolog();
changed = (metadata_store.player_state != PS_PAUSED);
metadata_store.player_state = PS_PAUSED;
- metadata_hub_modify_epilog(changed);
break;
case 'pffr': // this is sent when the first frame has been received
case 'prsm':
- metadata_hub_modify_prolog();
changed = (metadata_store.player_state != PS_PLAYING);
metadata_store.player_state = PS_PLAYING;
- metadata_hub_modify_epilog(changed);
break;
case 'pvol': {
- metadata_hub_modify_prolog();
// Note: it's assumed that the config.airplay volume has already been correctly set.
- //int32_t actual_volume;
- //int gv = dacp_get_volume(&actual_volume);
- //metadata_hub_modify_prolog();
- //if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
+ // int32_t actual_volume;
+ // int gv = dacp_get_volume(&actual_volume);
+ // metadata_hub_modify_prolog();
+ // if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
// metadata_store.speaker_volume = actual_volume;
// changed = 1;
//}
@@ -565,7 +593,6 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_store.airplay_volume = config.airplay_volume;
changed = 1;
}
- metadata_hub_modify_epilog(changed); // change
} break;
default: {
@@ -588,4 +615,6 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
}
}
}
+ pthread_cleanup_pop(0); // don't remove the lock
+ metadata_hub_modify_epilog(changed);
}
diff --git a/metadata_hub.h b/metadata_hub.h
index 75d2f852..760a848b 100644
--- a/metadata_hub.h
+++ b/metadata_hub.h
@@ -148,9 +148,17 @@ void metadata_hub_release_track_artwork(void);
// these functions lock and unlock the read-write mutex on the metadata hub and run the watchers
// afterwards
-void metadata_hub_modify_prolog(void);
-void metadata_hub_modify_epilog(int modified); // set to true if modifications occurred, 0 otherwise
+void _metadata_hub_modify_prolog(const char *filename, const int linenumber);
+void _metadata_hub_modify_epilog(int modified, const char *filename, const int linenumber); // set to true if modifications occurred, 0 otherwise
+/*
// these are for safe reading
-void metadata_hub_read_prolog(void);
-void metadata_hub_read_epilog(void);
+void _metadata_hub_read_prolog(const char *filename, const int linenumber);
+void _metadata_hub_read_epilog(const char *filename, const int linenumber);
+*/
+
+#define metadata_hub_modify_prolog(void) _metadata_hub_modify_prolog(__FILE__, __LINE__)
+#define metadata_hub_modify_epilog(modified) _metadata_hub_modify_epilog(modified, __FILE__, __LINE__)
+
+#define metadata_hub_read_prolog(void) _metadata_hub_read_prolog(__FILE__, __LINE__)
+#define metadata_hub_read_epilog(void) _metadata_hub_modify_epilog(__FILE__, __LINE__)
diff --git a/mpris-service.c b/mpris-service.c
index 125398a1..9851f0b7 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -23,9 +23,9 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
+#include
#include
#include
-#include
#include "config.h"
@@ -48,12 +48,12 @@ double airplay_volume_to_mpris_volume(double sp) {
sp = -30.0;
if (sp > 0.0)
sp = 0.0;
- sp = (sp/30.0)+1;
+ sp = (sp / 30.0) + 1;
return sp;
}
double mpris_volume_to_airplay_volume(double sp) {
- sp = (sp-1.0)*30.0;
+ sp = (sp - 1.0) * 30.0;
if (sp < -30.0)
sp = -30.0;
if (sp > 0.0)
@@ -64,7 +64,8 @@ double mpris_volume_to_airplay_volume(double sp) {
void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
// debug(1, "MPRIS metadata watcher called");
char response[100];
- media_player2_player_set_volume(mprisPlayerPlayerSkeleton, airplay_volume_to_mpris_volume(argc->airplay_volume));
+ media_player2_player_set_volume(mprisPlayerPlayerSkeleton,
+ airplay_volume_to_mpris_volume(argc->airplay_volume));
switch (argc->repeat_status) {
case RS_NOT_AVAILABLE:
strcpy(response, "Not Available");
@@ -175,7 +176,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
// Add in the Track ID based on the 'mper' metadata if it is non-zero
if (argc->item_id != 0) {
char trackidstring[128];
- snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "", argc->item_id);
+ snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%" PRIX64 "",
+ argc->item_id);
GVariant *trackid = g_variant_new("o", trackidstring);
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
}
@@ -276,8 +278,8 @@ static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocati
}
static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton,
- GDBusMethodInvocation *invocation, const gdouble volume,
- __attribute__((unused)) gpointer user_data) {
+ GDBusMethodInvocation *invocation, const gdouble volume,
+ __attribute__((unused)) gpointer user_data) {
double ap_volume = mpris_volume_to_airplay_volume(volume);
debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume);
char command[256] = "";
@@ -334,7 +336,6 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-set-volume", G_CALLBACK(on_handle_set_volume),
NULL);
-
add_metadata_watcher(mpris_metadata_watcher, NULL);
debug(1, "MPRIS service started at \"%s\" on the %s bus.", name,
@@ -344,7 +345,7 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
static void on_mpris_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
const gchar *name,
__attribute__((unused)) gpointer user_data) {
- warn("Could not acquire an MPRIS interface named \"%s\" on the %s bus.", name,
+ warn("could not acquire an MPRIS interface named \"%s\" on the %s bus.", name,
(config.mpris_service_bus_type == DBT_session) ? "session" : "system");
}
diff --git a/mqtt.c b/mqtt.c
index f1726f21..ad5a4c40 100644
--- a/mqtt.c
+++ b/mqtt.c
@@ -24,10 +24,10 @@ void _cb_log(__attribute__((unused)) struct mosquitto *mosq, __attribute__((unus
int level, const char *str) {
switch (level) {
case MOSQ_LOG_DEBUG:
- debug(1, str);
+ debug(3, str);
break;
case MOSQ_LOG_INFO:
- debug(2, str);
+ debug(3, str);
break;
case MOSQ_LOG_NOTICE:
debug(3, str);
@@ -50,7 +50,7 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
memcpy(payload, msg->payload, msg->payloadlen);
payload[msg->payloadlen] = 0;
- debug(1, "[MQTT]: received Message on topic %s: %s\n", msg->topic, payload);
+ debug(2, "[MQTT]: received Message on topic %s: %s\n", msg->topic, payload);
// All recognized commands
char *commands[] = {"command", "beginff", "beginrew", "mutetoggle", "nextitem",
@@ -63,7 +63,7 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
while (commands[it] != NULL) {
if ((size_t)msg->payloadlen >= strlen(commands[it]) &&
strncmp(msg->payload, commands[it], strlen(commands[it])) == 0) {
- debug(1, "[MQTT]: DACP Command: %s\n", commands[it]);
+ debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
send_simple_dacp_command(commands[it]);
break;
}
@@ -74,13 +74,13 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
void on_disconnect(__attribute__((unused)) struct mosquitto *mosq,
__attribute__((unused)) void *userdata, __attribute__((unused)) int rc) {
connected = 0;
- debug(1, "[MQTT]: disconnected");
+ debug(2, "[MQTT]: disconnected");
}
void on_connect(struct mosquitto *mosq, __attribute__((unused)) void *userdata,
__attribute__((unused)) int rc) {
connected = 1;
- debug(1, "[MQTT]: connected");
+ debug(2, "[MQTT]: connected");
// subscribe if requested
if (config.mqtt_enable_remote) {
@@ -95,7 +95,7 @@ void mqtt_publish(char *topic, char *data, uint32_t length) {
char fulltopic[strlen(config.mqtt_topic) + strlen(topic) + 3];
snprintf(fulltopic, strlen(config.mqtt_topic) + strlen(topic) + 2, "%s/%s", config.mqtt_topic,
topic);
- debug(1, "[MQTT]: publishing under %s", fulltopic);
+ debug(2, "[MQTT]: publishing under %s", fulltopic);
int rc;
if ((rc = mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0, 0)) !=
diff --git a/player.c b/player.c
index 5876bd33..0ce0df85 100644
--- a/player.c
+++ b/player.c
@@ -4,7 +4,7 @@
* All rights reserved.
*
* Modifications for audio synchronisation
- * and related work, copyright (c) Mike Brady 2014 -- 2019
+ * and related work, copyright (c) Mike Brady 2014 -- 2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -91,6 +91,14 @@
#include "activity_monitor.h"
+// make the first audio packet deliberately early to bias the sync error of
+// the very first packet, making the error more likely to be too early
+// rather than too late. It it's too early,
+// a delay exactly compensating for it can be sent just before the
+// first packet. This should exactly compensate for the error.
+
+int64_t first_frame_early_bias = 8;
+
// default buffer size
// needs to be a power of 2 because of the way BUFIDX(seqno) works
//#define BUFFER_FRAMES 512
@@ -123,8 +131,11 @@ static void ab_resync(rtsp_conn_info *conn) {
for (i = 0; i < BUFFER_FRAMES; i++) {
conn->audio_buffer[i].ready = 0;
conn->audio_buffer[i].resend_request_number = 0;
- conn->audio_buffer[i].resend_time = 0; // this is either zero or the time the last resend was requested.
- conn->audio_buffer[i].initialisation_time = 0; // this is either the time the packet was received or the time it was noticed the packet was missing.
+ conn->audio_buffer[i].resend_time =
+ 0; // this is either zero or the time the last resend was requested.
+ conn->audio_buffer[i].initialisation_time =
+ 0; // this is either the time the packet was received or the time it was noticed the packet
+ // was missing.
conn->audio_buffer[i].sequence_number = 0;
}
conn->ab_synced = 0;
@@ -132,6 +143,7 @@ static void ab_resync(rtsp_conn_info *conn) {
conn->ab_buffering = 1;
}
+
// given starting and ending points as unsigned 16-bit integers running modulo 2^16, returns the
// position of x in the interval in *pos
// returns true if x is actually within the buffer
@@ -162,80 +174,57 @@ int position_in_modulo_uint16_t_buffer(uint16_t x, uint16_t start, uint16_t end,
return response;
}
-// given starting and ending points as unsigned 32-bit integers running modulo 2^32, returns the
-// position of x in the interval in *pos
-// returns true if x is actually within the buffer
+static inline seq_t SUCCESSOR(seq_t x) {
+ return x + 1;
+}
-int position_in_modulo_uint32_t_buffer(uint32_t x, uint32_t start, uint32_t end, uint32_t *pos) {
- int response = 0; // not in the buffer
- if (start <= end) {
- if (x < start) {
- if (pos)
- *pos = UINT32_MAX - start + 1 + x;
- } else {
- if (pos)
- *pos = x - start;
- if (x < end)
- response = 1;
- }
- } else if ((x >= start)) { // && (x <= UINT32_MAX)) { // always true
- response = 1;
- if (pos)
- *pos = x - start;
- } else {
- if (pos)
- *pos = UINT32_MAX - start + 1 + x;
- if (x < end) {
- response = 1;
- }
- }
+// a minus b
+int16_t seq_diff(seq_t a, seq_t b) {
+ int16_t response;
+ seq_t diff = a - b;
+ seq_t invdiff = b - a;
+ if (diff < invdiff)
+ response = diff;
+ else
+ response = -invdiff;
return response;
}
-// this is used.
-static inline seq_t SUCCESSOR(seq_t x) {
- uint32_t p = x & 0xffff;
- p += 1;
- p = p & 0xffff;
- return p;
-}
-
-// used in seq_diff and seq_order
-
-// anything with ORDINATE in it must be protected by the ab_mutex
-static inline int32_t ORDINATE(seq_t x, seq_t base) {
- int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay
- int32_t q = base; // int32_t from seq_t, i.e. uint16_t, so okay
- int32_t t = (p + 0x10000 - q) & 0xffff;
- // we definitely will get a positive number in t at this point, but it might be a
- // positive alias of a negative number, i.e. x might actually be "before" ab_read
- // So, if the result is greater than 32767, we will assume its an
- // alias and subtract 65536 from it
- if (t >= 32767) {
- // debug(1,"OOB: %u, ab_r: %u, ab_w: %u",x,ab_read,ab_write);
- t -= 65536;
- }
- return t;
-}
-
-// wrapped number between two seq_t.
-int32_t seq_diff(seq_t a, seq_t b, seq_t base) {
- int32_t diff = ORDINATE(b, base) - ORDINATE(a, base);
- return diff;
-}
-
-// the sequence numbers will wrap pretty often.
-// this returns true if the second arg is after the first
-static inline int seq_order(seq_t a, seq_t b, seq_t base) {
- int32_t d = ORDINATE(b, base) - ORDINATE(a, base);
- return d > 0;
-}
-
static inline seq_t seq_sum(seq_t a, seq_t b) {
- // uint32_t p = a & 0xffff;
- // uint32_t q = b & 0x0ffff;
- uint32_t r = (a + b) & 0xffff;
- return r;
+ return a + b;
+}
+
+// This orders u and v by picking the smaller of the two modulo differences
+// in unsigned modulo arithmetic and setting the sign of the result accordingly.
+
+// if u - v gives the smaller modulo difference, then that modulo difference is returned
+// otherwise the negative of the v - u modulo difference is returned.
+
+// (Think of a modulo ring starting at 0 and circling around clockwise finally to
+// modulo-2, modulo-1 to 0 again. To determine the ordering of the two numbers,
+// Find the shorter path between them around the ring.
+// If you go from v to u in the positive (0, 1, 2 ... or clockwise) direction,
+// then u is greater than (or "after") v.)
+
+// The result will always fit in an int64_t as it must be less or equal to half way
+// around the ring.
+// Due to the asymmetry of 2's complement representation, however,
+// if the difference is equal to modulo / 2, it is ambiguous as to whether
+// u is "after" or "before" (i.e. greater or less than) v.
+// and will be returned as -modulo / 2, that is, "before".
+// If that ever happens and there is a real ambiguity in the application,
+// the modulo chosen is too small.
+
+
+int64_t int64_mod_difference(const uint64_t u, const uint64_t v, const uint64_t modulo) {
+ int64_t response;
+ uint64_t diff = (u - v) % modulo;
+ uint64_t invdiff = (v - u) % modulo;
+ if (diff < invdiff)
+ response = diff;
+ else
+ response = -invdiff;
+ return response;
}
void reset_input_flow_metrics(rtsp_conn_info *conn) {
@@ -323,16 +312,18 @@ int audio_packet_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_c
}
if (outsize > maximum_possible_outsize) {
- debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- "
- "truncated, but buffer overflow possible! Encrypted = %d.",
+ debug(2,
+ "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- "
+ "truncated, but buffer overflow possible! Encrypted = %d.",
outsize, maximum_possible_outsize, conn->stream.encrypted);
reply = -1; // output packet is the wrong size
}
*destlen = outsize / conn->input_bytes_per_frame;
if ((outsize % conn->input_bytes_per_frame) != 0)
- debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) "
- "and the audio frame size (%d).",
+ debug(1,
+ "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) "
+ "and the audio frame size (%d).",
*destlen, outsize, conn->input_bytes_per_frame);
return reply;
}
@@ -469,183 +460,178 @@ int first_possibly_missing_frame = -1;
void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, int len,
rtsp_conn_info *conn) {
- // ignore a request to flush that has been made before the first packet...
- if (conn->packet_count == 0) {
- debug_mutex_lock(&conn->flush_mutex, 1000, 1);
- conn->flush_requested = 0;
- conn->flush_rtp_timestamp = 0;
- debug_mutex_unlock(&conn->flush_mutex, 3);
- }
-
debug_mutex_lock(&conn->ab_mutex, 30000, 0);
- uint64_t time_now = get_absolute_time_in_fp();
+ uint64_t time_now = get_absolute_time_in_ns();
conn->packet_count++;
conn->packet_count_since_flush++;
conn->time_of_last_audio_packet = time_now;
if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
- if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) &&
- (modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) <
- conn->input_rate * 10)) { // if it's less than 10 seconds
- debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %" PRIu32
- ", flushing to "
- "timestamp: %" PRIu32 ".",
- seqno, actual_timestamp, conn->flush_rtp_timestamp);
- conn->initial_reference_time = 0;
- conn->initial_reference_timestamp = 0;
- } else {
- abuf_t *abuf = 0;
- if (!conn->ab_synced) {
- // if this is the first packet...
- debug(3, "syncing to seqno %u.", seqno);
- conn->ab_write = seqno;
- conn->ab_read = seqno;
- conn->ab_synced = 1;
- }
- if (conn->ab_write ==
- seqno) { // if this is the expected packet (which could be the first packet...)
- if (conn->input_frame_rate_starting_point_is_valid == 0) {
- if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) {
- conn->frames_inward_measurement_start_time = time_now;
- conn->frames_inward_frames_received_at_measurement_start_time = actual_timestamp;
- conn->input_frame_rate_starting_point_is_valid = 1; // valid now
- }
- }
- conn->frames_inward_measurement_time = time_now;
- conn->frames_inward_frames_received_at_measurement_time = actual_timestamp;
- abuf = conn->audio_buffer + BUFIDX(seqno);
- conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space
- } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
- int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read);
- if (gap <= 0)
- debug(1, "Unexpected gap size: %d.", gap);
- int i;
- for (i = 0; i < gap; i++) {
- abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
- abuf->ready = 0; // to be sure, to be sure
- abuf->resend_request_number = 0;
- abuf->initialisation_time = time_now; // this represents when the packet was noticed to be missing
- abuf->status = 1 << 0; // signifying missing
- abuf->resend_time = 0;
- abuf->given_timestamp = 0;
- abuf->sequence_number = 0;
- }
- // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write);
- abuf = conn->audio_buffer + BUFIDX(seqno);
- // rtp_request_resend(ab_write, gap);
- // resend_requests++;
- conn->ab_write = SUCCESSOR(seqno);
- } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // older than expected but not too late
- conn->late_packets++;
- abuf = conn->audio_buffer + BUFIDX(seqno);
- } else { // too late.
- conn->too_late_packets++;
- }
+ abuf_t *abuf = 0;
+ if (!conn->ab_synced) {
+ // if this is the first packet...
+ debug(3, "syncing to seqno %u.", seqno);
+ conn->ab_write = seqno;
+ conn->ab_read = seqno;
+ conn->ab_synced = 1;
+ }
+ int16_t write_point_gap = seq_diff(seqno,conn->ab_write); // this is the difference between
+ // the incoming packet number and the packet number that was expected.
+ if (write_point_gap == 0) { // if this is the expected packet (which could be the first packet...)
+ if (conn->input_frame_rate_starting_point_is_valid == 0) {
+ if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) {
+ conn->frames_inward_measurement_start_time = time_now;
+ conn->frames_inward_frames_received_at_measurement_start_time = actual_timestamp;
+ conn->input_frame_rate_starting_point_is_valid = 1; // valid now
+ }
+ }
+ conn->frames_inward_measurement_time = time_now;
+ conn->frames_inward_frames_received_at_measurement_time = actual_timestamp;
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space
+ } else if (write_point_gap > 0) { // newer than expected
+ // initialise the frames in between
+ int i;
+ for (i = 0; i < write_point_gap; i++) {
+ abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
+ abuf->ready = 0; // to be sure, to be sure
+ abuf->resend_request_number = 0;
+ abuf->initialisation_time =
+ time_now; // this represents when the packet was noticed to be missing
+ abuf->status = 1 << 0; // signifying missing
+ abuf->resend_time = 0;
+ abuf->given_timestamp = 0;
+ abuf->sequence_number = 0;
+ }
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ conn->ab_write = SUCCESSOR(seqno);
+ } else if (seq_diff(seqno,conn->ab_read) > 0) { // older than expected but still not too late
+ conn->late_packets++;
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ } else { // too late.
+ conn->too_late_packets++;
+ }
- if (abuf) {
- int datalen = conn->max_frames_per_packet;
- abuf->initialisation_time = time_now;
- abuf->resend_time = 0;
- if (audio_packet_decode(abuf->data, &datalen, data, len, conn) == 0) {
- abuf->ready = 1;
- abuf->status = 0; // signifying that it was received
- abuf->length = datalen;
- abuf->given_timestamp = actual_timestamp;
- abuf->sequence_number = seqno;
- } else {
- debug(1, "Bad audio packet detected and discarded.");
- abuf->ready = 0;
- abuf->status = 1 << 1; // bad packet, discarded
- abuf->resend_request_number = 0;
- abuf->given_timestamp = 0;
- abuf->sequence_number = 0;
- }
- }
+ if (abuf) {
+ int datalen = conn->max_frames_per_packet;
+ abuf->initialisation_time = time_now;
+ abuf->resend_time = 0;
+ if (audio_packet_decode(abuf->data, &datalen, data, len, conn) == 0) {
+ abuf->ready = 1;
+ abuf->status = 0; // signifying that it was received
+ abuf->length = datalen;
+ abuf->given_timestamp = actual_timestamp;
+ abuf->sequence_number = seqno;
+ } else {
+ debug(1, "Bad audio packet detected and discarded.");
+ abuf->ready = 0;
+ abuf->status = 1 << 1; // bad packet, discarded
+ abuf->resend_request_number = 0;
+ abuf->given_timestamp = 0;
+ abuf->sequence_number = 0;
+ }
+ }
- int rc = pthread_cond_signal(&conn->flowcontrol);
- if (rc)
- debug(1, "Error signalling flowcontrol.");
-
- // resend checks
- {
- uint64_t minimum_wait_time = (uint64_t)(config.resend_control_first_check_time * (uint64_t)0x100000000);
- uint64_t resend_repeat_interval = (uint64_t)(config.resend_control_check_interval_time * (uint64_t)0x100000000);
- uint64_t minimum_remaining_time = (uint64_t)((config.resend_control_last_check_time + config.audio_backend_buffer_desired_length)* (uint64_t)0x100000000);
- uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)0x100000000);
- latency_time = latency_time / (uint64_t)conn->input_rate;
-
- int x; // this is the first frame to be checked
- // if we detected a first empty frame before and if it's still in the buffer!
- if ((first_possibly_missing_frame >= 0) && (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read, conn->ab_write, NULL))) {
- x = first_possibly_missing_frame;
- } else {
- x = conn->ab_read;
- }
-
- first_possibly_missing_frame = -1; // has not been set
-
- int missing_frame_run_count = 0;
- int start_of_missing_frame_run = -1;
- int number_of_missing_frames = 0;
- while (x != conn->ab_write) {
- abuf_t *check_buf = conn->audio_buffer + BUFIDX(x);
- if (!check_buf->ready) {
- if (first_possibly_missing_frame < 0)
- first_possibly_missing_frame = x;
- number_of_missing_frames++;
- // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%" PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x, check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time);
- int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) || ((check_buf->initialisation_time - (time_now - latency_time)) < minimum_remaining_time));
- int too_early = ((time_now - check_buf->initialisation_time) < minimum_wait_time);
- int too_soon_after_last_request = ((check_buf->resend_time != 0) && ((time_now - check_buf->resend_time) < resend_repeat_interval)); // time_now can never be less than the time_tag
+ int rc = pthread_cond_signal(&conn->flowcontrol);
+ if (rc)
+ debug(1, "Error signalling flowcontrol.");
- if (too_late)
- check_buf->status |= 1<<2; // too late
- else
- check_buf->status &= 0xFF-(1<<2); // not too late
- if (too_early)
- check_buf->status |= 1<<3; // too early
- else
- check_buf->status &= 0xFF-(1<<3); // not too early
- if (too_soon_after_last_request)
- check_buf->status |= 1<<4; // too soon after last request
- else
- check_buf->status &= 0xFF-(1<<4); // not too soon after last request
-
- if ((!too_soon_after_last_request) && (!too_late) && (!too_early)){
- if (start_of_missing_frame_run == -1) {
- start_of_missing_frame_run = x;
- missing_frame_run_count = 1;
- } else {
- missing_frame_run_count++;
- }
- check_buf->resend_time = time_now; // setting the time to now because we are definitely going to take action
- check_buf->resend_request_number++;
- debug(3,"Frame %d is missing with ab_read of %u and ab_write of %u.", x, conn->ab_read, conn->ab_write);
- }
- // if (too_late) {
- // debug(1,"too late to get missing frame %u.", x);
- // }
- }
- //if (number_of_missing_frames != 0)
- // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame);
- x = (x + 1) & 0xffff;
- if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) {
- // send a resend request
- if (missing_frame_run_count > 1)
- debug(2,"request resend of %d packets starting at seqno %u.", missing_frame_run_count, start_of_missing_frame_run);
- if (config.disable_resend_requests == 0) {
- debug_mutex_unlock(&conn->ab_mutex, 3);
- rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn);
- debug_mutex_lock(&conn->ab_mutex, 20000, 1);
- conn->resend_requests++;
- }
- start_of_missing_frame_run = -1;
- missing_frame_run_count = 0;
- }
- }
- if (number_of_missing_frames == 0)
- first_possibly_missing_frame = conn->ab_write;
- }
- }
+ // resend checks
+ {
+ uint64_t minimum_wait_time =
+ (uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000);
+ uint64_t resend_repeat_interval =
+ (uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000);
+ uint64_t minimum_remaining_time = (uint64_t)(
+ (config.resend_control_last_check_time + config.audio_backend_buffer_desired_length) *
+ (uint64_t)1000000000);
+ uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000);
+ latency_time = latency_time / (uint64_t)conn->input_rate;
+
+ int x; // this is the first frame to be checked
+ // if we detected a first empty frame before and if it's still in the buffer!
+ if ((first_possibly_missing_frame >= 0) &&
+ (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read,
+ conn->ab_write, NULL))) {
+ x = first_possibly_missing_frame;
+ } else {
+ x = conn->ab_read;
+ }
+
+ first_possibly_missing_frame = -1; // has not been set
+
+ int missing_frame_run_count = 0;
+ int start_of_missing_frame_run = -1;
+ int number_of_missing_frames = 0;
+ while (x != conn->ab_write) {
+ abuf_t *check_buf = conn->audio_buffer + BUFIDX(x);
+ if (!check_buf->ready) {
+ if (first_possibly_missing_frame < 0)
+ first_possibly_missing_frame = x;
+ number_of_missing_frames++;
+ // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%"
+ // PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x,
+ // check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time);
+ int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) ||
+ ((check_buf->initialisation_time - (time_now - latency_time)) <
+ minimum_remaining_time));
+ int too_early = ((time_now - check_buf->initialisation_time) < minimum_wait_time);
+ int too_soon_after_last_request =
+ ((check_buf->resend_time != 0) &&
+ ((time_now - check_buf->resend_time) <
+ resend_repeat_interval)); // time_now can never be less than the time_tag
+
+ if (too_late)
+ check_buf->status |= 1 << 2; // too late
+ else
+ check_buf->status &= 0xFF - (1 << 2); // not too late
+ if (too_early)
+ check_buf->status |= 1 << 3; // too early
+ else
+ check_buf->status &= 0xFF - (1 << 3); // not too early
+ if (too_soon_after_last_request)
+ check_buf->status |= 1 << 4; // too soon after last request
+ else
+ check_buf->status &= 0xFF - (1 << 4); // not too soon after last request
+
+ if ((!too_soon_after_last_request) && (!too_late) && (!too_early)) {
+ if (start_of_missing_frame_run == -1) {
+ start_of_missing_frame_run = x;
+ missing_frame_run_count = 1;
+ } else {
+ missing_frame_run_count++;
+ }
+ check_buf->resend_time = time_now; // setting the time to now because we are
+ // definitely going to take action
+ check_buf->resend_request_number++;
+ debug(3, "Frame %d is missing with ab_read of %u and ab_write of %u.", x,
+ conn->ab_read, conn->ab_write);
+ }
+ // if (too_late) {
+ // debug(1,"too late to get missing frame %u.", x);
+ // }
+ }
+ // if (number_of_missing_frames != 0)
+ // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame
+ // = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame);
+ x = (x + 1) & 0xffff;
+ if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) {
+ // send a resend request
+ if (missing_frame_run_count > 1)
+ debug(3, "request resend of %d packets starting at seqno %u.",
+ missing_frame_run_count, start_of_missing_frame_run);
+ if (config.disable_resend_requests == 0) {
+ debug_mutex_unlock(&conn->ab_mutex, 3);
+ rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn);
+ debug_mutex_lock(&conn->ab_mutex, 20000, 1);
+ conn->resend_requests++;
+ }
+ start_of_missing_frame_run = -1;
+ missing_frame_run_count = 0;
+ }
+ }
+ if (number_of_missing_frames == 0)
+ first_possibly_missing_frame = conn->ab_write;
+ }
}
debug_mutex_unlock(&conn->ab_mutex, 0);
}
@@ -660,6 +646,31 @@ int32_t rand_in_range(int32_t exclusive_range_limit) {
return sp >> 32;
}
+int get_and_check_effective_latency(rtsp_conn_info *conn, uint32_t *effective_latency,
+ double offset_time) {
+ // check that the overall effective latency remains positive and is not greater than the capacity
+ // of the buffers return 0 if okay, -1 if the latency would be negative, +1 if it would be too
+ // large
+
+ int result = 0;
+ if (offset_time >= 0.0) {
+ uint32_t latency_addition = (uint32_t)(offset_time * conn->input_rate);
+ // keep about one second of buffers back
+ if ((*effective_latency + latency_addition) <=
+ (conn->max_frames_per_packet * (BUFFER_FRAMES - config.minimum_free_buffer_headroom)))
+ *effective_latency += latency_addition;
+ else
+ result = 1;
+ } else {
+ uint32_t latency_reduction = (uint32_t)((-offset_time) * conn->input_rate);
+ if (latency_reduction <= *effective_latency)
+ *effective_latency -= latency_reduction;
+ else
+ result = -1;
+ }
+ return result;
+}
+
static inline void process_sample(int32_t sample, char **outp, sps_format_t format, int volume,
int dither, rtsp_conn_info *conn) {
/*
@@ -898,11 +909,14 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
int wait;
long dac_delay = 0; // long because alsa returns a long
+ int have_sent_prefiller_silence =
+ 0; // set to true when we have sent at least one silent frame to the DAC
+
pthread_cleanup_push(buffer_get_frame_cleanup_handler,
(void *)conn); // undo what's been done so far
do {
// get the time
- local_time_now = get_absolute_time_in_fp(); // type okay
+ local_time_now = get_absolute_time_in_ns(); // type okay
// debug(3, "buffer_get_frame is iterating");
int rco = get_requested_connection_state_to_output();
@@ -911,32 +925,97 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
conn->connection_state_to_output = rco;
// change happening
if (conn->connection_state_to_output == 0) { // going off
+ debug(2, "request flush because connection_state_to_output is off");
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
conn->flush_requested = 1;
+ conn->flush_rtp_timestamp = 0;
debug_mutex_unlock(&conn->flush_mutex, 3);
}
}
if (config.output->is_running)
if (config.output->is_running() != 0) { // if the back end isn't running for any reason
- debug(3, "not running");
+ debug(2, "request flush because back end is not running");
debug_mutex_lock(&conn->flush_mutex, 1000, 0);
conn->flush_requested = 1;
+ conn->flush_rtp_timestamp = 0;
debug_mutex_unlock(&conn->flush_mutex, 0);
}
debug_mutex_lock(&conn->flush_mutex, 1000, 0);
if (conn->flush_requested == 1) {
- if (config.output->flush)
- config.output->flush(); // no cancellation points
- ab_resync(conn); // no cancellation points
- conn->first_packet_timestamp = 0;
- conn->first_packet_time_to_play = 0;
- conn->time_since_play_started = 0;
- conn->flush_requested = 0;
+ if (conn->flush_output_flushed == 0)
+ if (config.output->flush) {
+ config.output->flush(); // no cancellation points
+ debug(2, "flush request: flush output device.");
+ }
+ conn->flush_output_flushed = 1;
+ // now check to see it the flush request is for frames in the buffer or not
+ // if the first_packet_timestamp is zero, don't check
+ int flush_needed = 0;
+ int drop_request = 0;
+ if (conn->flush_rtp_timestamp == 0) {
+ debug(1, "flush request: flush frame 0 -- flush assumed to be needed.");
+ flush_needed = 1;
+ drop_request = 1;
+ } else {
+ if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) {
+ abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read);
+ abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1);
+ if ((firstPacket != NULL) && (firstPacket->ready)) {
+ // discard flushes more than 10 seconds into the future -- they are probably bogus
+ uint32_t first_frame_in_buffer = firstPacket->given_timestamp;
+ int32_t offset_from_first_frame = (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer);
+ if (offset_from_first_frame > (int)conn->input_rate * 10) {
+ debug(1, "flush request: sanity check -- flush frame %u is too far into the future from the first frame %u -- discarded.", conn->flush_rtp_timestamp, first_frame_in_buffer);
+ drop_request = 1;
+ } else {
+ if ((lastPacket != NULL) && (lastPacket->ready)) {
+ // we have enough information to check if the flush is needed or can be discarded
+ uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1;
+ // now we have to work out if the flush frame is in the buffer
+ // if it is later than the end of the buffer, flush everything and keep the request active.
+ // if it is in the buffer, we need to flush part of the buffer. Actually we flush the entire buffer and drop the request.
+ // if it is before the buffer, no flush is needed. Drop the request.
+ if (offset_from_first_frame > 0) {
+ int32_t offset_to_last_frame = (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp);
+ if (offset_to_last_frame >= 0) {
+ debug(2,"flush request: flush frame %u active -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
+ drop_request = 1;
+ flush_needed = 1;
+ } else {
+ debug(2,"flush request: flush frame %u pending -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
+ flush_needed = 1;
+ }
+ } else {
+ debug(2,"flush request: flush frame %u expired -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
+ drop_request = 1;
+ }
+ }
+ }
+ }
+ } else {
+ debug(2, "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: %u, ab_write: %u", conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write);
+ // leave flush request pending and don't do a buffer flush, because there isn't one
+ }
+ }
+ if (flush_needed) {
+ debug(2, "flush request: flush done.");
+ ab_resync(conn); // no cancellation points
+ conn->first_packet_timestamp = 0;
+ conn->first_packet_time_to_play = 0;
+ conn->time_since_play_started = 0;
+ have_sent_prefiller_silence = 0;
+ dac_delay = 0;
+ }
+ if (drop_request) {
+ debug(2, "flush request: request dropped.");
+ conn->flush_requested = 0;
+ conn->flush_rtp_timestamp = 0;
+ conn->flush_output_flushed = 0;
+ }
}
debug_mutex_unlock(&conn->flush_mutex, 0);
-
if (conn->ab_synced) {
curframe = conn->audio_buffer + BUFIDX(conn->ab_read);
@@ -948,71 +1027,29 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (curframe->sequence_number != conn->ab_read) {
// some kind of sync problem has occurred.
if (BUFIDX(curframe->sequence_number) == BUFIDX(conn->ab_read)) {
- // it looks like some kind of aliasing has happened
- if (seq_order(conn->ab_read, curframe->sequence_number, conn->ab_read)) {
- conn->ab_read = curframe->sequence_number;
- debug(1, "Aliasing of buffer index -- reset.");
- }
+ // it looks like aliasing has happened
+ // jump to the new incoming stuff...
+ conn->ab_read = curframe->sequence_number;
+ debug(1, "Aliasing of buffer index -- reset.");
} else {
debug(1, "Inconsistent sequence numbers detected");
}
}
-
- // if (conn->flush_rtp_timestamp != 0)
- // debug(2,"flush_rtp_timestamp is %" PRIx32 " and curframe->given_timestamp is %" PRIx32
- // ".", conn->flush_rtp_timestamp , curframe->given_timestamp);
-
- if ((conn->flush_rtp_timestamp != 0) &&
- (curframe->given_timestamp != conn->flush_rtp_timestamp) &&
- (modulo_32_offset(curframe->given_timestamp, conn->flush_rtp_timestamp) <
- conn->input_rate * 10)) { // if it's less than ten seconds
- debug(3, "Dropping flushed packet in buffer_get_frame, seqno %u, timestamp %" PRIu32
- ", flushing to "
- "timestamp: %" PRIu32 ".",
- curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp);
- curframe->ready = 0;
- curframe->resend_request_number = 0;
- curframe = NULL; // this will be returned and will cause the loop to go around again
- conn->initial_reference_time = 0;
- conn->initial_reference_timestamp = 0;
- } else if ((conn->flush_rtp_timestamp != 0) &&
- (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) >
- conn->input_rate / 5) &&
- (modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) <
- conn->input_rate * 10)) {
- debug(3, "Dropping flush request in buffer_get_frame");
- conn->flush_rtp_timestamp = 0;
- }
}
+
if ((curframe) && (curframe->ready)) {
notified_buffer_empty = 0; // at least one buffer now -- diagnostic only.
if (conn->ab_buffering) { // if we are getting packets but not yet forwarding them to the
// player
- int have_sent_prefiller_silence = 1; // set true when we have sent some silent frames to
- // the DAC
- /*
- int64_t reference_timestamp;
- uint64_t reference_timestamp_time, remote_reference_timestamp_time;
- get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
- &remote_reference_timestamp_time, conn);
- reference_timestamp *= conn->output_sample_ratio;
- */
- if (conn->first_packet_timestamp == 0) { // if this is the very first packet
- // debug(1,"First frame seen, time %u, with %d
- // frames...",curframe->timestamp,seq_diff(ab_read, ab_write));
-
+ if (conn->first_packet_timestamp == 0) { // if this is the very first packet
if (have_timestamp_timing_information(conn)) { // if we have a reference time
// debug(1,"First frame seen with timestamp...");
conn->first_packet_timestamp =
curframe->given_timestamp; // we will keep buffering until we are
// supposed to start playing this
- have_sent_prefiller_silence = 0;
-
-// debug(1, "First packet timestamp is %" PRId64 ".", conn->first_packet_timestamp);
-
-// say we have started playing here
#ifdef CONFIG_METADATA
+ // say we have started receiving frames here
debug(2, "pffr");
send_ssnc_metadata(
'pffr', NULL, 0,
@@ -1020,12 +1057,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
#endif
// Here, calculate when we should start playing. We need to know when to allow the
// packets to be sent to the player.
- // We will send packets of silence from now until that time and then we will send the
- // first packet, which will be followed by the subsequent packets.
- // we will get a fix every second or so, which will be stored as a pair consisting of
- // the time when the packet with a particular timestamp should be played, neglecting
- // latencies, etc.
+
+ // every second or so, we get a reference on when a particular packet should be
+ // played.
// It probably won't be the timestamp of our first packet, however, so we might have
// to do some calculations.
@@ -1038,254 +1073,207 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// that the audio back end has a latency of 100 ms, we would
// ask for the first packet to be emitted 100 ms earlier than it should, i.e. -4410
// frames, so that when it got through the audio back end,
- // if would be in sync. To do this, we would give it a latency offset of -100 ms, i.e.
- // -4410 frames.
- // debug(1, "Output sample ratio is %d", conn->output_sample_ratio);
+ // if would be in sync. To do this, we would give it a latency offset of -0.1 sec,
+ // i.e. -4410 frames.
+
+ uint64_t should_be_time;
+ uint32_t effective_latency = conn->latency;
+
+ get_and_check_effective_latency(conn, &effective_latency,
+ config.audio_backend_latency_offset);
+ // we are ignoring the returned status because it will be captured on subsequent
+ // frames, below.
// what we are asking for here is "what is the local time at which time the calculated
// frame should be played"
- uint64_t should_be_time;
- frame_to_local_time(conn->first_packet_timestamp + conn->latency +
- (uint32_t)(config.audio_backend_latency_offset *
- conn->input_rate), // this will go modulo 2^32
- &should_be_time,
- conn);
+ frame_to_local_time(conn->first_packet_timestamp +
+ effective_latency, // this will go modulo 2^32
+ &should_be_time, conn);
conn->first_packet_time_to_play = should_be_time;
+ debug(2,"first_packet_time set for frame %u.", conn->first_packet_timestamp);
+
if (local_time_now > conn->first_packet_time_to_play) {
uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
- lateness = (lateness * 1000000) >> 32; // microseconds
- debug(3, "First packet is %" PRIu64 " microseconds late! Flushing 0.5 seconds",
+ debug(2, "First packet is %" PRIu64 " nanoseconds late! Flushing 0.5 seconds",
lateness);
do_flush(conn->first_packet_timestamp + 5 * 4410, conn);
}
}
}
+
+
if (conn->first_packet_time_to_play != 0) {
- // recalculate conn->first_packet_time_to_play -- the latency might change
+ // Now that we know the timing of the first packet...
+ if (config.output->delay) {
+ // and that the output device is capable of synchronization...
- uint64_t should_be_time;
- frame_to_local_time(conn->first_packet_timestamp + conn->latency +
- (uint32_t)(config.audio_backend_latency_offset *
- conn->input_rate), // this should go modulo 2^32
- &should_be_time,
- conn);
+ // We may send packets of
+ // silence from now until the time the first audio packet should be sent
+ // and then we will send the first packet, which will be followed by
+ // the subsequent packets.
+ // here, we figure out whether and what silence to send.
- conn->first_packet_time_to_play = should_be_time;
+ uint64_t should_be_time;
+ uint32_t effective_latency = conn->latency;
- // we want the frames of silence sent at the start to be fairly large in case the output
- // device's minimum buffer size is large. But they can't be greater than the silent
- // lead_in time
- // which is either the agreed latency or the silent lead-in time specified by the
- // setting
- // In fact, if should be some fraction of them, to allow for adjustment.
+ switch (get_and_check_effective_latency(conn, &effective_latency,
+ config.audio_backend_latency_offset)) {
+ case -1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Negative latency! A latency of %d frames requested by the player, when "
+ "combined with an audio_backend_latency_offset of %f seconds, would make the "
+ "overall latency negative. The audio_backend_latency_offset setting is "
+ "ignored.",
+ conn->latency, config.audio_backend_latency_offset);
+ config.audio_backend_latency_offset = 0; // set it to zero
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ break;
+ case 1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("An audio_backend_latency_offset of %f seconds may exceed the frame buffering "
+ "capacity -- the setting is ignored.",
+ config.audio_backend_latency_offset);
+ config.audio_backend_latency_offset = 0; // set it to zero;
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ break;
+ default:
+ break;
+ }
- int64_t max_dac_delay = conn->latency;
- if (config.audio_backend_silent_lead_in_time >= 0)
- max_dac_delay =
- (int64_t)(config.audio_backend_silent_lead_in_time * conn->input_rate);
+ // readjust first packet time to play
+ frame_to_local_time(conn->first_packet_timestamp +
+ effective_latency, // this will go modulo 2^32
+ &should_be_time, conn);
- max_dac_delay = max_dac_delay / 4;
+ int64_t change_in_should_be_time = (int64_t)(should_be_time - conn->first_packet_time_to_play);
- // debug(1,"max_dac_delay is %" PRIu64 " frames.", max_dac_delay);
+ if (fabs(0.000001*change_in_should_be_time) > 0.001) // the clock drift estimation might be nudging the estimate, and we can ignore this unless if's more than a microsecond
+ debug(2,"Change in estimated first_packet_time: %8.4f milliseconds.", 0.000001*change_in_should_be_time);
- int64_t filler_size = max_dac_delay;
+ conn->first_packet_time_to_play = should_be_time;
- if (local_time_now > conn->first_packet_time_to_play) {
- uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
- lateness = (lateness * 1000000) >> 32; // microseconds
- debug(3, "Gone past starting time by %" PRIu64 " microseconds.", lateness);
- have_sent_prefiller_silence = 1;
- conn->ab_buffering = 0;
+ if (local_time_now > conn->first_packet_time_to_play) {
+ uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
+ debug(2, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness);
+ conn->ab_buffering = 0;
+ } else {
+ // do some calculations
+ int64_t lead_time = conn->first_packet_time_to_play - local_time_now;
+ if ((config.audio_backend_silent_lead_in_time_auto == 1) ||
+ (lead_time <=
+ (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) {
+ // debug(1, "Lead time: %" PRId64 " nanoseconds.", lead_time);
+ int resp = 0;
+ dac_delay = 0;
+ if (have_sent_prefiller_silence != 0)
+ resp = config.output->delay(&dac_delay); // we know the output device must have a delay function
+ if (resp == 0) {
+ int64_t gross_frame_gap =
+ ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) /
+ 1000000000;
+ int64_t exact_frame_gap = gross_frame_gap - dac_delay;
+ int64_t frames_needed_to_maintain_desired_buffer =
+ (int64_t)(config.audio_backend_buffer_desired_length * config.output_rate) -
+ dac_delay;
+ // below, remember that exact_frame_gap and
+ // frames_needed_to_maintain_desired_buffer could both be negative
+ int64_t fs = frames_needed_to_maintain_desired_buffer;
- // we've gone past the time...
- // debug(1,"Run past the exact start time by %llu frames, with time now of %llx, fpttp
- // of %llx and dac_delay of %d and %d packets;
- // flush.",(((tn-conn->first_packet_time_to_play)*config.output_rate)>>32)+dac_delay,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read,
- // ab_write));
+ // if there isn't enough time to have the desired buffer size
+ if (exact_frame_gap <= frames_needed_to_maintain_desired_buffer) {
+ fs = conn->max_frames_per_packet * 2;
+ }
- /*
- if (config.output->flush)
- config.output->flush();
- ab_resync(conn);
- conn->first_packet_timestamp = 0;
- conn->first_packet_time_to_play = 0;
- conn->time_since_play_started = 0;
- */
- } else {
- // do some calculations
- int64_t lead_time = conn->first_packet_time_to_play - local_time_now;
- // an audio_backend_silent_lead_in_time of less than zero means start filling ASAP
- int64_t lead_in_time = -1;
- if (config.audio_backend_silent_lead_in_time >= 0)
- lead_in_time =
- (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)0x100000000);
- // debug(1,"Lead time is %llx at fpttp
- // %llx.",lead_time,conn->first_packet_time_to_play);
- if ((lead_in_time < 0) || (lead_time <= lead_in_time)) {
- // debug(1,"Lead time is %" PRIx64 ", lead-in time is %" PRIx64 " at fpttp
- // %llx.",lead_time,conn->first_packet_time_to_play);
+ // if we are very close to the end of buffering, i.e. within two frame-lengths,
+ // add the remaining silence needed and end buffering
+ if (exact_frame_gap <= conn->max_frames_per_packet * 2) {
+ fs = exact_frame_gap;
+ if (fs > first_frame_early_bias)
+ fs = fs - first_frame_early_bias; // deliberately make the first packet a tiny bit early so that the player may compensate for it at the last minute
+ conn->ab_buffering = 0;
+ }
+ void *silence;
+ if (fs > 0) {
+ silence = malloc(conn->output_bytes_per_frame * fs);
+ if (silence == NULL)
+ debug(1, "Failed to allocate %d byte silence buffer.", fs);
+ else {
+ // generate frames of silence with dither if necessary
+ conn->previous_random_number =
+ generate_zero_frames(silence, fs, config.output_format,
+ conn->enable_dither, conn->previous_random_number);
+ config.output->play(silence, fs);
+ // debug(1, "Sent %" PRId64 " frames of silence", fs);
+ free(silence);
+ have_sent_prefiller_silence = 1;
+ }
+ }
+ } else {
- // debug(1,"Checking");
- if (config.output->delay) {
- // conn->first_packet_time_to_play is definitely later than local_time_now
- int resp = 0;
- dac_delay = 0;
- if (have_sent_prefiller_silence != 0)
- resp = config.output->delay(&dac_delay);
- if (resp == 0) {
- int64_t gross_frame_gap =
- ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) >>
- 32;
- int64_t exact_frame_gap = gross_frame_gap - dac_delay;
- // debug(1,"Exact and gross frame gaps are %" PRId64 " and %" PRId64 " frames,
- // and the dac delay is %ld.", exact_frame_gap, gross_frame_gap, dac_delay);
- if (exact_frame_gap < 0) {
- // we've gone past the time...
- // debug(1,"Run past time.");
+ if (resp == sps_extra_code_output_stalled) {
+ if (conn->unfixable_error_reported == 0) {
+ conn->unfixable_error_reported = 1;
+ if (config.cmd_unfixable) {
+ command_execute(config.cmd_unfixable, "output_device_stalled", 1);
+ } else {
+ warn("an unrecoverable error, \"output_device_stalled\", has been "
+ "detected.",
+ conn->connection_number);
+ }
+ }
+ } else {
+ debug(2, "Unexpected response to getting dac delay: %d.", resp);
+ }
+ }
+ }
+ }
+ } else {
+ // if the output device doesn't have a delay, we simply send the lead-in
+ int64_t lead_time = conn->first_packet_time_to_play - local_time_now; // negative if we are late
+ void *silence;
+ int64_t frame_gap = (lead_time * config.output_rate) / 1000000000;
+ // debug(1,"%d frames needed.",frame_gap);
+ while (frame_gap > 0) {
+ ssize_t fs = config.output_rate / 10;
+ if (fs > frame_gap)
+ fs = frame_gap;
- // this might happen if a big clock adjustment was made at just the wrong
- // time.
-
- debug(1, "Run a bit past the exact start time by %" PRId64
- " frames with a DAC delay of %ld frames.",
- -exact_frame_gap, dac_delay);
- if (config.output->flush)
- config.output->flush();
- ab_resync(conn);
- conn->first_packet_timestamp = 0;
- conn->first_packet_time_to_play = 0;
- } else {
- int64_t fs = filler_size;
- if (fs > (max_dac_delay - dac_delay))
- fs = max_dac_delay - dac_delay;
- if (fs < 0) {
- // this could happen if the dac delay mysteriously grows between samples,
- // which could happen in a transition between having no interpolation and
- // having interpolated buffer numbers.
-
- // this will happen benignly if standby is being prevented, because a
- // thread in the alsa back end will be stuffing frames of silence in there
- // to keep it busy
-
- debug(3,
- "frame size (fs) < 0 with max_dac_delay of %lld and dac_delay of %ld",
- max_dac_delay, dac_delay);
- fs = 0;
- }
- if ((exact_frame_gap <= fs) ||
- (exact_frame_gap <= conn->max_frames_per_packet * 2)) {
- fs = exact_frame_gap;
- // debug(1,"Exact frame gap is %llu; play %d frames of silence. Dac_delay is
- // %d,
- // with %d packets, ab_read is %04x, ab_write is
- // %04x.",exact_frame_gap,fs,dac_delay,seq_diff(ab_read,
- // ab_write),ab_read,ab_write);
- conn->ab_buffering = 0;
- }
- void *silence;
- // if (fs==0)
- // debug(2,"Zero length silence buffer needed with gross_frame_gap of %lld
- // and
- // dac_delay of %lld.",gross_frame_gap,dac_delay);
- // the fs (number of frames of silence to play) can be zero in the DAC doesn't
- // start
- // outputting frames for a while -- it could get loaded up but not start
- // responding
- // for many milliseconds.
- if (fs > 0) {
- silence = malloc(conn->output_bytes_per_frame * fs);
- if (silence == NULL)
- debug(1, "Failed to allocate %d byte silence buffer.", fs);
- else {
-
- conn->previous_random_number = generate_zero_frames(
- silence, fs, config.output_format, conn->enable_dither,
- conn->previous_random_number);
-
- // debug(1,"Frames to start: %llu, DAC delay %d, buffer: %d
- // packets.",exact_frame_gap,dac_delay,seq_diff(conn->ab_read,
- // conn->ab_write, conn->ab_read));
- config.output->play(silence, fs);
- // debug(1,"Sent %" PRId64 " frames of silence",fs);
- free(silence);
- }
- }
- have_sent_prefiller_silence =
- 1; // even if we haven't sent silence because it's zero frames long...
- }
- } else {
- if ((resp == sps_extra_code_output_stalled) &&
- (conn->unfixable_error_reported == 0)) {
- conn->unfixable_error_reported = 1;
- if (config.cmd_unfixable) {
- command_execute(config.cmd_unfixable, "output_device_stalled", 1);
- } else {
- warn(
- "an unrecoverable error, \"output_device_stalled\", has been detected.",
- conn->connection_number);
- }
- }
- }
- } else {
- // no delay function on back end -- just send the prefiller silence
- // debug(1,"Back end has no delay function.");
- // send the appropriate prefiller here...
-
- void *silence;
- if (lead_time != 0) {
- int64_t frame_gap = (lead_time * config.output_rate) >> 32;
- // debug(1,"%d frames needed.",frame_gap);
- while (frame_gap > 0) {
- ssize_t fs = config.output_rate / 10;
- if (fs > frame_gap)
- fs = frame_gap;
-
- silence = malloc(conn->output_bytes_per_frame * fs);
- if (silence == NULL)
- debug(1, "Failed to allocate %d frame silence buffer.", fs);
- else {
- // debug(1, "No delay function -- outputting %d frames of silence.", fs);
- conn->previous_random_number =
- generate_zero_frames(silence, fs, config.output_format,
- conn->enable_dither, conn->previous_random_number);
- config.output->play(silence, fs);
- free(silence);
- }
- frame_gap -= fs;
- }
- }
- have_sent_prefiller_silence = 1;
- conn->ab_buffering = 0;
- }
- }
- }
- }
- if (conn->ab_buffering == 0) {
-/*
- // note the time of the playing of the first frame
- uint64_t reference_timestamp_time; // don't need this...
- get_reference_timestamp_stuff(&conn->play_segment_reference_frame,
- &reference_timestamp_time,
- &conn->play_segment_reference_frame_remote_time, conn);
- conn->play_segment_reference_frame *= conn->output_sample_ratio;
-*/
+ silence = malloc(conn->output_bytes_per_frame * fs);
+ if (silence == NULL)
+ debug(1, "Failed to allocate %d frame silence buffer.", fs);
+ else {
+ // debug(1, "No delay function -- outputting %d frames of silence.", fs);
+ conn->previous_random_number =
+ generate_zero_frames(silence, fs, config.output_format,
+ conn->enable_dither, conn->previous_random_number);
+ config.output->play(silence, fs);
+ free(silence);
+ }
+ frame_gap -= fs;
+ }
+ conn->ab_buffering = 0;
+ }
+ }
#ifdef CONFIG_METADATA
- debug(2, "prsm");
- send_ssnc_metadata('prsm', NULL, 0,
- 0); // "resume", but don't wait if the queue is locked
+ if (conn->ab_buffering == 0) {
+ debug(2, "prsm");
+ send_ssnc_metadata('prsm', NULL, 0,
+ 0); // "resume", but don't wait if the queue is locked
+ }
#endif
}
}
}
- }
// Here, we work out whether to release a packet or wait
- // We release a buffer when the time is right.
+ // We release a packet when the time is right.
// To work out when the time is right, we need to take account of (1) the actual time the packet
// should be released,
@@ -1306,12 +1294,50 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (have_timestamp_timing_information(conn)) { // if we have a reference time
uint64_t time_to_play;
- frame_to_local_time(curframe->given_timestamp + conn->latency +
- (uint32_t)(config.audio_backend_latency_offset * conn->input_rate) -
- (uint32_t)(config.audio_backend_buffer_desired_length *
- conn->input_rate), // this will go modulo 2^32
- &time_to_play,
- conn);
+ uint32_t effective_latency = conn->latency;
+
+ switch (get_and_check_effective_latency(conn, &effective_latency,
+ config.audio_backend_latency_offset -
+ config.audio_backend_buffer_desired_length)) {
+ case -1:
+ // this means that the latency is negative, i.e. the packet must be played before its
+ // time. This is clearly a mistake, and can arise if the combination of a large negative
+ // latency offset and the desired buffer length are greater than the latency chosen by the
+ // player. For example if the latency is 88200 frames (2 seconds), the
+ // audio_backend_latency_offset is -1.9 and the audio_backend_buffer_desired_length is 0.2
+ // the effective latency is -0.1, so the frame must be played 0.1 seconds before it is
+ // time-tagged for, and in reality before it arrives at the player.
+
+ // to deal with the problem, rather than block the packets, we'll just let 'em go...
+
+ //
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Negative latency! A latency of %d frames requested by the player, when combined "
+ "with an audio_backend_latency_offset of %f seconds an "
+ "audio_backend_buffer_desired_length of %f seconds, would make the overall "
+ "latency negative. No latency is used. Synchronisation may fail.",
+ conn->latency, config.audio_backend_latency_offset,
+ config.audio_backend_buffer_desired_length);
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ time_to_play = local_time_now; // pretend the frame should be played now...
+ break;
+ case 1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("Latency too long! An audio_backend_latency_offset of %f seconds combined with an "
+ "audio_backend_buffer_desired_length of %f seconds may exceed the frame "
+ "buffering capacity.",
+ config.audio_backend_latency_offset, config.audio_backend_buffer_desired_length);
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ time_to_play = local_time_now; // pretend the frame should be played now...
+ break;
+ default:
+ frame_to_local_time(curframe->given_timestamp +
+ effective_latency, // this will go modulo 2^32
+ &time_to_play, conn);
+ break;
+ }
if (local_time_now >= time_to_play) {
do_wait = 0;
@@ -1323,22 +1349,24 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (notified_buffer_empty == 0) {
debug(3, "Buffers exhausted.");
notified_buffer_empty = 1;
- reset_input_flow_metrics(conn);
+ // reset_input_flow_metrics(conn); // don't do a full flush parameters reset
+ conn->initial_reference_time = 0;
+ conn->initial_reference_timestamp = 0;
}
do_wait = 1;
}
wait = (conn->ab_buffering || (do_wait != 0) || (!conn->ab_synced));
if (wait) {
- uint64_t time_to_wait_for_wakeup_fp =
- ((uint64_t)1 << 32) / conn->input_rate; // this is time period of one frame
- time_to_wait_for_wakeup_fp *= 2 * 352; // two full 352-frame packets
- time_to_wait_for_wakeup_fp /= 3; // two thirds of a packet time
+ uint64_t time_to_wait_for_wakeup_ns =
+ 1000000000 / conn->input_rate; // this is time period of one frame
+ time_to_wait_for_wakeup_ns *= 2 * 352; // two full 352-frame packets
+ time_to_wait_for_wakeup_ns /= 3; // two thirds of a packet time
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
- uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp;
- uint64_t sec = time_of_wakeup_fp >> 32;
- uint64_t nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ uint64_t time_of_wakeup_ns = local_time_now + time_to_wait_for_wakeup_ns;
+ uint64_t sec = time_of_wakeup_ns / 1000000000;
+ uint64_t nsec = time_of_wakeup_ns % 1000000000;
struct timespec time_of_wakeup;
time_of_wakeup.tv_sec = sec;
@@ -1350,8 +1378,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
debug(3, "pthread_cond_timedwait returned error code %d.", rc);
#endif
#ifdef COMPILE_FOR_OSX
- uint64_t sec = time_to_wait_for_wakeup_fp >> 32;
- uint64_t nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
+ uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
struct timespec time_to_wait;
time_to_wait.tv_sec = sec;
time_to_wait.tv_nsec = nsec;
@@ -1454,7 +1482,7 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, sps_format_t l_outp
int32_t stat_n = 0;
double stat_mean = 0.0;
double stat_M2 = 0.0;
-double longest_soxr_execution_time_us = 0.0;
+double longest_soxr_execution_time = 0.0;
int64_t packets_processed = 0;
int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
@@ -1482,7 +1510,7 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
size_t odone;
- uint64_t soxr_start_time = get_absolute_time_in_fp();
+ uint64_t soxr_start_time = get_absolute_time_in_ns();
soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, // Rates and # of chans.
inptr, length, NULL, // Input.
@@ -1499,15 +1527,14 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
// mean and variance calculations from "online_variance" algorithm at
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
- double soxr_execution_time_us =
- (((get_absolute_time_in_fp() - soxr_start_time) * 1000000) >> 32) * 1.0;
+ double soxr_execution_time = (get_absolute_time_in_ns() - soxr_start_time) * 0.000000001;
// debug(1,"soxr_execution_time_us: %10.1f",soxr_execution_time_us);
- if (soxr_execution_time_us > longest_soxr_execution_time_us)
- longest_soxr_execution_time_us = soxr_execution_time_us;
+ if (soxr_execution_time > longest_soxr_execution_time)
+ longest_soxr_execution_time = soxr_execution_time;
stat_n += 1;
- double stat_delta = soxr_execution_time_us - stat_mean;
+ double stat_delta = soxr_execution_time - stat_mean;
stat_mean += stat_delta / stat_n;
- stat_M2 += stat_delta * (soxr_execution_time_us - stat_mean);
+ stat_M2 += stat_delta * (soxr_execution_time - stat_mean);
int i;
int32_t *ip, *op;
@@ -1553,15 +1580,16 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
}
if (packets_processed % 1250 == 0) {
- debug(3, "soxr_oneshot execution time in microseconds: mean, standard deviation and max "
- "for %" PRId32 " interpolations in the last "
- "1250 packets. %10.1f, %10.1f, %10.1f.",
+ debug(3,
+ "soxr_oneshot execution time in nanoseconds: mean, standard deviation and max "
+ "for %" PRId32 " interpolations in the last "
+ "1250 packets. %10.6f, %10.6f, %10.6f.",
stat_n, stat_mean, stat_n <= 1 ? 0.0 : sqrtf(stat_M2 / (stat_n - 1)),
- longest_soxr_execution_time_us);
+ longest_soxr_execution_time);
stat_n = 0;
stat_mean = 0.0;
stat_M2 = 0.0;
- longest_soxr_execution_time_us = 0.0;
+ longest_soxr_execution_time = 0.0;
}
conn->amountStuffed = tstuff;
@@ -1569,10 +1597,6 @@ int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
}
#endif
-typedef struct stats { // statistics for running averages
- int64_t sync_error, correction, drift;
-} stats_t;
-
void player_thread_initial_cleanup_handler(__attribute__((unused)) void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
debug(3, "Connection %d: player thread main loop exit via player_thread_initial_cleanup_handler.",
@@ -1639,6 +1663,11 @@ void player_thread_cleanup_handler(void *arg) {
free(conn->tbuf);
conn->tbuf = NULL;
}
+
+ if (conn->statistics) {
+ free(conn->statistics);
+ conn->statistics = NULL;
+ }
free_audio_buffers(conn);
if (conn->stream.type == ast_apple_lossless)
terminate_decoders(conn);
@@ -1660,6 +1689,9 @@ void *player_thread_func(void *arg) {
conn->ab_synced = 0;
conn->first_packet_timestamp = 0;
conn->flush_requested = 0;
+ conn->flush_output_flushed = 0; // only send a flush command to the output device once
+ conn->flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to
+ // be used as a null operand, so we'll use it like that too
conn->fix_volume = 0x10000;
if (conn->latency == 0) {
@@ -1727,9 +1759,9 @@ void *player_thread_func(void *arg) {
debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame);
- conn->dac_buffer_queue_minimum_length = (int64_t)(
+ conn->dac_buffer_queue_minimum_length = (uint64_t)(
config.audio_backend_buffer_interpolation_threshold_in_seconds * config.output_rate);
- debug(3, "dac_buffer_queue_minimum_length is %" PRId64 " frames.",
+ debug(3, "dac_buffer_queue_minimum_length is %" PRIu64 " frames.",
conn->dac_buffer_queue_minimum_length);
conn->session_corrections = 0;
@@ -1746,15 +1778,17 @@ void *player_thread_func(void *arg) {
conn->connection_state_to_output = get_requested_connection_state_to_output();
// this is about half a minute
//#define trend_interval 3758
+
+// this is about 8 seconds
#define trend_interval 1003
- stats_t statistics[trend_interval];
int number_of_statistics, oldest_statistic, newest_statistic;
int at_least_one_frame_seen = 0;
+ int at_least_one_frame_seen_this_session = 0;
int64_t tsum_of_sync_errors, tsum_of_corrections, tsum_of_insertions_and_deletions,
tsum_of_drifts;
int64_t previous_sync_error = 0, previous_correction = 0;
- int64_t minimum_dac_queue_size = INT64_MAX;
+ uint64_t minimum_dac_queue_size = UINT64_MAX;
int32_t minimum_buffer_occupancy = INT32_MAX;
int32_t maximum_buffer_occupancy = INT32_MIN;
@@ -1769,7 +1803,7 @@ void *player_thread_func(void *arg) {
conn->buffer_occupancy = 0;
int play_samples = 0;
- int64_t current_delay;
+ uint64_t current_delay;
int play_number = 0;
conn->play_number_after_flush = 0;
// int last_timestamp = 0; // for debugging only
@@ -1842,20 +1876,17 @@ void *player_thread_func(void *arg) {
// we need an intermediate "transition" buffer
- // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) {
- // debug(1,"Define tbuf of length
- // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change));
- conn->tbuf =
- malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio +
- conn->max_frame_size_change));
+ conn->tbuf = malloc(
+ sizeof(int32_t) * 2 *
+ (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change));
if (conn->tbuf == NULL)
die("Failed to allocate memory for the transition buffer.");
// initialise this, because soxr stuffing might be chosen later
- conn->sbuf =
- malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio +
- conn->max_frame_size_change));
+ conn->sbuf = malloc(
+ sizeof(int32_t) * 2 *
+ (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change));
if (conn->sbuf == NULL)
die("Failed to allocate memory for the sbuf buffer.");
@@ -1868,11 +1899,13 @@ void *player_thread_func(void *arg) {
die("Failed to allocate memory for an output buffer.");
conn->first_packet_timestamp = 0;
conn->missing_packets = conn->late_packets = conn->too_late_packets = conn->resend_requests = 0;
- conn->flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to
- // be used as a null operand, so we'll use it like that too
int sync_error_out_of_bounds =
0; // number of times in a row that there's been a serious sync error
+ conn->statistics = malloc(sizeof(stats_t)*trend_interval);
+ if (conn->statistics == NULL)
+ die("Failed to allocate a statistics buffer");
+
conn->framesProcessedInThisEpoch = 0;
conn->framesGeneratedInThisEpoch = 0;
conn->correctionsRequestedInThisEpoch = 0;
@@ -1913,8 +1946,7 @@ void *player_thread_func(void *arg) {
"source clock drift sample count");
}
} else {
- inform("sync error in milliseconds, "
- "total packets, "
+ inform("total packets, "
"missing packets, "
"late packets, "
"too late packets, "
@@ -1965,10 +1997,12 @@ void *player_thread_func(void *arg) {
// debug(3, "Play frame %d.", play_number);
conn->play_number_after_flush++;
if (inframe->given_timestamp == 0) {
- debug(1, "Player has supplied a silent frame, (possibly frame %u) for play number %d, status 0x%X after %u resend requests.",
- SUCCESSOR(conn->last_seqno_read), play_number, inframe->status, inframe->resend_request_number);
- conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
- 0xffff); // manage the packet out of sequence minder
+ debug(1,
+ "Player has supplied a silent frame, (possibly frame %u) for play number %d, "
+ "status 0x%X after %u resend requests.",
+ SUCCESSOR(conn->last_seqno_read), play_number, inframe->status,
+ inframe->resend_request_number);
+ conn->last_seqno_read = SUCCESSOR(conn->last_seqno_read); // manage the packet out of sequence minder
void *silence = malloc(conn->output_bytes_per_frame * conn->max_frames_per_packet *
conn->output_sample_ratio);
@@ -2078,7 +2112,8 @@ void *player_thread_func(void *arg) {
die("Shairport Sync only supports 16 bit input");
}
- inbuflength *= conn->output_sample_ratio;
+
+ at_least_one_frame_seen = 1;
// We have a frame of data. We need to see if we want to add or remove a frame from it to
// keep in sync.
@@ -2087,47 +2122,27 @@ void *player_thread_func(void *arg) {
// If it's late, we remove an audio frame from this frame to bring a subsequent frame
// forward in time
- at_least_one_frame_seen = 1;
-
// now, go back as far as the total latency less, say, 100 ms, and check the presence of
// frames from then onwards
+ inbuflength *= conn->output_sample_ratio;
+ /*
uint32_t reference_timestamp;
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
&remote_reference_timestamp_time, conn); // types okay
- int64_t rt, nt;
- rt = reference_timestamp; // uint32_t to int64_t
+ */
+
+ // nt is the rtp timestamp of the first frame of the current packet of
+ // frames from the source.
+ // multiply it by the output frame ratio to get, effectively, the rtp timestamp
+ // of the first frame of the corresponding packet of output frames.
+
+ uint64_t nt;
nt = inframe->given_timestamp; // uint32_t to int64_t
- rt = rt * conn->output_sample_ratio;
nt = nt * conn->output_sample_ratio;
- uint64_t local_time_now = get_absolute_time_in_fp(); // types okay
- // struct timespec tn;
- // clock_gettime(CLOCK_MONOTONIC,&tn);
- // uint64_t
- // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000;
-
- int64_t td = 0; // td is the time difference between the reference timestamp time and the
- // present time. Only used to calculate td_in_frames
- int64_t td_in_frames = 0; // td_in_frames is the number of frames between between the
- // reference timestamp time and the present time
-
- if (local_time_now >= reference_timestamp_time) {
- td = local_time_now - reference_timestamp_time; // this is the positive value.
- // Conversion is positive uint64_t to
- // int64_t, thus okay
- td_in_frames = (td * config.output_rate) >> 32;
- } else {
- td = reference_timestamp_time - local_time_now; // this is the absolute value, which
- // should be negated. Conversion is
- // positive uint64_t to int64_t, thus
- // okay.
- td_in_frames = (td * config.output_rate) >>
- 32; // use the absolute td value for the present. Types okay
- td_in_frames = -td_in_frames;
- td = -td; // should be okay, as the range of values should be very small w.r.t 64 bits
- }
+ uint64_t local_time_now = get_absolute_time_in_ns(); // types okay
// This is the timing error for the next audio frame in the DAC, if applicable
int64_t sync_error = 0;
@@ -2143,15 +2158,16 @@ void *player_thread_func(void *arg) {
SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay.
if (inframe->sequence_number !=
conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay
- debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u "
- "and ab_write: %u.",
+ debug(2,
+ "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u "
+ "and ab_write: %u.",
conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write);
conn->last_seqno_read = inframe->sequence_number; // reset warning...
}
}
conn->buffer_occupancy =
- seq_diff(conn->ab_read, conn->ab_write, conn->ab_read); // int32_t from int32
+ seq_diff(conn->ab_write, conn->ab_read); // int32_t from int16_t
if (conn->buffer_occupancy < minimum_buffer_occupancy)
minimum_buffer_occupancy = conn->buffer_occupancy;
@@ -2159,21 +2175,25 @@ void *player_thread_func(void *arg) {
if (conn->buffer_occupancy > maximum_buffer_occupancy)
maximum_buffer_occupancy = conn->buffer_occupancy;
+
+
+
// here, we want to check (a) if we are meant to do synchronisation,
// (b) if we have a delay procedure, (c) if we can get the delay.
// If any of these are false, we don't do any synchronisation stuff
int resp = -1; // use this as a flag -- if negative, we can't rely on a real known delay
- current_delay = -1; // use this as a failure flag
if (config.output->delay) {
long l_delay;
resp = config.output->delay(&l_delay);
if (resp == 0) { // no error
current_delay = l_delay;
- if (current_delay < 0) {
- debug(2, "Underrun of %lld frames reported, but ignored.", current_delay);
+ if (l_delay >= 0)
+ current_delay = l_delay;
+ else {
+ debug(2, "Underrun of %ld frames reported, but ignored.", l_delay);
current_delay =
0; // could get a negative value if there was underrun, but ignore it.
}
@@ -2207,37 +2227,62 @@ void *player_thread_func(void *arg) {
uint32_t should_be_frame_32;
local_time_to_frame(local_time_now, &should_be_frame_32, conn);
- int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio;
+ // int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio;
- // int64_t absolute_difference_in_frames = td_in_frames + rt - should_be_frame;
- // if (absolute_difference_in_frames < 0)
- // absolute_difference_in_frames = -absolute_difference_in_frames;
+ int64_t delay = int64_mod_difference(should_be_frame_32 * conn->output_sample_ratio, nt - current_delay, UINT32_MAX * conn->output_sample_ratio);
- // if (absolute_difference_in_frames > 10 * conn->output_sample_ratio)
- // debug(1, "Difference between old and new frame number is %" PRId64 " frames.",
- // td_in_frames + rt - should_be_frame);
- // this is the actual delay, including the latency we actually want, which will
- // fluctuate a good bit about a potentially rising or falling trend.
+ //int64_t delay = should_be_frame - (nt - current_delay); // all int64_t
- // int64_t delay = td_in_frames + rt - (nt - current_delay); // all int64_t
- // cut over to the new calculation method
- int64_t delay = should_be_frame - (nt - current_delay); // all int64_t
+ // the original frame numbers are unsigned 32-bit integers that roll over modulo 2^32
+ // hence these delay figures will be unsigned numbers that roll over modulo 2^32 * conn->output_sample_ratio;
+ // therefore, calculating the delay must be done in the light of possible rollover
- // td_in_frames + rt is the frame number that should be output at local_time_now.
- // This is the timing error for the next audio frame in the DAC.
-
- // if positive, it means that the packet will be late -- the delay is longer than
- // requested
- // if negative, the packet will be early -- the delay is less than expected.
sync_error =
delay - ((int64_t)conn->latency * conn->output_sample_ratio +
(int64_t)(config.audio_backend_latency_offset *
config.output_rate)); // int64_t from int64_t - int32_t, so okay
- // debug(1,"%" PRId64 "",sync_error,inbuflength);
+ if (at_least_one_frame_seen_this_session == 0) {
+ at_least_one_frame_seen_this_session = 1;
+ // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", sync_error);
+
+ // this is a sneaky attempt to make a final adjustment to the timing of the first packet
+
+ // the very first packet generally has a first_frame_early_bias subtracted from its timing
+ // to make it more likely that it will be early than late,
+ // making it possible to compensate for it be adding a few frames of silence.
+
+ // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", sync_error);
+
+ // remove the bias when reporting the error to make it the true error
+
+ debug(2,"first frame sync error (positive --> late): %" PRId64 " frames, %.3f mS at %d frames per second output.", sync_error+first_frame_early_bias, (1000.0*(sync_error+first_frame_early_bias))/config.output_rate, config.output_rate);
+
+ // if the packet is early, add the frames needed to put it in sync.
+ if (sync_error < 0) {
+ size_t final_adjustment_length_sized = -sync_error;
+ char *final_adjustment_silence = malloc(conn->output_bytes_per_frame * final_adjustment_length_sized);
+ if (final_adjustment_silence) {
+
+ conn->previous_random_number =
+ generate_zero_frames(final_adjustment_silence, final_adjustment_length_sized, config.output_format,
+ conn->enable_dither, conn->previous_random_number);
+ int final_adjustment = -sync_error;
+ final_adjustment = final_adjustment - first_frame_early_bias;
+ debug(2, "final sync adjustment: %" PRId64 " silent frames added with a bias of %" PRId64 " frames.", -sync_error, first_frame_early_bias);
+ config.output->play(final_adjustment_silence, final_adjustment_length_sized);
+ free(final_adjustment_silence);
+ } else {
+ warn("Failed to allocate memory for a final_adjustment_silence buffer of %d frames for a "
+ "sync error of %d frames.",
+ final_adjustment_length_sized, sync_error);
+ }
+ sync_error = 0; // say the error was fixed!
+ }
+ }
// not too sure if abs() is implemented for int64_t, so we'll do it manually
int64_t abs_sync_error = sync_error;
if (abs_sync_error < 0)
@@ -2346,8 +2391,9 @@ void *player_thread_func(void *arg) {
if ((local_time_now) && (conn->first_packet_time_to_play) &&
(local_time_now >= conn->first_packet_time_to_play)) {
- int64_t tp = (local_time_now - conn->first_packet_time_to_play) >>
- 32; // seconds int64_t from uint64_t which is always positive, so ok
+ int64_t tp =
+ (local_time_now - conn->first_packet_time_to_play) /
+ 1000000000; // seconds int64_t from uint64_t which is always positive, so ok
if (tp < 5)
amount_to_stuff = 0; // wait at least five seconds
@@ -2365,29 +2411,30 @@ void *player_thread_func(void *arg) {
amount_to_stuff = 0; // no stuffing if it's been disabled
// Apply DSP here
-
- // check the state of loudness and convolution flags here and don't change them for the frame
-
+
+ // check the state of loudness and convolution flags here and don't change them for
+ // the frame
+
int do_loudness = config.loudness;
-
#ifdef CONFIG_CONVOLUTION
int do_convolution = 0;
if ((config.convolution) && (config.convolver_valid))
do_convolution = 1;
- // we will apply the convolution gain if convolution is enabled, even if there is no valid convolution happening
-
+ // we will apply the convolution gain if convolution is enabled, even if there is no
+ // valid convolution happening
+
int convolution_is_enabled = 0;
if (config.convolution)
convolution_is_enabled = 1;
#endif
-
+
if (do_loudness
#ifdef CONFIG_CONVOLUTION
|| convolution_is_enabled
#endif
- ) {
+ ) {
int32_t *tbuf32 = (int32_t *)conn->tbuf;
float fbuf_l[inbuflength];
float fbuf_r[inbuflength];
@@ -2442,7 +2489,7 @@ void *player_thread_func(void *arg) {
((config.packet_stuffing == ST_auto) &&
(config.soxr_delay_index >
config.soxr_delay_threshold)) // if the CPU is deemed too slow
- ) {
+ ) {
#endif
play_samples =
stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format,
@@ -2509,8 +2556,36 @@ void *player_thread_func(void *arg) {
*/
}
} else {
- // if there is no delay procedure, or it's not working or not allowed, there can be no
- // synchronising
+
+ // if there is no delay procedure, then we should be sending the packet
+ // to the output at the time determined by
+ // the packet's time to play + requested latency + requested offset.
+/*
+ // This is just for checking during development
+
+ uint32_t should_be_frame_32;
+ local_time_to_frame(local_time_now, &should_be_frame_32, conn);
+ // int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio;
+
+ int32_t ilatency = (int32_t)((config.audio_backend_latency_offset - config.audio_backend_buffer_desired_length) * conn->input_rate) + conn->latency;
+ if (ilatency < 0)
+ debug(1,"incorrect latency %d.", ilatency);
+
+ int32_t idelay = (int32_t)(should_be_frame_32 - inframe->given_timestamp);
+
+ idelay = idelay - ilatency;
+
+ debug(2,"delay is %d input frames.", idelay);
+*/
+
+ // if this is the first frame, see if it's close to when it's supposed to be
+ // release, which will be its time plus latency and any offset_time
+ if (at_least_one_frame_seen_this_session == 0) {
+ at_least_one_frame_seen_this_session = 1;
+
+
+ }
+
play_samples =
stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format,
conn->outbuf, 0, conn->enable_dither, conn);
@@ -2533,7 +2608,7 @@ void *player_thread_func(void *arg) {
// update the watchdog
if ((config.dont_check_timeout == 0) && (config.timeout != 0)) {
- uint64_t time_now = get_absolute_time_in_fp();
+ uint64_t time_now = get_absolute_time_in_ns();
debug_mutex_lock(&conn->watchdog_mutex, 1000, 0);
conn->watchdog_bark_time = time_now;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
@@ -2548,34 +2623,34 @@ void *player_thread_func(void *arg) {
// valid samples and the number of times sync wasn't checked due to non availability of a
// delay figure.
// for the present, stats are only updated when sync has been checked
- if (sync_error != -1) {
+ if (config.output->delay != NULL) {
if (number_of_statistics == trend_interval) {
// here we remove the oldest statistical data and take it from the summaries as well
- tsum_of_sync_errors -= statistics[oldest_statistic].sync_error;
- tsum_of_drifts -= statistics[oldest_statistic].drift;
- if (statistics[oldest_statistic].correction > 0)
- tsum_of_insertions_and_deletions -= statistics[oldest_statistic].correction;
+ tsum_of_sync_errors -= conn->statistics[oldest_statistic].sync_error;
+ tsum_of_drifts -= conn->statistics[oldest_statistic].drift;
+ if (conn->statistics[oldest_statistic].correction > 0)
+ tsum_of_insertions_and_deletions -= conn->statistics[oldest_statistic].correction;
else
- tsum_of_insertions_and_deletions += statistics[oldest_statistic].correction;
- tsum_of_corrections -= statistics[oldest_statistic].correction;
+ tsum_of_insertions_and_deletions += conn->statistics[oldest_statistic].correction;
+ tsum_of_corrections -= conn->statistics[oldest_statistic].correction;
oldest_statistic = (oldest_statistic + 1) % trend_interval;
number_of_statistics--;
}
- statistics[newest_statistic].sync_error = sync_error;
- statistics[newest_statistic].correction = conn->amountStuffed;
+ conn->statistics[newest_statistic].sync_error = sync_error;
+ conn->statistics[newest_statistic].correction = conn->amountStuffed;
if (number_of_statistics == 0)
- statistics[newest_statistic].drift = 0;
+ conn->statistics[newest_statistic].drift = 0;
else
- statistics[newest_statistic].drift =
+ conn->statistics[newest_statistic].drift =
sync_error - previous_sync_error - previous_correction;
previous_sync_error = sync_error;
previous_correction = conn->amountStuffed;
tsum_of_sync_errors += sync_error;
- tsum_of_drifts += statistics[newest_statistic].drift;
+ tsum_of_drifts += conn->statistics[newest_statistic].drift;
if (conn->amountStuffed > 0) {
tsum_of_insertions_and_deletions += conn->amountStuffed;
} else {
@@ -2601,13 +2676,8 @@ void *player_thread_func(void *arg) {
frames_received = conn->frames_inward_frames_received_at_measurement_time -
conn->frames_inward_frames_received_at_measurement_start_time;
conn->input_frame_rate =
- (1.0 * frames_received) /
+ (1.0E9 * frames_received) /
elapsed_reception_time; // an IEEE double calculation with two 64-bit integers
- conn->input_frame_rate =
- conn->input_frame_rate * (uint64_t)0x100000000; // this should just change the
- // [binary] exponent in the IEEE FP
- // representation; the mantissa
- // should be unaffected.
} else {
conn->input_frame_rate = 0.0;
}
@@ -2620,12 +2690,8 @@ void *player_thread_func(void *arg) {
conn->frame_rate_status = 0;
if (conn->frame_rate_status) {
conn->frame_rate =
- (1.0 * frames_played) /
+ (1.0E9 * frames_played) /
elapsed_play_time; // an IEEE double calculation with two 64-bit integers
- conn->frame_rate =
- conn->frame_rate * (uint64_t)0x100000000; // this should just change the [binary]
- // exponent in the IEEE FP representation;
- // the mantissa should be unaffected.
} else {
conn->frame_rate = 0.0;
}
@@ -2644,42 +2710,41 @@ void *player_thread_func(void *arg) {
if ((config.output->delay)) {
if (config.no_sync == 0) {
- inform("%*.2f," /* Sync error in milliseconds */
- "%*.1f," /* net correction in ppm */
- "%*.1f," /* corrections in ppm */
- "%*d," /* total packets */
- "%*" PRIu64 "," /* missing packets */
- "%*" PRIu64 "," /* late packets */
- "%*" PRIu64 "," /* too late packets */
- "%*" PRIu64 "," /* resend requests */
- "%*" PRId64 "," /* min DAC queue size */
- "%*" PRId32 "," /* min buffer occupancy */
- "%*" PRId32 "," /* max buffer occupancy */
- "%*.2f," /* source nominal frame rate */
- "%*.2f," /* source actual (average) frame rate */
- "%*.2f," /* output frame rate */
- "%*.2f," /* source clock drift */
- "%*d," /* source clock drift sample count */
- "%*.2f", /* rough calculated correction in ppm */
- 10,
- 1000 * moving_average_sync_error / config.output_rate, 10,
- moving_average_correction * 1000000 / (352 * conn->output_sample_ratio),
- 10, moving_average_insertions_plus_deletions * 1000000 /
- (352 * conn->output_sample_ratio),
- 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
- conn->too_late_packets, 7, conn->resend_requests, 7,
- minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5,
- maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11,
- conn->input_frame_rate, 11, conn->frame_rate, 10,
- (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6,
- conn->local_to_remote_time_gradient_sample_count, 10,
- (conn->frame_rate > 0.0)
- ? ((conn->frame_rate -
- conn->remote_frame_rate * conn->output_sample_ratio *
- conn->local_to_remote_time_gradient) *
- 1000000) /
- conn->frame_rate
- : 0.0);
+ inform(
+ "%*.2f," /* Sync error in milliseconds */
+ "%*.1f," /* net correction in ppm */
+ "%*.1f," /* corrections in ppm */
+ "%*d," /* total packets */
+ "%*" PRIu64 "," /* missing packets */
+ "%*" PRIu64 "," /* late packets */
+ "%*" PRIu64 "," /* too late packets */
+ "%*" PRIu64 "," /* resend requests */
+ "%*" PRIu64 "," /* min DAC queue size */
+ "%*" PRId32 "," /* min buffer occupancy */
+ "%*" PRId32 "," /* max buffer occupancy */
+ "%*.2f," /* source nominal frame rate */
+ "%*.2f," /* source actual (average) frame rate */
+ "%*.2f," /* output frame rate */
+ "%*.2f," /* source clock drift */
+ "%*d," /* source clock drift sample count */
+ "%*.2f", /* rough calculated correction in ppm */
+ 10, 1000 * moving_average_sync_error / config.output_rate, 10,
+ moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10,
+ moving_average_insertions_plus_deletions * 1000000 /
+ (352 * conn->output_sample_ratio),
+ 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
+ conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size,
+ 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11,
+ conn->remote_frame_rate, 11, conn->input_frame_rate, 11, conn->frame_rate, 10,
+ (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6,
+ conn->local_to_remote_time_gradient_sample_count, 10,
+ (conn->frame_rate > 0.0)
+ ? ((conn->frame_rate - conn->remote_frame_rate *
+ conn->output_sample_ratio *
+ conn->local_to_remote_time_gradient) *
+ 1000000) /
+ conn->frame_rate
+ : 0.0);
} else {
inform("%*.2f," /* Sync error in milliseconds */
"%*d," /* total packets */
@@ -2687,25 +2752,23 @@ void *player_thread_func(void *arg) {
"%*" PRIu64 "," /* late packets */
"%*" PRIu64 "," /* too late packets */
"%*" PRIu64 "," /* resend requests */
- "%*" PRId64 "," /* min DAC queue size */
+ "%*" PRIu64 "," /* min DAC queue size */
"%*" PRId32 "," /* min buffer occupancy */
"%*" PRId32 "," /* max buffer occupancy */
"%*.2f," /* source nominal frame rate */
"%*.2f," /* source actual (average) frame rate */
"%*.2f," /* source clock drift */
"%*d", /* source clock drift sample count */
- 10,
- 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7,
- conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7,
- conn->resend_requests, 7, minimum_dac_queue_size, 5,
+ 10, 1000 * moving_average_sync_error / config.output_rate, 12, play_number,
+ 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets,
+ 7, conn->resend_requests, 7, minimum_dac_queue_size, 5,
minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11,
conn->remote_frame_rate, 11, conn->input_frame_rate, 10,
(conn->local_to_remote_time_gradient - 1.0) * 1000000, 6,
conn->local_to_remote_time_gradient_sample_count);
}
} else {
- inform("%*.2f," /* Sync error in milliseconds */
- "%*d," /* total packets */
+ inform("%*d," /* total packets */
"%*" PRIu64 "," /* missing packets */
"%*" PRIu64 "," /* late packets */
"%*" PRIu64 "," /* too late packets */
@@ -2716,10 +2779,9 @@ void *player_thread_func(void *arg) {
"%*.2f," /* source actual (average) frame rate */
"%*.2f," /* source clock drift */
"%*d", /* source clock drift sample count */
- 10,
- 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7,
- conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7,
- conn->resend_requests, 5, minimum_buffer_occupancy, 5,
+ 12, play_number,
+ 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets,
+ 7, conn->resend_requests, 5, minimum_buffer_occupancy, 5,
maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11,
conn->input_frame_rate, 10,
(conn->local_to_remote_time_gradient - 1.0) * 1000000, 6,
@@ -2729,7 +2791,7 @@ void *player_thread_func(void *arg) {
inform("No frames received in the last sampling interval.");
}
}
- minimum_dac_queue_size = INT64_MAX; // hack reset
+ minimum_dac_queue_size = UINT64_MAX; // hack reset
maximum_buffer_occupancy = INT32_MIN; // can't be less than this
minimum_buffer_occupancy = INT32_MAX; // can't be more than this
at_least_one_frame_seen = 0;
@@ -2758,7 +2820,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
int32_t hw_max_db = 0, hw_min_db = 0; // zeroed to quieten an incorrect uninitialised warning
int32_t sw_max_db = 0, sw_min_db = -9630;
-
+
if (config.output->parameters) {
volume_mode = vol_hw_only;
audio_parameters audio_information;
@@ -2819,13 +2881,15 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
if (airplay_volume == -144.0) {
if ((config.output->mute) && (config.output->mute(1) == 0))
- debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
- "hardware mute is enabled.",
+ debug(2,
+ "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
+ "hardware mute is enabled.",
volume_mode, airplay_volume);
else {
conn->software_mute_enabled = 1;
- debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
- "software mute is enabled.",
+ debug(2,
+ "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
+ "software mute is enabled.",
volume_mode, airplay_volume);
}
@@ -2928,51 +2992,47 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
if (config.logOutputLevel) {
inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
}
-
-#ifdef CONFIG_METADATA
- // here, send the 'pvol' metadata message when the airplay volume information
- // is being used by shairport sync to control the output volume
- char *dv = malloc(128); // will be freed in the metadata thread
- if (dv) {
- memset(dv, 0, 128);
- if (volume_mode == vol_both) {
- // normalise the maximum output to the hardware device's max output
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, (scaled_attenuation - max_db + hw_max_db) / 100.0,
- (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
- } else {
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
- min_db / 100.0, max_db / 100.0);
- }
- send_ssnc_metadata('pvol', dv, strlen(dv), 1);
- }
-#endif
+#ifdef CONFIG_METADATA
+ // here, send the 'pvol' metadata message when the airplay volume information
+ // is being used by shairport sync to control the output volume
+ char dv[128];
+ memset(dv, 0, 128);
+ if (volume_mode == vol_both) {
+ // normalise the maximum output to the hardware device's max output
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume,
+ (scaled_attenuation - max_db + hw_max_db) / 100.0,
+ (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
+ } else {
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
+ min_db / 100.0, max_db / 100.0);
+ }
+ send_ssnc_metadata('pvol', dv, strlen(dv), 1);
+#endif
if (config.output->mute)
config.output->mute(0);
conn->software_mute_enabled = 0;
- debug(2, "player_volume_without_notification: volume mode is %d, airplay volume is %f, "
- "software_attenuation: %f, hardware_attenuation: %f, muting "
- "is disabled.",
+ debug(2,
+ "player_volume_without_notification: volume mode is %d, airplay volume is %f, "
+ "software_attenuation: %f, hardware_attenuation: %f, muting "
+ "is disabled.",
volume_mode, airplay_volume, software_attenuation, hardware_attenuation);
}
}
#ifdef CONFIG_METADATA
- else {
- // here, send the 'pvol' metadata message when the airplay volume information
- // is being used by shairport sync to control the output volume
- char *dv = malloc(128); // will be freed in the metadata thread
- if (dv) {
- memset(dv, 0, 128);
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
- send_ssnc_metadata('pvol', dv, strlen(dv), 1);
- }
+ else {
+ // here, send the 'pvol' metadata message when the airplay volume information
+ // is being used by shairport sync to control the output volume
+ char dv[128];
+ memset(dv, 0, 128);
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
+ send_ssnc_metadata('pvol', dv, strlen(dv), 1);
}
#endif
-
// here, store the volume for possible use in the future
config.airplay_volume = airplay_volume;
debug_mutex_unlock(&conn->volume_control_mutex, 3);
@@ -2985,27 +3045,22 @@ void player_volume(double airplay_volume, rtsp_conn_info *conn) {
void do_flush(uint32_t timestamp, rtsp_conn_info *conn) {
- debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp);
+ debug(2, "do_flush: flush to %u.", timestamp);
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
conn->flush_requested = 1;
- // if (timestamp!=0)
- conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this
- // conn->play_segment_reference_frame = 0;
+ conn->flush_rtp_timestamp = timestamp; // flush all packets up to, but not including, this one.
reset_input_flow_metrics(conn);
debug_mutex_unlock(&conn->flush_mutex, 3);
- debug(3, "Flush request made.");
}
void player_flush(uint32_t timestamp, rtsp_conn_info *conn) {
debug(3, "player_flush");
do_flush(timestamp, conn);
#ifdef CONFIG_METADATA
- // only send a flush metadata message if the first packet has been seen -- it's a bogus message
- // otherwise
- if (conn->first_packet_timestamp) {
- debug(2, "pfls");
- send_ssnc_metadata('pfls', NULL, 0, 1); // contains cancellation points
- }
+ debug(2, "pfls");
+ char numbuf[32];
+ snprintf(numbuf, sizeof(numbuf),"%u",timestamp);
+ send_ssnc_metadata('pfls', numbuf, strlen(numbuf), 1); // contains cancellation points
#endif
}
@@ -3027,17 +3082,10 @@ int player_play(rtsp_conn_info *conn) {
if (pt == NULL)
die("Couldn't allocate space for pthread_t");
conn->player_thread = pt;
- size_t size = (PTHREAD_STACK_MIN + 256 * 1024);
- pthread_attr_t tattr;
- pthread_attr_init(&tattr);
- int rc = pthread_attr_setstacksize(&tattr, size);
- if (rc)
- debug(1, "Error setting stack size for player_thread: %s", strerror(errno));
- // finished initialising.
- rc = pthread_create(pt, &tattr, player_thread_func, (void *)conn);
+ int rc = pthread_create(pt, NULL, player_thread_func, (void *)conn);
if (rc)
debug(1, "Error creating player_thread: %s", strerror(errno));
- pthread_attr_destroy(&tattr);
+
#ifdef CONFIG_METADATA
debug(2, "pbeg");
send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points
diff --git a/player.h b/player.h
index b6cae8a9..f6b86cd4 100644
--- a/player.h
+++ b/player.h
@@ -24,10 +24,10 @@
#include "alac.h"
#include "audio.h"
-#define time_ping_history 128 // at 1 per three seconds, approximately six minutes of records
+#define time_ping_history_power_of_two 7
+#define time_ping_history (1 << time_ping_history_power_of_two) // 2^7 is 128. At 1 per three seconds, approximately six minutes of records
typedef struct time_ping_record {
- uint64_t local_to_remote_difference;
uint64_t dispersion;
uint64_t local_time;
uint64_t remote_time;
@@ -43,12 +43,17 @@ typedef struct audio_buffer_entry { // decoded audio packets
uint16_t resend_request_number;
signed short *data;
seq_t sequence_number;
- uint64_t initialisation_time; // the time the packet was added or the time it was noticed the packet was missing
- uint64_t resend_time; // time of last resend request or zero
- uint32_t given_timestamp; // for debugging and checking
- int length; // the length of the decoded data
+ uint64_t initialisation_time; // the time the packet was added or the time it was noticed the
+ // packet was missing
+ uint64_t resend_time; // time of last resend request or zero
+ uint32_t given_timestamp; // for debugging and checking
+ int length; // the length of the decoded data
} abuf_t;
+typedef struct stats { // statistics for running averages
+ int64_t sync_error, correction, drift;
+} stats_t;
+
// default buffer size
// This needs to be a power of 2 because of the way BUFIDX(seqno) works.
// 512 is the minimum for normal operation -- it gives 512*352/44100 or just over 4 seconds of
@@ -87,6 +92,7 @@ typedef struct {
uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero
// otherwise
int software_mute_enabled; // if we don't have a real mute that we can use
+ int unachievable_audio_backend_latency_offset_notified; // set when a latency warning is issued
int fd;
int authorized; // set if a password is required and has been supplied
@@ -108,6 +114,10 @@ typedef struct {
int32_t *sbuf;
char *outbuf;
+ // for generating running statistics...
+
+ stats_t *statistics;
+
// for holding the output rate information until printed out at the end of a session
double frame_rate;
int frame_rate_status;
@@ -151,6 +161,7 @@ typedef struct {
int ab_buffering, ab_synced;
int64_t first_packet_timestamp;
int flush_requested;
+ int flush_output_flushed; // true if the output device has been flushed.
uint32_t flush_rtp_timestamp;
uint64_t time_of_last_audio_packet;
seq_t ab_read, ab_write;
@@ -177,7 +188,7 @@ typedef struct {
// RTP stuff
// only one RTP session can be active at a time.
int rtp_running;
- uint64_t rtp_time_of_last_resend_request_error_fp;
+ uint64_t rtp_time_of_last_resend_request_error_ns;
char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it
@@ -256,7 +267,7 @@ typedef struct {
void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled.
int enable_dither; // needed for filling silences before play actually starts
- int64_t dac_buffer_queue_minimum_length;
+ uint64_t dac_buffer_queue_minimum_length;
} rtsp_conn_info;
uint32_t modulo_32_offset(uint32_t from, uint32_t to);
diff --git a/rtp.c b/rtp.c
index 6a90a5cd..e56c5454 100644
--- a/rtp.c
+++ b/rtp.c
@@ -45,11 +45,19 @@
#include
#include
+struct Nvll {
+ char* name;
+ double value;
+ struct Nvll *next;
+};
+
+typedef struct Nvll nvll;
+
uint64_t local_to_remote_time_jitter;
uint64_t local_to_remote_time_jitter_count;
void rtp_initialise(rtsp_conn_info *conn) {
- conn->rtp_time_of_last_resend_request_error_fp = 0;
+ conn->rtp_time_of_last_resend_request_error_ns = 0;
conn->rtp_running = 0;
// initialise the timer mutex
int rc = pthread_mutex_init(&conn->reference_time_mutex, NULL);
@@ -69,32 +77,20 @@ uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) {
// this is an attempt to compensate for clock drift since the last time ping that was used
// so, if we have a non-zero clock drift, we will calculate the drift there would
// be from the time of the last time ping
- uint64_t local_time_now_fp = get_absolute_time_in_fp();
uint64_t time_since_last_local_to_remote_time_difference_measurement =
- local_time_now_fp - conn->local_to_remote_time_difference_measurement_time;
+ get_absolute_time_in_ns() - conn->local_to_remote_time_difference_measurement_time;
- uint64_t remote_time_since_last_local_to_remote_time_difference_measurement =
- (uint64_t)(conn->local_to_remote_time_gradient *
- time_since_last_local_to_remote_time_difference_measurement);
-
- double drift;
- if (remote_time_since_last_local_to_remote_time_difference_measurement >=
- time_since_last_local_to_remote_time_difference_measurement)
- drift = (1.0 * (remote_time_since_last_local_to_remote_time_difference_measurement -
- time_since_last_local_to_remote_time_difference_measurement)) /
- (uint64_t)0x100000000;
- else
- drift = -((1.0 * (time_since_last_local_to_remote_time_difference_measurement -
- remote_time_since_last_local_to_remote_time_difference_measurement)) /
- (uint64_t)0x100000000);
-
- // double interval_ms =
- // 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32);
- // debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f
- // milliseconds with drift of %.2f
- // ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000);
- // return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000));
- return conn->local_to_remote_time_difference + (uint64_t)(drift * (uint64_t)0x100000000);
+ uint64_t result = conn->local_to_remote_time_difference;
+ if (conn->local_to_remote_time_gradient >= 1.0) {
+ result = conn->local_to_remote_time_difference +
+ (uint64_t)((conn->local_to_remote_time_gradient - 1.0) *
+ time_since_last_local_to_remote_time_difference_measurement);
+ } else {
+ result = conn->local_to_remote_time_difference -
+ (uint64_t)((1.0 - conn->local_to_remote_time_gradient) *
+ time_since_last_local_to_remote_time_difference_measurement);
+ }
+ return result;
}
void rtp_audio_receiver_cleanup_handler(__attribute__((unused)) void *arg) {
@@ -108,7 +104,7 @@ void *rtp_audio_receiver(void *arg) {
int32_t last_seqno = -1;
uint8_t packet[2048], *pktp;
- uint64_t time_of_previous_packet_fp = 0;
+ uint64_t time_of_previous_packet_ns = 0;
float longest_packet_time_interval_us = 0.0;
// mean and variance calculations from "online_variance" algorithm at
@@ -125,11 +121,10 @@ void *rtp_audio_receiver(void *arg) {
frame_count++;
- uint64_t local_time_now_fp = get_absolute_time_in_fp();
- if (time_of_previous_packet_fp) {
- float time_interval_us =
- (((local_time_now_fp - time_of_previous_packet_fp) * 1000000) >> 32) * 1.0;
- time_of_previous_packet_fp = local_time_now_fp;
+ uint64_t local_time_now_ns = get_absolute_time_in_ns();
+ if (time_of_previous_packet_ns) {
+ float time_interval_us = (local_time_now_ns - time_of_previous_packet_ns) * 0.001;
+ time_of_previous_packet_ns = local_time_now_ns;
if (time_interval_us > longest_packet_time_interval_us)
longest_packet_time_interval_us = time_interval_us;
stat_n += 1;
@@ -137,17 +132,18 @@ void *rtp_audio_receiver(void *arg) {
stat_mean += stat_delta / stat_n;
stat_M2 += stat_delta * (time_interval_us - stat_mean);
if (stat_n % 2500 == 0) {
- debug(2, "Packet reception interval stats: mean, standard deviation and max for the last "
- "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",
+ debug(2,
+ "Packet reception interval stats: mean, standard deviation and max for the last "
+ "2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",
stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us);
stat_n = 0;
stat_mean = 0.0;
stat_M2 = 0.0;
- time_of_previous_packet_fp = 0;
+ time_of_previous_packet_ns = 0;
longest_packet_time_interval_us = 0.0;
}
} else {
- time_of_previous_packet_fp = local_time_now_fp;
+ time_of_previous_packet_ns = local_time_now_ns;
}
if (nread >= 0) {
@@ -248,9 +244,6 @@ void *rtp_control_receiver(void *arg) {
ssize_t nread;
while (1) {
nread = recv(conn->control_socket, packet, sizeof(packet), 0);
- // local_time_now = get_absolute_time_in_fp();
- // clock_gettime(CLOCK_MONOTONIC,&tn);
- // local_time_now=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000;
if (nread >= 0) {
@@ -270,8 +263,8 @@ void *rtp_control_receiver(void *arg) {
obfp += 2;
};
*obfp = 0;
-
-
+
+
// get raw timestamp information
// I think that a good way to understand these timestamps is that
// (1) the rtlt below is the timestamp of the frame that should be playing at the
@@ -282,19 +275,19 @@ void *rtp_control_receiver(void *arg) {
// Thus, (3) the latency can be calculated by subtracting the second from the
// first.
// There must be more to it -- there something missing.
-
+
// In addition, it seems that if the value of the short represented by the second
// pair of bytes in the packet is 7
// then an extra time lag is expected to be added, presumably by
// the AirPort Express.
-
+
// Best guess is that this delay is 11,025 frames.
-
+
uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
uint32_t rt = nctohl(&packet[16]); // raw timestamp
-
+
uint32_t fl = nctohs(&packet[2]); //
-
+
debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u,
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
//debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
@@ -303,11 +296,16 @@ void *rtp_control_receiver(void *arg) {
*/
if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged
// first...
+ uint64_t ps, pn;
- remote_time_of_sync = (uint64_t)nctohl(&packet[8]) << 32;
- remote_time_of_sync += nctohl(&packet[12]);
+ ps = nctohl(&packet[8]);
+ ps = ps * 1000000000; // this many nanoseconds from the whole seconds
+ pn = nctohl(&packet[12]);
+ pn = pn * 1000000000;
+ pn = pn >> 32; // this many nanoseconds from the fractional part
+ remote_time_of_sync = ps + pn;
- // debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync);
+ // debug(1,"Remote Sync Time: " PRIu64 "",remote_time_of_sync);
sync_rtp_timestamp = nctohl(&packet[16]);
uint32_t rtp_timestamp_less_latency = nctohl(&packet[4]);
@@ -368,9 +366,10 @@ void *rtp_control_receiver(void *arg) {
if (la != conn->latency) {
conn->latency = la;
- debug(3, "New latency detected: %" PRIu32 ", sync latency: %" PRIu32
- ", minimum latency: %" PRIu32 ", maximum "
- "latency: %" PRIu32 ", fixed offset: %" PRIu32 ".",
+ debug(3,
+ "New latency detected: %" PRIu32 ", sync latency: %" PRIu32
+ ", minimum latency: %" PRIu32 ", maximum "
+ "latency: %" PRIu32 ", fixed offset: %" PRIu32 ".",
la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency,
conn->maximum_latency, config.fixedLatencyOffset);
}
@@ -390,15 +389,8 @@ void *rtp_control_receiver(void *arg) {
conn->initial_reference_time; // here, this should never be zero
if (remote_frame_time_interval) {
conn->remote_frame_rate =
- (1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) /
- remote_frame_time_interval; // an IEEE double calculation with a 32-bit
- // numerator and 64-bit denominator
- // integers
- conn->remote_frame_rate =
- conn->remote_frame_rate * (uint64_t)0x100000000; // this should just change the
- // [binary] exponent in the IEEE
- // FP representation; the
- // mantissa should be unaffected.
+ (1.0E9 * (conn->reference_timestamp - conn->initial_reference_timestamp)) /
+ remote_frame_time_interval;
} else {
conn->remote_frame_rate = 0.0; // use as a flag.
}
@@ -423,28 +415,6 @@ void *rtp_control_receiver(void *arg) {
else
conn->reference_to_previous_frame_difference =
sync_rtp_timestamp - old_reference_timestamp;
-
- // int64_t delayed_frame_difference = rtp_timestamp_less_latency -
- // old_latency_delayed_timestamp;
-
- /*
- if (old_remote_reference_time)
- debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %"
- PRId64 " and %" PRId64 ", giving rates _at source!!_ of %f and %f respectively.",
- (conn->reference_to_previous_time_difference*1000000)>>32,conn->reference_to_previous_frame_difference,delayed_frame_difference,
- (1.0*(conn->reference_to_previous_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32));
- else
- debug(1,"First sync received");
- */
-
- // debug(1,"New Reference timestamp and timestamp time...");
- // get estimated remote time now
- // remote_time_now = local_time_now + local_to_remote_time_difference;
-
- // debug(1,"Sync Time is %lld us late (remote
- // times).",((remote_time_now-remote_time_of_sync)*1000000)>>32);
- // debug(1,"Sync Time is %lld us late (local
- // times).",((local_time_now-reference_timestamp_time)*1000000)>>32);
} else {
debug(2, "Sync packet received before we got a timing packet back.");
}
@@ -520,8 +490,7 @@ void *rtp_timing_sender(void *arg) {
req.filler = 0;
req.origin = req.receive = req.transmit = 0;
- // clock_gettime(CLOCK_MONOTONIC,&dtt);
- conn->departure_time = get_absolute_time_in_fp();
+ conn->departure_time = get_absolute_time_in_ns();
socklen_t msgsize = sizeof(struct sockaddr_in);
#ifdef AF_INET6
if (conn->rtp_client_timing_socket.SAFAMILY == AF_INET6) {
@@ -542,8 +511,8 @@ void *rtp_timing_sender(void *arg) {
request_number++;
- if (request_number <= 4)
- usleep(500000); // these are thread cancellation points
+ if (request_number <= 6)
+ usleep(300000); // these are thread cancellation points
else
usleep(3000000);
}
@@ -553,8 +522,29 @@ void *rtp_timing_sender(void *arg) {
}
void rtp_timing_receiver_cleanup_handler(void *arg) {
- debug(3, "Timing Receiver Cleanup.");
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
+ debug(3, "Timing Receiver Cleanup.");
+ // walk down the list of DACP / gradient pairs, if any
+ nvll *gradients = config.gradients;
+ if (conn->dacp_id)
+ while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string,gradients->name) != 0))
+ gradients = gradients->next;
+
+ // if gradients comes out of this non-null, it is pointing to the DACP and it's last-known gradient
+ if (gradients) {
+ gradients->value = conn->local_to_remote_time_gradient;
+ // debug(1,"Updating a drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, gradients->name);
+ } else {
+ nvll *new_entry = (nvll*)malloc(sizeof(nvll));
+ if (new_entry) {
+ new_entry->name = strdup((const char *)&conn->client_ip_string);
+ new_entry->value = conn->local_to_remote_time_gradient;
+ new_entry->next = config.gradients;
+ config.gradients = new_entry;
+ // debug(1,"Setting a new drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, new_entry->name);
+ }
+ }
+
debug(3, "Cancel Timing Requester.");
pthread_cancel(conn->timer_requester);
int oldState;
@@ -580,6 +570,34 @@ void *rtp_timing_receiver(void *arg) {
// uint64_t first_local_time = 0;
uint64_t first_local_to_remote_time_difference = 0;
+
+ conn->local_to_remote_time_gradient = 1.0; // initial value.
+ // walk down the list of DACP / gradient pairs, if any
+ nvll *gradients = config.gradients;
+ while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string,gradients->name) != 0))
+ gradients = gradients->next;
+
+ // if gradients comes out of this non-null, it is pointing to the IP and it's last-known gradient
+ if (gradients) {
+ conn->local_to_remote_time_gradient = gradients->value;
+ // debug(1,"Using a stored drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, gradients->name);
+ }
+
+ // calculate diffusion factor
+
+ // at the end of the array of time pings, the diffusion factor
+ // must be diffusion_expansion_factor
+ // this, at each step, the diffusion multiplication constant must
+ // be the nth root of diffusion_expansion_factor
+ // where n is the number of elements in the array
+
+ const double diffusion_expansion_factor = 10;
+ double log_of_multiplier = log10(diffusion_expansion_factor)/time_ping_history;
+ double multiplier = pow(10,log_of_multiplier);
+ uint64_t dispersion_factor = (uint64_t)(multiplier * 100);
+ // debug(1,"dispersion factor is %" PRIu64 ".", dispersion_factor);
+
+
// uint64_t first_local_to_remote_time_difference_time;
// uint64_t l2rtd = 0;
int sequence_number = 0;
@@ -596,45 +614,37 @@ void *rtp_timing_receiver(void *arg) {
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
(drand48() > config.diagnostic_drop_packet_fraction)) {
- arrival_time = get_absolute_time_in_fp();
+ arrival_time = get_absolute_time_in_ns();
// ssize_t plen = nread;
// debug(1,"Packet Received on Timing Port.");
if (packet[1] == 0xd3) { // timing reply
- /*
- char obf[4096];
- char *obfp = obf;
- int obfc;
- for (obfc=0;obfcdeparture_time;
+ debug(3,"clock synchronisation request: return time is %8.3f milliseconds.",0.000001*return_time);
- // uint64_t rtus = (return_time * 1000000) >> 32;
-
- if (((return_time * 1000000) >> 32) < 300000) {
-
- // debug(2,"Synchronisation ping return time is %f milliseconds.",(rtus*1.0)/1000);
-
+ if (return_time < 200000000) { // must be less than 0.2 seconds
// distant_receive_time =
// ((uint64_t)ntohl(*((uint32_t*)&packet[16])))<<32+ntohl(*((uint32_t*)&packet[20]));
- distant_receive_time = (uint64_t)nctohl(&packet[16]) << 32;
- distant_receive_time += nctohl(&packet[20]);
+ uint64_t ps, pn;
+
+ ps = nctohl(&packet[16]);
+ ps = ps * 1000000000; // this many nanoseconds from the whole seconds
+ pn = nctohl(&packet[20]);
+ pn = pn * 1000000000;
+ pn = pn >> 32; // this many nanoseconds from the fractional part
+ distant_receive_time = ps + pn;
// distant_transmit_time =
// ((uint64_t)ntohl(*((uint32_t*)&packet[24])))<<32+ntohl(*((uint32_t*)&packet[28]));
- distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32;
- distant_transmit_time += nctohl(&packet[28]);
+ ps = nctohl(&packet[24]);
+ ps = ps * 1000000000; // this many nanoseconds from the whole seconds
+ pn = nctohl(&packet[28]);
+ pn = pn * 1000000000;
+ pn = pn >> 32; // this many nanoseconds from the fractional part
+ distant_transmit_time = ps + pn;
uint64_t remote_processing_time = 0;
@@ -644,13 +654,8 @@ void *rtp_timing_receiver(void *arg) {
debug(1, "Yikes: distant_transmit_time is before distant_receive_time; remote "
"processing time set to zero.");
}
- // debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 "
- // uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32);
-
- uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2;
-
- // remove the remote processing time from the record of the return time, as long at the
- // processing time looks sensible.
+ // debug(1,"Return trip time: %" PRIu64 " nS, remote processing time: %" PRIu64 "
+ // nS.",return_time, remote_processing_time);
if (remote_processing_time < return_time)
return_time -= remote_processing_time;
@@ -658,24 +663,25 @@ void *rtp_timing_receiver(void *arg) {
debug(1, "Remote processing time greater than return time -- ignored.");
int cc;
+ // debug(1, "time ping history is %d entries.", time_ping_history);
for (cc = time_ping_history - 1; cc > 0; cc--) {
conn->time_pings[cc] = conn->time_pings[cc - 1];
// if ((conn->time_ping_count) && (conn->time_ping_count < 10))
// conn->time_pings[cc].dispersion =
// conn->time_pings[cc].dispersion * pow(2.14,
// 1.0/conn->time_ping_count);
- conn->time_pings[cc].dispersion =
- (conn->time_pings[cc].dispersion * 110) /
+ if (conn->time_pings[cc].dispersion > UINT64_MAX / dispersion_factor)
+ debug(1,"dispersion factor is too large at %" PRIu64 ".");
+ else
+ conn->time_pings[cc].dispersion =
+ (conn->time_pings[cc].dispersion * dispersion_factor) /
100; // make the dispersions 'age' by this rational factor
}
// these are used for doing a least squares calculation to get the drift
conn->time_pings[0].local_time = arrival_time;
- conn->time_pings[0].remote_time = distant_transmit_time;
+ conn->time_pings[0].remote_time = distant_transmit_time + return_time / 2;
conn->time_pings[0].sequence_number = sequence_number++;
conn->time_pings[0].chosen = 0;
-
- conn->time_pings[0].local_to_remote_difference =
- local_time_by_remote_clock - arrival_time;
conn->time_pings[0].dispersion = return_time;
if (conn->time_ping_count < time_ping_history)
conn->time_ping_count++;
@@ -685,28 +691,27 @@ void *rtp_timing_receiver(void *arg) {
// mean and variance calculations from "online_variance" algorithm at
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
- double rtfus = 1.0 * ((return_time * 1000000) >> 32);
stat_n += 1;
- double stat_delta = rtfus - stat_mean;
+ double stat_delta = return_time - stat_mean;
stat_mean += stat_delta / stat_n;
- stat_M2 += stat_delta * (rtfus - stat_mean);
+ stat_M2 += stat_delta * (return_time - stat_mean);
// debug(1, "Timing packet return time stats: current, mean and standard deviation over
- // %d packets: %.1f, %.1f, %.1f (microseconds).",
- // stat_n,rtfus,stat_mean, sqrtf(stat_M2 / (stat_n - 1)));
+ // %d packets: %.1f, %.1f, %.1f (nanoseconds).",
+ // stat_n,return_time,stat_mean, sqrtf(stat_M2 / (stat_n - 1)));
// here, pick the record with the least dispersion, and record that it's been chosen
// uint64_t local_time_chosen = arrival_time;
// uint64_t remote_time_chosen = distant_transmit_time;
// now pick the timestamp with the lowest dispersion
- uint64_t l2rtd = conn->time_pings[0].local_to_remote_difference;
+ uint64_t rt = conn->time_pings[0].remote_time;
uint64_t lt = conn->time_pings[0].local_time;
uint64_t tld = conn->time_pings[0].dispersion;
int chosen = 0;
for (cc = 1; cc < conn->time_ping_count; cc++)
if (conn->time_pings[cc].dispersion < tld) {
chosen = cc;
- l2rtd = conn->time_pings[cc].local_to_remote_difference;
+ rt = conn->time_pings[cc].remote_time;
lt = conn->time_pings[cc].local_time;
tld = conn->time_pings[cc].dispersion;
// local_time_chosen = conn->time_pings[cc].local_time;
@@ -716,46 +721,8 @@ void *rtp_timing_receiver(void *arg) {
// dispersion.",chosen,1.0*((tld * 1000000) >> 32));
conn->time_pings[chosen].chosen = 1; // record the fact that it has been used for timing
- /*
- // calculate the jitter -- the absolute time between the current
- local_to_remote_time_difference and the new one and add it to the total jitter count
- int64_t ji;
- int64_t ltd =0; // local time difference for the jitter
-
- if (conn->time_ping_count > 1) {
- if (l2rtd > conn->local_to_remote_time_difference) {
- local_to_remote_time_jitter =
- local_to_remote_time_jitter + l2rtd - conn->local_to_remote_time_difference;
- ji = l2rtd - conn->local_to_remote_time_difference; // this is the difference
- between the present local-to-remote-time-difference and the new one, i.e. the jitter
- step
- } else {
- local_to_remote_time_jitter =
- local_to_remote_time_jitter + conn->local_to_remote_time_difference - l2rtd;
- ji = -(conn->local_to_remote_time_difference - l2rtd);
- }
- local_to_remote_time_jitter_count += 1;
- }
- if (conn->local_to_remote_time_difference_measurement_time < lt)
- ltd = lt-conn->local_to_remote_time_difference_measurement_time;
- else
- ltd = -(conn->local_to_remote_time_difference_measurement_time-lt);
-
- if (ltd) {
- debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji *
- (int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32);
- debug(1,"Source clock to local clock drift: %.2f ppm.",((1.0*ji)/ltd)*1000000.0);
- }
- // uncomment below to print jitter between client's clock and our clock
-
- if (ji) {
- int64_t rtus = (tld*1000000)>>32;
- debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an
- adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32);
- }
- */
conn->local_to_remote_time_difference =
- l2rtd; // make this the new local-to-remote-time-difference
+ rt - lt; // make this the new local-to-remote-time-difference
conn->local_to_remote_time_difference_measurement_time = lt; // done at this time.
if (first_local_to_remote_time_difference == 0) {
@@ -785,53 +752,68 @@ void *rtp_timing_receiver(void *arg) {
if ((conn->time_pings[cc].chosen) &&
(conn->time_pings[cc].sequence_number >
(settling_time / 3))) { // wait for a approximate settling time
- y_bar += (conn->time_pings[cc].remote_time >>
- 12); // precision is down to 1/4th of a microsecond
- x_bar += (conn->time_pings[cc].local_time >> 12);
+ // have to scale them down so that the sum, possibly over every term in the array, doesn't overflow
+ y_bar += (conn->time_pings[cc].remote_time >> time_ping_history_power_of_two);
+ x_bar += (conn->time_pings[cc].local_time >> time_ping_history_power_of_two);
sample_count++;
}
+ conn->local_to_remote_time_gradient_sample_count = sample_count;
if (sample_count > sample_point_minimum) {
y_bar = y_bar / sample_count;
x_bar = x_bar / sample_count;
+
+
int64_t xid, yid;
- int64_t mtl, mbl;
+ double mtl, mbl;
mtl = 0;
mbl = 0;
for (cc = 0; cc < conn->time_ping_count; cc++)
if ((conn->time_pings[cc].chosen) &&
(conn->time_pings[cc].sequence_number > (settling_time / 3))) {
- uint64_t slt = conn->time_pings[cc].local_time >> 12;
+ uint64_t slt = conn->time_pings[cc].local_time >> time_ping_history_power_of_two;
if (slt > x_bar)
xid = slt - x_bar;
else
xid = -(x_bar - slt);
- uint64_t srt = conn->time_pings[cc].remote_time >> 12;
+ uint64_t srt = conn->time_pings[cc].remote_time >> time_ping_history_power_of_two;
if (srt > y_bar)
yid = srt - y_bar;
else
yid = -(y_bar - srt);
- mtl = mtl + xid * yid;
- mbl = mbl + xid * xid;
+ mtl = mtl + (1.0 * xid) * yid;
+ mbl = mbl + (1.0 * xid) * xid;
}
- conn->local_to_remote_time_gradient_sample_count = sample_count;
if (mbl)
- conn->local_to_remote_time_gradient = (1.0 * mtl) / mbl;
+ conn->local_to_remote_time_gradient = mtl / mbl;
else {
- conn->local_to_remote_time_gradient = 1.0;
- debug(1, "rtp_timing_receiver: mbl is 0");
+ // conn->local_to_remote_time_gradient = 1.0;
+ debug(1,"mbl is zero. Drift remains at %.2f ppm.", (conn->local_to_remote_time_gradient - 1.0)*1000000);
}
+
+ // scale the numbers back up
+ uint64_t ybf = y_bar << time_ping_history_power_of_two;
+ uint64_t xbf = x_bar << time_ping_history_power_of_two;
+
+ conn->local_to_remote_time_difference =
+ ybf - xbf; // make this the new local-to-remote-time-difference
+ conn->local_to_remote_time_difference_measurement_time = xbf;
+
} else {
- conn->local_to_remote_time_gradient = 1.0;
+ debug(3,"not enough samples to estimate drift -- remaining at %.2f ppm.", (conn->local_to_remote_time_gradient - 1.0)*1000000);
+ // conn->local_to_remote_time_gradient = 1.0;
}
// debug(1,"local to remote time gradient is %12.2f ppm, based on %d
// samples.",conn->local_to_remote_time_gradient*1000000,sample_count);
+
} else {
- debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.",
- (return_time * 1000000) >> 32);
+ debug(1,
+ "Time ping turnaround time: %" PRIu64
+ " ns -- it looks like a timing ping was lost.",
+ return_time);
}
} else {
debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread);
@@ -1039,16 +1021,13 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport
conn->local_control_port, conn->local_timing_port);
conn->reference_timestamp = 0;
- // pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
- // pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
- // pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
conn->request_sent = 0;
conn->rtp_running = 1;
#ifdef CONFIG_METADATA
- send_ssnc_metadata('clip', strdup(conn->client_ip_string), strlen(conn->client_ip_string), 1);
- send_ssnc_metadata('svip', strdup(conn->self_ip_string), strlen(conn->self_ip_string), 1);
+ send_ssnc_metadata('clip', conn->client_ip_string, strlen(conn->client_ip_string), 1);
+ send_ssnc_metadata('svip', conn->self_ip_string, strlen(conn->self_ip_string), 1);
#endif
}
}
@@ -1061,9 +1040,6 @@ void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time
*remote_timestamp_time = conn->remote_reference_timestamp_time;
*timestamp_time =
conn->remote_reference_timestamp_time - local_to_remote_time_difference_now(conn);
- // if ((*timestamp == 0) && (*timestamp_time == 0)) {
- // debug(1,"Reference timestamp is invalid.");
- //}
debug_mutex_unlock(&conn->reference_time_mutex, 0);
}
@@ -1088,9 +1064,8 @@ const int use_nominal_rate = 0; // specify whether to use the nominal input rate
int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn) {
int result = 1;
uint32_t fs = conn->input_rate;
- *frames = fs;
- uint64_t one_fp = (uint64_t)(0x100000000); // one second in fp form
- *time = one_fp;
+ *frames = fs; // default value to return
+ *time = 1000000000; // default value to return
if ((conn->initial_reference_time) && (conn->initial_reference_timestamp)) {
// uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp;
uint32_t local_frames =
@@ -1101,7 +1076,7 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con
} else {
double calculated_frame_rate = conn->input_rate;
if (local_time)
- calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp;
+ calculated_frame_rate = (1.0E9 * local_frames) / local_time;
else
debug(1, "sanitised_source_rate_information: local_time is zero");
if ((local_time == 0) || ((calculated_frame_rate / conn->input_rate) > 1.002) ||
@@ -1173,7 +1148,7 @@ int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) {
// here, we calculate the time interval, in terms of remote time
uint64_t offset = modulo_64_offset(conn->remote_reference_timestamp_time, remote_time);
- int reference_time_was_earlier = (offset <= (uint64_t)0x100000000 * 3600);
+ int reference_time_was_earlier = (offset <= (uint64_t)3600000000000);
if (reference_time_was_earlier) // if we haven't had a reference within the last hour, it'll be
// taken as afterwards
time_interval = remote_time - conn->remote_reference_timestamp_time;
@@ -1217,11 +1192,10 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
msgsize = sizeof(struct sockaddr_in6);
}
#endif
- uint64_t time_of_sending_fp = get_absolute_time_in_fp();
- uint64_t resend_error_backoff_time = (uint64_t)1000000 * 0.3; // 0.3 seconds
- resend_error_backoff_time = (resend_error_backoff_time << 32) / 1000000;
- if ((conn->rtp_time_of_last_resend_request_error_fp == 0) ||
- ((time_of_sending_fp - conn->rtp_time_of_last_resend_request_error_fp) >
+ uint64_t time_of_sending_ns = get_absolute_time_in_ns();
+ uint64_t resend_error_backoff_time = 300000000; // 0.3 seconds
+ if ((conn->rtp_time_of_last_resend_request_error_ns == 0) ||
+ ((time_of_sending_ns - conn->rtp_time_of_last_resend_request_error_ns) >
resend_error_backoff_time)) {
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
(drand48() > config.diagnostic_drop_packet_fraction)) {
@@ -1239,22 +1213,20 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
(struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) {
char em[1024];
strerror_r(errno, em, sizeof(em));
- debug(2, "Error %d using sendto to request a resend: \"%s\".",
- errno, em);
- conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
+ debug(2, "Error %d using sendto to request a resend: \"%s\".", errno, em);
+ conn->rtp_time_of_last_resend_request_error_ns = time_of_sending_ns;
} else {
- conn->rtp_time_of_last_resend_request_error_fp = 0;
+ conn->rtp_time_of_last_resend_request_error_ns = 0;
}
} else {
- debug(
- 3,
- "Dropping resend request packet to simulate a bad network. Backing off for 0.3 "
- "second.");
- conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
+ debug(3, "Dropping resend request packet to simulate a bad network. Backing off for 0.3 "
+ "second.");
+ conn->rtp_time_of_last_resend_request_error_ns = time_of_sending_ns;
}
} else {
- debug(1, "Suppressing a resend request due to a resend sendto error in the last 0.3 seconds.");
+ debug(1,
+ "Suppressing a resend request due to a resend sendto error in the last 0.3 seconds.");
}
} else {
// if (!request_sent) {
diff --git a/rtsp.c b/rtsp.c
index 6cf53927..2febacd0 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -114,7 +114,8 @@ typedef struct {
pthread_mutex_t pc_queue_lock;
pthread_cond_t pc_queue_item_added_signal;
pthread_cond_t pc_queue_item_removed_signal;
- size_t item_size; // number of bytes in each item
+ char *name;
+ size_t item_size; // number of bytes in each item
uint32_t count; // number of items in the queue
uint32_t capacity; // maximum number of items
uint32_t toq; // first item to take
@@ -151,7 +152,11 @@ typedef struct {
rtsp_message *carrier;
} metadata_package;
-void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) {
+void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items, const char* name) {
+ if (name)
+ debug(2, "Creating metadata queue \"%s\".", name);
+ else
+ debug(1, "Creating an unnamed metadata queue.");
pthread_mutex_init(&the_queue->pc_queue_lock, NULL);
pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL);
pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL);
@@ -161,12 +166,26 @@ void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t
the_queue->capacity = number_of_items;
the_queue->toq = 0;
the_queue->eoq = 0;
+ if (name == NULL)
+ the_queue->name = NULL;
+ else
+ the_queue->name = strdup(name);
}
void pc_queue_delete(pc_queue *the_queue) {
+ if (the_queue->name)
+ debug(2, "Deleting metadata queue \"%s\".", the_queue->name);
+ else
+ debug(1, "Deleting an unnamed metadata queue.");
+ if (the_queue->name != NULL)
+ free(the_queue->name);
+ // debug(2, "destroying pc_queue_item_removed_signal");
pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal);
+ // debug(2, "destroying pc_queue_item_added_signal");
pthread_cond_destroy(&the_queue->pc_queue_item_added_signal);
+ // debug(2, "destroying pc_queue_lock");
pthread_mutex_destroy(&the_queue->pc_queue_lock);
+ // debug(2, "destroying signals and locks done");
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
@@ -185,6 +204,7 @@ void pc_queue_cleanup_handler(void *arg) {
}
int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
+ int response = 0;
int rc;
if (the_queue) {
if (block == 0) {
@@ -196,33 +216,43 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
if (rc)
debug(1, "Error locking for pc_queue_add_item");
pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
- while (the_queue->count == the_queue->capacity) {
- rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock);
- if (rc)
- debug(1, "Error waiting for item to be removed");
- }
- uint32_t i = the_queue->eoq;
- void *p = the_queue->items + the_queue->item_size * i;
- // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
- memcpy(p, the_stuff, the_queue->item_size);
+ // leave this out if you want this to return if the queue is already full
+ // irrespective of the block flag.
+ /*
+ while (the_queue->count == the_queue->capacity) {
+ rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock);
+ if (rc)
+ debug(1, "Error waiting for item to be removed");
+ }
+ */
+ if (the_queue->count < the_queue->capacity) {
+ uint32_t i = the_queue->eoq;
+ void *p = the_queue->items + the_queue->item_size * i;
+ // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
+ memcpy(p, the_stuff, the_queue->item_size);
- // update the pointer
- i++;
- if (i == the_queue->capacity)
- // fold pointer if necessary
- i = 0;
- the_queue->eoq = i;
- the_queue->count++;
- if (the_queue->count == the_queue->capacity)
- debug(1, "pc_queue is full with %d items in it!", the_queue->count);
- rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
- if (rc)
- debug(1, "Error signalling after pc_queue_add_item");
+ // update the pointer
+ i++;
+ if (i == the_queue->capacity)
+ // fold pointer if necessary
+ i = 0;
+ the_queue->eoq = i;
+ the_queue->count++;
+ //debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count, the_queue->capacity);
+ if (the_queue->count == the_queue->capacity)
+ debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name, the_queue->count);
+ rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
+ if (rc)
+ debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item", the_queue->name);
+ } else {
+ response = EWOULDBLOCK; // a bit arbitrary, this.
+ debug(3,"metadata queue \"%s\": is already full with %d items in it. Not adding this item to the queue.", the_queue->name, the_queue->count);
+ }
pthread_cleanup_pop(1); // unlock the queue lock.
} else {
debug(1, "Adding an item to a NULL queue");
}
- return 0;
+ return response;
}
int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
@@ -230,12 +260,12 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
if (the_queue) {
rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
if (rc)
- debug(1, "Error locking for pc_queue_get_item");
+ debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name);
pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
while (the_queue->count == 0) {
rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock);
if (rc)
- debug(1, "Error waiting for item to be added");
+ debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name);
}
uint32_t i = the_queue->toq;
// void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq;
@@ -249,9 +279,10 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
i = 0;
the_queue->toq = i;
the_queue->count--;
+ debug(3,"metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count, the_queue->capacity);
rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal);
if (rc)
- debug(1, "Error signalling after pc_queue_removed_item");
+ debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name);
pthread_cleanup_pop(1); // unlock the queue lock.
} else {
debug(1, "Removing an item from a NULL queue");
@@ -286,7 +317,7 @@ void *player_watchdog_thread_code(void *arg) {
uint64_t last_watchdog_bark_time = conn->watchdog_bark_time;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
if (last_watchdog_bark_time != 0) {
- uint64_t time_since_last_bark = (get_absolute_time_in_fp() - last_watchdog_bark_time) >> 32;
+ uint64_t time_since_last_bark = (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000;
uint64_t ct = config.timeout; // go from int to 64-bit int
if (time_since_last_bark >= ct) {
@@ -337,11 +368,11 @@ static void track_thread(rtsp_conn_info *conn) {
void cancel_all_RTSP_threads(void) {
int i;
for (i = 0; i < nconns; i++) {
- debug(1, "Connection %d: cancelling.", conns[i]->connection_number);
+ debug(2, "Connection %d: cancelling.", conns[i]->connection_number);
pthread_cancel(conns[i]->thread);
}
for (i = 0; i < nconns; i++) {
- debug(1, "Connection %d: joining.", conns[i]->connection_number);
+ debug(2, "Connection %d: joining.", conns[i]->connection_number);
pthread_join(conns[i]->thread, NULL);
free(conns[i]);
}
@@ -415,6 +446,7 @@ void msg_retain(rtsp_message *msg) {
debug(1, "Error %d locking reference counter lock");
if (msg > (rtsp_message *)0x00010000) {
msg->referenceCount++;
+ debug(3,"msg_free increment reference counter message %d to %d.", msg->index_number, msg->referenceCount);
// debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
rc = pthread_mutex_unlock(&reference_counter_lock);
if (rc)
@@ -430,6 +462,7 @@ rtsp_message *msg_init(void) {
memset(msg, 0, sizeof(rtsp_message));
msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
msg->index_number = msg_indexes++;
+ debug(3,"msg_init message %d", msg->index_number);
} else {
die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
}
@@ -493,6 +526,8 @@ void msg_free(rtsp_message **msgh) {
if (*msgh > (rtsp_message *)0x00010000) {
rtsp_message *msg = *msgh;
msg->referenceCount--;
+ if (msg->referenceCount)
+ debug(3,"msg_free decrement reference counter message %d to %d", msg->index_number, msg->referenceCount);
if (msg->referenceCount == 0) {
unsigned int i;
for (i = 0; i < msg->nheaders; i++) {
@@ -507,6 +542,7 @@ void msg_free(rtsp_message **msgh) {
index = 0x10000; // ensure it doesn't fold to zero.
*msgh =
(rtsp_message *)(index); // put a version of the index number of the freed message in here
+ debug(3,"msg_free freed message %d", msg->index_number);
free(msg);
} else {
// debug(1,"msg_free item %d -- decrement reference to
@@ -666,7 +702,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
}
uint64_t threshold_time =
- get_absolute_time_in_fp() + ((uint64_t)15 << 32); // i.e. fifteen seconds from now
+ get_absolute_time_in_ns() + ((uint64_t)15000000000); // i.e. fifteen seconds from now
int warning_message_sent = 0;
const size_t max_read_chunk = 1024 * 1024 / 16;
@@ -679,7 +715,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
// metadata
if (warning_message_sent == 0) {
- uint64_t time_now = get_absolute_time_in_fp();
+ uint64_t time_now = get_absolute_time_in_ns();
if (time_now > threshold_time) { // it's taking too long
debug(1, "Error receiving metadata from source -- transmission seems "
"to be stalled.");
@@ -894,12 +930,17 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
}
}
// debug(1,"RTSP Flush Requested: %u.",rtptime);
+
+// the following is now done better by the player_flush routine as a 'pfls'
+/*
#ifdef CONFIG_METADATA
if (p)
send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1);
else
send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
#endif
+*/
+
player_flush(rtptime, conn); // will not crash even it there is no player thread.
resp->respcode = 200;
@@ -1070,7 +1111,7 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
char *progress = cp + strlen("progress: ");
// debug(2, "progress: \"%s\"",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per
// second
- send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1);
+ send_ssnc_metadata('prgr', progress, strlen(progress), 1);
} else
#endif
@@ -1113,7 +1154,10 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
// 'aend' -- active mode exited. No arguments
// 'pbeg' -- play stream begin. No arguments
// 'pend' -- play stream end. No arguments
-// 'pfls' -- play stream flush. No arguments
+// 'pfls' -- play stream flush. The argument is an unsigned 32-bit
+// frame number. It seems that all frames up to but not
+// including this frame are to be flushed.
+//
// 'prsm' -- play stream resume. No arguments
// `pffr` -- the first frame of a play session has been received and has been validly
// timed.
@@ -1153,8 +1197,11 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
// to send commands to the source's remote control (if it has one).
// `clip` -- the payload is the IP number of the client, i.e. the sender of audio.
// Can be an IPv4 or an IPv6 number.
+// `svip` -- the payload is the IP number of the server, i.e. the player itself.
+// Can be an IPv4 or an IPv6 number.
// `dapo` -- the payload is the port number (as text) on the server to which remote
// control commands should be sent. It is 3689 for iTunes but varies for iOS devices.
+
// A special sub-protocol is used for sending large data items over UDP
// If the payload exceeded 4 MB, it is chunked using the following format:
// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
@@ -1217,14 +1264,36 @@ char *base64_encode_so(const unsigned char *data, size_t input_length, char *enc
static int fd = -1;
// static int dirty = 0;
+
+
+
pc_queue metadata_queue;
+#define metadata_queue_size 500
+metadata_package metadata_queue_items[metadata_queue_size];
+pthread_t metadata_thread;
+
+#ifdef CONFIG_METADATA_HUB
+pc_queue metadata_hub_queue;
+#define metadata_hub_queue_size 500
+metadata_package metadata_hub_queue_items[metadata_hub_queue_size];
+pthread_t metadata_hub_thread;
+#endif
+
+#ifdef CONFIG_MQTT
+pc_queue metadata_mqtt_queue;
+#define metadata_mqtt_queue_size 500
+metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size];
+pthread_t metadata_mqtt_thread;
+#endif
+
static int metadata_sock = -1;
static struct sockaddr_in metadata_sockaddr;
static char *metadata_sockmsg;
-#define metadata_queue_size 500
-metadata_package metadata_queue_items[metadata_queue_size];
+pc_queue metadata_multicast_queue;
+#define metadata_multicast_queue_size 500
+metadata_package metadata_multicast_queue_items[metadata_queue_size];
+pthread_t metadata_multicast_thread;
-pthread_t metadata_thread;
void metadata_create_multicast_socket(void) {
if (config.metadata_enabled == 0)
@@ -1252,19 +1321,6 @@ void metadata_create_multicast_socket(void) {
}
}
}
-
- size_t pl = strlen(config.metadata_pipename) + 1;
-
- char *path = malloc(pl + 1);
- snprintf(path, pl + 1, "%s", config.metadata_pipename);
-
- mode_t oldumask = umask(000);
-
- if (mkfifo(path, 0666) && errno != EEXIST)
- die("Could not create metadata FIFO %s", path);
-
- free(path);
- umask(oldumask);
}
void metadata_delete_multicast_socket(void) {
@@ -1276,6 +1332,7 @@ void metadata_delete_multicast_socket(void) {
free(metadata_sockmsg);
}
+
void metadata_open(void) {
if (config.metadata_enabled == 0)
return;
@@ -1285,11 +1342,7 @@ void metadata_open(void) {
char *path = malloc(pl + 1);
snprintf(path, pl + 1, "%s", config.metadata_pipename);
- fd = open(path, O_WRONLY | O_NONBLOCK);
- // if (fd < 0)
- // debug(1, "Could not open metadata FIFO %s. Will try again later.",
- // path);
-
+ fd = try_to_open_pipe_for_writing(path);
free(path);
}
@@ -1300,10 +1353,8 @@ static void metadata_close(void) {
fd = -1;
}
-void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
- // debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length);
- int ret;
-
+void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
+ // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code, length);
if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) {
char *ptr = metadata_sockmsg;
uint32_t v;
@@ -1358,8 +1409,13 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
break;
} while (1);
}
+}
+void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
+ // debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length);
+ int ret = 0;
// readers may go away and come back
+
if (fd < 0)
metadata_open();
if (fd < 0)
@@ -1367,14 +1423,16 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
char thestring[1024];
snprintf(thestring, 1024, "%x %x%u ", type, code,
length);
- ret = non_blocking_write(fd, thestring, strlen(thestring));
+ // ret = non_blocking_write(fd, thestring, strlen(thestring));
+ ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 1",ret);
return;
}
if ((data != NULL) && (length > 0)) {
snprintf(thestring, 1024, "\n\n");
- ret = non_blocking_write(fd, thestring, strlen(thestring));
+ // ret = non_blocking_write(fd, thestring, strlen(thestring));
+ ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 2",ret);
return;
@@ -1397,7 +1455,8 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
debug(1, "Error encoding base64 data.");
// debug(1,"Remaining count: %d ret: %d, outbuf_size:
// %d.",remaining_count,ret,outbuf_size);
- ret = non_blocking_write(fd, outbuf, outbuf_size);
+ //ret = non_blocking_write(fd, outbuf, outbuf_size);
+ ret = write(fd, outbuf, outbuf_size);
if (ret < 0) {
// debug(1,"metadata_process error %d exit 3",ret);
return;
@@ -1406,27 +1465,22 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
remaining_count -= towrite_count;
}
snprintf(thestring, 1024, " ");
- ret = non_blocking_write(fd, thestring, strlen(thestring));
+ // ret = non_blocking_write(fd, thestring, strlen(thestring));
+ ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 4",ret);
return;
}
}
snprintf(thestring, 1024, " \n");
- ret = non_blocking_write(fd, thestring, strlen(thestring));
+ //ret = non_blocking_write(fd, thestring, strlen(thestring));
+ ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 5",ret);
return;
}
}
-void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) {
- debug(2, "metadata_thread_cleanup_function called");
- metadata_delete_multicast_socket();
- metadata_close();
- pc_queue_delete(&metadata_queue);
-}
-
void metadata_pack_cleanup_function(void *arg) {
// debug(1, "metadata_pack_cleanup_function called");
metadata_package *pack = (metadata_package *)arg;
@@ -1434,12 +1488,19 @@ void metadata_pack_cleanup_function(void *arg) {
msg_free(&pack->carrier); // release the message
else if (pack->data)
free(pack->data);
+ // debug(1, "metadata_pack_cleanup_function exit");
+}
+
+void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) {
+ // debug(2, "metadata_thread_cleanup_function called");
+ metadata_close();
+ pc_queue_delete(&metadata_queue);
}
void *metadata_thread_function(__attribute__((unused)) void *ignore) {
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package),
- metadata_queue_size);
+ metadata_queue_size, "pipe");
metadata_create_multicast_socket();
metadata_package pack;
pthread_cleanup_push(metadata_thread_cleanup_function, NULL);
@@ -1447,16 +1508,13 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) {
pc_queue_get_item(&metadata_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.metadata_enabled) {
+ if (pack.carrier) {
+ debug(3, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(3, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
+ }
metadata_process(pack.type, pack.code, pack.data, pack.length);
-#ifdef CONFIG_METADATA_HUB
- metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
-#endif
-
-#ifdef CONFIG_MQTT
- if (config.mqtt_enabled) {
- mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
- }
-#endif
+ debug(3, " pipe: done.");
}
pthread_cleanup_pop(1);
}
@@ -1464,22 +1522,185 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) {
pthread_exit(NULL);
}
+void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) {
+ // debug(2, "metadata_multicast_thread_cleanup_function called");
+ metadata_delete_multicast_socket();
+ pc_queue_delete(&metadata_multicast_queue);
+}
+
+void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
+ // create a pc_queue for passing information to a threaded metadata handler
+ pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items, sizeof(metadata_package),
+ metadata_multicast_queue_size, "multicast");
+ metadata_create_multicast_socket();
+ metadata_package pack;
+ pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL);
+ while (1) {
+ pc_queue_get_item(&metadata_multicast_queue, &pack);
+ pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
+ if (config.metadata_enabled) {
+ if (pack.carrier) {
+ debug(3, " multicast: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(3, " multicast: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
+ }
+ metadata_multicast_process(pack.type, pack.code, pack.data, pack.length);
+ debug(3, " multicast: done.");
+ }
+ pthread_cleanup_pop(1);
+ }
+ pthread_cleanup_pop(1); // will never happen
+ pthread_exit(NULL);
+}
+
+
+#ifdef CONFIG_METADATA_HUB
+void metadata_hub_close(void) {
+}
+
+void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) {
+ // debug(2, "metadata_hub_thread_cleanup_function called");
+ metadata_hub_close();
+ pc_queue_delete(&metadata_hub_queue);
+}
+
+void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
+
+ // create the fifo, if necessary
+ size_t pl = strlen(config.metadata_pipename) + 1;
+ char *path = malloc(pl + 1);
+ snprintf(path, pl + 1, "%s", config.metadata_pipename);
+
+ mode_t oldumask = umask(000);
+ if (mkfifo(path, 0644) && errno != EEXIST)
+ die("Could not create metadata pipe \"%s\".", path);
+ umask(oldumask);
+ debug(1, "metadata pipe name is \"%s\".", path);
+
+ // try to open it
+ fd = try_to_open_pipe_for_writing(path);
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ if ((fd == -1) && (errno != ENXIO)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ }
+ free(path);
+
+ // create a pc_queue for passing information to a threaded metadata handler
+ pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package),
+ metadata_hub_queue_size, "hub");
+ metadata_package pack;
+ pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL);
+ while (1) {
+ pc_queue_get_item(&metadata_hub_queue, &pack);
+ pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
+ if (pack.carrier) {
+ debug(3, " hub: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(3, " hub: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
+ }
+ metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
+ debug(3, " hub: done.");
+ pthread_cleanup_pop(1);
+ }
+ pthread_cleanup_pop(1); // will never happen
+ pthread_exit(NULL);
+}
+#endif
+
+#ifdef CONFIG_MQTT
+void metadata_mqtt_close(void) {
+}
+
+void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) {
+ // debug(2, "metadata_mqtt_thread_cleanup_function called");
+ metadata_mqtt_close();
+ pc_queue_delete(&metadata_mqtt_queue);
+ // debug(2, "metadata_mqtt_thread_cleanup_function done");
+}
+
+void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
+ // create a pc_queue for passing information to a threaded metadata handler
+ pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package),
+ metadata_mqtt_queue_size, "mqtt");
+ metadata_package pack;
+ pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL);
+ while (1) {
+ pc_queue_get_item(&metadata_mqtt_queue, &pack);
+ pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
+ if (config.mqtt_enabled) {
+ if (pack.carrier) {
+ debug(3, " mqtt: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(3, " mqtt: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
+ }
+ mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
+ debug(3, " mqtt: done.");
+ }
+
+ pthread_cleanup_pop(1);
+ }
+ pthread_cleanup_pop(1); // will never happen
+ pthread_exit(NULL);
+}
+#endif
+
void metadata_init(void) {
int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
if (ret)
debug(1, "Failed to create metadata thread!");
+
+ ret = pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, NULL);
+ if (ret)
+ debug(1, "Failed to create metadata multicast thread!");
+
+#ifdef CONFIG_METADATA_HUB
+ ret = pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL);
+ if (ret)
+ debug(1, "Failed to create metadata hub thread!");
+#endif
+#ifdef CONFIG_MQTT
+ ret = pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL);
+ if (ret)
+ debug(1, "Failed to create metadata mqtt thread!");
+#endif
metadata_running = 1;
}
void metadata_stop(void) {
if (metadata_running) {
debug(2, "metadata_stop called.");
+#ifdef CONFIG_MQTT
+ // debug(2, "metadata stop mqtt thread.");
+ pthread_cancel(metadata_mqtt_thread);
+ pthread_join(metadata_mqtt_thread, NULL);
+ // debug(2, "metadata stop mqtt done.");
+#endif
+#ifdef CONFIG_METADATA_HUB
+ // debug(2, "metadata stop hub thread.");
+ pthread_cancel(metadata_hub_thread);
+ pthread_join(metadata_hub_thread, NULL);
+ // debug(2, "metadata stop hub done.");
+#endif
+ // debug(2, "metadata stop multicast thread.");
+ pthread_cancel(metadata_multicast_thread);
+ pthread_join(metadata_multicast_thread, NULL);
+ // debug(2, "metadata stop multicast done.");
+
+ // debug(2, "metadata stop metadata_thread thread.");
pthread_cancel(metadata_thread);
pthread_join(metadata_thread, NULL);
+ // debug(2, "metadata_stop finished successfully.");
}
}
-int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
+int send_metadata_to_queue(pc_queue* queue, uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block) {
// parameters: type, code, pointer to data or NULL, length of data or NULL,
@@ -1496,37 +1717,64 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rts
// The reading of the parameters is a bit complex
// If the rtsp_message field is non-null, then it represents an rtsp_message
- // which should be freed
- // in the thread handler when the parameter pointed to by the pointer and
- // specified by the length
- // is finished with
- // If the rtsp_message is NULL, then if the pointer is non-null, it points to
- // a malloc'ed block
- // and should be freed when the thread is finished with it. The length of the
- // data in the block is
- // given in length
+ // and the data pointer is assumed to point to something within it.
+ // The reference counter of the rtsp_message is incremented here and
+ // should be decremented by the metadata handler when finished.
+ // If the reference count reduces to zero, the message will be freed.
+
+ // If the rtsp_message is NULL, then if the pointer is non-null then the data it
+ // points to, of the length specified, is memcpy'd and passed to the metadata
+ // handler. The handler should free it when done.
// If the rtsp_message is NULL and the pointer is also NULL, nothing further
// is done.
metadata_package pack;
pack.type = type;
pack.code = code;
- pack.data = data;
pack.length = length;
pack.carrier = carrier;
- if (pack.carrier)
+ pack.data = data;
+ if (pack.carrier) {
msg_retain(pack.carrier);
- int rc = pc_queue_add_item(&metadata_queue, &pack, block);
- if (rc == EBUSY) {
- if (pack.carrier)
+ } else {
+ if (data)
+ pack.data = memdup(data,length); // only if it's not a null
+ }
+ int rc = pc_queue_add_item(queue, &pack, block);
+ if (rc != 0) {
+ if (pack.carrier) {
+ if (rc == EWOULDBLOCK)
+ debug(2, "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %x, length %u, message %d.", queue->name, pack.type, pack.code, pack.data, pack.length, pack.carrier->index_number);
msg_free(&pack.carrier);
- else if (data)
- free(data);
- warn("Metadata queue is busy, discarding message of type 0x%08X, code 0x%08X.", type, code);
+ } else {
+ if (rc == EWOULDBLOCK)
+ debug(2, "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %x, length %u.", queue->name, pack.type, pack.code, pack.data, pack.length);
+ if (pack.data)
+ free(pack.data);
+ }
}
return rc;
}
+int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
+ int block) {
+ int rc;
+ rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
+
+#ifdef CONFIG_METADATA_HUB
+ rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
+#endif
+
+#ifdef CONFIG_MQTT
+ rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
+#endif
+
+ return rc;
+}
+
+
+
+
static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn,
rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {
@@ -2319,7 +2567,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
rtsp_conn_info *conn = pconn;
// create the watchdog mutex, initialise the watchdog time and start the watchdog thread;
- conn->watchdog_bark_time = get_absolute_time_in_fp();
+ conn->watchdog_bark_time = get_absolute_time_in_ns();
pthread_mutex_init(&conn->watchdog_mutex, NULL);
pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn);
@@ -2514,7 +2762,7 @@ static const char *format_address(struct sockaddr *fsa) {
void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- debug(1, "rtsp_listen_loop_cleanup_handler called.");
+ debug(2, "rtsp_listen_loop_cleanup_handler called.");
cancel_all_RTSP_threads();
int *sockfd = (int *)arg;
mdns_unregister();
@@ -2733,7 +2981,7 @@ void rtsp_listen_loop(void) {
} while (1);
pthread_cleanup_pop(1); // should never happen
} else {
- warn("could not establish a service on port %d -- program terminating. Is another instance of "
+ die("could not establish a service on port %d -- program terminating. Is another instance of "
"Shairport Sync running on this device?",
config.port);
}
diff --git a/scripts/shairport-sync-dbus-policy.conf b/scripts/shairport-sync-dbus-policy.conf
index 497dd885..2d42619c 100644
--- a/scripts/shairport-sync-dbus-policy.conf
+++ b/scripts/shairport-sync-dbus-policy.conf
@@ -4,11 +4,15 @@
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
-
+
+
+
+
+
diff --git a/scripts/shairport-sync-mpris-policy.conf b/scripts/shairport-sync-mpris-policy.conf
index 1efb0072..17ab847a 100644
--- a/scripts/shairport-sync-mpris-policy.conf
+++ b/scripts/shairport-sync-mpris-policy.conf
@@ -4,10 +4,13 @@
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
-
+
+
+
+
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 9c875156..efa34996 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -18,11 +18,20 @@ general =
// interpolation = "auto"; // aka "stuffing". Default is "auto". Alternatives are "basic" or "soxr". Choose "soxr" only if you have a reasonably fast processor and Shairport Sync has been built with "soxr" support.
// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one.
// mdns_backend = "avahi"; // Run "shairport-sync -h" to get a list of all mdns_backends. The default is the first one.
+// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically.
// port = 5000; // Listen for service requests on this port
// udp_port_base = 6001; // start allocating UDP ports from this port number when needed
// udp_port_range = 10; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state.
+// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
+
// drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it
// resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it
+
+// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
+// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose
+// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple.
+// If you build Shairport Sync with the flag --with-apple-alac, the Apple ALAC decoder will be chosen by default.
+
// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to.
// volume_range_db = 60 ; // use this advanced setting to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range.
// volume_max_db = 0.0 ; // use this advanced setting, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use.
@@ -36,38 +45,40 @@ general =
// The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument.
// AirPlay volume goes from 0 to -30 and -144 means "mute".
-// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
-// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
-// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose
-// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple.
-// If you build Shairport Sync with the flag --with-apple-alac, the Apple ALAC decoder will be chosen by default.
-// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically.
-
-// audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
-// audio_backend_buffer_desired_length_in_seconds = 0.2; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
+// audio_backend_latency_offset_in_seconds = 0.0; // This is added to the latency requested by the player to delay or advance the output by a fixed amount.
+// Use it, for example, to compensate for a fixed delay in the audio back end.
+// E.g. if the output device, e.g. a soundbar, takes 100 ms to process audio, set this to -0.1 to deliver the audio
+// to the output device 100 ms early, allowing it time to process the audio and output it perfectly in sync.
+// audio_backend_buffer_desired_length_in_seconds = 0.2; // If set too small, buffer underflow occurs on low-powered machines.
+// Too long and the response time to volume changes becomes annoying.
+// Default is 0.2 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
// audio_backend_buffer_interpolation_threshold_in_seconds = 0.075; // Advanced feature. If the buffer size drops below this, stop using time-consuming interpolation like soxr to avoid dropouts due to underrun.
-// audio_backend_silent_lead_in_time = 2.0; // This optional advanced setting, from 0.0 and 4.0 seconds, sets the length of the period of silence that precedes the start of the audio. The default is the latency, usually 2.0 seconds. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation.
+// audio_backend_silent_lead_in_time = "auto"; // This optional advanced setting, either "auto" or a positive number, sets the length of the period of silence that precedes the start of the audio.
+// The default is "auto" -- the silent lead-in starts as soon as the player starts sending packets.
+// Values greater than the latency are ignored. Values that are too low will affect initial synchronisation.
+
// dbus_service_bus = "system"; // The Shairport Sync dbus interface, if selected at compilation, will appear
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
// mpris_service_bus = "system"; // The Shairport Sync mpris interface, if selected at compilation, will appear
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
+
// resend_control_first_check_time = 0.10; // Use this optional advanced setting to set the wait time in seconds before deciding a packet is missing.
// resend_control_check_interval_time = 0.25; // Use this optional advanced setting to set the time in seconds between requests for a missing packet.
// resend_control_last_check_time = 0.10; // Use this optional advanced setting to set the latest time, in seconds, by which the last check should be done before the estimated time of a missing packet's transfer to the output buffer.
-//
+// missing_port_dacp_scan_interval_seconds = 2.0; // Use this optional advanced setting to set the time interval between scans for a DACP port number if no port number has been provided by the player for remote control commands
};
// Advanced parameters for controlling how Shairport Sync stays active and how it runs a session
sessioncontrol =
{
-// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
-// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
-
// "active" state starts when play begins and ends when the active_state_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed.
// run_this_before_entering_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// run_this_after_exiting_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// active_state_timeout = 10.0; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins.
+// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
+// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
+
// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument.
// Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem.
// You could hook on a program to do this automatically, but beware -- the device may then power off and restart without warning!
@@ -84,18 +95,22 @@ sessioncontrol =
// --with-alsa
alsa =
{
-// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
+// output_device = "default"; // the name of the alsa output device. Use "shairport-sync -h" to discover the names of ALSA hardware devices. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
// mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software.
// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this.
+
// output_rate = "auto"; // can be "auto", 44100, 88200, 176400 or 352800, but the device must have the capability.
// output_format = "auto"; // can be "auto", "U8", "S8", "S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE" or "S32_BE" but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
-// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no".
+
+// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no" This is really meant for troubleshootingG.
+
// period_size = ; // Use this optional advanced setting to set the alsa period size near to this value
// buffer_size = ; // Use this optional advanced setting to set the alsa buffer size near to this value
// use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes"
// use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players.
// maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms.
// use_precision_timing = "auto"; // Use this optional advanced setting to control how Shairport Sync gathers timing information. When set to "auto", if the output device is a real hardware device, precision timing will be used. Choose "no" for more compatible standard timing, choose "yes" to force the use of precision timing, which may cause problems.
+
// disable_standby_mode = "never"; // This setting prevents the DAC from entering the standby mode. Some DACs make small "popping" noises when they go in and out of standby mode. Settings can be: "always", "auto" or "never". Default is "never", but only for backwards compatibility. The "auto" setting prevents entry to standby mode while Shairport Sync is in the "active" mode. You can use "yes" instead of "always" and "no" instead of "never".
// disable_standby_mode_silence_threshold = 0.040; // Use this optional advanced setting to control how little audio should remain in the output buffer before the disable_standby code should start sending silence to the output device.
// disable_standby_mode_silence_scan_interval = 0.004; // Use this optional advanced setting to control how often the amount of audio remaining in the output buffer should be checked.
@@ -118,6 +133,7 @@ sndio =
// --with-pa
pa =
{
+// server = "host"; // Set this to override the default pulseaudio server that should be used.
// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active.
};
@@ -229,6 +245,7 @@ mqtt =
diagnostics =
{
// disable_resend_requests = "no"; // set this to yes to stop Shairport Sync from requesting the retransmission of missing packets. Default is "no".
+// log_output_to = "syslog"; // set this to "syslog" (default), "stderr" or "stdout" or a file or pipe path to specify were all logs, statistics and diagnostic messages are written to. If there's anything wrong with the file spec, output will be to "stderr".
// statistics = "no"; // set to "yes" to print statistics in the log
// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose.
// log_show_file_and_line = "yes"; // set this to yes if you want the file and line number of the message source in the log file
diff --git a/shairport-sync-dbus-test-client.c b/shairport-sync-dbus-test-client.c
index d0b15bb2..2433fd69 100644
--- a/shairport-sync-dbus-test-client.c
+++ b/shairport-sync-dbus-test-client.c
@@ -186,11 +186,10 @@ int main(int argc, char *argv[]) {
g_signal_connect(proxy4, "notify::volume", G_CALLBACK(notify_volume_callback),
"ShairportSync.AdvancedRemoteControl");
-
-
g_print("Starting test...\n");
- g_print("Using the RemoteControl interface, play for five seconds, pause for five seconds and then resume play...\n");
+ g_print("Using the RemoteControl interface, play for five seconds, pause for five seconds and "
+ "then resume play...\n");
g_print("Play...\n");
shairport_sync_remote_control_call_play(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
sleep(5);
@@ -200,24 +199,31 @@ int main(int argc, char *argv[]) {
g_print("Play...\n");
shairport_sync_remote_control_call_play(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, 0);
sleep(5);
- g_print("Using the RemoteControl interface, set AirPlay Volume (range -30 to 0) to -30, -20, -10, 0 and -15 for five seconds each...\n");
+ g_print("Using the RemoteControl interface, set AirPlay Volume (range -30 to 0) to -30, -20, "
+ "-10, 0 and -15 for five seconds each...\n");
g_print("Set AirPlay Volume (range -30 to 0) to -30\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -30, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -30,
+ NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -20\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -20, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -20,
+ NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -10\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -10, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -10,
+ NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -0\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), 0, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), 0,
+ NULL, NULL, 0);
sleep(5);
g_print("Set AirPlay Volume (range -30 to 0) to -15\n");
- shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -15, NULL, NULL, 0);
+ shairport_sync_remote_control_call_set_airplay_volume(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), -15,
+ NULL, NULL, 0);
sleep(5);
- g_print("Using the AdvancedRemoteControl interface, set Volume to 20%%, 100%%, 40%% and 60%% for five seconds each...\n");
+ g_print("Using the AdvancedRemoteControl interface, set Volume to 20%%, 100%%, 40%% and 60%% for "
+ "five seconds each...\n");
g_print("Set Volume to 20%%\n");
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 20, NULL, NULL, 0);
diff --git a/shairport.c b/shairport.c
index 5e375916..14e50725 100644
--- a/shairport.c
+++ b/shairport.c
@@ -143,10 +143,11 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
int i;
int number_of_iterations = 0;
- uint64_t soxr_start_time = get_absolute_time_in_fp();
+ uint64_t soxr_start_time = get_absolute_time_in_ns();
uint64_t loop_until_time =
- (uint64_t)0x180000000 + soxr_start_time; // loop for a second and a half, max -- no need to be able to cancel it, do _don't even try_!
- while (get_absolute_time_in_fp() < loop_until_time) {
+ (uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be
+ // able to cancel it, do _don't even try_!
+ while (get_absolute_time_in_ns() < loop_until_time) {
number_of_iterations++;
for (i = 0; i < buffer_length; i++) {
@@ -184,11 +185,10 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
NULL, NULL); // Default configuration.
}
- double soxr_execution_time_us =
- (((get_absolute_time_in_fp() - soxr_start_time) * 1000000) >> 32) * 1.0;
+ double soxr_execution_time_ns = (get_absolute_time_in_ns() - soxr_start_time) * 1.0;
// free(outbuffer);
// free(inbuffer);
- config.soxr_delay_index = (int)(0.9 + soxr_execution_time_us / (number_of_iterations * 1000));
+ config.soxr_delay_index = (int)(0.9 + soxr_execution_time_ns / (number_of_iterations * 1000000));
debug(2, "soxr_delay_index: %d.", config.soxr_delay_index);
if ((config.packet_stuffing == ST_soxr) &&
(config.soxr_delay_index > config.soxr_delay_threshold))
@@ -268,7 +268,7 @@ void usage(char *progname) {
printf(" The default is /tmp/shairport-sync-metadata.\n");
printf(" --get-coverart send cover art through the metadata pipe.\n");
#endif
- printf(" -u, --use-stderr log messages through STDERR rather than syslog.\n");
+ printf(" -u, --use-stderr log messages through STDERR rather than the system log.\n");
printf("\n");
mdns_ls_backends();
printf("\n");
@@ -388,7 +388,8 @@ int parse_options(int argc, char **argv) {
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
- config.audio_backend_silent_lead_in_time = -1.0; // flag to indicate it has not been set
+ config.audio_backend_silent_lead_in_time_auto =
+ 1; // start outputting silence as soon as packets start arriving
config.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if
// nothing else comes in first.
config.fixedLatencyOffset = 11025; // this sounds like it works properly.
@@ -399,12 +400,23 @@ int parse_options(int argc, char **argv) {
// automatically.
config.volume_range_hw_priority =
0; // if combining software and hardware volume control, give the software priority
-// i.e. when reducing volume, reduce the sw first before reducing the software.
-// this is because some hw mixers mute at the bottom of their range, and they don't always advertise
-// this fact
- config.resend_control_first_check_time = 0.10; // wait this many seconds before requesting the resending of a missing packet
- config.resend_control_check_interval_time = 0.25; // wait this many seconds before again requesting the resending of a missing packet
- config.resend_control_last_check_time = 0.10; // give up if the packet is still missing this close to when it's needed
+ // i.e. when reducing volume, reduce the sw first before reducing the software.
+ // this is because some hw mixers mute at the bottom of their range, and they don't always
+ // advertise this fact
+ config.resend_control_first_check_time =
+ 0.10; // wait this many seconds before requesting the resending of a missing packet
+ config.resend_control_check_interval_time =
+ 0.25; // wait this many seconds before again requesting the resending of a missing packet
+ config.resend_control_last_check_time =
+ 0.10; // give up if the packet is still missing this close to when it's needed
+ config.missing_port_dacp_scan_interval_seconds =
+ 2.0; // check at this interval if no DACP port number is known
+
+ config.minimum_free_buffer_headroom = 125; // leave approximately one second's worth of buffers
+ // free after calculating the effective latency.
+ // e.g. if we have 1024 buffers or 352 frames = 8.17 seconds and we have a nominal latency of 2.0
+ // seconds then we can add an offset of 5.17 seconds and still leave a second's worth of buffers
+ // for unexpected circumstances
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
@@ -414,9 +426,10 @@ int parse_options(int argc, char **argv) {
1; // number of seconds between DACP server scans when playing nothing
config.scan_max_bad_response_count =
5; // number of successive bad results to ignore before giving up
- //config.scan_max_inactive_count =
- // (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before stopping if
- // not made active again (not used)
+ // config.scan_max_inactive_count =
+ // (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before
+ // stopping if
+ // not made active again (not used)
#endif
// config_setting_t *setting;
@@ -430,9 +443,9 @@ int parse_options(int argc, char **argv) {
char *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);
+ 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);
+ 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);
@@ -649,6 +662,20 @@ int parse_options(int argc, char **argv) {
dvalue);
}
+ /* Get the diagnostics output default. */
+ if (config_lookup_string(config.cfg, "diagnostics.log_output_to", &str)) {
+ if (strcasecmp(str, "syslog") == 0)
+ log_to_syslog();
+ else if (strcasecmp(str, "stdout") == 0) {
+ log_to_stdout();
+ } else if (strcasecmp(str, "stderr") == 0) {
+ log_to_stderr();
+ } else {
+ config.log_file_path = (char *)str;
+ config.log_fd = -1;
+ log_to_file();
+ }
+ }
/* Get the ignore_volume_control setting. */
if (config_lookup_string(config.cfg, "general.ignore_volume_control", &str)) {
if (strcasecmp(str, "no") == 0)
@@ -746,9 +773,9 @@ int parse_options(int argc, char **argv) {
* range set by the mixer. */
if (config_lookup_int(config.cfg, "general.volume_range_db", &value)) {
if ((value < 30) || (value > 150))
- die("Invalid volume range \"%sd\". It should be between 30 and 150 dB. Zero means use "
- "the mixer's native range",
- value);
+ die("Invalid volume range %d dB. It should be between 30 and 150 dB. Zero means use "
+ "the mixer's native range. The setting reamins at %d.",
+ value, config.volume_range_db);
else
config.volume_range_db = value;
}
@@ -767,42 +794,50 @@ int parse_options(int argc, char **argv) {
die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"");
}
-
/* Get the resend control settings. */
- if (config_lookup_float(config.cfg, "general.resend_control_first_check_time",
- &dvalue)) {
+ if (config_lookup_float(config.cfg, "general.resend_control_first_check_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_first_check_time = dvalue;
else
- warn("Invalid general resend_control_first_check_time setting \"%d\". It should "
- "be "
- "between 0.0 and 3.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.resend_control_first_check_time);
+ warn("Invalid general resend_control_first_check_time setting \"%f\". It should "
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_first_check_time);
}
- if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time",
- &dvalue)) {
+ if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_check_interval_time = dvalue;
else
- warn("Invalid general resend_control_check_interval_time setting \"%d\". It should "
- "be "
- "between 0.0 and 3.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.resend_control_check_interval_time);
+ warn("Invalid general resend_control_check_interval_time setting \"%f\". It should "
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_check_interval_time);
}
- if (config_lookup_float(config.cfg, "general.resend_control_last_check_time",
- &dvalue)) {
+ if (config_lookup_float(config.cfg, "general.resend_control_last_check_time", &dvalue)) {
if ((dvalue >= 0.0) && (dvalue <= 3.0))
config.resend_control_last_check_time = dvalue;
else
- warn("Invalid general resend_control_last_check_time setting \"%d\". It should "
- "be "
- "between 0.0 and 3.0, "
- "inclusive. The setting remains at %f seconds.",
- dvalue, config.resend_control_last_check_time);
+ warn("Invalid general resend_control_last_check_time setting \"%f\". It should "
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_last_check_time);
+ }
+
+ if (config_lookup_float(config.cfg, "general.missing_port_dacp_scan_interval_seconds",
+ &dvalue)) {
+ if ((dvalue >= 0.0) && (dvalue <= 300.0))
+ config.missing_port_dacp_scan_interval_seconds = dvalue;
+ else
+ warn("Invalid general missing_port_dacp_scan_interval_seconds setting \"%f\". It should "
+ "be "
+ "between 0.0 and 300.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.missing_port_dacp_scan_interval_seconds);
}
/* Get the default latency. Deprecated! */
@@ -963,7 +998,8 @@ int parse_options(int argc, char **argv) {
if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) {
config.convolution_ir_file = strdup(str);
- config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ config.convolver_valid =
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
}
if (config.convolution && config.convolution_ir_file == NULL) {
@@ -1159,7 +1195,7 @@ int parse_options(int argc, char **argv) {
poptFreeContext(optCon);
-// here, we are finally finished reading the options
+ // here, we are finally finished reading the options
#ifdef CONFIG_LIBDAEMON
if ((daemonisewith) && (daemonisewithout))
@@ -1173,8 +1209,9 @@ int parse_options(int argc, char **argv) {
#else
/* Check if we are called with -d or --daemon or -j or justDaemoniseNoPIDFile options*/
if ((daemonisewith != 0) || (daemonisewithout != 0)) {
- fprintf(stderr, "%s was built without libdaemon, so does not support daemonisation using the "
- "-d, --daemon, -j or --justDaemoniseNoPIDFile options\n",
+ fprintf(stderr,
+ "%s was built without libdaemon, so does not support daemonisation using the "
+ "-d, --daemon, -j or --justDaemoniseNoPIDFile options\n",
config.appName);
exit(EXIT_FAILURE);
}
@@ -1197,8 +1234,6 @@ int parse_options(int argc, char **argv) {
char hostname[100];
gethostname(hostname, 100);
-
-
char *i0;
if (raw_service_name == NULL)
i0 = strdup("%H"); // this is the default it the Service Name wasn't specified
@@ -1269,99 +1304,115 @@ const char *pid_file_proc(void) {
}
#endif
-
void exit_function() {
-// the following is to ensure that if libdaemon has been included
-// that most of this code will be skipped when the parent process is exiting
-// exec
+ if (emergency_exit == 0) {
+ // the following is to ensure that if libdaemon has been included
+ // that most of this code will be skipped when the parent process is exiting
+ // exec
#ifdef CONFIG_LIBDAEMON
- if (this_is_the_daemon_process) { //this is the daemon that is exiting
+ if ((this_is_the_daemon_process) || (config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not actually deamonised at all
#endif
- debug(1, "exit function called...");
-
-/*
-Actually, there is no terminate_mqtt() function.
-#ifdef CONFIG_MQTT
- if (config.mqtt_enabled) {
- terminate_mqtt();
- }
-#endif
-*/
+ debug(2, "exit function called...");
+ /*
+ Actually, there is no terminate_mqtt() function.
+ #ifdef CONFIG_MQTT
+ if (config.mqtt_enabled) {
+ terminate_mqtt();
+ }
+ #endif
+ */
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
-
-/*
-Actually, there is no stop_mpris_service() function.
-#ifdef CONFIG_MPRIS_INTERFACE
- stop_mpris_service();
-#endif
-*/
+ /*
+ Actually, there is no stop_mpris_service() function.
+ #ifdef CONFIG_MPRIS_INTERFACE
+ stop_mpris_service();
+ #endif
+ */
#ifdef CONFIG_DBUS_INTERFACE
- stop_dbus_service();
+ stop_dbus_service();
#endif
- if (g_main_loop) {
- debug(2, "Stopping DBUS Loop Thread");
- g_main_loop_quit(g_main_loop);
- pthread_join(dbus_thread, NULL);
- }
+ if (g_main_loop) {
+ debug(2, "Stopping DBUS Loop Thread");
+ g_main_loop_quit(g_main_loop);
+ pthread_join(dbus_thread, NULL);
+ }
#endif
#ifdef CONFIG_DACP_CLIENT
- debug(2, "Stopping DACP Monitor");
- dacp_monitor_stop();
+ debug(2, "Stopping DACP Monitor");
+ dacp_monitor_stop();
#endif
#ifdef CONFIG_METADATA_HUB
- debug(2, "Stopping metadata hub");
- metadata_hub_stop();
+ debug(2, "Stopping metadata hub");
+ metadata_hub_stop();
#endif
#ifdef CONFIG_METADATA
- metadata_stop(); // close down the metadata pipe
+ metadata_stop(); // close down the metadata pipe
#endif
- activity_monitor_stop(0);
+ activity_monitor_stop(0);
- if ((config.output) && (config.output->deinit)) {
- debug(2, "Deinitialise the audio backend.");
- config.output->deinit();
- }
+ if ((config.output) && (config.output->deinit)) {
+ debug(2, "Deinitialise the audio backend.");
+ config.output->deinit();
+ }
#ifdef CONFIG_SOXR
- // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down
- pthread_join(soxr_time_check_thread, NULL);
+ // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down
+ pthread_join(soxr_time_check_thread, NULL);
#endif
+ if (conns)
+ free(conns); // make sure the connections have been deleted first
- if (conns)
- free(conns); // make sure the connections have been deleted first
-
- if (config.service_name)
- free(config.service_name);
+ if (config.service_name)
+ free(config.service_name);
#ifdef CONFIG_CONVOLUTION
- if (config.convolution_ir_file)
- free(config.convolution_ir_file);
+ if (config.convolution_ir_file)
+ free(config.convolution_ir_file);
#endif
- if (config.regtype)
- free(config.regtype);
+ if (config.regtype)
+ free(config.regtype);
#ifdef CONFIG_LIBDAEMON
- daemon_retval_send(0);
- daemon_pid_file_remove();
- daemon_signal_done();
- if (config.computed_piddir)
- free(config.computed_piddir);
- }
+ if (this_is_the_daemon_process) {
+ daemon_retval_send(0);
+ daemon_pid_file_remove();
+ daemon_signal_done();
+ if (config.computed_piddir)
+ free(config.computed_piddir);
+ }
+ }
#endif
- if (config.cfg)
- config_destroy(config.cfg);
- if (config.appName)
- free(config.appName);
- // probably should be freeing malloc'ed memory here, including strdup-created strings...
+ if (config.cfg)
+ config_destroy(config.cfg);
+ if (config.appName)
+ free(config.appName);
+ // probably should be freeing malloc'ed memory here, including strdup-created strings...
+
+
+#ifdef CONFIG_LIBDAEMON
+ if (this_is_the_daemon_process) { // this is the daemon that is exiting
+ debug(1,"libdaemon daemon exit");
+ } else {
+ if (config.daemonise)
+ debug(1,"libdaemon parent exit");
+ else
+ debug(1,"exit");
+ }
+#else
+ debug(1,"exit");
+#endif
+ } else {
+ debug(1,"emergency exit");
+ }
}
// for removing zombie script processes
@@ -1370,10 +1421,17 @@ Actually, there is no stop_mpris_service() function.
void handle_sigchld(__attribute__((unused)) int sig) {
int saved_errno = errno;
- while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}
+ while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {
+ }
errno = saved_errno;
}
+void main_thread_cleanup_handler(__attribute__((unused)) void *arg) {
+ debug(2,"main thread cleanup handler called");
+ exit(EXIT_SUCCESS);
+}
+
+
int main(int argc, char **argv) {
/* Check if we are called with -V or --version parameter */
if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
@@ -1390,11 +1448,12 @@ int main(int argc, char **argv) {
#ifdef CONFIG_LIBDAEMON
pid = getpid();
#endif
+ config.log_fd = -1;
conns = NULL; // no connections active
memset((void *)&main_thread_id, 0, sizeof(main_thread_id));
memset(&config, 0, sizeof(config)); // also clears all strings, BTW
- fp_time_at_startup = get_absolute_time_in_fp();
- fp_time_at_last_debug_message = fp_time_at_startup;
+ ns_time_at_startup = get_absolute_time_in_ns();
+ ns_time_at_last_debug_message = ns_time_at_startup;
// this is a bit weird, but necessary -- basename() may modify the argument passed in
char *basec = strdup(argv[0]);
char *bname = basename(basec);
@@ -1410,6 +1469,7 @@ int main(int argc, char **argv) {
setlogmask(LOG_UPTO(LOG_DEBUG));
openlog(NULL, 0, LOG_DAEMON);
#endif
+ emergency_exit = 0; // what to do or skip in the exit_function
atexit(exit_function);
// set defaults
@@ -1449,7 +1509,7 @@ int main(int argc, char **argv) {
// config.userSuppliedLatency = 0; // zero means none supplied
config.debugger_show_file_and_line =
- 1; // by default, log the file and line of the originating message
+ 1; // by default, log the file and line of the originating message
config.debugger_show_relative_time =
1; // by default, log the time back to the previous debug message
config.resyncthreshold = 0.05; // 50 ms
@@ -1472,7 +1532,7 @@ int main(int argc, char **argv) {
// snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname);
set_requested_connection_state_to_output(
1); // we expect to be able to connect to the output device
- config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds.
+ config.audio_backend_buffer_desired_length = 0.15; // seconds
config.udp_port_base = 6001;
config.udp_port_range = 10;
config.output_format = SPS_FORMAT_S16_LE; // default
@@ -1527,10 +1587,17 @@ int main(int argc, char **argv) {
/* Kill daemon with SIGTERM */
/* 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)
- daemon_log(LOG_WARNING, "Failed to kill daemon: %s", strerror(errno));
- else {
- // debug(1,"Successfully killed the shairport sync daemon.");
+ 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);
+ 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 deamon, but cannot remove old PID file: \"%s\", errno %u.", config.appName, strerror(errno), errno);
}
return ret < 0 ? 1 : 0;
#else
@@ -1543,7 +1610,7 @@ 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, "Daemon already running on PID file %u", pid);
+ daemon_log(LOG_ERR, "The %s daemon is already running as PID %u", config.appName, pid);
return 1;
}
@@ -1578,16 +1645,16 @@ int main(int argc, char **argv) {
break;
case 1:
daemon_log(LOG_ERR,
- "daemon failed to launch: could not close open file descriptors after forking.");
+ "the %s daemon failed to launch: could not close open file descriptors after forking.", config.appName);
break;
case 2:
- daemon_log(LOG_ERR, "daemon failed to launch: could not create PID file.");
+ daemon_log(LOG_ERR, "the %s daemon failed to launch: could not create PID file.", config.appName);
break;
case 3:
- daemon_log(LOG_ERR, "daemon failed to launch: could not create or access PID directory.");
+ daemon_log(LOG_ERR, "the %s daemon failed to launch: could not create or access PID directory.", config.appName);
break;
default:
- daemon_log(LOG_ERR, "daemon failed to launch, error %i.", ret);
+ daemon_log(LOG_ERR, "the %s daemon failed to launch, error %i.", config.appName, ret);
}
return ret;
} else { /* pid == 0 means we are the daemon */
@@ -1635,6 +1702,9 @@ int main(int argc, char **argv) {
#endif
debug(1, "Started!");
+ // stop a pipe signal from killing the program
+ signal(SIGPIPE, SIG_IGN);
+
// install a zombie process reaper
// see: http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
struct sigaction sa;
@@ -1672,7 +1742,7 @@ int main(int argc, char **argv) {
}
config.output->init(argc - audio_arg, argv + audio_arg);
- // pthread_cleanup_push(main_cleanup_handler, NULL);
+ pthread_cleanup_push(main_thread_cleanup_handler, NULL);
// daemon_log(LOG_NOTICE, "startup");
@@ -1712,8 +1782,9 @@ int main(int argc, char **argv) {
/* Print out options */
debug(1, "disable resend requests is %s.", config.disable_resend_requests ? "on" : "off");
- debug(1, "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped "
- "deliberately.",
+ debug(1,
+ "diagnostic_drop_packet_fraction is %f. A value of 0.0 means no packets will be dropped "
+ "deliberately.",
config.diagnostic_drop_packet_fraction);
debug(1, "statistics_requester status is %d.", config.statistics_requested);
#if CONFIG_LIBDAEMON
@@ -1736,8 +1807,8 @@ int main(int argc, char **argv) {
debug(1, "mdns backend \"%s\".", config.mdns_name);
debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency);
debug(1, "interpolation setting is \"%s\".",
- config.packet_stuffing == ST_basic ? "basic" : config.packet_stuffing == ST_soxr ? "soxr"
- : "auto");
+ config.packet_stuffing == ST_basic ? "basic"
+ : config.packet_stuffing == ST_soxr ? "soxr" : "auto");
debug(1, "interpolation soxr_delay_threshold is %d.", config.soxr_delay_threshold);
debug(1, "resync time is %f seconds.", config.resyncthreshold);
debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption);
@@ -1751,8 +1822,9 @@ int main(int argc, char **argv) {
debug(1, "volume_max_db is not set");
debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.",
config.volume_range_db);
- debug(1, "volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used "
- "first) is %d.",
+ debug(1,
+ "volume_range_combined_hardware_priority (1 means hardware mixer attenuation is used "
+ "first) is %d.",
config.volume_range_hw_priority);
debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).",
config.playback_mode);
@@ -1771,8 +1843,11 @@ int main(int argc, char **argv) {
debug(1, "audio_backend_buffer_interpolation_threshold_in_seconds is %f seconds.",
config.audio_backend_buffer_interpolation_threshold_in_seconds);
debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset);
- debug(1, "audio backend silence lead-in time is %f seconds. A value -1.0 means use the default.",
- config.audio_backend_silent_lead_in_time);
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ debug(1, "audio backend silence lead-in time is \"auto\".");
+ else
+ debug(1, "audio backend silence lead-in time is %f seconds.",
+ config.audio_backend_silent_lead_in_time);
debug(1, "zeroconf regtype is \"%s\".", config.regtype);
debug(1, "decoders_supported field is %d.", config.decoders_supported);
debug(1, "use_apple_decoder is %d.", config.use_apple_decoder);
@@ -1884,5 +1959,6 @@ int main(int argc, char **argv) {
activity_monitor_start();
rtsp_listen_loop();
+ pthread_cleanup_pop(1);
return 0;
}
diff --git a/tinysvcmdns.c b/tinysvcmdns.c
index 069a6436..9e605e07 100644
--- a/tinysvcmdns.c
+++ b/tinysvcmdns.c
@@ -1466,7 +1466,7 @@ int close_pipe(int s) {
// main loop to receive, process and send out MDNS replies
// also handles MDNS service announces
-void* main_loop(struct mdnsd *svr) {
+void *main_loop(struct mdnsd *svr) {
fd_set sockfd_set;
int max_fd = svr->sockfd;
char notify_buf[2]; // buffer for reading of notify_pipe
@@ -1724,7 +1724,7 @@ struct mdnsd *mdnsd_start() {
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- if (pthread_create(&tid, &attr, (void * (*)(void *))&main_loop, (void *)server) != 0) {
+ if (pthread_create(&tid, &attr, (void *(*)(void *)) & main_loop, (void *)server) != 0) {
pthread_mutex_destroy(&server->data_lock);
free(server);
return NULL;
@@ -1737,7 +1737,8 @@ void mdnsd_stop(struct mdnsd *s) {
assert(s != NULL);
struct timeval tv = {
- .tv_sec = 0, .tv_usec = 500 * 1000,
+ .tv_sec = 0,
+ .tv_usec = 500 * 1000,
};
s->stop_flag = 1;
From 2519f600dc1b4b1ebd2c8b044fd5038a94ba5fb3 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Jul 2020 17:10:04 +0100
Subject: [PATCH 028/180] 3.3.7rc0
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 3bbd67bf..3dbccf6e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.6], [mikebradydublin@icloud.com])
+AC_INIT([shairport-sync], [3.3.7rc0], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 4e7ce7ebe7edd12c3b9e9789e66d88861cfdd704 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Jul 2020 17:24:15 +0100
Subject: [PATCH 029/180] Update to 3.3.7.
---
shairport-sync.spec | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/shairport-sync.spec b/shairport-sync.spec
index ed467df2..2f9800f9 100644
--- a/shairport-sync.spec
+++ b/shairport-sync.spec
@@ -1,5 +1,5 @@
Name: shairport-sync
-Version: 3.3.6
+Version: 3.3.7
Release: 1%{?dist}
Summary: AirTunes emulator. Multi-Room with Audio Synchronisation
# MIT licensed except for tinysvcmdns under BSD,
@@ -66,6 +66,8 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
%license LICENSES
%changelog
+* Tue Jul 7 2020 Mike Brady
Date: Mon, 6 Jul 2020 17:27:50 +0100
Subject: [PATCH 030/180] Update RELEASENOTES.md
---
RELEASENOTES.md | 43 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 3bb0a374..7be271ad 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -1,8 +1,49 @@
Please see the [Release Notes for 3.3](https://github.com/mikebrady/shairport-sync/releases/tag/3.3).
-**Enhancements**
+Version 3.3.7
+====
+**Docker Integration**
* Docker Hub integration. From now on, prebuilt Docker images should be available on the Docker Hub at https://hub.docker.com/r/mikebrady/shairport-sync.
+**New Feature**
+* For the PulseAudio backend `pa`, added a new `server` entry to the `pa` section of the configuration file, allowing you to specify a connection to a remote or a local system PulseAudio instance instead of letting PulseAudio choose. Thanks to [Guillaume Revaillot](https://github.com/grevaillot) for this new feature.
+
+**Pesky Changes**
+* The underlying timing system has been moved from 64-bit fixed-point time representation (like NTP) to a 64-bit unsigned nanoseconds representation. This should make precisely no difference to the functionality of Shairport Sync but the transition might inadvertently have introduced bugs. Problem reports gratefully received.
+
+**Bug Fixes**
+* Fixed a number of bugs that prevented Shairport Sync from terminating cleanly in response to the `MPRIS` interface's `Quit()` command. Thanks to [João Gabriel](https://github.com/jgabriel98) for reporting this issue.
+* Related to the above, the code used to terminate the application after a fatal error has been cleaned up. It now uses the correct `exit()` call rather than the rather hacky `abort()` call, returning the value of the constant `EXIT_FAILURE` (typically `1`) to the caller .
+* Fixed a bug whereby the start and end of active mode tokens `abeg` and `aend` where not generated or published -- `pend` tokens were being generated instead. Thanks to [minix1234](https://github.com/minix1234) for the bug report and [fix](https://github.com/mikebrady/shairport-sync/pull/1023).
+* Fixed a bug calculating the instantaneous synchronisation error. This bug could occasionally cause Shairport Sync to lose synchronisation and maybe even to mute for a few seconds before resynchronising. It was caused doing modulo arithmetic incorrectly and it's been there for a while.
+* Removed a bug which would affect initial synchronisation if a `FLUSH` command was received from the player at an inopportune time.
+* Added code to do calculations involving the `audio_backend_latency_offset_in_seconds` and `audio_backend_silent_lead_in_time` settings correctly. Many thanks to [Tucker Kern](https://github.com/mill1000) for discovering a number of bugs associated with this and for [proposing a solution](https://github.com/mikebrady/shairport-sync/pull/1001). This prompted a closer investigation and a number of further improvements were made, and a few "hostages to fortune" removed.
+* The DACP ID looks like a 64-bit number expressed in hexadecimal. It is normally quoted with leading zeroes removed, but in the `_dacp._tcp` service string, leading zeros are not removed from the DACP ID. The bug fix removes those leading zeroes. Thanks to [julianc1969](https://github.com/juliandc1969) for tracking down this bug so tenaciously!
+
+**Enhancements**
+* Added the the string `-alac` to the the version string in '-V' if Apple ALAC decoder support is included.
+* Cleaned up and simplified the code that handles `FLUSH` requests coming from the player. (Debug messages are still a little verbose.)
+* Improved timing estimation. Shairport Sync has been using linear least-squares regression to estimate timing drift between the (remote) source clock and the local clock. This technique is now extended to provide an estimate of the remote-to-local clock difference itself. (The remote-to-local clock difference is used to remap the timing of audio frames from the remote device's clock to the local clock.)
+
+ Timing drift estimates are now saved when a sessions ends, keyed to the client IP number. When a new session starts from that IP number, the stored estimate is used until a new estimate can be generated from the new session.
+
+ In practice, the timing techniques in use up to now have been very accurate, but this should result in slightly smoother rates of correction.
+* Tidied up the creation and initial opening of pipes. Suppress repeated pipe-opening error messages.
+* Tidied up warnings and fatal error messages when log verbosity is zero.
+* Cleaned up the code that provides a silent lead-in to play on a back end without synchronisation, e.g. a pipe or `stdout`.
+* Added in commented-out code to check the timeliness of the release of audio to a back end without synchronisation, e.g. a pipe or `stdout`. TL;DR – so long as the back end does not block, frames will be released to it not more than one packet (352 frames) late.
+* Logs and statistics can now be directed to the system log (default), `stdout`, `stderr` or to a file or pipe of your choice using a new setting, `log_output_to` in the `diagnostics` section of the configuration file. This is very useful when the system log is disabled or diverted.
+* Audio data from the `pipe` back end and metadata from the metadata pipe are now written using standard blocking `write` commands rather than a slightly complex non-blocking write function. Pipes are now opened in non-blocking mode and changed to blocking mode when successfully opened.
+* Separate threads are now used for each metadata subsystem. Until now, all metadata was processed on a single thread. This included writing to the metadata pipe and the multicast stream and supplying metadata for the `mqtt` interface and for the `dbus` and `MPRIS` interfaces. Unfortunately, that meant that a problem with any one of these subsystems could propagate into the others. Now they all run on separate threads. If one thread blocks, it will not interfere with the other subsystems.
+* Cleaned up and improved the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchronisation, usually to under a millisecond, and often to within 20 or 30 microseconds. Syncronisation should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as much as -1.7 seconds, i.e. when only 0.3 seconds of latency are left when the latency would normally be 2.0 seconds.
+* Cleaned up some confused uses of modulo arithmetic.
+* Cleaned up the allocation of memory for gathering running statistics – the heap is now used instead of the stack.
+* Cleaned up the display of statistics for backends that do not implement active synchronisation, e.g. the `pipe` and `STDOUT` back ends.
+* Cleaned up the `audio_backend_silent_lead_in_time` setting by adding an `"auto"` setting.
+* Improved synchronisation accuracy with short silence lead-ins.
+* While a player is active, the DACP port number to which to send remote commands should be broadcast over ZEROCONF/Bonjour. However, if that information is not available, Shairport Sync will now check for it every two seconds.
+* The timing software in the `sndio` backend does some extra sanity checking on certain time estimates, it may help a little when running on virtual machines.
+
Version 3.3.6
====
**New Features**
From 492d81d7bdef082f4886ca08b430bc9c87c91552 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 10 Jul 2020 09:51:56 +0100
Subject: [PATCH 031/180] Extra check for leading zeros in the DACP ID received
in an RTSP packet
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 3dbccf6e..3d8b6e94 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.7rc0], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [3.3.7rc1], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 153c88a357cb9f7c84cc21c03b84fdae0e436fb9 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 28 Jul 2020 19:17:38 +0100
Subject: [PATCH 032/180] Update TROUBLESHOOTING.md
---
TROUBLESHOOTING.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index 966c0c71..93494ab4 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -54,6 +54,10 @@ sudo chmod +x /etc/network/if-up.d/off-power-manager
There are some more details in some the closed issues on this repository.
+### VPNs
+
+To see the AirPlay service Shairport Sync provides, your devices must be on the same subnet as Shairport Sync. If, say, a device such as an iPhone is on a VPN and Shairport Sync is not, or vice-versa, then even though the devices might be physically on the same network, they are effectively on separate networks due to the VPN and the AirPlay service will not be accessible to the device. So, when you are troubleshooting, look out for VPN issues.
+
### Faulty WiFi
For an example of what it can take to track down a bad WiFi situation – in this case, a faulty WiFi adapter – please look at [this report](https://github.com/mikebrady/shairport-sync/issues/689).
From 27faeaf69667e9625e3e7c8ac67fb1f88f3368cb Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 21 Aug 2020 10:43:09 +0100
Subject: [PATCH 033/180] Squashed commit of the following:
Use a gentler flush process for resyncing.
Potentially fix a flaw in resyncing. The problem was that a flush is set up but not triggered.
Open metadata and audio pipes with 666 permissions for easier integration with other audio sources to give them permission to write to the audio pipe later.
Ensure metadata and cover art are enabled if metadata support is included at compilation.
Reword some misleading error messages.
Set convolution defaults even if no configuration file is found.
Quieten a few debug messages.
---
audio_pipe.c | 2 +-
configure.ac | 2 +-
dacp.c | 8 ++--
metadata_hub.c | 4 +-
player.c | 118 ++++++++++++++++++++++++-------------------------
rtsp.c | 2 +-
shairport.c | 19 +++++---
7 files changed, 82 insertions(+), 73 deletions(-)
diff --git a/audio_pipe.c b/audio_pipe.c
index 6dedc464..45e0f060 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -121,7 +121,7 @@ static int init(int argc, char **argv) {
// here, create the pipe
mode_t oldumask = umask(000);
- if (mkfifo(pipename, 0644) && errno != EEXIST)
+ if (mkfifo(pipename, 0666) && errno != EEXIST)
die("Could not create audio pipe \"%s\"", pipename);
umask(oldumask);
diff --git a/configure.ac b/configure.ac
index 3d8b6e94..7f253699 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.7rc1], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [3.3.7rc2], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
diff --git a/dacp.c b/dacp.c
index c6b9968f..a25bf9e4 100644
--- a/dacp.c
+++ b/dacp.c
@@ -154,7 +154,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"dacp_send_command: command is: \"%s\".",command);
if (dacp_server.port == 0) {
- debug(3, "No DACP port specified yet");
+ // debug(3, "No DACP port specified yet");
result = 490; // no port specified
} else {
@@ -504,11 +504,13 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
(metadata_store.advanced_dacp_server_active != 0);
metadata_store.dacp_server_active = 0;
metadata_store.advanced_dacp_server_active = 0;
+ /*
debug(3,
"setting metadata_store.dacp_server_active and "
"metadata_store.advanced_dacp_server_active to 0 with an update "
"flag value of %d",
ch);
+ */
metadata_hub_modify_epilog(ch);
uint64_t time_to_wait_for_wakeup_ns =
@@ -1247,9 +1249,9 @@ int dacp_get_volume(int32_t *the_actual_volume) {
// metadata_store.speaker_volume = actual_volume;
// metadata_hub_modify_epilog(1);
} else {
- debug(1, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
+ debug(2, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
}
- } else if (http_response != 400) {
+ } else if ((http_response != 400) && (http_response != 490)) {
debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response);
}
if (the_actual_volume) {
diff --git a/metadata_hub.c b/metadata_hub.c
index 190895c0..7b20ae18 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -148,7 +148,7 @@ void _metadata_hub_modify_prolog(const char *filename, const int linenumber) {
}
last_metadata_hub_modify_prolog_file = strdup(filename);
last_metadata_hub_modify_prolog_line = linenumber;
- debug(3, "Metadata_hub write lock acquired.");
+ // debug(3, "Metadata_hub write lock acquired.");
}
metadata_hub_re_lock_access_is_delayed = 0;
}
@@ -169,7 +169,7 @@ void _metadata_hub_modify_epilog(int modified, const char *filename, const int l
}
}
pthread_rwlock_unlock(&metadata_hub_re_lock);
- debug(3, "Metadata_hub write lock unlocked.");
+ // debug(3, "Metadata_hub write lock unlocked.");
}
/*
diff --git a/player.c b/player.c
index dcef6c4e..edca685f 100644
--- a/player.c
+++ b/player.c
@@ -950,71 +950,71 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
debug(2, "flush request: flush output device.");
}
conn->flush_output_flushed = 1;
- // now check to see it the flush request is for frames in the buffer or not
- // if the first_packet_timestamp is zero, don't check
- int flush_needed = 0;
- int drop_request = 0;
- if (conn->flush_rtp_timestamp == 0) {
- debug(1, "flush request: flush frame 0 -- flush assumed to be needed.");
- flush_needed = 1;
- drop_request = 1;
- } else {
- if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) {
- abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read);
- abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1);
- if ((firstPacket != NULL) && (firstPacket->ready)) {
- // discard flushes more than 10 seconds into the future -- they are probably bogus
- uint32_t first_frame_in_buffer = firstPacket->given_timestamp;
- int32_t offset_from_first_frame = (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer);
- if (offset_from_first_frame > (int)conn->input_rate * 10) {
- debug(1, "flush request: sanity check -- flush frame %u is too far into the future from the first frame %u -- discarded.", conn->flush_rtp_timestamp, first_frame_in_buffer);
- drop_request = 1;
- } else {
- if ((lastPacket != NULL) && (lastPacket->ready)) {
- // we have enough information to check if the flush is needed or can be discarded
- uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1;
- // now we have to work out if the flush frame is in the buffer
- // if it is later than the end of the buffer, flush everything and keep the request active.
- // if it is in the buffer, we need to flush part of the buffer. Actually we flush the entire buffer and drop the request.
- // if it is before the buffer, no flush is needed. Drop the request.
- if (offset_from_first_frame > 0) {
- int32_t offset_to_last_frame = (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp);
- if (offset_to_last_frame >= 0) {
- debug(2,"flush request: flush frame %u active -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
- drop_request = 1;
- flush_needed = 1;
- } else {
- debug(2,"flush request: flush frame %u pending -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
- flush_needed = 1;
- }
+ }
+ // now check to see it the flush request is for frames in the buffer or not
+ // if the first_packet_timestamp is zero, don't check
+ int flush_needed = 0;
+ int drop_request = 0;
+ if ((conn->flush_requested == 1) && (conn->flush_rtp_timestamp == 0)) {
+ debug(1, "flush request: flush frame 0 -- flush assumed to be needed.");
+ flush_needed = 1;
+ drop_request = 1;
+ } else if (conn->flush_rtp_timestamp != 0) {
+ if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) {
+ abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read);
+ abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1);
+ if ((firstPacket != NULL) && (firstPacket->ready)) {
+ // discard flushes more than 10 seconds into the future -- they are probably bogus
+ uint32_t first_frame_in_buffer = firstPacket->given_timestamp;
+ int32_t offset_from_first_frame = (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer);
+ if (offset_from_first_frame > (int)conn->input_rate * 10) {
+ debug(1, "flush request: sanity check -- flush frame %u is too far into the future from the first frame %u -- discarded.", conn->flush_rtp_timestamp, first_frame_in_buffer);
+ drop_request = 1;
+ } else {
+ if ((lastPacket != NULL) && (lastPacket->ready)) {
+ // we have enough information to check if the flush is needed or can be discarded
+ uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1;
+ // now we have to work out if the flush frame is in the buffer
+ // if it is later than the end of the buffer, flush everything and keep the request active.
+ // if it is in the buffer, we need to flush part of the buffer. Actually we flush the entire buffer and drop the request.
+ // if it is before the buffer, no flush is needed. Drop the request.
+ if (offset_from_first_frame > 0) {
+ int32_t offset_to_last_frame = (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp);
+ if (offset_to_last_frame >= 0) {
+ debug(2,"flush request: flush frame %u active -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
+ drop_request = 1;
+ flush_needed = 1;
} else {
- debug(2,"flush request: flush frame %u expired -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
- drop_request = 1;
+ debug(2,"flush request: flush frame %u pending -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
+ flush_needed = 1;
}
+ } else {
+ debug(2,"flush request: flush frame %u expired -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
+ drop_request = 1;
}
}
}
- } else {
- debug(3, "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: %u, ab_write: %u", conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write);
- // leave flush request pending and don't do a buffer flush, because there isn't one
}
- }
- if (flush_needed) {
- debug(2, "flush request: flush done.");
- ab_resync(conn); // no cancellation points
- conn->first_packet_timestamp = 0;
- conn->first_packet_time_to_play = 0;
- conn->time_since_play_started = 0;
- have_sent_prefiller_silence = 0;
- dac_delay = 0;
+ } else {
+ debug(3, "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: %u, ab_write: %u", conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write);
+ // leave flush request pending and don't do a buffer flush, because there isn't one
}
- if (drop_request) {
- debug(2, "flush request: request dropped.");
- conn->flush_requested = 0;
- conn->flush_rtp_timestamp = 0;
- conn->flush_output_flushed = 0;
- }
- }
+ }
+ if (flush_needed) {
+ debug(2, "flush request: flush done.");
+ ab_resync(conn); // no cancellation points
+ conn->first_packet_timestamp = 0;
+ conn->first_packet_time_to_play = 0;
+ conn->time_since_play_started = 0;
+ have_sent_prefiller_silence = 0;
+ dac_delay = 0;
+ }
+ if (drop_request) {
+ debug(2, "flush request: request dropped.");
+ conn->flush_requested = 0;
+ conn->flush_rtp_timestamp = 0;
+ conn->flush_output_flushed = 0;
+ }
debug_mutex_unlock(&conn->flush_mutex, 0);
if (conn->ab_synced) {
curframe = conn->audio_buffer + BUFIDX(conn->ab_read);
@@ -1997,7 +1997,7 @@ void *player_thread_func(void *arg) {
// debug(3, "Play frame %d.", play_number);
conn->play_number_after_flush++;
if (inframe->given_timestamp == 0) {
- debug(1,
+ debug(2,
"Player has supplied a silent frame, (possibly frame %u) for play number %d, "
"status 0x%X after %u resend requests.",
SUCCESSOR(conn->last_seqno_read), play_number, inframe->status,
diff --git a/rtsp.c b/rtsp.c
index 2febacd0..1f1cb9f9 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1572,7 +1572,7 @@ void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
snprintf(path, pl + 1, "%s", config.metadata_pipename);
mode_t oldumask = umask(000);
- if (mkfifo(path, 0644) && errno != EEXIST)
+ if (mkfifo(path, 0666) && errno != EEXIST)
die("Could not create metadata pipe \"%s\".", path);
umask(oldumask);
debug(1, "metadata pipe name is \"%s\".", path);
diff --git a/shairport.c b/shairport.c
index 14e50725..975b71fc 100644
--- a/shairport.c
+++ b/shairport.c
@@ -266,7 +266,7 @@ void usage(char *progname) {
printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. "
"--metadata-pipename=/tmp/shairport-sync-metadata.\n");
printf(" The default is /tmp/shairport-sync-metadata.\n");
- printf(" --get-coverart send cover art through the metadata pipe.\n");
+ printf(" -g, --get-coverart send cover art through the metadata pipe.\n");
#endif
printf(" -u, --use-stderr log messages through STDERR rather than the system log.\n");
printf("\n");
@@ -418,6 +418,17 @@ int parse_options(int argc, char **argv) {
// seconds then we can add an offset of 5.17 seconds and still leave a second's worth of buffers
// for unexpected circumstances
+#ifdef CONFIG_METADATA
+ /* Get the metadata setting. */
+ config.metadata_enabled = 1; // if metadata support is included, then enable it by default
+ config.get_coverart = 1; // if metadata support is included, then enable it by default
+#endif
+
+#ifdef CONFIG_CONVOLUTION
+ config.convolution_max_length = 8192;
+#endif
+ config.loudness_reference_volume_db = -20;
+
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
config.scan_interval_when_active =
@@ -846,7 +857,6 @@ int parse_options(int argc, char **argv) {
#ifdef CONFIG_METADATA
/* Get the metadata setting. */
- config.metadata_enabled = 1; // if metadata support is included, then enable it by default
if (config_lookup_string(config.cfg, "metadata.enabled", &str)) {
if (strcasecmp(str, "no") == 0)
config.metadata_enabled = 0;
@@ -856,7 +866,6 @@ int parse_options(int argc, char **argv) {
die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\"");
}
- config.get_coverart = 1; // if metadata support is included, then enable it by default
if (config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) {
if (strcasecmp(str, "no") == 0)
config.get_coverart = 0;
@@ -988,7 +997,6 @@ int parse_options(int argc, char **argv) {
dvalue);
}
- config.convolution_max_length = 8192;
if (config_lookup_int(config.cfg, "dsp.convolution_max_length", &value)) {
config.convolution_max_length = value;
@@ -1015,7 +1023,6 @@ int parse_options(int argc, char **argv) {
die("Invalid dsp.convolution. It should be \"yes\" or \"no\"");
}
- config.loudness_reference_volume_db = -20;
if (config_lookup_float(config.cfg, "dsp.loudness_reference_volume_db", &dvalue)) {
config.loudness_reference_volume_db = dvalue;
if (dvalue > 0 || dvalue < -100)
@@ -1168,7 +1175,7 @@ int parse_options(int argc, char **argv) {
break;
case 'g':
if (config.metadata_enabled == 0)
- die("If you want to get cover art, you must also select the --metadata-pipename option.");
+ die("If you want to get cover art, ensure metadata_enabled is true.");
break;
#endif
case 'S':
From f2ff8c634fcb148a7846d2b97aa04bbdc331f0b6 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 24 Aug 2020 09:17:15 +0100
Subject: [PATCH 034/180] Update UPDATING.md
---
UPDATING.md | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/UPDATING.md b/UPDATING.md
index 66916847..51c1e1e8 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -15,7 +15,26 @@ $ sudo apt-get upgrade
```
Next, stop playing music to your device.
-Now, to update and install Shairport Sync, if you still have the directory in which you previously built Shairport Sync, it will contain the repository you originally downloaded. Navigate your way to it and 'pull' the changes from GitHub:
+### Remove Old Copies
+It's a good idea to search for and remove any existing copies of the application, called `shairport-sync`. Use the command `$ which shairport-sync` to find them. For example, if `shairport-sync` has been installed previously, this might happen:
+```
+$ which shairport-sync
+/usr/local/bin/shairport-sync
+```
+Remove it as follows:
+```
+# rm /usr/local/bin/shairport-sync
+```
+Do this until no more copies of `shairport-sync` are found. This is especially important if you are building Shairport Sync over an old version that was installed from packages, as the built application will be installed in a different directory to the packaged installation.
+
+### Remove Old Startup Scripts
+You should also remove the startup script files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed if necessary. As with the previous section, this is especially important if you are building Shairport Sync over an old version that was installed from packages, as the scripts for the built application may be different to those of the packaged version. If they are left in place, bad things can happen.
+
+### Reboot after Cleaning Up
+If you removed any installations of Shairport Sync or any of its startup script files in the last two steps, you should reboot.
+
+### Building the Updated Shairport Sync
+Now, to build, update and install Shairport Sync, if you still have the directory in which you previously built Shairport Sync, it will contain the repository you originally downloaded. Navigate your way to it and 'pull' the changes from GitHub:
```
$ git pull
From 431c66096609f084280afc818c16417d18da625c Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 24 Aug 2020 09:19:57 +0100
Subject: [PATCH 035/180] Update UPDATING.md
---
UPDATING.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/UPDATING.md b/UPDATING.md
index 51c1e1e8..6e34c526 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -7,7 +7,9 @@ but a few steps are shorter because you've done them before; you won't have to r
But before you begin, you should update and upgrade any packages.
-Here is the sequence for Raspbian Stretch, which is based on Debian Stretch. The same commands work for Ubuntu, and maybe more. Here, a non-`root` user with `sudo` privileges is assumed.
+Note that in this guide, a `$` prefix means you should be in the normal user mode; a `#` prefix means you should be in the superuser or root mode.
+
+Here is the sequence for Raspberry Pi OS (Buster), which is based on Debian Buster. The same commands work for Ubuntu, and maybe more. Here, a non-`root` user with `sudo` privileges is assumed.
```
$ sudo apt-get update
From 49084a098311bbad31046dd4ca91afb4a6210d81 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 24 Aug 2020 09:23:15 +0100
Subject: [PATCH 036/180] Update UPDATING.md
---
UPDATING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/UPDATING.md b/UPDATING.md
index 6e34c526..5803e27c 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -1,6 +1,6 @@
### Updating Shairport Sync
-This guide is for updating an installation of Shairport Sync. If you installed Shairport Sync from a package, most of these instructions don't apply – please go to the section at the end called "Post Update Tasks"
+This guide is for building and updating an installation of Shairport Sync. If you wish to build an installation of Shairport Sync to replace an installation that came from a package, this doesn't really apply – instead, please follow the guide at [INSTALL.md](https://github.com/mikebrady/shairport-sync/blob/master/INSTALL.md).
To do an update, you basically have to go through the whole process of building Shairport Sync again,
but a few steps are shorter because you've done them before; you won't have to reinstall the build tools or libraries needed, and you won't have to define the user and group or reconfigure the settings in the configuration file.
From 54cb9de1836fcb764bbae7397b6f79899f674268 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 30 Sep 2020 14:57:09 +0100
Subject: [PATCH 037/180] Handle active_remote_id as a string rather than
an unsigned 32-bit number -- seems ROON uses a longer character sequence.
Tidy up some error messages about which backend is selected. Tighten up
memory allocatons in DACP.c. Add a default name for the pipe backend:
/tmp/shairport-sync-audio Ensure the metadata pipe is created, if
necessary, when the --with-metadata option is chosen without dbus or mpris
Make the first output backend in the list of backends the default and make
its name the default output_name. Clang-format everything Add the word
'jack' to the version string if Jack Audio support is compiled in. Fix a
flaw in resyncing -- a flush is set up but not triggered. Open metadata
and audio pipes with 666 permissions. Ensure metadata and cover art are
enabled if metadata support is included at compilation. Reword some
misleading error messages. Set convolution defaults even if no
configuration file is found.
---
audio.c | 2 +-
audio.h | 2 +-
audio_pipe.c | 48 +-
common.c | 184 +++----
common.h | 21 +-
configure.ac | 8 +-
dacp.c | 48 +-
metadata_hub.c | 44 +-
metadata_hub.h | 7 +-
player.c | 994 +++++++++++++++++++-----------------
player.h | 9 +-
rtp.c | 99 ++--
rtsp.c | 402 ++++++++-------
scripts/shairport-sync.conf | 2 +-
shairport.c | 211 ++++----
15 files changed, 1100 insertions(+), 981 deletions(-)
diff --git a/audio.c b/audio.c
index 23b1760d..0fe7b04f 100644
--- a/audio.c
+++ b/audio.c
@@ -89,7 +89,7 @@ static audio_output *outputs[] = {
#endif
NULL};
-audio_output *audio_get_output(char *name) {
+audio_output *audio_get_output(const char *name) {
audio_output **out;
// default to the first
diff --git a/audio.h b/audio.h
index 856f184b..3413250c 100644
--- a/audio.h
+++ b/audio.h
@@ -54,7 +54,7 @@ typedef struct {
} audio_output;
-audio_output *audio_get_output(char *name);
+audio_output *audio_get_output(const char *name);
void audio_ls_outputs(void);
void parse_general_audio_options(void);
diff --git a/audio_pipe.c b/audio_pipe.c
index 45e0f060..a7925fbc 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -42,6 +42,7 @@
static int fd = -1;
char *pipename = NULL;
+char *default_pipe_name = "/tmp/shairport-sync-audio";
static void start(__attribute__((unused)) int sample_rate,
__attribute__((unused)) int sample_format) {
@@ -51,33 +52,34 @@ static void start(__attribute__((unused)) int sample_rate,
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
// open for reading."
- fd = try_to_open_pipe_for_writing(pipename);
- // we check that it's not a "real" error. From the "man 2 open" page:
- // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
- // open for reading." Which is okay.
- if ((fd == -1) && (errno != ENXIO)) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "audio_pipe start -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, pipename);
- warn("can not open audio pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, pipename);
- }
+ fd = try_to_open_pipe_for_writing(pipename);
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ if ((fd == -1) && (errno != ENXIO)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "audio_pipe start -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, pipename);
+ warn("can not open audio pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, pipename);
+ }
}
static int play(void *buf, int samples) {
// if the file is not open, try to open it.
char errorstring[1024];
if (fd == -1) {
- fd = try_to_open_pipe_for_writing(pipename);
+ fd = try_to_open_pipe_for_writing(pipename);
}
// if it's got a reader, write to it.
if (fd > 0) {
- //int rc = non_blocking_write(fd, buf, samples * 4);
+ // int rc = non_blocking_write(fd, buf, samples * 4);
int rc = write(fd, buf, samples * 4);
if ((rc < 0) && (errno != EPIPE)) {
strerror_r(errno, (char *)errorstring, 1024);
- debug(1, "audio_pip play: error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
+ debug(1, "audio_pip play: error %d writing to the pipe named \"%s\": \"%s\".", errno,
+ pipename, errorstring);
}
}
return 0;
@@ -85,7 +87,6 @@ static int play(void *buf, int samples) {
static void stop(void) {
// Don't close the pipe just because a play session has stopped.
-
}
static int init(int argc, char **argv) {
@@ -108,16 +109,19 @@ static int init(int argc, char **argv) {
if (config_lookup_string(config.cfg, "pipe.name", &str)) {
pipename = (char *)str;
}
-
- if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0))
- die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead.");
}
- if ((pipename == NULL) && (argc != 1))
- die("bad or missing argument(s) to pipe");
+ if (argc > 1)
+ die("too many command-line arguments to pipe");
if (argc == 1)
- pipename = strdup(argv[0]);
+ pipename = argv[0]; // command line argument has priority
+
+ if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0))
+ die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead.");
+
+ if (pipename == NULL)
+ pipename = default_pipe_name; // if none specified
// here, create the pipe
mode_t oldumask = umask(000);
diff --git a/common.c b/common.c
index 39c4d62e..b48adad4 100644
--- a/common.c
+++ b/common.c
@@ -29,6 +29,8 @@
#include "common.h"
#include
#include
+#include
+#include
#include
#include
#include
@@ -40,8 +42,6 @@
#include
#include
#include
-#include
-#include
#ifdef COMPILE_FOR_OSX
#include
@@ -141,64 +141,69 @@ void do_sps_log_to_stdout(__attribute__((unused)) int prio, const char *t, ...)
fprintf(stdout, "%s\n", s);
}
-int create_log_file(const char* path) {
- int fd = -1;
- if (path != NULL) {
- char *dirc = strdup(path);
- if (dirc) {
- char *dname = dirname(dirc);
- // create the directory, if necessary
- int result = 0;
- if (dname) {
- char *pdir = realpath(dname, NULL); // will return a NULL if the directory doesn't exist
- if (pdir == NULL) {
- mode_t oldumask = umask(000);
- result = mkpath(dname, 0777);
- umask(oldumask);
- } else {
- free(pdir);
- }
- if ((result == 0) || (result == -EEXIST)) {
- // now open the file
- fd = open(path, O_WRONLY | O_NONBLOCK | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
- if ((fd == -1) && (errno == EEXIST))
- fd = open(path, O_WRONLY | O_APPEND | O_NONBLOCK);
+int create_log_file(const char *path) {
+ int fd = -1;
+ if (path != NULL) {
+ char *dirc = strdup(path);
+ if (dirc) {
+ char *dname = dirname(dirc);
+ // create the directory, if necessary
+ int result = 0;
+ if (dname) {
+ char *pdir = realpath(dname, NULL); // will return a NULL if the directory doesn't exist
+ if (pdir == NULL) {
+ mode_t oldumask = umask(000);
+ result = mkpath(dname, 0777);
+ umask(oldumask);
+ } else {
+ free(pdir);
+ }
+ if ((result == 0) || (result == -EEXIST)) {
+ // now open the file
+ fd = open(path, O_WRONLY | O_NONBLOCK | O_CREAT | O_EXCL,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if ((fd == -1) && (errno == EEXIST))
+ fd = open(path, O_WRONLY | O_APPEND | O_NONBLOCK);
- if (fd >= 0) {
- // now we switch to blocking mode
- int flags = fcntl(fd, F_GETFL);
- if (flags == -1) {
-// strerror_r(errno, (char *)errorstring, sizeof(errorstring));
-// debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
-// (char *)errorstring, pathname);
- } else {
- flags = fcntl(fd, F_SETFL,flags & ~O_NONBLOCK);
-// if (flags == -1) {
-// strerror_r(errno, (char *)errorstring, sizeof(errorstring));
-// debug(1, "create_log_file -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
-// (char *)errorstring, pathname);
- }
- }
- }
- }
- free(dirc);
- }
- }
- return fd;
+ if (fd >= 0) {
+ // now we switch to blocking mode
+ int flags = fcntl(fd, F_GETFL);
+ if (flags == -1) {
+ // strerror_r(errno, (char
+ //*)errorstring, sizeof(errorstring));
+ // debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".",
+ // errno,
+ // (char *)errorstring, pathname);
+ } else {
+ flags = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+ // if (flags == -1) {
+ // strerror_r(errno,
+ //(char *)errorstring, sizeof(errorstring));
+ // debug(1, "create_log_file -- error %d
+ //(\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
+ //(char *)errorstring, pathname);
+ }
+ }
+ }
+ }
+ free(dirc);
+ }
+ }
+ return fd;
}
void do_sps_log_to_fd(__attribute__((unused)) int prio, const char *t, ...) {
- char s[1024];
- va_list args;
- va_start(args, t);
- vsnprintf(s, sizeof(s), t, args);
- va_end(args);
- if (config.log_fd == -1)
- config.log_fd = create_log_file(config.log_file_path);
- if (config.log_fd >= 0) {
- dprintf(config.log_fd, "%s\n", s);
+ char s[1024];
+ va_list args;
+ va_start(args, t);
+ vsnprintf(s, sizeof(s), t, args);
+ va_end(args);
+ if (config.log_fd == -1)
+ config.log_fd = create_log_file(config.log_file_path);
+ if (config.log_fd >= 0) {
+ dprintf(config.log_fd, "%s\n", s);
} else if (errno != ENXIO) { // maybe there is a pipe there but not hooked up
- fprintf(stderr, "%s\n", s);
+ fprintf(stderr, "%s\n", s);
}
}
@@ -207,9 +212,9 @@ void log_to_stdout() { sps_log = do_sps_log_to_stdout; }
void log_to_file() { sps_log = do_sps_log_to_fd; }
void log_to_syslog() {
#ifdef CONFIG_LIBDAEMON
- sps_log = daemon_log;
+ sps_log = daemon_log;
#else
- sps_log = syslog;
+ sps_log = syslog;
#endif
}
@@ -309,8 +314,8 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *fatal error: ");
} else {
- strncpy(b, "fatal error: ", sizeof(b));
- s = b+strlen(b);
+ strncpy(b, "fatal error: ", sizeof(b));
+ s = b + strlen(b);
}
va_list args;
va_start(args, format);
@@ -339,8 +344,8 @@ void _warn(const char *filename, const int linenumber, const char *format, ...)
1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *warning: ");
} else {
- strncpy(b, "warning: ", sizeof(b));
- s = b+strlen(b);
+ strncpy(b, "warning: ", sizeof(b));
+ s = b + strlen(b);
}
va_list args;
va_start(args, format);
@@ -1134,12 +1139,12 @@ uint64_t get_absolute_time_in_ns() {
return time_now_ns;
}
-int try_to_open_pipe_for_writing(const char* pathname) {
- // tries to open the pipe in non-blocking mode first.
- // if it succeeds, it sets it to blocking.
- // if not, it returns -1.
+int try_to_open_pipe_for_writing(const char *pathname) {
+ // tries to open the pipe in non-blocking mode first.
+ // if it succeeds, it sets it to blocking.
+ // if not, it returns -1.
- int fdis = open(pathname, O_WRONLY | O_NONBLOCK); // open it in non blocking mode first
+ int fdis = open(pathname, O_WRONLY | O_NONBLOCK); // open it in non blocking mode first
// we check that it's not a "real" error. From the "man 2 open" page:
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
@@ -1147,24 +1152,24 @@ int try_to_open_pipe_for_writing(const char* pathname) {
// This is checked by the caller.
if (fdis >= 0) {
- // now we switch to blocking mode
- int flags = fcntl(fdis, F_GETFL);
- if (flags == -1) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "try_to_open_pipe -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
- (char *)errorstring, pathname);
- } else {
- flags = fcntl(fdis, F_SETFL,flags & ~O_NONBLOCK);
- if (flags == -1) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "try_to_open_pipe -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
- (char *)errorstring, pathname);
- }
- }
+ // now we switch to blocking mode
+ int flags = fcntl(fdis, F_GETFL);
+ if (flags == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "try_to_open_pipe -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
+ (char *)errorstring, pathname);
+ } else {
+ flags = fcntl(fdis, F_SETFL, flags & ~O_NONBLOCK);
+ if (flags == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "try_to_open_pipe -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
+ (char *)errorstring, pathname);
+ }
+ }
}
- return fdis;
+ return fdis;
}
/* from
@@ -1460,6 +1465,9 @@ char *get_version_string() {
#ifdef CONFIG_SNDIO
strcat(version_string, "-sndio");
#endif
+#ifdef CONFIG_JACK
+ strcat(version_string, "-jack");
+#endif
#ifdef CONFIG_AO
strcat(version_string, "-ao");
#endif
@@ -1707,11 +1715,11 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
}
// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
-void* memdup(const void* mem, size_t size) {
- void* out = malloc(size);
+void *memdup(const void *mem, size_t size) {
+ void *out = malloc(size);
- if(out != NULL)
- memcpy(out, mem, size);
+ if (out != NULL)
+ memcpy(out, mem, size);
- return out;
+ return out;
}
diff --git a/common.h b/common.h
index 1f5d1c87..68666ac9 100644
--- a/common.h
+++ b/common.h
@@ -182,8 +182,8 @@ typedef struct {
char *pidfile;
#endif
- int log_fd; // file descriptor of the file or pipe to log stuff to.
- char *log_file_path; // path to file or pipe to log to, if any
+ int log_fd; // file descriptor of the file or pipe to log stuff to.
+ char *log_file_path; // path to file or pipe to log to, if any
int logOutputLevel; // log output level
int debugger_show_elapsed_time; // in the debug message, display the time since startup
int debugger_show_relative_time; // in the debug message, display the time since the last one
@@ -285,10 +285,10 @@ typedef struct {
int jack_soxr_resample_quality;
#endif
#endif
- void *gradients; // a linked list of the clock gradients discovered for all DACP IDs
- // can't use IP numbers as they might be given to different devices
- // can't get hold of MAC addresses.
- // can't define the nvll linked list struct here
+ void *gradients; // a linked list of the clock gradients discovered for all DACP IDs
+ // can't use IP numbers as they might be given to different devices
+ // can't get hold of MAC addresses.
+ // can't define the nvll linked list struct here
} shairport_cfg;
// accessors to config for multi-thread access
@@ -303,9 +303,7 @@ void memory_barrier();
void log_to_stderr(); // call this to direct logging to stderr;
void log_to_stdout(); // call this to direct logging to stdout;
void log_to_syslog(); // call this to direct logging to the system log;
-void log_to_file(); // call this to direct logging to a file or (pre-existing) pipe;
-
-
+void log_to_file(); // call this to direct logging to a file or (pre-existing) pipe;
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
@@ -313,7 +311,8 @@ int get_requested_connection_state_to_output();
void set_requested_connection_state_to_output(int v);
-int try_to_open_pipe_for_writing(const char* pathname); // open it without blocking if it's not hooked up
+int try_to_open_pipe_for_writing(
+ const char *pathname); // open it without blocking if it's not hooked up
/* from
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
@@ -446,6 +445,6 @@ void malloc_cleanup(void *arg);
int string_update_with_size(char **str, int *flag, char *s, size_t len);
// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
-void* memdup(const void* mem, size_t size);
+void *memdup(const void *mem, size_t size);
#endif // _COMMON_H
diff --git a/configure.ac b/configure.ac
index 7f253699..78e337e7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.7rc2], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [3.3.7rc3], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
@@ -156,7 +156,7 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
AC_MSG_ERROR(choose "openssl", "mbedtls" or "polarssl" encryption)
fi
if test "x${with_ssl}" = xopenssl ; then
- AC_DEFINE([CONFIG_OPENSSL], 1, [Use the OpenSSL libraries for encryption and encoding and decoding])
+ AC_DEFINE([CONFIG_OPENSSL], 1, [Use the OpenSSL libraries for encryption and encoding and decoding])
if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
[SSL], [libssl,libcrypto],
@@ -166,7 +166,7 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
AC_CHECK_LIB([ssl], [main], , AC_MSG_ERROR(libssl selected but the library cannot be found!))
fi
elif test "x${with_ssl}" = xmbedtls ; then
- AC_DEFINE([CONFIG_MBEDTLS], 1, [Use the mbed TLS libraries for encryption and encoding and decoding])
+ AC_DEFINE([CONFIG_MBEDTLS], 1, [Use the mbed TLS libraries for encryption and encoding and decoding])
AC_CHECK_LIB([mbedtls],[mbedtls_ssl_init],,
[AC_MSG_ERROR([mbed tls support requires the mbedtls library -- libmbedtls-dev suggested],1)])
AC_CHECK_LIB([mbedcrypto], [mbedtls_entropy_func],,
@@ -174,7 +174,7 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
AC_CHECK_LIB([mbedx509], [mbedtls_pk_init],,
[AC_MSG_ERROR([mbed tls support requires the mbedx509 library -- libmbedx509-0 suggested],1)])
elif test "x${with_ssl}" = xpolarssl ; then
- AC_DEFINE([CONFIG_POLARSSL], 1, [Use the PolarSSL libraries for encryption and encoding and decoding])
+ AC_DEFINE([CONFIG_POLARSSL], 1, [Use the PolarSSL libraries for encryption and encoding and decoding])
AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL is selected but the library cannot be found and is deprecated. Consider selecting mbed TLS instead using --with-ssl=mbedtls.))
else
AC_MSG_ERROR(unknown option "${with_ssl}"." Please choose with "openssl", "mbedtls" or "polarssl")
diff --git a/dacp.c b/dacp.c
index a25bf9e4..acb963b9 100644
--- a/dacp.c
+++ b/dacp.c
@@ -56,7 +56,7 @@ typedef struct {
int always_use_revision_number_1; // for dealing with forked-daapd;
uint32_t scope_id; // if it's an ipv6 connection, this will be its scope id
char ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
- uint32_t active_remote_id; // send this when you want to send remote control commands
+ char *active_remote_id; // send this when you want to send remote control commands
void *port_monitor_private_storage;
} dacp_server_record;
@@ -244,7 +244,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"DACP connect succeeded.");
snprintf(message, sizeof(message),
- "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
+ "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %s\r\n\r\n",
command, dacp_server.ip_string, dacp_server.port,
dacp_server.active_remote_id);
@@ -443,10 +443,12 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
// metadata_hub_modify_epilog(ch);
}
}
- dacp_server.active_remote_id = conn->dacp_active_remote; // even if the dacp_id remains the same,
- // the active remote will change.
- debug(3, "set_dacp_server_information set active-remote id to %" PRIu32 ".",
- dacp_server.active_remote_id);
+ if (dacp_server.active_remote_id)
+ free(dacp_server.active_remote_id);
+ dacp_server.active_remote_id =
+ strdup(conn->dacp_active_remote); // even if the dacp_id remains the same,
+ // the active remote will change.
+ debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id);
pthread_cond_signal(&dacp_server_information_cv);
debug_mutex_unlock(&dacp_server_information_lock, 3);
}
@@ -457,20 +459,24 @@ void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
"dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port "
"number %d.",
dacp_id, dacp_server.dacp_id, port);
- if (strcmp(dacp_id, dacp_server.dacp_id) == 0) {
- dacp_server.port = port;
- if (port == 0)
- dacp_server.scan_enable = 0;
- else {
- dacp_server.scan_enable = 1;
- // debug(2, "dacp_monitor_port_update_callback enables scan");
- }
- // metadata_hub_modify_prolog();
- // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
- // metadata_store.dacp_server_active = dacp_server.scan_enable;
- // metadata_hub_modify_epilog(ch);
+ if ((dacp_id == NULL) || (dacp_server.dacp_id == NULL)) {
+ warn("dacp_id or dacp_server.dacp_id NULL detected");
} else {
- debug(1, "dacp port monitor reporting on an out-of-use remote.");
+ if (strcmp(dacp_id, dacp_server.dacp_id) == 0) {
+ dacp_server.port = port;
+ if (port == 0)
+ dacp_server.scan_enable = 0;
+ else {
+ dacp_server.scan_enable = 1;
+ // debug(2, "dacp_monitor_port_update_callback enables scan");
+ }
+ // metadata_hub_modify_prolog();
+ // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
+ // metadata_store.dacp_server_active = dacp_server.scan_enable;
+ // metadata_hub_modify_epilog(ch);
+ } else {
+ debug(1, "dacp port monitor reporting on an out-of-use remote.");
+ }
}
pthread_cond_signal(&dacp_server_information_cv);
debug_mutex_unlock(&dacp_server_information_lock, 3);
@@ -999,6 +1005,10 @@ void dacp_monitor_stop() {
pthread_mutex_destroy(&dacp_conversation_lock);
pthread_cond_destroy(&dacp_server_information_cv);
debug(3, "DACP Server Information Condition Variable destroyed.");
+ if (dacp_server.active_remote_id) {
+ free(dacp_server.active_remote_id);
+ dacp_server.active_remote_id = NULL;
+ }
}
}
diff --git a/metadata_hub.c b/metadata_hub.c
index 7b20ae18..a899574a 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -136,18 +136,20 @@ void _metadata_hub_modify_prolog(const char *filename, const int linenumber) {
// debug(1, "locking metadata hub for writing");
if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) {
if (last_metadata_hub_modify_prolog_file)
- debug(2, "Metadata_hub write lock at \"%s:%d\" is already taken at \"%s:%d\" -- must wait.", filename, linenumber, last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line);
+ debug(2, "Metadata_hub write lock at \"%s:%d\" is already taken at \"%s:%d\" -- must wait.",
+ filename, linenumber, last_metadata_hub_modify_prolog_file,
+ last_metadata_hub_modify_prolog_line);
else
- debug(2, "Metadata_hub write lock is already taken by unknown -- must wait.");
+ debug(2, "Metadata_hub write lock is already taken by unknown -- must wait.");
metadata_hub_re_lock_access_is_delayed = 0;
pthread_rwlock_wrlock(&metadata_hub_re_lock);
debug(2, "Okay -- acquired the metadata_hub write lock at \"%s:%d\".", filename, linenumber);
} else {
- if (last_metadata_hub_modify_prolog_file) {
- free(last_metadata_hub_modify_prolog_file);
- }
- last_metadata_hub_modify_prolog_file = strdup(filename);
- last_metadata_hub_modify_prolog_line = linenumber;
+ if (last_metadata_hub_modify_prolog_file) {
+ free(last_metadata_hub_modify_prolog_file);
+ }
+ last_metadata_hub_modify_prolog_file = strdup(filename);
+ last_metadata_hub_modify_prolog_line = linenumber;
// debug(3, "Metadata_hub write lock acquired.");
}
metadata_hub_re_lock_access_is_delayed = 0;
@@ -160,13 +162,16 @@ void _metadata_hub_modify_epilog(int modified, const char *filename, const int l
run_metadata_watchers();
}
if (metadata_hub_re_lock_access_is_delayed) {
- if (last_metadata_hub_modify_prolog_file) {
- debug(1, "Metadata_hub write lock taken at \"%s:%d\" is freed at \"%s:%d\".", last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line, filename, linenumber);
- free(last_metadata_hub_modify_prolog_file);
- last_metadata_hub_modify_prolog_file = NULL;
- } else {
- debug(1, "Metadata_hub write lock taken at an unknown place is freed at \"%s:%d\".", filename, linenumber);
- }
+ if (last_metadata_hub_modify_prolog_file) {
+ debug(1, "Metadata_hub write lock taken at \"%s:%d\" is freed at \"%s:%d\".",
+ last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line, filename,
+ linenumber);
+ free(last_metadata_hub_modify_prolog_file);
+ last_metadata_hub_modify_prolog_file = NULL;
+ } else {
+ debug(1, "Metadata_hub write lock taken at an unknown place is freed at \"%s:%d\".", filename,
+ linenumber);
+ }
}
pthread_rwlock_unlock(&metadata_hub_re_lock);
// debug(3, "Metadata_hub write lock unlocked.");
@@ -506,10 +511,11 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
debug(2, "MH Picture received, length %u bytes.", length);
char uri[2048];
- if ((length > 16) && (strcmp(config.cover_art_cache_dir,"")!=0)) { // if it's okay to write the file
- // make this uncancellable
- int oldState;
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ if ((length > 16) &&
+ (strcmp(config.cover_art_cache_dir, "") != 0)) { // if it's okay to write the file
+ // make this uncancellable
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
char *pathname = metadata_write_image_file(data, length);
snprintf(uri, sizeof(uri), "file://%s", pathname);
free(pathname);
@@ -524,7 +530,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
changed = 1;
else
changed = 0;
-// pthread_cleanup_pop(0); // don't remove the lock -- it'll have been done
+ // pthread_cleanup_pop(0); // don't remove the lock -- it'll have been done
break;
case 'clip':
cs = strndup(data, length);
diff --git a/metadata_hub.h b/metadata_hub.h
index 760a848b..bdbbf321 100644
--- a/metadata_hub.h
+++ b/metadata_hub.h
@@ -149,7 +149,9 @@ void metadata_hub_release_track_artwork(void);
// these functions lock and unlock the read-write mutex on the metadata hub and run the watchers
// afterwards
void _metadata_hub_modify_prolog(const char *filename, const int linenumber);
-void _metadata_hub_modify_epilog(int modified, const char *filename, const int linenumber); // set to true if modifications occurred, 0 otherwise
+void _metadata_hub_modify_epilog(
+ int modified, const char *filename,
+ const int linenumber); // set to true if modifications occurred, 0 otherwise
/*
// these are for safe reading
@@ -158,7 +160,8 @@ void _metadata_hub_read_epilog(const char *filename, const int linenumber);
*/
#define metadata_hub_modify_prolog(void) _metadata_hub_modify_prolog(__FILE__, __LINE__)
-#define metadata_hub_modify_epilog(modified) _metadata_hub_modify_epilog(modified, __FILE__, __LINE__)
+#define metadata_hub_modify_epilog(modified) \
+ _metadata_hub_modify_epilog(modified, __FILE__, __LINE__)
#define metadata_hub_read_prolog(void) _metadata_hub_read_prolog(__FILE__, __LINE__)
#define metadata_hub_read_epilog(void) _metadata_hub_modify_epilog(__FILE__, __LINE__)
diff --git a/player.c b/player.c
index edca685f..78912e3b 100644
--- a/player.c
+++ b/player.c
@@ -143,7 +143,6 @@ static void ab_resync(rtsp_conn_info *conn) {
conn->ab_buffering = 1;
}
-
// given starting and ending points as unsigned 16-bit integers running modulo 2^16, returns the
// position of x in the interval in *pos
// returns true if x is actually within the buffer
@@ -174,25 +173,21 @@ int position_in_modulo_uint16_t_buffer(uint16_t x, uint16_t start, uint16_t end,
return response;
}
-static inline seq_t SUCCESSOR(seq_t x) {
- return x + 1;
-}
+static inline seq_t SUCCESSOR(seq_t x) { return x + 1; }
// a minus b
int16_t seq_diff(seq_t a, seq_t b) {
- int16_t response;
- seq_t diff = a - b;
- seq_t invdiff = b - a;
- if (diff < invdiff)
- response = diff;
- else
- response = -invdiff;
+ int16_t response;
+ seq_t diff = a - b;
+ seq_t invdiff = b - a;
+ if (diff < invdiff)
+ response = diff;
+ else
+ response = -invdiff;
return response;
}
-static inline seq_t seq_sum(seq_t a, seq_t b) {
- return a + b;
-}
+static inline seq_t seq_sum(seq_t a, seq_t b) { return a + b; }
// This orders u and v by picking the smaller of the two modulo differences
// in unsigned modulo arithmetic and setting the sign of the result accordingly.
@@ -215,16 +210,15 @@ static inline seq_t seq_sum(seq_t a, seq_t b) {
// If that ever happens and there is a real ambiguity in the application,
// the modulo chosen is too small.
-
int64_t int64_mod_difference(const uint64_t u, const uint64_t v, const uint64_t modulo) {
- int64_t response;
- uint64_t diff = (u - v) % modulo;
- uint64_t invdiff = (v - u) % modulo;
- if (diff < invdiff)
- response = diff;
- else
- response = -invdiff;
- return response;
+ int64_t response;
+ uint64_t diff = (u - v) % modulo;
+ uint64_t invdiff = (v - u) % modulo;
+ if (diff < invdiff)
+ response = diff;
+ else
+ response = -invdiff;
+ return response;
}
void reset_input_flow_metrics(rtsp_conn_info *conn) {
@@ -466,172 +460,173 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
conn->packet_count_since_flush++;
conn->time_of_last_audio_packet = time_now;
if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
- abuf_t *abuf = 0;
- if (!conn->ab_synced) {
- // if this is the first packet...
- debug(3, "syncing to seqno %u.", seqno);
- conn->ab_write = seqno;
- conn->ab_read = seqno;
- conn->ab_synced = 1;
- }
- int16_t write_point_gap = seq_diff(seqno,conn->ab_write); // this is the difference between
- // the incoming packet number and the packet number that was expected.
- if (write_point_gap == 0) { // if this is the expected packet (which could be the first packet...)
- if (conn->input_frame_rate_starting_point_is_valid == 0) {
- if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) {
- conn->frames_inward_measurement_start_time = time_now;
- conn->frames_inward_frames_received_at_measurement_start_time = actual_timestamp;
- conn->input_frame_rate_starting_point_is_valid = 1; // valid now
- }
- }
- conn->frames_inward_measurement_time = time_now;
- conn->frames_inward_frames_received_at_measurement_time = actual_timestamp;
- abuf = conn->audio_buffer + BUFIDX(seqno);
- conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space
- } else if (write_point_gap > 0) { // newer than expected
- // initialise the frames in between
- int i;
- for (i = 0; i < write_point_gap; i++) {
- abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
- abuf->ready = 0; // to be sure, to be sure
- abuf->resend_request_number = 0;
- abuf->initialisation_time =
- time_now; // this represents when the packet was noticed to be missing
- abuf->status = 1 << 0; // signifying missing
- abuf->resend_time = 0;
- abuf->given_timestamp = 0;
- abuf->sequence_number = 0;
- }
- abuf = conn->audio_buffer + BUFIDX(seqno);
- conn->ab_write = SUCCESSOR(seqno);
- } else if (seq_diff(seqno,conn->ab_read) > 0) { // older than expected but still not too late
- conn->late_packets++;
- abuf = conn->audio_buffer + BUFIDX(seqno);
- } else { // too late.
- conn->too_late_packets++;
- }
+ abuf_t *abuf = 0;
+ if (!conn->ab_synced) {
+ // if this is the first packet...
+ debug(3, "syncing to seqno %u.", seqno);
+ conn->ab_write = seqno;
+ conn->ab_read = seqno;
+ conn->ab_synced = 1;
+ }
+ int16_t write_point_gap = seq_diff(seqno, conn->ab_write); // this is the difference between
+ // the incoming packet number and the packet number that was expected.
+ if (write_point_gap ==
+ 0) { // if this is the expected packet (which could be the first packet...)
+ if (conn->input_frame_rate_starting_point_is_valid == 0) {
+ if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) {
+ conn->frames_inward_measurement_start_time = time_now;
+ conn->frames_inward_frames_received_at_measurement_start_time = actual_timestamp;
+ conn->input_frame_rate_starting_point_is_valid = 1; // valid now
+ }
+ }
+ conn->frames_inward_measurement_time = time_now;
+ conn->frames_inward_frames_received_at_measurement_time = actual_timestamp;
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space
+ } else if (write_point_gap > 0) { // newer than expected
+ // initialise the frames in between
+ int i;
+ for (i = 0; i < write_point_gap; i++) {
+ abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
+ abuf->ready = 0; // to be sure, to be sure
+ abuf->resend_request_number = 0;
+ abuf->initialisation_time =
+ time_now; // this represents when the packet was noticed to be missing
+ abuf->status = 1 << 0; // signifying missing
+ abuf->resend_time = 0;
+ abuf->given_timestamp = 0;
+ abuf->sequence_number = 0;
+ }
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ conn->ab_write = SUCCESSOR(seqno);
+ } else if (seq_diff(seqno, conn->ab_read) > 0) { // older than expected but still not too late
+ conn->late_packets++;
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ } else { // too late.
+ conn->too_late_packets++;
+ }
- if (abuf) {
- int datalen = conn->max_frames_per_packet;
- abuf->initialisation_time = time_now;
- abuf->resend_time = 0;
- if (audio_packet_decode(abuf->data, &datalen, data, len, conn) == 0) {
- abuf->ready = 1;
- abuf->status = 0; // signifying that it was received
- abuf->length = datalen;
- abuf->given_timestamp = actual_timestamp;
- abuf->sequence_number = seqno;
- } else {
- debug(1, "Bad audio packet detected and discarded.");
- abuf->ready = 0;
- abuf->status = 1 << 1; // bad packet, discarded
- abuf->resend_request_number = 0;
- abuf->given_timestamp = 0;
- abuf->sequence_number = 0;
- }
- }
+ if (abuf) {
+ int datalen = conn->max_frames_per_packet;
+ abuf->initialisation_time = time_now;
+ abuf->resend_time = 0;
+ if (audio_packet_decode(abuf->data, &datalen, data, len, conn) == 0) {
+ abuf->ready = 1;
+ abuf->status = 0; // signifying that it was received
+ abuf->length = datalen;
+ abuf->given_timestamp = actual_timestamp;
+ abuf->sequence_number = seqno;
+ } else {
+ debug(1, "Bad audio packet detected and discarded.");
+ abuf->ready = 0;
+ abuf->status = 1 << 1; // bad packet, discarded
+ abuf->resend_request_number = 0;
+ abuf->given_timestamp = 0;
+ abuf->sequence_number = 0;
+ }
+ }
- int rc = pthread_cond_signal(&conn->flowcontrol);
- if (rc)
- debug(1, "Error signalling flowcontrol.");
+ int rc = pthread_cond_signal(&conn->flowcontrol);
+ if (rc)
+ debug(1, "Error signalling flowcontrol.");
- // resend checks
- {
- uint64_t minimum_wait_time =
- (uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000);
- uint64_t resend_repeat_interval =
- (uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000);
- uint64_t minimum_remaining_time = (uint64_t)(
- (config.resend_control_last_check_time + config.audio_backend_buffer_desired_length) *
- (uint64_t)1000000000);
- uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000);
- latency_time = latency_time / (uint64_t)conn->input_rate;
+ // resend checks
+ {
+ uint64_t minimum_wait_time =
+ (uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000);
+ uint64_t resend_repeat_interval =
+ (uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000);
+ uint64_t minimum_remaining_time = (uint64_t)(
+ (config.resend_control_last_check_time + config.audio_backend_buffer_desired_length) *
+ (uint64_t)1000000000);
+ uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000);
+ latency_time = latency_time / (uint64_t)conn->input_rate;
- int x; // this is the first frame to be checked
- // if we detected a first empty frame before and if it's still in the buffer!
- if ((first_possibly_missing_frame >= 0) &&
- (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read,
- conn->ab_write, NULL))) {
- x = first_possibly_missing_frame;
- } else {
- x = conn->ab_read;
- }
+ int x; // this is the first frame to be checked
+ // if we detected a first empty frame before and if it's still in the buffer!
+ if ((first_possibly_missing_frame >= 0) &&
+ (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read,
+ conn->ab_write, NULL))) {
+ x = first_possibly_missing_frame;
+ } else {
+ x = conn->ab_read;
+ }
- first_possibly_missing_frame = -1; // has not been set
+ first_possibly_missing_frame = -1; // has not been set
- int missing_frame_run_count = 0;
- int start_of_missing_frame_run = -1;
- int number_of_missing_frames = 0;
- while (x != conn->ab_write) {
- abuf_t *check_buf = conn->audio_buffer + BUFIDX(x);
- if (!check_buf->ready) {
- if (first_possibly_missing_frame < 0)
- first_possibly_missing_frame = x;
- number_of_missing_frames++;
- // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%"
- // PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x,
- // check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time);
- int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) ||
- ((check_buf->initialisation_time - (time_now - latency_time)) <
- minimum_remaining_time));
- int too_early = ((time_now - check_buf->initialisation_time) < minimum_wait_time);
- int too_soon_after_last_request =
- ((check_buf->resend_time != 0) &&
- ((time_now - check_buf->resend_time) <
- resend_repeat_interval)); // time_now can never be less than the time_tag
+ int missing_frame_run_count = 0;
+ int start_of_missing_frame_run = -1;
+ int number_of_missing_frames = 0;
+ while (x != conn->ab_write) {
+ abuf_t *check_buf = conn->audio_buffer + BUFIDX(x);
+ if (!check_buf->ready) {
+ if (first_possibly_missing_frame < 0)
+ first_possibly_missing_frame = x;
+ number_of_missing_frames++;
+ // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%"
+ // PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x,
+ // check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time);
+ int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) ||
+ ((check_buf->initialisation_time - (time_now - latency_time)) <
+ minimum_remaining_time));
+ int too_early = ((time_now - check_buf->initialisation_time) < minimum_wait_time);
+ int too_soon_after_last_request =
+ ((check_buf->resend_time != 0) &&
+ ((time_now - check_buf->resend_time) <
+ resend_repeat_interval)); // time_now can never be less than the time_tag
- if (too_late)
- check_buf->status |= 1 << 2; // too late
- else
- check_buf->status &= 0xFF - (1 << 2); // not too late
- if (too_early)
- check_buf->status |= 1 << 3; // too early
- else
- check_buf->status &= 0xFF - (1 << 3); // not too early
- if (too_soon_after_last_request)
- check_buf->status |= 1 << 4; // too soon after last request
- else
- check_buf->status &= 0xFF - (1 << 4); // not too soon after last request
+ if (too_late)
+ check_buf->status |= 1 << 2; // too late
+ else
+ check_buf->status &= 0xFF - (1 << 2); // not too late
+ if (too_early)
+ check_buf->status |= 1 << 3; // too early
+ else
+ check_buf->status &= 0xFF - (1 << 3); // not too early
+ if (too_soon_after_last_request)
+ check_buf->status |= 1 << 4; // too soon after last request
+ else
+ check_buf->status &= 0xFF - (1 << 4); // not too soon after last request
- if ((!too_soon_after_last_request) && (!too_late) && (!too_early)) {
- if (start_of_missing_frame_run == -1) {
- start_of_missing_frame_run = x;
- missing_frame_run_count = 1;
- } else {
- missing_frame_run_count++;
- }
- check_buf->resend_time = time_now; // setting the time to now because we are
- // definitely going to take action
- check_buf->resend_request_number++;
- debug(3, "Frame %d is missing with ab_read of %u and ab_write of %u.", x,
- conn->ab_read, conn->ab_write);
- }
- // if (too_late) {
- // debug(1,"too late to get missing frame %u.", x);
- // }
- }
- // if (number_of_missing_frames != 0)
- // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame
- // = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame);
- x = (x + 1) & 0xffff;
- if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) {
- // send a resend request
- if (missing_frame_run_count > 1)
- debug(3, "request resend of %d packets starting at seqno %u.",
- missing_frame_run_count, start_of_missing_frame_run);
- if (config.disable_resend_requests == 0) {
- debug_mutex_unlock(&conn->ab_mutex, 3);
- rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn);
- debug_mutex_lock(&conn->ab_mutex, 20000, 1);
- conn->resend_requests++;
- }
- start_of_missing_frame_run = -1;
- missing_frame_run_count = 0;
- }
- }
- if (number_of_missing_frames == 0)
- first_possibly_missing_frame = conn->ab_write;
- }
+ if ((!too_soon_after_last_request) && (!too_late) && (!too_early)) {
+ if (start_of_missing_frame_run == -1) {
+ start_of_missing_frame_run = x;
+ missing_frame_run_count = 1;
+ } else {
+ missing_frame_run_count++;
+ }
+ check_buf->resend_time = time_now; // setting the time to now because we are
+ // definitely going to take action
+ check_buf->resend_request_number++;
+ debug(3, "Frame %d is missing with ab_read of %u and ab_write of %u.", x, conn->ab_read,
+ conn->ab_write);
+ }
+ // if (too_late) {
+ // debug(1,"too late to get missing frame %u.", x);
+ // }
+ }
+ // if (number_of_missing_frames != 0)
+ // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame
+ // = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame);
+ x = (x + 1) & 0xffff;
+ if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) {
+ // send a resend request
+ if (missing_frame_run_count > 1)
+ debug(3, "request resend of %d packets starting at seqno %u.", missing_frame_run_count,
+ start_of_missing_frame_run);
+ if (config.disable_resend_requests == 0) {
+ debug_mutex_unlock(&conn->ab_mutex, 3);
+ rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn);
+ debug_mutex_lock(&conn->ab_mutex, 20000, 1);
+ conn->resend_requests++;
+ }
+ start_of_missing_frame_run = -1;
+ missing_frame_run_count = 0;
+ }
+ }
+ if (number_of_missing_frames == 0)
+ first_possibly_missing_frame = conn->ab_write;
+ }
}
debug_mutex_unlock(&conn->ab_mutex, 0);
}
@@ -944,77 +939,98 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
debug_mutex_lock(&conn->flush_mutex, 1000, 0);
if (conn->flush_requested == 1) {
- if (conn->flush_output_flushed == 0)
- if (config.output->flush) {
- config.output->flush(); // no cancellation points
- debug(2, "flush request: flush output device.");
+ if (conn->flush_output_flushed == 0)
+ if (config.output->flush) {
+ config.output->flush(); // no cancellation points
+ debug(2, "flush request: flush output device.");
}
conn->flush_output_flushed = 1;
}
- // now check to see it the flush request is for frames in the buffer or not
- // if the first_packet_timestamp is zero, don't check
- int flush_needed = 0;
- int drop_request = 0;
- if ((conn->flush_requested == 1) && (conn->flush_rtp_timestamp == 0)) {
- debug(1, "flush request: flush frame 0 -- flush assumed to be needed.");
- flush_needed = 1;
- drop_request = 1;
- } else if (conn->flush_rtp_timestamp != 0) {
- if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) {
- abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read);
- abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1);
- if ((firstPacket != NULL) && (firstPacket->ready)) {
- // discard flushes more than 10 seconds into the future -- they are probably bogus
- uint32_t first_frame_in_buffer = firstPacket->given_timestamp;
- int32_t offset_from_first_frame = (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer);
- if (offset_from_first_frame > (int)conn->input_rate * 10) {
- debug(1, "flush request: sanity check -- flush frame %u is too far into the future from the first frame %u -- discarded.", conn->flush_rtp_timestamp, first_frame_in_buffer);
- drop_request = 1;
- } else {
- if ((lastPacket != NULL) && (lastPacket->ready)) {
- // we have enough information to check if the flush is needed or can be discarded
- uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1;
- // now we have to work out if the flush frame is in the buffer
- // if it is later than the end of the buffer, flush everything and keep the request active.
- // if it is in the buffer, we need to flush part of the buffer. Actually we flush the entire buffer and drop the request.
- // if it is before the buffer, no flush is needed. Drop the request.
- if (offset_from_first_frame > 0) {
- int32_t offset_to_last_frame = (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp);
- if (offset_to_last_frame >= 0) {
- debug(2,"flush request: flush frame %u active -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
- drop_request = 1;
- flush_needed = 1;
- } else {
- debug(2,"flush request: flush frame %u pending -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
- flush_needed = 1;
- }
- } else {
- debug(2,"flush request: flush frame %u expired -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer);
- drop_request = 1;
- }
- }
- }
- }
- } else {
- debug(3, "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: %u, ab_write: %u", conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write);
- // leave flush request pending and don't do a buffer flush, because there isn't one
- }
- }
- if (flush_needed) {
- debug(2, "flush request: flush done.");
- ab_resync(conn); // no cancellation points
- conn->first_packet_timestamp = 0;
- conn->first_packet_time_to_play = 0;
- conn->time_since_play_started = 0;
- have_sent_prefiller_silence = 0;
- dac_delay = 0;
- }
- if (drop_request) {
- debug(2, "flush request: request dropped.");
- conn->flush_requested = 0;
- conn->flush_rtp_timestamp = 0;
- conn->flush_output_flushed = 0;
- }
+ // now check to see it the flush request is for frames in the buffer or not
+ // if the first_packet_timestamp is zero, don't check
+ int flush_needed = 0;
+ int drop_request = 0;
+ if ((conn->flush_requested == 1) && (conn->flush_rtp_timestamp == 0)) {
+ debug(1, "flush request: flush frame 0 -- flush assumed to be needed.");
+ flush_needed = 1;
+ drop_request = 1;
+ } else if (conn->flush_rtp_timestamp != 0) {
+ if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) {
+ abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read);
+ abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1);
+ if ((firstPacket != NULL) && (firstPacket->ready)) {
+ // discard flushes more than 10 seconds into the future -- they are probably bogus
+ uint32_t first_frame_in_buffer = firstPacket->given_timestamp;
+ int32_t offset_from_first_frame =
+ (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer);
+ if (offset_from_first_frame > (int)conn->input_rate * 10) {
+ debug(1,
+ "flush request: sanity check -- flush frame %u is too far into the future from "
+ "the first frame %u -- discarded.",
+ conn->flush_rtp_timestamp, first_frame_in_buffer);
+ drop_request = 1;
+ } else {
+ if ((lastPacket != NULL) && (lastPacket->ready)) {
+ // we have enough information to check if the flush is needed or can be discarded
+ uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1;
+ // now we have to work out if the flush frame is in the buffer
+ // if it is later than the end of the buffer, flush everything and keep the request
+ // active. if it is in the buffer, we need to flush part of the buffer. Actually we
+ // flush the entire buffer and drop the request. if it is before the buffer, no flush
+ // is needed. Drop the request.
+ if (offset_from_first_frame > 0) {
+ int32_t offset_to_last_frame =
+ (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp);
+ if (offset_to_last_frame >= 0) {
+ debug(2,
+ "flush request: flush frame %u active -- buffer contains %u frames, from "
+ "%u to %u",
+ conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1,
+ first_frame_in_buffer, last_frame_in_buffer);
+ drop_request = 1;
+ flush_needed = 1;
+ } else {
+ debug(2,
+ "flush request: flush frame %u pending -- buffer contains %u frames, from "
+ "%u to %u",
+ conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1,
+ first_frame_in_buffer, last_frame_in_buffer);
+ flush_needed = 1;
+ }
+ } else {
+ debug(2,
+ "flush request: flush frame %u expired -- buffer contains %u frames, from %u "
+ "to %u",
+ conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1,
+ first_frame_in_buffer, last_frame_in_buffer);
+ drop_request = 1;
+ }
+ }
+ }
+ }
+ } else {
+ debug(3,
+ "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: "
+ "%u, ab_write: %u",
+ conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write);
+ // leave flush request pending and don't do a buffer flush, because there isn't one
+ }
+ }
+ if (flush_needed) {
+ debug(2, "flush request: flush done.");
+ ab_resync(conn); // no cancellation points
+ conn->first_packet_timestamp = 0;
+ conn->first_packet_time_to_play = 0;
+ conn->time_since_play_started = 0;
+ have_sent_prefiller_silence = 0;
+ dac_delay = 0;
+ }
+ if (drop_request) {
+ debug(2, "flush request: request dropped.");
+ conn->flush_requested = 0;
+ conn->flush_rtp_timestamp = 0;
+ conn->flush_output_flushed = 0;
+ }
debug_mutex_unlock(&conn->flush_mutex, 0);
if (conn->ab_synced) {
curframe = conn->audio_buffer + BUFIDX(conn->ab_read);
@@ -1037,7 +1053,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
}
}
-
if ((curframe) && (curframe->ready)) {
notified_buffer_empty = 0; // at least one buffer now -- diagnostic only.
if (conn->ab_buffering) { // if we are getting packets but not yet forwarding them to the
@@ -1058,7 +1073,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// Here, calculate when we should start playing. We need to know when to allow the
// packets to be sent to the player.
-
// every second or so, we get a reference on when a particular packet should be
// played.
@@ -1093,8 +1107,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
&should_be_time, conn);
conn->first_packet_time_to_play = should_be_time;
- debug(2,"first_packet_time set for frame %u.", conn->first_packet_timestamp);
-
+ debug(2, "first_packet_time set for frame %u.", conn->first_packet_timestamp);
if (local_time_now > conn->first_packet_time_to_play) {
uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
@@ -1105,172 +1118,180 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
}
}
-
-
if (conn->first_packet_time_to_play != 0) {
- // Now that we know the timing of the first packet...
- if (config.output->delay) {
- // and that the output device is capable of synchronization...
+ // Now that we know the timing of the first packet...
+ if (config.output->delay) {
+ // and that the output device is capable of synchronization...
- // We may send packets of
- // silence from now until the time the first audio packet should be sent
- // and then we will send the first packet, which will be followed by
- // the subsequent packets.
- // here, we figure out whether and what silence to send.
+ // We may send packets of
+ // silence from now until the time the first audio packet should be sent
+ // and then we will send the first packet, which will be followed by
+ // the subsequent packets.
+ // here, we figure out whether and what silence to send.
- uint64_t should_be_time;
- uint32_t effective_latency = conn->latency;
+ uint64_t should_be_time;
+ uint32_t effective_latency = conn->latency;
- switch (get_and_check_effective_latency(conn, &effective_latency,
- config.audio_backend_latency_offset)) {
- case -1:
- if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
- warn("Negative latency! A latency of %d frames requested by the player, when "
- "combined with an audio_backend_latency_offset of %f seconds, would make the "
- "overall latency negative. The audio_backend_latency_offset setting is "
- "ignored.",
- conn->latency, config.audio_backend_latency_offset);
- config.audio_backend_latency_offset = 0; // set it to zero
- conn->unachievable_audio_backend_latency_offset_notified = 1;
- };
- break;
- case 1:
- if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
- warn("An audio_backend_latency_offset of %f seconds may exceed the frame buffering "
- "capacity -- the setting is ignored.",
- config.audio_backend_latency_offset);
- config.audio_backend_latency_offset = 0; // set it to zero;
- conn->unachievable_audio_backend_latency_offset_notified = 1;
- };
- break;
- default:
- break;
- }
+ switch (get_and_check_effective_latency(conn, &effective_latency,
+ config.audio_backend_latency_offset)) {
+ case -1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn(
+ "Negative latency! A latency of %d frames requested by the player, when "
+ "combined with an audio_backend_latency_offset of %f seconds, would make the "
+ "overall latency negative. The audio_backend_latency_offset setting is "
+ "ignored.",
+ conn->latency, config.audio_backend_latency_offset);
+ config.audio_backend_latency_offset = 0; // set it to zero
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ break;
+ case 1:
+ if (conn->unachievable_audio_backend_latency_offset_notified == 0) {
+ warn("An audio_backend_latency_offset of %f seconds may exceed the frame "
+ "buffering "
+ "capacity -- the setting is ignored.",
+ config.audio_backend_latency_offset);
+ config.audio_backend_latency_offset = 0; // set it to zero;
+ conn->unachievable_audio_backend_latency_offset_notified = 1;
+ };
+ break;
+ default:
+ break;
+ }
- // readjust first packet time to play
- frame_to_local_time(conn->first_packet_timestamp +
- effective_latency, // this will go modulo 2^32
- &should_be_time, conn);
+ // readjust first packet time to play
+ frame_to_local_time(conn->first_packet_timestamp +
+ effective_latency, // this will go modulo 2^32
+ &should_be_time, conn);
- int64_t change_in_should_be_time = (int64_t)(should_be_time - conn->first_packet_time_to_play);
+ int64_t change_in_should_be_time =
+ (int64_t)(should_be_time - conn->first_packet_time_to_play);
- if (fabs(0.000001*change_in_should_be_time) > 0.001) // the clock drift estimation might be nudging the estimate, and we can ignore this unless if's more than a microsecond
- debug(2,"Change in estimated first_packet_time: %8.4f milliseconds.", 0.000001*change_in_should_be_time);
+ if (fabs(0.000001 * change_in_should_be_time) >
+ 0.001) // the clock drift estimation might be nudging the estimate, and we can
+ // ignore this unless if's more than a microsecond
+ debug(2, "Change in estimated first_packet_time: %8.4f milliseconds.",
+ 0.000001 * change_in_should_be_time);
- conn->first_packet_time_to_play = should_be_time;
+ conn->first_packet_time_to_play = should_be_time;
- if (local_time_now > conn->first_packet_time_to_play) {
- uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
- debug(2, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness);
- conn->ab_buffering = 0;
- } else {
- // do some calculations
- int64_t lead_time = conn->first_packet_time_to_play - local_time_now;
- if ((config.audio_backend_silent_lead_in_time_auto == 1) ||
- (lead_time <=
- (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) {
- // debug(1, "Lead time: %" PRId64 " nanoseconds.", lead_time);
- int resp = 0;
- dac_delay = 0;
- if (have_sent_prefiller_silence != 0)
- resp = config.output->delay(&dac_delay); // we know the output device must have a delay function
- if (resp == 0) {
- int64_t gross_frame_gap =
- ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) /
- 1000000000;
- int64_t exact_frame_gap = gross_frame_gap - dac_delay;
- int64_t frames_needed_to_maintain_desired_buffer =
- (int64_t)(config.audio_backend_buffer_desired_length * config.output_rate) -
- dac_delay;
- // below, remember that exact_frame_gap and
- // frames_needed_to_maintain_desired_buffer could both be negative
- int64_t fs = frames_needed_to_maintain_desired_buffer;
+ if (local_time_now > conn->first_packet_time_to_play) {
+ uint64_t lateness = local_time_now - conn->first_packet_time_to_play;
+ debug(2, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness);
+ conn->ab_buffering = 0;
+ } else {
+ // do some calculations
+ int64_t lead_time = conn->first_packet_time_to_play - local_time_now;
+ if ((config.audio_backend_silent_lead_in_time_auto == 1) ||
+ (lead_time <=
+ (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) {
+ // debug(1, "Lead time: %" PRId64 " nanoseconds.", lead_time);
+ int resp = 0;
+ dac_delay = 0;
+ if (have_sent_prefiller_silence != 0)
+ resp = config.output->delay(
+ &dac_delay); // we know the output device must have a delay function
+ if (resp == 0) {
+ int64_t gross_frame_gap =
+ ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) /
+ 1000000000;
+ int64_t exact_frame_gap = gross_frame_gap - dac_delay;
+ int64_t frames_needed_to_maintain_desired_buffer =
+ (int64_t)(config.audio_backend_buffer_desired_length * config.output_rate) -
+ dac_delay;
+ // below, remember that exact_frame_gap and
+ // frames_needed_to_maintain_desired_buffer could both be negative
+ int64_t fs = frames_needed_to_maintain_desired_buffer;
- // if there isn't enough time to have the desired buffer size
- if (exact_frame_gap <= frames_needed_to_maintain_desired_buffer) {
- fs = conn->max_frames_per_packet * 2;
- }
+ // if there isn't enough time to have the desired buffer size
+ if (exact_frame_gap <= frames_needed_to_maintain_desired_buffer) {
+ fs = conn->max_frames_per_packet * 2;
+ }
- // if we are very close to the end of buffering, i.e. within two frame-lengths,
- // add the remaining silence needed and end buffering
- if (exact_frame_gap <= conn->max_frames_per_packet * 2) {
- fs = exact_frame_gap;
- if (fs > first_frame_early_bias)
- fs = fs - first_frame_early_bias; // deliberately make the first packet a tiny bit early so that the player may compensate for it at the last minute
- conn->ab_buffering = 0;
- }
- void *silence;
- if (fs > 0) {
- silence = malloc(conn->output_bytes_per_frame * fs);
- if (silence == NULL)
- debug(1, "Failed to allocate %d byte silence buffer.", fs);
- else {
- // generate frames of silence with dither if necessary
- conn->previous_random_number =
- generate_zero_frames(silence, fs, config.output_format,
- conn->enable_dither, conn->previous_random_number);
- config.output->play(silence, fs);
- // debug(1, "Sent %" PRId64 " frames of silence", fs);
- free(silence);
- have_sent_prefiller_silence = 1;
- }
- }
- } else {
+ // if we are very close to the end of buffering, i.e. within two frame-lengths,
+ // add the remaining silence needed and end buffering
+ if (exact_frame_gap <= conn->max_frames_per_packet * 2) {
+ fs = exact_frame_gap;
+ if (fs > first_frame_early_bias)
+ fs = fs - first_frame_early_bias; // deliberately make the first packet a
+ // tiny bit early so that the player may
+ // compensate for it at the last minute
+ conn->ab_buffering = 0;
+ }
+ void *silence;
+ if (fs > 0) {
+ silence = malloc(conn->output_bytes_per_frame * fs);
+ if (silence == NULL)
+ debug(1, "Failed to allocate %d byte silence buffer.", fs);
+ else {
+ // generate frames of silence with dither if necessary
+ conn->previous_random_number =
+ generate_zero_frames(silence, fs, config.output_format,
+ conn->enable_dither, conn->previous_random_number);
+ config.output->play(silence, fs);
+ // debug(1, "Sent %" PRId64 " frames of silence", fs);
+ free(silence);
+ have_sent_prefiller_silence = 1;
+ }
+ }
+ } else {
- if (resp == sps_extra_code_output_stalled) {
- if (conn->unfixable_error_reported == 0) {
- conn->unfixable_error_reported = 1;
- if (config.cmd_unfixable) {
- command_execute(config.cmd_unfixable, "output_device_stalled", 1);
- } else {
- warn("an unrecoverable error, \"output_device_stalled\", has been "
- "detected.",
- conn->connection_number);
- }
- }
- } else {
- debug(2, "Unexpected response to getting dac delay: %d.", resp);
- }
- }
- }
- }
- } else {
- // if the output device doesn't have a delay, we simply send the lead-in
- int64_t lead_time = conn->first_packet_time_to_play - local_time_now; // negative if we are late
- void *silence;
- int64_t frame_gap = (lead_time * config.output_rate) / 1000000000;
- // debug(1,"%d frames needed.",frame_gap);
- while (frame_gap > 0) {
- ssize_t fs = config.output_rate / 10;
- if (fs > frame_gap)
- fs = frame_gap;
+ if (resp == sps_extra_code_output_stalled) {
+ if (conn->unfixable_error_reported == 0) {
+ conn->unfixable_error_reported = 1;
+ if (config.cmd_unfixable) {
+ command_execute(config.cmd_unfixable, "output_device_stalled", 1);
+ } else {
+ warn("an unrecoverable error, \"output_device_stalled\", has been "
+ "detected.",
+ conn->connection_number);
+ }
+ }
+ } else {
+ debug(2, "Unexpected response to getting dac delay: %d.", resp);
+ }
+ }
+ }
+ }
+ } else {
+ // if the output device doesn't have a delay, we simply send the lead-in
+ int64_t lead_time =
+ conn->first_packet_time_to_play - local_time_now; // negative if we are late
+ void *silence;
+ int64_t frame_gap = (lead_time * config.output_rate) / 1000000000;
+ // debug(1,"%d frames needed.",frame_gap);
+ while (frame_gap > 0) {
+ ssize_t fs = config.output_rate / 10;
+ if (fs > frame_gap)
+ fs = frame_gap;
- silence = malloc(conn->output_bytes_per_frame * fs);
- if (silence == NULL)
- debug(1, "Failed to allocate %d frame silence buffer.", fs);
- else {
- // debug(1, "No delay function -- outputting %d frames of silence.", fs);
- conn->previous_random_number =
- generate_zero_frames(silence, fs, config.output_format,
- conn->enable_dither, conn->previous_random_number);
- config.output->play(silence, fs);
- free(silence);
- }
- frame_gap -= fs;
- }
- conn->ab_buffering = 0;
- }
- }
-#ifdef CONFIG_METADATA
- if (conn->ab_buffering == 0) {
- debug(2, "prsm");
- send_ssnc_metadata('prsm', NULL, 0,
- 0); // "resume", but don't wait if the queue is locked
- }
-#endif
+ silence = malloc(conn->output_bytes_per_frame * fs);
+ if (silence == NULL)
+ debug(1, "Failed to allocate %d frame silence buffer.", fs);
+ else {
+ // debug(1, "No delay function -- outputting %d frames of silence.", fs);
+ conn->previous_random_number =
+ generate_zero_frames(silence, fs, config.output_format, conn->enable_dither,
+ conn->previous_random_number);
+ config.output->play(silence, fs);
+ free(silence);
+ }
+ frame_gap -= fs;
+ }
+ conn->ab_buffering = 0;
+ }
}
+#ifdef CONFIG_METADATA
+ if (conn->ab_buffering == 0) {
+ debug(2, "prsm");
+ send_ssnc_metadata('prsm', NULL, 0,
+ 0); // "resume", but don't wait if the queue is locked
+ }
+#endif
}
}
+ }
// Here, we work out whether to release a packet or wait
// We release a packet when the time is right.
@@ -1665,8 +1686,8 @@ void player_thread_cleanup_handler(void *arg) {
}
if (conn->statistics) {
- free(conn->statistics);
- conn->statistics = NULL;
+ free(conn->statistics);
+ conn->statistics = NULL;
}
free_audio_buffers(conn);
if (conn->stream.type == ast_apple_lossless)
@@ -1690,8 +1711,8 @@ void *player_thread_func(void *arg) {
conn->first_packet_timestamp = 0;
conn->flush_requested = 0;
conn->flush_output_flushed = 0; // only send a flush command to the output device once
- conn->flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to
- // be used as a null operand, so we'll use it like that too
+ conn->flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to
+ // be used as a null operand, so we'll use it like that too
conn->fix_volume = 0x10000;
if (conn->latency == 0) {
@@ -1902,9 +1923,9 @@ void *player_thread_func(void *arg) {
int sync_error_out_of_bounds =
0; // number of times in a row that there's been a serious sync error
- conn->statistics = malloc(sizeof(stats_t)*trend_interval);
+ conn->statistics = malloc(sizeof(stats_t) * trend_interval);
if (conn->statistics == NULL)
- die("Failed to allocate a statistics buffer");
+ die("Failed to allocate a statistics buffer");
conn->framesProcessedInThisEpoch = 0;
conn->framesGeneratedInThisEpoch = 0;
@@ -2002,7 +2023,8 @@ void *player_thread_func(void *arg) {
"status 0x%X after %u resend requests.",
SUCCESSOR(conn->last_seqno_read), play_number, inframe->status,
inframe->resend_request_number);
- conn->last_seqno_read = SUCCESSOR(conn->last_seqno_read); // manage the packet out of sequence minder
+ conn->last_seqno_read =
+ SUCCESSOR(conn->last_seqno_read); // manage the packet out of sequence minder
void *silence = malloc(conn->output_bytes_per_frame * conn->max_frames_per_packet *
conn->output_sample_ratio);
@@ -2112,8 +2134,7 @@ void *player_thread_func(void *arg) {
die("Shairport Sync only supports 16 bit input");
}
-
- at_least_one_frame_seen = 1;
+ at_least_one_frame_seen = 1;
// We have a frame of data. We need to see if we want to add or remove a frame from it to
// keep in sync.
@@ -2166,8 +2187,7 @@ void *player_thread_func(void *arg) {
}
}
- conn->buffer_occupancy =
- seq_diff(conn->ab_write, conn->ab_read); // int32_t from int16_t
+ conn->buffer_occupancy = seq_diff(conn->ab_write, conn->ab_read); // int32_t from int16_t
if (conn->buffer_occupancy < minimum_buffer_occupancy)
minimum_buffer_occupancy = conn->buffer_occupancy;
@@ -2175,9 +2195,6 @@ void *player_thread_func(void *arg) {
if (conn->buffer_occupancy > maximum_buffer_occupancy)
maximum_buffer_occupancy = conn->buffer_occupancy;
-
-
-
// here, we want to check (a) if we are meant to do synchronisation,
// (b) if we have a delay procedure, (c) if we can get the delay.
@@ -2191,7 +2208,7 @@ void *player_thread_func(void *arg) {
if (resp == 0) { // no error
current_delay = l_delay;
if (l_delay >= 0)
- current_delay = l_delay;
+ current_delay = l_delay;
else {
debug(2, "Underrun of %ld frames reported, but ignored.", l_delay);
current_delay =
@@ -2229,15 +2246,16 @@ void *player_thread_func(void *arg) {
local_time_to_frame(local_time_now, &should_be_frame_32, conn);
// int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio;
- int64_t delay = int64_mod_difference(should_be_frame_32 * conn->output_sample_ratio, nt - current_delay, UINT32_MAX * conn->output_sample_ratio);
+ int64_t delay =
+ int64_mod_difference(should_be_frame_32 * conn->output_sample_ratio,
+ nt - current_delay, UINT32_MAX * conn->output_sample_ratio);
- //int64_t delay = should_be_frame - (nt - current_delay); // all int64_t
+ // int64_t delay = should_be_frame - (nt - current_delay); // all int64_t
// the original frame numbers are unsigned 32-bit integers that roll over modulo 2^32
- // hence these delay figures will be unsigned numbers that roll over modulo 2^32 * conn->output_sample_ratio;
- // therefore, calculating the delay must be done in the light of possible rollover
-
-
+ // hence these delay figures will be unsigned numbers that roll over modulo 2^32 *
+ // conn->output_sample_ratio; therefore, calculating the delay must be done in the light
+ // of possible rollover
sync_error =
delay - ((int64_t)conn->latency * conn->output_sample_ratio +
@@ -2245,38 +2263,51 @@ void *player_thread_func(void *arg) {
config.output_rate)); // int64_t from int64_t - int32_t, so okay
if (at_least_one_frame_seen_this_session == 0) {
- at_least_one_frame_seen_this_session = 1;
+ at_least_one_frame_seen_this_session = 1;
- // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", sync_error);
+ // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.",
+ // sync_error);
- // this is a sneaky attempt to make a final adjustment to the timing of the first packet
+ // this is a sneaky attempt to make a final adjustment to the timing of the first
+ // packet
- // the very first packet generally has a first_frame_early_bias subtracted from its timing
- // to make it more likely that it will be early than late,
- // making it possible to compensate for it be adding a few frames of silence.
+ // the very first packet generally has a first_frame_early_bias subtracted from its
+ // timing to make it more likely that it will be early than late, making it possible
+ // to compensate for it be adding a few frames of silence.
- // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", sync_error);
+ // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.",
+ // sync_error);
- // remove the bias when reporting the error to make it the true error
+ // remove the bias when reporting the error to make it the true error
- debug(2,"first frame sync error (positive --> late): %" PRId64 " frames, %.3f mS at %d frames per second output.", sync_error+first_frame_early_bias, (1000.0*(sync_error+first_frame_early_bias))/config.output_rate, config.output_rate);
+ debug(2,
+ "first frame sync error (positive --> late): %" PRId64
+ " frames, %.3f mS at %d frames per second output.",
+ sync_error + first_frame_early_bias,
+ (1000.0 * (sync_error + first_frame_early_bias)) / config.output_rate,
+ config.output_rate);
- // if the packet is early, add the frames needed to put it in sync.
- if (sync_error < 0) {
- size_t final_adjustment_length_sized = -sync_error;
- char *final_adjustment_silence = malloc(conn->output_bytes_per_frame * final_adjustment_length_sized);
+ // if the packet is early, add the frames needed to put it in sync.
+ if (sync_error < 0) {
+ size_t final_adjustment_length_sized = -sync_error;
+ char *final_adjustment_silence =
+ malloc(conn->output_bytes_per_frame * final_adjustment_length_sized);
if (final_adjustment_silence) {
- conn->previous_random_number =
- generate_zero_frames(final_adjustment_silence, final_adjustment_length_sized, config.output_format,
- conn->enable_dither, conn->previous_random_number);
+ conn->previous_random_number = generate_zero_frames(
+ final_adjustment_silence, final_adjustment_length_sized, config.output_format,
+ conn->enable_dither, conn->previous_random_number);
int final_adjustment = -sync_error;
final_adjustment = final_adjustment - first_frame_early_bias;
- debug(2, "final sync adjustment: %" PRId64 " silent frames added with a bias of %" PRId64 " frames.", -sync_error, first_frame_early_bias);
+ debug(2,
+ "final sync adjustment: %" PRId64
+ " silent frames added with a bias of %" PRId64 " frames.",
+ -sync_error, first_frame_early_bias);
config.output->play(final_adjustment_silence, final_adjustment_length_sized);
free(final_adjustment_silence);
} else {
- warn("Failed to allocate memory for a final_adjustment_silence buffer of %d frames for a "
+ warn("Failed to allocate memory for a final_adjustment_silence buffer of %d "
+ "frames for a "
"sync error of %d frames.",
final_adjustment_length_sized, sync_error);
}
@@ -2560,30 +2591,30 @@ void *player_thread_func(void *arg) {
// if there is no delay procedure, then we should be sending the packet
// to the output at the time determined by
// the packet's time to play + requested latency + requested offset.
-/*
- // This is just for checking during development
+ /*
+ // This is just for checking during
+ development
- uint32_t should_be_frame_32;
- local_time_to_frame(local_time_now, &should_be_frame_32, conn);
- // int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio;
+ uint32_t should_be_frame_32;
+ local_time_to_frame(local_time_now, &should_be_frame_32, conn);
+ // int64_t should_be_frame = ((int64_t)should_be_frame_32) *
+ conn->output_sample_ratio;
- int32_t ilatency = (int32_t)((config.audio_backend_latency_offset - config.audio_backend_buffer_desired_length) * conn->input_rate) + conn->latency;
- if (ilatency < 0)
- debug(1,"incorrect latency %d.", ilatency);
+ int32_t ilatency = (int32_t)((config.audio_backend_latency_offset -
+ config.audio_backend_buffer_desired_length) * conn->input_rate) + conn->latency; if
+ (ilatency < 0) debug(1,"incorrect latency %d.", ilatency);
- int32_t idelay = (int32_t)(should_be_frame_32 - inframe->given_timestamp);
+ int32_t idelay = (int32_t)(should_be_frame_32 - inframe->given_timestamp);
- idelay = idelay - ilatency;
+ idelay = idelay - ilatency;
- debug(2,"delay is %d input frames.", idelay);
-*/
+ debug(2,"delay is %d input frames.", idelay);
+ */
// if this is the first frame, see if it's close to when it's supposed to be
// release, which will be its time plus latency and any offset_time
if (at_least_one_frame_seen_this_session == 0) {
- at_least_one_frame_seen_this_session = 1;
-
-
+ at_least_one_frame_seen_this_session = 1;
}
play_samples =
@@ -2779,11 +2810,10 @@ void *player_thread_func(void *arg) {
"%*.2f," /* source actual (average) frame rate */
"%*.2f," /* source clock drift */
"%*d", /* source clock drift sample count */
- 12, play_number,
- 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets,
- 7, conn->resend_requests, 5, minimum_buffer_occupancy, 5,
- maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11,
- conn->input_frame_rate, 10,
+ 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7,
+ conn->too_late_packets, 7, conn->resend_requests, 5,
+ minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11,
+ conn->remote_frame_rate, 11, conn->input_frame_rate, 10,
(conn->local_to_remote_time_gradient - 1.0) * 1000000, 6,
conn->local_to_remote_time_gradient_sample_count);
}
@@ -2791,7 +2821,7 @@ void *player_thread_func(void *arg) {
inform("No frames received in the last sampling interval.");
}
}
- minimum_dac_queue_size = UINT64_MAX; // hack reset
+ minimum_dac_queue_size = UINT64_MAX; // hack reset
maximum_buffer_occupancy = INT32_MIN; // can't be less than this
minimum_buffer_occupancy = INT32_MAX; // can't be more than this
at_least_one_frame_seen = 0;
@@ -2997,17 +3027,17 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
// here, send the 'pvol' metadata message when the airplay volume information
// is being used by shairport sync to control the output volume
char dv[128];
- memset(dv, 0, 128);
- if (volume_mode == vol_both) {
- // normalise the maximum output to the hardware device's max output
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume,
- (scaled_attenuation - max_db + hw_max_db) / 100.0,
- (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
- } else {
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
- min_db / 100.0, max_db / 100.0);
- }
- send_ssnc_metadata('pvol', dv, strlen(dv), 1);
+ memset(dv, 0, 128);
+ if (volume_mode == vol_both) {
+ // normalise the maximum output to the hardware device's max output
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume,
+ (scaled_attenuation - max_db + hw_max_db) / 100.0,
+ (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
+ } else {
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
+ min_db / 100.0, max_db / 100.0);
+ }
+ send_ssnc_metadata('pvol', dv, strlen(dv), 1);
#endif
if (config.output->mute)
@@ -3026,10 +3056,10 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
else {
// here, send the 'pvol' metadata message when the airplay volume information
// is being used by shairport sync to control the output volume
- char dv[128];
- memset(dv, 0, 128);
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
- send_ssnc_metadata('pvol', dv, strlen(dv), 1);
+ char dv[128];
+ memset(dv, 0, 128);
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
+ send_ssnc_metadata('pvol', dv, strlen(dv), 1);
}
#endif
@@ -3057,10 +3087,10 @@ void player_flush(uint32_t timestamp, rtsp_conn_info *conn) {
debug(3, "player_flush");
do_flush(timestamp, conn);
#ifdef CONFIG_METADATA
- debug(2, "pfls");
- char numbuf[32];
- snprintf(numbuf, sizeof(numbuf),"%u",timestamp);
- send_ssnc_metadata('pfls', numbuf, strlen(numbuf), 1); // contains cancellation points
+ debug(2, "pfls");
+ char numbuf[32];
+ snprintf(numbuf, sizeof(numbuf), "%u", timestamp);
+ send_ssnc_metadata('pfls', numbuf, strlen(numbuf), 1); // contains cancellation points
#endif
}
diff --git a/player.h b/player.h
index f6b86cd4..105ce869 100644
--- a/player.h
+++ b/player.h
@@ -25,7 +25,9 @@
#include "audio.h"
#define time_ping_history_power_of_two 7
-#define time_ping_history (1 << time_ping_history_power_of_two) // 2^7 is 128. At 1 per three seconds, approximately six minutes of records
+#define time_ping_history \
+ (1 << time_ping_history_power_of_two) // 2^7 is 128. At 1 per three seconds, approximately six
+ // minutes of records
typedef struct time_ping_record {
uint64_t dispersion;
@@ -195,7 +197,6 @@ typedef struct {
// could be one of many, so we need to know it
uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope
short connection_ip_family; // AF_INET / AF_INET6
- uint32_t client_active_remote; // used when you want to control the client...
SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client
SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client
@@ -263,8 +264,8 @@ typedef struct {
char *dacp_id; // id of the client -- used to find the port to be used
// uint16_t dacp_port; // port on the client to send remote control messages to, else
// zero
- uint32_t dacp_active_remote; // key to send to the remote controller
- void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled.
+ char *dacp_active_remote; // key to send to the remote controller
+ void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled.
int enable_dither; // needed for filling silences before play actually starts
uint64_t dac_buffer_queue_minimum_length;
diff --git a/rtp.c b/rtp.c
index e56c5454..c558645e 100644
--- a/rtp.c
+++ b/rtp.c
@@ -46,9 +46,9 @@
#include
struct Nvll {
- char* name;
- double value;
- struct Nvll *next;
+ char *name;
+ double value;
+ struct Nvll *next;
};
typedef struct Nvll nvll;
@@ -263,8 +263,8 @@ void *rtp_control_receiver(void *arg) {
obfp += 2;
};
*obfp = 0;
-
-
+
+
// get raw timestamp information
// I think that a good way to understand these timestamps is that
// (1) the rtlt below is the timestamp of the frame that should be playing at the
@@ -275,19 +275,19 @@ void *rtp_control_receiver(void *arg) {
// Thus, (3) the latency can be calculated by subtracting the second from the
// first.
// There must be more to it -- there something missing.
-
+
// In addition, it seems that if the value of the short represented by the second
// pair of bytes in the packet is 7
// then an extra time lag is expected to be added, presumably by
// the AirPort Express.
-
+
// Best guess is that this delay is 11,025 frames.
-
+
uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
uint32_t rt = nctohl(&packet[16]); // raw timestamp
-
+
uint32_t fl = nctohs(&packet[2]); //
-
+
debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u,
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
//debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
@@ -527,22 +527,25 @@ void rtp_timing_receiver_cleanup_handler(void *arg) {
// walk down the list of DACP / gradient pairs, if any
nvll *gradients = config.gradients;
if (conn->dacp_id)
- while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string,gradients->name) != 0))
- gradients = gradients->next;
+ while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string, gradients->name) != 0))
+ gradients = gradients->next;
- // if gradients comes out of this non-null, it is pointing to the DACP and it's last-known gradient
+ // if gradients comes out of this non-null, it is pointing to the DACP and it's last-known
+ // gradient
if (gradients) {
- gradients->value = conn->local_to_remote_time_gradient;
- // debug(1,"Updating a drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, gradients->name);
+ gradients->value = conn->local_to_remote_time_gradient;
+ // debug(1,"Updating a drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient
+ // - 1.0)*1000000, gradients->name);
} else {
- nvll *new_entry = (nvll*)malloc(sizeof(nvll));
- if (new_entry) {
- new_entry->name = strdup((const char *)&conn->client_ip_string);
- new_entry->value = conn->local_to_remote_time_gradient;
- new_entry->next = config.gradients;
- config.gradients = new_entry;
- // debug(1,"Setting a new drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, new_entry->name);
- }
+ nvll *new_entry = (nvll *)malloc(sizeof(nvll));
+ if (new_entry) {
+ new_entry->name = strdup((const char *)&conn->client_ip_string);
+ new_entry->value = conn->local_to_remote_time_gradient;
+ new_entry->next = config.gradients;
+ config.gradients = new_entry;
+ // debug(1,"Setting a new drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient
+ // - 1.0)*1000000, new_entry->name);
+ }
}
debug(3, "Cancel Timing Requester.");
@@ -574,13 +577,14 @@ void *rtp_timing_receiver(void *arg) {
conn->local_to_remote_time_gradient = 1.0; // initial value.
// walk down the list of DACP / gradient pairs, if any
nvll *gradients = config.gradients;
- while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string,gradients->name) != 0))
- gradients = gradients->next;
+ while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string, gradients->name) != 0))
+ gradients = gradients->next;
// if gradients comes out of this non-null, it is pointing to the IP and it's last-known gradient
if (gradients) {
- conn->local_to_remote_time_gradient = gradients->value;
- // debug(1,"Using a stored drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, gradients->name);
+ conn->local_to_remote_time_gradient = gradients->value;
+ // debug(1,"Using a stored drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient
+ // - 1.0)*1000000, gradients->name);
}
// calculate diffusion factor
@@ -591,13 +595,12 @@ void *rtp_timing_receiver(void *arg) {
// be the nth root of diffusion_expansion_factor
// where n is the number of elements in the array
- const double diffusion_expansion_factor = 10;
- double log_of_multiplier = log10(diffusion_expansion_factor)/time_ping_history;
- double multiplier = pow(10,log_of_multiplier);
+ const double diffusion_expansion_factor = 10;
+ double log_of_multiplier = log10(diffusion_expansion_factor) / time_ping_history;
+ double multiplier = pow(10, log_of_multiplier);
uint64_t dispersion_factor = (uint64_t)(multiplier * 100);
// debug(1,"dispersion factor is %" PRIu64 ".", dispersion_factor);
-
// uint64_t first_local_to_remote_time_difference_time;
// uint64_t l2rtd = 0;
int sequence_number = 0;
@@ -621,7 +624,8 @@ void *rtp_timing_receiver(void *arg) {
if (packet[1] == 0xd3) { // timing reply
return_time = arrival_time - conn->departure_time;
- debug(3,"clock synchronisation request: return time is %8.3f milliseconds.",0.000001*return_time);
+ debug(3, "clock synchronisation request: return time is %8.3f milliseconds.",
+ 0.000001 * return_time);
if (return_time < 200000000) { // must be less than 0.2 seconds
// distant_receive_time =
@@ -671,11 +675,11 @@ void *rtp_timing_receiver(void *arg) {
// conn->time_pings[cc].dispersion * pow(2.14,
// 1.0/conn->time_ping_count);
if (conn->time_pings[cc].dispersion > UINT64_MAX / dispersion_factor)
- debug(1,"dispersion factor is too large at %" PRIu64 ".");
+ debug(1, "dispersion factor is too large at %" PRIu64 ".");
else
- conn->time_pings[cc].dispersion =
- (conn->time_pings[cc].dispersion * dispersion_factor) /
- 100; // make the dispersions 'age' by this rational factor
+ conn->time_pings[cc].dispersion =
+ (conn->time_pings[cc].dispersion * dispersion_factor) /
+ 100; // make the dispersions 'age' by this rational factor
}
// these are used for doing a least squares calculation to get the drift
conn->time_pings[0].local_time = arrival_time;
@@ -752,7 +756,8 @@ void *rtp_timing_receiver(void *arg) {
if ((conn->time_pings[cc].chosen) &&
(conn->time_pings[cc].sequence_number >
(settling_time / 3))) { // wait for a approximate settling time
- // have to scale them down so that the sum, possibly over every term in the array, doesn't overflow
+ // have to scale them down so that the sum, possibly over
+ // every term in the array, doesn't overflow
y_bar += (conn->time_pings[cc].remote_time >> time_ping_history_power_of_two);
x_bar += (conn->time_pings[cc].local_time >> time_ping_history_power_of_two);
sample_count++;
@@ -762,8 +767,6 @@ void *rtp_timing_receiver(void *arg) {
y_bar = y_bar / sample_count;
x_bar = x_bar / sample_count;
-
-
int64_t xid, yid;
double mtl, mbl;
mtl = 0;
@@ -791,19 +794,21 @@ void *rtp_timing_receiver(void *arg) {
conn->local_to_remote_time_gradient = mtl / mbl;
else {
// conn->local_to_remote_time_gradient = 1.0;
- debug(1,"mbl is zero. Drift remains at %.2f ppm.", (conn->local_to_remote_time_gradient - 1.0)*1000000);
+ debug(1, "mbl is zero. Drift remains at %.2f ppm.",
+ (conn->local_to_remote_time_gradient - 1.0) * 1000000);
}
- // scale the numbers back up
- uint64_t ybf = y_bar << time_ping_history_power_of_two;
- uint64_t xbf = x_bar << time_ping_history_power_of_two;
+ // scale the numbers back up
+ uint64_t ybf = y_bar << time_ping_history_power_of_two;
+ uint64_t xbf = x_bar << time_ping_history_power_of_two;
- conn->local_to_remote_time_difference =
- ybf - xbf; // make this the new local-to-remote-time-difference
- conn->local_to_remote_time_difference_measurement_time = xbf;
+ conn->local_to_remote_time_difference =
+ ybf - xbf; // make this the new local-to-remote-time-difference
+ conn->local_to_remote_time_difference_measurement_time = xbf;
} else {
- debug(3,"not enough samples to estimate drift -- remaining at %.2f ppm.", (conn->local_to_remote_time_gradient - 1.0)*1000000);
+ debug(3, "not enough samples to estimate drift -- remaining at %.2f ppm.",
+ (conn->local_to_remote_time_gradient - 1.0) * 1000000);
// conn->local_to_remote_time_gradient = 1.0;
}
// debug(1,"local to remote time gradient is %12.2f ppm, based on %d
diff --git a/rtsp.c b/rtsp.c
index 1f1cb9f9..abfabc4f 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -3,7 +3,7 @@
* Copyright (c) James Laird 2013
* Modifications associated with audio synchronization, mutithreading and
- * metadata handling copyright (c) Mike Brady 2014-2019
+ * metadata handling copyright (c) Mike Brady 2014-2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -115,7 +115,7 @@ typedef struct {
pthread_cond_t pc_queue_item_added_signal;
pthread_cond_t pc_queue_item_removed_signal;
char *name;
- size_t item_size; // number of bytes in each item
+ size_t item_size; // number of bytes in each item
uint32_t count; // number of items in the queue
uint32_t capacity; // maximum number of items
uint32_t toq; // first item to take
@@ -152,11 +152,12 @@ typedef struct {
rtsp_message *carrier;
} metadata_package;
-void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items, const char* name) {
- if (name)
- debug(2, "Creating metadata queue \"%s\".", name);
- else
- debug(1, "Creating an unnamed metadata queue.");
+void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
+ const char *name) {
+ if (name)
+ debug(2, "Creating metadata queue \"%s\".", name);
+ else
+ debug(1, "Creating an unnamed metadata queue.");
pthread_mutex_init(&the_queue->pc_queue_lock, NULL);
pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL);
pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL);
@@ -167,25 +168,25 @@ void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t
the_queue->toq = 0;
the_queue->eoq = 0;
if (name == NULL)
- the_queue->name = NULL;
+ the_queue->name = NULL;
else
- the_queue->name = strdup(name);
+ the_queue->name = strdup(name);
}
void pc_queue_delete(pc_queue *the_queue) {
- if (the_queue->name)
- debug(2, "Deleting metadata queue \"%s\".", the_queue->name);
- else
- debug(1, "Deleting an unnamed metadata queue.");
- if (the_queue->name != NULL)
- free(the_queue->name);
- // debug(2, "destroying pc_queue_item_removed_signal");
+ if (the_queue->name)
+ debug(2, "Deleting metadata queue \"%s\".", the_queue->name);
+ else
+ debug(1, "Deleting an unnamed metadata queue.");
+ if (the_queue->name != NULL)
+ free(the_queue->name);
+ // debug(2, "destroying pc_queue_item_removed_signal");
pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal);
- // debug(2, "destroying pc_queue_item_added_signal");
+ // debug(2, "destroying pc_queue_item_added_signal");
pthread_cond_destroy(&the_queue->pc_queue_item_added_signal);
- // debug(2, "destroying pc_queue_lock");
+ // debug(2, "destroying pc_queue_lock");
pthread_mutex_destroy(&the_queue->pc_queue_lock);
- // debug(2, "destroying signals and locks done");
+ // debug(2, "destroying signals and locks done");
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
@@ -204,7 +205,7 @@ void pc_queue_cleanup_handler(void *arg) {
}
int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
- int response = 0;
+ int response = 0;
int rc;
if (the_queue) {
if (block == 0) {
@@ -219,34 +220,39 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
// leave this out if you want this to return if the queue is already full
// irrespective of the block flag.
/*
- while (the_queue->count == the_queue->capacity) {
- rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock);
- if (rc)
- debug(1, "Error waiting for item to be removed");
- }
- */
+ while (the_queue->count == the_queue->capacity) {
+ rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal,
+ &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed");
+ }
+ */
if (the_queue->count < the_queue->capacity) {
- uint32_t i = the_queue->eoq;
- void *p = the_queue->items + the_queue->item_size * i;
- // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
- memcpy(p, the_stuff, the_queue->item_size);
+ uint32_t i = the_queue->eoq;
+ void *p = the_queue->items + the_queue->item_size * i;
+ // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
+ memcpy(p, the_stuff, the_queue->item_size);
- // update the pointer
- i++;
- if (i == the_queue->capacity)
- // fold pointer if necessary
- i = 0;
- the_queue->eoq = i;
- the_queue->count++;
- //debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count, the_queue->capacity);
- if (the_queue->count == the_queue->capacity)
- debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name, the_queue->count);
- rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
- if (rc)
- debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item", the_queue->name);
+ // update the pointer
+ i++;
+ if (i == the_queue->capacity)
+ // fold pointer if necessary
+ i = 0;
+ the_queue->eoq = i;
+ the_queue->count++;
+ // debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count,
+ // the_queue->capacity);
+ if (the_queue->count == the_queue->capacity)
+ debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name,
+ the_queue->count);
+ rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
+ if (rc)
+ debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item",
+ the_queue->name);
} else {
- response = EWOULDBLOCK; // a bit arbitrary, this.
- debug(3,"metadata queue \"%s\": is already full with %d items in it. Not adding this item to the queue.", the_queue->name, the_queue->count);
+ response = EWOULDBLOCK; // a bit arbitrary, this.
+ debug(3,
+ "metadata queue \"%s\": is already full with %d items in it. Not adding this item to "
+ "the queue.",
+ the_queue->name, the_queue->count);
}
pthread_cleanup_pop(1); // unlock the queue lock.
} else {
@@ -279,7 +285,8 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
i = 0;
the_queue->toq = i;
the_queue->count--;
- debug(3,"metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count, the_queue->capacity);
+ debug(3, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count,
+ the_queue->capacity);
rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal);
if (rc)
debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name);
@@ -317,15 +324,17 @@ void *player_watchdog_thread_code(void *arg) {
uint64_t last_watchdog_bark_time = conn->watchdog_bark_time;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
if (last_watchdog_bark_time != 0) {
- uint64_t time_since_last_bark = (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000;
+ uint64_t time_since_last_bark =
+ (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000;
uint64_t ct = config.timeout; // go from int to 64-bit int
if (time_since_last_bark >= ct) {
conn->watchdog_barks++;
if (conn->watchdog_barks == 1) {
// debuglev = 3; // tell us everything.
- debug(1, "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone "
- "of the heart\".",
+ debug(1,
+ "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone "
+ "of the heart\".",
conn->connection_number);
conn->stop = 1;
pthread_cancel(conn->thread);
@@ -446,7 +455,8 @@ void msg_retain(rtsp_message *msg) {
debug(1, "Error %d locking reference counter lock");
if (msg > (rtsp_message *)0x00010000) {
msg->referenceCount++;
- debug(3,"msg_free increment reference counter message %d to %d.", msg->index_number, msg->referenceCount);
+ debug(3, "msg_free increment reference counter message %d to %d.", msg->index_number,
+ msg->referenceCount);
// debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
rc = pthread_mutex_unlock(&reference_counter_lock);
if (rc)
@@ -462,7 +472,7 @@ rtsp_message *msg_init(void) {
memset(msg, 0, sizeof(rtsp_message));
msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
msg->index_number = msg_indexes++;
- debug(3,"msg_init message %d", msg->index_number);
+ debug(3, "msg_init message %d", msg->index_number);
} else {
die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
}
@@ -526,8 +536,9 @@ void msg_free(rtsp_message **msgh) {
if (*msgh > (rtsp_message *)0x00010000) {
rtsp_message *msg = *msgh;
msg->referenceCount--;
- if (msg->referenceCount)
- debug(3,"msg_free decrement reference counter message %d to %d", msg->index_number, msg->referenceCount);
+ if (msg->referenceCount)
+ debug(3, "msg_free decrement reference counter message %d to %d", msg->index_number,
+ msg->referenceCount);
if (msg->referenceCount == 0) {
unsigned int i;
for (i = 0; i < msg->nheaders; i++) {
@@ -542,7 +553,7 @@ void msg_free(rtsp_message **msgh) {
index = 0x10000; // ensure it doesn't fold to zero.
*msgh =
(rtsp_message *)(index); // put a version of the index number of the freed message in here
- debug(3,"msg_free freed message %d", msg->index_number);
+ debug(3, "msg_free freed message %d", msg->index_number);
free(msg);
} else {
// debug(1,"msg_free item %d -- decrement reference to
@@ -607,7 +618,7 @@ int msg_handle_line(rtsp_message **pmsg, char *line) {
}
fail:
- debug(3,"msg_handle_line fail");
+ debug(3, "msg_handle_line fail");
msg_free(pmsg);
*pmsg = NULL;
return 0;
@@ -650,7 +661,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
if (errno == EINTR)
continue;
if (errno == EAGAIN) {
- debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!", conn->connection_number);
+ debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!",
+ conn->connection_number);
continue;
}
if (errno != ECONNRESET) {
@@ -663,15 +675,15 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
goto shutdown;
}
-/* // this outputs the message received
- {
- void *pt = malloc(nread+1);
- memset(pt, 0, nread+1);
- memcpy(pt, buf + inbuf, nread);
- debug(1, "Incoming string on port: \"%s\"",pt);
- free(pt);
- }
-*/
+ /* // this outputs the message received
+ {
+ void *pt = malloc(nread+1);
+ memset(pt, 0, nread+1);
+ memcpy(pt, buf + inbuf, nread);
+ debug(1, "Incoming string on port: \"%s\"",pt);
+ free(pt);
+ }
+ */
inbuf += nread;
@@ -680,7 +692,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
msg_size = msg_handle_line(the_packet, buf);
if (!(*the_packet)) {
- debug(1,"Connection %d: rtsp_read_request can't find an RTSP header.", conn->connection_number);
+ debug(1, "Connection %d: rtsp_read_request can't find an RTSP header.",
+ conn->connection_number);
reply = rtsp_read_request_response_bad_packet;
goto shutdown;
}
@@ -887,9 +900,10 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *
rtsp_message *resp) {
debug(3, "Connection %d: OPTIONS", conn->connection_number);
resp->respcode = 200;
- msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, "
- "PAUSE, FLUSH, TEARDOWN, "
- "OPTIONS, GET_PARAMETER, SET_PARAMETER");
+ msg_add_header(resp, "Public",
+ "ANNOUNCE, SETUP, RECORD, "
+ "PAUSE, FLUSH, TEARDOWN, "
+ "OPTIONS, GET_PARAMETER, SET_PARAMETER");
}
void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
@@ -929,17 +943,17 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1
}
}
-// debug(1,"RTSP Flush Requested: %u.",rtptime);
+ // debug(1,"RTSP Flush Requested: %u.",rtptime);
-// the following is now done better by the player_flush routine as a 'pfls'
-/*
-#ifdef CONFIG_METADATA
- if (p)
- send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1);
- else
- send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
-#endif
-*/
+ // the following is now done better by the player_flush routine as a 'pfls'
+ /*
+ #ifdef CONFIG_METADATA
+ if (p)
+ send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1);
+ else
+ send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0);
+ #endif
+ */
player_flush(rtptime, conn); // will not crash even it there is no player thread.
resp->respcode = 200;
@@ -961,15 +975,19 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(2, "Connection %d: SETUP -- Active-Remote string seen: \"%s\".",
conn->connection_number, ar);
// get the active remote
- char *p;
- conn->dacp_active_remote = strtoul(ar, &p, 10);
+ 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(2, "Connection %d: SETUP -- Note: no Active-Remote information the SETUP Record.",
conn->connection_number);
- conn->dacp_active_remote = 0;
+ 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");
@@ -984,9 +1002,10 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
} else {
debug(2, "Connection %d: SETUP doesn't include DACP-ID string information.",
conn->connection_number);
- if (conn->dacp_id) // this is in case SETUP was previously called
+ if (conn->dacp_id) { // this is in case SETUP was previously called
free(conn->dacp_id);
- conn->dacp_id = NULL;
+ conn->dacp_id = NULL;
+ }
}
char *hdr = msg_get_header(req, "Transport");
@@ -1035,8 +1054,9 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
msg_add_header(resp, "Session", "1");
resp->respcode = 200; // it all worked out okay
- debug(1, "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: "
- "%d, Timing: %d and Audio: %d.",
+ debug(1,
+ "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: "
+ "%d, Timing: %d and Audio: %d.",
conn->connection_number, conn->dacp_id, &conn->client_ip_string,
&conn->self_ip_string, conn->local_control_port, conn->local_timing_port,
conn->local_audio_port);
@@ -1265,8 +1285,6 @@ char *base64_encode_so(const unsigned char *data, size_t input_length, char *enc
static int fd = -1;
// static int dirty = 0;
-
-
pc_queue metadata_queue;
#define metadata_queue_size 500
metadata_package metadata_queue_items[metadata_queue_size];
@@ -1294,7 +1312,6 @@ pc_queue metadata_multicast_queue;
metadata_package metadata_multicast_queue_items[metadata_queue_size];
pthread_t metadata_multicast_thread;
-
void metadata_create_multicast_socket(void) {
if (config.metadata_enabled == 0)
return;
@@ -1308,7 +1325,6 @@ void metadata_create_multicast_socket(void) {
} else {
int buffer_size = METADATA_SNDBUF;
setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
- fcntl(fd, F_SETFL, O_NONBLOCK);
bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr));
metadata_sockaddr.sin_family = AF_INET;
metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr);
@@ -1332,7 +1348,6 @@ void metadata_delete_multicast_socket(void) {
free(metadata_sockmsg);
}
-
void metadata_open(void) {
if (config.metadata_enabled == 0)
return;
@@ -1354,7 +1369,8 @@ static void metadata_close(void) {
}
void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
- // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code, length);
+ // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code,
+ // length);
if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) {
char *ptr = metadata_sockmsg;
uint32_t v;
@@ -1455,7 +1471,7 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
debug(1, "Error encoding base64 data.");
// debug(1,"Remaining count: %d ret: %d, outbuf_size:
// %d.",remaining_count,ret,outbuf_size);
- //ret = non_blocking_write(fd, outbuf, outbuf_size);
+ // ret = non_blocking_write(fd, outbuf, outbuf_size);
ret = write(fd, outbuf, outbuf_size);
if (ret < 0) {
// debug(1,"metadata_process error %d exit 3",ret);
@@ -1473,7 +1489,7 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length)
}
}
snprintf(thestring, 1024, "\n");
- //ret = non_blocking_write(fd, thestring, strlen(thestring));
+ // ret = non_blocking_write(fd, thestring, strlen(thestring));
ret = write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 5",ret);
@@ -1508,11 +1524,12 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) {
pc_queue_get_item(&metadata_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.metadata_enabled) {
- if (pack.carrier) {
- debug(3, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
- } else {
- debug(3, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
- }
+ if (pack.carrier) {
+ debug(3, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code,
+ pack.length, pack.carrier->index_number);
+ } else {
+ debug(3, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
+ }
metadata_process(pack.type, pack.code, pack.data, pack.length);
debug(3, " pipe: done.");
}
@@ -1530,8 +1547,8 @@ void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *ar
void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
// create a pc_queue for passing information to a threaded metadata handler
- pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items, sizeof(metadata_package),
- metadata_multicast_queue_size, "multicast");
+ pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
+ sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
metadata_create_multicast_socket();
metadata_package pack;
pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL);
@@ -1539,13 +1556,20 @@ void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
pc_queue_get_item(&metadata_multicast_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
if (config.metadata_enabled) {
- if (pack.carrier) {
- debug(3, " multicast: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
- } else {
- debug(3, " multicast: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
- }
+ if (pack.carrier) {
+ debug(3,
+ " multicast: type "
+ "%x, code %x, length %u, message %d.",
+ pack.type, pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(3,
+ " multicast: type "
+ "%x, code %x, length %u.",
+ pack.type, pack.code, pack.length);
+ }
metadata_multicast_process(pack.type, pack.code, pack.data, pack.length);
- debug(3, " multicast: done.");
+ debug(3,
+ " multicast: done.");
}
pthread_cleanup_pop(1);
}
@@ -1553,10 +1577,8 @@ void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
pthread_exit(NULL);
}
-
#ifdef CONFIG_METADATA_HUB
-void metadata_hub_close(void) {
-}
+void metadata_hub_close(void) {}
void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_hub_thread_cleanup_function called");
@@ -1565,33 +1587,6 @@ void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) {
}
void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
-
- // create the fifo, if necessary
- size_t pl = strlen(config.metadata_pipename) + 1;
- char *path = malloc(pl + 1);
- snprintf(path, pl + 1, "%s", config.metadata_pipename);
-
- mode_t oldumask = umask(000);
- if (mkfifo(path, 0666) && errno != EEXIST)
- die("Could not create metadata pipe \"%s\".", path);
- umask(oldumask);
- debug(1, "metadata pipe name is \"%s\".", path);
-
- // try to open it
- fd = try_to_open_pipe_for_writing(path);
- // we check that it's not a "real" error. From the "man 2 open" page:
- // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
- // open for reading." Which is okay.
- if ((fd == -1) && (errno != ENXIO)) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
- warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
- }
- free(path);
-
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package),
metadata_hub_queue_size, "hub");
@@ -1600,13 +1595,15 @@ void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
while (1) {
pc_queue_get_item(&metadata_hub_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
- if (pack.carrier) {
- debug(3, " hub: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
- } else {
- debug(3, " hub: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
- }
- metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
- debug(3, " hub: done.");
+ if (pack.carrier) {
+ debug(3, " hub: type %x, code %x, length %u, message %d.", pack.type,
+ pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(3, " hub: type %x, code %x, length %u.", pack.type, pack.code,
+ pack.length);
+ }
+ metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
+ debug(3, " hub: done.");
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1); // will never happen
@@ -1615,8 +1612,7 @@ void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
#endif
#ifdef CONFIG_MQTT
-void metadata_mqtt_close(void) {
-}
+void metadata_mqtt_close(void) {}
void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) {
// debug(2, "metadata_mqtt_thread_cleanup_function called");
@@ -1634,15 +1630,19 @@ void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
while (1) {
pc_queue_get_item(&metadata_mqtt_queue, &pack);
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
- if (config.mqtt_enabled) {
- if (pack.carrier) {
- debug(3, " mqtt: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number);
- } else {
- debug(3, " mqtt: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
- }
- mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
- debug(3, " mqtt: done.");
- }
+ if (config.mqtt_enabled) {
+ if (pack.carrier) {
+ debug(3,
+ " mqtt: type %x, code %x, length %u, message "
+ "%d.",
+ pack.type, pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(3, " mqtt: type %x, code %x, length %u.",
+ pack.type, pack.code, pack.length);
+ }
+ mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
+ debug(3, " mqtt: done.");
+ }
pthread_cleanup_pop(1);
}
@@ -1652,6 +1652,33 @@ void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
#endif
void metadata_init(void) {
+
+ // create the metadata pipe, if necessary
+ size_t pl = strlen(config.metadata_pipename) + 1;
+ char *path = malloc(pl + 1);
+ snprintf(path, pl + 1, "%s", config.metadata_pipename);
+
+ mode_t oldumask = umask(000);
+ if (mkfifo(path, 0666) && errno != EEXIST)
+ die("Could not create metadata pipe \"%s\".", path);
+ umask(oldumask);
+ debug(1, "metadata pipe name is \"%s\".", path);
+
+ // try to open it
+ fd = try_to_open_pipe_for_writing(path);
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ if ((fd == -1) && (errno != ENXIO)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ }
+ free(path);
+
int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
if (ret)
debug(1, "Failed to create metadata thread!");
@@ -1700,8 +1727,8 @@ void metadata_stop(void) {
}
}
-int send_metadata_to_queue(pc_queue* queue, uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
- int block) {
+int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char *data,
+ uint32_t length, rtsp_message *carrier, int block) {
// parameters: type, code, pointer to data or NULL, length of data or NULL,
// the rtsp_message or
@@ -1737,20 +1764,27 @@ int send_metadata_to_queue(pc_queue* queue, uint32_t type, uint32_t code, char *
if (pack.carrier) {
msg_retain(pack.carrier);
} else {
- if (data)
- pack.data = memdup(data,length); // only if it's not a null
+ if (data)
+ pack.data = memdup(data, length); // only if it's not a null
}
int rc = pc_queue_add_item(queue, &pack, block);
if (rc != 0) {
if (pack.carrier) {
- if (rc == EWOULDBLOCK)
- debug(2, "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %x, length %u, message %d.", queue->name, pack.type, pack.code, pack.data, pack.length, pack.carrier->index_number);
+ if (rc == EWOULDBLOCK)
+ debug(2,
+ "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %x, "
+ "length %u, message %d.",
+ queue->name, pack.type, pack.code, pack.data, pack.length,
+ pack.carrier->index_number);
msg_free(&pack.carrier);
} else {
- if (rc == EWOULDBLOCK)
- debug(2, "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %x, length %u.", queue->name, pack.type, pack.code, pack.data, pack.length);
- if (pack.data)
- free(pack.data);
+ if (rc == EWOULDBLOCK)
+ debug(
+ 2,
+ "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %x, length %u.",
+ queue->name, pack.type, pack.code, pack.data, pack.length);
+ if (pack.data)
+ free(pack.data);
}
}
return rc;
@@ -1758,23 +1792,20 @@ int send_metadata_to_queue(pc_queue* queue, uint32_t type, uint32_t code, char *
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block) {
- int rc;
- rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
+ int rc;
+ rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
#ifdef CONFIG_METADATA_HUB
- rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
+ rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
#endif
#ifdef CONFIG_MQTT
- rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
+ rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
#endif
- return rc;
+ return rc;
}
-
-
-
static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn,
rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {
@@ -1838,7 +1869,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m
char *ct = msg_get_header(req, "Content-Type");
if (ct) {
-// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct);
+ // debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct);
#ifdef CONFIG_METADATA
// It seems that the rtptime of the message is used as a kind of an ID that
@@ -2136,7 +2167,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
unsigned int i = 0;
unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]);
- char* found;
+ char *found;
while ((found = strsep(&pfmtp, " \t")) != NULL && i < max_param) {
conn->stream.fmtp[i++] = atoi(found);
}
@@ -2617,8 +2648,9 @@ static void *rtsp_conversation_thread_func(void *pconn) {
if (strcmp(req->method, "OPTIONS") !=
0) // the options message is very common, so don't log it until level 3
debug_level = 2;
- debug(debug_level, "Connection %d: Received an RTSP Packet of type \"%s\":",
- conn->connection_number, req->method),
+ debug(debug_level,
+ "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number,
+ req->method),
debug_print_msg_headers(debug_level, req);
apple_challenge(conn->fd, req, resp);
@@ -2667,8 +2699,9 @@ static void *rtsp_conversation_thread_func(void *pconn) {
if (conn->stop == 0) {
int err = msg_write_response(conn->fd, resp);
if (err) {
- debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the "
- "connection.",
+ debug(1,
+ "Connection %d: Unable to write an RTSP message response. Terminating the "
+ "connection.",
conn->connection_number);
struct linger so_linger;
so_linger.l_onoff = 1; // "true"
@@ -2721,10 +2754,11 @@ static void *rtsp_conversation_thread_func(void *pconn) {
if (reply == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno, (char *)errorstring);
+ debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno,
+ (char *)errorstring);
} else if (reply != (ssize_t)strlen(response_text)) {
- debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.", strlen(response_text),
- reply);
+ debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.",
+ strlen(response_text), reply);
}
} else {
debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.",
@@ -2937,7 +2971,7 @@ void rtsp_listen_loop(void) {
debug(2, "Connection %d: new connection from %s:%u to self at %s:%u.",
conn->connection_number, remote_ip4, rport, ip4, tport);
}
- #ifdef AF_INET6
+#ifdef AF_INET6
if (local_info->SAFAMILY == AF_INET6) {
// IPv6:
@@ -2954,7 +2988,7 @@ void rtsp_listen_loop(void) {
debug(2, "Connection %d: new connection from [%s]:%u to self at [%s]:%u.",
conn->connection_number, remote_ip6, rport, ip6, tport);
}
- #endif
+#endif
} else {
debug(1, "Error figuring out Shairport Sync's own IP number.");
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index efa34996..6444b8a5 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -159,7 +159,7 @@ jack =
// --with-pipe
pipe =
{
-// name = "/path/to/pipe"; // there is no default pipe name for the output
+// name = "/tmp/shairport-sync-audio"; // this is the default
};
// There are no configuration file parameters for the "stdout" audio back end. No interpolation is done.
diff --git a/shairport.c b/shairport.c
index 975b71fc..ba995233 100644
--- a/shairport.c
+++ b/shairport.c
@@ -61,6 +61,7 @@
#endif
#include "activity_monitor.h"
+#include "audio.h"
#include "common.h"
#include "rtp.h"
#include "rtsp.h"
@@ -117,6 +118,8 @@ int daemonisewithout = 0;
char configuration_file_path[4096 + 1];
char actual_configuration_file_path[4096 + 1];
+char first_backend_name[256];
+
void print_version(void) {
char *version_string = get_version_string();
if (version_string) {
@@ -126,7 +129,6 @@ void print_version(void) {
debug(1, "Can't print version string!");
}
}
-
#ifdef CONFIG_SOXR
pthread_t soxr_time_check_thread;
void *soxr_time_check(__attribute__((unused)) void *arg) {
@@ -419,15 +421,15 @@ int parse_options(int argc, char **argv) {
// for unexpected circumstances
#ifdef CONFIG_METADATA
- /* Get the metadata setting. */
- config.metadata_enabled = 1; // if metadata support is included, then enable it by default
- config.get_coverart = 1; // if metadata support is included, then enable it by default
+ /* Get the metadata setting. */
+ config.metadata_enabled = 1; // if metadata support is included, then enable it by default
+ config.get_coverart = 1; // if metadata support is included, then enable it by default
#endif
#ifdef CONFIG_CONVOLUTION
- config.convolution_max_length = 8192;
+ config.convolution_max_length = 8192;
#endif
- config.loudness_reference_volume_db = -20;
+ config.loudness_reference_volume_db = -20;
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
@@ -682,9 +684,9 @@ int parse_options(int argc, char **argv) {
} else if (strcasecmp(str, "stderr") == 0) {
log_to_stderr();
} else {
- config.log_file_path = (char *)str;
- config.log_fd = -1;
- log_to_file();
+ config.log_file_path = (char *)str;
+ config.log_fd = -1;
+ log_to_file();
}
}
/* Get the ignore_volume_control setting. */
@@ -1313,113 +1315,114 @@ const char *pid_file_proc(void) {
void exit_function() {
- if (emergency_exit == 0) {
- // the following is to ensure that if libdaemon has been included
- // that most of this code will be skipped when the parent process is exiting
- // exec
+ if (emergency_exit == 0) {
+ // the following is to ensure that if libdaemon has been included
+ // that most of this code will be skipped when the parent process is exiting
+ // exec
#ifdef CONFIG_LIBDAEMON
- if ((this_is_the_daemon_process) || (config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not actually deamonised at all
+ if ((this_is_the_daemon_process) ||
+ (config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not
+ // actually deamonised at all
#endif
- debug(2, "exit function called...");
- /*
- Actually, there is no terminate_mqtt() function.
- #ifdef CONFIG_MQTT
- if (config.mqtt_enabled) {
- terminate_mqtt();
- }
- #endif
- */
+ debug(2, "exit function called...");
+ /*
+ Actually, there is no terminate_mqtt() function.
+ #ifdef CONFIG_MQTT
+ if (config.mqtt_enabled) {
+ terminate_mqtt();
+ }
+ #endif
+ */
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
- /*
- Actually, there is no stop_mpris_service() function.
- #ifdef CONFIG_MPRIS_INTERFACE
- stop_mpris_service();
- #endif
- */
+ /*
+ Actually, there is no stop_mpris_service() function.
+ #ifdef CONFIG_MPRIS_INTERFACE
+ stop_mpris_service();
+ #endif
+ */
#ifdef CONFIG_DBUS_INTERFACE
- stop_dbus_service();
+ stop_dbus_service();
#endif
- if (g_main_loop) {
- debug(2, "Stopping DBUS Loop Thread");
- g_main_loop_quit(g_main_loop);
- pthread_join(dbus_thread, NULL);
- }
+ if (g_main_loop) {
+ debug(2, "Stopping DBUS Loop Thread");
+ g_main_loop_quit(g_main_loop);
+ pthread_join(dbus_thread, NULL);
+ }
#endif
#ifdef CONFIG_DACP_CLIENT
- debug(2, "Stopping DACP Monitor");
- dacp_monitor_stop();
+ debug(2, "Stopping DACP Monitor");
+ dacp_monitor_stop();
#endif
#ifdef CONFIG_METADATA_HUB
- debug(2, "Stopping metadata hub");
- metadata_hub_stop();
+ debug(2, "Stopping metadata hub");
+ metadata_hub_stop();
#endif
#ifdef CONFIG_METADATA
- metadata_stop(); // close down the metadata pipe
+ metadata_stop(); // close down the metadata pipe
#endif
- activity_monitor_stop(0);
+ activity_monitor_stop(0);
- if ((config.output) && (config.output->deinit)) {
- debug(2, "Deinitialise the audio backend.");
- config.output->deinit();
- }
+ if ((config.output) && (config.output->deinit)) {
+ debug(2, "Deinitialise the audio backend.");
+ config.output->deinit();
+ }
#ifdef CONFIG_SOXR
- // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down
- pthread_join(soxr_time_check_thread, NULL);
+ // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down
+ pthread_join(soxr_time_check_thread, NULL);
#endif
- if (conns)
- free(conns); // make sure the connections have been deleted first
+ if (conns)
+ free(conns); // make sure the connections have been deleted first
- if (config.service_name)
- free(config.service_name);
+ if (config.service_name)
+ free(config.service_name);
#ifdef CONFIG_CONVOLUTION
- if (config.convolution_ir_file)
- free(config.convolution_ir_file);
+ if (config.convolution_ir_file)
+ free(config.convolution_ir_file);
#endif
- if (config.regtype)
- free(config.regtype);
+ if (config.regtype)
+ free(config.regtype);
#ifdef CONFIG_LIBDAEMON
- if (this_is_the_daemon_process) {
- daemon_retval_send(0);
- daemon_pid_file_remove();
- daemon_signal_done();
- if (config.computed_piddir)
- free(config.computed_piddir);
- }
- }
+ if (this_is_the_daemon_process) {
+ daemon_retval_send(0);
+ daemon_pid_file_remove();
+ daemon_signal_done();
+ if (config.computed_piddir)
+ free(config.computed_piddir);
+ }
+ }
#endif
- if (config.cfg)
- config_destroy(config.cfg);
- if (config.appName)
- free(config.appName);
- // probably should be freeing malloc'ed memory here, including strdup-created strings...
-
+ if (config.cfg)
+ config_destroy(config.cfg);
+ if (config.appName)
+ free(config.appName);
+ // probably should be freeing malloc'ed memory here, including strdup-created strings...
#ifdef CONFIG_LIBDAEMON
- if (this_is_the_daemon_process) { // this is the daemon that is exiting
- debug(1,"libdaemon daemon exit");
- } else {
- if (config.daemonise)
- debug(1,"libdaemon parent exit");
- else
- debug(1,"exit");
- }
+ if (this_is_the_daemon_process) { // this is the daemon that is exiting
+ debug(1, "libdaemon daemon exit");
+ } else {
+ if (config.daemonise)
+ debug(1, "libdaemon parent exit");
+ else
+ debug(1, "exit");
+ }
#else
- debug(1,"exit");
+ debug(1, "exit");
#endif
- } else {
- debug(1,"emergency exit");
- }
+ } else {
+ debug(1, "emergency exit");
+ }
}
// for removing zombie script processes
@@ -1434,11 +1437,10 @@ void handle_sigchld(__attribute__((unused)) int sig) {
}
void main_thread_cleanup_handler(__attribute__((unused)) void *arg) {
- debug(2,"main thread cleanup handler called");
+ debug(2, "main thread cleanup handler called");
exit(EXIT_SUCCESS);
}
-
int main(int argc, char **argv) {
/* Check if we are called with -V or --version parameter */
if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
@@ -1455,7 +1457,7 @@ int main(int argc, char **argv) {
#ifdef CONFIG_LIBDAEMON
pid = getpid();
#endif
- config.log_fd = -1;
+ config.log_fd = -1;
conns = NULL; // no connections active
memset((void *)&main_thread_id, 0, sizeof(main_thread_id));
memset(&config, 0, sizeof(config)); // also clears all strings, BTW
@@ -1476,7 +1478,7 @@ int main(int argc, char **argv) {
setlogmask(LOG_UPTO(LOG_DEBUG));
openlog(NULL, 0, LOG_DAEMON);
#endif
- emergency_exit = 0; // what to do or skip in the exit_function
+ emergency_exit = 0; // what to do or skip in the exit_function
atexit(exit_function);
// set defaults
@@ -1504,6 +1506,15 @@ int main(int argc, char **argv) {
// set non-zero / non-NULL default values here
// but note that audio back ends also have a chance to set defaults
+ // get the first output backend in the list and make it the default
+ audio_output *first_backend = audio_get_output(NULL);
+ if (first_backend == NULL) {
+ die("No audio backend found! Check your build of Shairport Sync.");
+ } else {
+ strncpy(first_backend_name, first_backend->name, sizeof(first_backend_name) - 1);
+ 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
@@ -1598,13 +1609,16 @@ int main(int argc, char **argv) {
if (errno == ENOENT)
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);
+ 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);
+ // 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 deamon, but cannot remove old PID file: \"%s\", errno %u.", config.appName, strerror(errno), errno);
+ daemon_log(LOG_WARNING,
+ "killed the %s deamon, but cannot remove old PID file: \"%s\", errno %u.",
+ config.appName, strerror(errno), errno);
}
return ret < 0 ? 1 : 0;
#else
@@ -1651,14 +1665,19 @@ int main(int argc, char **argv) {
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);
+ daemon_log(
+ LOG_ERR,
+ "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);
+ daemon_log(LOG_ERR, "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);
+ daemon_log(LOG_ERR,
+ "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);
@@ -1709,8 +1728,8 @@ int main(int argc, char **argv) {
#endif
debug(1, "Started!");
- // stop a pipe signal from killing the program
- signal(SIGPIPE, SIG_IGN);
+ // stop a pipe signal from killing the program
+ signal(SIGPIPE, SIG_IGN);
// install a zombie process reaper
// see: http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
@@ -1744,8 +1763,8 @@ int main(int argc, char **argv) {
config.output = audio_get_output(config.output_name);
if (!config.output) {
- audio_ls_outputs();
- die("Invalid audio output specified!");
+ die("Invalid audio backend \"%s\" selected!",
+ config.output_name == NULL ? "" : config.output_name);
}
config.output->init(argc - audio_arg, argv + audio_arg);
From 90636da36b96be61564ec31830ee965cfdc96135 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 3 Nov 2020 09:31:50 +0000
Subject: [PATCH 038/180] Update TROUBLESHOOTING.md
---
TROUBLESHOOTING.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index 93494ab4..66665c25 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -71,7 +71,7 @@ You can play from other devices but not from your Windows PC.
Allow network discovery. This setting creates a private type network and enables Windows to access the ports and protocols necessary to use Shairport Sync.
-### UFW firewall blocking connections on Raspbian (Raspberry Pi)
+### UFW firewall blocking ports (commonly includes Raspberry Pi)
**Problem**
@@ -91,7 +91,7 @@ You have installed Shairport Sync successfully, the daemon is running, you can s
**Solution**
-You have to allow connections to your Pi from remote devices. To do so, after re-enabling UFW (see last step of the previous section), enter the following commands in shell:
+You have to allow connections to your Shairport Sync device from remote devices. To do so, after re-enabling UFW (see last step of the previous section), enter the following commands in shell:
```
sudo ufw allow from 192.168.1.1/16 to any port 3689 proto tcp
From 850023d56d37bc0982fb57d47f0ca2f8fb952fc1 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 1 Dec 2020 12:02:37 +0000
Subject: [PATCH 039/180] 3.3.7 Final
---
shairport-sync.spec | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/shairport-sync.spec b/shairport-sync.spec
index 2f9800f9..215b303c 100644
--- a/shairport-sync.spec
+++ b/shairport-sync.spec
@@ -66,8 +66,8 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
%license LICENSES
%changelog
-* Tue Jul 7 2020 Mike Brady
Date: Tue, 1 Dec 2020 12:03:17 +0000
Subject: [PATCH 040/180] 3.3.7 Final
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 78e337e7..ed0dcdaa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.7rc3], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [3.3.7], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 8873923e84225f366c11787dddc5275565eb27a9 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 1 Dec 2020 12:13:05 +0000
Subject: [PATCH 041/180] 3.3.7 Final
---
RELEASENOTES.md | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 7be271ad..31ebd8d7 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -21,7 +21,7 @@ Version 3.3.7
* The DACP ID looks like a 64-bit number expressed in hexadecimal. It is normally quoted with leading zeroes removed, but in the `_dacp._tcp` service string, leading zeros are not removed from the DACP ID. The bug fix removes those leading zeroes. Thanks to [julianc1969](https://github.com/juliandc1969) for tracking down this bug so tenaciously!
**Enhancements**
-* Added the the string `-alac` to the the version string in '-V' if Apple ALAC decoder support is included.
+* Added the strings `-alac` and `-jack` to the the version string in '-V' if Apple ALAC decoder support and Jack Audio support are included respectively.
* Cleaned up and simplified the code that handles `FLUSH` requests coming from the player. (Debug messages are still a little verbose.)
* Improved timing estimation. Shairport Sync has been using linear least-squares regression to estimate timing drift between the (remote) source clock and the local clock. This technique is now extended to provide an estimate of the remote-to-local clock difference itself. (The remote-to-local clock difference is used to remap the timing of audio frames from the remote device's clock to the local clock.)
@@ -34,6 +34,7 @@ Version 3.3.7
* Added in commented-out code to check the timeliness of the release of audio to a back end without synchronisation, e.g. a pipe or `stdout`. TL;DR – so long as the back end does not block, frames will be released to it not more than one packet (352 frames) late.
* Logs and statistics can now be directed to the system log (default), `stdout`, `stderr` or to a file or pipe of your choice using a new setting, `log_output_to` in the `diagnostics` section of the configuration file. This is very useful when the system log is disabled or diverted.
* Audio data from the `pipe` back end and metadata from the metadata pipe are now written using standard blocking `write` commands rather than a slightly complex non-blocking write function. Pipes are now opened in non-blocking mode and changed to blocking mode when successfully opened.
+* Add a default name for the pipe backend: `/tmp/shairport-sync-audio`.
* Separate threads are now used for each metadata subsystem. Until now, all metadata was processed on a single thread. This included writing to the metadata pipe and the multicast stream and supplying metadata for the `mqtt` interface and for the `dbus` and `MPRIS` interfaces. Unfortunately, that meant that a problem with any one of these subsystems could propagate into the others. Now they all run on separate threads. If one thread blocks, it will not interfere with the other subsystems.
* Cleaned up and improved the code to synchronise the first frame of audio. This should result in more accurate and reliable initial synchronisation, usually to under a millisecond, and often to within 20 or 30 microseconds. Syncronisation should improve even when the silent lead-in time is as short as 0.3 seconds or when the `audio_backend_latency_offset_in_seconds` is as much as -1.7 seconds, i.e. when only 0.3 seconds of latency are left when the latency would normally be 2.0 seconds.
* Cleaned up some confused uses of modulo arithmetic.
@@ -43,6 +44,12 @@ Version 3.3.7
* Improved synchronisation accuracy with short silence lead-ins.
* While a player is active, the DACP port number to which to send remote commands should be broadcast over ZEROCONF/Bonjour. However, if that information is not available, Shairport Sync will now check for it every two seconds.
* The timing software in the `sndio` backend does some extra sanity checking on certain time estimates, it may help a little when running on virtual machines.
+* Make the first output backend in the list of backends the default and make its name the default `output_name`.
+* Fix a flaw in resyncing -- a flush is set up but not triggered.
+* Open metadata and audio pipes with 666 permissions to allow them to be shared by other applications.
+* Ensure metadata and cover art are enabled if metadata support is included at compilation.
+* Set convolution defaults even if no configuration file is found.
+* Handle the `active_remote_id` token as a string rather than an unsigned 32-bit number -- it seems ROON uses a longer character sequence.
Version 3.3.6
====
From 4bc77f43b71d1be57d49f1830e47cd6b6b1e6f46 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 1 Dec 2020 12:15:07 +0000
Subject: [PATCH 042/180] 3.3.7 Final
---
RELEASENOTES.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 31ebd8d7..83a98d8a 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -19,6 +19,11 @@ Version 3.3.7
* Removed a bug which would affect initial synchronisation if a `FLUSH` command was received from the player at an inopportune time.
* Added code to do calculations involving the `audio_backend_latency_offset_in_seconds` and `audio_backend_silent_lead_in_time` settings correctly. Many thanks to [Tucker Kern](https://github.com/mill1000) for discovering a number of bugs associated with this and for [proposing a solution](https://github.com/mikebrady/shairport-sync/pull/1001). This prompted a closer investigation and a number of further improvements were made, and a few "hostages to fortune" removed.
* The DACP ID looks like a 64-bit number expressed in hexadecimal. It is normally quoted with leading zeroes removed, but in the `_dacp._tcp` service string, leading zeros are not removed from the DACP ID. The bug fix removes those leading zeroes. Thanks to [julianc1969](https://github.com/juliandc1969) for tracking down this bug so tenaciously!
+* Make the first output backend in the list of backends the default and make its name the default `output_name`.
+* Fix a flaw in resyncing -- a flush is set up but not triggered.
+* Ensure metadata and cover art are enabled if metadata support is included at compilation.
+* Set convolution defaults even if no configuration file is found.
+* Handle the `active_remote_id` token as a string rather than an unsigned 32-bit number -- it seems ROON uses a longer character sequence.
**Enhancements**
* Added the strings `-alac` and `-jack` to the the version string in '-V' if Apple ALAC decoder support and Jack Audio support are included respectively.
@@ -44,12 +49,7 @@ Version 3.3.7
* Improved synchronisation accuracy with short silence lead-ins.
* While a player is active, the DACP port number to which to send remote commands should be broadcast over ZEROCONF/Bonjour. However, if that information is not available, Shairport Sync will now check for it every two seconds.
* The timing software in the `sndio` backend does some extra sanity checking on certain time estimates, it may help a little when running on virtual machines.
-* Make the first output backend in the list of backends the default and make its name the default `output_name`.
-* Fix a flaw in resyncing -- a flush is set up but not triggered.
* Open metadata and audio pipes with 666 permissions to allow them to be shared by other applications.
-* Ensure metadata and cover art are enabled if metadata support is included at compilation.
-* Set convolution defaults even if no configuration file is found.
-* Handle the `active_remote_id` token as a string rather than an unsigned 32-bit number -- it seems ROON uses a longer character sequence.
Version 3.3.6
====
From 02cd3f8fdb4f2ba0a601a1d395b558c95a49afa2 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 1 Dec 2020 12:16:11 +0000
Subject: [PATCH 043/180] 3.3.7 Final
---
RELEASENOTES.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 83a98d8a..273b6668 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -26,7 +26,7 @@ Version 3.3.7
* Handle the `active_remote_id` token as a string rather than an unsigned 32-bit number -- it seems ROON uses a longer character sequence.
**Enhancements**
-* Added the strings `-alac` and `-jack` to the the version string in '-V' if Apple ALAC decoder support and Jack Audio support are included respectively.
+* Added the strings `-alac` and `-jack` to the the version string returned by the `-V` command line option if Apple ALAC decoder support and Jack Audio support are included respectively.
* Cleaned up and simplified the code that handles `FLUSH` requests coming from the player. (Debug messages are still a little verbose.)
* Improved timing estimation. Shairport Sync has been using linear least-squares regression to estimate timing drift between the (remote) source clock and the local clock. This technique is now extended to provide an estimate of the remote-to-local clock difference itself. (The remote-to-local clock difference is used to remap the timing of audio frames from the remote device's clock to the local clock.)
From 5146efd768a00b7d084cf1e80f3de51c584cadd3 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 5 Dec 2020 18:00:47 +0000
Subject: [PATCH 044/180] Bugfix -- don't try to create the metadata pipe or
output metadata to it when metadata has not been enabled.
---
rtsp.c | 3 ++-
shairport.c | 6 ++++--
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index abfabc4f..184b411e 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1793,7 +1793,8 @@ int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char *
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block) {
int rc;
- rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
+ if (config.metadata_enabled)
+ rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
#ifdef CONFIG_METADATA_HUB
rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
diff --git a/shairport.c b/shairport.c
index ba995233..9cedc72d 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1362,7 +1362,8 @@ void exit_function() {
#endif
#ifdef CONFIG_METADATA
- metadata_stop(); // close down the metadata pipe
+ if (config.metadata_enabled)
+ metadata_stop(); // close down the metadata pipe
#endif
activity_monitor_stop(0);
@@ -1952,7 +1953,8 @@ int main(int argc, char **argv) {
#endif
memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr));
#ifdef CONFIG_METADATA
- metadata_init(); // create the metadata pipe if necessary
+ if (config.metadata_enabled)
+ metadata_init(); // create the metadata pipe if necessary
#endif
#ifdef CONFIG_METADATA_HUB
From fc1a32a18dbf543dc97bfa1a7a606816119e7fc8 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 5 Dec 2020 18:01:04 +0000
Subject: [PATCH 045/180] Bugfix -- don't try to create the metadata pipe or
output metadata to it when metadata has not been enabled.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index ed0dcdaa..d8ffc086 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.7], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [3.3.8d0], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 7b716cf41eaa18ae1d73e08e5e7d2e4202535f67 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 5 Dec 2020 18:02:03 +0000
Subject: [PATCH 046/180] Bugfix -- don't try to create the metadata pipe or
output metadata to it when metadata has not been enabled.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index d8ffc086..693b224d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.8d0], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [3.3.8rc0], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 83c9e3610dcba99d6f542c920a3143390c27afc4 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 5 Dec 2020 18:06:47 +0000
Subject: [PATCH 047/180] Update RELEASENOTES.md
---
RELEASENOTES.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 273b6668..71cb2197 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -1,5 +1,10 @@
Please see the [Release Notes for 3.3](https://github.com/mikebrady/shairport-sync/releases/tag/3.3).
+Version 3.3.8
+====
+**Bug Fix**
+* Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) and [Ircama](https://github.com/Ircama) for their reports.
+
Version 3.3.7
====
**Docker Integration**
From b8f348d459e326997498c8895ae8d24dda620ddd Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 7 Dec 2020 14:04:22 +0000
Subject: [PATCH 048/180] Restore compatibility with AirPower on Android.
---
dacp.c | 10 ++++--
rtsp.c | 91 ++++++++++++++++++++++++++++-------------------------
shairport.c | 4 +--
3 files changed, 57 insertions(+), 48 deletions(-)
diff --git a/dacp.c b/dacp.c
index acb963b9..effbe5ec 100644
--- a/dacp.c
+++ b/dacp.c
@@ -445,9 +445,13 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
}
if (dacp_server.active_remote_id)
free(dacp_server.active_remote_id);
- dacp_server.active_remote_id =
- strdup(conn->dacp_active_remote); // even if the dacp_id remains the same,
- // the active remote will change.
+ if (conn->dacp_active_remote)
+ dacp_server.active_remote_id =
+ strdup(conn->dacp_active_remote); // even if the dacp_id remains the same,
+ // the active remote will change.
+ else
+ dacp_server.active_remote_id = NULL;
+
debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id);
pthread_cond_signal(&dacp_server_information_cv);
debug_mutex_unlock(&dacp_server_information_lock, 3);
diff --git a/rtsp.c b/rtsp.c
index 184b411e..f3524ae2 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1652,41 +1652,41 @@ void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
#endif
void metadata_init(void) {
+ int ret;
+ if (config.metadata_enabled) {
+ // create the metadata pipe, if necessary
+ size_t pl = strlen(config.metadata_pipename) + 1;
+ char *path = malloc(pl + 1);
+ snprintf(path, pl + 1, "%s", config.metadata_pipename);
+ mode_t oldumask = umask(000);
+ if (mkfifo(path, 0666) && errno != EEXIST)
+ die("Could not create metadata pipe \"%s\".", path);
+ umask(oldumask);
+ debug(1, "metadata pipe name is \"%s\".", path);
- // create the metadata pipe, if necessary
- size_t pl = strlen(config.metadata_pipename) + 1;
- char *path = malloc(pl + 1);
- snprintf(path, pl + 1, "%s", config.metadata_pipename);
+ // try to open it
+ fd = try_to_open_pipe_for_writing(path);
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ if ((fd == -1) && (errno != ENXIO)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ }
+ free(path);
+ int ret;
+ ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
+ if (ret)
+ debug(1, "Failed to create metadata thread!");
- mode_t oldumask = umask(000);
- if (mkfifo(path, 0666) && errno != EEXIST)
- die("Could not create metadata pipe \"%s\".", path);
- umask(oldumask);
- debug(1, "metadata pipe name is \"%s\".", path);
-
- // try to open it
- fd = try_to_open_pipe_for_writing(path);
- // we check that it's not a "real" error. From the "man 2 open" page:
- // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
- // open for reading." Which is okay.
- if ((fd == -1) && (errno != ENXIO)) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
- warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
+ ret = pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, NULL);
+ if (ret)
+ debug(1, "Failed to create metadata multicast thread!");
}
- free(path);
-
- int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
- if (ret)
- debug(1, "Failed to create metadata thread!");
-
- ret = pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, NULL);
- if (ret)
- debug(1, "Failed to create metadata multicast thread!");
-
#ifdef CONFIG_METADATA_HUB
ret = pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL);
if (ret)
@@ -1715,15 +1715,20 @@ void metadata_stop(void) {
pthread_join(metadata_hub_thread, NULL);
// debug(2, "metadata stop hub done.");
#endif
- // debug(2, "metadata stop multicast thread.");
- pthread_cancel(metadata_multicast_thread);
- pthread_join(metadata_multicast_thread, NULL);
- // debug(2, "metadata stop multicast done.");
-
- // debug(2, "metadata stop metadata_thread thread.");
- pthread_cancel(metadata_thread);
- pthread_join(metadata_thread, NULL);
- // debug(2, "metadata_stop finished successfully.");
+ if (config.metadata_enabled) {
+ // debug(2, "metadata stop multicast thread.");
+ if (metadata_multicast_thread) {
+ pthread_cancel(metadata_multicast_thread);
+ pthread_join(metadata_multicast_thread, NULL);
+ // debug(2, "metadata stop multicast done.");
+ }
+ if (metadata_thread) {
+ // debug(2, "metadata stop metadata_thread thread.");
+ pthread_cancel(metadata_thread);
+ pthread_join(metadata_thread, NULL);
+ // debug(2, "metadata_stop finished successfully.");
+ }
+ }
}
}
@@ -1793,8 +1798,10 @@ int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char *
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block) {
int rc;
- if (config.metadata_enabled)
+ if (config.metadata_enabled) {
rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
+ rc = send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
+ }
#ifdef CONFIG_METADATA_HUB
rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
diff --git a/shairport.c b/shairport.c
index 9cedc72d..db2c7531 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1362,7 +1362,6 @@ void exit_function() {
#endif
#ifdef CONFIG_METADATA
- if (config.metadata_enabled)
metadata_stop(); // close down the metadata pipe
#endif
@@ -1953,8 +1952,7 @@ int main(int argc, char **argv) {
#endif
memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr));
#ifdef CONFIG_METADATA
- if (config.metadata_enabled)
- metadata_init(); // create the metadata pipe if necessary
+ metadata_init(); // create the metadata pipe if necessary
#endif
#ifdef CONFIG_METADATA_HUB
From b291b951d71f42d7770d8357af6499455f17f78d Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 7 Dec 2020 14:04:53 +0000
Subject: [PATCH 049/180] Restore compatibility with AirPower on Android.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 693b224d..d745ac71 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.8rc0], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [3.3.8rc1], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From dabaa32aa5824f51e3c27bb7ebafb0dce3bd633d Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 7 Dec 2020 14:08:43 +0000
Subject: [PATCH 050/180] Update RELEASENOTES.md
---
RELEASENOTES.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 71cb2197..dda91608 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -3,7 +3,8 @@ Please see the [Release Notes for 3.3](https://github.com/mikebrady/shairport-sy
Version 3.3.8
====
**Bug Fix**
-* Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) and [Ircama](https://github.com/Ircama) for their reports.
+* Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) for the report.
+* Fix a crash that occurred playing from AirPower on Android. Thanks to [Ircama](https://github.com/Ircama) for the report.
Version 3.3.7
====
From 272b3db5ffdc7be6d21216c0b602fe02f0cdf3b6 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 23 Dec 2020 17:41:39 +0000
Subject: [PATCH 051/180] Update RELEASENOTES.md
---
RELEASENOTES.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index dda91608..0554aee1 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -2,7 +2,11 @@ Please see the [Release Notes for 3.3](https://github.com/mikebrady/shairport-sy
Version 3.3.8
====
-**Bug Fix**
+**Enhancements**
+* Documentation for the MQTT interface. Many thanks to [minix1234](https://github.com/minix1234)!
+
+**Bug Fixes**
+* Fix a bug that caused Shairport Sync to hang, but not actually crash, if an on-play or any on-*** script failed.
* Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) for the report.
* Fix a crash that occurred playing from AirPower on Android. Thanks to [Ircama](https://github.com/Ircama) for the report.
From e189710499e97c72c0db3092022a206d32114fe7 Mon Sep 17 00:00:00 2001
From: Chris Boot
Date: Thu, 7 Jan 2021 01:04:16 +0000
Subject: [PATCH 052/180] Fix typos of "daemon"
---
shairport.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/shairport.c b/shairport.c
index ba995233..a32216d3 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1322,7 +1322,7 @@ void exit_function() {
#ifdef CONFIG_LIBDAEMON
if ((this_is_the_daemon_process) ||
(config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not
- // actually deamonised at all
+ // actually daemonised at all
#endif
debug(2, "exit function called...");
/*
@@ -1617,7 +1617,7 @@ int main(int argc, char **argv) {
debug(2, "killed the %s daemon.", config.appName);
else
daemon_log(LOG_WARNING,
- "killed the %s deamon, but cannot remove old PID file: \"%s\", errno %u.",
+ "killed the %s daemon, but cannot remove old PID file: \"%s\", errno %u.",
config.appName, strerror(errno), errno);
}
return ret < 0 ? 1 : 0;
From cfc6612e7536f438482c7b7723289452ac82ee5d Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 24 Jan 2021 17:34:33 +0000
Subject: [PATCH 053/180] Update RELEASENOTES.md
---
RELEASENOTES.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 0554aee1..5f67ac57 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -9,6 +9,7 @@ Version 3.3.8
* Fix a bug that caused Shairport Sync to hang, but not actually crash, if an on-play or any on-*** script failed.
* Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) for the report.
* Fix a crash that occurred playing from AirPower on Android. Thanks to [Ircama](https://github.com/Ircama) for the report.
+* Fix the configure.ac file so that `--without-` configuration options are not interpreted as `--with-` options instead! Thanks to [David Racine](https://github.com/bassdr) for the report.
Version 3.3.7
====
From e5362c770ddb0adfa351da56561d35e22458439a Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 30 Jan 2021 11:39:56 +0000
Subject: [PATCH 054/180] Update INSTALL.md
---
INSTALL.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/INSTALL.md b/INSTALL.md
index 964be275..4748b4fd 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -33,8 +33,8 @@ Remove it as follows:
```
Do this until no more copies of `shairport-sync` are found.
-### Remove Old Startup Scripts
-You should also remove the startup script files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed if necessary.
+### Remove Old Startup and Service Scripts
+You should also remove the startup script and service definition files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service`, `/etc/init.d/shairport-sync`, `/etc/dbus-1/system.d/shairport-sync-dbus.conf` and `/etc/dbus-1/system.d/shairport-sync-mpris.conf` if they exist – new ones will be installed if necessary.
### Reboot after Cleaning Up
If you removed any installations of Shairport Sync or any of its startup script files in the last two steps, you should reboot.
From be24f4747316170bad0e4d7332c036bce95ba25c Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 30 Jan 2021 11:40:49 +0000
Subject: [PATCH 055/180] Update UPDATING.md
---
UPDATING.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/UPDATING.md b/UPDATING.md
index 5803e27c..69885285 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -29,8 +29,8 @@ Remove it as follows:
```
Do this until no more copies of `shairport-sync` are found. This is especially important if you are building Shairport Sync over an old version that was installed from packages, as the built application will be installed in a different directory to the packaged installation.
-### Remove Old Startup Scripts
-You should also remove the startup script files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed if necessary. As with the previous section, this is especially important if you are building Shairport Sync over an old version that was installed from packages, as the scripts for the built application may be different to those of the packaged version. If they are left in place, bad things can happen.
+### Remove Old Startup and Service Scripts
+You should also remove the startup script and service definition files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service`, `/etc/init.d/shairport-sync`, `/etc/dbus-1/system.d/shairport-sync-dbus.conf` and `/etc/dbus-1/system.d/shairport-sync-mpris.conf` if they exist – new ones will be installed if necessary. As with the previous section, this is especially important if you are building Shairport Sync over an old version that was installed from packages, as the scripts for the built application may be different to those of the packaged version. If they are left in place, bad things can happen.
### Reboot after Cleaning Up
If you removed any installations of Shairport Sync or any of its startup script files in the last two steps, you should reboot.
From 1d6bd41db83553c5d0c3a88da81610030b83623c Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 30 Jan 2021 11:42:02 +0000
Subject: [PATCH 056/180] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 45534d9d..2a49d1cb 100644
--- a/README.md
+++ b/README.md
@@ -95,7 +95,7 @@ If you wish to build and install the latest version of Shairport Sync on Debian,
You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
-You should also remove the startup script files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
+You should also remove the startup script and service definition files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service`, `/etc/init.d/shairport-sync`, `/etc/dbus-1/system.d/shairport-sync-dbus.conf` and `/etc/dbus-1/system.d/shairport-sync-mpris.conf` if they exist – new ones will be installed if necessary.
If you removed any installations of Shairport Sync or any of its startup script files, you should reboot.
From b91eb443afebdc82e26e7a648c288f7b1d1237de Mon Sep 17 00:00:00 2001
From: GriffOn Yi
Date: Tue, 2 Feb 2021 23:09:35 +0800
Subject: [PATCH 057/180] add gentoo guide
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 2a49d1cb..9f8d8324 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,8 @@ Note that the installation uses the libao library and so synchronisation is not
**Fedora:** Please see the guide at [FEDORA.md](https://github.com/mikebrady/shairport-sync/blob/master/FEDORA.md).
+**Gentoo:** Shairport Sync is available in griffon_overlay, please see the guide at [https://github.com/windfail/griffon_overlay/wiki/shairport-sync](https://github.com/windfail/griffon_overlay/wiki/shairport-sync).
+
**Cygwin:** Please see the guide at [CYGWIN.md](https://github.com/mikebrady/shairport-sync/blob/master/CYGWIN.md).
Sincere thanks to all package contributors!
From 030923b666e37b6509039ff9980fafec1f7238e3 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 22 Feb 2021 07:39:06 +0000
Subject: [PATCH 058/180] Update INSTALL.md
---
INSTALL.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/INSTALL.md b/INSTALL.md
index 4748b4fd..85a24451 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -17,7 +17,7 @@ If you are using WiFi, you should turn off WiFi Power Management:
```
# iwconfig wlan0 power off
```
-WiFi Power Management will put the WiFi system in low-power mode when the WiFi system considered inactive, and in this mode it may not respond to events initiated from the network, such as AirPlay requests. Hence, WiFi Power Management should be turned off. (See [TROUBLESHOOTING.md](https://github.com/mikebrady/shairport-sync/blob/master/TROUBLESHOOTING.md#wifi-adapter-running-in-power-saving--low-power-mode) for more details.)
+WiFi Power Management will put the WiFi system in low-power mode when the WiFi system is considered inactive, and in this mode it may not respond to events initiated from the network, such as AirPlay requests. Hence, WiFi Power Management should be turned off. (See [TROUBLESHOOTING.md](https://github.com/mikebrady/shairport-sync/blob/master/TROUBLESHOOTING.md#wifi-adapter-running-in-power-saving--low-power-mode) for more details.)
Reboot the Pi.
From 8b65069e634f9d9d4395b30538f8f5dee0e66d53 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 8 Mar 2021 17:48:41 +0000
Subject: [PATCH 059/180] Update README.md
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 9f8d8324..e6f6d358 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,8 @@ Shairport Sync may already be available as a package in your Linux distribution
**Debian:** shairport-sync is in the Debian archive.
+**Ansible Playbooks for CentOS 8 Stream on Raspberry Pi** A suite of Ansible playbooks and scripts to make deploying Shairport Sync easy on CentOS 8 Stream specifically on Raspberry Pi is available at https://github.com/p3ck/ansible-shairport.
+
**OpenWrt:** There is a Shairport Sync package in OpenWrt trunk. Also, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt, including one that builds back to Barrier Breaker.
**Arch Linux:** Shairport Sync is available for x86_64 and i686 platforms in the Arch Linux Community Repository -- search for `shairport-sync`. See also https://www.archlinux.org/packages/.
From a946296302a44f4da9ebf2e8741854dbf9977b25 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 8 Mar 2021 17:49:10 +0000
Subject: [PATCH 060/180] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e6f6d358..15a04c70 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ Shairport Sync may already be available as a package in your Linux distribution
**Debian:** shairport-sync is in the Debian archive.
-**Ansible Playbooks for CentOS 8 Stream on Raspberry Pi** A suite of Ansible playbooks and scripts to make deploying Shairport Sync easy on CentOS 8 Stream specifically on Raspberry Pi is available at https://github.com/p3ck/ansible-shairport.
+**Ansible Playbooks for CentOS 8 Stream on Raspberry Pi:** A suite of Ansible playbooks and scripts to make deploying Shairport Sync easy on CentOS 8 Stream specifically on Raspberry Pi is available at https://github.com/p3ck/ansible-shairport.
**OpenWrt:** There is a Shairport Sync package in OpenWrt trunk. Also, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt, including one that builds back to Barrier Breaker.
From ec2323637072ad8358b0d7d55a708e3056b0047d Mon Sep 17 00:00:00 2001
From: Lukas Rusak
Date: Thu, 1 Apr 2021 08:50:48 -0700
Subject: [PATCH 061/180] pipewire: add initial support
---
Makefile.am | 4 +
audio.c | 6 +
audio_pw.c | 501 +++++++++++++++++++++++++++++++++++++++++++++++++++
configure.ac | 15 ++
4 files changed, 526 insertions(+)
create mode 100644 audio_pw.c
diff --git a/Makefile.am b/Makefile.am
index c9ccb75e..0de2c4ba 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -80,6 +80,10 @@ if USE_PA
shairport_sync_SOURCES += audio_pa.c
endif
+if USE_PW
+shairport_sync_SOURCES += audio_pw.c
+endif
+
if USE_CONVOLUTION
shairport_sync_SOURCES += FFTConvolver/AudioFFT.cpp FFTConvolver/FFTConvolver.cpp FFTConvolver/Utilities.cpp FFTConvolver/convolver.cpp
AM_CXXFLAGS += -std=c++11
diff --git a/audio.c b/audio.c
index 0fe7b04f..10efbd9e 100644
--- a/audio.c
+++ b/audio.c
@@ -43,6 +43,9 @@ extern audio_output audio_ao;
#ifdef CONFIG_SOUNDIO
extern audio_output audio_soundio;
#endif
+#ifdef CONFIG_PW
+extern audio_output audio_pw;
+#endif
#ifdef CONFIG_PA
extern audio_output audio_pa;
#endif
@@ -66,6 +69,9 @@ static audio_output *outputs[] = {
#ifdef CONFIG_SNDIO
&audio_sndio,
#endif
+#ifdef CONFIG_PW
+ &audio_pw,
+#endif
#ifdef CONFIG_PA
&audio_pa,
#endif
diff --git a/audio_pw.c b/audio_pw.c
new file mode 100644
index 00000000..58a5fd72
--- /dev/null
+++ b/audio_pw.c
@@ -0,0 +1,501 @@
+/*
+ * Asynchronous Pipewire Backend. This file is part of Shairport Sync.
+ * Copyright (c) Shairport Sync 2021
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "audio.h"
+#include "common.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+struct pw_data {
+ struct pw_thread_loop *mainloop;
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct pw_buffer *pw_buffer;
+
+ struct pw_properties *props;
+ int sync;
+
+ enum spa_audio_format format;
+ uint32_t rate;
+ uint32_t channels;
+ uint32_t stride;
+ uint32_t latency;
+
+} data;
+
+static void on_core_info(__attribute__((unused)) void *userdata, const struct pw_core_info *info)
+{
+ debug(1, "pw: remote %"PRIu32" is named \"%s\"", info->id, info->name);
+}
+
+static void on_core_error(__attribute__((unused)) void *userdata, uint32_t id, int seq, int res, const char *message)
+{
+ warn("pw: remote error: id=%"PRIu32" seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .info = on_core_info,
+ .error = on_core_error,
+};
+
+static void registry_event_global(__attribute__((unused)) void *userdata, uint32_t id,
+ __attribute__((unused)) uint32_t permissions, const char *type, __attribute__((unused)) uint32_t version,
+ const struct spa_dict *props)
+{
+ const struct spa_dict_item *item;
+ const char *name, *media_class;
+
+ if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0) {
+ name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
+ media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+
+ if (!name || !media_class)
+ return;
+
+ debug(1, "pw: registry: id=%"PRIu32" type=%s name=\"%s\" media_class=\"%s\"", id, type, name, media_class);
+
+ spa_dict_for_each(item, props) {
+ debug(1, "pw: \t\t%s = \"%s\"", item->key, item->value);
+ }
+ }
+}
+
+static void registry_event_global_remove(__attribute__((unused)) void *userdata, uint32_t id)
+{
+ debug(1, "pw: registry: remove id=%"PRIu32"", id);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static void on_state_changed(void *userdata, enum pw_stream_state old, enum pw_stream_state state, const char *error)
+{
+ struct pw_data *pipewire = userdata;
+
+ debug(1, "pw: stream state changed %s -> %s", pw_stream_state_as_string(old), pw_stream_state_as_string(state));
+
+ if (state == PW_STREAM_STATE_STREAMING)
+ debug(1, "pw: stream node %"PRIu32"", pw_stream_get_node_id(pipewire->stream));
+
+ if (state == PW_STREAM_STATE_ERROR)
+ debug(1, "pw: stream node %"PRIu32" error: %s", pw_stream_get_node_id(pipewire->stream), error);
+
+ pw_thread_loop_signal(pipewire->mainloop, 0);
+}
+
+static void on_process(void *userdata)
+{
+ struct pw_data *pipewire = userdata;
+
+ pw_thread_loop_signal(pipewire->mainloop, 0);
+}
+
+static void on_drained(void *userdata)
+{
+ struct pw_data *pipewire = userdata;
+
+ pw_stream_set_active(pipewire->stream, false);
+
+ pw_thread_loop_signal(pipewire->mainloop, 0);
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = on_state_changed,
+ .process = on_process,
+ .drained = on_drained,
+};
+
+static void deinit() {
+ pw_thread_loop_stop(data.mainloop);
+
+ if (data.stream) {
+ pw_stream_destroy(data.stream);
+ data.stream = NULL;
+ }
+
+ if (data.registry) {
+ pw_proxy_destroy((struct pw_proxy*)data.registry);
+ data.registry = NULL;
+ }
+
+ if (data.core) {
+ pw_core_disconnect(data.core);
+ data.core = NULL;
+ }
+
+ if (data.context) {
+ pw_context_destroy(data.context);
+ data.context = NULL;
+ }
+
+ if (data.mainloop) {
+ pw_thread_loop_destroy(data.mainloop);
+ data.mainloop = NULL;
+ }
+
+ if (data.props) {
+ pw_properties_free(data.props);
+ data.props = NULL;
+ }
+}
+
+static int init(__attribute__((unused)) int argc, __attribute__((unused)) char** argv) {
+
+ struct pw_loop *loop;
+ struct pw_properties* props;
+
+ // set up default values first
+ config.audio_backend_buffer_desired_length = 0.35;
+ config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.02;
+ config.audio_backend_latency_offset = 0;
+
+ pw_init(NULL, NULL);
+
+ debug(1, "pw: compiled with libpipewire %s", pw_get_headers_version());
+ debug(1, "pw: linked with libpipewire: %s", pw_get_library_version());
+
+ data.props = pw_properties_new(
+ PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Playback",
+ PW_KEY_MEDIA_ROLE, "Music",
+ PW_KEY_APP_NAME, "shairport-sync",
+ PW_KEY_NODE_NAME, "shairport-sync",
+ NULL);
+
+ if (!data.props) {
+ deinit();
+ die("pw: pw_properties_new() failed: %m");
+ }
+
+ data.mainloop = pw_thread_loop_new("pipewire", NULL);
+ if (!data.mainloop) {
+ deinit();
+ die("pw: pw_thread_loop_new_full() failed: %m");
+ }
+
+ props = pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", NULL);
+ if (!props) {
+ deinit();
+ die("pw: pw_properties_new() failed: %m");
+ }
+
+ loop = pw_thread_loop_get_loop(data.mainloop);
+
+ data.context = pw_context_new(loop, props, 0);
+ if (!data.context) {
+ deinit();
+ die("pw: pw_context_new() failed: %m");
+ }
+
+ props = pw_properties_new(PW_KEY_REMOTE_NAME, NULL, NULL);
+ if (!props) {
+ deinit();
+ die("pw: pw_properties_new() failed: %m");
+ }
+
+ pw_thread_loop_lock(data.mainloop);
+
+ if (pw_thread_loop_start(data.mainloop) != 0) {
+ deinit();
+ die("pw: pw_thread_loop_start() failed: %m");
+ }
+
+ data.core = pw_context_connect(data.context, props, 0);
+ if (!data.core) {
+ deinit();
+ die("pw: pw_context_connect() failed: %m");
+ }
+
+ pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
+
+ data.registry = pw_core_get_registry(data.core, PW_VERSION_REGISTRY, 0);
+ if (!data.registry) {
+ deinit();
+ die("pw: pw_core_get_registry() failed: %m");
+ }
+
+ pw_registry_add_listener(data.registry, &data.registry_listener, ®istry_events, &data);
+
+ data.sync = pw_core_sync(data.core, 0, data.sync);
+
+ pw_thread_loop_unlock(data.mainloop);
+
+ return 0;
+}
+
+static enum spa_audio_format sps_format_to_spa_format(sps_format_t sps_format) {
+ switch (sps_format) {
+ case SPS_FORMAT_S8:
+ return SPA_AUDIO_FORMAT_S8;
+ case SPS_FORMAT_U8:
+ return SPA_AUDIO_FORMAT_U8;
+ case SPS_FORMAT_S16:
+ return SPA_AUDIO_FORMAT_S16;
+ case SPS_FORMAT_S16_LE:
+ return SPA_AUDIO_FORMAT_S16_LE;
+ case SPS_FORMAT_S16_BE:
+ return SPA_AUDIO_FORMAT_S16_BE;
+ case SPS_FORMAT_S24:
+ return SPA_AUDIO_FORMAT_S24_32;
+ case SPS_FORMAT_S24_LE:
+ return SPA_AUDIO_FORMAT_S24_32_LE;
+ case SPS_FORMAT_S24_BE:
+ return SPA_AUDIO_FORMAT_S24_32_BE;
+ case SPS_FORMAT_S24_3LE:
+ return SPA_AUDIO_FORMAT_S24_LE;
+ case SPS_FORMAT_S24_3BE:
+ return SPA_AUDIO_FORMAT_S24_BE;
+ case SPS_FORMAT_S32:
+ return SPA_AUDIO_FORMAT_S32;
+ case SPS_FORMAT_S32_LE:
+ return SPA_AUDIO_FORMAT_S32_LE;
+ case SPS_FORMAT_S32_BE:
+ return SPA_AUDIO_FORMAT_S32_BE;
+
+ case SPS_FORMAT_UNKNOWN:
+ case SPS_FORMAT_AUTO:
+ case SPS_FORMAT_INVALID:
+ default:
+ return SPA_AUDIO_FORMAT_S16;
+ }
+}
+
+static int spa_format_samplesize(enum spa_audio_format audio_format)
+{
+ switch(audio_format) {
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_U8:
+ return 1;
+ case SPA_AUDIO_FORMAT_S16:
+ return 2;
+ case SPA_AUDIO_FORMAT_S24:
+ return 3;
+ case SPA_AUDIO_FORMAT_S24_32:
+ case SPA_AUDIO_FORMAT_S32:
+ return 4;
+ default:
+ die("pw: unhandled spa_audio_format: %d", audio_format);
+ return -1;
+ }
+}
+
+static const char* spa_format_to_str(enum spa_audio_format audio_format)
+{
+ switch(audio_format) {
+ case SPA_AUDIO_FORMAT_U8:
+ return "u8";
+ case SPA_AUDIO_FORMAT_S8:
+ return "s8";
+ case SPA_AUDIO_FORMAT_S16:
+ return "s16";
+ case SPA_AUDIO_FORMAT_S24:
+ case SPA_AUDIO_FORMAT_S24_32:
+ return "s24";
+ case SPA_AUDIO_FORMAT_S32:
+ return "s32";
+ default:
+ die("pw: unhandled spa_audio_format: %d", audio_format);
+ return "(invalid)";
+ }
+}
+
+static void start(int sample_rate, int sample_format) {
+
+ pw_thread_loop_lock(data.mainloop);
+
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct spa_audio_info_raw info;
+ uint32_t nom;
+ int ret;
+
+ data.format = sps_format_to_spa_format(sample_format);
+ data.rate = sample_rate;
+ data.channels = 2;
+ data.stride = spa_format_samplesize(data.format) * data.channels;
+ data.latency = 20000;
+
+ nom = nearbyint((data.latency * data.rate) / 1000000.0);
+
+ pw_properties_setf(data.props, PW_KEY_NODE_LATENCY, "%u/%u", nom, data.rate);
+
+ debug(1, "pw: rate: %d", data.rate);
+ debug(1, "pw: channgels: %d", data.channels);
+ debug(1, "pw: format: %s", spa_format_to_str(data.format));
+ debug(1, "pw: samplesize: %d", spa_format_samplesize(data.format));
+ debug(1, "pw: stride: %d", data.stride);
+ debug(1, "pw: latency: %d samples (%.3fs)", nom, (double)nom/data.rate);
+
+ info = SPA_AUDIO_INFO_RAW_INIT(
+ .flags = SPA_AUDIO_FLAG_NONE,
+ .format = data.format,
+ .rate = data.rate,
+ .channels = data.channels);
+
+ params[0] = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_EnumFormat, &info);
+
+ data.stream = pw_stream_new(data.core, "shairport-sync", data.props);
+
+ if (!data.stream) {
+ deinit();
+ die("pw: pw_stream_new() failed: %m");
+ }
+
+ debug(1, "pw: connecting stream: target_id=%"PRIu32"", PW_ID_ANY);
+
+ pw_stream_add_listener(data.stream, &data.stream_listener, &stream_events, &data);
+
+ ret = pw_stream_connect(data.stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS,
+ params, 1);
+
+ if (ret < 0) {
+ deinit();
+ die("pw: pw_stream_connect() failed: %s", spa_strerror(ret));
+ }
+
+ const struct pw_properties *props;
+ void *pstate;
+ const char *key, *val;
+
+ if ((props = pw_stream_get_properties(data.stream)) != NULL) {
+ debug(1, "pw: stream properties:");
+ pstate = NULL;
+ while ((key = pw_properties_iterate(props, &pstate)) != NULL &&
+ (val = pw_properties_get(props, key)) != NULL) {
+ debug(1, "pw: \t%s = \"%s\"", key, val);
+ }
+ }
+
+ while(1) {
+ enum pw_stream_state stream_state = pw_stream_get_state(data.stream, NULL);
+ if (stream_state == PW_STREAM_STATE_PAUSED)
+ break;
+
+ pw_thread_loop_wait(data.mainloop);
+ }
+
+ pw_thread_loop_unlock(data.mainloop);
+}
+
+static void stop() {
+ pw_thread_loop_lock(data.mainloop);
+
+ pw_stream_flush(data.stream, true);
+
+ pw_thread_loop_unlock(data.mainloop);
+}
+
+static void flush() {
+ pw_thread_loop_lock(data.mainloop);
+
+ pw_stream_flush(data.stream, false);
+
+ pw_thread_loop_unlock(data.mainloop);
+}
+
+static int play(void *buf, int samples) {
+ struct pw_buffer *pw_buffer;
+ struct spa_buffer *spa_buffer;
+ struct spa_data *spa_data;
+
+ pw_thread_loop_lock(data.mainloop);
+
+ if (pw_stream_get_state(data.stream, NULL) == PW_STREAM_STATE_PAUSED)
+ pw_stream_set_active(data.stream, true);
+
+ while (pw_buffer == NULL) {
+ pw_buffer = pw_stream_dequeue_buffer(data.stream);
+ if (pw_buffer)
+ break;
+
+ pw_thread_loop_wait(data.mainloop);
+ }
+
+ spa_buffer = pw_buffer->buffer;
+ spa_data = &spa_buffer->datas[0];
+
+ size_t bytes_to_copy = samples * data.stride;
+
+ debug(3, "pw: bytes_to_copy: %d", bytes_to_copy);
+
+ if (spa_data->maxsize < bytes_to_copy)
+ bytes_to_copy = spa_data->maxsize;
+
+ debug(3, "pw: spa_data->maxsize: %d", spa_data->maxsize);
+
+ memcpy(spa_data->data, buf, bytes_to_copy);
+
+ spa_data->chunk->offset = 0;
+ spa_data->chunk->stride = data.stride;
+ spa_data->chunk->size = bytes_to_copy;
+
+ pw_stream_queue_buffer(data.stream, pw_buffer);
+
+ pw_thread_loop_unlock(data.mainloop);
+
+ return 0;
+}
+
+audio_output audio_pw = {
+ .name = "pw",
+ .help = NULL,
+ .init = &init,
+ .deinit = &deinit,
+ .prepare = NULL,
+ .start = &start,
+ .stop = &stop,
+ .is_running = NULL,
+ .flush = &flush,
+ .delay = NULL,
+ .play = &play,
+ .volume = NULL,
+ .parameters = NULL,
+ .mute = NULL
+};
diff --git a/configure.ac b/configure.ac
index 21ba3cec..34701f84 100644
--- a/configure.ac
+++ b/configure.ac
@@ -299,6 +299,21 @@ if test "x$with_pa" = "xyes" ; then
fi
AM_CONDITIONAL([USE_PA], [test "x$with_pa" = "xyes"])
+# Look for pipewire flag
+AC_ARG_WITH(pw, [AS_HELP_STRING([--with-pw],[choose Pipewire support.])])
+if test "x$with_pw" = "xyes" ; then
+ AC_DEFINE([CONFIG_PW], 1, [Include Pipewire support.])
+ if test "x${with_pkg_config}" = xyes ; then
+ PKG_CHECK_MODULES(
+ [PIPEWIRE], [libpipewire-0.3 >= 0.3.24],
+ [CFLAGS="${PIPEWIRE_CFLAGS} ${CFLAGS} -Wno-missing-field-initializers" LIBS="${PIPEWIRE_LIBS} ${LIBS}"],
+ [AC_MSG_ERROR(Pipewire support requires the libpipewire-dev library!)])
+ else
+ AC_CHECK_LIB([pipewire], [pw_stream_queue_buffer], , AC_MSG_ERROR(Pipewire support requires the libpipewire-dev library.))
+ fi
+fi
+AM_CONDITIONAL([USE_PW], [test "x$with_pw" = "xyes"])
+
# Look for Convolution flag
AC_ARG_WITH(convolution, [AS_HELP_STRING([--with-convolution],[choose audio DSP convolution support])])
if test "x$with_convolution" = "xyes" ; then
From 15e72dfb3e689c6d26978641f5e735bab8aa44b6 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 2 Apr 2021 18:43:54 +0100
Subject: [PATCH 062/180] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 15a04c70..38c7075d 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ What else?
* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/) interface, partially complete and very functional, including access to metadata and artwork, and some limited remote control.
* An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), an often-used protocol in home automation projects.
* A native D-Bus interface, including access to metadata and artwork, some limited remote control and some system settings.
+* Digital Signal Processing facilities -- please see the [DSP Wiki Page Guide](https://github.com/mikebrady/shairport-sync/wiki/Digital-Signal-Processing-with-Shairport-Sync). (Thanks to [Yann Pomarède](https://github.com/yannpom) for the code and to [Paul Wieland](https://github.com/PaulWieland) for the guide.)
Heritage
-------
From 6c628da897b6661e9aab1b620d103cbe1d5da789 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 26 Apr 2021 11:21:01 +0100
Subject: [PATCH 063/180] Update RELEASENOTES.md
---
RELEASENOTES.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 5f67ac57..f744408c 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -6,7 +6,9 @@ Version 3.3.8
* Documentation for the MQTT interface. Many thanks to [minix1234](https://github.com/minix1234)!
**Bug Fixes**
-* Fix a bug that caused Shairport Sync to hang, but not actually crash, if an on-play or any on-*** script failed.
+* Fix a bug in the `alsa` back end. In the interval between checking that the alsa device handle was non-`NULL` and actually using it, the handle could be set to `NULL`. The interval between check and usage is now protected.
+* Fix a bug in the `alsa` precision timing code. Thanks to [durwin99](https://github.com/durwin99), [Nicolas Da Mutten](https://github.com/cleverer), [mistakenideas](https://github.com/mistakenideas), [Ben Willmore](https://github.com/ben-willmore) and [giggywithit](https://github.com/giggywithit) for the [report](https://github.com/mikebrady/shairport-sync/issues/1158).
+* Fix a bug that caused Shairport Sync to hang, but not actually crash, if an on-play or any `on-...` script failed.
* Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) for the report.
* Fix a crash that occurred playing from AirPower on Android. Thanks to [Ircama](https://github.com/Ircama) for the report.
* Fix the configure.ac file so that `--without-` configuration options are not interpreted as `--with-` options instead! Thanks to [David Racine](https://github.com/bassdr) for the report.
From b9854d3fdcfc66a0b593b39cbfd15480abf319cb Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 26 Apr 2021 11:23:13 +0100
Subject: [PATCH 064/180] Version 3.3.8 is 3.3.8rc7.
---
audio_alsa.c | 81 ++++++++++++++++++++-----------
common.c | 2 +-
configure.ac | 15 +++---
dacp.c | 8 ++--
rtsp.c | 96 +++++++++++++++++++------------------
scripts/shairport-sync.conf | 1 +
6 files changed, 116 insertions(+), 87 deletions(-)
diff --git a/audio_alsa.c b/audio_alsa.c
index 69722d12..b464279c 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -1455,19 +1455,31 @@ int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
int ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status);
if (ret == 0) {
-// must be 1.1 or later to use snd_pcm_status_get_driver_htstamp
-#if SND_LIB_MINOR == 0
snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp);
-#else
- snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &update_timestamp);
+
+/*
+// must be 1.1 or later to use snd_pcm_status_get_driver_htstamp
+#if SND_LIB_MINOR != 0
+ snd_htimestamp_t driver_htstamp;
+ snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &driver_htstamp);
+ uint64_t driver_htstamp_ns = driver_htstamp.tv_sec;
+ driver_htstamp_ns = driver_htstamp_ns * 1000000000;
+ driver_htstamp_ns = driver_htstamp_ns + driver_htstamp.tv_nsec;
+ debug(1,"driver_htstamp: %f.", driver_htstamp_ns * 0.000000001);
#endif
+*/
*state = snd_pcm_status_get_state(alsa_snd_pcm_status);
if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) {
- uint64_t update_timestamp_ns =
- update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec;
+ // uint64_t update_timestamp_ns =
+ // update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec;
+
+ uint64_t update_timestamp_ns = update_timestamp.tv_sec;
+ update_timestamp_ns = update_timestamp_ns * 1000000000;
+ update_timestamp_ns = update_timestamp_ns + update_timestamp.tv_nsec;
+
// if the update_timestamp is zero, we take this to mean that the device doesn't report
// interrupt timings. (It could be that it's not a real hardware device.)
@@ -1497,7 +1509,7 @@ int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
if (update_timestamp_ns == 0) {
ret = snd_pcm_delay(alsa_handle, delay);
} else {
- *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status);
+ snd_pcm_sframes_t delay_temp = snd_pcm_status_get_delay(alsa_snd_pcm_status);
/*
// It seems that the alsa library uses CLOCK_REALTIME before 1.0.28, even though
@@ -1514,11 +1526,15 @@ int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
else
clock_gettime(CLOCK_REALTIME, &tn);
- uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec;
+ // uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec;
+ uint64_t time_now_ns = tn.tv_sec;
+ time_now_ns = time_now_ns * 1000000000;
+ time_now_ns = time_now_ns + tn.tv_nsec;
+
// see if it's stalled
- if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) {
+ if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == delay_temp)) {
// hasn't outputted anything since the last call to delay()
if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) ||
@@ -1539,19 +1555,27 @@ int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
}
} else {
stall_monitor_start_time = update_timestamp_ns;
- stall_monitor_frame_count = *delay;
+ stall_monitor_frame_count = delay_temp;
}
if (ret == 0) {
uint64_t delta = time_now_ns - update_timestamp_ns;
- uint64_t frames_played_since_last_interrupt =
- ((uint64_t)config.output_rate * delta) / 1000000000;
+// uint64_t frames_played_since_last_interrupt =
+// ((uint64_t)config.output_rate * delta) / 1000000000;
+
+ uint64_t frames_played_since_last_interrupt = config.output_rate;
+ frames_played_since_last_interrupt = frames_played_since_last_interrupt * delta;
+ frames_played_since_last_interrupt = frames_played_since_last_interrupt / 1000000000;
+
+
snd_pcm_sframes_t frames_played_since_last_interrupt_sized =
frames_played_since_last_interrupt;
-
- *delay = *delay - frames_played_since_last_interrupt_sized;
+ if ((frames_played_since_last_interrupt_sized < 0) || ((uint64_t)frames_played_since_last_interrupt_sized != frames_played_since_last_interrupt))
+ debug(1,"overflow resizing frames_played_since_last_interrupt % " PRIx64 " to frames_played_since_last_interrupt %lx.", frames_played_since_last_interrupt, frames_played_since_last_interrupt_sized);
+ delay_temp = delay_temp - frames_played_since_last_interrupt_sized;
}
+ *delay = delay_temp;
}
} else { // not running, thus no delay information, thus can't check for
// stall
@@ -1582,25 +1606,26 @@ int delay(long *the_delay) {
// sps_extra_code_output_state_cannot_make_ready codes
int ret = 0;
*the_delay = 0;
+
+ int oldState;
+
+ snd_pcm_state_t state;
+ snd_pcm_sframes_t my_delay = 0; // this initialisation is to silence a clang warning
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0);
+
if (alsa_handle == NULL)
ret = ENODEV;
- else {
- int oldState;
-
- snd_pcm_state_t state;
- snd_pcm_sframes_t my_delay = 0; // this initialisation is to silence a clang warning
-
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
- pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0);
-
+ else
ret = delay_and_status(&state, &my_delay, NULL);
- debug_mutex_unlock(&alsa_mutex, 0);
- pthread_cleanup_pop(0);
- pthread_setcancelstate(oldState, NULL);
+ debug_mutex_unlock(&alsa_mutex, 0);
+ pthread_cleanup_pop(0);
+ pthread_setcancelstate(oldState, NULL);
+
+ *the_delay = my_delay; // note: snd_pcm_sframes_t is a long
- *the_delay = my_delay; // note: snd_pcm_sframes_t is a long
- }
return ret;
}
diff --git a/common.c b/common.c
index 8520c714..6e0f12ab 100644
--- a/common.c
+++ b/common.c
@@ -834,7 +834,7 @@ void command_set_volume(double volume) {
_exit(EXIT_FAILURE); /* only if execv fails */
}
}
- _exit(EXIT_SUCCESS);
+ _exit(EXIT_SUCCESS);
} else {
if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
process to finish */
diff --git a/configure.ac b/configure.ac
index 5898a172..f77087c5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.8rc3], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [3.3.8], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
@@ -147,17 +147,18 @@ AM_CONDITIONAL([USE_CUSTOMPIDDIR], [ test "x${with_piddir}" != "x" ])
# Look for libdaemon
AC_ARG_WITH(libdaemon,[AS_HELP_STRING([--with-libdaemon],[include support for daemonising in non-systemd systems])])
-if test "x$with_libdaemon" = "x1"; then
+if test "x$with_libdaemon" = "xyes"; then
AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon for daemonising in non-systemd systems])
if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
[DAEMON], [libdaemon],
- [LIBS="${DAEMON_LIBS} ${LIBS}"])
+ [LIBS="${DAEMON_LIBS} ${LIBS}"],
+ [AC_MSG_ERROR(the libdaemon library has been selected but is missing -- libdaemon-dev suggested!)])
else
- AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed))
+ AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(the libdaemon library has been selected but is missing -- libdaemon-dev suggested!))
fi
fi
-AM_CONDITIONAL([USE_LIBDAEMON], [test "x$with_libdaemon" = "x1"])
+AM_CONDITIONAL([USE_LIBDAEMON], [test "x$with_libdaemon" = "xyes"])
# Check --with-ssl=argument
AC_ARG_WITH(ssl, [AS_HELP_STRING([--with-ssl=],[choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=polarssl (deprecated) for encryption services ])])
@@ -241,7 +242,7 @@ if test "x$with_alsa" = "xyes" ; then
else
AC_CHECK_LIB([asound], [snd_pcm_open], , AC_MSG_ERROR(ALSA support requires the asound library!))
fi
-fi
+fi
AM_CONDITIONAL([USE_ALSA], [test "x$with_alsa" = "xyes"])
# Look for jack flag
@@ -317,7 +318,7 @@ AM_CONDITIONAL([USE_DNS_SD], [test "x$with_dns_sd" = "xyes"])
# Look for dbus flag
AC_ARG_WITH(dbus-interface, [AS_HELP_STRING([--with-dbus-interface],[include support for the native Shairport Sync D-Bus interface])])
-if test "x$with_dbus_interface" = "xyes" ; then
+if test "x$with_dbus_interface" = "xyes" ; then
AC_DEFINE([CONFIG_DBUS_INTERFACE], 1, [Support the native Shairport Sync D-Bus interface])
# remember to include glib, below
fi
diff --git a/dacp.c b/dacp.c
index effbe5ec..0230ac1c 100644
--- a/dacp.c
+++ b/dacp.c
@@ -446,11 +446,11 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
if (dacp_server.active_remote_id)
free(dacp_server.active_remote_id);
if (conn->dacp_active_remote)
- dacp_server.active_remote_id =
- strdup(conn->dacp_active_remote); // even if the dacp_id remains the same,
- // the active remote will change.
+ dacp_server.active_remote_id =
+ strdup(conn->dacp_active_remote); // even if the dacp_id remains the same,
+ // the active remote will change.
else
- dacp_server.active_remote_id = NULL;
+ dacp_server.active_remote_id = NULL;
debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id);
pthread_cond_signal(&dacp_server_information_cv);
diff --git a/rtsp.c b/rtsp.c
index f3524ae2..2661ccec 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1652,40 +1652,41 @@ void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
#endif
void metadata_init(void) {
- int ret;
- if (config.metadata_enabled) {
- // create the metadata pipe, if necessary
- size_t pl = strlen(config.metadata_pipename) + 1;
- char *path = malloc(pl + 1);
- snprintf(path, pl + 1, "%s", config.metadata_pipename);
- mode_t oldumask = umask(000);
- if (mkfifo(path, 0666) && errno != EEXIST)
- die("Could not create metadata pipe \"%s\".", path);
- umask(oldumask);
- debug(1, "metadata pipe name is \"%s\".", path);
+ int ret;
+ if (config.metadata_enabled) {
+ // create the metadata pipe, if necessary
+ size_t pl = strlen(config.metadata_pipename) + 1;
+ char *path = malloc(pl + 1);
+ snprintf(path, pl + 1, "%s", config.metadata_pipename);
+ mode_t oldumask = umask(000);
+ if (mkfifo(path, 0666) && errno != EEXIST)
+ die("Could not create metadata pipe \"%s\".", path);
+ umask(oldumask);
+ debug(1, "metadata pipe name is \"%s\".", path);
- // try to open it
- fd = try_to_open_pipe_for_writing(path);
- // we check that it's not a "real" error. From the "man 2 open" page:
- // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
- // open for reading." Which is okay.
- if ((fd == -1) && (errno != ENXIO)) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
- warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
- }
- free(path);
- int ret;
- ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
- if (ret)
- debug(1, "Failed to create metadata thread!");
+ // try to open it
+ fd = try_to_open_pipe_for_writing(path);
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ if ((fd == -1) && (errno != ENXIO)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ }
+ free(path);
+ int ret;
+ ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
+ if (ret)
+ debug(1, "Failed to create metadata thread!");
- ret = pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, NULL);
- if (ret)
- debug(1, "Failed to create metadata multicast thread!");
+ ret =
+ pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, NULL);
+ if (ret)
+ debug(1, "Failed to create metadata multicast thread!");
}
#ifdef CONFIG_METADATA_HUB
ret = pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL);
@@ -1715,19 +1716,19 @@ void metadata_stop(void) {
pthread_join(metadata_hub_thread, NULL);
// debug(2, "metadata stop hub done.");
#endif
- if (config.metadata_enabled) {
- // debug(2, "metadata stop multicast thread.");
- if (metadata_multicast_thread) {
- pthread_cancel(metadata_multicast_thread);
- pthread_join(metadata_multicast_thread, NULL);
- // debug(2, "metadata stop multicast done.");
- }
- if (metadata_thread) {
- // debug(2, "metadata stop metadata_thread thread.");
- pthread_cancel(metadata_thread);
- pthread_join(metadata_thread, NULL);
- // debug(2, "metadata_stop finished successfully.");
- }
+ if (config.metadata_enabled) {
+ // debug(2, "metadata stop multicast thread.");
+ if (metadata_multicast_thread) {
+ pthread_cancel(metadata_multicast_thread);
+ pthread_join(metadata_multicast_thread, NULL);
+ // debug(2, "metadata stop multicast done.");
+ }
+ if (metadata_thread) {
+ // debug(2, "metadata stop metadata_thread thread.");
+ pthread_cancel(metadata_thread);
+ pthread_join(metadata_thread, NULL);
+ // debug(2, "metadata_stop finished successfully.");
+ }
}
}
}
@@ -1799,8 +1800,9 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rts
int block) {
int rc;
if (config.metadata_enabled) {
- rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
- rc = send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
+ rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
+ rc =
+ send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
}
#ifdef CONFIG_METADATA_HUB
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 6444b8a5..e7f9e748 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -134,6 +134,7 @@ sndio =
pa =
{
// server = "host"; // Set this to override the default pulseaudio server that should be used.
+// sink = "Sink Name"; // Set this to override the default pulseaudio sink that should be used. (Untested)
// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active.
};
From 07c6d1dbfef9518d2e6e9a77b2f1bbef4196e0ed Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 26 Apr 2021 11:30:14 +0100
Subject: [PATCH 065/180] Update RELEASENOTES.md
---
RELEASENOTES.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index f744408c..e86b136f 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -8,7 +8,7 @@ Version 3.3.8
**Bug Fixes**
* Fix a bug in the `alsa` back end. In the interval between checking that the alsa device handle was non-`NULL` and actually using it, the handle could be set to `NULL`. The interval between check and usage is now protected.
* Fix a bug in the `alsa` precision timing code. Thanks to [durwin99](https://github.com/durwin99), [Nicolas Da Mutten](https://github.com/cleverer), [mistakenideas](https://github.com/mistakenideas), [Ben Willmore](https://github.com/ben-willmore) and [giggywithit](https://github.com/giggywithit) for the [report](https://github.com/mikebrady/shairport-sync/issues/1158).
-* Fix a bug that caused Shairport Sync to hang, but not actually crash, if an on-play or any `on-...` script failed.
+* Fix a bug that caused Shairport Sync to hang, but not actually crash, if an `on-...` script failed.
* Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) for the report.
* Fix a crash that occurred playing from AirPower on Android. Thanks to [Ircama](https://github.com/Ircama) for the report.
* Fix the configure.ac file so that `--without-` configuration options are not interpreted as `--with-` options instead! Thanks to [David Racine](https://github.com/bassdr) for the report.
From c19f697be2b6761616876787064d6b067cf87089 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 26 Apr 2021 11:35:11 +0100
Subject: [PATCH 066/180] Update shairport-sync.spec
---
shairport-sync.spec | 50 +++++++++++++++++++++++----------------------
1 file changed, 26 insertions(+), 24 deletions(-)
diff --git a/shairport-sync.spec b/shairport-sync.spec
index 215b303c..ed0c3d49 100644
--- a/shairport-sync.spec
+++ b/shairport-sync.spec
@@ -1,5 +1,5 @@
Name: shairport-sync
-Version: 3.3.7
+Version: 3.3.8
Release: 1%{?dist}
Summary: AirTunes emulator. Multi-Room with Audio Synchronisation
# MIT licensed except for tinysvcmdns under BSD,
@@ -66,29 +66,31 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
%license LICENSES
%changelog
-* Tue Nov 1 2020 Mike Brady 3.3.8
+- Bug fixes and enhancements.
+* Tue Nov 1 2020 Mike Brady <4265913+mikebrady@users.noreply.github.com> 3.3.7
- Jack resampling, bug fixes and additions to the pipe and stdout backends, to metadata and to the D-Bus and MPRIS interfaces.
-* Thu Feb 20 2020 Mike Brady 3.3.6
- Jack resampling, bug fixes and additions to the D-Bus and MPRIS interfaces.
-* Wed Nov 13 2019 Mike Brady 3.3.5
- Bug fixes and additions to the D-Bus interface.
-* Mon Oct 28 2019 Mike Brady 3.3.4
- Bug fixes and minor enhancements.
-* Fri Jul 26 2019 Mike Brady 3.3.2
- Minor bug fixes.
-* Wed Jun 05 2019 Mike Brady 3.3.1
- Bug fixes.
-* Fri May 24 2019 Mike Brady 3.3
- Audio enhancements, stability improvements, MQTT and Jack Audio interfaces.
-* Sun Oct 14 2018 Mike Brady 3.2.2
- Compatibility with iOS 12 and mac OS Mojave AirPlay latencies. Minor bug fix.
-* Fri Jul 13 2018 Mike Brady 3.2.1
- Stability improvements when soxr interpolation is chosen.
-* Mon Jul 09 2018 Mike Brady 3.2
- New D-Bus and MPRIS Interfaces, Bug Fixes and Enhancements.
-* Thu Dec 21 2017 Mike Brady 3.1.7
+* Thu Dec 21 2017 Mike Brady <4265913+mikebrady@users.noreply.github.com> 3.1.7
- Bug fix for unexpectedly resuming play at full volume from iOS 11.2 and macOS 10.3.2.
-* Mon Dec 11 2017 Mike Brady 3.1.5
+* Mon Dec 11 2017 Mike Brady <4265913+mikebrady@users.noreply.github.com> 3.1.5
- Bug fixes and better compatibility with iOS 11.2 and mac OS 10.13.2.
- Better AirPlay synchronisation.
* Wed Sep 13 2017 Bill Peck 3.1.2-1
@@ -115,27 +117,27 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
- new optional loudness and convolution filters
- improvements in non-synchronised backends
- enhancements, stability improvements and bug fixes
-* Fri Feb 24 2017 Mike Brady 2.8.6
+* Fri Feb 24 2017 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.8.6
- Many changes including 8- 16- 24- and 32-bit output
-* Fri Oct 21 2016 Mike Brady 2.8.6
+* Fri Oct 21 2016 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.8.6
- Advertise self as ShairportSync rather than AirPort device 2.8.6
-* Sun Sep 25 2016 Mike Brady 2.8.5
+* Sun Sep 25 2016 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.8.5
- Bug fixes and small enhancements 2.8.5
-* Sat May 28 2016 Mike Brady 2.8.4
+* Sat May 28 2016 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.8.4
- Bug fixes and a few small enhancements 2.8.4
-* Fri Apr 15 2016 Mike Brady 2.8.2
+* Fri Apr 15 2016 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.8.2
- Stability improvements, bug fixes and a few special-purpose settings 2.8.2
-* Wed Mar 02 2016 Mike Brady 2.8.1
+* Wed Mar 02 2016 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.8.1
- Stability improvements and important bug fixes 2.8.1
-* Sat Jan 30 2016 Mike Brady 2.8.0
+* Sat Jan 30 2016 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.8.0
- Enhancements and bug fixes 2.8.0
-* Sun Oct 18 2015 Mike Brady 2.6
+* Sun Oct 18 2015 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.6
- Important enhancements and bug fixes 2.6
-* Thu Aug 27 2015 Mike Brady 2.4.1
+* Thu Aug 27 2015 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.4.1
- Minor bug fixes 2.4.1
-* Thu Aug 27 2015 Mike Brady 2.4
+* Thu Aug 27 2015 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.4
- Prepare for stable release 2.4
-* Wed Aug 26 2015 Mike Brady 2.3.13.1-1
+* Wed Aug 26 2015 Mike Brady <4265913+mikebrady@users.noreply.github.com> 2.3.13.1-1
- Harmonise release numbers
* Fri Jul 24 2015 Bill Peck 2.3.7-1
- Initial spec file
From 59b87c7d6640e1123080f7b985e8d2305df083e5 Mon Sep 17 00:00:00 2001
From: dasl-
Date: Sun, 2 May 2021 21:33:26 -0400
Subject: [PATCH 067/180] update raspberry pi installation guide as per
https://github.com/mikebrady/shairport-sync/issues/1183
---
INSTALL.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/INSTALL.md b/INSTALL.md
index 2d21b9c5..fa36f91b 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -9,7 +9,7 @@ Do the usual update and upgrade:
```
# apt-get update
# apt-get upgrade
-```
+```
(Separately, if you haven't done so already, consider using the `raspi-config` tool to expand the file system to use the entire card.)
### Turn Off WiFi Power Management
@@ -63,13 +63,13 @@ Now to configure Shairport Sync. Here are the important options for the Shairpor
// Sample Configuration File for Shairport Sync on a Raspberry Pi using the built-in audio DAC
general =
{
- volume_range_db = 60;
+ volume_range_db = 60;
};
alsa =
{
- output_device = "hw:0";
- mixer_control_name = "PCM";
+ output_device = "hw:Headphones";
+ mixer_control_name = "Headphone";
};
```
From 291455871e3ef0c1967b8aa9b6d480fa00507983 Mon Sep 17 00:00:00 2001
From: Felix Schmidt
Date: Mon, 21 Jun 2021 06:37:50 +0200
Subject: [PATCH 068/180] Fix tinysvcmdns: rr_data_len goes beyond packet
buffer
---
tinysvcmdns.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tinysvcmdns.c b/tinysvcmdns.c
index 9e605e07..55e82ad2 100644
--- a/tinysvcmdns.c
+++ b/tinysvcmdns.c
@@ -735,7 +735,8 @@ static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
if (name == NULL)
goto err;
- // parse the MDNS RR section p += label_len(pkt_buf, pkt_len, off);
+ // parse the MDNS RR section
+ p += label_len(pkt_buf, pkt_len, off);
rr->name = name;
rr->type = mdns_read_u16(p);
From ebdc0b24d8d98f58d204c0c15cb1ec7fb2b8197a Mon Sep 17 00:00:00 2001
From: Vasilis van Gemert
Date: Wed, 30 Jun 2021 18:35:19 +0200
Subject: [PATCH 069/180] Chapters about optimising boot time and about
read-only
I added a chapter about disabling unnecessary services in order to decrease boot-time. And I added a short tip about read-only mode.
---
CAR INSTALL.md | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index 062b6df5..b24c86c7 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -193,6 +193,24 @@ denyinterfaces wlan0
```
From this point on, at least on the Raspberry Pi, if you reboot the machine, it will not reconnect to your network – instead, it will act as the WiFi base station you have configured with `hostapd` and `isc-dhcp-server`.
+### Optimise startup time
+
+There are quite a few services that are not necessary for this setup. Disabling them can increase startup time. Running these commands disables them:
+
+````
+sudo systemctl disable systemd-timesyncd.service
+sudo systemctl disable keyboard-setup.service
+sudo systemctl disable triggerhappy.service
+sudo systemctl disable dhcpcd.service
+sudo systemctl disable wpa_supplicant.service
+sudo systemctl disable dphys-swapfile.service
+sudo systemctl disable networking.service
+````
+
+### Read-only mode
+
+Run `sudo raspi-config` and then choose `Performance Options` > `Overlay Filesystem` and choose to enable the overlay filesystem, and to set the boot partition to be write-protected.
+
### Ready
Install the Raspberry Pi in your car. It should be powered from a source that is switched off when you leave the car, otherwise the slight current drain will eventually flatten the car's battery.
From dda615975de21abec41140923a58f9bc2ec79601 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 6 Jul 2021 19:47:10 +0100
Subject: [PATCH 070/180] Ignore some files created in the development branch
---
.gitignore | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/.gitignore b/.gitignore
index 9d13084c..4f2b703b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,9 @@ man/Makefile.in
scripts/shairport-sync.service
scripts/shairport-sync
shairport-sync.core
+gitversion.h
+plist_xml_strings.*
+
#Some dbus files that are automatically generated
/org.gnome.ShairportSync.service
@@ -42,3 +45,5 @@ shairport-sync.core
# macOS stuff
.DS_Store
shairport-sync.xcodeproj
+
+
From cdc249ba51bc7ac8cc14da05bb6a86a43669e6f2 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 8 Jul 2021 12:02:40 +0000
Subject: [PATCH 071/180] Temporarily remove prior to making public.
---
.github/workflows/docker-build.yaml | 44 ---------
docker/Dockerfile | 139 ----------------------------
docker/README.md | 12 ---
docker/docker-compose.yaml | 12 ---
docker/start.sh | 18 ----
documentation/Example Setups.md | 21 -----
6 files changed, 246 deletions(-)
delete mode 100644 .github/workflows/docker-build.yaml
delete mode 100644 docker/Dockerfile
delete mode 100644 docker/README.md
delete mode 100644 docker/docker-compose.yaml
delete mode 100644 docker/start.sh
delete mode 100644 documentation/Example Setups.md
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
deleted file mode 100644
index fc670487..00000000
--- a/.github/workflows/docker-build.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-# Pushes to tag 'unstable-BRANCH'.
-# Releases are pushed to latest and tagged - see docker-build-releases.yaml.
-
-name: Build and push docker image based on specified branches.
-
-on:
- push:
- branches: [ master, development, charles-dev ]
-
-jobs:
- main:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
- with:
- fetch-depth: 0
- - name: Set SHAIRPORT_SYNC_BRANCH env.
- run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - name: Set up ssh agent
- uses: webfactory/ssh-agent@v0.5.2 # Can be removed when no longer pulling from private repos.
- with:
- ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v1
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
- - name: Login to Docker Registry
- uses: docker/login-action@v1
- with:
- registry: registry.nova.do
- username: ${{ secrets.DOCKER_REGISTRY_USER }}
- password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
- - name: Build and push
- uses: docker/build-push-action@v2
- with:
- context: ./
- file: ./docker/Dockerfile
- platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
- push: true
- tags: registry.nova.do/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
- build-args: |
- SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
- ssh: default=${{ env.SSH_AUTH_SOCK }} # Can be removed when no longer pulling from private repos.
\ No newline at end of file
diff --git a/docker/Dockerfile b/docker/Dockerfile
deleted file mode 100644
index 40822505..00000000
--- a/docker/Dockerfile
+++ /dev/null
@@ -1,139 +0,0 @@
-FROM alpine:3.12 AS builder-base
-# Version 3.12 is required for now since libplist has breaking changes in v3.13 and above which are not yet standard
-# in debian/ubuntu.
-
-# General Build System:
-RUN apk -U add \
- git \
- build-base \
- autoconf \
- automake \
- libtool \
- dbus \
- su-exec \
- alsa-lib-dev \
- libdaemon-dev \
- popt-dev \
- mbedtls-dev \
- soxr-dev \
- avahi-dev \
- libconfig-dev \
- libsndfile-dev \
- mosquitto-dev \
- xmltoman \
- openssh-client \
- libsodium-dev \
- ffmpeg-dev \
- xxd \
- libressl-dev \
- openssl-dev \
- libplist-dev \
- libgcrypt-dev
-
-# ALAC Build System:
-FROM builder-base AS builder-alac
-
-RUN git clone https://github.com/mikebrady/alac
-WORKDIR /alac
-RUN autoreconf -fi
-RUN ./configure
-RUN make
-RUN make install
-
-# Shairport Sync Build System:
-FROM builder-base AS builder-sps
-
-# This will be modified by the Github Action Workflow and is required
-# to ensure the correct branch is being used.
-ARG SHAIRPORT_SYNC_BRANCH
-RUN test -n "$SHAIRPORT_SYNC_BRANCH"
-
-COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
-COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
-COPY --from=builder-alac /usr/local/include /usr/local/include
-
-# TODO: Remove when no longer pulling from private repos.
-RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
-
-RUN --mount=type=ssh git clone git@github.com:mikebrady/nqptp.git
-WORKDIR /nqptp
-# Use development branch for now.
-RUN git checkout development
-RUN autoreconf -fi
-RUN ./configure
-RUN make
-RUN make install
-WORKDIR /
-
-WORKDIR /shairport-sync
-COPY . .
-RUN git checkout $SHAIRPORT_SYNC_BRANCH
-RUN git submodule init
-RUN git submodule update
-RUN autoreconf -fi
-# RUN ./configure \
-# --with-alsa \
-# --with-dummy \
-# --with-pipe \
-# --with-stdout \
-# --with-avahi \
-# --with-ssl=mbedtls \
-# --with-soxr \
-# --sysconfdir=/etc \
-# --with-dbus-interface \
-# --with-mpris-interface \
-# --with-mqtt-client \
-# --with-apple-alac \
-# --with-convolution \
-# --with-airplay-2
-# RUN make -j $(nproc)
-RUN CFLAGS="-O0 -g" CXXFLAGS="-O0 -g" ./configure --sysconfdir=/etc --with-metadata --with-dummy --with-pipe \
- --with-alsa --with-soxr --with-avahi --with-ssl=openssl --with-dbus-interface --with-stdout \
- --with-mpris-interface --with-mqtt-client --with-apple-alac --with-convolution --with-airplay-2
-RUN make -j
-RUN make install
-
-# Shairport Sync Runtime System:
-FROM alpine:3.12
-
-RUN apk -U add \
- alsa-lib \
- dbus \
- popt \
- glib \
- mbedtls \
- soxr \
- avahi \
- libconfig \
- libsndfile \
- mosquitto \
- su-exec \
- libuuid \
- ffmpeg \
- libsodium \
- libplist \
- libgcrypt
-
-RUN rm -rf /lib/apk/db/*
-
-COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
-COPY --from=builder-sps /etc/shairport-sync* /etc/
-COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-dbus.conf /etc/dbus-1/system.d/
-COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-mpris.conf /etc/dbus-1/system.d/
-COPY --from=builder-sps /usr/local/bin/shairport-sync /usr/local/bin/shairport-sync
-COPY --from=builder-sps /usr/local/bin/nqptp /usr/local/bin/nqptp
-
-# Create non-root user for running the container -- running as the user 'shairport-sync' also allows
-# Shairport Sync to provide the D-Bus and MPRIS interfaces within the container
-
-RUN addgroup shairport-sync
-RUN adduser -D shairport-sync -G shairport-sync
-
-# Add the shairport-sync user to the pre-existing audio group, which has ID 29, for access to the ALSA stuff
-RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio && addgroup shairport-sync audio
-
-COPY ./docker/start.sh /
-
-RUN chmod +x ./start.sh
-
-ENTRYPOINT [ "/start.sh" ]
\ No newline at end of file
diff --git a/docker/README.md b/docker/README.md
deleted file mode 100644
index a460af13..00000000
--- a/docker/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Shairport Sync Docker Image
-
-Available at: https://hub.docker.com/r/mikebrady/shairport-sync
-
-## Build Example (for arm7 devices)
-
-TODO: remove `--ssh` when going public as this is only for private repos.
-```
-docker buildx build --ssh default=$SSH_AUTH_SOCK --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg SHAIRPORT_SYNC_BRANCH=development -t registry.nova.do/shairport-sync:unstable-development .
-```
-
-`SHAIRPORT_SYNC_BRANCH` is required to ensure the image is built using the expected branch.
\ No newline at end of file
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
deleted file mode 100644
index 672639c9..00000000
--- a/docker/docker-compose.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-# Example docker compose config.
-
-version: "3.8"
-services:
- shairport-sync:
- image: registry.nova.do/shairport-sync:unstable-development
- network_mode: host
- restart: unless-stopped
- devices:
- - "/dev/snd"
- # volumes:
- # - ./volumes/shairport-sync/shairport-sync.conf:/etc/shairport-sync.conf # Customised Shairport Sync configuration file.
\ No newline at end of file
diff --git a/docker/start.sh b/docker/start.sh
deleted file mode 100644
index bb790283..00000000
--- a/docker/start.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/sh
-
-set -e
-
-rm -rf /var/run/dbus.pid
-#mkdir -p /var/run/dbus
-
-dbus-uuidgen --ensure
-dbus-daemon --system
-
-avahi-daemon --daemonize --no-chroot
-
-# TODO: This should probably be implemented in a better way and using the correct nqptp group.
-nqptp &
-
-# su-exec shairport-sync shairport-sync $@
-
-shairport-sync -vu --statistics
\ No newline at end of file
diff --git a/documentation/Example Setups.md b/documentation/Example Setups.md
deleted file mode 100644
index 7ce6f658..00000000
--- a/documentation/Example Setups.md
+++ /dev/null
@@ -1,21 +0,0 @@
-Example Setups
-===
-
-Amazon Echo Devices
----
-Since some Amazon Echo devices have either a 3.5mm input or can be used as a bluetooth speaker, Shairport Sync can be connected allowing you to Airplay to these speakers. Currently using the 3.5mm jack is the most reliable method - bluetooth connectivity can have an unreliable delay. Check first to see if your Echo device supports either of these. Volume control of the Echo device can be added via Home Assistant and a MQTT broker. Below goes through some example setups.
-### _Raspberry Pi --> Echo 3.5mm Input_
-* Set the Echo device to use the 3.5mm jack as an input.
-* Via the Shairport Sync config, set the correct `output_device` and `mixer_control_name` for your configuration. Likely to be
-`output_device = "hw:1";` and `mixer_control_name = "Headphone";`.
-* At this point, you should be able to hear music via the Echo. Remember that so far, the volume of the Echo and Shairport Sync are _independent_. If you prefer, you can disable any volume control in Shairport Sync by setting `ignore_volume_control = "yes";`.
-* Amazon Echo devices appear to add a bit of latency, currently this is thought to be 0.12 seconds. This can be countered by setting the following: `audio_backend_latency_offset_in_seconds = -0.12;`. This was measured _very_ crudely using Audacity so could be wrong. If anyone is able to determine the latency more accurately, please open an issue or pull request so these documents can be updated.
-* You should now be able to use your Echo device in sync with other AirPlay devices!
-
-### _Raspberry Pi --> Echo as Bluetooth Speaker (Docker Version)_
-* WIP
-
-### _Raspberry Pi --> Echo with Volume Control_
-* Before continuing with volume control via Home Assistant, please note that currently only one way volume control is tested - Shairport Sync to the Echo device. This means if the volume of the Echo is changed (e.g. by voice control or by the physical controls on the device), this will not be reflected in the volume of Shairport Sync and will be overwritten the next time Shairport Sync's volume is changed.
-* This setup requires Home Assistant and a MQTT broker. The basic idea is that Shairport Sync sends a MQTT message containing the new volume amount to the MQTT broker when the volume is changed, this is then picked up by Home Assistant (which is monitoring the MQTT broker). Home Assistant will then send a command to the Amazon Echo device to change the volume.
-* WIP
From e8076187a7e0f7020220158df791d037d0f03f48 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 8 Jul 2021 12:15:53 +0000
Subject: [PATCH 072/180] Remove superceded document.
---
README-DEVELOPMENT.md | 589 ------------------------------------------
1 file changed, 589 deletions(-)
delete mode 100644 README-DEVELOPMENT.md
diff --git a/README-DEVELOPMENT.md b/README-DEVELOPMENT.md
deleted file mode 100644
index 8193f84e..00000000
--- a/README-DEVELOPMENT.md
+++ /dev/null
@@ -1,589 +0,0 @@
-Shairport Sync
-=============
-Shairport Sync is an AirPlay audio player – it plays audio streamed from iTunes, iOS, Apple TV and macOS devices and AirPlay sources such as Quicktime Player and [ForkedDaapd](http://ejurgensen.github.io/forked-daapd/), among others.
-
-Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible for players that support it, such as iTunes.
-
-Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming.
-
-This is the unstable "development" branch. Changes and updates are incorporated into this branch quickly. To access the stable version, where changes are made after due time, please switch to the "master" branch.
-
-More Information
-----------
-Shairport Sync offers *full audio synchronisation*, a feature of AirPlay that previous implementations do not provide. Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source. To accomplish this, Shairport Sync needs access to audio systems – such as `alsa` on Linux and `sndio` on FreeBSD – that provide very accurate timing information about output devices. Shairport Sync must have direct access to the output device used, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem).
-
-Alternatively, Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing information is not as accurate as that of `alsa` or `sndio`, it is often impractical to remove or disable PulseAudio. In that case, the `pa` backend can be used. An older backend for PulseAudio called `pulse` does not support synchronisation and is deprecated.
-
-For other use cases, Shairport Sync can provide synchronised audio output to a unix pipe or to standard output, or to audio systems that do not provide timing information. This could perhaps be described as *partial audio synchronisation*, where synchronised audio is provided by Shairport Sync, but what happens to it in the subsequent processing chain, before it reaches the listener's ear, is outside the control of Shairport Sync.
-
-For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki.
-
-Synchronisation, Latency, "Stuffing"
----------
-The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on.
-
-As mentioned previously, Shairport Sync implements full audio synchronisation when used with `alsa`, `sndio` or PulseAudio systems. This is done by monitoring the timestamps present in data coming from the audio source and the timing information from the audio system, e.g. `alsa`. To maintain the latency required for exact synchronisation, if the output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”.
-
-Stuffing is not done for partial audio synchronisation – the audio samples are simply presented at exactly the right time to the next stage in the processing chain.
-
-Timestamps are referenced relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. So, another thing Shairport Sync has to do is to synchronize the source clock and the local clock, and it does this usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols.
-
-What else?
---------------
-* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range.
-* Hardware Mute — Shairport Sync can mute properly if the hardware supports it.
-* Support for the Apple ALAC decoder.
-* Output bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits.
-* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.2 seconds with `alsa`, 0.35 seconds with `sndio`.
-* Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.)
-* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
-* Support for the [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/) protocol with some support for remote control of the source.
-* A native D-Bus IPC interface with access to metadata and with some support for remote control of the source.
-* A native [MQTT](https://en.wikipedia.org/wiki/MQTT) interface for metadata and/or controls.
-* Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync.
-* Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available.
-
-Heritage
--------
-Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/development/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0.
-
-Status
-------
-Shairport Sync works on a wide variety of Linux distributions including Debian, Ubuntu, Fedora asnd OpenWrt and works on FreeBSD and on OpenBSD. It works with built-in audio and with a variety of USB-connected sound cards, audio amplifiers and DACs, and works with I2S sound cards.
-
-Shairport Sync runs well on the Raspberry Pi -- including the original Raspberry Pi B -- using the built-in sound card or on USB and I2S cards.
-
-Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normally runs in the *user mode* but can be configured to run in *system mode*, though this is not recommended. Shairport Sync can work with it in either mode.
-
-Shairport Sync runs natively on FreeBSD and OpenBSD using the `sndio` sound system.
-
-Shairport Sync runs on Linuxes and the BSDs inside VMWare Fusion on a Mac, but synchronisation is inaccurate — possibly because the sound card is being emulated.
-
-In addition, Shairport Sync can output to standard output, pipes, `soundio` and `ao` devices using appropriate backends.
-
-For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution.
-
-Building And Installing the Development Version
----------------------
-See [here](https://github.com/mikebrady/shairport-sync/blob/development/INSTALL.md) for simple installation instructions for modern Linux systems like Rapsberry Pis.
-
-The following procedures will build and install the `shairport-sync` application into your system.
-
-**Remove Old Versions of Shairport Sync and its Startup Scripts**
-
-You should check to see if `shairport-sync` is already installed – you can use the command `$ which -a shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
-
-You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
-
-**FreeBSD, OpenBSD, CYGWIN**
-
-Please refer to the relevant pages for building for the above systems.
-
-**Determine The Configuration Needed**
-
-Shairport Sync has a number of different "backends" that connect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary:
-```
-$ pactl info
-Server String: unix:/run/user/1000/pulse/native
-Library Protocol Version: 30
-Server Protocol Version: 30
-Is Local: yes
-Client Index: 9
-Tile Size: 65472
-User Name: mike
-Host Name: ubuntu
-Server Name: pulseaudio
-Server Version: 8.0
-Default Sample Specification: s16le 2ch 44100Hz
-Default Channel Map: front-left,front-right
-Default Sink: alsa_output.pci-0000_02_02.0.analog-stereo
-Default Source: alsa_input.pci-0000_02_02.0.analog-stereo
-Cookie: 96f9:3e8d
-$
-```
-If PulseAudio in not installed, you'll get something like this:
-```
-$ pactl info
--bash: pactl: command not found
-$
-```
-If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend.
-
-If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it.
-
-**Building**
-
-To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions.
-
-The following libraries are required:
-* OpenSSL or mbed TLS (PolarSSL is supported but deprecated)
-* Avahi
-* ALSA and/or PulseAudio
-* autoconf
-* automake
-* libtool
-* libpopt
-* libconfig
-
-If you are building for a system that needs to be able to daemonise Shairport Sync with the `-d` option and to kill the daemon with the `-k` option, you need to include the `libdaemon` library:
-* libdaemon
-
-Most current releases of Linuxes use the `systemd` startup and system manager, and this daemonises applications differently, so the `libdaemon` library can be omitted.
-
-Optional:
-* libsoxr
-* libalac (This is a library containing the Apple ALAC decoder.)
-* libmosquitto (This is to enable the mqtt-metadata-interface.)
-
-Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up.
-
-Debian, Ubuntu and Raspbian users can get the basics with:
-
-- `# apt-get install build-essential git xmltoman` – these may already be installed.
-- `# apt-get install autoconf automake libtool libpopt-dev libconfig-dev`
-- `# apt-get install libdaemon-dev` – if you need libdaemon support.
-- `# apt-get install libasound2-dev` for the ALSA libraries
-- `# apt-get install libpulse-dev` for the PulseAudio libraries
-- `# apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended).
-- `# apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise.
-- `# apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported.
-- `# apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md).
-- `# apt-get install libmosquitto-dev` if you want to use the mqtt client
-
-If you just want to have the default configuration options (systemd):
-- `# apt-get install build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev libpulse-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev`
-
-If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information.
-
-**Download Shairport Sync:**
-```
-$ git clone https://github.com/mikebrady/shairport-sync.git
-```
-
-Next, `cd` into the shairport-sync directory and execute the following commands:
-```
-$ git checkout development
-$ autoreconf -i -f
-```
-(Note that the `autoreconf...` step may take some time on less powerful machines.)
-
-**Choose the appropriate `--with-*` options:**
-
-(Don't worry -- there's a recommended set of configuration options further down.)
-
-- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality.
-- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed.
-- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout).
-- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe.
-- `--with-soundio` include an optional backend module to enable raw audio to be output through the soundio system.
-- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15.
-- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL.
-- `--with-soxr` for libsoxr-based resampling.
-- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location.
-- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader.
-- `--with-mqtt-client` to add support for publishing metadata and remote control via [MQTT](https://en.wikipedia.org/wiki/MQTT).
-- `--with-configfiles` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten.
-- `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use.
-- `--with-apple-alac` to include the Apple ALAC Decoder.
-- `--with-convolution` to include a convolution filter that can be used to apply effects such as frequency and phase correction, and a loudness filter that compensates for human ear non-linearity. Requires `libsndfile`.
-- `--with-libdaemon` to allow Shairport Sync to daemonise itself.
-- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. Default is not to to install.
-- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. Default is not to to install.
-
-**Determine if it's a `systemd` or a "System V" installation:**
-
-If you wish to have Shairport Sync start automatically when your system boots or to daemonise itself and run in the background, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.)
-
-There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system.
-
-The easiest way to look at the first few lines of the `init` manual. Enter the command:
-
-```
-$ man init
-```
-In a `systemd` system, the top lines of the `man` page make it clear that it's a `systemd` system, as follows (from Ubuntu 16.04):
-```
-SYSTEMD(1) systemd SYSTEMD(1)
-
-NAME
- systemd, init - systemd system and service manager
-...
-```
-Other init systems will look considerably different. For instance, in an `upstart` system, the top lines of the `man` page indicate it's using `upstart` system, as follows (from Ubuntu 14.04):
-
-```
-init(8) System Manager's Manual init(8)
-
-NAME
- init - Upstart process management daemon
-
-SYNOPSIS
- init [OPTION]...
-...
-```
-In a System V system, the top lines of the `man` page are as follows (from Debian 7.11):
-```
-INIT(8) Linux System Administrator's Manual INIT(8)
-
-NAME
- init, telinit - process control initialization
-
-SYNOPSIS
- /sbin/init [ -a ] [ -s ] [ -b ] [ -z xxx ] [ 0123456Ss ]
- /sbin/telinit [ -t SECONDS ] [ 0123456sSQqabcUu ]
- /sbin/telinit [ -e VAR[=VAL] ]
-
-...
-```
-If your system is definitely a `systemd` system, choose `--with-systemd` below. Otherwise, choose `--with-systemv`.
-
-**Choose the location of the configuration file**
-
-A final consideration is the location of the configuration file `shairport-sync.conf`. This will be placed in the directory specified by the `sysconfdir` configuration variable, which defaults to `/usr/local/etc`. This is normal in BSD Unixes, but is unusual in Linux. Hence, for Linux installations, you need to set the `sysconfdir` variable to `/etc` using the configuration setting `--sysconfdir=/etc`.
-
-**Sample `./configure` command with parameters for a typical Linux `systemd` installation:**
-
-Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and later, and Raspbian Stretch and Jessie. It specifies both the ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot:
-
-`$ ./configure --sysconfdir=/etc --with-alsa --with-pa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd`
-
-* Omit the `--with-soxr` if the libsoxr library is not available.
-* For installation into a System V system, add `--with-libdaemon` and replace the `--with-systemd` with `--with-systemv`.
-* If you intend to use Shairport Sync with PulseAudio in the standard user mode, it can not be a system service, so you should omit both `--with-systemd` and `--with-systemv`. If you want to be able to daemonise Shairport Sync without installing it as a service, include the `--with-libdaemon` flag.
-
-**Build and Install the Application:**
-
-Enter:
-
-```
-$ make
-```
-
-to build the application.
-
-Assuming you have used the `./configure` settings suggested above, you can now install the application:
-
-```
-$ sudo make install
-```
-The user and group `shairport-sync` will be created if necessary, and a runtime folder will be created at `/var/run/shairport-sync` if you have chosen `--with-systemv`. In addition, a `man` page, a default configuration file and automatic power-on startup script will be installed.
-
-
-**Complete installation into to a `systemd` system**
-
-If you have chosen the `--with-systemd` configuration option, then, to enable Shairport Sync to start automatically at system startup, enter:
-
-```
-$ sudo systemctl enable shairport-sync
-```
-
-**Complete installation into a System V system**
-
-If you have chosen the `--with-systemv` configuration option, enter:
-```
-$ sudo update-rc.d shairport-sync defaults 90 10
-```
-
-**Identifying the configuration of an installed Shairport Sync
-
-To find out what configuration Shairport Sync was built with, enter:
-```
-$ shairport-sync -V
-```
-This will return something like the following:
-```
-3.3-libdaemon-OpenSSL-Avahi-ALSA-sndio-stdout-pipe-metadata-dbus-sysconfdir:/etc
-```
-This information may be useful when [troubleshooting](https://github.com/mikebrady/shairport-sync/blob/master/TROUBLESHOOTING.md) or [reporting issues](https://github.com/mikebrady/shairport-sync/blob/master/CONTRIBUTING.md).
-**Man Page**
-
-You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html
-
-Configuring Shairport Sync
---------
-There are two logically distinct parts to getting Shairport Sync to run properly on your machine: (1) starting and stopping it and (2) ensuring it has the right settings.
-
-**(1) Starting and Stopping:**
-Starting and stopping Shairport Sync automatically is taken care of differently in different versions of Linux – see the previous section for an example of installing into a `systemd` or a System V based system.
-
-If you are using PulseAudio in the standard "user" mode, you can not start Shairport Sync automatically at boot time – it must be started when the user logs in to a GUI session. Various GUI tools exist to enable you to build a Startup Application. However, there does not appear to be a similar tool for terminating the program when you log out. Also, it the GUI switches to another user, the user's PulseAudio session is disconnected from the output device.
-
-**(2) Settings:**
-To get the best from Shairport Sync, you’ll need to (a) give Shairport Sync a service name by which it will be seen in iTunes etc. and (b) specify the backend you wish to use - `alsa` for the ALSA backend, or `pa` for the PulseAudio back end. If only one backend is included at compilation, or if the backend is ALSA, there is no need to explicitly specify the backend.
-
-For the ALSA backend you may need to (c) specify the output device to use and (d) specify the name of the mixer volume control, if any, to use to control the output level. To get values for (b) and (c) you might need to explore the ALSA output devices with a program like `alsamixer` or similar.
-
-Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card.
-
-Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room`, give the service the password `secret` and use `libsoxr` interpolation, then you should do the following:
-
-```
-general =
-{
- name = "Front Room";
- password = "secret";
- interpolation = "soxr";
- // ... other general settings
-};
-```
-(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.)
-
-**Important:** You should *never* use an important password as the AirPlay password for a Shairport Sync player -- the password is stored in Shairport Sync's configuration file in plain text and is thus completely vulnerable.
-
-No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add:
-
-```
- output_backend = "pa";
-```
-to the `general` stanza.
-
-The `alsa` group is used to specify properties of the output device. The most obvious setting is the name of the output device which you can set using the `output_device` tag.
-
-The following `alsa` group settings are very important for maximum performance. If your audio device has a mixer that can be use to control the volume, then Shairport Sync can use it to give instant response to volume and mute commands and it can offload some work from the processor.
-* The `mixer_control_name` tag allows you to specify the name of the mixer volume control.
-* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is on the `output_device`, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`:
-
-```
-alsa =
-{
- output_device = "hw:0,5";
- mixer_device = "hw:0";
- mixer_control_name = "PCM";
- // ... other alsa settings
-};
-```
-
-The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. You can also set the sink name that audio will be pushed to. Here's an example `pa` group configuration:
-
-```
-pa =
-{
- application_name = "Shairport Sync - Bedroom";
- sink = "server_bedroom"; // Sink name or ID. If not specified, the default sink will be used.
-};
-```
-
-Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing.
-
-Note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. (This behaviour may be different from other Shairports.)
-
-Shairport Sync can run a program whenever the volume is set or changed. You specify them using the `general` group settings `run_this_when_volume_changes`. This is to facilitate situations where something has to be done when the volume changes, e.g. adjust an external mixer value. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the command has been completed before continuing. Again, please note that the full path to the program must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line.
-
-Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method.
-
-**Raspberry Pi**
-
-The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. Apart from a loud click when used for the first time after power-up, it is quite adequate for casual listening. A problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 50 dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 50, the top 50 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -46dB, below which muting will occur.
-
-From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume.
-
-*Command Line Arguments*
-
-As previously mentioned, you can use command line arguments to provide settings to Shairport Sync as before, though newer settings will only be available via the configuration file. For full information, please read the Shairport Sync `man` page, also available at http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html.
-
-Apart from the following options, all command line options can be replaced by settings in the configuration file. Here is a brief description of command line options that are not replicated by settings in the settings file.
-
-* The `-c` option allows you to specify the location of the configuration file.
-* The `-V` option gives you version information about Shairport Sync and then quits.
-* The `-d` option causes Shairport Sync to properly daemonise itself, that is, to run in the background. You may need sudo privileges for this. This is available if Shairport Sync is built with the `--with-libdaemon` configuration flag.
-* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this. This is available if Shairport Sync is built with the `--with-libdaemon` configuration flag.
-
-The System V init script at `/etc/init.d/shairport-sync` has a bare minimum :
-`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file.
-
-Examples
---------
-
-Here are some examples of complete configuration files.
-
-```
-general = {
- name = "Joe's Stereo";
-};
-
-alsa = {
- output_device = "hw:0";
-};
-```
-
-This gives the service a particular name — "Joe's Stereo" and specifies that audio device hw:0 be used.
-
-For best results with the `alsa` backend — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the mixer control to be used as the `mixer_control_name`.
-
-Here is an example for for a Raspberry Pi using its internal soundcard — device hw:0 — that drives the headphone jack:
-```
-general = {
- name = "Mike's Boombox";
-};
-
-alsa = {
- output_device = "hw:0";
- mixer_control_name = "PCM";
-};
-```
-
-Here is an example of using soxr-based resampling and driving a Topping TP30 Digital Amplifier, which has an integrated USB DAC and which is connected as audio device `hw:1`:
-```
-general = {
- name = "Kitchen";
- interpolation = "soxr";
-};
-
-alsa = {
- output_device = "hw:1";
- mixer_control_name = "PCM";
-};
-```
-
-For a cheapo "3D Sound" USB card (Stereo output and input only) on a Raspberry Pi:
-```
-general = {
- name = "Front Room";
-};
-
-alsa = {
- output_device = "hw:1";
- mixer_control_name = "Speaker";
-};
-```
-
-For a first generation Griffin iMic on a Raspberry Pi:
-```
-general = {
- name = "Attic";
-};
-
-alsa = {
- output_device = "hw:1";
- mixer_control_name = "PCM";
-};
-```
-
-For an NSLU2, to drive a first generation Griffin iMic, a USB sound card:
-```
-general = {
- name = "Den";
-};
-
-alsa = {
- mixer_control_name = "PCM";
-};
-```
-
-On an NSLU2, to drive the "3D Sound" USB card:
-```
-general = {
- name = "TV Room";
-};
-
-alsa = {
- mixer_control_name = "Speaker";
-};
-```
-
-Finally, here is an example of using the PulseAudio backend:
-```
-general = {
- name = "Zoe's Computer";
- output_backend = "pa";
-};
-
-```
-
-
-Metadata
---------
-
-Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
-
-**Metadata broadcasting over UDP**
-
-As an alternative to sending metadata to a pipe, the `socket_address` and `socket_port` tags may be set in the metadata group to cause Shairport Sync to broadcast UDP packets containing the track metadata.
-
-The advantage of UDP is that packets can be sent to a single listener or, if a multicast address is used, to multiple listeners. It also allows metadata to be routed to a different host. However UDP has a maximum packet size of about 65000 bytes; while large enough for most data, Cover Art will often exceed this value. Any metadata exceeding this limit will not be sent over the socket interface. The maximum packet size may be set with the `socket_msglength` tag to any value between 500 and 65000 to control this - lower values may be used to ensure that each UDP packet is sent in a single network frame. The default is 500. Other than this restriction, metadata sent over the socket interface is identical to metadata sent over the pipe interface.
-
-The UDP metadata format is very simple - the first four bytes are the metadata *type*, and the next four bytes are the metadata *code* (both are sent in network byte order - see https://github.com/mikebrady/shairport-sync-metadata-reader for a definition of those terms). The remaining bytes of the packet, if any, make up the raw value of the metadata.
-
-Latency
--------
-Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer.
-
-Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them.
-
-Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset_in_seconds`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -0.1.
-
-Resynchronisation
--------------
-Shairport Sync actively maintains synchronisation with the source.
-If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above.
-* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting.
-
-Tolerance
----------
-Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, 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 `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections.
-* You can vary the tolerance with the `general` `drift_tolerance_in_seconds` setting.
-
-Some Statistics
----------------
-If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file:
-```
-sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy, source nominal frames per second, source actual frames per second, output frames per second, source clock drift in ppm, source clock drift sample count, rough calculated correction in ppm
-```
-This will be followed by the statistics themselves at regular intervals, for example:
-```
-1.04, 0.0, 0.0, 5015, 0, 2, 0, 2, 7847, 202, 226, 44099.99, 44103.19, 44100.30, 0.00, 0, 7.16
-1.00, 0.0, 0.0, 6018, 0, 2, 0, 2, 8375, 215, 226, 44099.99, 44106.36, 44100.32, 0.00, 0, 7.43
-0.84, 0.0, 0.0, 7021, 0, 2, 0, 2, 8453, 217, 226, 44099.99, 44102.16, 44100.23, 0.00, 0, 5.51
-0.79, 0.0, 0.0, 8024, 0, 2, 0, 2, 8215, 216, 226, 44100.00, 44098.16, 44100.31, 0.00, 0, 7.22
-0.83, 2.8, 2.8, 9027, 0, 2, 0, 2, 8078, 217, 226, 44099.99, 44105.64, 44100.31, 0.00, 0, 7.37
-1.09, 2.8, 2.8, 10030, 0, 2, 0, 2, 8117, 215, 226, 44099.99, 44100.59, 44100.31, 0.00, 0, 7.25
-1.37, 0.0, 0.0, 11033, 0, 2, 0, 2, 8429, 217, 226, 44099.99, 44081.51, 44100.32, 19.27, 10, -11.87
-1.49, 0.0, 0.0, 12036, 0, 2, 0, 2, 8423, 215, 226, 44099.99, 44095.55, 44100.31, 25.43, 12, -18.13
-1.46, 2.8, 2.8, 13039, 0, 2, 0, 2, 8064, 217, 226, 44099.99, 44097.07, 44100.31, 22.39, 14, -15.16
-1.77, 2.8, 2.8, 14042, 0, 2, 0, 2, 8195, 216, 226, 44100.00, 44095.31, 44100.32, 22.96, 16, -15.76
-1.88, 0.0, 5.7, 15045, 0, 2, 0, 2, 8054, 215, 226, 44100.00, 44100.36, 44100.32, 24.36, 18, -17.18
-2.01, -39.7, 45.3, 16048, 0, 2, 0, 2, 8138, 217, 226, 44099.99, 44098.67, 44100.32, 25.23, 19, -17.71
-1.85, -5.7, 17.0, 17051, 0, 2, 0, 2, 8063, 218, 226, 44100.00, 44100.22, 44100.32, 25.43, 22, -18.15
-1.92, 0.0, 0.0, 18054, 0, 2, 0, 2, 8381, 217, 226, 44100.00, 44101.46, 44100.31, 24.62, 23, -17.42
-1.96, -17.0, 17.0, 19057, 0, 2, 0, 2, 8479, 216, 226, 44099.99, 44098.06, 44100.32, 24.19, 24, -16.82
-2.02, -90.6, 90.6, 20060, 0, 2, 0, 2, 8438, 217, 226, 44100.00, 44097.24, 44100.33, 26.16, 26, -18.73
-2.01, -25.5, 25.5, 21063, 0, 2, 0, 2, 8417, 216, 226, 44100.00, 44103.05, 44100.31, 26.16, 26, -18.97
-2.00, -19.8, 25.5, 22066, 0, 5, 0, 5, 8121, 217, 226, 44100.00, 44101.97, 44100.32, 26.12, 27, -18.83
-1.44, 0.0, 0.0, 23069, 0, 5, 0, 5, 8451, 216, 226, 44100.00, 44102.68, 44100.31, 25.49, 30, -18.37
-1.47, 0.0, 0.0, 24072, 0, 5, 0, 5, 8439, 217, 226, 44100.00, 44102.03, 44100.31, 24.92, 33, -17.75
-1.50, 0.0, 0.0, 25075, 0, 5, 0, 5, 8401, 214, 226, 44100.00, 44102.00, 44100.32, 24.92, 33, -17.66
-1.86, 2.8, 8.5, 26078, 0, 5, 0, 5, 7778, 215, 227, 44100.00, 44103.63, 44100.31, 24.66, 35, -17.62
-1.87, -17.0, 17.0, 27081, 0, 5, 0, 5, 8489, 217, 226, 44100.00, 44100.92, 44100.32, 24.15, 37, -16.98
-1.92, -5.7, 17.0, 28084, 0, 5, 0, 5, 8172, 217, 226, 44100.00, 44100.05, 44100.31, 24.10, 39, -16.95
-2.00, -36.8, 36.8, 29087, 0, 5, 0, 5, 8468, 216, 226, 44100.00, 44100.87, 44100.32, 23.94, 41, -16.64
-1.62, -11.3, 11.3, 30090, 0, 5, 0, 5, 8433, 218, 226, 44100.00, 44097.75, 44100.28, 23.34, 43, -16.91
-1.75, 2.8, 2.8, 31093, 0, 5, 0, 5, 8224, 215, 226, 44100.00, 44101.39, 44100.31, 23.21, 44, -16.05
-1.95, -2.8, 2.8, 32096, 0, 5, 0, 5, 8472, 216, 226, 44100.00, 44101.39, 44100.31, 22.79, 46, -15.71
-1.83, -102.0, 102.0, 33099, 0, 5, 0, 5, 8411, 214, 226, 44100.00, 44100.58, 44100.31, 23.00, 49, -15.95
-1.48, 0.0, 0.0, 34102, 0, 5, 0, 5, 8419, 209, 226, 44100.00, 44101.87, 44100.31, 23.00, 49, -15.87
-1.62, 2.8, 2.8, 35105, 0, 11, 0, 11, 8074, 216, 226, 44100.00, 44102.71, 44100.31, 23.07, 50, -15.92
-1.71, -2.8, 8.5, 36108, 0, 11, 0, 11, 7835, 216, 227, 44100.00, 44102.47, 44100.32, 23.27, 51, -15.92
-1.94, -5.7, 5.7, 37111, 0, 11, 0, 11, 8470, 217, 226, 44100.00, 44102.54, 44100.31, 23.73, 53, -16.54
-1.95, -2.8, 2.8, 38114, 0, 11, 0, 11, 8461, 217, 226, 44100.00, 44102.93, 44100.32, 23.78, 54, -16.56
-```
-
-"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output drifts to 1.95 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made.
-
-"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm.
-
-"Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections.
-
-For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an iPhone XS Max to a Raspberry Pi Zero W using a Pimoroni pHAT DAC.
-
-It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway...
-
-"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.20 seconds = 8,820 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up.
-
-WiFi Issues
----------
-If you are using WiFi, you should ensure that WiFi power management is off. See [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for more details.
-
-Troubleshooting
----------------
-Please refer to [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for a few hints, contributed by users.
-
From 5abb4ac0d5aff6c23f9107f96edc6e44d007121f Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 8 Jul 2021 12:21:32 +0000
Subject: [PATCH 073/180] Update URLs to point to the public repository.
---
AIRPLAY2.md | 2 +-
BUILDFORAP1.md | 2 +-
BUILDFORAP2.md | 2 +-
README.md | 8 ++++----
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/AIRPLAY2.md b/AIRPLAY2.md
index f9d1face..65229145 100644
--- a/AIRPLAY2.md
+++ b/AIRPLAY2.md
@@ -45,7 +45,7 @@ So, here are some guideline requirements:
Guides
---
-* To build Shairport Sync for AirPlay 2 on Linux, please follow the guide at [BUILDFORAP2.md](https://github.com/aillwee/shairport-sync/blob/development/BUILDFORAP2.md).
+* To build Shairport Sync for AirPlay 2 on Linux, please follow the guide at [BUILDFORAP2.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILDFORAP2.md).
* A guide for building on FreeBSD is forthcoming.
AirPlay 2 -- More About What Works
diff --git a/BUILDFORAP1.md b/BUILDFORAP1.md
index 07dac81d..d3834240 100644
--- a/BUILDFORAP1.md
+++ b/BUILDFORAP1.md
@@ -48,7 +48,7 @@ First, install the packages needed by Shairport Sync:
```
Next, download Shairport Sync, check out the `development` branch, configure it, compile and install it:
```
-$ git clone https://github.com/aillwee/shairport-sync.git
+$ git clone https://github.com/mikebrady/shairport-sync.git
$ cd shairport-sync
$ git checkout development
$ autoreconf -fi
diff --git a/BUILDFORAP2.md b/BUILDFORAP2.md
index 8bbc2a09..eac2baf4 100644
--- a/BUILDFORAP2.md
+++ b/BUILDFORAP2.md
@@ -81,7 +81,7 @@ Version without git information:
#### Build and Install
Download Shairport Sync, check out the `development` branch and configure, compile and install it:
```
-$ git clone git@github.com:aillwee/shairport-sync.git
+$ git clone https://github.com/mikebrady/shairport-sync.git
$ cd shairport-sync
$ git checkout development
$ autoreconf -fi
diff --git a/README.md b/README.md
index fe9eec75..234dc391 100644
--- a/README.md
+++ b/README.md
@@ -7,17 +7,17 @@ Shairport Sync
AirPlay 2
---
-For the latest on AirPlay 2, please visit [AIRPLAY2.md](https://github.com/aillwee/shairport-sync/blob/development/AIRPLAY2.md).
+For the latest on AirPlay 2, please visit [AIRPLAY2.md](https://github.com/mikebrady/shairport-sync/blob/development/AIRPLAY2.md).
Guides
---
-* A brief guide to building classic Shairport Sync (for AirPlay 1) is available at [BUILDFORAP1.md](https://github.com/aillwee/shairport-sync/blob/development/BUILDFORAP1.md).
-* A guide to building Shairport Sync for AirPlay 2 on Linux is available at [BUILDFORAP2.md](https://github.com/aillwee/shairport-sync/blob/development/BUILDFORAP2.md).
+* A brief guide to building classic Shairport Sync (for AirPlay 1) is available at [BUILDFORAP1.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILDFORAP1.md).
+* A guide to building Shairport Sync for AirPlay 2 on Linux is available at [BUILDFORAP2.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILDFORAP2.md).
* A guide to building for AirPlay 2 on FreeBSD is forthcoming.
More Information
---
-For more information, please visit [MOREINFO.md](https://github.com/aillwee/shairport-sync/blob/development/MOREINFO.md).
+For more information, please visit [MOREINFO.md](https://github.com/mikebrady/shairport-sync/blob/development/MOREINFO.md).
Acknowledgements
---
From fae983cd60e18b73b1ec230e171aeab16e040800 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 8 Jul 2021 12:28:19 +0000
Subject: [PATCH 074/180] Highlight the AirPlay 2 information page.
---
RELEASENOTES-DEVELOPMENT.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 5f73973d..ac4023e3 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -3,9 +3,9 @@ Version 4.0-dev
Big Update
----
-Version 4 brings limited AirPlay 2 functionality to Shairport Sync.
+Version 4 brings [limited AirPlay 2 functionality](https://github.com/mikebrady/shairport-sync/blob/development/AIRPLAY2.md) to Shairport Sync.
-For more information about AirPlay 2, including acknowledgements, please visit [AIRPLAY2.md].
+For information about AirPlay 2, including acknowledgements, please visit [AIRPLAY2.md](https://github.com/mikebrady/shairport-sync/blob/development/AIRPLAY2.md).
#### Known Issues
- In the AirPlay 2 build, a crash can occur occasionally when the player is being selected. If you can get this to happen reliably, please let us know.
From b4a8249d0e88e5059b5551e905a1598bbba787f2 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 8 Jul 2021 12:55:33 +0000
Subject: [PATCH 075/180] Minor format changes.
---
RELEASENOTES-DEVELOPMENT.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index ac4023e3..b1010052 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -10,8 +10,8 @@ For information about AirPlay 2, including acknowledgements, please visit [AIRPL
#### Known Issues
- In the AirPlay 2 build, a crash can occur occasionally when the player is being selected. If you can get this to happen reliably, please let us know.
-Warning
-----
+#### Warning
+
1. Big changes have been made to the codebase to enable it to be used to build either as the "classic" AirPlay 1 player or as a new AirPlay 2 player. These changes have probably introduced bugs into the classic AirPlay 1 build.
2. There is lots we don't know about AirPlay 2. This can result in incorrect behaviour. Watch out for very high volume levels, especially if you have a powerful audio system.
3. This is a `development` branch and is probably more buggy than usual. Normal support will not be provided -- instead, we will be interested in bug reports that help improve the program. You need to be adept with Linux or FreeBSD to get involved.
From a787d95bdb1841de88afcddddba435d9fa9b11e3 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 8 Jul 2021 13:20:44 +0000
Subject: [PATCH 076/180] Chage some ambiguous phrasing.
---
AIRPLAY2.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/AIRPLAY2.md b/AIRPLAY2.md
index 65229145..6dbd97a8 100644
--- a/AIRPLAY2.md
+++ b/AIRPLAY2.md
@@ -9,7 +9,7 @@ For AirPlay 2, Shairport Sync uses another application called [`nqptp`](https://
AirPlay 2 functionality is available on recent Linux and FreeBSD builds. The FreeBSD implementation has not been extensively tested. OpenBSD and Cygwin are not supported.
-AirPlay 2 support definitely does not work on macOS. This is because [`nqptp`](https://github.com/mikebrady/nqptp) uses ports `319` and `320` but these ports are unavailable because there are used by macOS to support its implementation of AirPlay 2.
+The AirPlay 2 version of Shairport Sync will not work if installed on macOS. This is because [`nqptp`](https://github.com/mikebrady/nqptp) uses ports `319` and `320` but these ports are unavailable because there are used by macOS to support its implementation of AirPlay 2.
The AirPlay 2 build requires a good deal of extra library support and may not fit into smaller devices. It also requires more CPU power and more RAM.
@@ -29,7 +29,7 @@ The AirPlay 2 build requires a good deal of extra library support and may not fi
### Limitations
- Only a single instance of Shairport Sync can run on a device.
-- The AirPlay 2 version of Shairport Sync will not run on macOS, since `nqptp` can not be installed on it.
+- The AirPlay 2 version of Shairport Sync will not work if installed on macOS, since `nqptp` can not be installed on macOS.
### Known Issues
- A crash can occur occasionally when a Shairport Sync player is being selected for output. If you can get this to happen reliably, please let us know.
From d0e00fa381bb843eb32620baecd3bddb5d81271d Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 8 Jul 2021 13:26:45 +0000
Subject: [PATCH 077/180] Slight rephrasing.
---
AIRPLAY2.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/AIRPLAY2.md b/AIRPLAY2.md
index 6dbd97a8..42a20730 100644
--- a/AIRPLAY2.md
+++ b/AIRPLAY2.md
@@ -56,12 +56,12 @@ AirPlay 2 -- More About What Works
Acknowledgements
----
-Much of Shairport Sync's AirPlay 2 functionality is based on ideas developed at the [openairplay airplay2-receiver]( https://github.com/openairplay/airplay2-receiver) repository.
-
-Big thanks are due to number of individuals made direct and valuable contributions to Shairport Sync:
+Huge thanks are due to number of individuals made direct and valuable contributions to Shairport Sync:
1. [ejurgensen](https://github.com/ejurgensen) contributed ideas and code. The `pair_ap` submodule and related code in `rtsp.c` is theirs.
2. [JD Smith](https://github.com/jdtsmith) contributed ideas, comments and suggestions along with extensive and thorough testing.
-3. [Charles Omer](https://github.com/charlesomer) contributed ideas, comments, bug fixes and Docker automation (forthcoming).
+3. [Charles Omer](https://github.com/charlesomer) contributed ideas, comments, bug fixes, documentation and Docker automation (forthcoming).
+
+Much of Shairport Sync's AirPlay 2 functionality is based on ideas developed at the [openairplay airplay2-receiver]( https://github.com/openairplay/airplay2-receiver) repository. It is a pleasure to acknowledge the work of the contributors there.
Finally
----
From 9ccde50c47934a4879cf9a199387d223dee4ed00 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 8 Jul 2021 15:27:23 +0100
Subject: [PATCH 078/180] Update BUILDFORAP2.md
---
BUILDFORAP2.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/BUILDFORAP2.md b/BUILDFORAP2.md
index eac2baf4..780a3c81 100644
--- a/BUILDFORAP2.md
+++ b/BUILDFORAP2.md
@@ -154,4 +154,4 @@ Connect and enjoy...
### Restart Your Mac!
-At the time of writing, there seems to be a bug in the Mac Music app, particularly on Apple silicon Macs. It appears that, if the Mac has been sleeping, the AirPlay 2 infrastructure is not fully reenabled, and it will be unable to drive AirPlay 2 devices for more than a few seconds. The only know solution is to reboot.
+At the time of writing, there seems to be a bug in the Mac Music app, particularly on Apple silicon Macs. It appears that, if the Mac has been sleeping, the AirPlay 2 infrastructure is not fully reenabled, and it will be unable to drive AirPlay 2 devices for more than a few seconds. The only known solution is to reboot.
From 25de2bd3cefbd1273ca72121d8b69af9675bcf17 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 18:29:58 +0100
Subject: [PATCH 079/180] Added docker.
---
.github/workflows/docker-build-releases.yaml | 64 +++++++++
.github/workflows/docker-build.yaml | 62 +++++++++
docker/Dockerfile | 137 +++++++++++++++++++
docker/README.md | 39 ++++++
docker/airplay1/Dockerfile | 103 ++++++++++++++
docker/airplay1/README.md | 3 +
docker/airplay1/start.sh | 13 ++
docker/docker-compose.yaml | 11 ++
docker/start.sh | 18 +++
9 files changed, 450 insertions(+)
create mode 100644 .github/workflows/docker-build-releases.yaml
create mode 100644 .github/workflows/docker-build.yaml
create mode 100644 docker/Dockerfile
create mode 100644 docker/README.md
create mode 100644 docker/airplay1/Dockerfile
create mode 100644 docker/airplay1/README.md
create mode 100644 docker/airplay1/start.sh
create mode 100644 docker/docker-compose.yaml
create mode 100644 docker/start.sh
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
new file mode 100644
index 00000000..745d4ffd
--- /dev/null
+++ b/.github/workflows/docker-build-releases.yaml
@@ -0,0 +1,64 @@
+# Builds & pushes a docker image when a release occurs.
+
+# The following tags are created:
+# 'latest' & 'latest-airplay1'
+# '[RELEASE TAG]' & '[RELEASE TAG]-airplay1'
+
+name: Build and push docker images on releases.
+
+on:
+ release:
+ types: [ published ]
+
+jobs:
+ main:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Set SHAIRPORT_SYNC_BRANCH env.
+ run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
+
+ - name: Set up ssh agent
+ uses: webfactory/ssh-agent@v0.5.2 # Can be removed when no longer pulling from private repos.
+ with:
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+
+ - name: Login to Docker Registry
+ uses: docker/login-action@v1
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_REGISTRY_USER }}
+ password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v2
+ with:
+ context: ./
+ file: ./docker/Dockerfile
+ platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+ push: true
+ tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:latest ${{ env.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}
+ build-args: |
+ SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
+ ssh: default=${{ env.SSH_AUTH_SOCK }} # Can be removed when no longer pulling from private repos.
+
+ - name: Build and push (AirPlay 1)
+ uses: docker/build-push-action@v2
+ with:
+ context: ./
+ file: ./docker/airplay1/Dockerfile-AirPlay1
+ platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+ push: true
+ tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:latest-airplay1, ${{ env.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}-airplay1
+ build-args: |
+ SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
\ No newline at end of file
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
new file mode 100644
index 00000000..3a3d8392
--- /dev/null
+++ b/.github/workflows/docker-build.yaml
@@ -0,0 +1,62 @@
+# Builds & pushes a docker image when a commit is made to one of the branches specified below.
+# Tag pattern: 'unstable-[BRANCH NAME]'.
+# See docker-build-releases.yaml for release build process.
+
+name: Build and push docker image based on commit to specified branches.
+
+on:
+ push:
+ branches: [ master, development, charles-dev ]
+
+jobs:
+ main:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Set SHAIRPORT_SYNC_BRANCH env.
+ run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
+
+ - name: Set up ssh agent
+ uses: webfactory/ssh-agent@v0.5.2 # Can be removed when no longer pulling from private repos.
+ with:
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+
+ - name: Login to Docker Registry
+ uses: docker/login-action@v1
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_REGISTRY_USER }}
+ password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v2
+ with:
+ context: ./
+ file: ./docker/Dockerfile
+ platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+ push: true
+ tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
+ build-args: |
+ SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
+ ssh: default=${{ env.SSH_AUTH_SOCK }} # Can be removed when no longer pulling from private repos.
+
+ - name: Build and push (AirPlay 1)
+ uses: docker/build-push-action@v2
+ with:
+ context: ./
+ file: ./docker/airplay1/Dockerfile
+ platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+ push: true
+ tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}-airplay1
+ build-args: |
+ SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
\ No newline at end of file
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 00000000..1203b1c6
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,137 @@
+FROM alpine:3.12 AS builder-base
+# Version 3.12 is required for now since libplist has breaking changes in v3.13 and above which are not yet standard
+# in debian/ubuntu.
+
+# General Build System:
+RUN apk -U add \
+ git \
+ build-base \
+ autoconf \
+ automake \
+ libtool \
+ dbus \
+ su-exec \
+ alsa-lib-dev \
+ libdaemon-dev \
+ popt-dev \
+ mbedtls-dev \
+ soxr-dev \
+ avahi-dev \
+ libconfig-dev \
+ libsndfile-dev \
+ mosquitto-dev \
+ xmltoman \
+ openssh-client \
+ libsodium-dev \
+ ffmpeg-dev \
+ xxd \
+ libressl-dev \
+ openssl-dev \
+ libplist-dev \
+ libgcrypt-dev
+
+# ALAC Build System:
+FROM builder-base AS builder-alac
+
+RUN git clone https://github.com/mikebrady/alac
+WORKDIR /alac
+RUN autoreconf -fi
+RUN ./configure
+RUN make
+RUN make install
+
+# Shairport Sync Build System:
+FROM builder-base AS builder-sps
+
+# This will be modified by the Github Action Workflow and is required
+# to ensure the correct branch is being used.
+ARG SHAIRPORT_SYNC_BRANCH
+RUN test -n "$SHAIRPORT_SYNC_BRANCH"
+
+COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
+COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
+COPY --from=builder-alac /usr/local/include /usr/local/include
+
+# TODO: Remove when no longer pulling from private repos.
+RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
+
+RUN --mount=type=ssh git clone git@github.com:mikebrady/nqptp.git
+WORKDIR /nqptp
+# Use development branch for now.
+RUN git checkout development
+RUN autoreconf -fi
+RUN ./configure
+RUN make
+RUN make install
+WORKDIR /
+
+WORKDIR /shairport-sync
+COPY . .
+RUN git checkout "$SHAIRPORT_SYNC_BRANCH"
+RUN autoreconf -fi
+# RUN ./configure \
+# --with-alsa \
+# --with-dummy \
+# --with-pipe \
+# --with-stdout \
+# --with-avahi \
+# --with-ssl=mbedtls \
+# --with-soxr \
+# --sysconfdir=/etc \
+# --with-dbus-interface \
+# --with-mpris-interface \
+# --with-mqtt-client \
+# --with-apple-alac \
+# --with-convolution \
+# --with-airplay-2
+# RUN make -j $(nproc)
+RUN CFLAGS="-O0 -g" CXXFLAGS="-O0 -g" ./configure --sysconfdir=/etc --with-metadata --with-dummy --with-pipe \
+ --with-alsa --with-soxr --with-avahi --with-ssl=openssl --with-dbus-interface --with-stdout \
+ --with-mpris-interface --with-mqtt-client --with-apple-alac --with-convolution --with-airplay-2
+RUN make -j
+RUN make install
+
+# Shairport Sync Runtime System:
+FROM alpine:3.12
+
+RUN apk -U add \
+ alsa-lib \
+ dbus \
+ popt \
+ glib \
+ mbedtls \
+ soxr \
+ avahi \
+ libconfig \
+ libsndfile \
+ mosquitto \
+ su-exec \
+ libuuid \
+ ffmpeg \
+ libsodium \
+ libplist \
+ libgcrypt
+
+RUN rm -rf /lib/apk/db/*
+
+COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
+COPY --from=builder-sps /etc/shairport-sync* /etc/
+COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-dbus.conf /etc/dbus-1/system.d/
+COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-mpris.conf /etc/dbus-1/system.d/
+COPY --from=builder-sps /usr/local/bin/shairport-sync /usr/local/bin/shairport-sync
+COPY --from=builder-sps /usr/local/bin/nqptp /usr/local/bin/nqptp
+
+# Create non-root user for running the container -- running as the user 'shairport-sync' also allows
+# Shairport Sync to provide the D-Bus and MPRIS interfaces within the container
+
+RUN addgroup shairport-sync
+RUN adduser -D shairport-sync -G shairport-sync
+
+# Add the shairport-sync user to the pre-existing audio group, which has ID 29, for access to the ALSA stuff
+RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio && addgroup shairport-sync audio
+
+COPY ./docker/start.sh /
+
+RUN chmod +x ./start.sh
+
+ENTRYPOINT [ "/start.sh" ]
\ No newline at end of file
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 00000000..fa2b1b53
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,39 @@
+# Shairport Sync Docker Image
+
+Available at: https://hub.docker.com/r/mikebrady/shairport-sync
+
+## Example Docker Compose File
+See the `docker-compose.yaml` file in this folder for an example.
+
+## Basic Usage
+
+```
+$ docker run -d --restart unless-stopped --net host --device /dev/snd \
+ mikebrady/shairport-sync
+```
+The above command will run Shairport Sync as a daemon in a Docker container, accessing the computer's ALSA audio infrastructure. It will send audio to the default output device and make no use of any hardware mixers the default device might have. The AirPlay service name will be the host's `hostname` with the first letter capitalised, e.g. `Ubuntu`.
+
+## Options
+
+Any options you add to the command above will be passed to Shairport Sync. Here is an example:
+```
+$ docker run -d --restart unless-stopped --net host --device /dev/snd \
+ mikebrady/shairport-sync -a DenSystem -- -d hw:0 -c PCM
+```
+This will sent audio to alsa hardware device `hw:0` and make use of the that device's mixer control called `PCM`. The service will be visible as `DenSystem` on the network.
+
+## Configuration File
+
+Edit the configuration file `/etc/shairport-sync.conf` in the container (or use the `-v` option to mirror an external copy of `shairport-sync.conf` in to `/etc/shairport-sync.conf`) to get access to the full range of configuration options.
+
+## Build Example (for arm7 devices)
+
+TODO: remove `--ssh` when going public as this is only for private repos.
+```
+docker buildx build --ssh default=$SSH_AUTH_SOCK --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg SHAIRPORT_SYNC_BRANCH=development -t shairport-sync:unstable-development .
+```
+
+`SHAIRPORT_SYNC_BRANCH` is required to ensure the image is built using the expected branch.
+
+## AirPlay 1 Only
+The AirPlay1 only dockerfile is in the `airplay1` folder. This also includes the `start.sh` script used by the container.
\ No newline at end of file
diff --git a/docker/airplay1/Dockerfile b/docker/airplay1/Dockerfile
new file mode 100644
index 00000000..e60fd0b5
--- /dev/null
+++ b/docker/airplay1/Dockerfile
@@ -0,0 +1,103 @@
+FROM alpine:3.12 AS builder-base
+# General Build System:
+RUN apk -U add \
+ git \
+ build-base \
+ autoconf \
+ automake \
+ libtool \
+ dbus \
+ su-exec \
+ alsa-lib-dev \
+ libdaemon-dev \
+ popt-dev \
+ mbedtls-dev \
+ soxr-dev \
+ avahi-dev \
+ libconfig-dev \
+ libsndfile-dev \
+ mosquitto-dev \
+ xmltoman
+
+# ALAC Build System:
+FROM builder-base AS builder-alac
+
+RUN git clone https://github.com/mikebrady/alac
+WORKDIR alac
+RUN autoreconf -fi
+RUN ./configure
+RUN make
+RUN make install
+
+# Shairport Sync Build System:
+FROM builder-base AS builder-sps
+
+# This will be modified by the Github Action Workflow and is required
+# to ensure the correct branch is being used.
+ARG SHAIRPORT_SYNC_BRANCH
+RUN test -n "$SHAIRPORT_SYNC_BRANCH"
+
+COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
+COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
+COPY --from=builder-alac /usr/local/include /usr/local/include
+
+WORKDIR shairport-sync
+COPY . .
+RUN git checkout "$SHAIRPORT_SYNC_BRANCH"
+RUN autoreconf -fi
+RUN ./configure \
+ --with-alsa \
+ --with-dummy \
+ --with-pipe \
+ --with-stdout \
+ --with-avahi \
+ --with-ssl=mbedtls \
+ --with-soxr \
+ --sysconfdir=/etc \
+ --with-dbus-interface \
+ --with-mpris-interface \
+ --with-mqtt-client \
+ --with-apple-alac \
+ --with-convolution
+RUN make -j $(nproc)
+RUN make install
+
+# Shairport Sync Runtime System:
+FROM alpine:3.12
+
+RUN apk -U add \
+ alsa-lib \
+ dbus \
+ popt \
+ glib \
+ mbedtls \
+ soxr \
+ avahi \
+ libconfig \
+ libsndfile \
+ mosquitto-libs \
+ su-exec \
+ libgcc \
+ libgc++
+
+RUN rm -rf /lib/apk/db/*
+
+COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
+COPY --from=builder-sps /etc/shairport-sync* /etc/
+COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-dbus.conf /etc/dbus-1/system.d/
+COPY --from=builder-sps /etc/dbus-1/system.d/shairport-sync-mpris.conf /etc/dbus-1/system.d/
+COPY --from=builder-sps /usr/local/bin/shairport-sync /usr/local/bin/shairport-sync
+
+# Create non-root user for running the container -- running as the user 'shairport-sync' also allows
+# Shairport Sync to provide the D-Bus and MPRIS interfaces within the container
+
+RUN addgroup shairport-sync
+RUN adduser -D shairport-sync -G shairport-sync
+
+# Add the shairport-sync user to the pre-existing audio group, which has ID 29, for access to the ALSA stuff
+RUN addgroup -g 29 docker_audio && addgroup shairport-sync docker_audio
+
+COPY ./docker/airplay1/start.sh /
+RUN chmod +x /start.sh
+
+ENTRYPOINT [ "/start.sh" ]
diff --git a/docker/airplay1/README.md b/docker/airplay1/README.md
new file mode 100644
index 00000000..247754e6
--- /dev/null
+++ b/docker/airplay1/README.md
@@ -0,0 +1,3 @@
+# AirPlay 1 Only Docker Image
+
+See the [Shairport Sync Docker Hub Repo](https://hub.docker.com/r/mikebrady/shairport-sync) for available tags. For the latest build, use `latest-airplay1`.
\ No newline at end of file
diff --git a/docker/airplay1/start.sh b/docker/airplay1/start.sh
new file mode 100644
index 00000000..f68a122f
--- /dev/null
+++ b/docker/airplay1/start.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+set -e
+
+rm -rf /var/run/dbus.pid
+#mkdir -p /var/run/dbus
+
+dbus-uuidgen --ensure
+dbus-daemon --system
+
+avahi-daemon --daemonize --no-chroot
+
+su-exec shairport-sync shairport-sync $@
\ No newline at end of file
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
new file mode 100644
index 00000000..74ed09ac
--- /dev/null
+++ b/docker/docker-compose.yaml
@@ -0,0 +1,11 @@
+# Example docker compose config.
+
+services:
+ shairport-sync:
+ image: mikebrady/shairport-sync:unstable-development
+ network_mode: host
+ restart: unless-stopped
+ devices:
+ - "/dev/snd"
+ # volumes:
+ # - ./volumes/shairport-sync/shairport-sync.conf:/etc/shairport-sync.conf # Customised Shairport Sync configuration file.
\ No newline at end of file
diff --git a/docker/start.sh b/docker/start.sh
new file mode 100644
index 00000000..bb790283
--- /dev/null
+++ b/docker/start.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -e
+
+rm -rf /var/run/dbus.pid
+#mkdir -p /var/run/dbus
+
+dbus-uuidgen --ensure
+dbus-daemon --system
+
+avahi-daemon --daemonize --no-chroot
+
+# TODO: This should probably be implemented in a better way and using the correct nqptp group.
+nqptp &
+
+# su-exec shairport-sync shairport-sync $@
+
+shairport-sync -vu --statistics
\ No newline at end of file
From bb26b1bc8d228d72bd8d65bcb2bebc5da557c6ae Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 18:32:30 +0100
Subject: [PATCH 080/180] Remove ssh key stuff.
---
.github/workflows/docker-build-releases.yaml | 8 +-------
.github/workflows/docker-build.yaml | 8 +-------
docker/Dockerfile | 5 +----
docker/README.md | 3 +--
4 files changed, 4 insertions(+), 20 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index 745d4ffd..437c87e3 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -8,7 +8,7 @@ name: Build and push docker images on releases.
on:
release:
- types: [ published ]
+ types: [ ]
jobs:
main:
@@ -22,11 +22,6 @@ jobs:
- name: Set SHAIRPORT_SYNC_BRANCH env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - name: Set up ssh agent
- uses: webfactory/ssh-agent@v0.5.2 # Can be removed when no longer pulling from private repos.
- with:
- ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
-
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
@@ -50,7 +45,6 @@ jobs:
tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:latest ${{ env.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
- ssh: default=${{ env.SSH_AUTH_SOCK }} # Can be removed when no longer pulling from private repos.
- name: Build and push (AirPlay 1)
uses: docker/build-push-action@v2
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index 3a3d8392..d3eab23d 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -6,7 +6,7 @@ name: Build and push docker image based on commit to specified branches.
on:
push:
- branches: [ master, development, charles-dev ]
+ branches: [ development ]
jobs:
main:
@@ -20,11 +20,6 @@ jobs:
- name: Set SHAIRPORT_SYNC_BRANCH env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - name: Set up ssh agent
- uses: webfactory/ssh-agent@v0.5.2 # Can be removed when no longer pulling from private repos.
- with:
- ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
-
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
@@ -48,7 +43,6 @@ jobs:
tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
- ssh: default=${{ env.SSH_AUTH_SOCK }} # Can be removed when no longer pulling from private repos.
- name: Build and push (AirPlay 1)
uses: docker/build-push-action@v2
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 1203b1c6..210d4868 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -52,10 +52,7 @@ COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
COPY --from=builder-alac /usr/local/include /usr/local/include
-# TODO: Remove when no longer pulling from private repos.
-RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
-
-RUN --mount=type=ssh git clone git@github.com:mikebrady/nqptp.git
+RUN git clone git@github.com:mikebrady/nqptp.git
WORKDIR /nqptp
# Use development branch for now.
RUN git checkout development
diff --git a/docker/README.md b/docker/README.md
index fa2b1b53..87c26f99 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -28,9 +28,8 @@ Edit the configuration file `/etc/shairport-sync.conf` in the container (or use
## Build Example (for arm7 devices)
-TODO: remove `--ssh` when going public as this is only for private repos.
```
-docker buildx build --ssh default=$SSH_AUTH_SOCK --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg SHAIRPORT_SYNC_BRANCH=development -t shairport-sync:unstable-development .
+docker buildx build --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg SHAIRPORT_SYNC_BRANCH=development -t shairport-sync:unstable-development .
```
`SHAIRPORT_SYNC_BRANCH` is required to ensure the image is built using the expected branch.
From 95a9f68bc82a4a9782993b62fdb23854e5bc803b Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 18:37:02 +0100
Subject: [PATCH 081/180] Update docs.
---
docker/airplay1/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker/airplay1/README.md b/docker/airplay1/README.md
index 247754e6..1d68a959 100644
--- a/docker/airplay1/README.md
+++ b/docker/airplay1/README.md
@@ -1,3 +1,3 @@
# AirPlay 1 Only Docker Image
-See the [Shairport Sync Docker Hub Repo](https://hub.docker.com/r/mikebrady/shairport-sync) for available tags. For the latest build, use `latest-airplay1`.
\ No newline at end of file
+See the [Shairport Sync Docker Hub Repo](https://hub.docker.com/r/mikebrady/shairport-sync) for available tags.
\ No newline at end of file
From 49a292759d2f9e95b4e358efe2a8daac4e0d42ce Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 18:39:40 +0100
Subject: [PATCH 082/180] Update docker registry location.
---
.github/workflows/docker-build-releases.yaml | 6 +++---
.github/workflows/docker-build.yaml | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index 437c87e3..a9da88e4 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -31,7 +31,7 @@ jobs:
- name: Login to Docker Registry
uses: docker/login-action@v1
with:
- registry: ${{ env.DOCKER_REGISTRY }}
+ registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_REGISTRY_USER }}
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
@@ -42,7 +42,7 @@ jobs:
file: ./docker/Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
push: true
- tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:latest ${{ env.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}
+ tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:latest ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
@@ -53,6 +53,6 @@ jobs:
file: ./docker/airplay1/Dockerfile-AirPlay1
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
push: true
- tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:latest-airplay1, ${{ env.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}-airplay1
+ tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:latest-airplay1, ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}-airplay1
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
\ No newline at end of file
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index d3eab23d..fe14f93c 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -29,7 +29,7 @@ jobs:
- name: Login to Docker Registry
uses: docker/login-action@v1
with:
- registry: ${{ env.DOCKER_REGISTRY }}
+ registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_REGISTRY_USER }}
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
@@ -40,7 +40,7 @@ jobs:
file: ./docker/Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
push: true
- tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
+ tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
@@ -51,6 +51,6 @@ jobs:
file: ./docker/airplay1/Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
push: true
- tags: ${{ env.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}-airplay1
+ tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}-airplay1
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
\ No newline at end of file
From 782af5e39549945164b2e2671b1a83d43dc93783 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 18:43:53 +0100
Subject: [PATCH 083/180] Trigger test run.
---
docker/README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docker/README.md b/docker/README.md
index 87c26f99..883c74af 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -35,4 +35,5 @@ docker buildx build --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg S
`SHAIRPORT_SYNC_BRANCH` is required to ensure the image is built using the expected branch.
## AirPlay 1 Only
+
The AirPlay1 only dockerfile is in the `airplay1` folder. This also includes the `start.sh` script used by the container.
\ No newline at end of file
From 99c74482f8d13fffebc9bf65e102fb58e7f06e13 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 18:52:21 +0100
Subject: [PATCH 084/180] Comments.
---
.github/workflows/docker-build.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index fe14f93c..dc27674f 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -1,5 +1,5 @@
# Builds & pushes a docker image when a commit is made to one of the branches specified below.
-# Tag pattern: 'unstable-[BRANCH NAME]'.
+# Tag pattern: 'unstable-[BRANCH NAME]' & 'unstable-[BRANCH NAME]-airplay1'
# See docker-build-releases.yaml for release build process.
name: Build and push docker image based on commit to specified branches.
From 932713b243bc9c3c7e1c9802c1a6039828842b6b Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 18:53:27 +0100
Subject: [PATCH 085/180] Updated nqptp clone.
---
docker/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 210d4868..8dc5fe1e 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -52,7 +52,7 @@ COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
COPY --from=builder-alac /usr/local/include /usr/local/include
-RUN git clone git@github.com:mikebrady/nqptp.git
+RUN git clone https://github.com/mikebrady/nqptp
WORKDIR /nqptp
# Use development branch for now.
RUN git checkout development
From bd82293c4b54bccd919a07164c7a9d07c526058e Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:06:10 +0100
Subject: [PATCH 086/180] Added nqptp branch switching.
---
.github/workflows/docker-build-releases.yaml | 8 +++++++-
.github/workflows/docker-build.yaml | 8 +++++++-
docker/Dockerfile | 8 +++++---
docker/README.md | 2 +-
4 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index a9da88e4..6fe5c7e3 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -19,9 +19,15 @@ jobs:
with:
fetch-depth: 0
- - name: Set SHAIRPORT_SYNC_BRANCH env.
+ - name: Set SHAIRPORT_SYNC_BRANCH & NQPTP env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
+ - if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ run: echo "NQPTP_BRANCH=development"
+
+ - if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ run: echo "NQPTP_BRANCH=master"
+
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index dc27674f..d40636a0 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -17,9 +17,15 @@ jobs:
with:
fetch-depth: 0
- - name: Set SHAIRPORT_SYNC_BRANCH env.
+ - name: Set SHAIRPORT_SYNC_BRANCH & NQPTP env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
+ - if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ run: echo "NQPTP_BRANCH=development"
+
+ - if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ run: echo "NQPTP_BRANCH=master"
+
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 8dc5fe1e..98a3900c 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -43,10 +43,12 @@ RUN make install
# Shairport Sync Build System:
FROM builder-base AS builder-sps
-# This will be modified by the Github Action Workflow and is required
-# to ensure the correct branch is being used.
+# These will be modified by the Github Action Workflow and are required
+# to ensure the correct branches are being used.
ARG SHAIRPORT_SYNC_BRANCH
RUN test -n "$SHAIRPORT_SYNC_BRANCH"
+ARG NQPTP_BRANCH
+RUN test -n "$NQPTP_BRANCH"
COPY --from=builder-alac /usr/local/lib/libalac.* /usr/local/lib/
COPY --from=builder-alac /usr/local/lib/pkgconfig/alac.pc /usr/local/lib/pkgconfig/alac.pc
@@ -55,7 +57,7 @@ COPY --from=builder-alac /usr/local/include /usr/local/include
RUN git clone https://github.com/mikebrady/nqptp
WORKDIR /nqptp
# Use development branch for now.
-RUN git checkout development
+RUN git checkout "$NQPTP_BRANCH"
RUN autoreconf -fi
RUN ./configure
RUN make
diff --git a/docker/README.md b/docker/README.md
index 883c74af..25df20ec 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -29,7 +29,7 @@ Edit the configuration file `/etc/shairport-sync.conf` in the container (or use
## Build Example (for arm7 devices)
```
-docker buildx build --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg SHAIRPORT_SYNC_BRANCH=development -t shairport-sync:unstable-development .
+docker buildx build --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg SHAIRPORT_SYNC_BRANCH=development --build-arg NQPTP_BRANCH=development -t shairport-sync:unstable-development .
```
`SHAIRPORT_SYNC_BRANCH` is required to ensure the image is built using the expected branch.
From 60afdc09849da0396a48423b21367da26f227dd6 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:09:45 +0100
Subject: [PATCH 087/180] Use github ref.
---
.github/workflows/docker-build-releases.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index 6fe5c7e3..43bd7927 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -22,10 +22,10 @@ jobs:
- name: Set SHAIRPORT_SYNC_BRANCH & NQPTP env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ - if: github.ref == 'development'
run: echo "NQPTP_BRANCH=development"
- - if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ - if: github.ref != 'development'
run: echo "NQPTP_BRANCH=master"
- name: Set up QEMU
From 15f0bb34dc81d127b0f77df95e37bde6f55908b1 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:11:42 +0100
Subject: [PATCH 088/180] Use github ref 2.
---
.github/workflows/docker-build.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index d40636a0..f6c2481e 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -20,10 +20,10 @@ jobs:
- name: Set SHAIRPORT_SYNC_BRANCH & NQPTP env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ - if: github.ref == 'development'
run: echo "NQPTP_BRANCH=development"
- - if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ - if: github.ref != 'development'
run: echo "NQPTP_BRANCH=master"
- name: Set up QEMU
From 5bcf0d563f17ef72b73eb7fc916282a7506c956a Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:15:37 +0100
Subject: [PATCH 089/180] Use github ref 3.
---
.github/workflows/docker-build-releases.yaml | 4 ++--
.github/workflows/docker-build.yaml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index 43bd7927..6fe5c7e3 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -22,10 +22,10 @@ jobs:
- name: Set SHAIRPORT_SYNC_BRANCH & NQPTP env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - if: github.ref == 'development'
+ - if: env.SHAIRPORT_SYNC_BRANCH == 'development'
run: echo "NQPTP_BRANCH=development"
- - if: github.ref != 'development'
+ - if: env.SHAIRPORT_SYNC_BRANCH != 'development'
run: echo "NQPTP_BRANCH=master"
- name: Set up QEMU
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index f6c2481e..d40636a0 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -20,10 +20,10 @@ jobs:
- name: Set SHAIRPORT_SYNC_BRANCH & NQPTP env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - if: github.ref == 'development'
+ - if: env.SHAIRPORT_SYNC_BRANCH == 'development'
run: echo "NQPTP_BRANCH=development"
- - if: github.ref != 'development'
+ - if: env.SHAIRPORT_SYNC_BRANCH != 'development'
run: echo "NQPTP_BRANCH=master"
- name: Set up QEMU
From 799d2e54d8592c92c1b6c55c9aeefc43898e3860 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:17:32 +0100
Subject: [PATCH 090/180] Test.
---
.github/workflows/docker-build-releases.yaml | 8 +++++---
.github/workflows/docker-build.yaml | 8 +++++---
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index 6fe5c7e3..b39f4b0e 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -19,13 +19,15 @@ jobs:
with:
fetch-depth: 0
- - name: Set SHAIRPORT_SYNC_BRANCH & NQPTP env.
+ - name: Set SHAIRPORT_SYNC_BRANCH env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ - name: Determine NQPTP env (part 1).
+ if: env.SHAIRPORT_SYNC_BRANCH == 'development'
run: echo "NQPTP_BRANCH=development"
- - if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ - name: Determine NQPTP env (part 2).
+ if: env.SHAIRPORT_SYNC_BRANCH != 'development'
run: echo "NQPTP_BRANCH=master"
- name: Set up QEMU
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index d40636a0..9e95cd0d 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -17,13 +17,15 @@ jobs:
with:
fetch-depth: 0
- - name: Set SHAIRPORT_SYNC_BRANCH & NQPTP env.
+ - name: Set SHAIRPORT_SYNC_BRANCH env.
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- - if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ - name: Determine NQPTP env (part 1).
+ if: env.SHAIRPORT_SYNC_BRANCH == 'development'
run: echo "NQPTP_BRANCH=development"
- - if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ - name: Determine NQPTP env (part 2).
+ if: env.SHAIRPORT_SYNC_BRANCH != 'development'
run: echo "NQPTP_BRANCH=master"
- name: Set up QEMU
From e4a762579ca5710095182d19387fd6741d970745 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:19:24 +0100
Subject: [PATCH 091/180] Test.
---
.github/workflows/docker-build-releases.yaml | 6 +++---
.github/workflows/docker-build.yaml | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index b39f4b0e..de44e7ea 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -23,13 +23,13 @@ jobs:
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Determine NQPTP env (part 1).
- if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
run: echo "NQPTP_BRANCH=development"
- name: Determine NQPTP env (part 2).
- if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
run: echo "NQPTP_BRANCH=master"
-
+
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index 9e95cd0d..0fd3b7ef 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -21,11 +21,11 @@ jobs:
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Determine NQPTP env (part 1).
- if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
run: echo "NQPTP_BRANCH=development"
- name: Determine NQPTP env (part 2).
- if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
run: echo "NQPTP_BRANCH=master"
- name: Set up QEMU
From e973c782ad27d70dcc8a738f1dca46f194f75e22 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:23:00 +0100
Subject: [PATCH 092/180] Test.
---
.github/workflows/docker-build-releases.yaml | 8 ++++----
.github/workflows/docker-build.yaml | 8 ++++----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index de44e7ea..8ad75497 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -23,12 +23,12 @@ jobs:
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Determine NQPTP env (part 1).
- if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
- run: echo "NQPTP_BRANCH=development"
+ if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ run: echo "NQPTP_BRANCH=development" >> $GITHUB_ENV
- name: Determine NQPTP env (part 2).
- if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
- run: echo "NQPTP_BRANCH=master"
+ if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index 0fd3b7ef..fad6f5a3 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -21,12 +21,12 @@ jobs:
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Determine NQPTP env (part 1).
- if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
- run: echo "NQPTP_BRANCH=development"
+ if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ run: echo "NQPTP_BRANCH=development" >> $GITHUB_ENV
- name: Determine NQPTP env (part 2).
- if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
- run: echo "NQPTP_BRANCH=master"
+ if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
From 8eaa80f4fb2e290259913a0d2f33b56c626b5a3b Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:25:01 +0100
Subject: [PATCH 093/180] Test.
---
.github/workflows/docker-build-releases.yaml | 4 ++--
.github/workflows/docker-build.yaml | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index 8ad75497..e20ecc47 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -23,11 +23,11 @@ jobs:
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Determine NQPTP env (part 1).
- if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
run: echo "NQPTP_BRANCH=development" >> $GITHUB_ENV
- name: Determine NQPTP env (part 2).
- if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
- name: Set up QEMU
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index fad6f5a3..b02d0ce1 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -21,13 +21,13 @@ jobs:
run: echo "SHAIRPORT_SYNC_BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Determine NQPTP env (part 1).
- if: env.SHAIRPORT_SYNC_BRANCH == 'development'
+ if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
run: echo "NQPTP_BRANCH=development" >> $GITHUB_ENV
- name: Determine NQPTP env (part 2).
- if: env.SHAIRPORT_SYNC_BRANCH != 'development'
+ if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
-
+
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
From 19608dbb3b20e3b15bee06e45012cc4fbb9d015a Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:29:20 +0100
Subject: [PATCH 094/180] Test.
---
.github/workflows/docker-build-releases.yaml | 7 +++++--
.github/workflows/docker-build.yaml | 6 ++++--
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index e20ecc47..9d695d99 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -24,11 +24,14 @@ jobs:
- name: Determine NQPTP env (part 1).
if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
- run: echo "NQPTP_BRANCH=development" >> $GITHUB_ENV
+ env:
+ NQPTP_BRANCH: development
- name: Determine NQPTP env (part 2).
if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
- run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
+ env:
+ NQPTP_BRANCH: master
+
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index b02d0ce1..01435a89 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -22,11 +22,13 @@ jobs:
- name: Determine NQPTP env (part 1).
if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
- run: echo "NQPTP_BRANCH=development" >> $GITHUB_ENV
+ env:
+ NQPTP_BRANCH: development
- name: Determine NQPTP env (part 2).
if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
- run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
+ env:
+ NQPTP_BRANCH: master
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
From f80a5abc15da40605d2056a3f9496b74af222ece Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 19:37:09 +0100
Subject: [PATCH 095/180] Test.
---
.github/workflows/docker-build-releases.yaml | 8 +++-----
.github/workflows/docker-build.yaml | 7 +++----
2 files changed, 6 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index 9d695d99..e664d7a9 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -24,14 +24,11 @@ jobs:
- name: Determine NQPTP env (part 1).
if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
- env:
- NQPTP_BRANCH: development
+ run: echo "NQPTP_BRANCH=development" >> $GITHUB_ENV
- name: Determine NQPTP env (part 2).
if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
- env:
- NQPTP_BRANCH: master
-
+ run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
@@ -56,6 +53,7 @@ jobs:
tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:latest ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
+ NQPTP_BRANCH=${{ env.NQPTP_BRANCH }}
- name: Build and push (AirPlay 1)
uses: docker/build-push-action@v2
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index 01435a89..b3d8eebb 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -22,13 +22,11 @@ jobs:
- name: Determine NQPTP env (part 1).
if: ${{ env.SHAIRPORT_SYNC_BRANCH == 'development' }}
- env:
- NQPTP_BRANCH: development
+ run: echo "NQPTP_BRANCH=development" >> $GITHUB_ENV
- name: Determine NQPTP env (part 2).
if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
- env:
- NQPTP_BRANCH: master
+ run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
@@ -53,6 +51,7 @@ jobs:
tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
+ NQPTP_BRANCH=${{ env.NQPTP_BRANCH }}
- name: Build and push (AirPlay 1)
uses: docker/build-push-action@v2
From 2b313027ba94d4cbb3255bf57aaa009b60f11066 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 20:09:40 +0100
Subject: [PATCH 096/180] README update.
---
docker/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker/README.md b/docker/README.md
index 25df20ec..99bd85f2 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -36,4 +36,4 @@ docker buildx build --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg S
## AirPlay 1 Only
-The AirPlay1 only dockerfile is in the `airplay1` folder. This also includes the `start.sh` script used by the container.
\ No newline at end of file
+The AirPlay1 only dockerfile is in the `airplay1` folder. This also includes the `start.sh` script used by the container. Please note that the AirPlay 1 image build via the AirPlay 2 branch does not work with multiple instances on the same hardware.
\ No newline at end of file
From 7d43349f748c6df1b0bf4b1f2867a54cd4e760fb Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 20:18:42 +0100
Subject: [PATCH 097/180] Test.
---
docker/airplay1/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker/airplay1/README.md b/docker/airplay1/README.md
index 1d68a959..f8335d76 100644
--- a/docker/airplay1/README.md
+++ b/docker/airplay1/README.md
@@ -1,3 +1,3 @@
# AirPlay 1 Only Docker Image
-See the [Shairport Sync Docker Hub Repo](https://hub.docker.com/r/mikebrady/shairport-sync) for available tags.
\ No newline at end of file
+See the [Shairport Sync Docker Hub Repo](https://hub.docker.com/r/mikebrady/shairport-sync) for available tags.
From f634cc816cc7b9cf2eee39d1c092fba0743b8eca Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 20:22:13 +0100
Subject: [PATCH 098/180] Compatibility with docker hub.
---
.github/workflows/docker-build-releases.yaml | 4 ++--
.github/workflows/docker-build.yaml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index e664d7a9..59ef477e 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -50,7 +50,7 @@ jobs:
file: ./docker/Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
push: true
- tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:latest ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}
+ tags: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:latest ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:${{ github.event.release.tag_name }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
NQPTP_BRANCH=${{ env.NQPTP_BRANCH }}
@@ -62,6 +62,6 @@ jobs:
file: ./docker/airplay1/Dockerfile-AirPlay1
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
push: true
- tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:latest-airplay1, ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:${{ github.event.release.tag_name }}-airplay1
+ tags: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:latest-airplay1, ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:${{ github.event.release.tag_name }}-airplay1
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
\ No newline at end of file
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index b3d8eebb..c2d33331 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -48,7 +48,7 @@ jobs:
file: ./docker/Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
push: true
- tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
+ tags: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
NQPTP_BRANCH=${{ env.NQPTP_BRANCH }}
@@ -60,6 +60,6 @@ jobs:
file: ./docker/airplay1/Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
push: true
- tags: ${{ secrets.DOCKER_REGISTRY }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}-airplay1
+ tags: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}-airplay1
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
\ No newline at end of file
From 58f0c34930a2a254a4b34ac488a592ef1ee14e40 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 20:38:49 +0100
Subject: [PATCH 099/180] Centralised docker image name.
---
.github/workflows/docker-build-releases.yaml | 11 +++++++----
.github/workflows/docker-build.yaml | 11 +++++++----
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index 59ef477e..ef355fa8 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -10,6 +10,9 @@ on:
release:
types: [ ]
+env:
+ DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+
jobs:
main:
runs-on: ubuntu-latest
@@ -48,9 +51,9 @@ jobs:
with:
context: ./
file: ./docker/Dockerfile
- platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+ platforms: ${{ env.PLATFORMS }}
push: true
- tags: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:latest ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:${{ github.event.release.tag_name }}
+ tags: ${{ secrets.DOCKER_IMAGE_NAME }}:latest ${{ secrets.DOCKER_IMAGE_NAME }}:${{ github.event.release.tag_name }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
NQPTP_BRANCH=${{ env.NQPTP_BRANCH }}
@@ -60,8 +63,8 @@ jobs:
with:
context: ./
file: ./docker/airplay1/Dockerfile-AirPlay1
- platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+ platforms: ${{ env.PLATFORMS }}
push: true
- tags: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:latest-airplay1, ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:${{ github.event.release.tag_name }}-airplay1
+ tags: ${{ secrets.DOCKER_IMAGE_NAME }}:latest-airplay1, ${{ secrets.DOCKER_IMAGE_NAME }}:${{ github.event.release.tag_name }}-airplay1
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
\ No newline at end of file
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index c2d33331..af394e1c 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -7,6 +7,9 @@ name: Build and push docker image based on commit to specified branches.
on:
push:
branches: [ development ]
+
+env:
+ DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
jobs:
main:
@@ -46,9 +49,9 @@ jobs:
with:
context: ./
file: ./docker/Dockerfile
- platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+ platforms: ${{ env.DOCKER_PLATFORMS }}
push: true
- tags: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
+ tags: ${{ secrets.DOCKER_IMAGE_NAME }}:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
NQPTP_BRANCH=${{ env.NQPTP_BRANCH }}
@@ -58,8 +61,8 @@ jobs:
with:
context: ./
file: ./docker/airplay1/Dockerfile
- platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64,linux/arm/v7
+ platforms: ${{ env.DOCKER_PLATFORMS }}
push: true
- tags: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_REGISTRY_USER }}/shairport-sync:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}-airplay1
+ tags: ${{ secrets.DOCKER_IMAGE_NAME }}:unstable-${{ env.SHAIRPORT_SYNC_BRANCH }}-airplay1
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
\ No newline at end of file
From 4b2518bbdd7d7b9757560163f6210db0316a591a Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 8 Jul 2021 20:51:44 +0100
Subject: [PATCH 100/180] Updated README.
---
docker/README.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/docker/README.md b/docker/README.md
index 99bd85f2..6043cc44 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -36,4 +36,12 @@ docker buildx build --platform linux/arm/v7 -f ./docker/Dockerfile --build-arg S
## AirPlay 1 Only
-The AirPlay1 only dockerfile is in the `airplay1` folder. This also includes the `start.sh` script used by the container. Please note that the AirPlay 1 image build via the AirPlay 2 branch does not work with multiple instances on the same hardware.
\ No newline at end of file
+The AirPlay1 only dockerfile is in the `airplay1` folder. This also includes the `start.sh` script used by the container. Please note that the AirPlay 1 image build via the AirPlay 2 branch does not work with multiple instances on the same hardware.
+
+## GitHub Action Builds
+
+Requires the following secrets to be set in the repo:
+- `DOCKER_REGISTRY` - docker.io if using Docker Hub, else set to your registry URL.
+- `DOCKER_REGISTRY_TOKEN` - Access token for your registry.
+- `DOCKER_REGISTRY_USER` - Login user for your registry.
+- `DOCKER_IMAGE_NAME` - The name of the image, for example `your-registry.com/shairport-sync` or just `your-username/shairport-sync` if using Docker Hub.
\ No newline at end of file
From 87c588819ea68a2903a75f9580c84eaa3d50f8c6 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 9 Jul 2021 09:03:14 +0100
Subject: [PATCH 101/180] 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 b1010052..c14ba33e 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,4 +1,4 @@
-Version 4.0-dev
+Version 4.0-dev-153-g9ccde50
====
Big Update
From 0947bf3cc039be03a605b286520b3b4d9f8e8363 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 11 Jul 2021 18:38:45 +0100
Subject: [PATCH 102/180] Update CAR INSTALL.md
---
CAR INSTALL.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index 0a82b68a..f2a97c26 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -92,6 +92,7 @@ A number of packages to enable the Pi to work as a WiFi base station are needed:
```
Disable both of these services from starting at boot time (this is because we will launch them sequentially later on):
```
+# systemctl unmask hostapd
# systemctl disable hostapd
# systemctl disable isc-dhcp-server
```
From 2184f1b744cbd9acf854808ac7584b88773742ba Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 11 Jul 2021 18:39:38 +0100
Subject: [PATCH 103/180] Update CAR INSTALL.md
---
CAR INSTALL.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index f2a97c26..8fb2294b 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -42,7 +42,6 @@ The first thing to do on a Pi would be to use the `raspi-config` tool to expand
```
# apt-get update
# apt-get upgrade
-# rpi-update
```
### Shairport Sync
From 8fe5e33fc94cc072b7121259f16e59e94bedfa2b Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 12 Jul 2021 15:03:03 +0100
Subject: [PATCH 104/180] Update CAR INSTALL.md
---
CAR INSTALL.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index b24c86c7..93c4da05 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -193,9 +193,9 @@ denyinterfaces wlan0
```
From this point on, at least on the Raspberry Pi, if you reboot the machine, it will not reconnect to your network – instead, it will act as the WiFi base station you have configured with `hostapd` and `isc-dhcp-server`.
-### Optimise startup time
+### Optimise startup time -- Raspberry Pi
-There are quite a few services that are not necessary for this setup. Disabling them can increase startup time. Running these commands disables them:
+This is applicable to a Raspberry Pi only. Some of it may be applicable to other systems, but it has not been tested on them. There are quite a few services that are not necessary for this setup. Disabling them can increase startup time. Running these commands disables them:
````
sudo systemctl disable systemd-timesyncd.service
@@ -207,7 +207,7 @@ sudo systemctl disable dphys-swapfile.service
sudo systemctl disable networking.service
````
-### Read-only mode
+### Read-only mode -- Raspberry Pi
Run `sudo raspi-config` and then choose `Performance Options` > `Overlay Filesystem` and choose to enable the overlay filesystem, and to set the boot partition to be write-protected.
From 4d746ba26ce71b73321148d08a88ddccc1c0836f Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 12 Jul 2021 15:04:02 +0100
Subject: [PATCH 105/180] Update CAR INSTALL.md
---
CAR INSTALL.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index 93c4da05..d5680671 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -14,7 +14,7 @@ Note that Android devices can not, so far, do this trick of using the two networ
## Example
-In this example, a Raspberry Pi Zero W and a Pimoroni PHAT DAC are used. This combination has been tested for well over a year. Please note that some of the details of setting up networks are specific to the version of Linux used -- Raspbian Stretch.
+In this example, a Raspberry Pi Zero W and a Pimoroni PHAT DAC are used. This combination has been tested for over two years. Please note that some of the details of setting up networks are specific to the version of Linux used -- Raspbian Stretch or later.
### Prepare the initial SD Image
* Download the latest version of Raspbian Lite -- Stretch Lite of 2018-03-13 at the time of writing -- and install it onto an SD Card.
From fc5772936279c835a7fc891e4328c2d6a6fead5d Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 12 Jul 2021 15:04:58 +0100
Subject: [PATCH 106/180] Update CAR INSTALL.md
---
CAR INSTALL.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index d5680671..a9617b66 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -193,7 +193,7 @@ denyinterfaces wlan0
```
From this point on, at least on the Raspberry Pi, if you reboot the machine, it will not reconnect to your network – instead, it will act as the WiFi base station you have configured with `hostapd` and `isc-dhcp-server`.
-### Optimise startup time -- Raspberry Pi
+### Optimise startup time -- Raspberry Pi Specific
This is applicable to a Raspberry Pi only. Some of it may be applicable to other systems, but it has not been tested on them. There are quite a few services that are not necessary for this setup. Disabling them can increase startup time. Running these commands disables them:
@@ -207,7 +207,7 @@ sudo systemctl disable dphys-swapfile.service
sudo systemctl disable networking.service
````
-### Read-only mode -- Raspberry Pi
+### Read-only mode -- Raspberry Pi Specific
Run `sudo raspi-config` and then choose `Performance Options` > `Overlay Filesystem` and choose to enable the overlay filesystem, and to set the boot partition to be write-protected.
From d7fbdd2ef37c30491db6f13db8a80cdbe9ec62e0 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Mon, 12 Jul 2021 22:11:42 +0100
Subject: [PATCH 107/180] Added automated issue and PR management.
---
.github/workflows/actions-stale.yaml | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 .github/workflows/actions-stale.yaml
diff --git a/.github/workflows/actions-stale.yaml b/.github/workflows/actions-stale.yaml
new file mode 100644
index 00000000..3b0616f6
--- /dev/null
+++ b/.github/workflows/actions-stale.yaml
@@ -0,0 +1,16 @@
+name: 'Close stale issues and PRs'
+on:
+ schedule:
+ - cron: '* 8 * * *'
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v3
+ with:
+ stale-issue-message: 'This issue has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
+ stale-pr-message: 'This PR has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
+ # These are the defaults at the time of writing. https://github.com/marketplace/actions/close-stale-issues
+ # days-before-stale: 60
+ # days-before-close: 7
From f5d43c4b5c550385309a6a59b9dd15763e5eec14 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Wed, 14 Jul 2021 22:50:54 +0100
Subject: [PATCH 108/180] Rename file.
---
.github/workflows/{actions-stale.yaml => stale.yaml} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename .github/workflows/{actions-stale.yaml => stale.yaml} (100%)
diff --git a/.github/workflows/actions-stale.yaml b/.github/workflows/stale.yaml
similarity index 100%
rename from .github/workflows/actions-stale.yaml
rename to .github/workflows/stale.yaml
From 5ba110058c2538aa36669deff81cc25ed2108d8f Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Wed, 14 Jul 2021 22:57:25 +0100
Subject: [PATCH 109/180] Added permissions.
---
.github/workflows/stale.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
index 3b0616f6..9a755de2 100644
--- a/.github/workflows/stale.yaml
+++ b/.github/workflows/stale.yaml
@@ -3,6 +3,10 @@ on:
schedule:
- cron: '* 8 * * *'
+permissions:
+ issues: write
+ pull-requests: write
+
jobs:
stale:
runs-on: ubuntu-latest
From 46284bb115ae056a4567a1d175b67a48bf23f5d9 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Wed, 14 Jul 2021 23:33:22 +0100
Subject: [PATCH 110/180] Update version.
---
.github/workflows/stale.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
index 9a755de2..8ca23def 100644
--- a/.github/workflows/stale.yaml
+++ b/.github/workflows/stale.yaml
@@ -11,7 +11,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v3
+ - uses: actions/stale@v4
with:
stale-issue-message: 'This issue has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
stale-pr-message: 'This PR has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
From 91fdd7c3c2df891ce0ca4690c879e6e118f3a513 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Wed, 14 Jul 2021 23:41:47 +0100
Subject: [PATCH 111/180] Test stale action.
---
.github/workflows/stale.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
index 8ca23def..88a26e63 100644
--- a/.github/workflows/stale.yaml
+++ b/.github/workflows/stale.yaml
@@ -1,7 +1,7 @@
name: 'Close stale issues and PRs'
on:
schedule:
- - cron: '* 8 * * *'
+ - cron: '*/5 * * * *'
permissions:
issues: write
From 7c2d773e838a184dae29b89af049cb5fe581ba3f Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Wed, 14 Jul 2021 23:56:22 +0100
Subject: [PATCH 112/180] Test.
---
.github/workflows/stale.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
index 88a26e63..5faf85f6 100644
--- a/.github/workflows/stale.yaml
+++ b/.github/workflows/stale.yaml
@@ -16,5 +16,5 @@ jobs:
stale-issue-message: 'This issue has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
stale-pr-message: 'This PR has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
# These are the defaults at the time of writing. https://github.com/marketplace/actions/close-stale-issues
- # days-before-stale: 60
- # days-before-close: 7
+ days-before-stale: 60
+ days-before-close: 7
From 5d40aa62ba43b5a1ccd668fc05fd1c8a1251fb87 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 15 Jul 2021 00:00:59 +0100
Subject: [PATCH 113/180] Test.
---
.github/workflows/stale.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
index 5faf85f6..88a26e63 100644
--- a/.github/workflows/stale.yaml
+++ b/.github/workflows/stale.yaml
@@ -16,5 +16,5 @@ jobs:
stale-issue-message: 'This issue has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
stale-pr-message: 'This PR has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
# These are the defaults at the time of writing. https://github.com/marketplace/actions/close-stale-issues
- days-before-stale: 60
- days-before-close: 7
+ # days-before-stale: 60
+ # days-before-close: 7
From a819ec5df564398e5b343d09f976c7bfc7e88619 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 15 Jul 2021 00:08:46 +0100
Subject: [PATCH 114/180] Update stale.yaml
---
.github/workflows/stale.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
index 88a26e63..be61da3d 100644
--- a/.github/workflows/stale.yaml
+++ b/.github/workflows/stale.yaml
@@ -1,7 +1,7 @@
name: 'Close stale issues and PRs'
on:
schedule:
- - cron: '*/5 * * * *'
+ - cron: '0 0 * * *'
permissions:
issues: write
From ffdfaf8de5c451a94fde5751f9fcd31dc9fda4e1 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 15 Jul 2021 12:33:11 +0100
Subject: [PATCH 115/180] Added stale action.
---
.github/workflows/stale.yaml | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 .github/workflows/stale.yaml
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
new file mode 100644
index 00000000..be61da3d
--- /dev/null
+++ b/.github/workflows/stale.yaml
@@ -0,0 +1,20 @@
+name: 'Close stale issues and PRs'
+on:
+ schedule:
+ - cron: '0 0 * * *'
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v4
+ with:
+ stale-issue-message: 'This issue has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
+ stale-pr-message: 'This PR has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
+ # These are the defaults at the time of writing. https://github.com/marketplace/actions/close-stale-issues
+ # days-before-stale: 60
+ # days-before-close: 7
From 6564cf1600cbd101c613fc516a7f4e667852a27e Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 15 Jul 2021 12:45:40 +0100
Subject: [PATCH 116/180] Use 'main' branch instead of 'master' for nqptp.
---
.github/workflows/docker-build-releases.yaml | 2 +-
.github/workflows/docker-build.yaml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docker-build-releases.yaml b/.github/workflows/docker-build-releases.yaml
index ef355fa8..6894a0e7 100644
--- a/.github/workflows/docker-build-releases.yaml
+++ b/.github/workflows/docker-build-releases.yaml
@@ -31,7 +31,7 @@ jobs:
- name: Determine NQPTP env (part 2).
if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
- run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
+ run: echo "NQPTP_BRANCH=main" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index af394e1c..e4ac6b48 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -29,7 +29,7 @@ jobs:
- name: Determine NQPTP env (part 2).
if: ${{ env.SHAIRPORT_SYNC_BRANCH != 'development' }}
- run: echo "NQPTP_BRANCH=master" >> $GITHUB_ENV
+ run: echo "NQPTP_BRANCH=main" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
From 77895f1dccb3845fd57a7383476305d2462ff958 Mon Sep 17 00:00:00 2001
From: Charles <32498151+charlesomer@users.noreply.github.com>
Date: Thu, 15 Jul 2021 12:49:22 +0100
Subject: [PATCH 117/180] Remove stale since it's now on master instead.
---
.github/workflows/stale.yaml | 20 --------------------
1 file changed, 20 deletions(-)
delete mode 100644 .github/workflows/stale.yaml
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
deleted file mode 100644
index be61da3d..00000000
--- a/.github/workflows/stale.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: 'Close stale issues and PRs'
-on:
- schedule:
- - cron: '0 0 * * *'
-
-permissions:
- issues: write
- pull-requests: write
-
-jobs:
- stale:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/stale@v4
- with:
- stale-issue-message: 'This issue has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
- stale-pr-message: 'This PR has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
- # These are the defaults at the time of writing. https://github.com/marketplace/actions/close-stale-issues
- # days-before-stale: 60
- # days-before-close: 7
From 978862c2ec2c1c141ef2ed235057a024a7dbc46d Mon Sep 17 00:00:00 2001
From: ejurgensen
Date: Tue, 13 Jul 2021 22:28:34 +0200
Subject: [PATCH 118/180] Update pair_ap to 0.8 (fixes incorrect pair-list
response)
---
pair_ap/README.md | 13 +++++++------
pair_ap/pair.h | 2 +-
pair_ap/pair_homekit.c | 22 +++++++++++++++++++---
3 files changed, 27 insertions(+), 10 deletions(-)
diff --git a/pair_ap/README.md b/pair_ap/README.md
index 1c855444..d6b00d65 100644
--- a/pair_ap/README.md
+++ b/pair_ap/README.md
@@ -2,9 +2,10 @@
C client implementation of pairing for:
* Apple TV device verification, which became mandatory with tvOS 10.2 (this is
called fruit mode in pair_ap)
-* Homekit pairing (for AirPlay 2, not working for Home app)
+* Homekit pairing (also for AirPlay 2)
Credit goes to @funtax and @ViktoriiaKh for doing some of the heavy lifting.
+
## Requirements
- libsodium
- libgcrypt or libopenssl
@@ -33,7 +34,7 @@ The controller uses `/pair-add` to make sure that all devices on a network get
the ID and public key of all the other devices, so that the user only needs to
pair a device once.
-### Normal pairing
+### Normal pairing with one-time code
For a normal first-time pairing, the client needs a one-time code (the device
announces via mDNS whether a code is required). The client calls
`/pair-pin-start` and the device displays the code. There is also QR-based
@@ -42,12 +43,12 @@ pairing, which is (probably?) an encoded code.
After obtaining the code, the client initiates a three step `/pair-setup`
sequence, which results in both peers registering each other's ID and public
key. Henceforth, a pairing is verified with the two step `/pair-verify`, where
-the parties check each-others identify. Saving the peer's ID + public key isn't
+the parties check eachothers identify. Saving the peer's ID + public key isn't
strictly necessary if client or server doesn't care about verifying the peer,
i.e. that `/pair-setup` has actually been completed.
The result of `/pair-verify` is a shared secret that is used for symmetric
-encryption of the following communinacation between the parties.
+encryption of the following communication between the parties.
### Transient pairing
Some devices don't require a code from the user for pairing (e.g. an Airport
@@ -55,8 +56,7 @@ Express 2). If so, the client just needs to go through a two-step `/pair-setup`
sequence which results in a shared secret, which is then used for encrypted
communication. A fixed code of 3939 is used.
-Such devices don't appear to be fully Homekit compatible - they will not, for
-instance - appear in the Home app.
+The controller can still use `/pair-add` etc. towards such devices.
## "fruit" pairing
Like normal Homekit pairing, this consists of first requesting a code with
@@ -69,4 +69,5 @@ shared secret.
- [AirPlayAuth](https://github.com/funtax/AirPlayAuth)
- [AirPlayAuth-ObjC](https://github.com/ViktoriiaKh/AirPlayAuth-ObjC)
- [ap2-sender](https://github.com/ViktoriiaKh/ap2-sender)
+- [airplay2-receiver](https://github.com/ckdo/airplay2-receiver)
- [csrp](https://github.com/cocagne/csrp)
diff --git a/pair_ap/pair.h b/pair_ap/pair.h
index 6324bc15..3877e2bb 100644
--- a/pair_ap/pair.h
+++ b/pair_ap/pair.h
@@ -4,7 +4,7 @@
#include
#define PAIR_AP_VERSION_MAJOR 0
-#define PAIR_AP_VERSION_MINOR 5
+#define PAIR_AP_VERSION_MINOR 8
#define PAIR_AP_DEVICE_ID_LEN_MAX 64
diff --git a/pair_ap/pair_homekit.c b/pair_ap/pair_homekit.c
index 076ea14a..ed59807e 100644
--- a/pair_ap/pair_homekit.c
+++ b/pair_ap/pair_homekit.c
@@ -2679,6 +2679,7 @@ server_add_remove_request(pair_cb cb, void *cb_arg, const uint8_t *in, size_t in
pair_tlv_t *device_id;
pair_tlv_t *pk;
char id_str[PAIR_AP_DEVICE_ID_LEN_MAX] = { 0 };
+ uint8_t *public_key = NULL;
request = message_process(in, in_len, &errmsg);
if (!request)
@@ -2687,15 +2688,21 @@ server_add_remove_request(pair_cb cb, void *cb_arg, const uint8_t *in, size_t in
}
device_id = pair_tlv_get_value(request, TLVType_Identifier);
- pk = pair_tlv_get_value(request, TLVType_PublicKey);
- if (!device_id || device_id->size >= sizeof(id_str) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES)
+ if (!device_id || device_id->size >= sizeof(id_str))
{
goto error;
}
+ // Only present when adding
+ pk = pair_tlv_get_value(request, TLVType_PublicKey);
+ if (pk && pk->size == crypto_sign_PUBLICKEYBYTES)
+ {
+ public_key = pk->value;
+ }
+
memcpy(id_str, device_id->value, device_id->size);
- cb(pk->value, id_str, cb_arg);
+ cb(public_key, id_str, cb_arg);
pair_tlv_free(request);
return 0;
@@ -2757,9 +2764,18 @@ static int
server_list_cb(uint8_t public_key[crypto_sign_PUBLICKEYBYTES], const char *device_id, void *cb_arg)
{
pair_tlv_values_t *response = cb_arg;
+ pair_tlv_t *previous_id;
+ uint8_t permissions = 1; // Means admin (TODO don't hardcode - let caller set)
+
+ // If this isn't the first iteration (item) then we must add a separator
+ previous_id = pair_tlv_get_value(response, TLVType_Identifier);
+ if (previous_id)
+ pair_tlv_add_value(response, TLVType_Separator, NULL, 0);
pair_tlv_add_value(response, TLVType_Identifier, (unsigned char *)device_id, strlen(device_id));
pair_tlv_add_value(response, TLVType_PublicKey, public_key, crypto_sign_PUBLICKEYBYTES);
+ pair_tlv_add_value(response, TLVType_Permissions, &permissions, sizeof(permissions));
+
return 0;
}
From 1a64a56c3f807d69b9c27e8dc2b3c47ec6db453b Mon Sep 17 00:00:00 2001
From: ejurgensen
Date: Tue, 13 Jul 2021 23:21:01 +0200
Subject: [PATCH 119/180] Add Homekit pairing methods (pair-add/list/remove +
configure)
Also change the features flag to match airplay2-receiver
---
rtsp.c | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++
shairport.c | 13 ++---
2 files changed, 166 insertions(+), 8 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index d0cc762c..ba09ce15 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1555,6 +1555,125 @@ void handle_get(__attribute((unused)) rtsp_conn_info *conn, __attribute((unused)
#endif
#ifdef CONFIG_AIRPLAY_2
+struct pairings {
+ char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
+ uint8_t public_key[32];
+
+ struct pairings *next;
+} *pairings;
+
+static struct pairings * pairing_find(const char *device_id)
+{
+ for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
+ if (strcmp(device_id, pairing->device_id) == 0)
+ return pairing;
+ }
+ return NULL;
+}
+
+static void pairing_add(uint8_t public_key[32], const char *device_id) {
+ struct pairings *pairing = calloc(1, sizeof(struct pairings));
+ snprintf(pairing->device_id, sizeof(pairing->device_id), "%s", device_id);
+ memcpy(pairing->public_key, public_key, sizeof(pairing->public_key));
+
+ pairing->next = pairings;
+ pairings = pairing;
+}
+
+static void pairing_remove(struct pairings *pairing) {
+ if (pairing == pairings) {
+ pairings = pairing->next;
+ } else {
+ struct pairings *iter;
+ for (iter = pairings; iter && (iter->next != pairing); iter = iter->next)
+ ; /* EMPTY */
+
+ if (iter)
+ iter->next = pairing->next;
+ }
+
+ free(pairing);
+}
+
+static int pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg __attribute__((unused))) {
+ debug(1, "pair-add cb for %s", device_id);
+
+ struct pairings *pairing = pairing_find(device_id);
+ if (pairing) {
+ memcpy(pairing->public_key, public_key, sizeof(pairing->public_key));
+ return 0;
+ }
+
+ pairing_add(public_key, device_id);
+ return 0;
+}
+
+static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id, void *cb_arg __attribute__((unused))) {
+ debug(1, "pair-remove cb for %s", device_id);
+
+ struct pairings *pairing = pairing_find(device_id);
+ if (!pairing) {
+ debug(1, "pair-remove callback for unknown device");
+ return -1;
+ }
+
+ pairing_remove(pairing);
+ return 0;
+}
+
+static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg __attribute__((unused))) {
+ debug(1, "pair-list cb");
+
+ for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
+ enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg);
+ }
+}
+
+void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) {
+ uint8_t *body = NULL;
+ size_t body_len = 0;
+ int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL, (const uint8_t *)req->content, req->contentlength);
+ if (ret < 0) {
+ debug(1, "pair-add returned an error");
+ resp->respcode = 451;
+ return;
+ }
+ resp->content = (char *)body; // these will be freed when the data is sent
+ resp->contentlength = body_len;
+ msg_add_header(resp, "Content-Type", "application/octet-stream");
+ debug_log_rtsp_message(2, "pair-add response", resp);
+}
+
+void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) {
+ uint8_t *body = NULL;
+ size_t body_len = 0;
+ int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL, (const uint8_t *)req->content, req->contentlength);
+ if (ret < 0) {
+ debug(1, "pair-list returned an error");
+ resp->respcode = 451;
+ return;
+ }
+ resp->content = (char *)body; // these will be freed when the data is sent
+ resp->contentlength = body_len;
+ msg_add_header(resp, "Content-Type", "application/octet-stream");
+ debug_log_rtsp_message(2, "pair-list response", resp);
+}
+
+void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) {
+ uint8_t *body = NULL;
+ size_t body_len = 0;
+ int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL, (const uint8_t *)req->content, req->contentlength);
+ if (ret < 0) {
+ debug(1, "pair-remove returned an error");
+ resp->respcode = 451;
+ return;
+ }
+ resp->content = (char *)body; // these will be freed when the data is sent
+ resp->contentlength = body_len;
+ msg_add_header(resp, "Content-Type", "application/octet-stream");
+ debug_log_rtsp_message(2, "pair-remove response", resp);
+}
+
void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int ret;
uint8_t *body = NULL;
@@ -1747,14 +1866,56 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message
msg_add_header(resp, "Content-Type", "application/octet-stream");
}
+/*
+ Identifier
+ 21cc689d-d5de-4814-872c-71d1426b57e0
+ Enable_HK_Access_Control
+
+ PublicKey
+
+ qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ=
+
+ Device_Name
+ MyDevice
+ Access_Control_Level
+ 0
+*/
+void handle_configure(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req __attribute__((unused)), rtsp_message *resp) {
+ uint8_t public_key[32];
+
+ pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, config.airplay_device_id);
+
+ plist_t response_plist = plist_new_dict();
+
+ plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi));
+ plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1));
+ plist_dict_set_item(response_plist, "PublicKey", plist_new_data((const char *)public_key, sizeof(public_key)));
+ plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name));
+ plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0));
+
+ plist_to_bin(response_plist, &resp->content, &resp->contentlength);
+ plist_free(response_plist);
+
+ msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
+ debug_log_rtsp_message(2, "POST /configure response:", resp);
+}
+
void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
if (strcmp(req->path, "/pair-setup") == 0) {
handle_pair_setup(conn, req, resp);
} else if (strcmp(req->path, "/pair-verify") == 0) {
handle_pair_verify(conn, req, resp);
+ } else if (strcmp(req->path, "/pair-add") == 0) {
+ handle_pair_add(conn, req, resp);
+ } else if (strcmp(req->path, "/pair-remove") == 0) {
+ handle_pair_remove(conn, req, resp);
+ } else if (strcmp(req->path, "/pair-list") == 0) {
+ handle_pair_list(conn, req, resp);
} else if (strcmp(req->path, "/fp-setup") == 0) {
handle_fp_setup(conn, req, resp);
+ } else if (strcmp(req->path, "/configure") == 0) {
+ handle_configure(conn, req, resp);
} else {
debug(3, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path,
req->contentlength);
diff --git a/shairport.c b/shairport.c
index 45a08c7f..0f7f141f 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1587,15 +1587,12 @@ int main(int argc, char **argv) {
// features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
// 496155702020608 this setting here is the source of both the plist features response and the
// mDNS string.
- config.airplay_features = 0x1C340405F4A00;
+ // note: 0x300401F4A00 works but with weird delays and stuff
+ config.airplay_features = 0x1C340405FCA00;
// Advertised with mDNS and returned with GET /info, see
// https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN
- // required (transient pairing), no Homekit access control 0x204: Audio cable attached,
- // OneTimePairingRequired 0x604: Audio cable attached, OneTimePairingRequired, allow Homekit
- // access control
-
- // note: 0x300401F4A00 works but with weird delays and stuff
-
+ // required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio
+ // cable attached, OneTimePairingRequired, device was setup for Homekit access control
config.airplay_statusflags = 0x4;
// Set to NULL to work with transient pairing
config.airplay_pin = NULL;
@@ -1622,7 +1619,7 @@ int main(int argc, char **argv) {
uuid_generate_random(binuuid);
char *uuid = malloc(UUID_STR_LEN);
- /* Produces a UUID string at uuid consisting of lower-case letters. */
+ // Produces a UUID string at uuid consisting of lower-case letters
uuid_unparse_lower(binuuid, uuid);
config.airplay_pi = strdup(uuid);
config.airplay_gid = strdup(uuid); // initially the gid is the same as the pi
From 5630e20eb02d28ce3d53abd38faab7c991986238 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 16 Jul 2021 11:24:05 +0100
Subject: [PATCH 120/180] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index c14ba33e..0f0202cb 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,12 @@
+Version 4.0-dev-182-gc0ee8d1
+====
+#### New Features
+- **Home App Integration** An AirPlay 2 build of Shairport Sync now offers limited integration with the Home app. Specifically, a Shairport Sync AirPlay 2 instance can now be added as a speaker to the Home app. Thanks to [ckdo](https://github.com/ckdo) and [ejurgensen](https://github.com/ejurgensen) for figuring this out and for coding it up. It means that some Siri interaction is possible.
+- **Automated Docker Builds** Thanks to the work of [Charles Omer](https://github.com/charlesomer), Docker builds on the `development` branch should appear from time to time on the [Docker Hub](https://hub.docker.com/r/mikebrady/shairport-sync).
+
+#### Bug Fixes
+- [Crashing bug fix](https://github.com/mikebrady/nqptp/blob/main/RELEASE_NOTES.md) in `nqptp` -- thanks to [ste94pz](https://github.com/ste94pz).
+
Version 4.0-dev-153-g9ccde50
====
From 3a6297bc9aca9fa2b88d7761b12e9a328764dd84 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 16 Jul 2021 15:04:51 +0100
Subject: [PATCH 121/180] Honour max_output_db setting even when
ignore_volume_control is true. Spell out "Shairport Sync" in the Home app
information.
---
player.c | 312 ++++++++++++++++++++++++++++---------------------------
rtsp.c | 71 +++++++------
2 files changed, 198 insertions(+), 185 deletions(-)
diff --git a/player.c b/player.c
index 70ba36ec..ac332a43 100644
--- a/player.c
+++ b/player.c
@@ -1760,27 +1760,25 @@ void *player_thread_func(void *arg) {
// successive rtptimes, at worst
conn->output_sample_ratio = config.output_rate / conn->input_rate;
-
-
-// Sign extending rtptime calculations to 64 bit is needed from time to time.
-// The standard rtptime is unsigned 32 bits,
-// so you can do modulo 2^32 difference calculations
-// and get a signed result simply by typing the result as a signed 32-bit number.
+ // Sign extending rtptime calculations to 64 bit is needed from time to time.
-// So long as you can be sure the numbers are within 2^31 of each other,
-// the sign of the result calculated in this way indicates the order of the operands.
-// For example, if you subtract a from b and the result is positive, you can conclude
-// b is the same as or comes after a in module 2^32 order.
+ // The standard rtptime is unsigned 32 bits,
+ // so you can do modulo 2^32 difference calculations
+ // and get a signed result simply by typing the result as a signed 32-bit number.
-// We want to do the same with the rtptime calculations for multiples of
-// the rtptimes (1, 2, 4 or 8 times), and we want to do this in signed 64-bit/
-// Therefore we need to sign extend these modulo 2^32, 2^33, 2^34, or 2^35 bit unsigned
-// numbers on the same basis.
+ // So long as you can be sure the numbers are within 2^31 of each other,
+ // the sign of the result calculated in this way indicates the order of the operands.
+ // For example, if you subtract a from b and the result is positive, you can conclude
+ // b is the same as or comes after a in module 2^32 order.
-// That is what the output_rtptime_sign_bit, output_rtptime_mask, output_rtptime_mask_not and
-// output_rtptime_sign_mask are for -- see later, calculating the sync error.
+ // We want to do the same with the rtptime calculations for multiples of
+ // the rtptimes (1, 2, 4 or 8 times), and we want to do this in signed 64-bit/
+ // Therefore we need to sign extend these modulo 2^32, 2^33, 2^34, or 2^35 bit unsigned
+ // numbers on the same basis.
+ // That is what the output_rtptime_sign_bit, output_rtptime_mask, output_rtptime_mask_not and
+ // output_rtptime_sign_mask are for -- see later, calculating the sync error.
int output_rtptime_sign_bit;
switch (conn->output_sample_ratio) {
@@ -2022,7 +2020,6 @@ void *player_thread_func(void *arg) {
}
#endif
-
#ifdef CONFIG_AIRPLAY_2
if (conn->timing_type == ts_ntp) {
#endif
@@ -2940,45 +2937,46 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
// if it's both, we haven't decided whether hw or sw should be on top
// we have to consider the settings ignore_volume_control and mute.
- if (config.ignore_volume_control == 0) {
- if (airplay_volume == -144.0) {
+ if (airplay_volume == -144.0) {
- if ((config.output->mute) && (config.output->mute(1) == 0))
- debug(2,
- "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
- "hardware mute is enabled.",
- volume_mode, airplay_volume);
- else {
- conn->software_mute_enabled = 1;
- debug(2,
- "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
- "software mute is enabled.",
- volume_mode, airplay_volume);
- }
+ if ((config.output->mute) && (config.output->mute(1) == 0))
+ debug(2,
+ "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
+ "hardware mute is enabled.",
+ volume_mode, airplay_volume);
+ else {
+ conn->software_mute_enabled = 1;
+ debug(2,
+ "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
+ "software mute is enabled.",
+ volume_mode, airplay_volume);
+ }
+
+ } else {
+ int32_t max_db = 0, min_db = 0;
+ switch (volume_mode) {
+ case vol_hw_only:
+ max_db = hw_max_db;
+ min_db = hw_min_db;
+ break;
+ case vol_sw_only:
+ max_db = sw_max_db;
+ min_db = sw_min_db;
+ break;
+ case vol_both:
+ // debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db,
+ // sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db));
+ max_db =
+ (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested
+ min_db = 0;
+ break;
+ default:
+ debug(1, "player_volume_without_notification: error: not in a volume mode");
+ break;
+ }
+ double scaled_attenuation = max_db;
+ if (config.ignore_volume_control == 0) {
- } else {
- int32_t max_db = 0, min_db = 0;
- switch (volume_mode) {
- case vol_hw_only:
- max_db = hw_max_db;
- min_db = hw_min_db;
- break;
- case vol_sw_only:
- max_db = sw_max_db;
- min_db = sw_min_db;
- break;
- case vol_both:
- // debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db,
- // sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db));
- max_db =
- (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested
- min_db = 0;
- break;
- default:
- debug(1, "player_volume_without_notification: error: not in a volume mode");
- break;
- }
- double scaled_attenuation = 0.0;
if (config.volume_control_profile == VCP_standard)
scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points
else if (config.volume_control_profile == VCP_flat)
@@ -2986,116 +2984,122 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points
else
debug(1, "player_volume_without_notification: unrecognised volume control profile");
-
- // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward.
- double hardware_attenuation = 0.0;
- double software_attenuation = 0.0;
-
- switch (volume_mode) {
- case vol_hw_only:
- hardware_attenuation = scaled_attenuation;
- break;
- case vol_sw_only:
- software_attenuation = scaled_attenuation;
- break;
- case vol_both:
- // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers
- // if we give the hw priority, that means when lowering the volume, set the hw volume to its
- // lowest
- // before using the sw attenuation.
- // similarly, if we give the sw priority, that means when lowering the volume, set the sw
- // volume to its lowest
- // before using the hw attenuation.
- // one imagines that hw priority is likely to be much better
- // if (config.volume_range_hw_priority) {
- if (config.volume_range_hw_priority != 0) {
- // hw priority
- if ((sw_max_db - sw_min_db) > scaled_attenuation) {
- software_attenuation = sw_min_db + scaled_attenuation;
- hardware_attenuation = hw_min_db;
- } else {
- software_attenuation = sw_max_db;
- hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db);
- }
- } else {
- // sw priority
- if ((hw_max_db - hw_min_db) > scaled_attenuation) {
- hardware_attenuation = hw_min_db + scaled_attenuation;
- software_attenuation = sw_min_db;
- } else {
- hardware_attenuation = hw_max_db;
- software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db);
- }
- }
- break;
- default:
- debug(1, "player_volume_without_notification: error: not in a volume mode");
- break;
- }
-
- if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) {
- config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value
- // debug(1,"Hardware attenuation set to %f for airplay volume of
- // %f.",hardware_attenuation,airplay_volume);
- if (volume_mode == vol_hw_only)
- conn->fix_volume = 0x10000;
- }
-
- if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) {
- double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000);
- // debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of
- // %f",software_attenuation,temp_fix_volume,airplay_volume);
-
- conn->fix_volume = temp_fix_volume;
-
- // if (config.loudness)
- loudness_set_volume(software_attenuation / 100);
- }
-
- if (config.logOutputLevel) {
- inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
- }
-
-#ifdef CONFIG_METADATA
- // here, send the 'pvol' metadata message when the airplay volume information
- // is being used by shairport sync to control the output volume
- char dv[128];
- memset(dv, 0, 128);
- if (volume_mode == vol_both) {
- // normalise the maximum output to the hardware device's max output
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume,
- (scaled_attenuation - max_db + hw_max_db) / 100.0,
- (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
- } else {
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
- min_db / 100.0, max_db / 100.0);
- }
- send_ssnc_metadata('pvol', dv, strlen(dv), 1);
-#endif
-
- if (config.output->mute)
- config.output->mute(0);
- conn->software_mute_enabled = 0;
-
- debug(2,
- "player_volume_without_notification: volume mode is %d, airplay volume is %f, "
- "software_attenuation: %f, hardware_attenuation: %f, muting "
- "is disabled.",
- volume_mode, airplay_volume, software_attenuation, hardware_attenuation);
}
- }
+ // so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward.
+ double hardware_attenuation = 0.0;
+ double software_attenuation = 0.0;
+
+ switch (volume_mode) {
+ case vol_hw_only:
+ hardware_attenuation = scaled_attenuation;
+ break;
+ case vol_sw_only:
+ software_attenuation = scaled_attenuation;
+ break;
+ case vol_both:
+ // here, we now the attenuation required, so we have to apportion it to the sw and hw mixers
+ // if we give the hw priority, that means when lowering the volume, set the hw volume to its
+ // lowest
+ // before using the sw attenuation.
+ // similarly, if we give the sw priority, that means when lowering the volume, set the sw
+ // volume to its lowest
+ // before using the hw attenuation.
+ // one imagines that hw priority is likely to be much better
+ // if (config.volume_range_hw_priority) {
+ if (config.volume_range_hw_priority != 0) {
+ // hw priority
+ if ((sw_max_db - sw_min_db) > scaled_attenuation) {
+ software_attenuation = sw_min_db + scaled_attenuation;
+ hardware_attenuation = hw_min_db;
+ } else {
+ software_attenuation = sw_max_db;
+ hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db);
+ }
+ } else {
+ // sw priority
+ if ((hw_max_db - hw_min_db) > scaled_attenuation) {
+ hardware_attenuation = hw_min_db + scaled_attenuation;
+ software_attenuation = sw_min_db;
+ } else {
+ hardware_attenuation = hw_max_db;
+ software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db);
+ }
+ }
+ break;
+ default:
+ debug(1, "player_volume_without_notification: error: not in a volume mode");
+ break;
+ }
+
+ if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) {
+ config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value
+ // debug(1,"Hardware attenuation set to %f for airplay volume of
+ // %f.",hardware_attenuation,airplay_volume);
+ if (volume_mode == vol_hw_only)
+ conn->fix_volume = 0x10000;
+ }
+
+ if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) {
+ double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000);
+
+ if (config.ignore_volume_control == 0)
+ debug(2, "Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of %f",
+ software_attenuation, temp_fix_volume, airplay_volume);
+ else
+ debug(2, "Software attenuation set to %f, i.e %f out of 65,536. Volume control is ignored.",
+ software_attenuation, temp_fix_volume);
+
+ conn->fix_volume = temp_fix_volume;
+
+ // if (config.loudness)
+ loudness_set_volume(software_attenuation / 100);
+ }
+
+ if (config.logOutputLevel) {
+ inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
+ }
#ifdef CONFIG_METADATA
- else {
// here, send the 'pvol' metadata message when the airplay volume information
// is being used by shairport sync to control the output volume
char dv[128];
memset(dv, 0, 128);
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
+ if (volume_mode == vol_both) {
+ // normalise the maximum output to the hardware device's max output
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume,
+ (scaled_attenuation - max_db + hw_max_db) / 100.0,
+ (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
+ } else {
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
+ min_db / 100.0, max_db / 100.0);
+ }
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
- }
#endif
+ if (config.output->mute)
+ config.output->mute(0);
+ conn->software_mute_enabled = 0;
+
+ debug(2,
+ "player_volume_without_notification: volume mode is %d, airplay volume is %f, "
+ "software_attenuation: %f, hardware_attenuation: %f, muting "
+ "is disabled.",
+ volume_mode, airplay_volume, software_attenuation, hardware_attenuation);
+ }
+ /*
+ }
+
+ #ifdef CONFIG_METADATA
+ else {
+ // here, send the 'pvol' metadata message when the airplay volume information
+ // is being used by shairport sync to control the output volume
+ char dv[128];
+ memset(dv, 0, 128);
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
+ send_ssnc_metadata('pvol', dv, strlen(dv), 1);
+ }
+ #endif
+ */
// here, store the volume for possible use in the future
config.airplay_volume = airplay_volume;
debug_mutex_unlock(&conn->volume_control_mutex, 3);
diff --git a/rtsp.c b/rtsp.c
index ba09ce15..7e6ff7cf 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -50,8 +50,8 @@
#include
-#include "config.h"
#include "activity_monitor.h"
+#include "config.h"
#ifdef CONFIG_OPENSSL
#include
@@ -1560,10 +1560,9 @@ struct pairings {
uint8_t public_key[32];
struct pairings *next;
-} *pairings;
+} * pairings;
-static struct pairings * pairing_find(const char *device_id)
-{
+static struct pairings *pairing_find(const char *device_id) {
for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
if (strcmp(device_id, pairing->device_id) == 0)
return pairing;
@@ -1595,7 +1594,8 @@ static void pairing_remove(struct pairings *pairing) {
free(pairing);
}
-static int pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg __attribute__((unused))) {
+static int pairing_add_cb(uint8_t public_key[32], const char *device_id,
+ void *cb_arg __attribute__((unused))) {
debug(1, "pair-add cb for %s", device_id);
struct pairings *pairing = pairing_find(device_id);
@@ -1608,7 +1608,8 @@ static int pairing_add_cb(uint8_t public_key[32], const char *device_id, void *c
return 0;
}
-static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id, void *cb_arg __attribute__((unused))) {
+static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id,
+ void *cb_arg __attribute__((unused))) {
debug(1, "pair-remove cb for %s", device_id);
struct pairings *pairing = pairing_find(device_id);
@@ -1621,7 +1622,8 @@ static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), con
return 0;
}
-static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg __attribute__((unused))) {
+static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg,
+ void *cb_arg __attribute__((unused))) {
debug(1, "pair-list cb");
for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
@@ -1629,10 +1631,12 @@ static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg __a
}
}
-void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) {
+void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
+ rtsp_message *resp) {
uint8_t *body = NULL;
size_t body_len = 0;
- int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL, (const uint8_t *)req->content, req->contentlength);
+ int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL,
+ (const uint8_t *)req->content, req->contentlength);
if (ret < 0) {
debug(1, "pair-add returned an error");
resp->respcode = 451;
@@ -1644,10 +1648,12 @@ void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message
debug_log_rtsp_message(2, "pair-add response", resp);
}
-void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) {
+void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
+ rtsp_message *resp) {
uint8_t *body = NULL;
size_t body_len = 0;
- int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL, (const uint8_t *)req->content, req->contentlength);
+ int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL,
+ (const uint8_t *)req->content, req->contentlength);
if (ret < 0) {
debug(1, "pair-list returned an error");
resp->respcode = 451;
@@ -1659,10 +1665,12 @@ void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message
debug_log_rtsp_message(2, "pair-list response", resp);
}
-void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) {
+void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
+ rtsp_message *resp) {
uint8_t *body = NULL;
size_t body_len = 0;
- int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL, (const uint8_t *)req->content, req->contentlength);
+ int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL,
+ (const uint8_t *)req->content, req->contentlength);
if (ret < 0) {
debug(1, "pair-remove returned an error");
resp->respcode = 451;
@@ -1867,20 +1875,21 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message
}
/*
- Identifier
- 21cc689d-d5de-4814-872c-71d1426b57e0
- Enable_HK_Access_Control
-
- PublicKey
-
- qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ=
-
- Device_Name
- MyDevice
- Access_Control_Level
- 0
+ Identifier
+ 21cc689d-d5de-4814-872c-71d1426b57e0
+ Enable_HK_Access_Control
+
+ PublicKey
+
+ qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ=
+
+ Device_Name
+ MyDevice
+ Access_Control_Level
+ 0
*/
-void handle_configure(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req __attribute__((unused)), rtsp_message *resp) {
+void handle_configure(rtsp_conn_info *conn __attribute__((unused)),
+ rtsp_message *req __attribute__((unused)), rtsp_message *resp) {
uint8_t public_key[32];
pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, config.airplay_device_id);
@@ -1889,7 +1898,8 @@ void handle_configure(rtsp_conn_info *conn __attribute__((unused)), rtsp_message
plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi));
plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1));
- plist_dict_set_item(response_plist, "PublicKey", plist_new_data((const char *)public_key, sizeof(public_key)));
+ plist_dict_set_item(response_plist, "PublicKey",
+ plist_new_data((const char *)public_key, sizeof(public_key)));
plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name));
plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0));
@@ -2238,7 +2248,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
// 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_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);
@@ -2254,7 +2264,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
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);
@@ -4414,7 +4423,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
*p++ = ap1_featuresString;
*p++ = firmware_version;
*p++ = "md=2";
- *p++ = "am=SPS";
+ *p++ = "am=Shairport Sync";
*p++ = "sf=0x4";
*p++ = "tp=UDP";
*p++ = "vn=65537";
@@ -4475,7 +4484,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
*p++ = "acl=0";
*p++ = "rsf=0x0";
*p++ = firmware_version;
- *p++ = "model=SPS";
+ *p++ = "model=Shairport Sync";
char piString[64];
snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
*p++ = piString;
From e26d3edd5c429da2c011eed6dfbf91bf1a7349a7 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 16 Jul 2021 15:12:33 +0100
Subject: [PATCH 122/180] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 0f0202cb..c10e455d 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,11 +1,17 @@
-Version 4.0-dev-182-gc0ee8d1
+Version 4.0-dev-185-g0c02ee2
====
+
#### New Features
- **Home App Integration** An AirPlay 2 build of Shairport Sync now offers limited integration with the Home app. Specifically, a Shairport Sync AirPlay 2 instance can now be added as a speaker to the Home app. Thanks to [ckdo](https://github.com/ckdo) and [ejurgensen](https://github.com/ejurgensen) for figuring this out and for coding it up. It means that some Siri interaction is possible.
- **Automated Docker Builds** Thanks to the work of [Charles Omer](https://github.com/charlesomer), Docker builds on the `development` branch should appear from time to time on the [Docker Hub](https://hub.docker.com/r/mikebrady/shairport-sync).
#### Bug Fixes
- [Crashing bug fix](https://github.com/mikebrady/nqptp/blob/main/RELEASE_NOTES.md) in `nqptp` -- thanks to [ste94pz](https://github.com/ste94pz).
+- Honour the `max_volume_db` setting even when `ignore_volume_control` is true. Note that dithering is enabled if software attenuation is needed.
+
+#### Other Changes
+- Spell out `Shairport Sync` instead of `SPS` in a few places.
+- Clang format some source files.
Version 4.0-dev-153-g9ccde50
====
From 4c9d1f7904c972392843891669439a65c6b35518 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 19 Jul 2021 12:07:14 +0100
Subject: [PATCH 123/180] Make mDNS txt record arrays global and permanent. We
hope to be able to modify them dynamically later.
---
rtsp.c | 140 +++++++++++++++++++++++++++++----------------------------
1 file changed, 71 insertions(+), 69 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index 7e6ff7cf..71e93b05 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -4053,6 +4053,25 @@ void msg_cleanup_function(void *arg) {
msg_free((rtsp_message **)arg);
}
+// mDNS advertisement strings
+
+// Create these strings and then keep them updated.
+// When necessary, update the mDNS service records, using e.g. Avahi
+// from these sources.
+
+char *txt_records[64];
+char firmware_version[64];
+char ap1_featuresString[64];
+char pkString[128];
+#ifdef CONFIG_AIRPLAY_2
+char *secondary_txt_records[64];
+char deviceIdString[64];
+char featuresString[64];
+char statusflagsString[32];
+char piString[64];
+char gidString[64];
+#endif
+
static void *rtsp_conversation_thread_func(void *pconn) {
rtsp_conn_info *conn = pconn;
@@ -4383,19 +4402,12 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
maxfd = sockfd[i];
}
- char **t1; // ap1 test records
- char **t2; // two text records
+ char **t1 = txt_records; // ap1 test records
+ char **t2 = NULL; // possibly two text records
- t1 = NULL;
- t2 = NULL;
-
- // make up a txt record
- char *txt_records[64];
- char **p = txt_records;
- t1 = p; // first set of text records
+ int entry_number = 0;
// make up a firmware version
- char firmware_version[64];
#ifdef CONFIG_USE_GIT_VERSION_STRING
if (git_version_string[0] != '\0')
snprintf(firmware_version, sizeof(firmware_version), "fv=%s", git_version_string);
@@ -4404,96 +4416,86 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
snprintf(firmware_version, sizeof(firmware_version), "fv=%s", PACKAGE_VERSION);
#ifdef CONFIG_AIRPLAY_2
- char ap1_featuresString[64];
uint64_t features_hi = config.airplay_features;
features_hi = (features_hi >> 32) & 0xffffffff;
uint64_t features_lo = config.airplay_features;
features_lo = features_lo & 0xffffffff;
snprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
features_lo, features_hi);
- char pkString[128];
snprintf(pkString, sizeof(pkString), "pk=");
pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="),
config.airplay_device_id);
- // the ap1 text record is different if it is set up for ap2
- *p++ = "cn=0,1";
- *p++ = "da=true";
- *p++ = "et=0,4";
- *p++ = ap1_featuresString;
- *p++ = firmware_version;
- *p++ = "md=2";
- *p++ = "am=Shairport Sync";
- *p++ = "sf=0x4";
- *p++ = "tp=UDP";
- *p++ = "vn=65537";
- *p++ = "vs=366.0";
- *p++ = pkString;
- *p++ = NULL;
+ txt_records[entry_number++] = "cn=0,1";
+ txt_records[entry_number++] = "da=true";
+ txt_records[entry_number++] = "et=0,4";
+ txt_records[entry_number++] = ap1_featuresString;
+ txt_records[entry_number++] = firmware_version;
+ txt_records[entry_number++] = "md=2";
+ txt_records[entry_number++] = "am=Shairport Sync";
+ txt_records[entry_number++] = "sf=0x4";
+ txt_records[entry_number++] = "tp=UDP";
+ txt_records[entry_number++] = "vn=65537";
+ txt_records[entry_number++] = "vs=366.0";
+ txt_records[entry_number++] = pkString;
+ txt_records[entry_number++] = NULL;
#else
// here, just replicate what happens in mdns.h when using those #defines
- *p++ = "sf=0x4";
- *p++ = firmware_version;
- *p++ = "am=ShairportSync";
- *p++ = "vs=105.1";
- *p++ = "tp=TCP,UDP";
- *p++ = "vn=65537";
+ txt_records[entry_number++] = "sf=0x4";
+ txt_records[entry_number++] = firmware_version;
+ txt_records[entry_number++] = "am=ShairportSync";
+ txt_records[entry_number++] = "vs=105.1";
+ txt_records[entry_number++] = "tp=TCP,UDP";
+ txt_records[entry_number++] = "vn=65537";
#ifdef CONFIG_METADATA
if (config.get_coverart == 0)
- *p++ = "md=0,2";
+ txt_records[entry_number++] = "md=0,2";
else
- *p++ = "md=0,1,2";
+ txt_records[entry_number++] = "md=0,1,2";
#endif
- *p++ = "ss=16";
- *p++ = "sr=44100";
- *p++ = "da=true";
- *p++ = "sv=false";
- *p++ = "et=0,1";
- *p++ = "ek=1";
- *p++ = "cn=0,1";
- *p++ = "ch=2";
- *p++ = "txtvers=1";
+ txt_records[entry_number++] = "ss=16";
+ txt_records[entry_number++] = "sr=44100";
+ txt_records[entry_number++] = "da=true";
+ txt_records[entry_number++] = "sv=false";
+ txt_records[entry_number++] = "et=0,1";
+ txt_records[entry_number++] = "ek=1";
+ txt_records[entry_number++] = "cn=0,1";
+ txt_records[entry_number++] = "ch=2";
+ txt_records[entry_number++] = "txtvers=1";
if (config.password == 0)
- *p++ = "pw=false";
+ txt_records[entry_number++] = "pw=false";
else
- *p++ = "pw=true";
-
- *p++ = NULL;
+ txt_records[entry_number++] = "pw=true";
+ txt_records[entry_number++] = NULL;
#endif
#ifdef CONFIG_AIRPLAY_2
// make up a secondary set of text records
- char *secondary_txt_records[64];
- p = secondary_txt_records;
- t2 = p; // second set of text records in AirPlay 2 only
+ t2 = secondary_txt_records; // second set of text records in AirPlay 2 only
+ entry_number = 0;
- *p++ = "srcvers=366.0";
- char deviceIdString[64];
+ secondary_txt_records[entry_number++] = "srcvers=366.0";
snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
- *p++ = deviceIdString;
- char featuresString[64];
+ secondary_txt_records[entry_number++] = deviceIdString;
snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIX64 ",0x%" PRIX64 "",
features_lo, features_hi);
- *p++ = featuresString;
- char statusflagsString[32];
+ secondary_txt_records[entry_number++] = featuresString;
snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
config.airplay_statusflags);
- *p++ = statusflagsString;
- *p++ = "protovers=1.1";
- *p++ = "acl=0";
- *p++ = "rsf=0x0";
- *p++ = firmware_version;
- *p++ = "model=Shairport Sync";
- char piString[64];
+ secondary_txt_records[entry_number++] = statusflagsString;
+ secondary_txt_records[entry_number++] = "protovers=1.1";
+ secondary_txt_records[entry_number++] = "acl=0";
+ secondary_txt_records[entry_number++] = "rsf=0x0";
+ secondary_txt_records[entry_number++] = firmware_version;
+ secondary_txt_records[entry_number++] = "model=Shairport Sync";
snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
- *p++ = piString;
- char gidString[64];
+ secondary_txt_records[entry_number++] = piString;
snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_gid);
- *p++ = gidString;
- *p++ = "gcgl=0";
- *p++ = pkString;
- *p++ = NULL;
+ secondary_txt_records[entry_number++] = gidString;
+ secondary_txt_records[entry_number++] = "gcgl=0";
+ secondary_txt_records[entry_number++] = pkString;
+ secondary_txt_records[entry_number++] = NULL;
#endif
mdns_register(t1, t2);
From 67455c19d65d296d6f353398a8446daf91632d68 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 19 Jul 2021 17:09:52 +0100
Subject: [PATCH 124/180] If using libdaemon logs default to daemonlog,
otherwise to stderr.
---
common.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common.c b/common.c
index fcbf4fb5..5d922b74 100644
--- a/common.c
+++ b/common.c
@@ -231,7 +231,7 @@ void log_to_syslog() {
#ifdef CONFIG_LIBDAEMON
sps_log = daemon_log;
#else
- sps_log = syslog;
+ sps_log = do_sps_log_to_stderr;
#endif
}
From 2ab97d980e3dffba0b1cda8d8cfda9182398bd38 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 19 Jul 2021 17:10:51 +0100
Subject: [PATCH 125/180] add a simple mdns_update
---
mdns.c | 7 +++++++
mdns.h | 2 ++
2 files changed, 9 insertions(+)
diff --git a/mdns.c b/mdns.c
index 4e44e38f..7427dc7e 100644
--- a/mdns.c
+++ b/mdns.c
@@ -109,6 +109,13 @@ void mdns_register(char **txt_records, char **secondary_txt_records) {
mdns_dacp_monitor_start(); // create a dacp monitor thread
}
+void mdns_update(char **txt_records, char **secondary_txt_records) {
+if ((config.mdns) && (config.mdns->mdns_update)) {
+ config.mdns->mdns_update(txt_records, secondary_txt_records);
+ } else
+ debug(1, "Can't mdns_update -- no mdns_update registered.");
+}
+
void mdns_unregister(void) {
mdns_dacp_monitor_stop();
if (config.mdns) {
diff --git a/mdns.h b/mdns.h
index dc6e30d9..d43b9513 100644
--- a/mdns.h
+++ b/mdns.h
@@ -9,6 +9,7 @@ extern int mdns_pid;
void mdns_unregister(void);
void mdns_register(char **txt_records, char **secondary_txt_records);
+void mdns_update(char **txt_records, char **secondary_txt_records);
void mdns_dacp_monitor_start();
void mdns_dacp_monitor_stop(void);
void mdns_dacp_monitor_set_id(const char *dacp_id);
@@ -19,6 +20,7 @@ typedef struct {
char *name;
int (*mdns_register)(char *ap1name, char *ap2name, int port, char **txt_records,
char **secondary_txt_records);
+ int (*mdns_update)(char **txt_records, char **secondary_txt_records);
void (*mdns_unregister)(void);
void (*mdns_dacp_monitor_start)();
void (*mdns_dacp_monitor_set_id)(const char *);
From 6d781c8c193813d71ba9eae652fc056bf4051439 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 19 Jul 2021 17:11:59 +0100
Subject: [PATCH 126/180] By default, log to daemonlog/stderr
---
shairport.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/shairport.c b/shairport.c
index 0f7f141f..2de86a9d 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1493,6 +1493,8 @@ int main(int argc, char **argv) {
usage(argv[0]);
exit(EXIT_SUCCESS);
}
+
+ log_to_syslog();
#ifdef CONFIG_LIBDAEMON
pid = getpid();
From 9748297737580643042fc30760079e61d7bf3151 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 19 Jul 2021 17:13:27 +0100
Subject: [PATCH 127/180] Pick up the DACP-ID for AP2 streams. Move the mdns
txt string globals around a bit.
---
rtsp.c | 75 ++++++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 55 insertions(+), 20 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index 71e93b05..cead4378 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -92,6 +92,27 @@
#include "ptp-utilities.h"
#endif
+#include "mdns.h"
+
+// mDNS advertisement strings
+
+// Create these strings and then keep them updated.
+// When necessary, update the mDNS service records, using e.g. Avahi
+// from these sources.
+
+char *txt_records[64];
+char firmware_version[64];
+char ap1_featuresString[64];
+char pkString[128];
+#ifdef CONFIG_AIRPLAY_2
+char *secondary_txt_records[64];
+char deviceIdString[64];
+char featuresString[64];
+char statusflagsString[32];
+char piString[64];
+char gidString[64];
+#endif
+
#define METADATA_SNDBUF (4 * 1024 * 1024)
enum rtsp_read_request_response {
@@ -158,6 +179,14 @@ typedef struct {
int respcode;
} rtsp_message;
+#ifdef CONFIG_AIRPLAY_2
+void mdns_update_flags(uint32_t flags) {
+ snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
+ flags);
+ mdns_update(NULL, secondary_txt_records);
+}
+#endif
+
#ifdef CONFIG_METADATA
typedef struct {
uint32_t type;
@@ -2168,6 +2197,26 @@ 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;
plist_get_data_val(item, (char **)&conn->session_key, &item_value);
+
+ // get the DACP-ID for remote control stuff
+
+ char *ar = msg_get_header(req, "DACP-ID");
+ if (ar) {
+ debug(1, "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");
@@ -2224,6 +2273,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
ptp_send_control_message_string(conn->ap2_timing_peer_list_message);
else
debug(1, "No timing peer list!");
+ //config.airplay_statusflags |= 1 << 10; // ReceiverSessionIsActive
+ //mdns_update_flags(config.airplay_statusflags);
activity_monitor_signify_activity(1);
player_prepare_to_play(conn);
player_play(conn);
@@ -2266,14 +2317,16 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
// this should be cancelled by an activity_monitor_signify_activity(1)
// call in the SETRATEANCHORI handler, which should come up right away
+ //config.airplay_statusflags |= 1 << 10; // ReceiverSessionIsActive
+ //mdns_update_flags(config.airplay_statusflags);
activity_monitor_signify_activity(0);
-
player_play(conn);
conn->rtp_running = 1; // hack!
} break;
default:
debug(1, "Unhandled stream type %" PRIu64 ".", 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));
@@ -4053,25 +4106,6 @@ void msg_cleanup_function(void *arg) {
msg_free((rtsp_message **)arg);
}
-// mDNS advertisement strings
-
-// Create these strings and then keep them updated.
-// When necessary, update the mDNS service records, using e.g. Avahi
-// from these sources.
-
-char *txt_records[64];
-char firmware_version[64];
-char ap1_featuresString[64];
-char pkString[128];
-#ifdef CONFIG_AIRPLAY_2
-char *secondary_txt_records[64];
-char deviceIdString[64];
-char featuresString[64];
-char statusflagsString[32];
-char piString[64];
-char gidString[64];
-#endif
-
static void *rtsp_conversation_thread_func(void *pconn) {
rtsp_conn_info *conn = pconn;
@@ -4483,6 +4517,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
secondary_txt_records[entry_number++] = featuresString;
snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
config.airplay_statusflags);
+
secondary_txt_records[entry_number++] = statusflagsString;
secondary_txt_records[entry_number++] = "protovers=1.1";
secondary_txt_records[entry_number++] = "acl=0";
From 2e4156d39ff633c185f205203afdac97ea0b2b01 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 19 Jul 2021 17:16:37 +0100
Subject: [PATCH 128/180] Add the ability to update the ap1 and ap2 strings
dynamically
---
mdns_avahi.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/mdns_avahi.c b/mdns_avahi.c
index bf24ddba..9b53a539 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -104,7 +104,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
while (*dacpid == '0')
dacpid++; // skip any leading zeroes
if (strcmp(dacpid, dbs->dacp_id) == 0) {
- debug(3, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
+ debug(1, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
#ifdef CONFIG_DACP_CLIENT
dacp_monitor_port_update_callback(dacpid, port);
#endif
@@ -318,6 +318,50 @@ static void client_callback(AvahiClient *c, AvahiClientState state,
}
}
+static int avahi_update(char **txt_records,
+ char **secondary_txt_records) {
+ // debug(1, "avahi_update.");
+
+/*
+ service_name = strdup(ap1name);
+ if (ap2name != NULL)
+ ap2_service_name = strdup(ap2name);
+ port = srvport;
+*/
+ int err = 0;
+ AvahiIfIndex selected_interface;
+ if (config.interface != NULL)
+ selected_interface = config.interface_index;
+ else
+ selected_interface = AVAHI_IF_UNSPEC;
+
+ if (txt_records != NULL) {
+ if (text_record_string_list)
+ avahi_string_list_free(text_record_string_list);
+ text_record_string_list =
+ avahi_string_list_new_from_array((const char **)txt_records, -1);
+ err = avahi_entry_group_update_service_txt_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
+ service_name, config.regtype, NULL,
+ text_record_string_list);
+ if (err != 0)
+ debug(1,"avahi_update error updating primary txt records.");
+ }
+
+ if (secondary_txt_records != NULL) {
+ if (ap2_text_record_string_list)
+ avahi_string_list_free(ap2_text_record_string_list);
+ ap2_text_record_string_list =
+ avahi_string_list_new_from_array((const char **)secondary_txt_records, -1);
+ err = avahi_entry_group_update_service_txt_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
+ ap2_service_name, config.regtype2, NULL,
+ ap2_text_record_string_list);
+ if (err != 0)
+ debug(1,"avahi_update error updating secondary txt records.");
+ }
+ return 0;
+}
+
+
static int avahi_register(char *ap1name, char *ap2name, int srvport, char **txt_records,
char **secondary_txt_records) {
// debug(1, "avahi_register.");
@@ -429,6 +473,7 @@ void avahi_dacp_monitor_set_id(const char *dacp_id) {
avahi_strerror(avahi_client_errno(client)));
}
avahi_threaded_poll_unlock(tpoll);
+ debug(1,"dacp_monitor for \"%s\"", dacp_id);
} else {
warn("avahi_dacp_set_id: can not allocate a dacp_id string in dacp_browser_struct.");
}
@@ -451,6 +496,7 @@ void avahi_dacp_monitor_stop() {
mdns_backend mdns_avahi = {.name = "avahi",
.mdns_register = avahi_register,
+ .mdns_update = avahi_update,
.mdns_unregister = avahi_unregister,
.mdns_dacp_monitor_start = avahi_dacp_monitor_start,
.mdns_dacp_monitor_set_id = avahi_dacp_monitor_set_id,
From 4e6f1e5e659ed2e4db0f39629e7caadd74508153 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 19 Jul 2021 21:51:47 +0100
Subject: [PATCH 129/180] Pick up active Remote information as well as DACP-ID.
---
rtsp.c | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index cead4378..425bd71f 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -2198,9 +2198,30 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
uint64_t item_value = 0;
plist_get_data_val(item, (char **)&conn->session_key, &item_value);
- // get the DACP-ID for remote control stuff
+ // get the DACP-ID and Active Remote for remote control stuff
- char *ar = msg_get_header(req, "DACP-ID");
+
+ char *ar = msg_get_header(req, "Active-Remote");
+ if (ar) {
+ debug(1, "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(1, "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
From c4b5e0928f0abcc9fbc8f16b180ec6f28f5bdb56 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 22 Jul 2021 11:01:53 +0100
Subject: [PATCH 130/180] Use a local copy of the nqptp shared memory interface
description file.
---
nqptp-shm-structures.h | 48 ++++++++++++++++++++++++++++++++++++++++++
ptp-utilities.c | 2 +-
2 files changed, 49 insertions(+), 1 deletion(-)
create mode 100644 nqptp-shm-structures.h
diff --git a/nqptp-shm-structures.h b/nqptp-shm-structures.h
new file mode 100644
index 00000000..36c8a813
--- /dev/null
+++ b/nqptp-shm-structures.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp).
+ * Copyright (c) 2021 Mike Brady.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Commercial licensing is also available.
+ */
+
+#ifndef NQPTP_SHM_STRUCTURES_H
+#define NQPTP_SHM_STRUCTURES_H
+
+#define STORAGE_ID "/nqptp"
+#define MAX_CLOCKS 32
+#define NQPTP_SHM_STRUCTURES_VERSION 5
+#define NQPTP_CONTROL_PORT 9000
+
+// the control port will accept a UDP packet with the first letter being:
+// "T", followed by a space and then a space-delimited
+// list of ip numbers, either IPv4 or IPv6
+// the whole not to exceed 4096 characters in total
+// The IPs will become the new list of timing peers, replacing any previous
+
+#include
+#include
+
+struct shm_structure {
+ pthread_mutex_t shm_mutex; // for safely accessing the structure
+ uint16_t version; // check this is equal to NQPTP_SHM_STRUCTURES_VERSION
+ uint32_t flags; // unused
+ uint64_t master_clock_id; // the current master clock
+ char master_clock_ip[64]; // where it's coming from
+ uint64_t local_time; // the time when the offset was calculated
+ uint64_t local_to_master_time_offset; // add this to the local time to get master clock time
+ uint64_t master_clock_start_time; // this is when the master clock became master
+};
+
+#endif
diff --git a/ptp-utilities.c b/ptp-utilities.c
index a9cf614f..0f80e913 100644
--- a/ptp-utilities.c
+++ b/ptp-utilities.c
@@ -41,7 +41,7 @@
#include
#include
-#include "../nqptp/nqptp-shm-structures.h"
+#include "nqptp-shm-structures.h"
#include "ptp-utilities.h"
int shm_fd;
From a85a9ac0e466076e05f1c8f90f86cbbe4c25a27e Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 22 Jul 2021 11:03:32 +0100
Subject: [PATCH 131/180] Add a dummy handler for the POST /feedback request.
---
rtsp.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index 425bd71f..d10f0905 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1939,6 +1939,10 @@ void handle_configure(rtsp_conn_info *conn __attribute__((unused)),
debug_log_rtsp_message(2, "POST /configure response:", resp);
}
+void handle_feedback(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+
+}
+
void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
if (strcmp(req->path, "/pair-setup") == 0) {
@@ -1955,11 +1959,12 @@ void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
handle_fp_setup(conn, req, resp);
} else if (strcmp(req->path, "/configure") == 0) {
handle_configure(conn, req, resp);
+ } else if (strcmp(req->path, "/feedback") == 0) {
+ handle_feedback(conn, req, resp);
} else {
- debug(3, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path,
+ debug(1, "Connection %d: Unhandled POST %s Content-Length %d", conn->connection_number, req->path,
req->contentlength);
debug_log_rtsp_message(2, "POST request", req);
- debug(3, "Unhandled POST, path \"%s\".", req->path);
}
}
@@ -2246,7 +2251,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
switch (item_value) {
case 96: {
- debug(1, "Connection %d. Realtime Audio Stream Detected.", conn->connection_number);
+ debug(2, "Connection %d. Realtime Audio Stream Detected.", 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;
@@ -2303,7 +2308,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->rtp_running = 1; // hack!
} break;
case 103: {
- debug(1, "Connection %d. Buffered Audio Stream Detected.", conn->connection_number);
+ debug(2, "Connection %d. Buffered Audio Stream Detected.", 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;
@@ -2360,6 +2365,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
"SETUP on Connection %d: No \"streams\" array has been found -- create an event thread "
"and open a TCP port.",
conn->connection_number);
+ debug_log_rtsp_message(2, "SETUP on Connection %d: No \"streams\" array has been found.", req);
// first, get and retain the incoming plist.
if (conn->client_setup_plist) {
plist_free(conn->client_setup_plist);
@@ -4172,7 +4178,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
resp = msg_init();
pthread_cleanup_push(msg_cleanup_function, (void *)&resp);
resp->respcode = 400;
-
+/*
if (strcmp(req->method, "OPTIONS") !=
0) // the options message is very common, so don't log it until level 3
debug_level = 2;
@@ -4180,6 +4186,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
"Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number,
req->method),
debug_print_msg_headers(debug_level, req);
+*/
apple_challenge(conn->fd, req, resp);
hdr = msg_get_header(req, "CSeq");
From 707204e7e81d38186282d9e6b6fec5cbc12e19ca Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 22 Jul 2021 12:43:21 +0100
Subject: [PATCH 132/180] Quieten a few temporary wanrings.
---
rtsp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rtsp.c b/rtsp.c
index d10f0905..e087fbf1 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1939,7 +1939,7 @@ void handle_configure(rtsp_conn_info *conn __attribute__((unused)),
debug_log_rtsp_message(2, "POST /configure response:", resp);
}
-void handle_feedback(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+void handle_feedback(__attribute__((unused)) rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, __attribute__((unused)) rtsp_message *resp) {
}
From 4090b2d8fb383ad8873d2c4538da9de9a0a26136 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 22 Jul 2021 13:23:57 +0100
Subject: [PATCH 133/180] Avoid repreatedly asking Avahi to monitor for the
same DACP-ID. Quieten some debug messages.
---
mdns_avahi.c | 94 +++++++++++++++++++--------------------
rtp.c | 121 +++++++++++++++++++++++++++++----------------------
rtsp.c | 53 +++++++++++-----------
3 files changed, 142 insertions(+), 126 deletions(-)
diff --git a/mdns_avahi.c b/mdns_avahi.c
index 9b53a539..c4568078 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -104,7 +104,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
while (*dacpid == '0')
dacpid++; // skip any leading zeroes
if (strcmp(dacpid, dbs->dacp_id) == 0) {
- debug(1, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
+ debug(3, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
#ifdef CONFIG_DACP_CLIENT
dacp_monitor_port_update_callback(dacpid, port);
#endif
@@ -318,50 +318,47 @@ static void client_callback(AvahiClient *c, AvahiClientState state,
}
}
-static int avahi_update(char **txt_records,
- char **secondary_txt_records) {
+static int avahi_update(char **txt_records, char **secondary_txt_records) {
// debug(1, "avahi_update.");
-/*
- service_name = strdup(ap1name);
- if (ap2name != NULL)
- ap2_service_name = strdup(ap2name);
- port = srvport;
-*/
+ /*
+ service_name = strdup(ap1name);
+ if (ap2name != NULL)
+ ap2_service_name = strdup(ap2name);
+ port = srvport;
+ */
int err = 0;
AvahiIfIndex selected_interface;
- if (config.interface != NULL)
- selected_interface = config.interface_index;
- else
- selected_interface = AVAHI_IF_UNSPEC;
+ if (config.interface != NULL)
+ selected_interface = config.interface_index;
+ else
+ selected_interface = AVAHI_IF_UNSPEC;
if (txt_records != NULL) {
if (text_record_string_list)
avahi_string_list_free(text_record_string_list);
- text_record_string_list =
- avahi_string_list_new_from_array((const char **)txt_records, -1);
- err = avahi_entry_group_update_service_txt_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
- service_name, config.regtype, NULL,
- text_record_string_list);
+ text_record_string_list = avahi_string_list_new_from_array((const char **)txt_records, -1);
+ err = avahi_entry_group_update_service_txt_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC,
+ 0, service_name, config.regtype, NULL,
+ text_record_string_list);
if (err != 0)
- debug(1,"avahi_update error updating primary txt records.");
+ debug(1, "avahi_update error updating primary txt records.");
}
if (secondary_txt_records != NULL) {
if (ap2_text_record_string_list)
avahi_string_list_free(ap2_text_record_string_list);
ap2_text_record_string_list =
- avahi_string_list_new_from_array((const char **)secondary_txt_records, -1);
- err = avahi_entry_group_update_service_txt_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
- ap2_service_name, config.regtype2, NULL,
- ap2_text_record_string_list);
+ avahi_string_list_new_from_array((const char **)secondary_txt_records, -1);
+ err = avahi_entry_group_update_service_txt_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC,
+ 0, ap2_service_name, config.regtype2, NULL,
+ ap2_text_record_string_list);
if (err != 0)
- debug(1,"avahi_update error updating secondary txt records.");
+ debug(1, "avahi_update error updating secondary txt records.");
}
return 0;
}
-
static int avahi_register(char *ap1name, char *ap2name, int srvport, char **txt_records,
char **secondary_txt_records) {
// debug(1, "avahi_register.");
@@ -446,7 +443,7 @@ static void avahi_unregister(void) {
void avahi_dacp_monitor_start(void) {
// debug(1, "avahi_dacp_monitor_start.");
memset((void *)&private_dbs, 0, sizeof(dacp_browser_struct));
- debug(1, "avahi_dacp_monitor_start Avahi DACP monitor successfully started");
+ debug(2, "avahi_dacp_monitor_start Avahi DACP monitor successfully started");
return;
}
@@ -454,28 +451,33 @@ void avahi_dacp_monitor_set_id(const char *dacp_id) {
// debug(1, "avahi_dacp_monitor_set_id: Search for DACP ID \"%s\".", t);
dacp_browser_struct *dbs = &private_dbs;
- if (dbs->dacp_id)
- free(dbs->dacp_id);
- if (dacp_id == NULL)
- dbs->dacp_id = NULL;
- else {
- char *t = strdup(dacp_id);
- if (t) {
- dbs->dacp_id = t;
- avahi_threaded_poll_lock(tpoll);
- if (dbs->service_browser)
- avahi_service_browser_free(dbs->service_browser);
+ if (((dbs->dacp_id) && (dacp_id) && (strcmp(dbs->dacp_id, dacp_id) == 0)) ||
+ ((dbs->dacp_id == NULL) && (dacp_id == NULL))) {
+ debug(3, "no change...");
+ } else {
+ if (dbs->dacp_id)
+ free(dbs->dacp_id);
+ if (dacp_id == NULL)
+ dbs->dacp_id = NULL;
+ else {
+ char *t = strdup(dacp_id);
+ if (t) {
+ dbs->dacp_id = t;
+ avahi_threaded_poll_lock(tpoll);
+ if (dbs->service_browser)
+ avahi_service_browser_free(dbs->service_browser);
- if (!(dbs->service_browser =
- avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_dacp._tcp",
- NULL, 0, browse_callback, (void *)dbs))) {
- warn("failed to create avahi service browser: %s\n",
- avahi_strerror(avahi_client_errno(client)));
+ if (!(dbs->service_browser =
+ avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ "_dacp._tcp", NULL, 0, browse_callback, (void *)dbs))) {
+ warn("failed to create avahi service browser: %s\n",
+ avahi_strerror(avahi_client_errno(client)));
+ }
+ avahi_threaded_poll_unlock(tpoll);
+ debug(2, "dacp_monitor for \"%s\"", dacp_id);
+ } else {
+ warn("avahi_dacp_set_id: can not allocate a dacp_id string in dacp_browser_struct.");
}
- avahi_threaded_poll_unlock(tpoll);
- debug(1,"dacp_monitor for \"%s\"", dacp_id);
- } else {
- warn("avahi_dacp_set_id: can not allocate a dacp_id string in dacp_browser_struct.");
}
}
}
diff --git a/rtp.c b/rtp.c
index 1b2cffad..e074e113 100644
--- a/rtp.c
+++ b/rtp.c
@@ -290,8 +290,8 @@ void *rtp_control_receiver(void *arg) {
obfp += 2;
};
*obfp = 0;
-
-
+
+
// get raw timestamp information
// I think that a good way to understand these timestamps is that
// (1) the rtlt below is the timestamp of the frame that should be playing at the
@@ -302,19 +302,19 @@ void *rtp_control_receiver(void *arg) {
// Thus, (3) the latency can be calculated by subtracting the second from the
// first.
// There must be more to it -- there something missing.
-
+
// In addition, it seems that if the value of the short represented by the second
// pair of bytes in the packet is 7
// then an extra time lag is expected to be added, presumably by
// the AirPort Express.
-
+
// Best guess is that this delay is 11,025 frames.
-
+
uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
uint32_t rt = nctohl(&packet[16]); // raw timestamp
-
+
uint32_t fl = nctohs(&packet[2]); //
-
+
debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u,
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
//debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
@@ -1271,18 +1271,21 @@ int long_time_notifcation_done = 0;
int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP,
uint64_t *anchorLocalTime) {
uint64_t actual_clock_id, actual_time_of_sample, actual_offset, start_of_mastership;
- int response = ptp_get_clock_info(&actual_clock_id, &actual_time_of_sample, &actual_offset, &start_of_mastership);
+ int response = ptp_get_clock_info(&actual_clock_id, &actual_time_of_sample, &actual_offset,
+ &start_of_mastership);
if (response == clock_ok) {
uint64_t time_now = get_absolute_time_in_ns();
int64_t time_since_sample = time_now - actual_time_of_sample;
if (time_since_sample > 10000000000) {
if (long_time_notifcation_done == 0) {
- debug(1,"The last PTP timing sample is pretty old: %f seconds.", 0.000000001 * time_since_sample);
+ debug(1, "The last PTP timing sample is pretty old: %f seconds.",
+ 0.000000001 * time_since_sample);
long_time_notifcation_done = 1;
}
} else if ((time_since_sample < 2000000000) && (long_time_notifcation_done != 0)) {
- debug(1,"The last PTP timing sample is no longer too old: %f seconds.", 0.000000001 * time_since_sample);
+ debug(1, "The last PTP timing sample is no longer too old: %f seconds.",
+ 0.000000001 * time_since_sample);
long_time_notifcation_done = 0;
}
@@ -1296,11 +1299,11 @@ int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP,
// using the master clock timing
if (actual_clock_id == conn->anchor_clock) {
- // if the master clock and the anchor clock are the same
- // wait at least this time before using the new master clock
- // note that mastershipo may be backdated
+ // if the master clock and the anchor clock are the same
+ // wait at least this time before using the new master clock
+ // note that mastershipo may be backdated
if (duration_of_mastership < 1500000000) {
- debug(3,"master not old enough yet: %f ms", 0.000001 * duration_of_mastership);
+ debug(3, "master not old enough yet: %f ms", 0.000001 * duration_of_mastership);
response = clock_not_ready;
} else if ((duration_of_mastership > 5000000000) ||
(conn->last_anchor_info_is_valid == 0)) {
@@ -1314,7 +1317,8 @@ int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP,
conn->last_anchor_info_is_valid = 1;
if (conn->anchor_clock_is_new != 0)
debug(1,
- "Connection %d: Clock %" PRIx64 " is now the new anchor clock and master clock. History: %f milliseconds.",
+ "Connection %d: Clock %" PRIx64
+ " is now the new anchor clock and master clock. History: %f milliseconds.",
conn->connection_number, conn->anchor_clock, 0.000001 * duration_of_mastership);
conn->anchor_clock_is_new = 0;
}
@@ -1326,16 +1330,19 @@ int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP,
// so, if the anchor has not changed, it must be that the master clock has changed
if (conn->anchor_clock_is_new != 0)
- debug(1,
+ debug(3,
"Connection %d: Anchor clock has changed to %" PRIx64 ", master clock is: %" PRIx64
". History: %f milliseconds.",
- conn->connection_number, conn->anchor_clock, actual_clock_id, 0.000001 * duration_of_mastership);
+ conn->connection_number, conn->anchor_clock, actual_clock_id,
+ 0.000001 * duration_of_mastership);
if ((conn->last_anchor_info_is_valid != 0) && (conn->anchor_clock_is_new == 0)) {
int64_t time_since_last_update =
get_absolute_time_in_ns() - conn->last_anchor_time_of_update;
if (time_since_last_update > 5000000000) {
- debug(1, "Connection %d: Master clock has changed to %" PRIx64 ". History: %f milliseconds.",
+ debug(1,
+ "Connection %d: Master clock has changed to %" PRIx64
+ ". History: %f milliseconds.",
conn->connection_number, actual_clock_id, 0.000001 * duration_of_mastership);
// here we adjust the time of the anchor rtptime
// we know its local time, so we use the new clocks's offset to
@@ -2157,7 +2164,8 @@ void *rtp_buffered_audio_processor(void *arg) {
av_opt_set_int(swr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(swr, "in_sample_rate", conn->input_rate, 0);
- av_opt_set_int(swr, "out_sample_rate", conn->input_rate, 0); // must match or the timing will be wrong`
+ av_opt_set_int(swr, "out_sample_rate", conn->input_rate,
+ 0); // must match or the timing will be wrong`
av_opt_set_sample_fmt(swr, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
enum AVSampleFormat av_format;
@@ -2280,9 +2288,14 @@ void *rtp_buffered_audio_processor(void *arg) {
if ((blocks_read != 0) && (seq_no >= flushUntilSeq)) {
// we have reached or overshot the flushUntilSeq block
if (flushUntilSeq != seq_no)
- debug(2, "flush request ended with flushUntilSeq %u overshot at %u, flushUntilTS: %u, incoming timestamp: %u.", flushUntilSeq, seq_no, flushUntilTS, timestamp);
+ debug(2,
+ "flush request ended with flushUntilSeq %u overshot at %u, flushUntilTS: %u, "
+ "incoming timestamp: %u.",
+ flushUntilSeq, seq_no, flushUntilTS, timestamp);
else
- debug(2, "flush request ended with flushUntilSeq, flushUntilTS: %u, incoming timestamp: %u", flushUntilSeq, flushUntilTS, timestamp);
+ debug(2,
+ "flush request ended with flushUntilSeq, flushUntilTS: %u, incoming timestamp: %u",
+ flushUntilSeq, flushUntilTS, timestamp);
conn->ap2_flush_requested = 0;
flush_request_active = 0;
flush_newly_requested = 0;
@@ -2301,7 +2314,6 @@ void *rtp_buffered_audio_processor(void *arg) {
if (flush_newly_complete) {
debug(2, "Flush Complete.");
blocks_read_since_flush = 0;
-
}
if (play_newly_stopped != 0)
@@ -2329,9 +2341,10 @@ void *rtp_buffered_audio_processor(void *arg) {
// is there space in the player thread's buffer system?
unsigned int player_buffer_size, player_buffer_occupancy;
get_audio_buffer_size_and_occupancy(&player_buffer_size, &player_buffer_occupancy, conn);
- //debug(1,"player buffer size and occupancy: %u and %u", player_buffer_size, player_buffer_occupancy);
- if (player_buffer_occupancy >
- ((requested_lead_time + 0.4) * conn->input_rate / 352)) { // must be greater than the lead time.
+ // debug(1,"player buffer size and occupancy: %u and %u", player_buffer_size,
+ // player_buffer_occupancy);
+ if (player_buffer_occupancy > ((requested_lead_time + 0.4) * conn->input_rate /
+ 352)) { // must be greater than the lead time.
// if there is enough stuff in the player's buffer, sleep for a while and try again
usleep(1000); // wait for a while
} else {
@@ -2358,26 +2371,26 @@ void *rtp_buffered_audio_processor(void *arg) {
// it seems that some garbage blocks can be left after the flush, so
// only accept them if they have sensible lead times
if ((lead_time_ms < 5000.0) && (lead_time > -1000.0)) {
- // if it's the very first block (thus no priming needed)
- if ((blocks_read == 1) || (blocks_read_since_flush > 3)) {
- if ((lead_time >= (int64_t)(requested_lead_time * 1000000000)) ||
- (streaming_has_started != 0)) {
- if (streaming_has_started == 0)
- debug(
- 2,
- "Connection %d: buffered audio starting frame: %u, lead time: %f seconds.",
- conn->connection_number, pcm_buffer_read_point_rtptime,
- 0.000000001 * lead_time);
- // else {
- // if (expected_rtptime != pcm_buffer_read_point_rtptime)
- // debug(1,"actual rtptime is %u, expected rtptime is %u.",
- // pcm_buffer_read_point_rtptime, expected_rtptime);
- //}
- // expected_rtptime = pcm_buffer_read_point_rtptime + 352;
+ // if it's the very first block (thus no priming needed)
+ if ((blocks_read == 1) || (blocks_read_since_flush > 3)) {
+ if ((lead_time >= (int64_t)(requested_lead_time * 1000000000)) ||
+ (streaming_has_started != 0)) {
+ if (streaming_has_started == 0)
+ debug(2,
+ "Connection %d: buffered audio starting frame: %u, lead time: %f "
+ "seconds.",
+ conn->connection_number, pcm_buffer_read_point_rtptime,
+ 0.000000001 * lead_time);
+ // else {
+ // if (expected_rtptime != pcm_buffer_read_point_rtptime)
+ // debug(1,"actual rtptime is %u, expected rtptime is %u.",
+ // pcm_buffer_read_point_rtptime, expected_rtptime);
+ //}
+ // expected_rtptime = pcm_buffer_read_point_rtptime + 352;
- // this is a diagnostic for introducing a timing error that will force the
- // processing chain to resync
- // clang-format off
+ // this is a diagnostic for introducing a timing error that will force the
+ // processing chain to resync
+ // clang-format off
/*
if ((not_first_time_out == 0) && (blocks_read >= 20)) {
int timing_error = 150;
@@ -2390,21 +2403,23 @@ void *rtp_buffered_audio_processor(void *arg) {
not_first_time_out = 1;
}
*/
- // clang-format on
+ // clang-format on
- player_put_packet(0, 0, pcm_buffer_read_point_rtptime,
- pcm_buffer + pcm_buffer_read_point, 352, conn);
- streaming_has_started++;
+ player_put_packet(0, 0, pcm_buffer_read_point_rtptime,
+ pcm_buffer + pcm_buffer_read_point, 352, conn);
+ streaming_has_started++;
+ }
}
- }
} else {
- debug(2,"Dropping packet %u from block %u with out-of-range lead_time: %.3f seconds.", pcm_buffer_read_point_rtptime, seq_no, 0.001 * lead_time_ms);
+ debug(2,
+ "Dropping packet %u from block %u with out-of-range lead_time: %.3f seconds.",
+ pcm_buffer_read_point_rtptime, seq_no, 0.001 * lead_time_ms);
}
pcm_buffer_read_point_rtptime += 352;
pcm_buffer_read_point += 352 * conn->input_bytes_per_frame;
} else {
- debug(1,"frame to local time error");
+ debug(1, "frame to local time error");
}
} else {
usleep(1000); // wait before asking if play is enabled again
@@ -2488,8 +2503,8 @@ void *rtp_buffered_audio_processor(void *arg) {
// valid
int64_t lead_time = should_be_time - get_absolute_time_in_ns();
debug(1,
- "flush completed to seq: %u, flushUntilTS; %u with rtptime: %u, lead time: 0x%" PRIx64
- " nanoseconds, i.e. %f sec.",
+ "flush completed to seq: %u, flushUntilTS; %u with rtptime: %u, lead time: "
+ "0x%" PRIx64 " nanoseconds, i.e. %f sec.",
seq_no, flushUntilTS, timestamp, lead_time, lead_time * 0.000000001);
} else {
debug(2, "flush completed to seq: %u with rtptime: %u.", seq_no, timestamp);
diff --git a/rtsp.c b/rtsp.c
index e087fbf1..ca5178cd 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -181,8 +181,7 @@ typedef struct {
#ifdef CONFIG_AIRPLAY_2
void mdns_update_flags(uint32_t flags) {
- snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
- flags);
+ snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32, flags);
mdns_update(NULL, secondary_txt_records);
}
#endif
@@ -1939,9 +1938,9 @@ void handle_configure(rtsp_conn_info *conn __attribute__((unused)),
debug_log_rtsp_message(2, "POST /configure response:", resp);
}
-void handle_feedback(__attribute__((unused)) rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, __attribute__((unused)) rtsp_message *resp) {
-
-}
+void handle_feedback(__attribute__((unused)) rtsp_conn_info *conn,
+ __attribute__((unused)) rtsp_message *req,
+ __attribute__((unused)) rtsp_message *resp) {}
void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
@@ -1962,8 +1961,8 @@ void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
} else if (strcmp(req->path, "/feedback") == 0) {
handle_feedback(conn, req, resp);
} else {
- debug(1, "Connection %d: Unhandled POST %s Content-Length %d", conn->connection_number, req->path,
- req->contentlength);
+ debug(2, "Connection %d: Unhandled POST %s Content-Length %d", conn->connection_number,
+ req->path, req->contentlength);
debug_log_rtsp_message(2, "POST request", req);
}
}
@@ -2202,13 +2201,12 @@ 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;
plist_get_data_val(item, (char **)&conn->session_key, &item_value);
-
+
// get the DACP-ID and Active Remote for remote control stuff
-
-
+
char *ar = msg_get_header(req, "Active-Remote");
if (ar) {
- debug(1, "Connection %d: SETUP AP2 -- Active-Remote string seen: \"%s\".",
+ 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
@@ -2225,10 +2223,11 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->dacp_active_remote = NULL;
}
}
-
+
ar = msg_get_header(req, "DACP-ID");
if (ar) {
- debug(1, "Connection %d: SETUP AP2 -- DACP-ID string seen: \"%s\".", conn->connection_number, 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);
@@ -2299,8 +2298,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
ptp_send_control_message_string(conn->ap2_timing_peer_list_message);
else
debug(1, "No timing peer list!");
- //config.airplay_statusflags |= 1 << 10; // ReceiverSessionIsActive
- //mdns_update_flags(config.airplay_statusflags);
+ // config.airplay_statusflags |= 1 << 10; // ReceiverSessionIsActive
+ // mdns_update_flags(config.airplay_statusflags);
activity_monitor_signify_activity(1);
player_prepare_to_play(conn);
player_play(conn);
@@ -2343,8 +2342,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
// this should be cancelled by an activity_monitor_signify_activity(1)
// call in the SETRATEANCHORI handler, which should come up right away
- //config.airplay_statusflags |= 1 << 10; // ReceiverSessionIsActive
- //mdns_update_flags(config.airplay_statusflags);
+ // config.airplay_statusflags |= 1 << 10; // ReceiverSessionIsActive
+ // mdns_update_flags(config.airplay_statusflags);
activity_monitor_signify_activity(0);
player_play(conn);
@@ -4178,15 +4177,15 @@ static void *rtsp_conversation_thread_func(void *pconn) {
resp = msg_init();
pthread_cleanup_push(msg_cleanup_function, (void *)&resp);
resp->respcode = 400;
-/*
- if (strcmp(req->method, "OPTIONS") !=
- 0) // the options message is very common, so don't log it until level 3
- debug_level = 2;
- debug(debug_level,
- "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number,
- req->method),
- debug_print_msg_headers(debug_level, req);
-*/
+ /*
+ if (strcmp(req->method, "OPTIONS") !=
+ 0) // the options message is very common, so don't log it until level 3
+ debug_level = 2;
+ debug(debug_level,
+ "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number,
+ req->method),
+ debug_print_msg_headers(debug_level, req);
+ */
apple_challenge(conn->fd, req, resp);
hdr = msg_get_header(req, "CSeq");
@@ -4545,7 +4544,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
secondary_txt_records[entry_number++] = featuresString;
snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
config.airplay_statusflags);
-
+
secondary_txt_records[entry_number++] = statusflagsString;
secondary_txt_records[entry_number++] = "protovers=1.1";
secondary_txt_records[entry_number++] = "acl=0";
From dd0c73798737c3ce93ddbad63450367a4ab1032d Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 22 Jul 2021 14:30:17 +0100
Subject: [PATCH 134/180] Update CAR INSTALL.md
---
CAR INSTALL.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index a9617b66..e52d8d4a 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -92,6 +92,7 @@ A number of packages to enable the Pi to work as a WiFi base station are needed:
```
Disable both of these services from starting at boot time (this is because we will launch them sequentially later on):
```
+# systemctl unmask hostapd
# systemctl disable hostapd
# systemctl disable isc-dhcp-server
```
From 99f078d478546d87ea46a81da910ab9a136cfe42 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 24 Jul 2021 09:23:02 +0100
Subject: [PATCH 135/180] Simplify UFW Stuff
Make is work for any source, IPv4 or IPv6, add in 7000 for AP2
---
TROUBLESHOOTING.md | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index fd500e60..92d8b969 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -90,15 +90,14 @@ You have installed Shairport Sync successfully, the daemon is running, you can s
You have to allow connections to your Pi from remote devices. To do so, after re-enabling UFW (see last step of the previous section), enter the following commands in shell:
```
-sudo ufw allow from 192.168.1.1/16 to any port 3689 proto tcp
-sudo ufw allow from 192.168.1.1/16 to any port 5353
-sudo ufw allow from 192.168.1.1/16 to any port 5000:5005 proto tcp
-sudo ufw allow from 192.168.1.1/16 to any port 6000:6005 proto udp
-sudo ufw allow from 192.168.1.1/16 to any port 35000:65535 proto udp
+sudo ufw allow 3689/tcp
+sudo ufw allow 5353
+sudo ufw allow 5000/tcp
+sudo ufw allow 7000/tcp
+sudo ufw allow 6000:6009/udp
+sudo ufw allow 35000:65535/udp
```
-You may have to change the IP addresses range depending on your own local network settings.
-
You can check UFW config by typing `sudo ufw status` in shell. Please make sure that UFW is active, especially if you have deactivated it previously for testing purpose.
Run your song from your remote device. Enjoy !
From 8f54828fd707b2bad28b8365009976994061a2c2 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 24 Jul 2021 09:28:12 +0100
Subject: [PATCH 136/180] Update TROUBLESHOOTING.md
---
TROUBLESHOOTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index 92d8b969..1cdac7b8 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -98,7 +98,7 @@ sudo ufw allow 6000:6009/udp
sudo ufw allow 35000:65535/udp
```
-You can check UFW config by typing `sudo ufw status` in shell. Please make sure that UFW is active, especially if you have deactivated it previously for testing purpose.
+You can check UFW config by typing `sudo ufw status` in shell. Please make sure that UFW is active, especially if you have deactivated it previously for testing purposes. Check out the [ufw man pages](http://manpages.ubuntu.com/manpages/man8/ufw.8.html) for more.
Run your song from your remote device. Enjoy !
From e0983421854d2d999d068a9bd1839bee1d760945 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 28 Jul 2021 12:56:55 +0100
Subject: [PATCH 137/180] remove a misleading obsolete comment.
---
common.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common.h b/common.h
index 26e9e17d..4572d198 100644
--- a/common.h
+++ b/common.h
@@ -379,7 +379,7 @@ double flat_vol2attn(double vol, long max_db, long min_db);
// dB), return an attenuation depending on the transfer function
double vol2attn(double vol, long max_db, long min_db);
-// return a monolithic (always increasing) time in nanoseconds
+// return a time in nanoseconds
// uint64_t get_absolute_time_in_fp(void); // obselete
uint64_t get_absolute_time_in_ns(void);
From 90740694ba136c638785ffc57ec1e0b279a5aa4a Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 28 Jul 2021 12:58:20 +0100
Subject: [PATCH 138/180] When an unrecognised SETUP message is received, just
say okay and ignore it. Remove reduncant client_setup_plist from the conn
data structure.
---
player.h | 1 -
rtsp.c | 94 +++++++++++++++++++++++---------------------------------
2 files changed, 39 insertions(+), 56 deletions(-)
diff --git a/player.h b/player.h
index c35e5726..2596eb78 100644
--- a/player.h
+++ b/player.h
@@ -328,7 +328,6 @@ typedef struct {
uint16_t local_realtime_audio_port;
uint16_t local_buffered_audio_port;
- plist_t client_setup_plist;
uint64_t audio_format;
uint64_t compression;
unsigned char *session_key; // needs to be free'd at the end
diff --git a/rtsp.c b/rtsp.c
index ca5178cd..c459dfc9 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -2069,10 +2069,6 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
if (conn->event_socket)
close(conn->event_socket);
- if (conn->client_setup_plist) {
- plist_free(conn->client_setup_plist);
- conn->client_setup_plist = NULL;
- }
debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
}
//} else {
@@ -2162,12 +2158,13 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
// the timing peer info in the accompanying plist
plist_t messagePlist = plist_from_rtsp_content(req);
- plist_t setupResponsePlist = plist_new_dict();
+ plist_t setupResponsePlist = NULL; // we'll create it when we need it
// now see if the incoming plist contains a "streams" array
plist_t streams = plist_dict_get_item(messagePlist, "streams");
if (streams) {
+ setupResponsePlist = plist_new_dict(); // now that we know we need it, create it
debug(2,
"SETUP on Connection %d: A \"streams\" array has been found -- create control and audio "
"threads and ports",
@@ -2250,7 +2247,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
switch (item_value) {
case 96: {
- debug(2, "Connection %d. Realtime Audio Stream Detected.", conn->connection_number);
+ debug(1, "Connection %d. Realtime Audio Stream Detected.", 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;
@@ -2307,7 +2304,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->rtp_running = 1; // hack!
} break;
case 103: {
- debug(2, "Connection %d. Buffered Audio Stream Detected.", conn->connection_number);
+ debug(1, "Connection %d. Buffered Audio Stream Detected.", 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;
@@ -2365,19 +2362,16 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
"and open a TCP port.",
conn->connection_number);
debug_log_rtsp_message(2, "SETUP on Connection %d: No \"streams\" array has been found.", req);
- // first, get and retain the incoming plist.
- if (conn->client_setup_plist) {
- plist_free(conn->client_setup_plist);
- }
- conn->client_setup_plist = messagePlist;
- plist_t addresses = plist_new_array(); // to hold the device's peer interfaces
// now determine which of the present device's IP numbers are in the
// same subnet as any IP numbers in the timing peer info
// provided by the client
- plist_t timing_peer_info = plist_dict_get_item(conn->client_setup_plist, "timingPeerInfo");
+ plist_t timing_peer_info = plist_dict_get_item(messagePlist, "timingPeerInfo");
if (timing_peer_info) {
+ // first, get and retain the incoming plist.
+ setupResponsePlist = plist_new_dict(); // now that we know we need it, create it
+ plist_t addresses = plist_new_array(); // to hold the device's peer interfaces
plist_t addresses_array = plist_dict_get_item(timing_peer_info, "Addresses");
if (addresses_array) {
// iterate through the array of items
@@ -2456,45 +2450,40 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
} else {
debug(1, "Can't find timingPeerInfo addresses");
}
- } else {
- debug_log_rtsp_message(1, "Unrecognised SETUP incoming message", req);
+ // make up the timing peer info list part of the response...
+ // debug(1,"Create timingPeerInfoPlist");
+ plist_t timingPeerInfoPlist = plist_new_dict();
+ plist_dict_set_item(timingPeerInfoPlist, "Addresses", addresses);
+ plist_dict_set_item(timingPeerInfoPlist, "ID", plist_new_string(conn->self_ip_string));
+ plist_dict_set_item(setupResponsePlist, "timingPeerInfo", timingPeerInfoPlist);
+ // get a port to use as an event port
+
+ // bind a new TCP port and get a socket
+ conn->local_event_port = 0; // any port
+
+ int err =
+ bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string,
+ conn->self_scope_id, &conn->local_event_port, &conn->event_socket);
+ if (err) {
+ die("Error %d: could not find a TCP port to use as an event port", err);
+ }
+
+ pthread_create(&conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
+
+ plist_dict_set_item(setupResponsePlist, "eventPort", plist_new_uint(conn->local_event_port));
+ plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy
}
-
- // make up the timing peer info list part of the response...
- // debug(1,"Create timingPeerInfoPlist");
- plist_t timingPeerInfoPlist = plist_new_dict();
- plist_dict_set_item(timingPeerInfoPlist, "Addresses", addresses);
- plist_dict_set_item(timingPeerInfoPlist, "ID", plist_new_string(conn->self_ip_string));
- plist_dict_set_item(setupResponsePlist, "timingPeerInfo", timingPeerInfoPlist);
- // get a port to use as an event port
-
- // bind a new TCP port and get a socket
- conn->local_event_port = 0; // any port
-
- int err =
- bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string,
- conn->self_scope_id, &conn->local_event_port, &conn->event_socket);
- if (err) {
- die("Error %d: could not find a TCP port to use as an event port", err);
- }
-
- pthread_create(&conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
-
- plist_dict_set_item(setupResponsePlist, "eventPort", plist_new_uint(conn->local_event_port));
- plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy
}
-
- plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength);
- plist_free(setupResponsePlist);
-
- msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
+ if (setupResponsePlist) {
+ plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength);
+ plist_free(setupResponsePlist);
+ msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
+ } else {
+ debug_log_rtsp_message(1, "Unrecognised SETUP incoming message", req);
+ warn("Unrecognised SETUP incoming message -- ignored.");
+ }
debug_log_rtsp_message(2, " SETUP response", resp);
- resp->respcode = 200; // it all worked out okay
- // } else {
- // resp->respcode = 453;
- // debug(1, "Connection %d: SETUP failed because another connection is already playing.",
- // conn->connection_number);
- // }
+ resp->respcode = 200;
}
#endif
@@ -4072,11 +4061,6 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
}
#ifdef CONFIG_AIRPLAY_2
- if (conn->client_setup_plist) {
- plist_free(conn->client_setup_plist);
- conn->client_setup_plist = NULL;
- }
-
buf_drain(&conn->ap2_control_pairing.plain_buf, -1);
buf_drain(&conn->ap2_control_pairing.encrypted_buf, -1);
pair_setup_free(conn->ap2_control_pairing.setup_ctx);
From 5c600f9bd96a24e5bebabe34fa2ad0972d7f523b Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 28 Jul 2021 15:47:21 +0100
Subject: [PATCH 139/180] Update RELEASENOTES-DEVELOPMENT
---
RELEASENOTES-DEVELOPMENT.md | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index c10e455d..be36f514 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,23 @@
+Version 4.0-dev-303-g9074069
+====
+#### New Features
+- An initial [PipeWire](https://pipewire.org) backend, with thanks to [Lukas Rusak](https://github.com/lrusak). Use the `--with-pw` configuration flag at the `./configure...` stage to include support.
+
+
+#### Bug Fixes
+- Fix a bug when using the `tinysvcmdns` mDNS implementation, with thanks to [fesc2000](https://github.com/fesc2000).
+- When an unrecognised SETUP message is received, just ignore it and put a warning in the log. Remove a redundant `client_setup_plist` data item from the `conn` data structure.
+
+#### Enhancements
+- Shairport Sync no longer needs to be in the same directory of NQPTP during compilation.
+- Add Docker automations -- thanks to [Charles Omer](https://github.com/charlesomer).
+- Add Issue management automations -- thanks to [Charles Omer](https://github.com/charlesomer).
+- Reconnect the DACP scanner for AirPlay 2. (It still isn't useful, unfortunately.)
+- Add initial support to allow dynamic modification of Rendezvous advertisements.
+- Simplify some UFW suggestions in the Troubleshooting Guide.
+- Merge some documentation changes from the `master` branch.
+- Add information to the Car Install Guide about improving boot times, thanks to [vasilisvg](https://github.com/vasilisvg)
+
Version 4.0-dev-185-g0c02ee2
====
From 099d21d3971dc6c3b8e58de8c8baf5af923b91a0 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 28 Jul 2021 16:59:35 +0100
Subject: [PATCH 140/180] Documentation updates
---
AIRPLAY2.md | 11 ++++++-----
BUILDFORAP2.md | 10 +++-------
README.md | 2 +-
3 files changed, 10 insertions(+), 13 deletions(-)
diff --git a/AIRPLAY2.md b/AIRPLAY2.md
index 42a20730..b253f39c 100644
--- a/AIRPLAY2.md
+++ b/AIRPLAY2.md
@@ -1,9 +1,9 @@
AirPlay 2
===
-Shairport Sync offers limited AirPlay 2 support for audio sources on iOS devices and Macs. In addition, it partly works if the audio source is a HomePod mini or an Apple TV. It does not work for iTunes on Windows. Unfortunately, it does not integrate with the Home app or with Siri.
+Shairport Sync offers limited AirPlay 2 support for audio sources on iOS devices and Macs. In addition, it partly works if the audio source is a HomePod mini or an Apple TV. It does not work for iTunes on Windows. It has limited integration with the Home app and Siri.
-The focus of the development effort has been on getting good audio performance for iOS and Mac. Features outside this focus may be missing or broken. So, for example, integration with Apple's Home app is missing; remote control doesn't work.
+The focus of the development effort has been on getting good audio performance for iOS and Mac. Features outside this focus may be missing or broken. So, for example, remote control doesn't work.
For AirPlay 2, Shairport Sync uses another application called [`nqptp`](https://github.com/mikebrady/nqptp) ("Not Quite PTP") for timing and synchronisation. `nqptp` must run as `root` and must have exclusive access to ports `319` and `320`.
@@ -17,13 +17,13 @@ The AirPlay 2 build requires a good deal of extra library support and may not fi
- AirPlay 2 for iOS and Mac players.
### What Does Not Work
-- No integration with Siri or HomeKit
- Lossless or High Definition Lossless material is transcoded to AAC before transmission over AirPlay 2.
- No Remote Control
- No Artwork in the Metadata
- No AirPlay 2 for Windows iTunes
### What Partly Works
+- Incomplete functionality with Siri and HomeKit
- Incomplete functionality using AppleTV or HomePod as player.
A Shairport Sync player will work -- i.e. it will play -- but the selection button won't persist and the volume can't be set.
@@ -51,15 +51,16 @@ Guides
AirPlay 2 -- More About What Works
---
* Two types of audio are received by Shairport Sync -- "Realtime" streams of CD quality ALAC (like AirPlay 1) and "Buffered Audio" streams of AAC stereo at 44,100 frames per second. The selection of stream type is made by the player.
-* Audio is synchronised with other AirPlay 2 devices, including AirPlay 2 devices that have their own master clocks. (Note: This has not been tested with multiple nearly-identical master clock devices such as with two HomePod minis -- Shairport Sync will get confused about which is the current master.)
+* Audio is synchronised with other AirPlay 2 devices, including AirPlay 2 devices that have their own master clocks.
* Shairport Sync continues to support AirPlay 1, and offers an AirPlay 1 compatibility mode for situations where iTunes on macOS or macOS Music plays to multiple speakers and one of more of them is compatible with AirPlay 1 only.
Acknowledgements
----
-Huge thanks are due to number of individuals made direct and valuable contributions to Shairport Sync:
+Huge thanks are due to a number of individuals who made direct and valuable contributions to Shairport Sync:
1. [ejurgensen](https://github.com/ejurgensen) contributed ideas and code. The `pair_ap` submodule and related code in `rtsp.c` is theirs.
2. [JD Smith](https://github.com/jdtsmith) contributed ideas, comments and suggestions along with extensive and thorough testing.
3. [Charles Omer](https://github.com/charlesomer) contributed ideas, comments, bug fixes, documentation and Docker automation (forthcoming).
+4. [ckdo](https://github.com/ckdo) for their pathfinding work on AirPlay 2, especially the HomeKit pairing.
Much of Shairport Sync's AirPlay 2 functionality is based on ideas developed at the [openairplay airplay2-receiver]( https://github.com/openairplay/airplay2-receiver) repository. It is a pleasure to acknowledge the work of the contributors there.
diff --git a/BUILDFORAP2.md b/BUILDFORAP2.md
index 780a3c81..49c89227 100644
--- a/BUILDFORAP2.md
+++ b/BUILDFORAP2.md
@@ -5,9 +5,8 @@ Building Shairport Sync for AirPlay 2
* Be especially careful with audio systems capable of very high volume output -- the volume control in this software may not be reliable!
* For now, leave the settings in the configuration file at default except as noted below.
* At the time of writing, May 2, 2021, everything is on the latest version of the software -- macOS 11.3, iOS 14.5, Raspberry Pi OS 5.10.17-v7l+ (Buster), Ubuntu 20.04 -- fully updated.
-* Shairport Sync relies on a program called [`nqptp`](https://github.com/mikebrady/nqptp) to monitor timing signals. This program uses ports 319 and 320 and replaces any PTP service you have on the computer.
- (FYI, most computers do not have a PTP clock running. They often use a (totally different) [Network Timing Protocol (NTP)](http://www.ntp.org) service to keep the system clock synchronised with world time.)
-* Shairport Sync has a build dependency on `nqptp`, so you should build or update `nqptp` *before* building or updating Shairport Sync.
+* Shairport Sync relies on a program called [`nqptp`](https://github.com/mikebrady/nqptp) to monitor timing signals. This program uses ports 319 and 320 and replaces any PTP service you have on the computer.
+ (FYI, most computers do not have a PTP clock running. They often use a (totally different) [Network Timing Protocol (NTP)](http://www.ntp.org) service to keep the system clock synchronised with world time.)
* The POSIX Shared Memory Interface (SMI) Version numbers of `nqptp` and Shairport Sync must match. If they don't, you'll get a message in the logs. It means that one of the programs is out of date with respect to the other.
* Build instructions are likely to change.
@@ -59,17 +58,14 @@ First, install the packages needed by Shairport Sync:
libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libglib2.0-dev \
libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev
```
-(Note: if you don't want the `D-Bus` interface, you may omit `libglib2.0-dev`.)
### nqptp ###
Download, install, enable and start `nqptp` from [here](https://github.com/mikebrady/nqptp).
-***Note:*** Shairport Sync expects the `nqptp` repository folder to be in the same directory as the `shairport-sync` repository folder, as it will look for a header file at `../nqptp/nqptp-shm-structures.h`.
-
### Shairport Sync
#### Please use `git` to clone the repository!
-As you probably know, you can download the repository in two ways: (1) using `git` to clone it -- recommended -- or (2) downloading the repository as a ZIP archive. Please us the `git` method. The reason it that when you use `git`, the build process can incorporate the `git` build information in the version string you get when you execute the command `$ shairport-sync -V`. This will be very useful for identifying the exact build if you are making comments or bug reports. Here is an example:
+As you probably know, you can download the repository in two ways: (1) using `git` to clone it -- recommended -- or (2) downloading the repository as a ZIP archive. Please use the `git` method. The reason it that when you use `git`, the build process can incorporate the `git` build information in the version string you get when you execute the command `$ shairport-sync -V`. This will be very useful for identifying the exact build if you are making comments or bug reports. Here is an example:
```
Version with git information:
4.0-dev-138-g2789572-AirPlay2-OpenSSL-Avahi-ALSA-soxr-metadata-dbus-sysconfdir:/etc
diff --git a/README.md b/README.md
index 234dc391..b98156dc 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,6 @@ For the development of AirPlay 2 support, special thanks are due to:
* [ejurgensen](https://github.com/ejurgensen) for advice and [code to deal with pairing and encryption](https://github.com/ejurgensen/pair_ap).
* [ckdo](https://github.com/ckdo) for pointing the way, particularly with pairing and encryption protocols, with a [functional Python implementation](https://github.com/ckdo/airplay2-receiver) of AirPlay 2.
* [invano](https://github.com/invano) for showing what might be possible and for initial Python development.
-* [Charles Omer](https://github.com/charlesomer) for Docker automation (forthcoming), testing, encouragement, enthusiasm.
+* [Charles Omer](https://github.com/charlesomer) for Docker automation, repository management automation, testing, encouragement, enthusiasm.
And of course, thanks to everyone who has supported and improved Shairport Sync over the years.
From 14c12b77ed3886bc27fc0dcca02ac236ebe5ec00 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 29 Jul 2021 13:11:39 +0100
Subject: [PATCH 141/180] Update BUILDFORAP2.md
---
BUILDFORAP2.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/BUILDFORAP2.md b/BUILDFORAP2.md
index 49c89227..b6106455 100644
--- a/BUILDFORAP2.md
+++ b/BUILDFORAP2.md
@@ -54,7 +54,7 @@ Okay, now let's get the tools and libraries for building and installing Shairpor
First, install the packages needed by Shairport Sync:
```
-# apt-get install build-essential git xmltoman autoconf automake libtool \
+# apt install --no-install-recommends build-essential git xmltoman autoconf automake libtool \
libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libglib2.0-dev \
libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev
```
From 3208165961d5312bf4dc7931a1dbdccab16defa4 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 29 Jul 2021 13:13:01 +0100
Subject: [PATCH 142/180] Update CAR INSTALL.md
---
CAR INSTALL.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index dcf78cba..27b3ae43 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -47,7 +47,7 @@ The first thing to do on a Pi would be to use the `raspi-config` tool to expand
### Shairport Sync
First, install the packages needed by Shairport Sync:
```
-# apt-get install build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
+# apt install --no-install-recommends build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
```
Next, download Shairport Sync, configure it, compile and install it:
```
From 23da206c7eae2f7a83429419a8b0b9a31ae1170c Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 30 Jul 2021 19:15:17 +0100
Subject: [PATCH 143/180] revert output to syslog rather than stderr
---
common.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common.c b/common.c
index 5d922b74..fcbf4fb5 100644
--- a/common.c
+++ b/common.c
@@ -231,7 +231,7 @@ void log_to_syslog() {
#ifdef CONFIG_LIBDAEMON
sps_log = daemon_log;
#else
- sps_log = do_sps_log_to_stderr;
+ sps_log = syslog;
#endif
}
From 5a51c43d98188d954efea2b12c97c00e48009e0d Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 30 Jul 2021 20:39:08 +0100
Subject: [PATCH 144/180] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index be36f514..775a74e2 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,15 +1,19 @@
+Version 4.0-dev-308-g23da206
+====
+#### Bug Fixes
+- Restore logging to `syslog`.
+
Version 4.0-dev-303-g9074069
====
#### New Features
- An initial [PipeWire](https://pipewire.org) backend, with thanks to [Lukas Rusak](https://github.com/lrusak). Use the `--with-pw` configuration flag at the `./configure...` stage to include support.
-
#### Bug Fixes
- Fix a bug when using the `tinysvcmdns` mDNS implementation, with thanks to [fesc2000](https://github.com/fesc2000).
- When an unrecognised SETUP message is received, just ignore it and put a warning in the log. Remove a redundant `client_setup_plist` data item from the `conn` data structure.
#### Enhancements
-- Shairport Sync no longer needs to be in the same directory of NQPTP during compilation.
+- Shairport Sync no longer needs to be in the same directory as NQPTP during compilation.
- Add Docker automations -- thanks to [Charles Omer](https://github.com/charlesomer).
- Add Issue management automations -- thanks to [Charles Omer](https://github.com/charlesomer).
- Reconnect the DACP scanner for AirPlay 2. (It still isn't useful, unfortunately.)
From 246f26afe41d992d0be2ff44b9cd5596d3a051c4 Mon Sep 17 00:00:00 2001
From: Fabrice Fontaine
Date: Fri, 6 Aug 2021 11:41:49 +0200
Subject: [PATCH 145/180] configure.ac: find sndfile through pkg-config
Find sndfile through pkg-config to retrieve sndfile dependencies such as
flac and avoid the following static build failure:
/tmp/instance-3/output-1/host/lib/gcc/xtensa-buildroot-linux-uclibc/10.3.0/../../../../xtensa-buildroot-linux-uclibc/bin/ld: /tmp/instance-3/output-1/host/bin/../xtensa-buildroot-linux-uclibc/sysroot/usr/lib/libsndfile.a(libsndfile_la-flac.o): in function `flac_byterate':
flac.c:(.text+0xfc): undefined reference to `FLAC__StreamDecoderErrorStatusString'
Fixes:
- http://autobuild.buildroot.org/results/92ed30a6855ca11800b779718822bcba4a69c9a3
Signed-off-by: Fabrice Fontaine
---
configure.ac | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 4f27e346..ef32fda5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -325,7 +325,14 @@ AC_ARG_WITH(convolution, [AS_HELP_STRING([--with-convolution],[choose audio DSP
if test "x$with_convolution" = "xyes" ; then
AM_INIT_AUTOMAKE([subdir-objects])
AC_DEFINE([CONFIG_CONVOLUTION], 1, [Include audio DSP convolution support.])
- AC_CHECK_LIB([sndfile], [sf_open], , AC_MSG_ERROR(Convolution support requires the sndfile library -- libsndfile1-dev suggested!))
+ if test "x${with_pkg_config}" = xyes ; then
+ PKG_CHECK_MODULES(
+ [sndfile], [sndfile],
+ [CFLAGS="${sndfile_CFLAGS} ${CFLAGS}"
+ LIBS="${sndfile_LIBS} ${LIBS}"], AC_MSG_ERROR(Convolution support requires the sndfile library -- libsndfile1-dev suggested!))
+ else
+ AC_CHECK_LIB([sndfile], [sf_open], , AC_MSG_ERROR(Convolution support requires the sndfile library -- libsndfile1-dev suggested!))
+ fi
fi
AM_CONDITIONAL([USE_CONVOLUTION], [test "x$with_convolution" = "xyes"])
From 687fd16758e34e3e2b0686a88110d1e97399f36c Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 10 Aug 2021 22:04:37 +0100
Subject: [PATCH 146/180] Update MQTT.md
---
MQTT.md | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/MQTT.md b/MQTT.md
index da21a3fc..3189a54a 100644
--- a/MQTT.md
+++ b/MQTT.md
@@ -1,3 +1,21 @@
+MQTT in Shairport Sync
+====
+To enable Shairport Sync to act as an MQTT publisher, you need to:
+1. Install the mosquitto library:
+```
+# apt install libmosquitto-dev
+```
+2. Add the configuration flag `--with-mqtt-client` to the list of parameters to the `./configure...` command, for example:
+```
+$ ./configure --with-mqtt-client --sysconfdir=/etc --with-alsa --with-avahi --with-ssl=openssl --with-systemd
+```
+
+If Shairport Sync has MQTT support, it will have the string `mqtt` in its configuration string. For example:
+```
+$ shairport-sync -V
+3.3.8-OpenSSL-Avahi-ALSA-metadata-mqtt-sysconfdir:/etc
+```
+
Setting Up MQTT Publishing
====
From 6beb73e99e2703101bff10207fc039ac9ebcbfd4 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 23 Aug 2021 12:57:56 +0100
Subject: [PATCH 147/180] Allow debug/inform/die/warn messages up to 16384
bytes (up from 1024 bytes)
---
common.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/common.c b/common.c
index fcbf4fb5..866222c2 100644
--- a/common.c
+++ b/common.c
@@ -141,7 +141,7 @@ static void (*sps_log)(int prio, const char *t, ...) = syslog;
#endif
void do_sps_log_to_stderr(__attribute__((unused)) int prio, const char *t, ...) {
- char s[1024];
+ char s[16384];
va_list args;
va_start(args, t);
vsnprintf(s, sizeof(s), t, args);
@@ -150,7 +150,7 @@ void do_sps_log_to_stderr(__attribute__((unused)) int prio, const char *t, ...)
}
void do_sps_log_to_stdout(__attribute__((unused)) int prio, const char *t, ...) {
- char s[1024];
+ char s[16384];
va_list args;
va_start(args, t);
vsnprintf(s, sizeof(s), t, args);
@@ -210,7 +210,7 @@ int create_log_file(const char *path) {
}
void do_sps_log_to_fd(__attribute__((unused)) int prio, const char *t, ...) {
- char s[1024];
+ char s[16384];
va_list args;
va_start(args, t);
vsnprintf(s, sizeof(s), t, args);
@@ -459,7 +459,7 @@ char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss
void _die(const char *filename, const int linenumber, const char *format, ...) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- char b[1024];
+ char b[16384];
b[0] = 0;
char *s;
if (debuglev) {
@@ -489,7 +489,7 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
void _warn(const char *filename, const int linenumber, const char *format, ...) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- char b[1024];
+ char b[16384];
b[0] = 0;
char *s;
if (debuglev) {
@@ -519,7 +519,7 @@ void _debug(const char *filename, const int linenumber, int level, const char *f
return;
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- char b[1024];
+ char b[16384];
b[0] = 0;
pthread_mutex_lock(&debug_timing_lock);
uint64_t time_now = get_absolute_time_in_ns();
@@ -541,7 +541,7 @@ void _debug(const char *filename, const int linenumber, int level, const char *f
void _inform(const char *filename, const int linenumber, const char *format, ...) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- char b[1024];
+ char b[16384];
b[0] = 0;
char *s;
if (debuglev) {
@@ -1079,7 +1079,7 @@ void command_start(void) {
void command_execute(const char *command, const char *extra_argument, const int block) {
// this has a cancellation point if waiting is enabled
if (command) {
- char new_command_buffer[1024];
+ char new_command_buffer[2048];
char *full_command = (char *)command;
if (extra_argument != NULL) {
memset(new_command_buffer, 0, sizeof(new_command_buffer));
From d2dc03d07c5696c6f5241060e0de257a688f9dca Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 23 Aug 2021 13:00:09 +0100
Subject: [PATCH 148/180] break out the POST /command stuff
---
rtsp.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/rtsp.c b/rtsp.c
index c459dfc9..8e3fa8a7 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1942,6 +1942,14 @@ void handle_feedback(__attribute__((unused)) rtsp_conn_info *conn,
__attribute__((unused)) rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {}
+void handle_command(__attribute__((unused)) rtsp_conn_info *conn,
+ rtsp_message *req,
+ __attribute__((unused)) rtsp_message *resp) {
+
+ debug_log_rtsp_message(2, "POST /command", req);
+}
+
+
void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
if (strcmp(req->path, "/pair-setup") == 0) {
@@ -1960,6 +1968,8 @@ void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
handle_configure(conn, req, resp);
} else if (strcmp(req->path, "/feedback") == 0) {
handle_feedback(conn, req, resp);
+ } else if (strcmp(req->path, "/command") == 0) {
+ handle_command(conn, req, resp);
} else {
debug(2, "Connection %d: Unhandled POST %s Content-Length %d", conn->connection_number,
req->path, req->contentlength);
From 5d4d75ad4eacde7681a505553c09bcb4b8242541 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 31 Aug 2021 17:43:56 +0100
Subject: [PATCH 149/180] add a place to store the groupContainsGroupLeader
from SETUP
---
player.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/player.h b/player.h
index 2596eb78..05019417 100644
--- a/player.h
+++ b/player.h
@@ -146,7 +146,6 @@ typedef struct {
uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero
// otherwise
int software_mute_enabled; // if we don't have a real mute that we can use
-
int fd;
int authorized; // set if a password is required and has been supplied
char *auth_nonce; // the session nonce, if needed
@@ -333,7 +332,8 @@ typedef struct {
unsigned char *session_key; // needs to be free'd at the end
uint64_t frames_packet;
uint64_t type;
- uint64_t networkTimeTimelineID; // the clock ID used by the player
+ uint64_t networkTimeTimelineID; // the clock ID used by the player
+ uint64_t groupContainsGroupLeader; // information coming from the SETUP
char *ap2_timing_peer_list_message;
From f62ec6686501d9a39650adf933af523aba477c40 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 31 Aug 2021 17:45:27 +0100
Subject: [PATCH 150/180] Make the features more like the APX2
---
shairport.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/shairport.c b/shairport.c
index 2de86a9d..c40ae1cc 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1493,7 +1493,7 @@ int main(int argc, char **argv) {
usage(argv[0]);
exit(EXIT_SUCCESS);
}
-
+
log_to_syslog();
#ifdef CONFIG_LIBDAEMON
@@ -1590,12 +1590,13 @@ int main(int argc, char **argv) {
// 496155702020608 this setting here is the source of both the plist features response and the
// mDNS string.
// note: 0x300401F4A00 works but with weird delays and stuff
- config.airplay_features = 0x1C340405FCA00;
+ // config.airplay_features = 0x1C340405FCA00;
+ config.airplay_features = 0x1C340405D4A00; // APX + Authentication4 (b14)
// Advertised with mDNS and returned with GET /info, see
// https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN
// required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio
// cable attached, OneTimePairingRequired, device was setup for Homekit access control
- config.airplay_statusflags = 0x4;
+ config.airplay_statusflags = 0x04;
// Set to NULL to work with transient pairing
config.airplay_pin = NULL;
@@ -1619,7 +1620,7 @@ int main(int argc, char **argv) {
// with thanks
uuid_t binuuid;
uuid_generate_random(binuuid);
-
+
char *uuid = malloc(UUID_STR_LEN);
// Produces a UUID string at uuid consisting of lower-case letters
uuid_unparse_lower(binuuid, uuid);
From 620d6fae601d0ef33a8968d5bbd956f792a4d950 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 31 Aug 2021 17:54:59 +0100
Subject: [PATCH 151/180] Rearrange code for setting bonjour strings. Improve
response text from msg_write_response. Decode updateMRSupportedCommands stuff
for curiosity. Capture group ID and groupContainsGroupLeader from setup and
post them in the Bonjour strings. Include 'isGroupLeader=0' if it's in a
group. Restore group ID and related stuff when deselected. Set bit 11
'DeviceSupportsRelay' when selected and clear when deselected.
---
rtsp.c | 381 ++++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 255 insertions(+), 126 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index 8e3fa8a7..f40053d6 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -101,11 +101,12 @@
// from these sources.
char *txt_records[64];
+char *secondary_txt_records[64];
+
char firmware_version[64];
char ap1_featuresString[64];
char pkString[128];
#ifdef CONFIG_AIRPLAY_2
-char *secondary_txt_records[64];
char deviceIdString[64];
char featuresString[64];
char statusflagsString[32];
@@ -180,12 +181,122 @@ typedef struct {
} rtsp_message;
#ifdef CONFIG_AIRPLAY_2
-void mdns_update_flags(uint32_t flags) {
- snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32, flags);
- mdns_update(NULL, secondary_txt_records);
+
+static void pkString_make(char *str, size_t str_size, const char *device_id) {
+ uint8_t public_key[32];
+ if (str_size < 2 * sizeof(public_key) + 1) {
+ warn("Insufficient string size");
+ str[0] = '\0';
+ return;
+ }
+ pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, device_id);
+ char *ptr = str;
+ for (size_t i = 0; i < sizeof(public_key); i++)
+ ptr += sprintf(ptr, "%02x", public_key[i]);
}
#endif
+void build_bonjour_strings(rtsp_conn_info *conn) {
+
+ int entry_number = 0;
+
+ // make up a firmware version
+#ifdef CONFIG_USE_GIT_VERSION_STRING
+ if (git_version_string[0] != '\0')
+ snprintf(firmware_version, sizeof(firmware_version), "fv=%s", git_version_string);
+ else
+#endif
+ snprintf(firmware_version, sizeof(firmware_version), "fv=%s", PACKAGE_VERSION);
+
+#ifdef CONFIG_AIRPLAY_2
+ uint64_t features_hi = config.airplay_features;
+ features_hi = (features_hi >> 32) & 0xffffffff;
+ uint64_t features_lo = config.airplay_features;
+ features_lo = features_lo & 0xffffffff;
+ snprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
+ features_lo, features_hi);
+ snprintf(pkString, sizeof(pkString), "pk=");
+ pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="),
+ config.airplay_device_id);
+
+ txt_records[entry_number++] = "cn=0,1";
+ txt_records[entry_number++] = "da=true";
+ txt_records[entry_number++] = "et=0,4";
+ txt_records[entry_number++] = ap1_featuresString;
+ txt_records[entry_number++] = firmware_version;
+ txt_records[entry_number++] = "md=2";
+ txt_records[entry_number++] = "am=Shairport Sync";
+ txt_records[entry_number++] = "sf=0x4";
+ txt_records[entry_number++] = "tp=UDP";
+ txt_records[entry_number++] = "vn=65537";
+ txt_records[entry_number++] = "vs=366.0";
+ txt_records[entry_number++] = pkString;
+ txt_records[entry_number++] = NULL;
+
+#else
+ // here, just replicate what happens in mdns.h when using those #defines
+ txt_records[entry_number++] = "sf=0x4";
+ txt_records[entry_number++] = firmware_version;
+ txt_records[entry_number++] = "am=ShairportSync";
+ txt_records[entry_number++] = "vs=105.1";
+ txt_records[entry_number++] = "tp=TCP,UDP";
+ txt_records[entry_number++] = "vn=65537";
+#ifdef CONFIG_METADATA
+ if (config.get_coverart == 0)
+ txt_records[entry_number++] = "md=0,2";
+ else
+ txt_records[entry_number++] = "md=0,1,2";
+#endif
+ txt_records[entry_number++] = "ss=16";
+ txt_records[entry_number++] = "sr=44100";
+ txt_records[entry_number++] = "da=true";
+ txt_records[entry_number++] = "sv=false";
+ txt_records[entry_number++] = "et=0,1";
+ txt_records[entry_number++] = "ek=1";
+ txt_records[entry_number++] = "cn=0,1";
+ txt_records[entry_number++] = "ch=2";
+ txt_records[entry_number++] = "txtvers=1";
+ if (config.password == 0)
+ txt_records[entry_number++] = "pw=false";
+ else
+ txt_records[entry_number++] = "pw=true";
+ txt_records[entry_number++] = NULL;
+#endif
+
+#ifdef CONFIG_AIRPLAY_2
+ // make up a secondary set of text records
+ entry_number = 0;
+
+ secondary_txt_records[entry_number++] = "srcvers=366.0";
+ snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
+ secondary_txt_records[entry_number++] = deviceIdString;
+ snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIX64 ",0x%" PRIX64 "",
+ features_lo, features_hi);
+ secondary_txt_records[entry_number++] = featuresString;
+ snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
+ config.airplay_statusflags);
+
+ secondary_txt_records[entry_number++] = statusflagsString;
+ secondary_txt_records[entry_number++] = "protovers=1.1";
+ secondary_txt_records[entry_number++] = "acl=0";
+ secondary_txt_records[entry_number++] = "rsf=0x0";
+ secondary_txt_records[entry_number++] = firmware_version;
+ secondary_txt_records[entry_number++] = "model=Shairport Sync";
+ snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
+ secondary_txt_records[entry_number++] = piString;
+ snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_gid);
+ secondary_txt_records[entry_number++] = gidString;
+ if ((conn != NULL) && (conn->groupContainsGroupLeader != 0))
+ secondary_txt_records[entry_number++] = "gcgl=1";
+ else
+ secondary_txt_records[entry_number++] = "gcgl=0";
+ if (strcmp(config.airplay_pi, config.airplay_gid) != 0) // if it's in a group
+ secondary_txt_records[entry_number++] = "isGroupLeader=0";
+ secondary_txt_records[entry_number++] = pkString;
+ secondary_txt_records[entry_number++] = NULL;
+#endif
+}
+
#ifdef CONFIG_METADATA
typedef struct {
uint32_t type;
@@ -960,19 +1071,6 @@ static ssize_t write_encrypted(rtsp_conn_info *conn, const void *buf, size_t cou
free(encrypted);
return count;
}
-
-static void pkString_make(char *str, size_t str_size, const char *device_id) {
- uint8_t public_key[32];
- if (str_size < 2 * sizeof(public_key) + 1) {
- warn("Insufficient string size");
- str[0] = '\0';
- return;
- }
- pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, device_id);
- char *ptr = str;
- for (size_t i = 0; i < sizeof(public_key); i++)
- ptr += sprintf(ptr, "%02x", public_key[i]);
-}
#endif
ssize_t read_from_rtsp_connection(rtsp_conn_info *conn, void *buf, size_t count) {
@@ -1159,9 +1257,26 @@ int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) {
int n;
unsigned int i;
- n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode,
- resp->respcode == 200 ? "OK" : "Unauthorized");
- // debug(1, "sending response: %s", pkt);
+ struct response_t {
+ int code;
+ char *string;
+ };
+
+ struct response_t responses[] = {{200, "OK"}, {400, "Bad Request"}, {403, "Unauthorized"}};
+ int found = 0;
+ char *respcode_text = "Unauthorized";
+ for (i = 0; i < sizeof(responses) / sizeof(struct response_t); i++) {
+ if (resp->respcode == responses[i].code) {
+ found = 1;
+ respcode_text = responses[i].string;
+ }
+ }
+
+ if (found == 0)
+ debug(1, "can't find text for response code %d. Using \"%s\" instead.", resp->respcode,
+ respcode_text);
+
+ n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode, respcode_text);
pktfree -= n;
p += n;
@@ -1843,7 +1958,7 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message
static uint8_t server_fp_header[] = "\x46\x50\x4c\x59\x03\x01\x04\x00\x00\x00\x00\x14";
- resp->respcode = 200; // assume it's not handled
+ resp->respcode = 200; // assume it's handled
// uint8_t *out;
// size_t out_len;
@@ -1942,13 +2057,77 @@ void handle_feedback(__attribute__((unused)) rtsp_conn_info *conn,
__attribute__((unused)) rtsp_message *req,
__attribute__((unused)) rtsp_message *resp) {}
-void handle_command(__attribute__((unused)) rtsp_conn_info *conn,
- rtsp_message *req,
- __attribute__((unused)) rtsp_message *resp) {
-
+void handle_command(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req,
+ __attribute__((unused)) rtsp_message *resp) {
debug_log_rtsp_message(2, "POST /command", req);
-}
+ if (rtsp_message_contains_plist(req)) {
+ plist_t command_dict = NULL;
+ plist_from_memory(req->content, req->contentlength, &command_dict);
+ if (command_dict != NULL) {
+ // we have a plist -- try to get the dict item keyed to "updateMRSupportedCommands"
+ plist_t item = plist_dict_get_item(command_dict, "type");
+ if (item != NULL) {
+ char *typeValue = NULL;
+ plist_get_string_val(item, &typeValue);
+ if (strcmp(typeValue, "updateMRSupportedCommands") == 0) {
+ item = plist_dict_get_item(command_dict, "params");
+ if (item != NULL) {
+ // the item should be a dict
+ plist_t item_array = plist_dict_get_item(item, "mrSupportedCommandsFromSender");
+ if (item_array != NULL) {
+ // here we have an array of data items
+ uint32_t items = plist_array_get_size(item_array);
+ if (items) {
+ uint32_t item_number;
+ for (item_number = 0; item_number < items; item_number++) {
+ plist_t the_item = plist_array_get_item(item_array, item_number);
+ char *buff = NULL;
+ uint64_t length = 0;
+ plist_get_data_val(the_item, &buff, &length);
+ // debug(1,"Item %d, length: %" PRId64 " bytes", item_number, length);
+ if ((length >= strlen("bplist00")) && (strstr(buff, "bplist00") == buff)) {
+ // debug(1,"Contains a plist.");
+ plist_t subsidiary_plist = NULL;
+ plist_from_memory(buff, length, &subsidiary_plist);
+ if (subsidiary_plist) {
+ char *printable_plist = plist_content(subsidiary_plist);
+ if (printable_plist) {
+ debug(2, "\n%s", printable_plist);
+ free(printable_plist);
+ } else {
+ debug(1, "Can't print the plist!");
+ }
+ // plist_free(subsidiary_plist);
+ } else {
+ debug(1, "Can't access the plist!");
+ }
+ }
+ }
+ }
+ } else {
+ debug(1, "POST /command no mrSupportedCommandsFromSender item.");
+ }
+ } else {
+ debug(1, "POST /command no params dict.");
+ }
+ resp->respcode = 400; // say it's a bad request
+ } else {
+ debug(1,
+ "POST /command plist type is \"%s\", but \"updateMRSupportedCommands\" expected.",
+ typeValue);
+ }
+ } else {
+ debug(1, "Could not get the \"type\" item.");
+ }
+ plist_free(command_dict);
+ } else {
+ debug(1, "POST /command plist cannot be inputted.");
+ }
+ } else {
+ debug(1, "POST /command contains no plist");
+ }
+}
void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
@@ -2087,6 +2266,14 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
// resp->respcode = 451;
plist_free(messagePlist);
+ if (config.airplay_gid != NULL)
+ free(config.airplay_gid);
+ config.airplay_gid = strdup(config.airplay_pi);
+ // mdns_update_gid(config.airplay_gid);
+ config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
+ // mdns_update_flags(config.airplay_statusflags);
+ build_bonjour_strings(conn);
+ mdns_update(NULL, secondary_txt_records);
resp->respcode = 200;
} else {
debug(1, "Connection %d: missing plist!", conn->connection_number);
@@ -2161,8 +2348,8 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
#ifdef CONFIG_AIRPLAY_2
void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int err;
- debug(2, "Connection %d: SETUP (AirPlay 2)", conn->connection_number);
- // debug_log_rtsp_message(1, "Connection %d: SETUP (AirPlay 2) SETUP incoming message", req);
+ // debug(1, "Connection %d: SETUP (AirPlay 2)", conn->connection_number);
+ debug_log_rtsp_message(1, "SETUP (AirPlay 2) SETUP incoming message", req);
// we need to get the timing peer interfaces.
// I'm guessing they are all this device's adresses that are on the same subnets as
// the timing peer info in the accompanying plist
@@ -2170,6 +2357,38 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_t messagePlist = plist_from_rtsp_content(req);
plist_t setupResponsePlist = NULL; // we'll create it when we need it
+ plist_t groupUUID = plist_dict_get_item(messagePlist, "groupUUID");
+ if (groupUUID) {
+ char *gid = NULL;
+ plist_get_string_val(groupUUID, &gid);
+ if (gid) {
+ if (config.airplay_gid)
+ free(config.airplay_gid);
+ config.airplay_gid = gid; // it'll be free'd later on...
+ // mdns_update_gid(config.airplay_gid);
+ build_bonjour_strings(conn);
+ mdns_update(NULL, secondary_txt_records);
+ // debug(1,"Updated groupUUID to \"%s\"", config.airplay_gid);
+ } else {
+ debug(1, "Invalid groupUUID");
+ }
+ } else {
+ debug(1, "No groupUUID in SETUP");
+ }
+
+ // now see if the group contains a group leader
+ plist_t groupContainsGroupLeader = plist_dict_get_item(messagePlist, "groupContainsGroupLeader");
+ if (groupContainsGroupLeader) {
+ uint8_t value = 0;
+ plist_get_bool_val(groupContainsGroupLeader, &value);
+ conn->groupContainsGroupLeader = value;
+ build_bonjour_strings(conn);
+ mdns_update(NULL, secondary_txt_records);
+ debug(1, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader);
+ } else {
+ debug(1, "No groupContainsGroupLeader in SETUP");
+ }
+
// now see if the incoming plist contains a "streams" array
plist_t streams = plist_dict_get_item(messagePlist, "streams");
@@ -2305,8 +2524,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
ptp_send_control_message_string(conn->ap2_timing_peer_list_message);
else
debug(1, "No timing peer list!");
- // config.airplay_statusflags |= 1 << 10; // ReceiverSessionIsActive
- // mdns_update_flags(config.airplay_statusflags);
activity_monitor_signify_activity(1);
player_prepare_to_play(conn);
player_play(conn);
@@ -2349,8 +2566,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
// this should be cancelled by an activity_monitor_signify_activity(1)
// call in the SETRATEANCHORI handler, which should come up right away
- // config.airplay_statusflags |= 1 << 10; // ReceiverSessionIsActive
- // mdns_update_flags(config.airplay_statusflags);
activity_monitor_signify_activity(0);
player_play(conn);
@@ -2371,7 +2586,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
"SETUP on Connection %d: No \"streams\" array has been found -- create an event thread "
"and open a TCP port.",
conn->connection_number);
- debug_log_rtsp_message(2, "SETUP on Connection %d: No \"streams\" array has been found.", req);
// now determine which of the present device's IP numbers are in the
// same subnet as any IP numbers in the timing peer info
@@ -2488,9 +2702,14 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength);
plist_free(setupResponsePlist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
+ config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
+ build_bonjour_strings(conn);
+ mdns_update(NULL, secondary_txt_records);
} else {
- debug_log_rtsp_message(1, "Unrecognised SETUP incoming message", req);
- warn("Unrecognised SETUP incoming message -- ignored.");
+ // debug(1, "Unrecognised SETUP incoming message from %s", (const char *)
+ // conn->client_ip_string); debug_log_rtsp_message(1, "Unrecognised SETUP incoming message.",
+ // req);
+ // warn("Unrecognised SETUP incoming message -- ignored.");
}
debug_log_rtsp_message(2, " SETUP response", resp);
resp->respcode = 200;
@@ -4459,101 +4678,11 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
char **t1 = txt_records; // ap1 test records
char **t2 = NULL; // possibly two text records
-
- int entry_number = 0;
-
- // make up a firmware version
-#ifdef CONFIG_USE_GIT_VERSION_STRING
- if (git_version_string[0] != '\0')
- snprintf(firmware_version, sizeof(firmware_version), "fv=%s", git_version_string);
- else
-#endif
- snprintf(firmware_version, sizeof(firmware_version), "fv=%s", PACKAGE_VERSION);
-
-#ifdef CONFIG_AIRPLAY_2
- uint64_t features_hi = config.airplay_features;
- features_hi = (features_hi >> 32) & 0xffffffff;
- uint64_t features_lo = config.airplay_features;
- features_lo = features_lo & 0xffffffff;
- snprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
- features_lo, features_hi);
- snprintf(pkString, sizeof(pkString), "pk=");
- pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="),
- config.airplay_device_id);
-
- txt_records[entry_number++] = "cn=0,1";
- txt_records[entry_number++] = "da=true";
- txt_records[entry_number++] = "et=0,4";
- txt_records[entry_number++] = ap1_featuresString;
- txt_records[entry_number++] = firmware_version;
- txt_records[entry_number++] = "md=2";
- txt_records[entry_number++] = "am=Shairport Sync";
- txt_records[entry_number++] = "sf=0x4";
- txt_records[entry_number++] = "tp=UDP";
- txt_records[entry_number++] = "vn=65537";
- txt_records[entry_number++] = "vs=366.0";
- txt_records[entry_number++] = pkString;
- txt_records[entry_number++] = NULL;
-
-#else
- // here, just replicate what happens in mdns.h when using those #defines
- txt_records[entry_number++] = "sf=0x4";
- txt_records[entry_number++] = firmware_version;
- txt_records[entry_number++] = "am=ShairportSync";
- txt_records[entry_number++] = "vs=105.1";
- txt_records[entry_number++] = "tp=TCP,UDP";
- txt_records[entry_number++] = "vn=65537";
-#ifdef CONFIG_METADATA
- if (config.get_coverart == 0)
- txt_records[entry_number++] = "md=0,2";
- else
- txt_records[entry_number++] = "md=0,1,2";
-#endif
- txt_records[entry_number++] = "ss=16";
- txt_records[entry_number++] = "sr=44100";
- txt_records[entry_number++] = "da=true";
- txt_records[entry_number++] = "sv=false";
- txt_records[entry_number++] = "et=0,1";
- txt_records[entry_number++] = "ek=1";
- txt_records[entry_number++] = "cn=0,1";
- txt_records[entry_number++] = "ch=2";
- txt_records[entry_number++] = "txtvers=1";
- if (config.password == 0)
- txt_records[entry_number++] = "pw=false";
- else
- txt_records[entry_number++] = "pw=true";
- txt_records[entry_number++] = NULL;
-#endif
-
#ifdef CONFIG_AIRPLAY_2
// make up a secondary set of text records
t2 = secondary_txt_records; // second set of text records in AirPlay 2 only
- entry_number = 0;
-
- secondary_txt_records[entry_number++] = "srcvers=366.0";
- snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
- secondary_txt_records[entry_number++] = deviceIdString;
- snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIX64 ",0x%" PRIX64 "",
- features_lo, features_hi);
- secondary_txt_records[entry_number++] = featuresString;
- snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
- config.airplay_statusflags);
-
- secondary_txt_records[entry_number++] = statusflagsString;
- secondary_txt_records[entry_number++] = "protovers=1.1";
- secondary_txt_records[entry_number++] = "acl=0";
- secondary_txt_records[entry_number++] = "rsf=0x0";
- secondary_txt_records[entry_number++] = firmware_version;
- secondary_txt_records[entry_number++] = "model=Shairport Sync";
- snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
- secondary_txt_records[entry_number++] = piString;
- snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_gid);
- secondary_txt_records[entry_number++] = gidString;
- secondary_txt_records[entry_number++] = "gcgl=0";
- secondary_txt_records[entry_number++] = pkString;
- secondary_txt_records[entry_number++] = NULL;
#endif
-
+ build_bonjour_strings(NULL); // no conn yet
mdns_register(t1, t2);
pthread_setcancelstate(oldState, NULL);
From d703314c06e844edcda932bd4be335ba8733f830 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 10:28:30 +0100
Subject: [PATCH 152/180] Move group ID store to the conn. Fix the size of the
groupContainsGroupLeader plist boolean store.
---
common.h | 1 -
player.h | 5 +++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/common.h b/common.h
index 4572d198..9c3496f6 100644
--- a/common.h
+++ b/common.h
@@ -302,7 +302,6 @@ typedef struct {
char *airplay_device_id; // for the Bonjour advertisement and the GETINFO PList
char *airplay_pin; // non-NULL, 4 char PIN, if required for pairing
char *airplay_pi; // UUID in the Bonjour advertisement and the GETINFO Plist
- char *airplay_gid; // UUID in the Bonjour advertisement -- initially the same as the pi
#endif
} shairport_cfg;
diff --git a/player.h b/player.h
index 05019417..e8d661b5 100644
--- a/player.h
+++ b/player.h
@@ -287,6 +287,7 @@ typedef struct {
clock_status_t clock_status;
#ifdef CONFIG_AIRPLAY_2
+ char *airplay_gid; // UUID in the Bonjour advertisement -- if NULL, the group UUID is the same as the pi UUID
airplay_t airplay_type; // are we using AirPlay 1 or AirPlay 2 protocol on this connection?
airplay_stream_t airplay_stream_type; // is it realtime audio or buffered audio...
timing_t timing_type; // are we using NTP or PTP on this connection?
@@ -332,8 +333,8 @@ typedef struct {
unsigned char *session_key; // needs to be free'd at the end
uint64_t frames_packet;
uint64_t type;
- uint64_t networkTimeTimelineID; // the clock ID used by the player
- uint64_t groupContainsGroupLeader; // information coming from the SETUP
+ uint64_t networkTimeTimelineID; // the clock ID used by the player
+ uint8_t groupContainsGroupLeader; // information coming from the SETUP
char *ap2_timing_peer_list_message;
From 7fb8b288f07311e552db4ca7f0f18be56dd7755f Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 10:30:24 +0100
Subject: [PATCH 153/180] Stop initialising the airplay_gid. Now if it's NULL
it means the group UUID is the same as the deviice's pi. Alos airplay_gid is
moved to the conn.
---
shairport.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/shairport.c b/shairport.c
index c40ae1cc..9ab3f51a 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1625,7 +1625,6 @@ int main(int argc, char **argv) {
// Produces a UUID string at uuid consisting of lower-case letters
uuid_unparse_lower(binuuid, uuid);
config.airplay_pi = strdup(uuid);
- config.airplay_gid = strdup(uuid); // initially the gid is the same as the pi
// now we need to create the sequence of items for a Bonjour TXT record.
#else
From 7d36f0402542378833bedd3273cb74f769168ec7 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 10:31:06 +0100
Subject: [PATCH 154/180] If the airplay_gid is not NULL, free it before
deleting the player.
---
player.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/player.c b/player.c
index ac332a43..c95588e9 100644
--- a/player.c
+++ b/player.c
@@ -1705,6 +1705,9 @@ void player_thread_cleanup_handler(void *arg) {
if (conn->stream.type == ast_apple_lossless)
terminate_decoders(conn);
+ if (conn->airplay_gid)
+ free(conn->airplay_gid);
+
reset_anchor_info(conn);
release_play_lock(conn);
conn->rtp_running = 0;
From 7c4308ce3a3b304b386be6f5ef35d5021fc2aed6 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 10:33:29 +0100
Subject: [PATCH 155/180] Move groupUUID storage to the conn, as it's a
property of a connection not of the player. Improve some debug messages.
Alsways update the bonjour strings dueing a SETUP, only reset grooup UUID
when a TEARDOWN closes a connection, not when it closes a stream.
---
rtsp.c | 81 +++++++++++++++++++++++++++++++---------------------------
1 file changed, 43 insertions(+), 38 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index f40053d6..12c500d6 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -284,13 +284,17 @@ void build_bonjour_strings(rtsp_conn_info *conn) {
secondary_txt_records[entry_number++] = "model=Shairport Sync";
snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
secondary_txt_records[entry_number++] = piString;
- snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_gid);
+ if ((conn != NULL) && (conn->airplay_gid != 0)) {
+ snprintf(gidString, sizeof(gidString), "gid=%s", conn->airplay_gid);
+ } else {
+ snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_pi);
+ }
secondary_txt_records[entry_number++] = gidString;
if ((conn != NULL) && (conn->groupContainsGroupLeader != 0))
secondary_txt_records[entry_number++] = "gcgl=1";
else
secondary_txt_records[entry_number++] = "gcgl=0";
- if (strcmp(config.airplay_pi, config.airplay_gid) != 0) // if it's in a group
+ if ((conn != NULL) && (conn->airplay_gid != 0)) // if it's in a group
secondary_txt_records[entry_number++] = "isGroupLeader=0";
secondary_txt_records[entry_number++] = pkString;
secondary_txt_records[entry_number++] = NULL;
@@ -2225,7 +2229,6 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *
void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
- debug(2, "Connection %d: TEARDOWN", conn->connection_number);
// debug_log_rtsp_message(1, "TEARDOWN: ", req);
// if (have_player(conn)) {
@@ -2238,6 +2241,7 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
plist_t streams = plist_dict_get_item(messagePlist, "streams");
if (streams) {
+ debug(1, "Connection %d: TEARDOWN a stream.", conn->connection_number);
// we are being asked to close a stream
player_stop(conn);
activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
@@ -2249,8 +2253,7 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
debug(2, "Connection %d: Stream TEARDOWN complete", conn->connection_number);
} else {
// we are being asked to disconnect
- debug(2, "Connection %d: TEARDOWN No \"streams\" array has been found.",
- conn->connection_number);
+ debug(1, "Connection %d: TEARDOWN a connection.", conn->connection_number);
debug(2, "Connection %d: TEARDOWN Delete Event Thread.", conn->connection_number);
pthread_cancel(conn->rtp_event_thread);
pthread_join(conn->rtp_event_thread, NULL);
@@ -2259,6 +2262,15 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
close(conn->event_socket);
debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
+ if (conn->airplay_gid != NULL) {
+ free(conn->airplay_gid);
+ conn->airplay_gid = NULL;
+ }
+ conn->groupContainsGroupLeader = 0;
+ config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
+ // mdns_update_flags(config.airplay_statusflags);
+ build_bonjour_strings(conn);
+ mdns_update(NULL, secondary_txt_records);
}
//} else {
// warn("Connection %d TEARDOWN received without having the player (no ANNOUNCE?)",
@@ -2266,14 +2278,6 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
// resp->respcode = 451;
plist_free(messagePlist);
- if (config.airplay_gid != NULL)
- free(config.airplay_gid);
- config.airplay_gid = strdup(config.airplay_pi);
- // mdns_update_gid(config.airplay_gid);
- config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
- // mdns_update_flags(config.airplay_statusflags);
- build_bonjour_strings(conn);
- mdns_update(NULL, secondary_txt_records);
resp->respcode = 200;
} else {
debug(1, "Connection %d: missing plist!", conn->connection_number);
@@ -2348,8 +2352,8 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
#ifdef CONFIG_AIRPLAY_2
void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int err;
- // debug(1, "Connection %d: SETUP (AirPlay 2)", conn->connection_number);
- debug_log_rtsp_message(1, "SETUP (AirPlay 2) SETUP incoming message", req);
+ debug(1, "Connection %d: SETUP (AirPlay 2)", conn->connection_number);
+ // debug_log_rtsp_message(1, "SETUP (AirPlay 2) SETUP incoming message", req);
// we need to get the timing peer interfaces.
// I'm guessing they are all this device's adresses that are on the same subnets as
// the timing peer info in the accompanying plist
@@ -2362,13 +2366,9 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
char *gid = NULL;
plist_get_string_val(groupUUID, &gid);
if (gid) {
- if (config.airplay_gid)
- free(config.airplay_gid);
- config.airplay_gid = gid; // it'll be free'd later on...
- // mdns_update_gid(config.airplay_gid);
- build_bonjour_strings(conn);
- mdns_update(NULL, secondary_txt_records);
- // debug(1,"Updated groupUUID to \"%s\"", config.airplay_gid);
+ if (conn->airplay_gid)
+ free(conn->airplay_gid);
+ conn->airplay_gid = gid; // it'll be free'd later on...
} else {
debug(1, "Invalid groupUUID");
}
@@ -2382,8 +2382,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
uint8_t value = 0;
plist_get_bool_val(groupContainsGroupLeader, &value);
conn->groupContainsGroupLeader = value;
- build_bonjour_strings(conn);
- mdns_update(NULL, secondary_txt_records);
debug(1, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader);
} else {
debug(1, "No groupContainsGroupLeader in SETUP");
@@ -2394,8 +2392,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
if (streams) {
setupResponsePlist = plist_new_dict(); // now that we know we need it, create it
- debug(2,
- "SETUP on Connection %d: A \"streams\" array has been found -- create control and audio "
+ debug(1,
+ "Connection %d: SETUP. A \"streams\" array has been found -- create control and audio "
"threads and ports",
conn->connection_number);
// open a player)
@@ -2531,7 +2529,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->rtp_running = 1; // hack!
} break;
case 103: {
- debug(1, "Connection %d. Buffered Audio Stream Detected.", conn->connection_number);
+ debug(1, "SETUP on Connection %d. Buffered Audio Stream Detected.", 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;
@@ -2543,7 +2541,9 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->self_scope_id, &conn->local_buffered_audio_port,
&conn->buffered_audio_socket);
if (err) {
- die("Error %d: could not find a TCP port to use as a buffered_audio port", 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);
}
// hack.
@@ -2572,7 +2572,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->rtp_running = 1; // hack!
} break;
default:
- debug(1, "Unhandled stream type %" PRIu64 ".", item_value);
+ 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);
}
@@ -2582,7 +2583,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_dict_set_item(setupResponsePlist, "streams", streams_array);
} else {
- debug(2,
+ debug(1,
"SETUP on Connection %d: No \"streams\" array has been found -- create an event thread "
"and open a TCP port.",
conn->connection_number);
@@ -2663,16 +2664,19 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
}
}
} else {
- debug(1, "Don't recognise this as an IP address.");
+ debug(1, "SETUP on Connection %d: Don't recognise this as an IP address.",
+ conn->connection_number);
}
free(ip_address);
}
freeifaddrs(addrs);
} else {
- debug(1, "No timingPeerInfo addresses in the array.");
+ debug(1, "SETUP on Connection %d: No timingPeerInfo addresses in the array.",
+ conn->connection_number);
}
} else {
- debug(1, "Can't find timingPeerInfo addresses");
+ debug(1, "SETUP on Connection %d: Can't find timingPeerInfo addresses",
+ conn->connection_number);
}
// make up the timing peer info list part of the response...
// debug(1,"Create timingPeerInfoPlist");
@@ -2689,7 +2693,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string,
conn->self_scope_id, &conn->local_event_port, &conn->event_socket);
if (err) {
- die("Error %d: could not find a TCP port to use as an event port", err);
+ die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event port",
+ conn->connection_number, err);
}
pthread_create(&conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
@@ -2706,10 +2711,10 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
build_bonjour_strings(conn);
mdns_update(NULL, secondary_txt_records);
} else {
- // debug(1, "Unrecognised SETUP incoming message from %s", (const char *)
- // conn->client_ip_string); debug_log_rtsp_message(1, "Unrecognised SETUP incoming message.",
- // req);
- // warn("Unrecognised SETUP incoming message -- ignored.");
+ debug(1, "SETUP on Connection %d: Unrecognised SETUP incoming message from %s",
+ conn->connection_number, (const char *)conn->client_ip_string);
+ debug_log_rtsp_message(1, "Unrecognised SETUP incoming message.", req);
+ warn("Unrecognised SETUP incoming message -- ignored.");
}
debug_log_rtsp_message(2, " SETUP response", resp);
resp->respcode = 200;
From 650077bc836875144e497e9f43e48ff23b43593f Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 12:08:13 +0100
Subject: [PATCH 156/180] Tiny update. Note thay SPS can not participate in a
remote control connection.
---
rtsp.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/rtsp.c b/rtsp.c
index 12c500d6..4b04aa83 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -2268,7 +2268,6 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
}
conn->groupContainsGroupLeader = 0;
config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
- // mdns_update_flags(config.airplay_statusflags);
build_bonjour_strings(conn);
mdns_update(NULL, secondary_txt_records);
}
From 7fde77c45d50d92dd64ba91192af6a149b850d7f Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 16:39:32 +0100
Subject: [PATCH 157/180] lock the avahi thread before modifying the strings
---
mdns_avahi.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/mdns_avahi.c b/mdns_avahi.c
index c4568078..95984419 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -334,6 +334,7 @@ static int avahi_update(char **txt_records, char **secondary_txt_records) {
else
selected_interface = AVAHI_IF_UNSPEC;
+ avahi_threaded_poll_lock(tpoll);
if (txt_records != NULL) {
if (text_record_string_list)
avahi_string_list_free(text_record_string_list);
@@ -356,6 +357,8 @@ static int avahi_update(char **txt_records, char **secondary_txt_records) {
if (err != 0)
debug(1, "avahi_update error updating secondary txt records.");
}
+
+ avahi_threaded_poll_unlock(tpoll);
return 0;
}
From 063b02cb11593094cc7963e2493c3b879275cfd8 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 16:40:04 +0100
Subject: [PATCH 158/180] Mark the airplay_gid free by nulling it.
---
player.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/player.c b/player.c
index c95588e9..a61dc841 100644
--- a/player.c
+++ b/player.c
@@ -1705,8 +1705,10 @@ void player_thread_cleanup_handler(void *arg) {
if (conn->stream.type == ast_apple_lossless)
terminate_decoders(conn);
- if (conn->airplay_gid)
+ if (conn->airplay_gid) {
free(conn->airplay_gid);
+ conn->airplay_gid = NULL;
+ }
reset_anchor_info(conn);
release_play_lock(conn);
From e2153d275de02798d5bb8f69cf8c37eff6b08f07 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 16:40:43 +0100
Subject: [PATCH 159/180] Add new type of airplasy stream
---
player.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/player.h b/player.h
index e8d661b5..48755997 100644
--- a/player.h
+++ b/player.h
@@ -104,7 +104,7 @@ typedef struct {
#ifdef CONFIG_AIRPLAY_2
typedef enum { ts_ntp, ts_ptp } timing_t;
typedef enum { ap_1, ap_2 } airplay_t;
-typedef enum { realtime_stream, buffered_stream } airplay_stream_t;
+typedef enum { realtime_stream, buffered_stream, remote_control_stream } airplay_stream_t;
typedef struct {
uint8_t *data;
From f5e21ade647bda9ca1770cd9ca81677134b065a7 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 16:53:14 +0100
Subject: [PATCH 160/180] clang
---
mdns.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mdns.c b/mdns.c
index 7427dc7e..9a5465c1 100644
--- a/mdns.c
+++ b/mdns.c
@@ -110,7 +110,7 @@ void mdns_register(char **txt_records, char **secondary_txt_records) {
}
void mdns_update(char **txt_records, char **secondary_txt_records) {
-if ((config.mdns) && (config.mdns->mdns_update)) {
+ if ((config.mdns) && (config.mdns->mdns_update)) {
config.mdns->mdns_update(txt_records, secondary_txt_records);
} else
debug(1, "Can't mdns_update -- no mdns_update registered.");
From fcd57227ebf307ece3716aef092bb69462082ced Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 16:53:48 +0100
Subject: [PATCH 161/180] clang
---
player.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/player.h b/player.h
index 48755997..5e1e6e7b 100644
--- a/player.h
+++ b/player.h
@@ -287,7 +287,8 @@ typedef struct {
clock_status_t clock_status;
#ifdef CONFIG_AIRPLAY_2
- char *airplay_gid; // UUID in the Bonjour advertisement -- if NULL, the group UUID is the same as the pi UUID
+ char *airplay_gid; // UUID in the Bonjour advertisement -- if NULL, the group UUID is the same as
+ // the pi UUID
airplay_t airplay_type; // are we using AirPlay 1 or AirPlay 2 protocol on this connection?
airplay_stream_t airplay_stream_type; // is it realtime audio or buffered audio...
timing_t timing_type; // are we using NTP or PTP on this connection?
From 6ebc2517912c603f8a7ebad58465088385c8cf38 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 1 Sep 2021 16:54:19 +0100
Subject: [PATCH 162/180] quieten a few debug messages
---
rtsp.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index 4b04aa83..4ffe4fb2 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -2260,8 +2260,6 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
debug(2, "Connection %d: TEARDOWN Close Event Socket.", conn->connection_number);
if (conn->event_socket)
close(conn->event_socket);
-
- debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
if (conn->airplay_gid != NULL) {
free(conn->airplay_gid);
conn->airplay_gid = NULL;
@@ -2270,6 +2268,7 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
build_bonjour_strings(conn);
mdns_update(NULL, secondary_txt_records);
+ debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
}
//} else {
// warn("Connection %d TEARDOWN received without having the player (no ANNOUNCE?)",
@@ -2381,7 +2380,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
uint8_t value = 0;
plist_get_bool_val(groupContainsGroupLeader, &value);
conn->groupContainsGroupLeader = value;
- debug(1, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader);
+ debug(2, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader);
} else {
debug(1, "No groupContainsGroupLeader in SETUP");
}
From b24380d34b9433a599db93ab6cae35bdecd0c7b1 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 2 Sep 2021 12:20:53 +0100
Subject: [PATCH 163/180] Move the release of the group UUID to when the
connection ends, not when the play session ends.
---
player.c | 5 -----
rtsp.c | 6 ++++++
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/player.c b/player.c
index a61dc841..ac332a43 100644
--- a/player.c
+++ b/player.c
@@ -1705,11 +1705,6 @@ void player_thread_cleanup_handler(void *arg) {
if (conn->stream.type == ast_apple_lossless)
terminate_decoders(conn);
- if (conn->airplay_gid) {
- free(conn->airplay_gid);
- conn->airplay_gid = NULL;
- }
-
reset_anchor_info(conn);
release_play_lock(conn);
conn->rtp_running = 0;
diff --git a/rtsp.c b/rtsp.c
index 4ffe4fb2..8737a2da 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -2267,6 +2267,7 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
conn->groupContainsGroupLeader = 0;
config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
build_bonjour_strings(conn);
+ debug(1,"Connection %d: TEARDOWN mdns_update.", conn->connection_number);
mdns_update(NULL, secondary_txt_records);
debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
}
@@ -2707,6 +2708,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
build_bonjour_strings(conn);
+ debug(1,"Connection %d: SETUP mdns_update.", conn->connection_number);
mdns_update(NULL, secondary_txt_records);
} else {
debug(1, "SETUP on Connection %d: Unrecognised SETUP incoming message from %s",
@@ -4298,6 +4300,10 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
pair_setup_free(conn->ap2_control_pairing.setup_ctx);
pair_verify_free(conn->ap2_control_pairing.verify_ctx);
pair_cipher_free(conn->ap2_control_pairing.cipher_ctx);
+ if (conn->airplay_gid) {
+ free(conn->airplay_gid);
+ conn->airplay_gid = NULL;
+ }
#endif
rtp_terminate(conn);
From f058d118c18ddc8b6ae28f91ba2b4f0b44faaf6b Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 3 Sep 2021 15:38:28 +0100
Subject: [PATCH 164/180] Quieten a really noise message on verbosity 3.
---
audio_alsa.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/audio_alsa.c b/audio_alsa.c
index 4aa6ee9b..3afdd2ed 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -1667,7 +1667,7 @@ int do_play(void *buf, int samples) {
}
snd_pcm_state_t prior_state = state; // keep this for afterwards....
- debug(3, "alsa: write %d frames.", samples);
+ // debug(3, "alsa: write %d frames.", samples);
ret = alsa_pcm_write(alsa_handle, buf, samples);
if (ret == samples) {
stall_monitor_frame_count += samples;
From 078b7080874d9eba4d336de797e034a562592a92 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 3 Sep 2021 15:41:47 +0100
Subject: [PATCH 165/180] Uncomment code to list the contents of an incoming
message. Get the source airplay version. Quieten some messages.
---
rtsp.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index 8737a2da..98d0a75c 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -840,7 +840,7 @@ int msg_handle_line(rtsp_message **pmsg, char *line) {
*p = 0;
p += 2;
msg_add_header(msg, line, p);
- // debug(3, " %s: %s.", line, p);
+ debug(3, " %s: %s.", line, p);
return -1;
} else {
char *cl = msg_get_header(msg, "Content-Length");
@@ -1408,8 +1408,19 @@ void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
void handle_get_info(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
- if (rtsp_message_contains_plist(req)) { // it's stage one
+
+ if (rtsp_message_contains_plist(req)) { // it's stage one
+ // get version of AirPlay -- it might be too old. Not using it yet.
+ char *hdr = msg_get_header(req, "User-Agent");
+ if (hdr) {
+ if (strstr(hdr, "AirPlay/") == hdr) {
+ hdr = hdr + strlen("AirPlay/");
+ //double airplay_version = 0.0;
+ //airplay_version = atof(hdr);
+ debug(1,"Connection %d: GET_INFO: Source AirPlay Version is: %s.",conn->connection_number, hdr);
+ }
+ }
plist_t info_plist = NULL;
plist_from_memory(req->content, req->contentlength, &info_plist);
@@ -1490,7 +1501,7 @@ void handle_get_info(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *r
}
void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
- debug(3, "Connection %d: FLUSHBUFFERED %s :: Content-Length %d", conn->connection_number,
+ debug(3, "Connection %d: FLUSHBUFFERED %s : Content-Length %d", conn->connection_number,
req->path, req->contentlength);
debug_log_rtsp_message(2, "FLUSHBUFFERED request", req);
@@ -2713,7 +2724,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
} else {
debug(1, "SETUP on Connection %d: Unrecognised SETUP incoming message from %s",
conn->connection_number, (const char *)conn->client_ip_string);
- debug_log_rtsp_message(1, "Unrecognised SETUP incoming message.", req);
+ debug_log_rtsp_message(2, "Unrecognised SETUP incoming message.", req);
warn("Unrecognised SETUP incoming message -- ignored.");
}
debug_log_rtsp_message(2, " SETUP response", resp);
From ae1c4f1d2938207f2f386b5a588d7f9f012a4a78 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 4 Sep 2021 15:08:44 +0100
Subject: [PATCH 166/180] redo handle_setup to identify ptp, ntp and remote
control streams, returns 400 for remote control and unrecognised stuff.
---
player.h | 10 +-
rtsp.c | 751 ++++++++++++++++++++++++++++++-------------------------
2 files changed, 419 insertions(+), 342 deletions(-)
diff --git a/player.h b/player.h
index 5e1e6e7b..d980f1c2 100644
--- a/player.h
+++ b/player.h
@@ -104,7 +104,13 @@ typedef struct {
#ifdef CONFIG_AIRPLAY_2
typedef enum { ts_ntp, ts_ptp } timing_t;
typedef enum { ap_1, ap_2 } airplay_t;
-typedef enum { realtime_stream, buffered_stream, remote_control_stream } airplay_stream_t;
+typedef enum {
+ unknown_stream_category,
+ ptp_stream,
+ ntp_stream,
+ remote_control_stream
+} airplay_stream_c; // "c" for category
+typedef enum { realtime_stream, buffered_stream } airplay_stream_t;
typedef struct {
uint8_t *data;
@@ -290,6 +296,8 @@ typedef struct {
char *airplay_gid; // UUID in the Bonjour advertisement -- if NULL, the group UUID is the same as
// the pi UUID
airplay_t airplay_type; // are we using AirPlay 1 or AirPlay 2 protocol on this connection?
+ airplay_stream_c
+ airplay_stream_category; // is it a remote control stream or a normal "full service" stream?
airplay_stream_t airplay_stream_type; // is it realtime audio or buffered audio...
timing_t timing_type; // are we using NTP or PTP on this connection?
diff --git a/rtsp.c b/rtsp.c
index 98d0a75c..f0a1c495 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1409,16 +1409,16 @@ void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
void handle_get_info(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
-
if (rtsp_message_contains_plist(req)) { // it's stage one
- // get version of AirPlay -- it might be too old. Not using it yet.
+ // get version of AirPlay -- it might be too old. Not using it yet.
char *hdr = msg_get_header(req, "User-Agent");
if (hdr) {
if (strstr(hdr, "AirPlay/") == hdr) {
hdr = hdr + strlen("AirPlay/");
- //double airplay_version = 0.0;
- //airplay_version = atof(hdr);
- debug(1,"Connection %d: GET_INFO: Source AirPlay Version is: %s.",conn->connection_number, hdr);
+ // double airplay_version = 0.0;
+ // airplay_version = atof(hdr);
+ debug(1, "Connection %d: GET_INFO: Source AirPlay Version is: %s.", conn->connection_number,
+ hdr);
}
}
plist_t info_plist = NULL;
@@ -2278,7 +2278,7 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
conn->groupContainsGroupLeader = 0;
config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
build_bonjour_strings(conn);
- debug(1,"Connection %d: TEARDOWN mdns_update.", conn->connection_number);
+ debug(1, "Connection %d: TEARDOWN mdns_update.", conn->connection_number);
mdns_update(NULL, secondary_txt_records);
debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
}
@@ -2362,373 +2362,442 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
#ifdef CONFIG_AIRPLAY_2
void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int err;
- debug(1, "Connection %d: SETUP (AirPlay 2)", conn->connection_number);
+ debug(2, "Connection %d: SETUP (AirPlay 2)", conn->connection_number);
// debug_log_rtsp_message(1, "SETUP (AirPlay 2) SETUP incoming message", req);
- // we need to get the timing peer interfaces.
- // I'm guessing they are all this device's adresses that are on the same subnets as
- // the timing peer info in the accompanying plist
plist_t messagePlist = plist_from_rtsp_content(req);
- plist_t setupResponsePlist = NULL; // we'll create it when we need it
+ plist_t setupResponsePlist = plist_new_dict();
+ resp->respcode = 400;
- plist_t groupUUID = plist_dict_get_item(messagePlist, "groupUUID");
- if (groupUUID) {
- char *gid = NULL;
- plist_get_string_val(groupUUID, &gid);
- if (gid) {
- if (conn->airplay_gid)
- free(conn->airplay_gid);
- conn->airplay_gid = gid; // it'll be free'd later on...
- } else {
- debug(1, "Invalid groupUUID");
- }
- } else {
- debug(1, "No groupUUID in SETUP");
- }
-
- // now see if the group contains a group leader
- plist_t groupContainsGroupLeader = plist_dict_get_item(messagePlist, "groupContainsGroupLeader");
- if (groupContainsGroupLeader) {
- uint8_t value = 0;
- plist_get_bool_val(groupContainsGroupLeader, &value);
- conn->groupContainsGroupLeader = value;
- debug(2, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader);
- } else {
- debug(1, "No groupContainsGroupLeader in SETUP");
- }
-
- // now see if the incoming plist contains a "streams" array
+ // see if the incoming plist contains a "streams" array
plist_t streams = plist_dict_get_item(messagePlist, "streams");
-
- if (streams) {
- setupResponsePlist = plist_new_dict(); // now that we know we need it, create it
- debug(1,
- "Connection %d: SETUP. A \"streams\" array has been found -- create control and audio "
- "threads and ports",
- conn->connection_number);
- // open a player)
-
- // if (ptp_shm_interface_open() != 0)
- // warn("Error opening the nqptp clock daemon!");
-
- // get stream[0]
- plist_t stream0 = plist_array_get_item(streams, 0);
-
- 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);
- }
-
- pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn);
-
- // get the session key
-
- plist_t item = plist_dict_get_item(stream0, "shk"); // session key
- uint64_t item_value = 0;
- plist_get_data_val(item, (char **)&conn->session_key, &item_value);
-
- // 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. Realtime Audio Stream Detected.", 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);
- }
-
- 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");
- if (conn->ap2_timing_peer_list_message)
- ptp_send_control_message_string(conn->ap2_timing_peer_list_message);
- else
- debug(1, "No timing peer list!");
- activity_monitor_signify_activity(1);
- player_prepare_to_play(conn);
- player_play(conn);
-
- conn->rtp_running = 1; // hack!
- } break;
- case 103: {
- debug(1, "SETUP on Connection %d. Buffered Audio Stream Detected.", 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);
- }
-
- // 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);
-
- } else {
- debug(1,
+ if (streams == NULL) {
+ // no "streams" plist, so it must (?) be an initial setup
+ debug(2,
"SETUP on Connection %d: No \"streams\" array has been found -- create an event thread "
"and open a TCP port.",
conn->connection_number);
-
- // now determine which of the present device's IP numbers are in the
- // same subnet as any IP numbers in the timing peer info
- // provided by the client
-
- plist_t timing_peer_info = plist_dict_get_item(messagePlist, "timingPeerInfo");
- if (timing_peer_info) {
- // first, get and retain the incoming plist.
- setupResponsePlist = plist_new_dict(); // now that we know we need it, create it
- plist_t addresses = plist_new_array(); // to hold the device's peer interfaces
- plist_t addresses_array = plist_dict_get_item(timing_peer_info, "Addresses");
- if (addresses_array) {
- // iterate through the array of items
- uint32_t items = plist_array_get_size(addresses_array);
- if (items) {
-
- uint32_t item;
- struct ifaddrs *addrs, *iap;
- getifaddrs(&addrs);
- for (item = 0; item < items; item++) {
- plist_t n = plist_array_get_item(addresses_array, item);
- char *ip_address = NULL;
- plist_get_string_val(n, &ip_address);
- // debug(1, "Timing peer: %s", ip_address);
- // find its family and convert it into an IPv4/6 address
-
- struct sockaddr_storage peer_address;
- struct sockaddr_in6 *pa6 = (struct sockaddr_in6 *)&peer_address;
- struct sockaddr_in *pa4 = (struct sockaddr_in *)&peer_address;
-
- if (inet_pton(AF_INET6, ip_address, &pa6->sin6_addr) == 1) { // is an IPv6 address...
- peer_address.ss_family = AF_INET6;
- for (iap = addrs; iap != NULL; iap = iap->ifa_next) {
- if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) &&
- ((iap->ifa_flags & IFF_LOOPBACK) == 0) &&
- (iap->ifa_addr->sa_family == AF_INET6)) {
- struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(iap->ifa_addr);
- struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *)(iap->ifa_netmask);
- unsigned int i;
- int different = 0;
- for (i = 0; ((i < 16) && (different == 0)); i++) {
- unsigned char host_byte =
- (addr6->sin6_addr.s6_addr[i] & mask6->sin6_addr.s6_addr[i]);
- unsigned char peer_byte =
- (pa6->sin6_addr.s6_addr[i] & mask6->sin6_addr.s6_addr[i]);
- if (host_byte != peer_byte)
- different = 1;
- }
-
- char buf[32];
- memset(buf, 0, sizeof(buf));
- inet_ntop(AF_INET6, (void *)&addr6->sin6_addr, buf, sizeof(buf));
- if (!different) {
- // debug(1, "%s is in the same subnet as %s.", buf, ip_address);
- plist_array_append_item(addresses, plist_new_string(buf));
- }
- }
- }
- } else if (inet_pton(AF_INET, ip_address, &pa4->sin_addr) == 1) {
- peer_address.ss_family = AF_INET;
- for (iap = addrs; iap != NULL; iap = iap->ifa_next) {
- if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) &&
- ((iap->ifa_flags & IFF_LOOPBACK) == 0) &&
- (iap->ifa_addr->sa_family == AF_INET)) {
- struct sockaddr_in *addr = (struct sockaddr_in *)(iap->ifa_addr);
- struct sockaddr_in *mask = (struct sockaddr_in *)(iap->ifa_netmask);
- if ((addr->sin_addr.s_addr & mask->sin_addr.s_addr) ==
- (pa4->sin_addr.s_addr & mask->sin_addr.s_addr)) {
- char buf[32];
- memset(buf, 0, sizeof(buf));
- inet_ntop(AF_INET, (void *)&addr->sin_addr, buf, sizeof(buf));
- // debug(1, "%s is in the same subnet as %s.", buf, ip_address);
- plist_array_append_item(addresses, plist_new_string(buf));
- }
- }
- }
+ // figure out what category of stream it is, by looking at the plist
+ conn->airplay_stream_category = unknown_stream_category;
+ plist_t timingProtocol = plist_dict_get_item(messagePlist, "timingProtocol");
+ if (timingProtocol != NULL) {
+ char *timingProtocolString = NULL;
+ plist_get_string_val(timingProtocol, &timingProtocolString);
+ if (timingProtocolString) {
+ if (strcmp(timingProtocolString, "PTP") == 0) {
+ debug(1, "SETUP on Connection %d: PTP setup detected.", conn->connection_number);
+ conn->airplay_stream_category = ptp_stream;
+ conn->timing_type = ts_ptp;
+ } else if (strcmp(timingProtocolString, "NTP") == 0) {
+ debug(1, "SETUP on Connection %d: NTP setup detected.", conn->connection_number);
+ conn->airplay_stream_category = ntp_stream;
+ conn->timing_type = ts_ntp;
+ } else if (strcmp(timingProtocolString, "None") == 0) {
+ debug(2, "SETUP on Connection %d: a \"None\" setup detected.", conn->connection_number);
+ // now check to see if it's got the "isRemoteControlOnly" item and check it's true
+ plist_t isRemoteControlOnly = plist_dict_get_item(messagePlist, "isRemoteControlOnly");
+ if (isRemoteControlOnly != NULL) {
+ uint8_t isRemoteControlOnlyBoolean = 0;
+ plist_get_bool_val(isRemoteControlOnly, &isRemoteControlOnlyBoolean);
+ if (isRemoteControlOnlyBoolean != 0) {
+ debug(1, "SETUP on Connection %d: Remote Control setup detected.",
+ conn->connection_number);
+ conn->airplay_stream_category = remote_control_stream;
} else {
- debug(1, "SETUP on Connection %d: Don't recognise this as an IP address.",
+ debug(1,
+ "SETUP on Connection %d: a \"None\" setup detected, with "
+ "\"isRemoteControlOnly\" item set to \"false\".",
conn->connection_number);
}
- free(ip_address);
+ } else {
+ debug(1,
+ "SETUP on Connection %d: a \"None\" setup detected, but no "
+ "\"isRemoteControlOnly\" item detected.",
+ conn->connection_number);
}
- freeifaddrs(addrs);
- } else {
- debug(1, "SETUP on Connection %d: No timingPeerInfo addresses in the array.",
- conn->connection_number);
}
+
+ // here, we know it's an initial setup and we know the kind of setup being requested
+ // if it's a full service PTP stream, we get groupUUID, groupContainsGroupLeader and
+ // timingPeerList
+ if (conn->airplay_stream_category == ptp_stream) {
+ debug_log_rtsp_message(2, "SETUP \"PTP\" message", req);
+ plist_t groupUUID = plist_dict_get_item(messagePlist, "groupUUID");
+ if (groupUUID) {
+ char *gid = NULL;
+ plist_get_string_val(groupUUID, &gid);
+ if (gid) {
+ if (conn->airplay_gid)
+ free(conn->airplay_gid);
+ conn->airplay_gid = gid; // it'll be free'd later on...
+ } else {
+ debug(1, "Invalid groupUUID");
+ }
+ } else {
+ debug(1, "No groupUUID in SETUP");
+ }
+
+ // now see if the group contains a group leader
+ plist_t groupContainsGroupLeader =
+ plist_dict_get_item(messagePlist, "groupContainsGroupLeader");
+ if (groupContainsGroupLeader) {
+ uint8_t value = 0;
+ plist_get_bool_val(groupContainsGroupLeader, &value);
+ conn->groupContainsGroupLeader = value;
+ debug(2, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader);
+ } else {
+ debug(1, "No groupContainsGroupLeader in SETUP");
+ }
+
+ plist_t timing_peer_info = plist_dict_get_item(messagePlist, "timingPeerInfo");
+ if (timing_peer_info) {
+ // first, get and retain the incoming plist.
+ plist_t addresses = plist_new_array(); // to hold the device's peer interfaces
+ plist_t addresses_array = plist_dict_get_item(timing_peer_info, "Addresses");
+ if (addresses_array) {
+ // iterate through the array of items
+ uint32_t items = plist_array_get_size(addresses_array);
+ if (items) {
+
+ uint32_t item;
+ struct ifaddrs *addrs, *iap;
+ getifaddrs(&addrs);
+ for (item = 0; item < items; item++) {
+ plist_t n = plist_array_get_item(addresses_array, item);
+ char *ip_address = NULL;
+ plist_get_string_val(n, &ip_address);
+ // debug(1, "Timing peer: %s", ip_address);
+ // find its family and convert it into an IPv4/6 address
+
+ struct sockaddr_storage peer_address;
+ struct sockaddr_in6 *pa6 = (struct sockaddr_in6 *)&peer_address;
+ struct sockaddr_in *pa4 = (struct sockaddr_in *)&peer_address;
+
+ if (inet_pton(AF_INET6, ip_address, &pa6->sin6_addr) ==
+ 1) { // is an IPv6 address...
+ peer_address.ss_family = AF_INET6;
+ for (iap = addrs; iap != NULL; iap = iap->ifa_next) {
+ if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) &&
+ ((iap->ifa_flags & IFF_LOOPBACK) == 0) &&
+ (iap->ifa_addr->sa_family == AF_INET6)) {
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(iap->ifa_addr);
+ struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *)(iap->ifa_netmask);
+ unsigned int i;
+ int different = 0;
+ for (i = 0; ((i < 16) && (different == 0)); i++) {
+ unsigned char host_byte =
+ (addr6->sin6_addr.s6_addr[i] & mask6->sin6_addr.s6_addr[i]);
+ unsigned char peer_byte =
+ (pa6->sin6_addr.s6_addr[i] & mask6->sin6_addr.s6_addr[i]);
+ if (host_byte != peer_byte)
+ different = 1;
+ }
+
+ char buf[32];
+ memset(buf, 0, sizeof(buf));
+ inet_ntop(AF_INET6, (void *)&addr6->sin6_addr, buf, sizeof(buf));
+ if (!different) {
+ // debug(1, "%s is in the same subnet as %s.", buf, ip_address);
+ plist_array_append_item(addresses, plist_new_string(buf));
+ }
+ }
+ }
+ } else if (inet_pton(AF_INET, ip_address, &pa4->sin_addr) == 1) {
+ peer_address.ss_family = AF_INET;
+ for (iap = addrs; iap != NULL; iap = iap->ifa_next) {
+ if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) &&
+ ((iap->ifa_flags & IFF_LOOPBACK) == 0) &&
+ (iap->ifa_addr->sa_family == AF_INET)) {
+ struct sockaddr_in *addr = (struct sockaddr_in *)(iap->ifa_addr);
+ struct sockaddr_in *mask = (struct sockaddr_in *)(iap->ifa_netmask);
+ if ((addr->sin_addr.s_addr & mask->sin_addr.s_addr) ==
+ (pa4->sin_addr.s_addr & mask->sin_addr.s_addr)) {
+ char buf[32];
+ memset(buf, 0, sizeof(buf));
+ inet_ntop(AF_INET, (void *)&addr->sin_addr, buf, sizeof(buf));
+ // debug(1, "%s is in the same subnet as %s.", buf, ip_address);
+ plist_array_append_item(addresses, plist_new_string(buf));
+ }
+ }
+ }
+ } else {
+ debug(1, "SETUP on Connection %d: Don't recognise this as an IP address.",
+ conn->connection_number);
+ }
+ free(ip_address);
+ }
+ freeifaddrs(addrs);
+ } else {
+ debug(1, "SETUP on Connection %d: No timingPeerInfo addresses in the array.",
+ conn->connection_number);
+ }
+ } else {
+ debug(1, "SETUP on Connection %d: Can't find timingPeerInfo addresses",
+ conn->connection_number);
+ }
+ // make up the timing peer info list part of the response...
+ // debug(1,"Create timingPeerInfoPlist");
+ plist_t timingPeerInfoPlist = plist_new_dict();
+ plist_dict_set_item(timingPeerInfoPlist, "Addresses", addresses);
+ plist_dict_set_item(timingPeerInfoPlist, "ID", plist_new_string(conn->self_ip_string));
+ plist_dict_set_item(setupResponsePlist, "timingPeerInfo", timingPeerInfoPlist);
+ // get a port to use as an event port
+ // bind a new TCP port and get a socket
+ conn->local_event_port = 0; // any port
+ int err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family,
+ conn->self_ip_string, conn->self_scope_id,
+ &conn->local_event_port, &conn->event_socket);
+ if (err) {
+ die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event "
+ "port",
+ conn->connection_number, err);
+ }
+
+ pthread_create(&conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
+
+ plist_dict_set_item(setupResponsePlist, "eventPort",
+ plist_new_uint(conn->local_event_port));
+ plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy
+ resp->respcode = 200;
+ } else {
+ debug(1, "SETUP on Connection %d: PTP setup -- no timingPeerInfo plist.",
+ conn->connection_number);
+ }
+ } else if (conn->airplay_stream_category == ntp_stream) {
+ debug(1, "SETUP on Connection %d: ntp stream handling is not implemented!",
+ conn->connection_number, req);
+ warn("Shairport Sync can not handle NTP streams.");
+ } else if (conn->airplay_stream_category == remote_control_stream) {
+ debug_log_rtsp_message(1, "SETUP \"isRemoteControlOnly\" message", req);
+ // resp->respcode = 200;
+ } else {
+ debug(1, "SETUP on Connection %d: an unrecognised \"%s\" setup detected.",
+ conn->connection_number, timingProtocolString);
+ warn("Shairport Sync can not handle streams of this type: \"%s\".", timingProtocolString);
+ }
+ free(timingProtocolString);
} else {
- debug(1, "SETUP on Connection %d: Can't find timingPeerInfo addresses",
+ debug(1, "SETUP on Connection %d: Can't retrieve timingProtocol string in initial SETUP.",
conn->connection_number);
}
- // make up the timing peer info list part of the response...
- // debug(1,"Create timingPeerInfoPlist");
- plist_t timingPeerInfoPlist = plist_new_dict();
- plist_dict_set_item(timingPeerInfoPlist, "Addresses", addresses);
- plist_dict_set_item(timingPeerInfoPlist, "ID", plist_new_string(conn->self_ip_string));
- plist_dict_set_item(setupResponsePlist, "timingPeerInfo", timingPeerInfoPlist);
- // get a port to use as an event port
+ } else {
+ debug(1,
+ "SETUP on Connection %d: Unrecognised SETUP incoming message from \"%s\": no "
+ "timingProtocol or streams plist found.",
+ conn->connection_number, (const char *)conn->client_ip_string);
+ debug_log_rtsp_message(2, "Unrecognised SETUP incoming message.", req);
+ warn("Unrecognised SETUP incoming message -- ignored.");
+ }
+ } else {
+ debug(2,
+ "Connection %d: SETUP. A \"streams\" array has been found -- create control and audio "
+ "threads and ports",
+ conn->connection_number);
+ if (conn->airplay_stream_category == ptp_stream) {
+ // get stream[0]
+ plist_t stream0 = plist_array_get_item(streams, 0);
- // bind a new TCP port and get a socket
- conn->local_event_port = 0; // any port
+ 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
- int err =
- bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string,
- conn->self_scope_id, &conn->local_event_port, &conn->event_socket);
+ // 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("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event port",
- conn->connection_number, err);
+ die("Error %d: could not find a UDP port to use as an ap2_control port", err);
}
- pthread_create(&conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
+ pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn);
- plist_dict_set_item(setupResponsePlist, "eventPort", plist_new_uint(conn->local_event_port));
- plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy
+ // get the session key
+
+ plist_t item = plist_dict_get_item(stream0, "shk"); // session key
+ uint64_t item_value = 0;
+ plist_get_data_val(item, (char **)&conn->session_key, &item_value);
+
+ // 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. Realtime Audio Stream Detected.", 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);
+ }
+
+ 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");
+ if (conn->ap2_timing_peer_list_message)
+ ptp_send_control_message_string(conn->ap2_timing_peer_list_message);
+ else
+ debug(1, "No timing peer list!");
+ activity_monitor_signify_activity(1);
+ player_prepare_to_play(conn);
+ player_play(conn);
+
+ conn->rtp_running = 1; // hack!
+ } break;
+ case 103: {
+ debug(1, "SETUP on Connection %d. Buffered Audio Stream Detected.",
+ 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);
+ }
+
+ // 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(1, "SETUP on Connection %d: Remote Control Stream received. Nothing done.",
+ conn->connection_number);
+ // resp->respcode = 200;
+ } else {
+ debug(1, "SETUP on Connection %d: Stream received but no airplay category set. Nothing done.",
+ conn->connection_number);
}
}
- if (setupResponsePlist) {
+
+ if (resp->respcode == 200) {
plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength);
plist_free(setupResponsePlist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
build_bonjour_strings(conn);
- debug(1,"Connection %d: SETUP mdns_update.", conn->connection_number);
+ debug(1, "Connection %d: SETUP mdns_update.", conn->connection_number);
mdns_update(NULL, secondary_txt_records);
- } else {
- debug(1, "SETUP on Connection %d: Unrecognised SETUP incoming message from %s",
- conn->connection_number, (const char *)conn->client_ip_string);
- debug_log_rtsp_message(2, "Unrecognised SETUP incoming message.", req);
- warn("Unrecognised SETUP incoming message -- ignored.");
}
debug_log_rtsp_message(2, " SETUP response", resp);
- resp->respcode = 200;
}
#endif
From 8d477d73dcb0e444d27a6f3f28f3d363334bf992 Mon Sep 17 00:00:00 2001
From: Mao Liu <1684060+mao-liu@users.noreply.github.com>
Date: Sat, 4 Sep 2021 14:31:15 +0000
Subject: [PATCH 167/180] small change to fix codec loading for
ubuntu-18/ffmpeg-3.4
---
rtp.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rtp.c b/rtp.c
index e074e113..e7324570 100644
--- a/rtp.c
+++ b/rtp.c
@@ -2114,8 +2114,8 @@ void *rtp_buffered_audio_processor(void *arg) {
// initialize all muxers, demuxers and protocols for libavformat
// (does nothing if called twice during the course of one program execution)
- // not needed in ffmpeg 4.0 and later...
- // av_register_all();
+ // not needed in ffmpeg 4.0 and later... but still needed in ffmpeg 3.6 / ubuntu 18
+ avcodec_register_all();
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
if (codec == NULL) {
From c9b34cc367e9e59e9cb3b2d95214840f439a356b Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 5 Sep 2021 09:09:44 +0100
Subject: [PATCH 168/180] Update rtp.c
Stop a deprecated warning.
---
rtp.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/rtp.c b/rtp.c
index e7324570..c4ac08b5 100644
--- a/rtp.c
+++ b/rtp.c
@@ -2115,7 +2115,11 @@ void *rtp_buffered_audio_processor(void *arg) {
// initialize all muxers, demuxers and protocols for libavformat
// (does nothing if called twice during the course of one program execution)
// not needed in ffmpeg 4.0 and later... but still needed in ffmpeg 3.6 / ubuntu 18
+
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
avcodec_register_all();
+ #pragma GCC diagnostic pop
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
if (codec == NULL) {
From dd795d9157ca4f651265ab5c7e1f211579564f7d Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 5 Sep 2021 10:12:41 +0100
Subject: [PATCH 169/180] To address Issue 1107, set the number of items to
buffer to 255. Thanks to https://github.com/PieGuy314 and
https://github.com/Hello71 for reporting.
---
rtsp.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index f0a1c495..3ef385da 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -2792,7 +2792,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength);
plist_free(setupResponsePlist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
- config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
+ // config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
build_bonjour_strings(conn);
debug(1, "Connection %d: SETUP mdns_update.", conn->connection_number);
mdns_update(NULL, secondary_txt_records);
@@ -4737,7 +4737,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
debug(1, "unable to listen on %s port %d. The error is: \"%s\".", family, config.port,
strerror(errno));
} else {
- listen(fd, 5);
+ listen(fd, 255);
nsock++;
sockfd = realloc(sockfd, (nsock + 1) * sizeof(int));
sockfd[nsock] = fd;
From 8ec99e806f86cbc29a1806b23f499a9e78d79d7c Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 5 Sep 2021 10:13:17 +0100
Subject: [PATCH 170/180] Slight comment change.
---
rtp.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/rtp.c b/rtp.c
index c4ac08b5..618e068b 100644
--- a/rtp.c
+++ b/rtp.c
@@ -290,8 +290,8 @@ void *rtp_control_receiver(void *arg) {
obfp += 2;
};
*obfp = 0;
-
-
+
+
// get raw timestamp information
// I think that a good way to understand these timestamps is that
// (1) the rtlt below is the timestamp of the frame that should be playing at the
@@ -302,19 +302,19 @@ void *rtp_control_receiver(void *arg) {
// Thus, (3) the latency can be calculated by subtracting the second from the
// first.
// There must be more to it -- there something missing.
-
+
// In addition, it seems that if the value of the short represented by the second
// pair of bytes in the packet is 7
// then an extra time lag is expected to be added, presumably by
// the AirPort Express.
-
+
// Best guess is that this delay is 11,025 frames.
-
+
uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
uint32_t rt = nctohl(&packet[16]); // raw timestamp
-
+
uint32_t fl = nctohs(&packet[2]); //
-
+
debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u,
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
//debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
@@ -2114,8 +2114,8 @@ void *rtp_buffered_audio_processor(void *arg) {
// initialize all muxers, demuxers and protocols for libavformat
// (does nothing if called twice during the course of one program execution)
- // not needed in ffmpeg 4.0 and later... but still needed in ffmpeg 3.6 / ubuntu 18
-
+ // deprecated in ffmpeg 4.0 and later... but still needed in ffmpeg 3.6 / ubuntu 18
+
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
avcodec_register_all();
From 1fc00e13e96f91014d337b2b171ebf226ca0aeca Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Sep 2021 09:11:24 +0100
Subject: [PATCH 171/180] Seems to work better with iOS / HomePod mini speaker
selection. Does not recover discarded rtsp stream memory!
---
player.h | 3 +-
rtsp.c | 145 ++++++++++++++++++++++++++++++++++++++-----------------
2 files changed, 103 insertions(+), 45 deletions(-)
diff --git a/player.h b/player.h
index d980f1c2..64e9e3cb 100644
--- a/player.h
+++ b/player.h
@@ -105,7 +105,7 @@ typedef struct {
typedef enum { ts_ntp, ts_ptp } timing_t;
typedef enum { ap_1, ap_2 } airplay_t;
typedef enum {
- unknown_stream_category,
+ unspecified_stream_category = 0,
ptp_stream,
ntp_stream,
remote_control_stream
@@ -144,6 +144,7 @@ typedef struct flush_request_t {
typedef struct {
int connection_number; // for debug ID purposes, nothing else...
int resend_interval; // this is really just for debugging
+ int has_been_cancelled; // for cleaning up later.
char *UserAgent; // free this on teardown
int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency
uint32_t latency; // the actual latency used for this play session
diff --git a/rtsp.c b/rtsp.c
index 3ef385da..7d4e5ecd 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -624,20 +624,30 @@ static void track_thread(rtsp_conn_info *conn) {
}
}
-void cancel_all_RTSP_threads(void) {
+// note: connection numbers start at 1, so an except_this_one value of zero means "all threads"
+void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_one) {
+ // if the stream category is unspecified_stream_category
+ // all categories are elegible for cancellation
+ // otherwise just the category itself
+
int i;
for (i = 0; i < nconns; i++) {
- debug(2, "Connection %d: cancelling.", conns[i]->connection_number);
- pthread_cancel(conns[i]->thread);
+ if ((conns[i]->has_been_cancelled == 0) && (conns[i]->connection_number != except_this_one) && ((stream_category == unspecified_stream_category) || (stream_category == conns[i]->airplay_stream_category))) {
+ debug(2, "Connection %d: cancelling.", conns[i]->connection_number);
+ pthread_cancel(conns[i]->thread);
+ }
}
for (i = 0; i < nconns; i++) {
- debug(2, "Connection %d: joining.", conns[i]->connection_number);
- pthread_join(conns[i]->thread, NULL);
- free(conns[i]);
+ if ((conns[i]->has_been_cancelled == 0) && (conns[i]->connection_number != except_this_one) && ((stream_category == unspecified_stream_category) || (stream_category == conns[i]->airplay_stream_category))) {
+ debug(2, "Connection %d: joining.", conns[i]->connection_number);
+ pthread_join(conns[i]->thread, NULL);
+ conns[i]->has_been_cancelled = 1;
+ }
}
}
void cleanup_threads(void) {
+ /*
void *retval;
int i;
// debug(2, "culling threads.");
@@ -655,6 +665,7 @@ void cleanup_threads(void) {
i++;
}
}
+ */
}
// park a null at the line ending, and return the next line pointer
@@ -1350,9 +1361,31 @@ int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) {
return 0;
}
+char *get_category_string(airplay_stream_c cat) {
+ char *category;
+ switch (cat) {
+ case unspecified_stream_category:
+ category = "unspecified stream";
+ break;
+ case ptp_stream:
+ category = "PTP stream";
+ break;
+ case ntp_stream:
+ category = "NTP stream";
+ break;
+ case remote_control_stream:
+ category = "Remote Control stream";
+ break;
+ default:
+ category = "Unexpected stream code";
+ break;
+ }
+ return category;
+}
+
void handle_record_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
- debug(2, "Connection %d: RECORD (AirPlay 2)", conn->connection_number);
- debug_log_rtsp_message(2, "RECORD incoming message", req);
+ debug(1, "Connection %d: RECORD on %s", conn->connection_number, get_category_string(conn->airplay_stream_category));
+ //debug_log_rtsp_message(1, "RECORD incoming message", req);
resp->respcode = 200;
}
@@ -1417,7 +1450,7 @@ void handle_get_info(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *r
hdr = hdr + strlen("AirPlay/");
// double airplay_version = 0.0;
// airplay_version = atof(hdr);
- debug(1, "Connection %d: GET_INFO: Source AirPlay Version is: %s.", conn->connection_number,
+ debug(2, "Connection %d: GET_INFO: Source AirPlay Version is: %s.", conn->connection_number,
hdr);
}
}
@@ -2241,7 +2274,7 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *
void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
- // debug_log_rtsp_message(1, "TEARDOWN: ", req);
+ debug_log_rtsp_message(2, "TEARDOWN: ", req);
// if (have_player(conn)) {
resp->respcode = 200;
msg_add_header(resp, "Connection", "close");
@@ -2252,7 +2285,7 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
plist_t streams = plist_dict_get_item(messagePlist, "streams");
if (streams) {
- debug(1, "Connection %d: TEARDOWN a stream.", conn->connection_number);
+ debug(1, "Connection %d: TEARDOWN a %s.", conn->connection_number, get_category_string(conn->airplay_stream_category));
// we are being asked to close a stream
player_stop(conn);
activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
@@ -2264,22 +2297,26 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
debug(2, "Connection %d: Stream TEARDOWN complete", conn->connection_number);
} else {
// we are being asked to disconnect
- debug(1, "Connection %d: TEARDOWN a connection.", conn->connection_number);
+ debug(1, "Connection %d: TEARDOWN a %s connection.", conn->connection_number, get_category_string(conn->airplay_stream_category));
debug(2, "Connection %d: TEARDOWN Delete Event Thread.", conn->connection_number);
pthread_cancel(conn->rtp_event_thread);
pthread_join(conn->rtp_event_thread, NULL);
debug(2, "Connection %d: TEARDOWN Close Event Socket.", conn->connection_number);
if (conn->event_socket)
close(conn->event_socket);
- if (conn->airplay_gid != NULL) {
- free(conn->airplay_gid);
- conn->airplay_gid = NULL;
+
+ // if we are closing a PTP stream only, do this
+ if (conn->airplay_stream_category == ptp_stream) {
+ if (conn->airplay_gid != NULL) {
+ free(conn->airplay_gid);
+ conn->airplay_gid = NULL;
+ }
+ conn->groupContainsGroupLeader = 0;
+ config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
+ build_bonjour_strings(conn);
+ debug(1, "Connection %d: TEARDOWN mdns_update on %s.", conn->connection_number, get_category_string(conn->airplay_stream_category));
+ mdns_update(NULL, secondary_txt_records);
}
- conn->groupContainsGroupLeader = 0;
- config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
- build_bonjour_strings(conn);
- debug(1, "Connection %d: TEARDOWN mdns_update.", conn->connection_number);
- mdns_update(NULL, secondary_txt_records);
debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
}
//} else {
@@ -2374,44 +2411,44 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
if (streams == NULL) {
// no "streams" plist, so it must (?) be an initial setup
debug(2,
- "SETUP on Connection %d: No \"streams\" array has been found -- create an event thread "
+ "Connection %d SETUP: No \"streams\" array has been found -- create an event thread "
"and open a TCP port.",
conn->connection_number);
+ conn->airplay_stream_category = unspecified_stream_category;
// figure out what category of stream it is, by looking at the plist
- conn->airplay_stream_category = unknown_stream_category;
plist_t timingProtocol = plist_dict_get_item(messagePlist, "timingProtocol");
if (timingProtocol != NULL) {
char *timingProtocolString = NULL;
plist_get_string_val(timingProtocol, &timingProtocolString);
if (timingProtocolString) {
if (strcmp(timingProtocolString, "PTP") == 0) {
- debug(1, "SETUP on Connection %d: PTP setup detected.", conn->connection_number);
+ debug(1, "Connection %d: SETUP: PTP setup detected.", conn->connection_number);
conn->airplay_stream_category = ptp_stream;
conn->timing_type = ts_ptp;
} else if (strcmp(timingProtocolString, "NTP") == 0) {
- debug(1, "SETUP on Connection %d: NTP setup detected.", conn->connection_number);
+ debug(1, "Connection %d: SETUP: NTP setup detected.", conn->connection_number);
conn->airplay_stream_category = ntp_stream;
conn->timing_type = ts_ntp;
} else if (strcmp(timingProtocolString, "None") == 0) {
- debug(2, "SETUP on Connection %d: a \"None\" setup detected.", conn->connection_number);
+ debug(2, "Connection %d: SETUP: a \"None\" setup detected.", conn->connection_number);
// now check to see if it's got the "isRemoteControlOnly" item and check it's true
plist_t isRemoteControlOnly = plist_dict_get_item(messagePlist, "isRemoteControlOnly");
if (isRemoteControlOnly != NULL) {
uint8_t isRemoteControlOnlyBoolean = 0;
plist_get_bool_val(isRemoteControlOnly, &isRemoteControlOnlyBoolean);
if (isRemoteControlOnlyBoolean != 0) {
- debug(1, "SETUP on Connection %d: Remote Control setup detected.",
+ debug(1, "Connection %d: SETUP: Remote Control setup detected.",
conn->connection_number);
conn->airplay_stream_category = remote_control_stream;
} else {
debug(1,
- "SETUP on Connection %d: a \"None\" setup detected, with "
+ "Connection %d: SETUP: a \"None\" setup detected, with "
"\"isRemoteControlOnly\" item set to \"false\".",
conn->connection_number);
}
} else {
debug(1,
- "SETUP on Connection %d: a \"None\" setup detected, but no "
+ "Connection %d: SETUP: a \"None\" setup detected, but no "
"\"isRemoteControlOnly\" item detected.",
conn->connection_number);
}
@@ -2558,6 +2595,13 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_dict_set_item(setupResponsePlist, "eventPort",
plist_new_uint(conn->local_event_port));
plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy
+ cancel_all_RTSP_threads(unspecified_stream_category, conn->connection_number); // kill all the other listeners
+
+ config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
+ build_bonjour_strings(conn);
+ debug(1, "Connection %d: SETUP mdns_update on %s.", conn->connection_number, get_category_string(conn->airplay_stream_category));
+ mdns_update(NULL, secondary_txt_records);
+
resp->respcode = 200;
} else {
debug(1, "SETUP on Connection %d: PTP setup -- no timingPeerInfo plist.",
@@ -2568,8 +2612,26 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->connection_number, req);
warn("Shairport Sync can not handle NTP streams.");
} else if (conn->airplay_stream_category == remote_control_stream) {
- debug_log_rtsp_message(1, "SETUP \"isRemoteControlOnly\" message", req);
- // resp->respcode = 200;
+ debug_log_rtsp_message(2, "SETUP \"isRemoteControlOnly\" message", req);
+
+ // get a port to use as an event port
+ // bind a new TCP port and get a socket
+ conn->local_event_port = 0; // any port
+ int err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family,
+ conn->self_ip_string, conn->self_scope_id,
+ &conn->local_event_port, &conn->event_socket);
+ if (err) {
+ die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event "
+ "port",
+ conn->connection_number, err);
+ }
+
+ pthread_create(&conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
+
+ plist_dict_set_item(setupResponsePlist, "eventPort",
+ plist_new_uint(conn->local_event_port));
+ cancel_all_RTSP_threads(remote_control_stream, conn->connection_number); // kill all the other remote control listeners
+ resp->respcode = 200;
} else {
debug(1, "SETUP on Connection %d: an unrecognised \"%s\" setup detected.",
conn->connection_number, timingProtocolString);
@@ -2590,9 +2652,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
}
} else {
debug(2,
- "Connection %d: SETUP. A \"streams\" array has been found -- create control and audio "
- "threads and ports",
- conn->connection_number);
+ "Connection %d: SETUP on %s. A \"streams\" array has been found",
+ conn->connection_number, get_category_string(conn->airplay_stream_category));
if (conn->airplay_stream_category == ptp_stream) {
// get stream[0]
plist_t stream0 = plist_array_get_item(streams, 0);
@@ -2779,11 +2840,11 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_dict_set_item(setupResponsePlist, "streams", streams_array);
resp->respcode = 200;
} else if (conn->airplay_stream_category == remote_control_stream) {
- debug(1, "SETUP on Connection %d: Remote Control Stream received. Nothing done.",
+ debug(1, "Connection %d: SETUP: Remote Control Stream received.",
conn->connection_number);
- // resp->respcode = 200;
+ resp->respcode = 200;
} else {
- debug(1, "SETUP on Connection %d: Stream received but no airplay category set. Nothing done.",
+ debug(1, "Connection %d: SETUP: Stream received but no airplay category set. Nothing done.",
conn->connection_number);
}
}
@@ -2792,10 +2853,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength);
plist_free(setupResponsePlist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
- // config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
- build_bonjour_strings(conn);
- debug(1, "Connection %d: SETUP mdns_update.", conn->connection_number);
- mdns_update(NULL, secondary_txt_records);
}
debug_log_rtsp_message(2, " SETUP response", resp);
}
@@ -2808,7 +2865,7 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
uint16_t cport, tport;
char *ar = msg_get_header(req, "Active-Remote");
if (ar) {
- debug(2, "Connection %d: SETUP -- Active-Remote string seen: \"%s\".",
+ debug(2, "Connection %d: SETUP: 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
@@ -2818,7 +2875,7 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1);
#endif
} else {
- debug(2, "Connection %d: SETUP -- Note: no Active-Remote information the SETUP Record.",
+ debug(2, "Connection %d: SETUP: Note: no Active-Remote information seen.",
conn->connection_number);
if (conn->dacp_active_remote) { // this is in case SETUP was previously called
free(conn->dacp_active_remote);
@@ -2828,7 +2885,7 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
ar = msg_get_header(req, "DACP-ID");
if (ar) {
- debug(2, "Connection %d: SETUP -- DACP-ID string seen: \"%s\".", conn->connection_number, ar);
+ debug(2, "Connection %d: SETUP: 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);
@@ -4645,7 +4702,7 @@ void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
debug(2, "rtsp_listen_loop_cleanup_handler called.");
- cancel_all_RTSP_threads();
+ cancel_all_RTSP_threads(unspecified_stream_category,0); // kill all RTSP listeners
int *sockfd = (int *)arg;
mdns_unregister();
if (sockfd) {
From 7ff6d07c83fdcea71d8953a6cac59919e4211719 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Sep 2021 11:02:59 +0100
Subject: [PATCH 172/180] Don't need 'had_been_cancelled'. Remoted.
---
player.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/player.h b/player.h
index 64e9e3cb..2badc274 100644
--- a/player.h
+++ b/player.h
@@ -144,7 +144,6 @@ typedef struct flush_request_t {
typedef struct {
int connection_number; // for debug ID purposes, nothing else...
int resend_interval; // this is really just for debugging
- int has_been_cancelled; // for cleaning up later.
char *UserAgent; // free this on teardown
int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency
uint32_t latency; // the actual latency used for this play session
From 8049f1e60b3c7ff435889980f9fecb6425304efc Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Sep 2021 11:05:20 +0100
Subject: [PATCH 173/180] Improve cancelled connection memory recovery. Remove
some trick-bicycling programming from connection memory recovery. Use
mutexes. Recover cancelled connection memory immediately.
---
rtsp.c | 68 +++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 44 insertions(+), 24 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index 7d4e5ecd..2c6a887a 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -130,9 +130,13 @@ rtsp_conn_info **conns;
int metadata_running = 0;
-// always lock use this when accessing the playing conn value
+// always lock this when accessing the playing conn value
pthread_mutex_t playing_conn_lock = PTHREAD_MUTEX_INITIALIZER;
+// always lock this when accessing the list of connection threads
+pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER;
+
+
// every time we want to retain or release a reference count, lock it with this
// if a reference count is read as zero, it means the it's being deallocated.
static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -615,13 +619,29 @@ void *player_watchdog_thread_code(void *arg) {
// keep track of the threads we have spawned so we can join() them
static int nconns = 0;
static void track_thread(rtsp_conn_info *conn) {
- conns = realloc(conns, sizeof(rtsp_conn_info *) * (nconns + 1));
- if (conns) {
- conns[nconns] = conn;
- nconns++;
- } else {
- die("could not reallocate memnory for \"conns\" in rtsp.c.");
+ debug_mutex_lock(&conns_lock, 1000000, 3);
+ // look for an empty slot first
+ int i = 0;
+ int found = 0;
+ while ((i < nconns) && (found == 0)) {
+ if (conns[i] == NULL)
+ found = 1;
+ else
+ i++;
}
+ if (found != 0){
+ conns[i] = conn;
+ } else {
+ // make space for a new element
+ conns = realloc(conns, sizeof(rtsp_conn_info *) * (nconns + 1));
+ if (conns) {
+ conns[nconns] = conn;
+ nconns++;
+ } else {
+ die("could not reallocate memory for conns");
+ }
+ }
+ debug_mutex_unlock(&conns_lock, 3);
}
// note: connection numbers start at 1, so an except_this_one value of zero means "all threads"
@@ -629,43 +649,43 @@ void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_o
// if the stream category is unspecified_stream_category
// all categories are elegible for cancellation
// otherwise just the category itself
-
+ debug_mutex_lock(&conns_lock, 1000000, 3);
int i;
for (i = 0; i < nconns; i++) {
- if ((conns[i]->has_been_cancelled == 0) && (conns[i]->connection_number != except_this_one) && ((stream_category == unspecified_stream_category) || (stream_category == conns[i]->airplay_stream_category))) {
- debug(2, "Connection %d: cancelling.", conns[i]->connection_number);
+ if ((conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) && ((stream_category == unspecified_stream_category) || (stream_category == conns[i]->airplay_stream_category))) {
+ debug(1, "Connection %d: stopped.", conns[i]->connection_number);
pthread_cancel(conns[i]->thread);
}
}
for (i = 0; i < nconns; i++) {
- if ((conns[i]->has_been_cancelled == 0) && (conns[i]->connection_number != except_this_one) && ((stream_category == unspecified_stream_category) || (stream_category == conns[i]->airplay_stream_category))) {
- debug(2, "Connection %d: joining.", conns[i]->connection_number);
+ if ((conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) && ((stream_category == unspecified_stream_category) || (stream_category == conns[i]->airplay_stream_category))) {
+ debug(1, "Connection %d: deleted.", conns[i]->connection_number);
pthread_join(conns[i]->thread, NULL);
- conns[i]->has_been_cancelled = 1;
+ free(conns[i]);
+ conns[i] = NULL;
}
}
+ debug_mutex_unlock(&conns_lock, 3);
}
void cleanup_threads(void) {
- /*
+
void *retval;
int i;
// debug(2, "culling threads.");
- for (i = 0; i < nconns;) {
- if (conns[i]->running == 0) {
+ debug_mutex_lock(&conns_lock, 1000000, 3);
+ for (i = 0; i < nconns; i++) {
+ if ((conns[i] != NULL ) && (conns[i]->running == 0)) {
debug(3, "found RTSP connection thread %d in a non-running state.",
conns[i]->connection_number);
pthread_join(conns[i]->thread, &retval);
- debug(3, "RTSP connection thread %d deleted...", conns[i]->connection_number);
+ debug(1, "Connection %d: deleted in cleanup.", conns[i]->connection_number);
free(conns[i]);
- nconns--;
- if (nconns)
- conns[i] = conns[nconns];
- } else {
- i++;
+ conns[i] = NULL;
}
}
- */
+ debug_mutex_unlock(&conns_lock, 3);
+
}
// park a null at the line ending, and return the next line pointer
@@ -1383,7 +1403,7 @@ char *get_category_string(airplay_stream_c cat) {
return category;
}
-void handle_record_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req, rtsp_message *resp) {
debug(1, "Connection %d: RECORD on %s", conn->connection_number, get_category_string(conn->airplay_stream_category));
//debug_log_rtsp_message(1, "RECORD incoming message", req);
resp->respcode = 200;
From 7def758f1367ce01c34e87685369a3c5a6933cab Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Sep 2021 14:30:13 +0100
Subject: [PATCH 174/180] Clang
---
rtsp.c | 83 ++++++++++++++++++++++++++++++++--------------------------
1 file changed, 46 insertions(+), 37 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index 2c6a887a..ba8b3fc2 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -136,7 +136,6 @@ pthread_mutex_t playing_conn_lock = PTHREAD_MUTEX_INITIALIZER;
// always lock this when accessing the list of connection threads
pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER;
-
// every time we want to retain or release a reference count, lock it with this
// if a reference count is read as zero, it means the it's being deallocated.
static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -629,7 +628,7 @@ static void track_thread(rtsp_conn_info *conn) {
else
i++;
}
- if (found != 0){
+ if (found != 0) {
conns[i] = conn;
} else {
// make space for a new element
@@ -652,13 +651,17 @@ void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_o
debug_mutex_lock(&conns_lock, 1000000, 3);
int i;
for (i = 0; i < nconns; i++) {
- if ((conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) && ((stream_category == unspecified_stream_category) || (stream_category == conns[i]->airplay_stream_category))) {
+ if ((conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) &&
+ ((stream_category == unspecified_stream_category) ||
+ (stream_category == conns[i]->airplay_stream_category))) {
debug(1, "Connection %d: stopped.", conns[i]->connection_number);
pthread_cancel(conns[i]->thread);
}
}
for (i = 0; i < nconns; i++) {
- if ((conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) && ((stream_category == unspecified_stream_category) || (stream_category == conns[i]->airplay_stream_category))) {
+ if ((conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) &&
+ ((stream_category == unspecified_stream_category) ||
+ (stream_category == conns[i]->airplay_stream_category))) {
debug(1, "Connection %d: deleted.", conns[i]->connection_number);
pthread_join(conns[i]->thread, NULL);
free(conns[i]);
@@ -675,7 +678,7 @@ void cleanup_threads(void) {
// debug(2, "culling threads.");
debug_mutex_lock(&conns_lock, 1000000, 3);
for (i = 0; i < nconns; i++) {
- if ((conns[i] != NULL ) && (conns[i]->running == 0)) {
+ if ((conns[i] != NULL) && (conns[i]->running == 0)) {
debug(3, "found RTSP connection thread %d in a non-running state.",
conns[i]->connection_number);
pthread_join(conns[i]->thread, &retval);
@@ -685,7 +688,6 @@ void cleanup_threads(void) {
}
}
debug_mutex_unlock(&conns_lock, 3);
-
}
// park a null at the line ending, and return the next line pointer
@@ -1384,28 +1386,30 @@ int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) {
char *get_category_string(airplay_stream_c cat) {
char *category;
switch (cat) {
- case unspecified_stream_category:
- category = "unspecified stream";
- break;
- case ptp_stream:
- category = "PTP stream";
- break;
- case ntp_stream:
- category = "NTP stream";
- break;
- case remote_control_stream:
- category = "Remote Control stream";
- break;
- default:
- category = "Unexpected stream code";
- break;
+ case unspecified_stream_category:
+ category = "unspecified stream";
+ break;
+ case ptp_stream:
+ category = "PTP stream";
+ break;
+ case ntp_stream:
+ category = "NTP stream";
+ break;
+ case remote_control_stream:
+ category = "Remote Control stream";
+ break;
+ default:
+ category = "Unexpected stream code";
+ break;
}
return category;
}
-void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req, rtsp_message *resp) {
- debug(1, "Connection %d: RECORD on %s", conn->connection_number, get_category_string(conn->airplay_stream_category));
- //debug_log_rtsp_message(1, "RECORD incoming message", req);
+void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req,
+ rtsp_message *resp) {
+ debug(1, "Connection %d: RECORD on %s", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ // debug_log_rtsp_message(1, "RECORD incoming message", req);
resp->respcode = 200;
}
@@ -2305,7 +2309,8 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
plist_t streams = plist_dict_get_item(messagePlist, "streams");
if (streams) {
- debug(1, "Connection %d: TEARDOWN a %s.", conn->connection_number, get_category_string(conn->airplay_stream_category));
+ debug(1, "Connection %d: TEARDOWN a %s.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
// we are being asked to close a stream
player_stop(conn);
activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
@@ -2317,7 +2322,8 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
debug(2, "Connection %d: Stream TEARDOWN complete", conn->connection_number);
} else {
// we are being asked to disconnect
- debug(1, "Connection %d: TEARDOWN a %s connection.", conn->connection_number, get_category_string(conn->airplay_stream_category));
+ debug(1, "Connection %d: TEARDOWN a %s connection.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
debug(2, "Connection %d: TEARDOWN Delete Event Thread.", conn->connection_number);
pthread_cancel(conn->rtp_event_thread);
pthread_join(conn->rtp_event_thread, NULL);
@@ -2334,7 +2340,8 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
conn->groupContainsGroupLeader = 0;
config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
build_bonjour_strings(conn);
- debug(1, "Connection %d: TEARDOWN mdns_update on %s.", conn->connection_number, get_category_string(conn->airplay_stream_category));
+ debug(1, "Connection %d: TEARDOWN mdns_update on %s.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
mdns_update(NULL, secondary_txt_records);
}
debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
@@ -2615,11 +2622,13 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_dict_set_item(setupResponsePlist, "eventPort",
plist_new_uint(conn->local_event_port));
plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy
- cancel_all_RTSP_threads(unspecified_stream_category, conn->connection_number); // kill all the other listeners
+ cancel_all_RTSP_threads(unspecified_stream_category,
+ conn->connection_number); // kill all the other listeners
config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
build_bonjour_strings(conn);
- debug(1, "Connection %d: SETUP mdns_update on %s.", conn->connection_number, get_category_string(conn->airplay_stream_category));
+ debug(1, "Connection %d: SETUP mdns_update on %s.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
mdns_update(NULL, secondary_txt_records);
resp->respcode = 200;
@@ -2650,7 +2659,9 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_dict_set_item(setupResponsePlist, "eventPort",
plist_new_uint(conn->local_event_port));
- cancel_all_RTSP_threads(remote_control_stream, conn->connection_number); // kill all the other remote control listeners
+ cancel_all_RTSP_threads(
+ remote_control_stream,
+ conn->connection_number); // kill all the other remote control listeners
resp->respcode = 200;
} else {
debug(1, "SETUP on Connection %d: an unrecognised \"%s\" setup detected.",
@@ -2671,8 +2682,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
warn("Unrecognised SETUP incoming message -- ignored.");
}
} else {
- debug(2,
- "Connection %d: SETUP on %s. A \"streams\" array has been found",
+ debug(2, "Connection %d: SETUP on %s. A \"streams\" array has been found",
conn->connection_number, get_category_string(conn->airplay_stream_category));
if (conn->airplay_stream_category == ptp_stream) {
// get stream[0]
@@ -2860,8 +2870,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_dict_set_item(setupResponsePlist, "streams", streams_array);
resp->respcode = 200;
} else if (conn->airplay_stream_category == remote_control_stream) {
- debug(1, "Connection %d: SETUP: Remote Control Stream received.",
- conn->connection_number);
+ debug(1, "Connection %d: SETUP: Remote Control Stream received.", conn->connection_number);
resp->respcode = 200;
} else {
debug(1, "Connection %d: SETUP: Stream received but no airplay category set. Nothing done.",
@@ -2885,8 +2894,8 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
uint16_t cport, tport;
char *ar = msg_get_header(req, "Active-Remote");
if (ar) {
- debug(2, "Connection %d: SETUP: Active-Remote string seen: \"%s\".",
- conn->connection_number, ar);
+ debug(2, "Connection %d: SETUP: 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);
@@ -4722,7 +4731,7 @@ void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
debug(2, "rtsp_listen_loop_cleanup_handler called.");
- cancel_all_RTSP_threads(unspecified_stream_category,0); // kill all RTSP listeners
+ cancel_all_RTSP_threads(unspecified_stream_category, 0); // kill all RTSP listeners
int *sockfd = (int *)arg;
mdns_unregister();
if (sockfd) {
From 9f9513f2e3ea670e2d4831fd11d662cd53ad3913 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 7 Sep 2021 07:44:28 +0100
Subject: [PATCH 175/180] Fix a crashing bug in cancel_all_RTSP_threads.
---
rtsp.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index ba8b3fc2..244ecd44 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -651,7 +651,7 @@ void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_o
debug_mutex_lock(&conns_lock, 1000000, 3);
int i;
for (i = 0; i < nconns; i++) {
- if ((conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) &&
+ if ((conns[i] != NULL) && (conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) &&
((stream_category == unspecified_stream_category) ||
(stream_category == conns[i]->airplay_stream_category))) {
debug(1, "Connection %d: stopped.", conns[i]->connection_number);
@@ -659,7 +659,7 @@ void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_o
}
}
for (i = 0; i < nconns; i++) {
- if ((conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) &&
+ if ((conns[i] != NULL) && (conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) &&
((stream_category == unspecified_stream_category) ||
(stream_category == conns[i]->airplay_stream_category))) {
debug(1, "Connection %d: deleted.", conns[i]->connection_number);
From 366eb27afc4b4e48d17aa76c52a8ef25144ab452 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 9 Sep 2021 11:23:25 +0100
Subject: [PATCH 176/180] Stop a message from repeating endlessly.
---
audio_alsa.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/audio_alsa.c b/audio_alsa.c
index 3afdd2ed..f723cb7d 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -438,7 +438,7 @@ int actual_open_alsa_device(int do_auto_setup) {
} else {
char errorstring[1024];
strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
- warn("alsa: error %d (\"%s\") opening alsa device \"%s\".", ret, (char *)errorstring,
+ die("alsa: error %d (\"%s\") opening alsa device \"%s\".", ret, (char *)errorstring,
alsa_out_dev);
}
return ret;
From 2e123cf39acf0ccaf562d4a65af02909db424ce0 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 9 Sep 2021 11:24:23 +0100
Subject: [PATCH 177/180] Fix compilation errors that accumulated with no
--with-airplay-2 flag set.
---
player.h | 14 +++++++++-----
rtsp.c | 14 ++++++++++++++
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/player.h b/player.h
index 2badc274..2097dfdd 100644
--- a/player.h
+++ b/player.h
@@ -101,15 +101,18 @@ typedef struct {
audio_stream_type type;
} stream_cfg;
-#ifdef CONFIG_AIRPLAY_2
-typedef enum { ts_ntp, ts_ptp } timing_t;
-typedef enum { ap_1, ap_2 } airplay_t;
+// the following is used even when not built for AirPlay 2
typedef enum {
unspecified_stream_category = 0,
ptp_stream,
ntp_stream,
remote_control_stream
} airplay_stream_c; // "c" for category
+
+
+#ifdef CONFIG_AIRPLAY_2
+typedef enum { ts_ntp, ts_ptp } timing_t;
+typedef enum { ap_1, ap_2 } airplay_t;
typedef enum { realtime_stream, buffered_stream } airplay_stream_t;
typedef struct {
@@ -292,12 +295,13 @@ typedef struct {
clock_status_t clock_status;
+ airplay_stream_c
+ airplay_stream_category; // is it a remote control stream or a normal "full service" stream? (will be unspecified if not build for AirPlay 2)
+
#ifdef CONFIG_AIRPLAY_2
char *airplay_gid; // UUID in the Bonjour advertisement -- if NULL, the group UUID is the same as
// the pi UUID
airplay_t airplay_type; // are we using AirPlay 1 or AirPlay 2 protocol on this connection?
- airplay_stream_c
- airplay_stream_category; // is it a remote control stream or a normal "full service" stream?
airplay_stream_t airplay_stream_type; // is it realtime audio or buffered audio...
timing_t timing_type; // are we using NTP or PTP on this connection?
diff --git a/rtsp.c b/rtsp.c
index 244ecd44..dccb21a6 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -199,7 +199,11 @@ static void pkString_make(char *str, size_t str_size, const char *device_id) {
}
#endif
+#ifdef CONFIG_AIRPLAY_2
void build_bonjour_strings(rtsp_conn_info *conn) {
+#else
+void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) {
+#endif
int entry_number = 0;
@@ -1767,6 +1771,12 @@ void handle_get(__attribute((unused)) rtsp_conn_info *conn, __attribute((unused)
req->contentlength);
resp->respcode = 500;
}
+void handle_post(__attribute((unused)) rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req,
+ __attribute((unused)) rtsp_message *resp) {
+ debug(1, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path,
+ req->contentlength);
+ resp->respcode = 500;
+}
#endif
#ifdef CONFIG_AIRPLAY_2
@@ -2871,6 +2881,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
resp->respcode = 200;
} else if (conn->airplay_stream_category == remote_control_stream) {
debug(1, "Connection %d: SETUP: Remote Control Stream received.", conn->connection_number);
+ debug_log_rtsp_message(1, "Remote Control Stream stream (second) message", req);
+
resp->respcode = 200;
} else {
debug(1, "Connection %d: SETUP: Stream received but no airplay category set. Nothing done.",
@@ -4166,6 +4178,8 @@ static struct method_handler {
void (*handler)(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp); // for AirPlay 1 only
} method_handlers[] = {{"OPTIONS", handle_options},
+ {"GET", handle_get},
+ {"POST", handle_post},
{"ANNOUNCE", handle_announce},
{"FLUSH", handle_flush},
{"TEARDOWN", handle_teardown},
From 2e4c391bf2c91273061754b67fb7771dddf4c183 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 10 Sep 2021 17:42:47 +0100
Subject: [PATCH 178/180] Change features flags in agreement with the metadata
settings in the config file.
---
shairport.c | 95 ++++++++++++++++++++++++++++-------------------------
1 file changed, 50 insertions(+), 45 deletions(-)
diff --git a/shairport.c b/shairport.c
index 9ab3f51a..fb7fdfea 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1584,49 +1584,6 @@ int main(int argc, char **argv) {
#ifdef CONFIG_AIRPLAY_2
config.timeout = 0; // disable watchdog
config.port = 7000;
- // the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32
- // bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as
- // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
- // 496155702020608 this setting here is the source of both the plist features response and the
- // mDNS string.
- // note: 0x300401F4A00 works but with weird delays and stuff
- // config.airplay_features = 0x1C340405FCA00;
- config.airplay_features = 0x1C340405D4A00; // APX + Authentication4 (b14)
- // Advertised with mDNS and returned with GET /info, see
- // https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN
- // required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio
- // cable attached, OneTimePairingRequired, device was setup for Homekit access control
- config.airplay_statusflags = 0x04;
- // Set to NULL to work with transient pairing
- config.airplay_pin = NULL;
-
- // use the config.hw_addr to generated an airplay_device_id
- {
- char obf[256] = {0};
- char *obfp = obf;
- int i;
- for (i = 0; i < 6; i++) {
- snprintf(obfp, 4, "%02X:", config.hw_addr[i]);
- obfp += 3;
- }
- obfp -= 1;
- *obfp = 0;
- config.airplay_device_id = strdup(obf);
- }
- debug(1, "Started in Airplay 2 mode on device \"%s\"!", config.airplay_device_id);
-
- // now generate a UUID
- // from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c
- // with thanks
- uuid_t binuuid;
- uuid_generate_random(binuuid);
-
- char *uuid = malloc(UUID_STR_LEN);
- // Produces a UUID string at uuid consisting of lower-case letters
- uuid_unparse_lower(binuuid, uuid);
- config.airplay_pi = strdup(uuid);
-
- // now we need to create the sequence of items for a Bonjour TXT record.
#else
config.port = 5000;
#endif
@@ -1820,10 +1777,58 @@ int main(int argc, char **argv) {
}
#endif
- debug(1, "Started!");
#ifdef CONFIG_AIRPLAY_2
- debug(1, "Started in Airplay 2 mode!");
+ // the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32
+ // bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as
+ // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
+ // 496155702020608 this setting here is the source of both the plist features response and the
+ // mDNS string.
+ // note: 0x300401F4A00 works but with weird delays and stuff
+ // config.airplay_features = 0x1C340405FCA00;
+ uint64_t mask = ((uint64_t)1 << 17) | ((uint64_t)1 << 16) | ((uint64_t)1 << 15) | ((uint64_t)1 << 50);
+ config.airplay_features = 0x1C340405D4A00 & ~mask; // APX + Authentication4 (b14) with no metadata (see below)
+ // Advertised with mDNS and returned with GET /info, see
+ // https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN
+ // required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio
+ // cable attached, OneTimePairingRequired, device was setup for Homekit access control
+
+ // If we are asking for metadata, turn on the relevant bits
+ if (config.metadata_enabled != 0)
+ config.airplay_features |= (1 << 17) | (1 << 16);
+ // If we are asking for artwork, turn on the relevant bit
+ if (config.get_coverart)
+ config.airplay_features |= (1 << 15);
+ debug(1,"Features: 0x%" PRIx64 ".", config.airplay_features);
+ config.airplay_statusflags = 0x04;
+ // Set to NULL to work with transient pairing
+ config.airplay_pin = NULL;
+
+ // use the config.hw_addr to generated an airplay_device_id
+ {
+ char obf[256] = {0};
+ char *obfp = obf;
+ int i;
+ for (i = 0; i < 6; i++) {
+ snprintf(obfp, 4, "%02X:", config.hw_addr[i]);
+ obfp += 3;
+ }
+ obfp -= 1;
+ *obfp = 0;
+ config.airplay_device_id = strdup(obf);
+ }
+
+ // now generate a UUID
+ // from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c
+ // with thanks
+ uuid_t binuuid;
+ uuid_generate_random(binuuid);
+
+ char *uuid = malloc(UUID_STR_LEN);
+ // Produces a UUID string at uuid consisting of lower-case letters
+ uuid_unparse_lower(binuuid, uuid);
+ config.airplay_pi = strdup(uuid);
+ debug(1, "Started in Airplay 2 mode on device \"%s\"!", config.airplay_device_id);
#else
debug(1, "Started in Airplay 1 mode!");
#endif
From 1438216878a66acb093d1f9cacdef24fd5cf7dfb Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 12 Sep 2021 15:24:48 +0100
Subject: [PATCH 179/180] Add full teardown actions to RTSP stream cleanup to
correctly close all connections.
---
rtp.c | 5 ++-
rtsp.c | 112 ++++++++++++++++++++++++++++++++++++---------------------
2 files changed, 76 insertions(+), 41 deletions(-)
diff --git a/rtp.c b/rtp.c
index 618e068b..86792369 100644
--- a/rtp.c
+++ b/rtp.c
@@ -1564,6 +1564,7 @@ void rtp_ap2_control_handler_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
debug(2, "Connection %d: AP2 Control Receiver Cleanup.", conn->connection_number);
close(conn->ap2_control_socket);
+ debug(1, "Connection %d: UDP control port %u closed.", conn->connection_number, conn->local_ap2_control_port);
conn->ap2_control_socket = 0;
conn->ap2_remote_control_socket_addr_length =
0; // indicates to the control receiver thread that the socket address need to be
@@ -1748,8 +1749,9 @@ void *rtp_ap2_control_receiver(void *arg) {
void rtp_realtime_audio_cleanup_handler(__attribute__((unused)) void *arg) {
debug(2, "Realtime Audio Receiver Cleanup Start.");
- rtsp_conn_info *conn = (rtsp_conn_info *)arg;
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
close(conn->realtime_audio_socket);
+ debug(1, "Connection %d: closing realtime audio port %u", conn->local_realtime_audio_port);
conn->realtime_audio_socket = 0;
debug(2, "Realtime Audio Receiver Cleanup Done.");
}
@@ -2028,6 +2030,7 @@ void rtp_buffered_audio_cleanup_handler(__attribute__((unused)) void *arg) {
debug(2, "Buffered Audio Receiver Cleanup Start.");
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
close(conn->buffered_audio_socket);
+ debug(1, "Connection %d: TCP Buffered Audio port closed: %u.", conn->connection_number, conn->local_buffered_audio_port);
conn->buffered_audio_socket = 0;
debug(2, "Buffered Audio Receiver Cleanup Done.");
}
diff --git a/rtsp.c b/rtsp.c
index dccb21a6..c8bec51c 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -658,16 +658,16 @@ void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_o
if ((conns[i] != NULL) && (conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) &&
((stream_category == unspecified_stream_category) ||
(stream_category == conns[i]->airplay_stream_category))) {
- debug(1, "Connection %d: stopped.", conns[i]->connection_number);
pthread_cancel(conns[i]->thread);
+ debug(1, "Connection %d: cancelled.", conns[i]->connection_number);
}
}
for (i = 0; i < nconns; i++) {
if ((conns[i] != NULL) && (conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) &&
((stream_category == unspecified_stream_category) ||
(stream_category == conns[i]->airplay_stream_category))) {
- debug(1, "Connection %d: deleted.", conns[i]->connection_number);
pthread_join(conns[i]->thread, NULL);
+ debug(1, "Connection %d: joined.", conns[i]->connection_number);
free(conns[i]);
conns[i] = NULL;
}
@@ -2305,6 +2305,47 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *
"OPTIONS, POST, GET, PUT");
}
+void teardown_phase_one(rtsp_conn_info *conn) {
+ if (conn->player_thread) {
+ player_stop(conn);
+ activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
+ }
+ if (conn->session_key) {
+ free(conn->session_key);
+ conn->session_key = NULL;
+ }
+}
+
+void teardown_phase_two(rtsp_conn_info *conn) {
+ // we are being asked to disconnect
+ debug(1, "Connection %d: TEARDOWN a %s connection.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ debug(2, "Connection %d: TEARDOWN Delete Event Thread.", conn->connection_number);
+ pthread_cancel(conn->rtp_event_thread);
+ pthread_join(conn->rtp_event_thread, NULL);
+ debug(1, "Connection %d: TEARDOWN Close Event Socket.", conn->connection_number);
+ if (conn->event_socket) {
+ close(conn->event_socket);
+ conn->event_socket = 0;
+ debug(1, "Connection %d: closing TCP event port %u.", conn->connection_number, conn->local_event_port);
+ }
+
+ // if we are closing a PTP stream only, do this
+ if (conn->airplay_stream_category == ptp_stream) {
+ if (conn->airplay_gid != NULL) {
+ free(conn->airplay_gid);
+ conn->airplay_gid = NULL;
+ }
+ conn->groupContainsGroupLeader = 0;
+ config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
+ build_bonjour_strings(conn);
+ debug(1, "Connection %d: TEARDOWN mdns_update on %s.", conn->connection_number,
+ get_category_string(conn->airplay_stream_category));
+ mdns_update(NULL, secondary_txt_records);
+ }
+}
+
+
void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
@@ -2322,38 +2363,11 @@ void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_messag
debug(1, "Connection %d: TEARDOWN a %s.", conn->connection_number,
get_category_string(conn->airplay_stream_category));
// we are being asked to close a stream
- player_stop(conn);
- activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
- if (conn->session_key) {
- free(conn->session_key);
- conn->session_key = NULL;
- }
+ teardown_phase_one(conn);
plist_free(streams);
debug(2, "Connection %d: Stream TEARDOWN complete", conn->connection_number);
} else {
- // we are being asked to disconnect
- debug(1, "Connection %d: TEARDOWN a %s connection.", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- debug(2, "Connection %d: TEARDOWN Delete Event Thread.", conn->connection_number);
- pthread_cancel(conn->rtp_event_thread);
- pthread_join(conn->rtp_event_thread, NULL);
- debug(2, "Connection %d: TEARDOWN Close Event Socket.", conn->connection_number);
- if (conn->event_socket)
- close(conn->event_socket);
-
- // if we are closing a PTP stream only, do this
- if (conn->airplay_stream_category == ptp_stream) {
- if (conn->airplay_gid != NULL) {
- free(conn->airplay_gid);
- conn->airplay_gid = NULL;
- }
- conn->groupContainsGroupLeader = 0;
- config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
- build_bonjour_strings(conn);
- debug(1, "Connection %d: TEARDOWN mdns_update on %s.", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- mdns_update(NULL, secondary_txt_records);
- }
+ teardown_phase_two(conn);
debug(2, "Connection %d: non-stream TEARDOWN complete", conn->connection_number);
}
//} else {
@@ -2627,6 +2641,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->connection_number, err);
}
+ debug(1, "Connection %d: TCP PTP event port opened: %u.", conn->connection_number, conn->local_event_port);
+
pthread_create(&conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
plist_dict_set_item(setupResponsePlist, "eventPort",
@@ -2664,6 +2680,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
"port",
conn->connection_number, err);
}
+
+ debug(1, "Connection %d: TCP Remote Control event port opened: %u.", conn->connection_number, conn->local_event_port);
pthread_create(&conn->rtp_event_thread, NULL, &rtp_event_receiver, (void *)conn);
@@ -2711,6 +2729,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
if (err) {
die("Error %d: could not find a UDP port to use as an ap2_control port", err);
}
+ debug(1, "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);
@@ -2780,6 +2799,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
if (err) {
die("Error %d: could not find a UDP port to use as a realtime_audio port", err);
}
+ debug(1, "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);
@@ -2842,6 +2862,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->connection_number, err);
}
+ debug(1, "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.
@@ -4448,26 +4470,36 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- debug(3, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.",
+ debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.",
conn->connection_number);
- if (conn->player_thread) {
- player_stop(conn);
- activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
- }
+ teardown_phase_one(conn);
+ teardown_phase_two(conn);
debug(3, "Connection %d terminating:Closing timing, control and audio sockets...",
conn->connection_number);
- if (conn->control_socket)
+ if (conn->control_socket) {
close(conn->control_socket);
- if (conn->timing_socket)
+ debug(1, "Connection %d: closing control port %u", conn->local_ap2_control_port);
+ }
+ if (conn->timing_socket) {
close(conn->timing_socket);
- if (conn->audio_socket)
+ debug(1, "Connection %d: closing timing port %u", conn->local_ap2_control_port);
+ }
+ if (conn->audio_socket) {
close(conn->audio_socket);
+ debug(1, "Connection %d: closing audio port %u", conn->local_ap2_control_port);
+ }
+
+
if (conn->fd > 0) {
debug(3, "Connection %d terminating: closing fd %d.", conn->connection_number, conn->fd);
close(conn->fd);
debug(3, "Connection %d terminating: closed fd %d.", conn->connection_number, conn->fd);
+
+ debug(1, "Connection %d: terminating connection from %s:%u to self at %s:%u.",
+ conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
+ conn->self_ip_string, conn->self_rtsp_port);
}
if (conn->auth_nonce) {
free(conn->auth_nonce);
@@ -4958,7 +4990,7 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string,
sizeof(conn->self_ip_string));
- debug(2, "Connection %d: new connection from %s:%u to self at %s:%u.",
+ debug(1, "Connection %d: new connection from %s:%u to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
conn->self_ip_string, conn->self_rtsp_port);
} else {
From e06be8c91673e396356f4002fa03399675059aac Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 12 Sep 2021 15:31:45 +0100
Subject: [PATCH 180/180] Allow compilation with AirPlay2 not selected.
---
rtsp.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index c8bec51c..62fbb7da 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -4472,22 +4472,21 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
debug(1, "Connection %d: rtsp_conversation_thread_func_cleanup_function called.",
conn->connection_number);
+#ifdef CONFIG_AIRPLAY_2
teardown_phase_one(conn);
teardown_phase_two(conn);
+#endif
debug(3, "Connection %d terminating:Closing timing, control and audio sockets...",
conn->connection_number);
if (conn->control_socket) {
close(conn->control_socket);
- debug(1, "Connection %d: closing control port %u", conn->local_ap2_control_port);
}
if (conn->timing_socket) {
close(conn->timing_socket);
- debug(1, "Connection %d: closing timing port %u", conn->local_ap2_control_port);
}
if (conn->audio_socket) {
close(conn->audio_socket);
- debug(1, "Connection %d: closing audio port %u", conn->local_ap2_control_port);
}