From 7b1c85b40b731e4f9ff1d9f7ca576a0b39b09ef7 Mon Sep 17 00:00:00 2001
From: Chris Boot
Date: Sun, 21 Jan 2018 13:02:29 +0000
Subject: Import shairport-sync_3.1.7.orig.tar.gz
[dgit import orig shairport-sync_3.1.7.orig.tar.gz]
---
.gitignore | 43 +
.travis.yml | 34 +
AUTHORS | 0
CONTRIBUTING.md | 24 +
COPYING | 1 +
CYGWIN.md | 49 +
ChangeLog | 0
FEDORA.md | 24 +
FFTConvolver/AudioFFT.cpp | 1018 ++++++++
FFTConvolver/AudioFFT.h | 176 ++
FFTConvolver/FFTConvolver.cpp | 202 ++
FFTConvolver/FFTConvolver.h | 100 +
FFTConvolver/Utilities.cpp | 113 +
FFTConvolver/Utilities.h | 352 +++
FFTConvolver/convolver.cpp | 67 +
FFTConvolver/convolver.h | 14 +
FREEBSD.md | 99 +
INSTALL.md | 4 +
LIBSOXR.md | 42 +
LICENSES | 35 +
Makefile.am | 161 ++
NEWS | 1 +
README | 0
README.md | 550 +++++
RELEASENOTES.md | 954 +++++++
TROUBLESHOOTING.md | 157 ++
UPDATING.md | 63 +
alac.c | 1010 ++++++++
alac.h | 48 +
apple_alac.cpp | 58 +
apple_alac.h | 20 +
audio.c | 187 ++
audio.h | 52 +
audio_alsa.c | 1029 ++++++++
audio_ao.c | 138 ++
audio_dummy.c | 68 +
audio_pa.c | 407 +++
audio_pipe.c | 141 ++
audio_sndio.c | 269 ++
audio_soundio.c | 224 ++
audio_stdout.c | 86 +
common.c | 864 +++++++
common.h | 241 ++
configure.ac | 335 +++
definitions.h | 37 +
documents/Fudge Factor Calculation.pdf | Bin 0 -> 15869 bytes
.../Shairport Volume Control Transfer Function.pdf | Bin 0 -> 32213 bytes
.../Fudge Factor Calculation.graffle | Bin 0 -> 2912 bytes
.../Shairport Volume Control Transfer Function.gcx | Bin 0 -> 101748 bytes
loudness.c | 50 +
loudness.h | 14 +
man/Makefile.am | 11 +
man/shairport-sync.7 | 418 ++++
man/shairport-sync.7.xml | 852 +++++++
man/shairport-sync.html | 847 +++++++
man/xmltoman.css | 36 +
man/xmltoman.dtd | 45 +
man/xmltoman.xsl | 135 +
mdns.c | 129 +
mdns.h | 49 +
mdns_avahi.c | 471 ++++
mdns_dns_sd.c | 97 +
mdns_external.c | 171 ++
mdns_tinysvcmdns.c | 168 ++
player.c | 2591 ++++++++++++++++++++
player.h | 208 ++
rtp.c | 803 ++++++
rtp.h | 28 +
rtsp.c | 2103 ++++++++++++++++
rtsp.h | 24 +
scripts/shairport-sync.conf | 146 ++
scripts/shairport-sync.freebsd | 28 +
scripts/shairport-sync.in | 175 ++
scripts/shairport-sync.service.in | 15 +
shairport-sync.spec | 122 +
shairport.c | 1569 ++++++++++++
tinysvcmdns.c | 1736 +++++++++++++
tinysvcmdns.h | 233 ++
78 files changed, 22771 insertions(+)
create mode 100644 .gitignore
create mode 100644 .travis.yml
create mode 100644 AUTHORS
create mode 100644 CONTRIBUTING.md
create mode 100644 COPYING
create mode 100644 CYGWIN.md
create mode 100644 ChangeLog
create mode 100644 FEDORA.md
create mode 100755 FFTConvolver/AudioFFT.cpp
create mode 100755 FFTConvolver/AudioFFT.h
create mode 100755 FFTConvolver/FFTConvolver.cpp
create mode 100755 FFTConvolver/FFTConvolver.h
create mode 100644 FFTConvolver/Utilities.cpp
create mode 100644 FFTConvolver/Utilities.h
create mode 100644 FFTConvolver/convolver.cpp
create mode 100644 FFTConvolver/convolver.h
create mode 100644 FREEBSD.md
create mode 100644 INSTALL.md
create mode 100644 LIBSOXR.md
create mode 100644 LICENSES
create mode 100644 Makefile.am
create mode 100644 NEWS
create mode 100644 README
create mode 100644 README.md
create mode 100644 RELEASENOTES.md
create mode 100644 TROUBLESHOOTING.md
create mode 100644 UPDATING.md
create mode 100644 alac.c
create mode 100644 alac.h
create mode 100644 apple_alac.cpp
create mode 100644 apple_alac.h
create mode 100644 audio.c
create mode 100644 audio.h
create mode 100644 audio_alsa.c
create mode 100644 audio_ao.c
create mode 100644 audio_dummy.c
create mode 100644 audio_pa.c
create mode 100644 audio_pipe.c
create mode 100644 audio_sndio.c
create mode 100644 audio_soundio.c
create mode 100644 audio_stdout.c
create mode 100644 common.c
create mode 100644 common.h
create mode 100644 configure.ac
create mode 100644 definitions.h
create mode 100644 documents/Fudge Factor Calculation.pdf
create mode 100644 documents/Shairport Volume Control Transfer Function.pdf
create mode 100644 documents/Source Documents/Fudge Factor Calculation.graffle
create mode 100644 documents/Source Documents/Shairport Volume Control Transfer Function.gcx
create mode 100644 loudness.c
create mode 100644 loudness.h
create mode 100644 man/Makefile.am
create mode 100644 man/shairport-sync.7
create mode 100644 man/shairport-sync.7.xml
create mode 100644 man/shairport-sync.html
create mode 100644 man/xmltoman.css
create mode 100644 man/xmltoman.dtd
create mode 100644 man/xmltoman.xsl
create mode 100644 mdns.c
create mode 100644 mdns.h
create mode 100644 mdns_avahi.c
create mode 100644 mdns_dns_sd.c
create mode 100644 mdns_external.c
create mode 100644 mdns_tinysvcmdns.c
create mode 100644 player.c
create mode 100644 player.h
create mode 100644 rtp.c
create mode 100644 rtp.h
create mode 100644 rtsp.c
create mode 100644 rtsp.h
create mode 100644 scripts/shairport-sync.conf
create mode 100644 scripts/shairport-sync.freebsd
create mode 100755 scripts/shairport-sync.in
create mode 100644 scripts/shairport-sync.service.in
create mode 100644 shairport-sync.spec
create mode 100644 shairport.c
create mode 100644 tinysvcmdns.c
create mode 100644 tinysvcmdns.h
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ffed6d6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+/INSTALL
+/shairport-sync
+/shairport-sync.exe
+/shairport-sync-dbus-test-client
+/shairport-sync-mpris-test-client
+
+*.o
+/*~
+*.xml~
+/config.mk
+/config.h
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.*
+configure
+depcomp
+install-sh
+missing
+stamp-h1
+.dirstamp
+.deps
+man/Makefile
+man/Makefile.in
+scripts/shairport-sync.service
+scripts/shairport-sync
+shairport-sync.core
+
+#Some dbus files that are automatically generated
+/org.gnome.ShairportSync.service
+/dbus-interface.*
+/mpris-interface.*
+/mpris-player-interface.*
+
+# Some eclipse project files
+.cproject
+.project
+
+# macOS stuff
+.DS_Store
+shairport-sync.xcodeproj
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..37c4fd6
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,34 @@
+language: c
+sudo: required
+dist: trusty
+env:
+ matrix:
+ - CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemv"
+ - CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemv"
+ - CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
+ - CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
+ - CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemv"
+ - CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemv"
+ - CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
+ - CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
+
+script:
+ - autoreconf -fi
+ - ./configure $CFG
+ - make
+
+before_install:
+ - sudo apt-get -qq update
+ - sudo apt-get install -y libdaemon-dev avahi-daemon libavahi-client-dev
+
+addons:
+ apt:
+ packages:
+ - libasound2-dev
+ - libsoxr-dev
+ - libpopt-dev
+ - libssl-dev
+ - libpolarssl-dev
+ - libconfig-dev
+ - xmltoman
+ - libao-dev
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e69de29
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ab4d544
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+
+Contributing to Shairport Sync
+====
+
+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.
+
+In general, a log verbosity of 2 is adequate (`-vv`, or the relevant entry in the configuration file), and it's usually helpful if statistics have been enabled (`--statistics` on the command line, or the relevant entry in the configuration file).
+
+If you are pasting in code or a log file, format it as code by preceding it and following it with a line containing exactly three backquotes and nothing else ([see here](https://guides.github.com/features/mastering-markdown/) for more on formatting):
+
+\`\`\`
+```
+code or log file entries
+```
+\`\`\`
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..6518e8f
--- /dev/null
+++ b/COPYING
@@ -0,0 +1 @@
+Please refer to the individual source files for licenses.
diff --git a/CYGWIN.md b/CYGWIN.md
new file mode 100644
index 0000000..3c9027b
--- /dev/null
+++ b/CYGWIN.md
@@ -0,0 +1,49 @@
+Installing to Cygwin
+----
+
+This is based on installing onto a fresh default installation of Cygwin 2.4.1 (64-bit installation) running in Windows 10
+inside VMWare Fusion on a Mac.
+
+Note: to enable Cygwin to resolve domain names, it was found necessary to go to Windows `Control Panel` > `Network and Internet` > `Network Connections` >
+`Ethernet0` > `Properties` > `Internet Protocol Version 4 (TCP/IPv4)` > `Properties`, select `Use the following DNS server addresses`
+and enter valid DNS server addresses, e.g. `8.8.8.8`.
+
+* Use `Cygwin Setup` to install the following packages:
+ * `pkg-config`
+ * `autoconf`
+ * `automake`
+ * `clang`
+ * `libdaemon-devel`
+ * `popt-devel`
+ * `make`
+ * `libao-devel`
+ * `openssl-devel`
+ * `libtool`
+ * `git`
+ * `wget` for convenience,
+ * `flex` for compiling `libconfig`
+ * `bison` for compiling `libconfig`
+
+* Download, configure, compile and install `libconfig`.
+
+For this, if your home directory name has a space in it, which happens by default with Cygwin, you should
+temporarily rename it to a single word, e.g. `Mike Brady` -> `mike`. This is to allow the `make install` script to work properly.
+```
+$ wget http://www.hyperrealm.com/libconfig/libconfig-1.5.tar.gz
+$ tar -xvzf libconfig-1.5.tar.gz
+$ cd libconfig-1.5
+$ ./configure
+$ make
+$ make install
+$ cd ..
+```
+* Next, download, configure and compile Shairport Sync:
+```
+$ git clone https://github.com/mikebrady/shairport-sync.git
+$ cd shairport-sync
+$ autoreconf -fi
+$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl --with-tinysvcmdns
+$ make
+$ make install
+```
+* That's it. There should be a `shairport-sync.exe` file in your directory.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
diff --git a/FEDORA.md b/FEDORA.md
new file mode 100644
index 0000000..ae581cb
--- /dev/null
+++ b/FEDORA.md
@@ -0,0 +1,24 @@
+Fedora Installation Guide
+-----
+
+Install the toolchain and pre-requisites, if necessary:
+```
+% sudo yum install make automake gcc gcc-c++ kernel-devel
+% sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-devel openssl-devel popt-devel soxr-devel
+```
+Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.6:
+```
+% 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
+```
+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):
+```
+%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:
+```
+%sudo systemctl enable shairport-sync.service
+%sudo systemctl start shairport-sync.service
+```
diff --git a/FFTConvolver/AudioFFT.cpp b/FFTConvolver/AudioFFT.cpp
new file mode 100755
index 0000000..32b3dda
--- /dev/null
+++ b/FFTConvolver/AudioFFT.cpp
@@ -0,0 +1,1018 @@
+// ==================================================================================
+// Copyright (c) 2016 HiFi-LoFi
+//
+// 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 "AudioFFT.h"
+
+#include
+#include
+#include
+
+
+#if defined(AUDIOFFT_APPLE_ACCELERATE)
+ #define AUDIOFFT_APPLE_ACCELERATE_USED
+ #include
+ #include
+#elif defined (AUDIOFFT_FFTW3)
+ #define AUDIOFFT_FFTW3_USED
+ #include
+#else
+ #if !defined(AUDIOFFT_OOURA)
+ #define AUDIOFFT_OOURA
+ #endif
+ #define AUDIOFFT_OOURA_USED
+ #include
+#endif
+
+
+namespace audiofft
+{
+
+ namespace details
+ {
+
+ static bool IsPowerOf2(size_t val)
+ {
+ return (val == 1 || (val & (val-1)) == 0);
+ }
+
+
+ template
+ void ConvertBuffer(TypeDest* dest, const TypeSrc* src, size_t len)
+ {
+ for (size_t i=0; i(src[i]);
+ }
+ }
+
+
+ template
+ void ScaleBuffer(TypeDest* dest, const TypeSrc* src, const TypeFactor factor, size_t len)
+ {
+ for (size_t i=0; i(static_cast(src[i]) * factor);
+ }
+ }
+
+
+ // ================================================================
+
+
+#ifdef AUDIOFFT_OOURA_USED
+
+ /**
+ * @internal
+ * @class OouraFFT
+ * @brief FFT implementation based on the great radix-4 routines by Takuya Ooura
+ */
+ class OouraFFT : public AudioFFTImpl
+ {
+ public:
+ OouraFFT() :
+ AudioFFTImpl(),
+ _size(0),
+ _ip(),
+ _w(),
+ _buffer()
+ {
+ }
+
+ virtual void init(size_t size) override
+ {
+ if (_size != size)
+ {
+ _ip.resize(2 + static_cast(std::sqrt(static_cast(size))));
+ _w.resize(size / 2);
+ _buffer.resize(size);
+ _size = size;
+
+ const int size4 = static_cast(_size) / 4;
+ makewt(size4, _ip.data(), _w.data());
+ makect(size4, _ip.data(), _w.data() + size4);
+ }
+ }
+
+ virtual void fft(const float* data, float* re, float* im) override
+ {
+ // Convert into the format as required by the Ooura FFT
+ ConvertBuffer(&_buffer[0], data, _size);
+
+ rdft(static_cast(_size), +1, _buffer.data(), _ip.data(), _w.data());
+
+ // Convert back to split-complex
+ {
+ double* b = &_buffer[0];
+ double* bEnd = b + _size;
+ float *r = re;
+ float *i = im;
+ while (b != bEnd)
+ {
+ *(r++) = static_cast(*(b++));
+ *(i++) = static_cast(-(*(b++)));
+ }
+ }
+ const size_t size2 = _size / 2;
+ re[size2] = -im[0];
+ im[0] = 0.0;
+ im[size2] = 0.0;
+ }
+
+ virtual void ifft(float* data, const float* re, const float* im) override
+ {
+ // Convert into the format as required by the Ooura FFT
+ {
+ double* b = &_buffer[0];
+ double* bEnd = b + _size;
+ const float *r = re;
+ const float *i = im;
+ while (b != bEnd)
+ {
+ *(b++) = static_cast(*(r++));
+ *(b++) = -static_cast(*(i++));
+ }
+ _buffer[1] = re[_size / 2];
+ }
+
+ rdft(static_cast(_size), -1, _buffer.data(), _ip.data(), _w.data());
+
+ // Convert back to split-complex
+ ScaleBuffer(data, &_buffer[0], 2.0 / static_cast(_size), _size);
+ }
+
+ private:
+ size_t _size;
+ std::vector _ip;
+ std::vector _w;
+ std::vector _buffer;
+
+ void rdft(int n, int isgn, double *a, int *ip, double *w)
+ {
+ int nw = ip[0];
+ int nc = ip[1];
+
+ if (isgn >= 0)
+ {
+ if (n > 4)
+ {
+ bitrv2(n, ip + 2, a);
+ cftfsub(n, a, w);
+ rftfsub(n, a, nc, w + nw);
+ }
+ else if (n == 4)
+ {
+ cftfsub(n, a, w);
+ }
+ double xi = a[0] - a[1];
+ a[0] += a[1];
+ a[1] = xi;
+ }
+ else
+ {
+ a[1] = 0.5 * (a[0] - a[1]);
+ a[0] -= a[1];
+ if (n > 4)
+ {
+ rftbsub(n, a, nc, w + nw);
+ bitrv2(n, ip + 2, a);
+ cftbsub(n, a, w);
+ }
+ else if (n == 4)
+ {
+ cftfsub(n, a, w);
+ }
+ }
+ }
+
+
+ /* -------- initializing routines -------- */
+
+ void makewt(int nw, int *ip, double *w)
+ {
+ int j, nwh;
+ double delta, x, y;
+
+ ip[0] = nw;
+ ip[1] = 1;
+ if (nw > 2) {
+ nwh = nw >> 1;
+ delta = atan(1.0) / nwh;
+ w[0] = 1;
+ w[1] = 0;
+ w[nwh] = cos(delta * nwh);
+ w[nwh + 1] = w[nwh];
+ if (nwh > 2) {
+ for (j = 2; j < nwh; j += 2) {
+ x = cos(delta * j);
+ y = sin(delta * j);
+ w[j] = x;
+ w[j + 1] = y;
+ w[nw - j] = y;
+ w[nw - j + 1] = x;
+ }
+ bitrv2(nw, ip + 2, w);
+ }
+ }
+ }
+
+
+ void makect(int nc, int *ip, double *c)
+ {
+ int j, nch;
+ double delta;
+
+ ip[1] = nc;
+ if (nc > 1) {
+ nch = nc >> 1;
+ delta = atan(1.0) / nch;
+ c[0] = cos(delta * nch);
+ c[nch] = 0.5 * c[0];
+ for (j = 1; j < nch; j++) {
+ c[j] = 0.5 * cos(delta * j);
+ c[nc - j] = 0.5 * sin(delta * j);
+ }
+ }
+ }
+
+
+ /* -------- child routines -------- */
+
+
+ void bitrv2(int n, int *ip, double *a)
+ {
+ int j, j1, k, k1, l, m, m2;
+ double xr, xi, yr, yi;
+
+ ip[0] = 0;
+ l = n;
+ m = 1;
+ while ((m << 3) < l) {
+ l >>= 1;
+ for (j = 0; j < m; j++) {
+ ip[m + j] = ip[j] + l;
+ }
+ m <<= 1;
+ }
+ m2 = 2 * m;
+ if ((m << 3) == l) {
+ for (k = 0; k < m; k++) {
+ for (j = 0; j < k; j++) {
+ j1 = 2 * j + ip[k];
+ k1 = 2 * k + ip[j];
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += 2 * m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 -= m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += 2 * m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ }
+ j1 = 2 * k + m2 + ip[k];
+ k1 = j1 + m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ }
+ } else {
+ for (k = 1; k < m; k++) {
+ for (j = 0; j < k; j++) {
+ j1 = 2 * j + ip[k];
+ k1 = 2 * k + ip[j];
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ }
+ }
+ }
+ }
+
+
+ void cftfsub(int n, double *a, double *w)
+ {
+ int j, j1, j2, j3, l;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ l = 2;
+ if (n > 8) {
+ cft1st(n, a, w);
+ l = 8;
+ while ((l << 2) < n) {
+ cftmdl(n, l, a, w);
+ l <<= 2;
+ }
+ }
+ if ((l << 2) == n) {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ a[j2] = x0r - x2r;
+ a[j2 + 1] = x0i - x2i;
+ a[j1] = x1r - x3i;
+ a[j1 + 1] = x1i + x3r;
+ a[j3] = x1r + x3i;
+ a[j3 + 1] = x1i - x3r;
+ }
+ } else {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ x0r = a[j] - a[j1];
+ x0i = a[j + 1] - a[j1 + 1];
+ a[j] += a[j1];
+ a[j + 1] += a[j1 + 1];
+ a[j1] = x0r;
+ a[j1 + 1] = x0i;
+ }
+ }
+ }
+
+
+ void cftbsub(int n, double *a, double *w)
+ {
+ int j, j1, j2, j3, l;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ l = 2;
+ if (n > 8) {
+ cft1st(n, a, w);
+ l = 8;
+ while ((l << 2) < n) {
+ cftmdl(n, l, a, w);
+ l <<= 2;
+ }
+ }
+ if ((l << 2) == n) {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = -a[j + 1] - a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = -a[j + 1] + a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i - x2i;
+ a[j2] = x0r - x2r;
+ a[j2 + 1] = x0i + x2i;
+ a[j1] = x1r - x3i;
+ a[j1 + 1] = x1i - x3r;
+ a[j3] = x1r + x3i;
+ a[j3 + 1] = x1i + x3r;
+ }
+ } else {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ x0r = a[j] - a[j1];
+ x0i = -a[j + 1] + a[j1 + 1];
+ a[j] += a[j1];
+ a[j + 1] = -a[j + 1] - a[j1 + 1];
+ a[j1] = x0r;
+ a[j1 + 1] = x0i;
+ }
+ }
+ }
+
+
+ void cft1st(int n, double *a, double *w)
+ {
+ int j, k1, k2;
+ double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ x0r = a[0] + a[2];
+ x0i = a[1] + a[3];
+ x1r = a[0] - a[2];
+ x1i = a[1] - a[3];
+ x2r = a[4] + a[6];
+ x2i = a[5] + a[7];
+ x3r = a[4] - a[6];
+ x3i = a[5] - a[7];
+ a[0] = x0r + x2r;
+ a[1] = x0i + x2i;
+ a[4] = x0r - x2r;
+ a[5] = x0i - x2i;
+ a[2] = x1r - x3i;
+ a[3] = x1i + x3r;
+ a[6] = x1r + x3i;
+ a[7] = x1i - x3r;
+ wk1r = w[2];
+ x0r = a[8] + a[10];
+ x0i = a[9] + a[11];
+ x1r = a[8] - a[10];
+ x1i = a[9] - a[11];
+ x2r = a[12] + a[14];
+ x2i = a[13] + a[15];
+ x3r = a[12] - a[14];
+ x3i = a[13] - a[15];
+ a[8] = x0r + x2r;
+ a[9] = x0i + x2i;
+ a[12] = x2i - x0i;
+ a[13] = x0r - x2r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[10] = wk1r * (x0r - x0i);
+ a[11] = wk1r * (x0r + x0i);
+ x0r = x3i + x1r;
+ x0i = x3r - x1i;
+ a[14] = wk1r * (x0i - x0r);
+ a[15] = wk1r * (x0i + x0r);
+ k1 = 0;
+ for (j = 16; j < n; j += 16) {
+ k1 += 2;
+ k2 = 2 * k1;
+ wk2r = w[k1];
+ wk2i = w[k1 + 1];
+ wk1r = w[k2];
+ wk1i = w[k2 + 1];
+ wk3r = wk1r - 2 * wk2i * wk1i;
+ wk3i = 2 * wk2i * wk1r - wk1i;
+ x0r = a[j] + a[j + 2];
+ x0i = a[j + 1] + a[j + 3];
+ x1r = a[j] - a[j + 2];
+ x1i = a[j + 1] - a[j + 3];
+ x2r = a[j + 4] + a[j + 6];
+ x2i = a[j + 5] + a[j + 7];
+ x3r = a[j + 4] - a[j + 6];
+ x3i = a[j + 5] - a[j + 7];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j + 4] = wk2r * x0r - wk2i * x0i;
+ a[j + 5] = wk2r * x0i + wk2i * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j + 2] = wk1r * x0r - wk1i * x0i;
+ a[j + 3] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j + 6] = wk3r * x0r - wk3i * x0i;
+ a[j + 7] = wk3r * x0i + wk3i * x0r;
+ wk1r = w[k2 + 2];
+ wk1i = w[k2 + 3];
+ wk3r = wk1r - 2 * wk2r * wk1i;
+ wk3i = 2 * wk2r * wk1r - wk1i;
+ x0r = a[j + 8] + a[j + 10];
+ x0i = a[j + 9] + a[j + 11];
+ x1r = a[j + 8] - a[j + 10];
+ x1i = a[j + 9] - a[j + 11];
+ x2r = a[j + 12] + a[j + 14];
+ x2i = a[j + 13] + a[j + 15];
+ x3r = a[j + 12] - a[j + 14];
+ x3i = a[j + 13] - a[j + 15];
+ a[j + 8] = x0r + x2r;
+ a[j + 9] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j + 12] = -wk2i * x0r - wk2r * x0i;
+ a[j + 13] = -wk2i * x0i + wk2r * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j + 10] = wk1r * x0r - wk1i * x0i;
+ a[j + 11] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j + 14] = wk3r * x0r - wk3i * x0i;
+ a[j + 15] = wk3r * x0i + wk3i * x0r;
+ }
+ }
+
+
+ void cftmdl(int n, int l, double *a, double *w)
+ {
+ int j, j1, j2, j3, k, k1, k2, m, m2;
+ double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ m = l << 2;
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ a[j2] = x0r - x2r;
+ a[j2 + 1] = x0i - x2i;
+ a[j1] = x1r - x3i;
+ a[j1 + 1] = x1i + x3r;
+ a[j3] = x1r + x3i;
+ a[j3 + 1] = x1i - x3r;
+ }
+ wk1r = w[2];
+ for (j = m; j < l + m; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ a[j2] = x2i - x0i;
+ a[j2 + 1] = x0r - x2r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j1] = wk1r * (x0r - x0i);
+ a[j1 + 1] = wk1r * (x0r + x0i);
+ x0r = x3i + x1r;
+ x0i = x3r - x1i;
+ a[j3] = wk1r * (x0i - x0r);
+ a[j3 + 1] = wk1r * (x0i + x0r);
+ }
+ k1 = 0;
+ m2 = 2 * m;
+ for (k = m2; k < n; k += m2) {
+ k1 += 2;
+ k2 = 2 * k1;
+ wk2r = w[k1];
+ wk2i = w[k1 + 1];
+ wk1r = w[k2];
+ wk1i = w[k2 + 1];
+ wk3r = wk1r - 2 * wk2i * wk1i;
+ wk3i = 2 * wk2i * wk1r - wk1i;
+ for (j = k; j < l + k; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j2] = wk2r * x0r - wk2i * x0i;
+ a[j2 + 1] = wk2r * x0i + wk2i * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j1] = wk1r * x0r - wk1i * x0i;
+ a[j1 + 1] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j3] = wk3r * x0r - wk3i * x0i;
+ a[j3 + 1] = wk3r * x0i + wk3i * x0r;
+ }
+ wk1r = w[k2 + 2];
+ wk1i = w[k2 + 3];
+ wk3r = wk1r - 2 * wk2r * wk1i;
+ wk3i = 2 * wk2r * wk1r - wk1i;
+ for (j = k + m; j < l + (k + m); j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j2] = -wk2i * x0r - wk2r * x0i;
+ a[j2 + 1] = -wk2i * x0i + wk2r * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j1] = wk1r * x0r - wk1i * x0i;
+ a[j1 + 1] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j3] = wk3r * x0r - wk3i * x0i;
+ a[j3 + 1] = wk3r * x0i + wk3i * x0r;
+ }
+ }
+ }
+
+
+ void rftfsub(int n, double *a, int nc, double *c)
+ {
+ int j, k, kk, ks, m;
+ double wkr, wki, xr, xi, yr, yi;
+
+ m = n >> 1;
+ ks = 2 * nc / m;
+ kk = 0;
+ for (j = 2; j < m; j += 2) {
+ k = n - j;
+ kk += ks;
+ wkr = 0.5 - c[nc - kk];
+ wki = c[kk];
+ xr = a[j] - a[k];
+ xi = a[j + 1] + a[k + 1];
+ yr = wkr * xr - wki * xi;
+ yi = wkr * xi + wki * xr;
+ a[j] -= yr;
+ a[j + 1] -= yi;
+ a[k] += yr;
+ a[k + 1] -= yi;
+ }
+ }
+
+
+ void rftbsub(int n, double *a, int nc, double *c)
+ {
+ int j, k, kk, ks, m;
+ double wkr, wki, xr, xi, yr, yi;
+
+ a[1] = -a[1];
+ m = n >> 1;
+ ks = 2 * nc / m;
+ kk = 0;
+ for (j = 2; j < m; j += 2) {
+ k = n - j;
+ kk += ks;
+ wkr = 0.5 - c[nc - kk];
+ wki = c[kk];
+ xr = a[j] - a[k];
+ xi = a[j + 1] + a[k + 1];
+ yr = wkr * xr + wki * xi;
+ yi = wkr * xi - wki * xr;
+ a[j] -= yr;
+ a[j + 1] = yi - a[j + 1];
+ a[k] += yr;
+ a[k + 1] = yi - a[k + 1];
+ }
+ a[m + 1] = -a[m + 1];
+ }
+
+ OouraFFT(const OouraFFT&) = delete;
+ OouraFFT& operator=(const OouraFFT&) = delete;
+ };
+
+ std::unique_ptr MakeAudioFFTImpl()
+ {
+ return std::unique_ptr(new OouraFFT());
+ }
+
+
+#endif // AUDIOFFT_OOURA_USED
+
+
+ // ================================================================
+
+
+#ifdef AUDIOFFT_APPLE_ACCELERATE_USED
+
+
+ /**
+ * @internal
+ * @class AppleAccelerateFFT
+ * @brief FFT implementation using the Apple Accelerate framework internally
+ */
+ class AppleAccelerateFFT : public AudioFFTImpl
+ {
+ public:
+ AppleAccelerateFFT() :
+ AudioFFTImpl(),
+ _size(0),
+ _powerOf2(0),
+ _fftSetup(0),
+ _re(),
+ _im()
+ {
+ }
+
+ virtual ~AppleAccelerateFFT()
+ {
+ init(0);
+ }
+
+ virtual void init(size_t size) override
+ {
+ if (_fftSetup)
+ {
+ vDSP_destroy_fftsetup(_fftSetup);
+ _size = 0;
+ _powerOf2 = 0;
+ _fftSetup = 0;
+ _re.clear();
+ _im.clear();
+ }
+
+ if (size > 0)
+ {
+ _size = size;
+ _powerOf2 = 0;
+ while ((1 << _powerOf2) < _size)
+ {
+ ++_powerOf2;
+ }
+ _fftSetup = vDSP_create_fftsetup(_powerOf2, FFT_RADIX2);
+ _re.resize(_size / 2);
+ _im.resize(_size / 2);
+ }
+ }
+
+ virtual void fft(const float* data, float* re, float* im) override
+ {
+ const size_t size2 = _size / 2;
+ DSPSplitComplex splitComplex;
+ splitComplex.realp = re;
+ splitComplex.imagp = im;
+ vDSP_ctoz(reinterpret_cast(data), 2, &splitComplex, 1, size2);
+ vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_FORWARD);
+ const float factor = 0.5f;
+ vDSP_vsmul(re, 1, &factor, re, 1, size2);
+ vDSP_vsmul(im, 1, &factor, im, 1, size2);
+ re[size2] = im[0];
+ im[0] = 0.0f;
+ im[size2] = 0.0f;
+ }
+
+ virtual void ifft(float* data, const float* re, const float* im) override
+ {
+ const size_t size2 = _size / 2;
+ ::memcpy(_re.data(), re, size2 * sizeof(float));
+ ::memcpy(_im.data(), im, size2 * sizeof(float));
+ _im[0] = re[size2];
+ DSPSplitComplex splitComplex;
+ splitComplex.realp = _re.data();
+ splitComplex.imagp = _im.data();
+ vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_INVERSE);
+ vDSP_ztoc(&splitComplex, 1, reinterpret_cast(data), 2, size2);
+ const float factor = 1.0f / static_cast(_size);
+ vDSP_vsmul(data, 1, &factor, data, 1, _size);
+ }
+
+ private:
+ size_t _size;
+ size_t _powerOf2;
+ FFTSetup _fftSetup;
+ std::vector _re;
+ std::vector _im;
+
+ AppleAccelerateFFT(const AppleAccelerateFFT&) = delete;
+ AppleAccelerateFFT& operator=(const AppleAccelerateFFT&) = delete;
+ };
+
+
+ std::unique_ptr MakeAudioFFTImpl()
+ {
+ return std::unique_ptr(new AppleAccelerateFFT());
+ }
+
+
+#endif // AUDIOFFT_APPLE_ACCELERATE_USED
+
+
+ // ================================================================
+
+
+#ifdef AUDIOFFT_FFTW3_USED
+
+
+ /**
+ * @internal
+ * @class FFTW3FFT
+ * @brief FFT implementation using FFTW3 internally (see fftw.org)
+ */
+ class FFTW3FFT : public AudioFFTImpl
+ {
+ public:
+ FFTW3FFT() :
+ AudioFFTImpl(),
+ _size(0),
+ _complexSize(0),
+ _planForward(0),
+ _planBackward(0),
+ _data(0),
+ _re(0),
+ _im(0)
+ {
+ }
+
+ virtual ~FFTW3FFT()
+ {
+ init(0);
+ }
+
+ virtual void init(size_t size) override
+ {
+ if (_size != size)
+ {
+ if (_size > 0)
+ {
+ fftwf_destroy_plan(_planForward);
+ fftwf_destroy_plan(_planBackward);
+ _planForward = 0;
+ _planBackward = 0;
+ _size = 0;
+ _complexSize = 0;
+
+ if (_data)
+ {
+ fftwf_free(_data);
+ _data = 0;
+ }
+
+ if (_re)
+ {
+ fftwf_free(_re);
+ _re = 0;
+ }
+
+ if (_im)
+ {
+ fftwf_free(_im);
+ _im = 0;
+ }
+ }
+
+ if (size > 0)
+ {
+ _size = size;
+ _complexSize = AudioFFT::ComplexSize(_size);
+ const size_t complexSize = AudioFFT::ComplexSize(_size);
+ _data = reinterpret_cast(fftwf_malloc(_size * sizeof(float)));
+ _re = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float)));
+ _im = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float)));
+
+ fftw_iodim dim;
+ dim.n = static_cast(size);
+ dim.is = 1;
+ dim.os = 1;
+ _planForward = fftwf_plan_guru_split_dft_r2c(1, &dim, 0, 0, _data, _re, _im, FFTW_MEASURE);
+ _planBackward = fftwf_plan_guru_split_dft_c2r(1, &dim, 0, 0, _re, _im, _data, FFTW_MEASURE);
+ }
+ }
+ }
+
+ virtual void fft(const float* data, float* re, float* im) override
+ {
+ ::memcpy(_data, data, _size * sizeof(float));
+ fftwf_execute_split_dft_r2c(_planForward, _data, _re, _im);
+ ::memcpy(re, _re, _complexSize * sizeof(float));
+ ::memcpy(im, _im, _complexSize * sizeof(float));
+ }
+
+ void ifft(float* data, const float* re, const float* im)
+ {
+ ::memcpy(_re, re, _complexSize * sizeof(float));
+ ::memcpy(_im, im, _complexSize * sizeof(float));
+ fftwf_execute_split_dft_c2r(_planBackward, _re, _im, _data);
+ ScaleBuffer(data, _data, 1.0f / static_cast(_size), _size);
+ }
+
+ private:
+ size_t _size;
+ size_t _complexSize;
+ fftwf_plan _planForward;
+ fftwf_plan _planBackward;
+ float* _data;
+ float* _re;
+ float* _im;
+
+ FFTW3FFT(const FFTW3FFT&) = delete;
+ FFTW3FFT& operator=(const FFTW3FFT&) = delete;
+ };
+
+
+ std::unique_ptr MakeAudioFFTImpl()
+ {
+ return std::unique_ptr(new FFTW3FFT());
+ }
+
+
+#endif // AUDIOFFT_FFTW3_USED
+
+ } // End of namespace details
+
+
+ // =============================================================
+
+
+ AudioFFT::AudioFFT() :
+ _impl(details::MakeAudioFFTImpl())
+ {
+ }
+
+
+ void AudioFFT::init(size_t size)
+ {
+ assert(details::IsPowerOf2(size));
+ _impl->init(size);
+ }
+
+
+ void AudioFFT::fft(const float* data, float* re, float* im)
+ {
+ _impl->fft(data, re, im);
+ }
+
+
+ void AudioFFT::ifft(float* data, const float* re, const float* im)
+ {
+ _impl->ifft(data, re, im);
+ }
+
+
+ size_t AudioFFT::ComplexSize(size_t size)
+ {
+ return (size / 2) + 1;
+ }
+
+} // End of namespace
diff --git a/FFTConvolver/AudioFFT.h b/FFTConvolver/AudioFFT.h
new file mode 100755
index 0000000..80cd2ed
--- /dev/null
+++ b/FFTConvolver/AudioFFT.h
@@ -0,0 +1,176 @@
+// ==================================================================================
+// Copyright (c) 2016 HiFi-LoFi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is furnished
+// to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ==================================================================================
+
+#ifndef _AUDIOFFT_H
+#define _AUDIOFFT_H
+
+
+/**
+* AudioFFT provides real-to-complex/complex-to-real FFT routines.
+*
+* Features:
+*
+* - Real-complex FFT and complex-real inverse FFT for power-of-2-sized real data.
+*
+* - Uniform interface to different FFT implementations (currently Ooura, FFTW3 and Apple Accelerate).
+*
+* - Complex data is handled in "split-complex" format, i.e. there are separate
+* arrays for the real and imaginary parts which can be useful for SIMD optimizations
+* (split-complex arrays have to be of length (size/2+1) representing bins from DC
+* to Nyquist frequency).
+*
+* - Output is "ready to use" (all scaling etc. is already handled internally).
+*
+* - No allocations/deallocations after the initialization which makes it usable
+* for real-time audio applications (that's what I wrote it for and using it).
+*
+*
+* How to use it in your project:
+*
+* - Add the .h and .cpp file to your project - that's all.
+*
+* - To get extra speed, you can link FFTW3 to your project and define
+* AUDIOFFT_FFTW3 (however, please check whether your project suits the
+* according license).
+*
+* - To get the best speed on Apple platforms, you can link the Apple
+* Accelerate framework to your project and define
+* AUDIOFFT_APPLE_ACCELERATE (however, please check whether your
+* project suits the according license).
+*
+*
+* Remarks:
+*
+* - AudioFFT is not intended to be the fastest FFT, but to be a fast-enough
+* FFT suitable for most audio applications.
+*
+* - AudioFFT uses the quite liberal MIT license.
+*
+*
+* Example usage:
+* @code
+* #include "AudioFFT.h"
+*
+* void Example()
+* {
+* const size_t fftSize = 1024; // Needs to be power of 2!
+*
+* std::vector input(fftSize, 0.0f);
+* std::vector re(audiofft::AudioFFT::ComplexSize(fftSize));
+* std::vector im(audiofft::AudioFFT::ComplexSize(fftSize));
+* std::vector output(fftSize);
+*
+* audiofft::AudioFFT fft;
+* fft.init(1024);
+* fft.fft(input.data(), re.data(), im.data());
+* fft.ifft(output.data(), re.data(), im.data());
+* }
+* @endcode
+*/
+
+
+#include
+#include
+
+
+namespace audiofft
+{
+
+ namespace details
+ {
+
+ class AudioFFTImpl
+ {
+ public:
+ AudioFFTImpl() = default;
+ virtual ~AudioFFTImpl() = default;
+ virtual void init(size_t size) = 0;
+ virtual void fft(const float* data, float* re, float* im) = 0;
+ virtual void ifft(float* data, const float* re, const float* im) = 0;
+
+ private:
+ AudioFFTImpl(const AudioFFTImpl&) = delete;
+ AudioFFTImpl& operator=(const AudioFFTImpl&) = delete;
+ };
+ }
+
+
+ // ======================================================
+
+
+ /**
+ * @class AudioFFT
+ * @brief Performs 1D FFTs
+ */
+ class AudioFFT
+ {
+ public:
+ /**
+ * @brief Constructor
+ */
+ AudioFFT();
+
+ /**
+ * @brief Initializes the FFT object
+ * @param size Size of the real input (must be power 2)
+ */
+ void init(size_t size);
+
+ /**
+ * @brief Performs the forward FFT
+ * @param data The real input data (has to be of the length as specified in init())
+ * @param re The real part of the complex output (has to be of length as returned by ComplexSize())
+ * @param im The imaginary part of the complex output (has to be of length as returned by ComplexSize())
+ */
+ void fft(const float* data, float* re, float* im);
+
+ /**
+ * @brief Performs the inverse FFT
+ * @param data The real output data (has to be of the length as specified in init())
+ * @param re The real part of the complex input (has to be of length as returned by ComplexSize())
+ * @param im The imaginary part of the complex input (has to be of length as returned by ComplexSize())
+ */
+ void ifft(float* data, const float* re, const float* im);
+
+ /**
+ * @brief Calculates the necessary size of the real/imaginary complex arrays
+ * @param size The size of the real data
+ * @return The size of the real/imaginary complex arrays
+ */
+ static size_t ComplexSize(size_t size);
+
+ private:
+ std::unique_ptr _impl;
+
+ AudioFFT(const AudioFFT&) = delete;
+ AudioFFT& operator=(const AudioFFT&) = delete;
+ };
+
+
+ /**
+ * @deprecated
+ * @brief Let's keep an AudioFFTBase type around for now because it has been here already in the 1st version in order to avoid breaking existing code.
+ */
+ typedef AudioFFT AudioFFTBase;
+
+} // End of namespace
+
+#endif // Header guard
diff --git a/FFTConvolver/FFTConvolver.cpp b/FFTConvolver/FFTConvolver.cpp
new file mode 100755
index 0000000..049dbed
--- /dev/null
+++ b/FFTConvolver/FFTConvolver.cpp
@@ -0,0 +1,202 @@
+// ==================================================================================
+// Copyright (c) 2012 HiFi-LoFi
+//
+// This 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, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 .
+// ==================================================================================
+
+#include "FFTConvolver.h"
+
+#include
+#include
+
+#if defined (FFTCONVOLVER_USE_SSE)
+ #include
+#endif
+
+
+namespace fftconvolver
+{
+
+FFTConvolver::FFTConvolver() :
+ _blockSize(0),
+ _segSize(0),
+ _segCount(0),
+ _fftComplexSize(0),
+ _segments(),
+ _segmentsIR(),
+ _fftBuffer(),
+ _fft(),
+ _preMultiplied(),
+ _conv(),
+ _overlap(),
+ _current(0),
+ _inputBuffer(),
+ _inputBufferFill(0)
+{
+}
+
+
+FFTConvolver::~FFTConvolver()
+{
+ reset();
+}
+
+
+void FFTConvolver::reset()
+{
+ for (size_t i=0; i<_segCount; ++i)
+ {
+ delete _segments[i];
+ delete _segmentsIR[i];
+ }
+
+ _blockSize = 0;
+ _segSize = 0;
+ _segCount = 0;
+ _fftComplexSize = 0;
+ _segments.clear();
+ _segmentsIR.clear();
+ _fftBuffer.clear();
+ _fft.init(0);
+ _preMultiplied.clear();
+ _conv.clear();
+ _overlap.clear();
+ _current = 0;
+ _inputBuffer.clear();
+ _inputBufferFill = 0;
+}
+
+
+bool FFTConvolver::init(size_t blockSize, const Sample* ir, size_t irLen)
+{
+ reset();
+
+ if (blockSize == 0)
+ {
+ return false;
+ }
+
+ // Ignore zeros at the end of the impulse response because they only waste computation time
+ while (irLen > 0 && ::fabs(ir[irLen-1]) < 0.000001f)
+ {
+ --irLen;
+ }
+
+ if (irLen == 0)
+ {
+ return true;
+ }
+
+ _blockSize = NextPowerOf2(blockSize);
+ _segSize = 2 * _blockSize;
+ _segCount = static_cast(::ceil(static_cast(irLen) / static_cast(_blockSize)));
+ _fftComplexSize = audiofft::AudioFFT::ComplexSize(_segSize);
+
+ // FFT
+ _fft.init(_segSize);
+ _fftBuffer.resize(_segSize);
+
+ // Prepare segments
+ for (size_t i=0; i<_segCount; ++i)
+ {
+ _segments.push_back(new SplitComplex(_fftComplexSize));
+ }
+
+ // Prepare IR
+ for (size_t i=0; i<_segCount; ++i)
+ {
+ SplitComplex* segment = new SplitComplex(_fftComplexSize);
+ const size_t remaining = irLen - (i * _blockSize);
+ const size_t sizeCopy = (remaining >= _blockSize) ? _blockSize : remaining;
+ CopyAndPad(_fftBuffer, &ir[i*_blockSize], sizeCopy);
+ _fft.fft(_fftBuffer.data(), segment->re(), segment->im());
+ _segmentsIR.push_back(segment);
+ }
+
+ // Prepare convolution buffers
+ _preMultiplied.resize(_fftComplexSize);
+ _conv.resize(_fftComplexSize);
+ _overlap.resize(_blockSize);
+
+ // Prepare input buffer
+ _inputBuffer.resize(_blockSize);
+ _inputBufferFill = 0;
+
+ // Reset current position
+ _current = 0;
+
+ return true;
+}
+
+
+void FFTConvolver::process(const Sample* input, Sample* output, size_t len)
+{
+ if (_segCount == 0)
+ {
+ ::memset(output, 0, len * sizeof(Sample));
+ return;
+ }
+
+ size_t processed = 0;
+ while (processed < len)
+ {
+ const bool inputBufferWasEmpty = (_inputBufferFill == 0);
+ const size_t processing = std::min(len-processed, _blockSize-_inputBufferFill);
+ const size_t inputBufferPos = _inputBufferFill;
+ ::memcpy(_inputBuffer.data()+inputBufferPos, input+processed, processing * sizeof(Sample));
+
+ // Forward FFT
+ CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize);
+ _fft.fft(_fftBuffer.data(), _segments[_current]->re(), _segments[_current]->im());
+
+ // Complex multiplication
+ if (inputBufferWasEmpty)
+ {
+ _preMultiplied.setZero();
+ for (size_t i=1; i<_segCount; ++i)
+ {
+ const size_t indexIr = i;
+ const size_t indexAudio = (_current + i) % _segCount;
+ ComplexMultiplyAccumulate(_preMultiplied, *_segmentsIR[indexIr], *_segments[indexAudio]);
+ }
+ }
+ _conv.copyFrom(_preMultiplied);
+ ComplexMultiplyAccumulate(_conv, *_segments[_current], *_segmentsIR[0]);
+
+ // Backward FFT
+ _fft.ifft(_fftBuffer.data(), _conv.re(), _conv.im());
+
+ // Add overlap
+ Sum(output+processed, _fftBuffer.data()+inputBufferPos, _overlap.data()+inputBufferPos, processing);
+
+ // Input buffer full => Next block
+ _inputBufferFill += processing;
+ if (_inputBufferFill == _blockSize)
+ {
+ // Input buffer is empty again now
+ _inputBuffer.setZero();
+ _inputBufferFill = 0;
+
+ // Save the overlap
+ ::memcpy(_overlap.data(), _fftBuffer.data()+_blockSize, _blockSize * sizeof(Sample));
+
+ // Update current segment
+ _current = (_current > 0) ? (_current - 1) : (_segCount - 1);
+ }
+
+ processed += processing;
+ }
+}
+
+} // End of namespace fftconvolver
diff --git a/FFTConvolver/FFTConvolver.h b/FFTConvolver/FFTConvolver.h
new file mode 100755
index 0000000..0c44608
--- /dev/null
+++ b/FFTConvolver/FFTConvolver.h
@@ -0,0 +1,100 @@
+// ==================================================================================
+// Copyright (c) 2012 HiFi-LoFi
+//
+// This 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, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 .
+// ==================================================================================
+
+#ifndef _FFTCONVOLVER_FFTCONVOLVER_H
+#define _FFTCONVOLVER_FFTCONVOLVER_H
+
+#include "AudioFFT.h"
+#include "Utilities.h"
+
+#include
+
+
+namespace fftconvolver
+{
+
+/**
+* @class FFTConvolver
+* @brief Implementation of a partitioned FFT convolution algorithm with uniform block size
+*
+* Some notes on how to use it:
+*
+* - After initialization with an impulse response, subsequent data portions of
+* arbitrary length can be convolved. The convolver internally can handle
+* this by using appropriate buffering.
+*
+* - The convolver works without "latency" (except for the required
+* processing time, of course), i.e. the output always is the convolved
+* input for each processing call.
+*
+* - The convolver is suitable for real-time processing which means that no
+* "unpredictable" operations like allocations, locking, API calls, etc. are
+* performed during processing (all necessary allocations and preparations take
+* place during initialization).
+*/
+class FFTConvolver
+{
+public:
+ FFTConvolver();
+ virtual ~FFTConvolver();
+
+ /**
+ * @brief Initializes the convolver
+ * @param blockSize Block size internally used by the convolver (partition size)
+ * @param ir The impulse response
+ * @param irLen Length of the impulse response
+ * @return true: Success - false: Failed
+ */
+ bool init(size_t blockSize, const Sample* ir, size_t irLen);
+
+ /**
+ * @brief Convolves the the given input samples and immediately outputs the result
+ * @param input The input samples
+ * @param output The convolution result
+ * @param len Number of input/output samples
+ */
+ void process(const Sample* input, Sample* output, size_t len);
+
+ /**
+ * @brief Resets the convolver and discards the set impulse response
+ */
+ void reset();
+
+private:
+ size_t _blockSize;
+ size_t _segSize;
+ size_t _segCount;
+ size_t _fftComplexSize;
+ std::vector _segments;
+ std::vector _segmentsIR;
+ SampleBuffer _fftBuffer;
+ audiofft::AudioFFT _fft;
+ SplitComplex _preMultiplied;
+ SplitComplex _conv;
+ SampleBuffer _overlap;
+ size_t _current;
+ SampleBuffer _inputBuffer;
+ size_t _inputBufferFill;
+
+ // Prevent uncontrolled usage
+ FFTConvolver(const FFTConvolver&);
+ FFTConvolver& operator=(const FFTConvolver&);
+};
+
+} // End of namespace fftconvolver
+
+#endif // Header guard
diff --git a/FFTConvolver/Utilities.cpp b/FFTConvolver/Utilities.cpp
new file mode 100644
index 0000000..a30ff14
--- /dev/null
+++ b/FFTConvolver/Utilities.cpp
@@ -0,0 +1,113 @@
+// ==================================================================================
+// Copyright (c) 2012 HiFi-LoFi
+//
+// This 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, either version 3 of the License, or
+// (at your option) any later version.
+//
+// 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 .
+// ==================================================================================
+
+#include "Utilities.h"
+
+
+namespace fftconvolver
+{
+
+bool SSEEnabled()
+{
+#if defined(FFTCONVOLVER_USE_SSE)
+ return true;
+#else
+ return false;
+#endif
+}
+
+
+void Sum(Sample* FFTCONVOLVER_RESTRICT result,
+ const Sample* FFTCONVOLVER_RESTRICT a,
+ const Sample* FFTCONVOLVER_RESTRICT b,
+ size_t len)
+{
+ const size_t end4 = 4 * (len / 4);
+ for (size_t i=0; i.
+// ==================================================================================
+
+#ifndef _FFTCONVOLVER_UTILITIES_H
+#define _FFTCONVOLVER_UTILITIES_H
+
+#include
+#include
+#include
+#include
+#include
+
+
+namespace fftconvolver
+{
+
+#if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
+ #if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE)
+ #define FFTCONVOLVER_USE_SSE
+ #endif
+#endif
+
+
+#if defined (FFTCONVOLVER_USE_SSE)
+ #include
+#endif
+
+
+#if defined(__GNUC__)
+ #define FFTCONVOLVER_RESTRICT __restrict__
+#else
+ #define FFTCONVOLVER_RESTRICT
+#endif
+
+
+/**
+* @brief Returns whether SSE optimization for the convolver is enabled
+* @return true: Enabled - false: Disabled
+*/
+bool SSEEnabled();
+
+
+/**
+* @class Buffer
+* @brief Simple buffer implementation (uses 16-byte alignment if SSE optimization is enabled)
+*/
+template
+class Buffer
+{
+public:
+ explicit Buffer(size_t initialSize = 0) :
+ _data(0),
+ _size(0)
+ {
+ resize(initialSize);
+ }
+
+ virtual ~Buffer()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ deallocate(_data);
+ _data = 0;
+ _size = 0;
+ }
+
+ void resize(size_t size)
+ {
+ if (_size != size)
+ {
+ clear();
+
+ if (size > 0)
+ {
+ assert(!_data && _size == 0);
+ _data = allocate(size);
+ _size = size;
+ }
+ }
+ if (_data)
+ setZero();
+ }
+
+ size_t size() const
+ {
+ return _size;
+ }
+
+ void setZero()
+ {
+ ::memset(_data, 0, _size * sizeof(T));
+ }
+
+ void copyFrom(const Buffer& other)
+ {
+ assert(_size == other._size);
+ if (this != &other)
+ {
+ ::memcpy(_data, other._data, _size * sizeof(T));
+ }
+ }
+
+ T& operator[](size_t index)
+ {
+ assert(_data && index < _size);
+ return _data[index];
+ }
+
+ const T& operator[](size_t index) const
+ {
+ assert(_data && index < _size);
+ return _data[index];
+ }
+
+ operator bool() const
+ {
+ return (_data != 0 && _size > 0);
+ }
+
+ T* data()
+ {
+ return _data;
+ }
+
+ const T* data() const
+ {
+ return _data;
+ }
+
+ static void Swap(Buffer& a, Buffer& b)
+ {
+ std::swap(a._data, b._data);
+ std::swap(a._size, b._size);
+ }
+
+private:
+ T* allocate(size_t size)
+ {
+#if defined(FFTCONVOLVER_USE_SSE)
+ return static_cast(_mm_malloc(size * sizeof(T), 16));
+#else
+ return new T[size];
+#endif
+ }
+
+ void deallocate(T* ptr)
+ {
+#if defined(FFTCONVOLVER_USE_SSE)
+ _mm_free(ptr);
+#else
+ delete [] ptr;
+#endif
+ }
+
+ T* _data;
+ size_t _size;
+
+ // Prevent uncontrolled usage
+ Buffer(const Buffer&);
+ Buffer& operator=(const Buffer&);
+};
+
+
+/**
+* @brief Type of one sample
+*/
+typedef float Sample;
+
+
+/**
+* @brief Buffer for samples
+*/
+typedef Buffer SampleBuffer;
+
+
+/**
+* @class SplitComplex
+* @brief Buffer for split-complex representation of FFT results
+*
+* The split-complex representation stores the real and imaginary parts
+* of FFT results in two different memory buffers which is useful e.g. for
+* SIMD optimizations.
+*/
+class SplitComplex
+{
+public:
+ explicit SplitComplex(size_t initialSize = 0) :
+ _size(0),
+ _re(),
+ _im()
+ {
+ resize(initialSize);
+ }
+
+ ~SplitComplex()
+ {
+ clear();
+ }
+
+ void clear()
+ {
+ _re.clear();
+ _im.clear();
+ _size = 0;
+ }
+
+ void resize(size_t newSize)
+ {
+ _re.resize(newSize);
+ _im.resize(newSize);
+ _size = newSize;
+ }
+
+ void setZero()
+ {
+ _re.setZero();
+ _im.setZero();
+ }
+
+ void copyFrom(const SplitComplex& other)
+ {
+ _re.copyFrom(other._re);
+ _im.copyFrom(other._im);
+ }
+
+ Sample* re()
+ {
+ return _re.data();
+ }
+
+ const Sample* re() const
+ {
+ return _re.data();
+ }
+
+ Sample* im()
+ {
+ return _im.data();
+ }
+
+ const Sample* im() const
+ {
+ return _im.data();
+ }
+
+ size_t size() const
+ {
+ return _size;
+ }
+
+private:
+ size_t _size;
+ SampleBuffer _re;
+ SampleBuffer _im;
+
+ // Prevent uncontrolled usage
+ SplitComplex(const SplitComplex&);
+ SplitComplex& operator=(const SplitComplex&);
+};
+
+
+/**
+* @brief Returns the next power of 2 of a given number
+* @param val The number
+* @return The next power of 2
+*/
+template
+T NextPowerOf2(const T& val)
+{
+ T nextPowerOf2 = 1;
+ while (nextPowerOf2 < val)
+ {
+ nextPowerOf2 *= 2;
+ }
+ return nextPowerOf2;
+}
+
+
+/**
+* @brief Sums two given sample arrays
+* @param result The result array
+* @param a The 1st array
+* @param b The 2nd array
+* @param len The length of the arrays
+*/
+void Sum(Sample* FFTCONVOLVER_RESTRICT result,
+ const Sample* FFTCONVOLVER_RESTRICT a,
+ const Sample* FFTCONVOLVER_RESTRICT b,
+ size_t len);
+
+
+/**
+* @brief Copies a source array into a destination buffer and pads the destination buffer with zeros
+* @param dest The destination buffer
+* @param src The source array
+* @param srcSize The size of the source array
+*/
+template
+void CopyAndPad(Buffer& dest, const T* src, size_t srcSize)
+{
+ assert(dest.size() >= srcSize);
+ ::memcpy(dest.data(), src, srcSize * sizeof(T));
+ ::memset(dest.data() + srcSize, 0, (dest.size()-srcSize) * sizeof(T));
+}
+
+
+/**
+* @brief Adds the complex product of two split-complex buffers to a result buffer
+* @param result The result buffer
+* @param a The 1st factor of the complex product
+* @param b The 2nd factor of the complex product
+*/
+void ComplexMultiplyAccumulate(SplitComplex& result, const SplitComplex& a, const SplitComplex& b);
+
+
+/**
+* @brief Adds the complex product of two split-complex arrays to a result array
+* @param re The real part of the result buffer
+* @param im The imaginary part of the result buffer
+* @param reA The real part of the 1st factor of the complex product
+* @param imA The imaginary part of the 1st factor of the complex product
+* @param reB The real part of the 2nd factor of the complex product
+* @param imB The imaginary part of the 2nd factor of the complex product
+*/
+void ComplexMultiplyAccumulate(Sample* FFTCONVOLVER_RESTRICT re,
+ Sample* FFTCONVOLVER_RESTRICT im,
+ const Sample* FFTCONVOLVER_RESTRICT reA,
+ const Sample* FFTCONVOLVER_RESTRICT imA,
+ const Sample* FFTCONVOLVER_RESTRICT reB,
+ const Sample* FFTCONVOLVER_RESTRICT imB,
+ const size_t len);
+
+} // End of namespace fftconvolver
+
+#endif // Header guard
diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp
new file mode 100644
index 0000000..4589c8e
--- /dev/null
+++ b/FFTConvolver/convolver.cpp
@@ -0,0 +1,67 @@
+
+
+#include "convolver.h"
+#include
+#include "FFTConvolver.h"
+#include "Utilities.h"
+extern "C" {
+#include "../common.h"
+}
+
+
+static fftconvolver::FFTConvolver convolver_l;
+static fftconvolver::FFTConvolver convolver_r;
+
+
+void convolver_init(const char* filename, int max_length)
+{
+ SF_INFO info;
+ assert(filename);
+ SNDFILE* file = sf_open(filename, SFM_READ, &info);
+ assert(file);
+
+ if (info.samplerate != 44100)
+ die("Impulse file \"%s\" sample rate is %d Hz. Only 44100 Hz is supported", filename, info.samplerate);
+
+ if (info.channels != 1 && info.channels != 2)
+ die("Impulse file \"%s\" contains %d channels. Only 1 or 2 is supported.", filename, info.channels);
+
+ const size_t size = info.frames > max_length ? max_length : info.frames;
+ float buffer[size*info.channels];
+
+ size_t l = sf_readf_float(file, buffer, size);
+ assert(l == size);
+
+ if (info.channels == 1) {
+ convolver_l.init(352, buffer, size);
+ convolver_r.init(352, buffer, size);
+ } else {
+ // deinterleave
+ float buffer_l[size];
+ float buffer_r[size];
+
+ int i;
+ for (i=0; i
+
+Shairport Sync:
+ Copyright (c) 2014–2017 Mike Brady
+
+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.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..48ef77a
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,161 @@
+SUBDIRS = man
+
+bin_PROGRAMS = shairport-sync
+
+# See below for the flags for the test client program
+
+shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c
+
+AM_CFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\"
+if BUILD_FOR_FREEBSD
+ AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" -O2
+else
+ AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\"
+endif
+
+if USE_APPLE_ALAC
+ shairport_sync_SOURCES += apple_alac.cpp
+endif
+
+if USE_CUSTOMPIDDIR
+AM_CFLAGS+= \
+ -DPIDDIR=\"$(CUSTOM_PID_DIR)\"
+endif
+
+if USE_AVAHI
+shairport_sync_SOURCES += mdns_avahi.c
+endif
+
+if USE_TINYSVCMDNS
+shairport_sync_SOURCES += mdns_tinysvcmdns.c tinysvcmdns.c
+endif
+
+if USE_ALSA
+shairport_sync_SOURCES += audio_alsa.c
+endif
+
+if USE_SNDIO
+shairport_sync_SOURCES += audio_sndio.c
+endif
+
+if USE_STDOUT
+shairport_sync_SOURCES += audio_stdout.c
+endif
+
+if USE_PIPE
+shairport_sync_SOURCES += audio_pipe.c
+endif
+
+if USE_DUMMY
+shairport_sync_SOURCES += audio_dummy.c
+endif
+
+if USE_AO
+shairport_sync_SOURCES += audio_ao.c
+endif
+
+if USE_SOUNDIO
+shairport_sync_SOURCES += audio_soundio.c
+endif
+
+if USE_PA
+shairport_sync_SOURCES += audio_pa.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
+endif
+
+if USE_DNS_SD
+shairport_sync_SOURCES += mdns_dns_sd.c
+endif
+
+if USE_DBUS
+shairport_sync_SOURCES += dbus-service.c dbus-interface.c
+BUILT_SOURCES = dbus-interface.h dbus-interface.c
+# We don't want to install this header
+noinst_HEADERS = $(BUILT_SOURCES)
+# Correctly clean the generated headers, but keep the xml description
+CLEANFILES = $(BUILT_SOURCES)
+
+#Rule to generate the binding headers
+dbus-interface.h: org.gnome.ShairportSync.xml
+ gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-interface org.gnome.ShairportSync.xml
+
+dbus-interface.c: org.gnome.ShairportSync.xml
+ gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-interface org.gnome.ShairportSync.xml
+endif
+
+if USE_DBUS_CORE_AND_DACP
+shairport_sync_SOURCES += dacp.c
+endif
+
+if USE_MPRIS
+shairport_sync_SOURCES += mpris-service.c mpris-interface.c mpris-player-interface.c
+BUILT_SOURCES = mpris-interface.h mpris-interface.c mpris-player-interface.h mpris-player-interface.c
+# We don't want to install this header
+noinst_HEADERS = $(BUILT_SOURCES)
+# Correctly clean the generated headers, but keep the xml description
+CLEANFILES = $(BUILT_SOURCES)
+
+#Rule to generate the binding headers
+mpris-interface.h: org.mpris.MediaPlayer2.xml
+ gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml
+
+mpris-interface.c: org.mpris.MediaPlayer2.xml
+ gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml
+
+mpris-player-interface.h: org.mpris.MediaPlayer2.Player.xml
+ gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-player-interface org.mpris.MediaPlayer2.Player.xml
+
+mpris-player-interface.c: org.mpris.MediaPlayer2.Player.xml
+ gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-player-interface org.mpris.MediaPlayer2.Player.xml
+endif
+
+if USE_DBUS_CLIENT
+ #Make it, but don't install it anywhere
+noinst_PROGRAMS = shairport-sync-dbus-test-client
+shairport_sync_dbus_test_client_SOURCES = dbus-interface.c dbus-interface.h shairport-sync-dbus-test-client.c
+endif
+
+if USE_MPRIS_CLIENT
+ #Make it, but don't install it anywhere
+noinst_PROGRAMS = shairport-sync-mpris-test-client
+shairport_sync_mpris_test_client_SOURCES = mpris-interface.c mpris-interface.h mpris-player-interface.c mpris-player-interface.h shairport-sync-mpris-test-client.c
+endif
+
+install-exec-hook:
+if INSTALL_CONFIG_FILES
+ [ -e $(DESTDIR)$(sysconfdir) ] || mkdir $(DESTDIR)$(sysconfdir)
+ cp scripts/shairport-sync.conf $(DESTDIR)$(sysconfdir)/shairport-sync.conf.sample
+ [ -f $(DESTDIR)$(sysconfdir)/shairport-sync.conf ] || cp scripts/shairport-sync.conf $(DESTDIR)$(sysconfdir)/shairport-sync.conf
+if USE_DBUS
+ cp scripts/shairport-sync-dbus-policy.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-dbus.conf
+endif
+if USE_MPRIS
+ cp scripts/shairport-sync-mpris-policy.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-mpris.conf
+endif
+endif
+if INSTALL_SYSTEMV
+ getent group shairport-sync &>/dev/null || groupadd -r shairport-sync >/dev/null
+ getent passwd shairport-sync &> /dev/null || useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync >/dev/null
+ [ -e /var/run/shairport-sync ] || mkdir -p /var/run/shairport-sync
+ chown shairport-sync:shairport-sync /var/run/shairport-sync
+ [ -e $(DESTDIR)$(sysconfdir)/init.d ] || mkdir -p $(DESTDIR)$(sysconfdir)/init.d
+ [ -f $(DESTDIR)$(sysconfdir)/init.d/shairport-sync ] || cp scripts/shairport-sync $(DESTDIR)$(sysconfdir)/init.d/
+endif
+if INSTALL_SYSTEMD
+ getent group shairport-sync &>/dev/null || groupadd -r shairport-sync >/dev/null
+ getent passwd shairport-sync &> /dev/null || useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync >/dev/null
+ [ -e $(DESTDIR)$(systemdsystemunitdir) ] || mkdir -p $(DESTDIR)$(systemdsystemunitdir)
+ [ -f $(DESTDIR)$(systemdsystemunitdir)/shairport-sync.service ] || cp scripts/shairport-sync.service $(DESTDIR)$(systemdsystemunitdir)
+endif
+if INSTALL_FREEBSD_SERVICE
+ pw showgroup shairport-sync > /dev/null 2>&1 || pw addgroup shairport-sync > /dev/null 2>&1
+ pw showuser shairport-sync > /dev/null 2>&1 || pw adduser shairport-sync > /dev/null 2>&1
+ [ -e /var/run/shairport-sync ] || mkdir -p /var/run/shairport-sync
+ chown shairport-sync:shairport-sync /var/run/shairport-sync
+ [ -f /usr/local/etc/rc.d/shairport_sync ] || cp scripts/shairport-sync.freebsd /usr/local/etc/rc.d/shairport_sync
+ chmod 555 /usr/local/etc/rc.d/shairport_sync
+endif
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..cff5c18
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+Please refer to RELEASENOTES.md and README.md for news.
diff --git a/README b/README
new file mode 100644
index 0000000..e69de29
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2cb2f7f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,550 @@
+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 suitable players. (Hence the name Shairport Sync, BTW.)
+
+Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming.
+
+This is the stable "master" branch. Changes and updates are incorporated into this branch relatively slowly. To access the development version, where all the latest changes are made first, please switch to the "development" 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.15 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.
+* 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/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.
+
+Status
+------
+Shairport Sync works on a wide variety of Linux devices and FreeBSD. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Stretch, Jessie and Wheezy, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input.
+
+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 well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card – see the note below on configuring the Raspberry Pi to make best use of it. It runs well on the Raspberry Pi Zero W with a suitable USB or I2S card.
+
+Shairport Sync runs natively on FreeBSD using the `sndio` sound system.
+
+Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux, Fedora and FreeBSD inside VMWare Fusion on a Mac, but synchronisation in 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
+---------------------
+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.
+
+**Debian:** shairport-sync is in the Debian archive.
+
+**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/.
+An Arch Linux installation package, suitable for compilation on any platform, is also available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD).
+
+**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
+```
+Note that the installation uses the libao library and so synchronisation is not available — playback glitches will occur occasionally, when the ao system's buffers overflow or underflow.
+
+**Fedora:** Please see the guide at [FEDORA.md](https://github.com/mikebrady/shairport-sync/blob/master/FEDORA.md).
+
+**Cygwin:** Please see the guide at [CYGWIN.md](https://github.com/mikebrady/shairport-sync/blob/master/CYGWIN.md).
+
+Sincere thanks to all package contributors!
+
+If you wish to build and install the latest version of Shairport Sync on Debian, Ubuntu, Fedora or Arch platforms, please continue to follow these instructions. When the program has been compiled and installed, refer to the section on Configuring Shairport Sync that follows. To build Shairport Sync from sources on FreeBSD please refer to [FREEBSD.md](https://github.com/mikebrady/shairport-sync/blob/master/FREEBSD.md).
+
+**Remove Old Versions Of Shairport Sync**
+
+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.
+
+
+**Determine The Configuration Needed**
+
+Shairport Sync has a number of different "backends" that connnect 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
+* libdaemon
+* autoconf
+* automake
+* libtool
+* libpopt
+* libconfig
+
+Optional:
+* libsoxr
+* libalac (This is a library containing the Apple ALAC decoder.)
+
+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 libdaemon-dev libpopt-dev libconfig-dev`
+- `# 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 libsndfile1-dev` if you want to use the convolution filter.
+
+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:
+```
+$ 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-configfile` 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-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, 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, 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`.
+
+**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
+```
+
+**Man Page**
+
+You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/master/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 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.) 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.
+
+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 standard `#!/bin/...` 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 standard `#!/bin/...` 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. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line:
+```
+audio_pwm_mode=2
+```
+to `/boot/config.txt` and reboot.
+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 40dB 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 40, the top 40 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -36dB, 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.
+
+Another setting to consider is the `general` `drift_tolerance_in_seconds` setting: you should set it to a larger tolerance, say 10 milliseconds – `drift_tolerance_in_seconds=0.010;` – to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC.
+
+*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/master/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.
+* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this.
+
+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, which has no internal sound card, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus:
+
+On an NSLU2, to drive a first generation Griffin iMic:
+```
+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
+```
+This will be followed by the statistics themselves at regular intervals, for example:
+```
+ -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270
+ -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267
+ -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268
+ -2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267
+ -2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268
+ -2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267
+ -2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267
+ -2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266
+```
+
+"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 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 Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+.
+
+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.15 seconds = 6,615 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/master/TROUBLESHOOTING.md) for more details.
+
+Troubleshooting
+---------------
+Please refer to [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/master/TROUBLESHOOTING.md) for a few hints, contributed by users.
+
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
new file mode 100644
index 0000000..7f086d5
--- /dev/null
+++ b/RELEASENOTES.md
@@ -0,0 +1,954 @@
+Version 3.1.7
+====
+
+**Bug Fixes**
+* In recent versions of iOS (11.2) and mac OS (10.13.2), when play is resumed after a pause, the volume level is not always restored, and, if software volume control is being used, Shairport Sync plays at full volume. This issue has been addressed by storing the last airplay volume setting when a play session ends and using it as a default when a new play session begins.
+* Better AirPlay synchronisation. Older versions of Shairport Sync added an 11,025 frame (0.25 seconds) offset to all the latencies negotiated with the sender. This seems now only to be correct for iTunes and ForkedDaapd sources, but incorrect for AirPlay sources. Accordingly, the offset is only added for iTunes and ForkedDaapd. The result is better sync with videos, e.g, YouTube, while iTunes and ForkedDaapd synchronisation is unaffected.
+* A bug in the hardware volume control affected output devices that have hardware mixers but that do not allow the volume to be set in dB. One example is the Softvol plugin in ALSA. Shairport Sync failed silently when presented with such a device when hardware volume control is enabled: the volume events have no effect. The bug has been fixed by adding two missing lines of code to the `init()` function in `audio_alsa.c`. Thanks to [Jakub Nabaglo](https://github.com/nbgl) for finding and fixing the bug.
+* A number of bug fixes due to [belboj](https://github.com/belboj). Many thanks for these!
+* Enhancements to the handling of quit requests by threads, thanks(again) to [belboj](https://github.com/belboj)!
+
+**Other Stuff**
+* Typo fix! Thanks to [corbinsantin](https://github.com/corbinsantin).
+* Stable 3.1.5 and 3.1.6 skipped to synchronise the shairport-sync.spec file contents with the release.
+
+**Enhancement**
+* The metdata output stream can include a `dapo` message carrying the DACP port number to be used when communicating with the DACP remote control. This might be useful because the port number is actually pretty hard to find and requires the use of asynchronous mdns operations. You must be using the Avahi mdns back end.
+
+Version 3.1.4
+====
+
+**Security Update**
+
+* The version of `tinysvcmdns` bundled in Shairport Sync has a buffer overflow bug: *"An exploitable heap overflow vulnerability exists in the tinysvcmdns library version 2016-07-18. A specially crafted packet can make the library overwrite an arbitrary amount of data on the heap with attacker controlled values. An attacker needs send a dns packet to trigger this vulnerability."* The vulnerability is addressed by additional checking on packet sizes. See also [CVE-2017-12087](https://bugs.launchpad.net/bugs/cve/2017-12087) and [Vulnerability in tinysvcmdns](https://bugs.launchpad.net/ubuntu/+source/shairport-sync/+bug/1729668).
+Thanks and [Chris Boot](https://github.com/bootc) for fixing this bug.
+
+**Bug Fix**
+
+* Somewhere in version 3.x, the `softvol` plugin got broken as the volume change is not applied anymore. Turned out that, for the `softvol` plugin, no `volume()` and `parameters()` are defined. Thanks to [Jörg Krause](https://github.com/joerg-krause) for locating and fixing this bug.
+
+Version 3.1.3
+====
+
+**Bug Fixes**
+* Fixed a bug that prevented Shairport Sync from starting automatically on systems using the System V startup system (e.g. Ubuntu 14.04). The problem was that the directory to be used – `/var/run/shairport-sync/` – was deleted on power down and needed to be recreated on startup. In it's absence, Shairport Sync would not start and would report a mysterious daemon error \#2.
+
+Version 3.1.2
+====
+
+Shairport Sync is more stable playing audio from YouTube and SoundCloud on the Mac.
+
+**Pesky Changes You Should Not Ignore**
+* When you update from a previous version of Shairport Sync, your output device may have been left in a muted state. You should use a command line tool like `alsamixer` or `amixer` to unmute the output device before further use.
+
+**Change of Default**
+* The default value for the `alsa` setting `mute_using_playback_switch` has been changed to `"no"` for compatability with other audio players on the same machine. The reason is that when this setting is set to `"yes"`, the output device will be muted when Shairport Sync releases it. Unfortunately, other audio players using the output device expect it to be unmuted, causing problems. Thanks to [Tim Curtis](https://github.com/moodeaudio) at [Moode Audio](http://moodeaudio.org) and [Peter Pablo](https://github.com/PeterPablo) for clarifying the issue.
+
+**Bug Fixes**
+* Fixed bugs that made Shairport Sync drop out or become unavailable when playing YouTube videos, SoundCloud streams etc. from the Mac. Background: there has been a persistent problem with Shairport Sync becoming unavailable after playing, say, a YouTube clip in a browser on the Mac. Shairport Sync 3.1.2 incorporates a change to how certain AirPlay messages are handled. Introduced in nascent form in 3.1.1, further follow-on changes have improved the handling of player lock and have simplified and improved the handling of unexpected loss of connection. Shairport Sync also now works properly with SoundCloud clips played in a browser on the Mac.
+* Using [infer](https://github.com/facebook/infer/blob/master/README.md), a number of silent issues have been detected, such as not checking some calls to `malloc` to ensure the response is not NULL. Most of these have been addressed by additional checks.
+
+Version 3.1.1
+====
+
+**Bug Fixes**
+* A bug in the `sndio` backend has been fixed that caused problems on some versions of Linux.
+* A change has been made to how Shairport Sync responds to a `TEARDOWN` request, which should make it respond better to sequences of rapid termination and restarting of play sessions. This can happen, for example, playing YouTube videos in Safari or Chrome on a Mac.
+* Choosing `soxr` interpolation in the configuration file will now cause Shairport Sync to terminate with a message if Shairport Sync has not been compiled with SoX support.
+* Other small changes.
+
+Version 3.1
+====
+Version 3.1 brings two new backends, optional loudness and convolution filters, improvements in non-synchronised backends, enhancements, stability improvements and bug fixes.
+
+**New Features**
+* A `sndio` backend gives Shairport Sync native fully synchronised output on OpenBSD and FreeBSD, thanks to the work of [Tobias Kortkamp (t6)](https://github.com/t6).
+* A `pa` backend now allows Shairport Sync to provide synchronised output on PulseAudio-equipped systems -- many desktop Linuxes use PulseAudio as their sound manager.
+* Optional loudness and convolution filters can be incorporated in the audio processing chain, thanks to the fantastic work of [yannpom](https://github.com/yannpom).
+* A volume-change program hook `run_this_when_volume_is_set` has been added to the `general` settings stanza to execute an application whenever the volume is changed.
+
+**Pesky Changes You Should Know About**
+* The `audio_backend_buffer_desired_length_in_seconds` and `audio_backend_latency_offset_in_seconds` settings have been moved from individual backend stanzas to the `general` stanza. They now have an effect on every type of backend.
+* If you are using a System V (aka `systemv`) installation, please note that the default location for PID file has moved -- it is now stored at `/var/run/shairport-sync/shairport-sync.pid`. This change is needed to improve security a little and to improve compatability across platforms. If you're not doing anything strange, this should make no difference.
+
+**Enhancements**
+* Resynchronisation, which happens when the synchronisation is incorrect by more than 50 ms by default, should be a lot less intrusive when it occurs – it should now either insert silence or skip frames, as appropriate.
+* The Linux installer has been improved and simplified and a FreeBSD installer introduced.
+* A new setting, `audio_backend_silent_lead_in_time`, allows you to set the duration of the period of silence played (the "silent lead-in") before a play session starts.
+* A new command-line option, `--logOutputLevel`, allows you to output the volume levels to the log whenever they are changed. This may be useful during setup.
+* Improvements have been made to the handling of large items of metadata over UDP.
+* A new command line option, `-j`, demonizes Shairport Sync without creating a PID file.
+* A new `alsa`-only setting, `mute_using_playback_switch`, is available for advanced use.
+* Other minor enhancements.
+
+**Bug Fixes**
+* Stability improvements. More care has been taken (!) to make code thread-safe, resulting in improved stability.
+* Conversion from stereo to mono has been fixed to avoid clipping while preserving full resolution. Thanks to [Robert Jones (RobDeBagel)](https://github.com/RobDeBagel) for bringing this to notice.
+* Short intrusions of audio at the start of a new session from the end of the previous session have been eliminated.
+* Many (many!) miscellaneous bugs fixed.
+
+Version 3.0.2
+====
+**Bug Fixes**
+* Fixed bugs in the `ao`, `pulseaudio` and `sndio` back ends. Basically they were expecting default sample rate and depth information, and were terminating when they saw explicit rate and depth data.
+
+Version 3.0.1
+====
+This update fixes one alarming and potentially very noisy bug and restores the identification of Shairport Sync as "ShairportSync" so that TuneBlade recognises it as an open source application.
+**Bug Fixes**
+* Fixed a bug that was causing Shairport Sync to possibly make a very loud and alarming noise whenever an audio frame was missing.
+* In 2.8.6, a change was made to the way Shairport Sync identified itself, so that it could be recognised by TuneBlade as an open source application and treated preferentially. That change was inadventently lost in the transition from 2.8.6 to 3.0. Now it's restored.
+
+Version 3.0
+====
+
+Big Update
+----
+Version 3 brings in support for 24-bit and 32-bit (and 8 bit!) DACs and for DACs running at multiples of 44,100 samples per second.
+
+The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC.
+
+Here is the list of new features:
+
+**New Features**
+* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices.
+* 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. It's only worth doing if 44,100 samples per second output is not available.
+* Internal processing including software volume control and interpolation is done after sample size and rate conversion.
+* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac), to be installed. Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file.
+* Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further.
+* Choose Network Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically.
+* Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3.
+* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details.
+
+
+Pesky Changes You Cannot Ignore
+----
+* Processor load is up by about 11%.
+* Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details.
+* Sox-based interpolation at higher sample rates may overload your CPU -- you might have to choose between higher sample rates and sox-based interpolation.
+
+
+Version 3.0rc0 – Release Candidate 0
+----
+Note: all Version 3 changes are summarized above.
+
+**New Feature**
+* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details.
+
+**Other changes**
+* Updates to `man` page and [README](https://github.com/mikebrady/shairport-sync/blob/development/README.md). Reports of typos or suggestions for improvement are welcome!
+
+Version 3.0d24 – Development Version
+----
+Note: all Version 3 changes are summarized above.
+
+**New Feature**
+* Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3.
+
+Version 3.0d23 – Development Version
+----
+Note: all Version 3 changes are summarized above.
+
+**New Feature**
+* Choose Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically.
+
+Version 3.0d22 – Development Version
+----
+Note: all Version 3 changes are summarized above.
+
+**Bug Fix**
+* Fixed a bug which prevented successful building in the OpenWrt build system. The problem was caused by an `#include apple_alac.h` in `player.c` which was actioned even if the apple alac decoder was not selected. This caused the OpenWrt build system to expect the standard C++ library – required by the apple alac code – to be referenced, but it was not specified on the build manifest and therefore stopped the build. The solution was to make the `#include` conditional on selecting the apple alac decoder.
+
+Version 3.0d21 – Development Version
+----
+Note: all Version 3 changes are summarized above.
+
+**Bug Fix**
+* Fixed a bug which turned off resync by default. Duh.
+
+Version 3.0d20 – Development Version
+----
+Note: all Version 3 changes are summarized above.
+
+**Bug Fix**
+* Fix a small and generally silent error in configure.ac so that it only looks for the systemd direcotry if systemd has been chosen. It caused a warning when cross-compiling.
+
+Version 3.0d19 – Development Version
+----
+Note: all Version 3 changes are summarized above.
+
+**New Feature**
+* Reduces processor load back to V2.X levels by using a precalculated array of pseudorandom numbers to do dithering. Doesn't seem to make any audible difference.
+
+Version 3.0d18 – Development Version
+----
+Note: all Version 3 changes are summarized above.
+
+**New Features**
+* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. (Other back ends are not updated yet.)
+* 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling.
+* Internal processing including software volume control and interpolation is done after sample size and rate conversion.
+* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file.
+* Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further.
+* Settings that were denominated in frames are now deprecated but still honoured. Deprecation warnings are issued.
+
+Pesky Changes You Cannot Ignore
+====
+* Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details.
+* Sox-based interpolation at higher sample rates may overload your CPU -- yopu might have to choose between higher sample rates and sox-based interpolation.
+
+**Bugs**
+* Documentation is not updated.
+
+Version 2.8.6 – Stable Candidate
+----
+
+**Enhancements**
+* This release contains a small change – it identifies itself as a ShairportSync device rather than an AirPort device. This should make it possible for Tuneblade, and possibly other players, to recognise it correctly.
+
+Version 2.8.5 – Stable Version
+----
+This release includes bug fixes and minor enhancements and is recommended for all users.
+
+Note: if you're upgrading, there is a new `./configure` option:
+====
+The build process now uses the directory path `sysconfdir` to determine where to place the configuration file `shairport-sync.conf`.
+The default value for `sysconfdir` is `/usr/local/etc` which is used in the BSD family, whereas `/etc` is normally used in Linux.
+To retain the present behaviour of Shairport Sync, *you must add an extra parameter to the `./configure... ` command.* The parameter you must add is `--sysconfdir=/etc`. (This has been added to the sample configuration command line in README.md.)
+
+The enhancements and bug fixes in 2.8.5 were made in versions 2.8.4.1 to 2.8.4.8 inclusive. Please read below for the full list.
+
+For advice on updating an installation you built yourself,
+please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page.
+
+Version 2.8.4.8 – Development Version
+----
+**Enhancements**
+* Add a new metadata item `clip` (for `CL`ient `IP`). This item is a string comprising the IP number of the "client", and is sent when a play session is starting. The "client" is the sender of the audio stream, e.g. iTunes on a Mac, or the Music player in iOS.
+* When synchronisation has been disabled on the ALSA device (you should only do this for testing), Shairport Sync now refrains from asking for buffer length information from the device.
+
+Version 2.8.4.7 – Development Version
+----
+
+* This update means the build process now uses the directory path `sysconfdir` to determine where to place the configuration file `shairport-sync.conf`. The default value for `sysconfdir` is `/usr/local/etc` which is used in the BSD family, whereas `/etc` is normally used in Linux. So, to retain the present behaviour of Shairport Sync, you must add an extra parameter to the `./configure... ` command. The parameter you must add is `--sysconfdir=/etc`. (This has been added to the sample configuration command line in README.md.)
+* Shairport Sync has been updated to use the value of `sysconfdir` to determine where to look for the configuration file. If `sysconfdir` has been left with its default value of `/usr/local/etc`, then Shairport Sync will look for `/usr/local/etc/shairport-sync.conf`. If, as recommended for Linux, `sysconfdir` has been set to `/etc`, then Shairport Sync will look, as before, for `/etc/shairport-sync.conf`.
+
+**Enhancement**
+* The version string output when you use the command-line option `-V` now includes the value of the `sysconfdir`, e.g. `2.8.4.7-OpenSSL-Avahi-ALSA-soxr-sysconfdir:/etc`.
+
+Version 2.8.4.6 – Development Version
+----
+**Enhancement**
+* Add a new `alsa` configuration setting: `use_mmap_if_available` to control the use of mmap. The default is `"yes"` -- see [#351](https://github.com/mikebrady/shairport-sync/issues/351).
+
+Version 2.8.4.5 – Development Version
+----
+**Enhancement**
+* Handle varying packet lengths -- this makes it compatible with the HTC Connect, HTCs AirPlay implementation. Thanks to [Jörg Krause](https://github.com/joerg-krause) for his detective work, and see [#338](https://github.com/mikebrady/shairport-sync/issues/338).
+
+Version 2.8.4.4 – Development Version
+----
+**Enhancement**
+* Use alsa direct access (mmap) feature to improve performance if mmap is supported. Thanks to [Yihui Xiong](https://github.com/xiongyihui).
+
+Version 2.8.4.3 – Development Version
+----
+**Bug Fix**
+
+* Set the RTSP socket to close on `exec()` of child processes; otherwise, background `run_this_before_play_begins` or `run_this_after_play_ends` commands that are sleeping prevent the daemon from being restarted because the listening RTSP port is still in use. Fixes [#329](https://github.com/mikebrady/shairport-sync/issues/329).
+
+Version 2.8.4.2 – Development Version
+----
+**Bug Fixes**
+
+* Fixed an issue where you could not compile the audio_pipe back end without enabling metadata support (thanks to [busa-projects](https://github.com/busa-projects) for reporting the issue).
+* Fixed a few small issues causing compiler warnings in `mdns_dns_sd.c`.
+
+
+**Other**
+* Removed the INSTALL file – it's generated automatically by `autoreconf -fi` anyway – added it to the files to be ignored in `.gitignore` and added a simple `INSTALL.md` file.
+
+Version 2.8.4.1 – Development Version
+----
+**Bug Fixes**
+
+* Fixed two issues when including support for `pulseaudio`.
+* Corrected two small errors in sample parameters for the UDP metadata stream settings, thanks to [rkam](https://github.com/rkam).
+
+Version 2.8.4 – Stable Version
+----
+This release includes important bug fixes and minor enhancements and is recommended for all users. No settings need to be changed. For advice on updating an installation you built yourself, please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page.
+
+The following is a summary of the bug fixes and enhancements since version 2.8.3.
+
+**Bug Fixes**
+
+* Checks have been added for empty or NULL audio buffers that were causing assertion violations and subsequent abnormal program termination.
+
+* An IPv6 bug has been fixed — a bug in the networking software would not allow an IPv6 link-local connection to be made from a client if Shairport Sync was running on a device with more than one network interface. The solution was to take account of the `config_id` information.
+
+* Some problems have been fixed with the non-blocking write function used to write metadata.
+
+* A bug in the volume control transfer function has been fixed, thanks to [Jörg Krause](https://github.com/joerg-krause).
+
+**Enhancements**
+
+* Shairport Sync now works with [AllConnect/Streambels](http://allconnectapp.com) on Android with password protection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1,000, as the timekeeping of these clients isn't as accurate as that of iTunes, etc.)
+
+* The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have many IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, when Shairport Sync has to create connections back to the source, it would use an automatically-assigned IP number for itself, but that number might not be same as the the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. Thanks to [ejurgensen](https://github.com/ejurgensen) for help with this.
+
+* Experimental support has been added for a softvol plugin, thanks to the work of [Jörg Krause](https://github.com/joerg-krause) -- see [#293](https://github.com/mikebrady/shairport-sync/pull/293).
+
+* A `playback_mode` setting has been added to allow the selection of `stereo` (default) or `mono` playback -- thanks to [faceless2](https://github.com/faceless2).
+
+* The new default service name is now the device's `hostname`, with its first character capitalised (ASCII only).
+
+* Substitutions can now be made in the service name. The following substitutions can be used in the service name: `%h` for the `hostname`, `%H` for the `hostname` with the first letter capitalised, `%v` for the version number and `%V` for the full version string. Maximum length is 50 characters.
+
+* An existing `shairport-sync.service` file will not be overwritten by `sudo make install`.
+
+* The text strings advertising the capabilities of Shairport Sync over Bonjour/Zeroconf/Avahi have been changed and now more closely match those of an AirPort Express Base Station (First Generation).
+
+* It is now possible to set the amount of time to wait for the metadata pipe to become ready for writing. The setting is called `pipe_timeout` in the `metadata` section. Default is 5,000 milliseconds.
+
+* Metadata can now be provided via UDP -- thanks to [faceless2](https://github.com/faceless2).
+
+* Statistics output is more machine readable -- thanks to [Jörg Krause](https://github.com/joerg-krause)
+* The `shairport-sync.spec` file has been updated for compatability with building Debian packages using `checkinstall` -- thanks to [vru1](https://github.com/vru1).
+
+Version 2.8.3.11 – Development Version
+----
+**Bug Fix**
+
+Fixed some problems with the non-blocking write function used to write to the metadata pipe.
+
+**Enhancement**
+
+It is now possible to set the amount of time to wait for the metadata pipe to become ready for writing. The setting is called `pipe_timeout` in the `metadata` section. Default is 5,000 milliseconds.
+
+Version 2.8.3.10 – Development Version
+----
+**Bug Fix**
+
+* Restored metadata feed lost in 2.8.3.7.
+
+Version 2.8.3.9 – Development Version
+----
+**Enhancements**
+
+* Substitutions can now be made in the service name, i.e. the name that appears in iTunes, etc. The following substitutions can be used in the service name you specify: `%h` for the hostname, `%H` for the hostname with the first letter capitalised, `%v` for the version number and `%V` for the full version string. Maximum length is 50 characters.
+
+* The new default service name is simply the hostname, with its first character capitalised.
+
+* An existing `shairport-sync.service` file will not be overwritten by `sudo make install`.
+
+Version 2.8.3.7 – Development Version
+----
+**Enhancements**
+
+* Shairport Sync now works with AllConnect/Streambels on Android with password protection. (As with all Android clients, you should set the `drift` to something large, like 500 or 1,000, as the timekeeping of these clients isn't as accurate as that of iTunes, etc.)
+
+* The text strings advertising the capabilities of Shairport Sync over Bonjour/Zeroconf/Avahi have been changed and now more closely match those of an AirPort Express Base Station (First Generation).
+
+Version 2.8.3.6 – Development Version
+----
+**Bug fix**
+
+An IPv6 link-local connection issue was fixed. A bug in the networking software would not allow an IPv6 link-local connection to be made from a client if Shairport Sync was running on a device with more than one network interface. The solution was to take account of the `config_id` information.
+
+Version 2.8.3.5 – Development Version
+----
+**Enhancement**
+
+Experimental support for a softvol plugin, thanks to the work of [Jörg Krause](https://github.com/joerg-krause) -- see [#293](https://github.com/mikebrady/shairport-sync/pull/293).
+
+**Bug fix**
+
+Add checks for empty or NULL audio buffers that seem to be causing assertion violations and subsequent abnormal program termination.
+
+Version 2.8.3.4 – Development Version
+----
+
+**Bug Fix**
+
+The networking subsystem has been modified to always use the same IP number during a session. Background: the computer Shairport Sync is running on can have many IP numbers active at the same time – various IPv6 numbers and also various IPv4 numbers. During a play session, when Shairport Sync has to create connections back to the source, it would use an automatically-assigned IP number for itself, but that number might not be same as the the number used earlier in the session. From now on, it will always use the same IP number it used when the connection was first established. Thanks to [ejurgensen](https://github.com/ejurgensen) for help with this.
+
+
+Changed the `mono` setting for a `playback_mode` setting with two possible values: `stereo` (default) and `mono`.
+
+Version 2.8.3.3 – Deleted
+----
+
+Version 2.8.3.2 – Deleted
+----
+
+Version 2.8.3.1 – Development Version
+----
+Added a new `mono` setting -- thanks to [faceless2](https://github.com/faceless2). Documentation to follow.
+
+Version 2.8.3 – Stable Version
+----
+A bug in 2.8.2 caused Avahi to fail at startup under some circumstances with older installations. The problem was that sometimes the `regtype` setting would not be initialised properly.
+
+Version 2.8.2 – Stable Version
+----
+Version 2.8.2 is derived from development version 2.9.5.7 and has stability improvements, bug fixes and a few special-purpose enhancements.
+
+For full details, please refer to the release notes here, back as far as 2.8.1.
+
+Version 2.9.5.7 – Development Version
+----
+Version 2.9.5.7 contains general bug fixes and enhancements for some special situations.
+
+**Bug Fixes**
+
+* Getting delay and latency information from the `alsa` subsystem has been improved -- bugs fixed, error codes handled better, arithmetic handling (hopefully) better.
+* If latency information is temporarily unavailable from the `alsa` subsystem, skip trying to synchronise until the next time.
+* Some condition variables and a mutex were uninitialised, yikes! Fixed.
+* A bug that set the output volume to maximum at the same time as muting the output has been fixed. AFAIK, this was inaudible, but it was scary looking.
+* Recover from name collisions in Avahi.
+* Detect and handle empty buffers better.
+
+**Enhancements**
+
+* Turn off synchronisation. This is an advanced feature and generally leads to buffer underrun or overrun.
+* Set `alsa` buffer size and `alsa` period size. There are advanced features, mainly for debugging. They may be removed.
+* Change the Zeroconf/Bonjour `regtype` to enable Shairport Sync to continue to run but to be invisible to AirPlay clients. Special purpose usage only.
+* Output total number of packets and the play time of a session when statistics are enabled.
+
+Version 2.9.4 – Development Version
+----
+Version 2.9.4 corrects some bugs in how Avahi error conditions are handled.
+
+**Bug Fix**
+* During operation, if the network disappeared, Avahi would occasionally report an error. This would cause Shairport Sync to attempt to terminate gracefully (which is the wrong thing to do in the circumstances). However, the termination attempt was actually causing an assertion violation crash. These errors are now simply logged.
+
+Version 2.9.3 – Development Version
+----
+Version 2.9.3 is 2.8.1 with documentation and version changes to indicate that it's in the development branch.
+
+Version 2.8.1 – Stable Version
+----
+Version 2.8.1 is derived from development version 2.9.2 and has stability improvements and important bug fixes.
+
+For full details, please refer to the release notes here, back as far as 2.9.1.
+
+Version 2.9.2 – Development Version
+----
+Version 2.9.2 focusses on further bug fixes and stability improvements.
+* Enhanced stability: an important bug has been fixed in the handling of missing audio frames – i.e. what happens when a frame of audio is truly missing, after all attempts to fetch it have been unsuccessful. The bug would cause Shairport Sync to do an unnecessary resynchronisation, or, if resync was turned off, to jump out of sync. This is a long-standing bug – thanks to [Jörg Krause](https://github.com/joerg-krause) for identifying it.
+* An extra diagnostic has been added which gives the mean, standard deviation and maximum values for inter-packet reception time on the audio port. It may be useful for exploring line quality.
+
+Version 2.9.1 – Development Version
+----
+Version 2.9.1 focusses on bug fixes and stability improvements.
+* Stability improvements are concentrated on what happens when a play sessions ends and is followed immediately by a new session. This happens in iOS 9.2 when you click to the next track or to the previous track. It also happens playing YouTube videos when a Mac's System Audio is routed through AirPlay. Thanks to [Tim Curtis](https://github.com/moodeaudio) for help with these issues.
+* A workaround for an apparent flushing issue in TuneBlade has been included. Thanks to [gibman](https://github.com/gibman) for reporting this issue.
+* A number of bug fixes have been made to `configure.ac` – thanks to [Jörg Krause](https://github.com/joerg-krause).
+
+Version 2.8 – Stable Version
+----
+Version 2.8 is derived from version 2.7.10 with slight documentation updates. Here is a summary of changes between the last stable version – 2.6 – and this version. For full details, refer to the release notes here, back as far as 2.7.
+
+**New Feature**
+* For hardware mixers with a restricted range (including many cheaper USB DACS), the general `volume_range_db` can be used to specify a wider range than the hardware provides – the extra range is provided by software.
+
+**Enhancements**
+* The `man` manual and the html version of it are automagically rebuilt if `xml2man` and friends are available.
+* Volume-setting metadata is now sent even when the volume level is to be ignored by Shairport Sync itself.
+* Shairport Sync waits a little longer before asking for missing packets to be resent. Sometimes packets are just arriving slightly out of order and don't need to be asked for again.
+* The build scripts have been modified to be a little more compatible with standard practice.
+* A Continuous Integration (CI) system – Travis CI – is now used to do some limited build checking (thanks guys!).
+* Support added for compiling on Cygwin.
+* Added `rtptime` tags to metadata and picture metadata.
+* Replaced and improved the dither algorithm used with the software volume control. The new dither code gives a two bit peak-to-peak dither based on a Triangular Probability Distribution Function (TPDF).
+* Disabled picture sending if pictures haven’t been asked for.
+
+**Bug fixes**
+* Fixed a bug that prevented Shairport Sync from correctly setting the hardware mixer volume if it had been altered externally. Thanks to [Tim Curtis](https://github.com/moodeaudio) for help with these issues.
+* Modified the shutdown behaviour so that a shutdown followed immediately by a play request is handled better. This was causing iOS 9.2 sometimes to drop the Airplay link between tunes.
+* Fixed a data-alignment bug that would cause a crash in certain circumstances on ARM processors with metadata enabled.
+* Corrected the names for a few settings tags.
+* Fixed some typos and misspellings.
+* Miscellaneous small bug fixes.
+
+Version 2.7.10 -- Development Version
+----
+**New Feature**
+* If the `ignore_volume_control` setting was `yes`, Shairport Sync really did ignore volume control settings and did not send any volume metadata (i.e. `pvol` coded metadata). Now, while continuing to ignore volume control settings, it sends a `pvol` token where the first number is the AirPlay volume, as before, but the remaining three parameters are set to zero.
+
+Version 2.7.9 -- Development Version
+----
+**Bug Fix**
+* Oops – brown-bag update. Fixed a crashing bug introduced in the last release, caused by not checking for a hardware mixer before trying to access it, duh.
+
+Version 2.7.8 -- Development Version
+----
+**Bug Fix**
+* Fixed an issue whereby Shairport Sync did not reset the hardware mixer volume level before resuming playing. The issue was caused by not releasing and later reaquiring the mixer when pausing and resuming. Thanks to [Tim Curtis](https://github.com/moodeaudio) for reporting the issue.
+
+Version 2.7.7 -- Development Version
+----
+**Enhancements**
+* Add note about the Arch Linux Community repository package `shairport-sync`. Thanks to [Anatol Pomozov](https://github.com/anatol).
+* Shairport Sync doesn't ask for packets to be resent quite so quickly -- it waits about half a second now before asking for missing packets to be resent.
+
+**Bug Fixes**
+* Improved Shairport Sync's behaviour when it's asked to stop a play session and immediately start another. The signalling system used to stop threads was sometimes stopping threads belonging to the new session. This affected iOS 9.2 users going to the next track -- sometimes the player would become unavailable for an instant and disconnect the session. Th problem still happens occasionally.
+* Removed code favouring the use of "public" IPv6 addresses as source addresses when connecting to a distant IPv6 port – Neither OpenWrt nor FreeBSD can use it at present. Also, it's not clear if any problems are being caused by not favouring public IPv6 addresses.
+
+Version 2.7.6 -- Development Version
+----
+**Bug Fixes**
+* Look for the correct tag name for desired `ao` buffer length: `audio_backend_buffer_desired_length` rather than `audio_backend_buffer_desired_length_software`.
+* Fix a few FreeBSD compilation bugs.
+* Fix a few documentation issues and typos. Thanks to [Chris Boot](https://github.com/bootc).
+
+**Enhancements**
+* Add note about installing to Mac OS X. Thanks to [Serg Podtynnyi](https://github.com/shtirlic).
+* Add automatic rebuild of manpage and html documentation when `xmltoman` and friends are available. Thanks to [Chris Boot](https://github.com/bootc).
+* Favour the use of "public" IPv6 addresses as source addresses when connecting to a distant IPv6 port.
+
+Version 2.7.5 -- Development Version
+----
+**New Features**
+* Ubuntu PPA files now available at https://launchpad.net/~dantheperson.
+
+**Enhancements**
+* Broaden the use of the value `$PREFIX` instead of the path `/usr/local/bin` during configuration. Thanks to [dantheperson](https://github.com/dantheperson).
+
+Version 2.7.4 -- Development Version
+----
+**Enhancements**
+* Use the correct method for finding the `systemd` unit path, as recomended by debain maintainers and
+http://www.freedesktop.org/software/systemd/man/daemon.html#Installing%20Systemd%20Service%20Files. Thanks to [dantheperson](https://github.com/dantheperson).
+* Rather than hardwire the path `/usr/local/bin` as the path to the shairport-sync executable, the value of `$PREFIX` is now used during configuration. Thanks to [Nick Steel](https://github.com/kingosticks).
+* Add some extra diagnostic messages if the hardware buffer in the DAC is smaller than desired.
+* If metadata has been enabled, but if picture sending has not been requested and the source sends pictures anyway, omit them from the metadata feed. Thanks to [Jörg Krause](https://github.com/joerg-krause).
+
+**Bug Fixes**
+* Fixed a data alignment issue in the handling of metadata on some processors. Thanks to [Jörg Krause](https://github.com/joerg-krause).
+* Removed an `assert` which would terminate the program if a malformed packet of data was received.
+* Look for the correct tag name for desired alsa buffer length: `audio_backend_buffer_desired_length` rather than `audio_backend_buffer_desired_length_software`.
+
+Version 2.7.3 -- Development Version
+----
+**Bug Fix**
+* The dither code was broken in Shairport Sync and also less than ideal anyway. Fixed and improved. Dither is added whenever you use the software volume control at less than full volume. See http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf for a very influential paper by Lipshitz, Wannamaker and Vanderkooy, 1992. The dither code in Shairport Sync was inherited from Shairport and does not conform to the recommendations in the paper -- specifically the implementation would give one bit of dither where the paper recommends two bits peak-to-peak. The other thing is that the inherited dither code was actually broken in Shairport Sync. So, the new dither code gives a two bit peak-to-peak dither based on a Triangular Probability Distribution Function (TPDF). It sounds like a very low-level white noise, unmodulated by the audio material. It would be nice if it was even lower, but it's better than listening to the artifacts present when dithering is disabled.
+
+Version 2.7.2 -- Development Version
+----
+**Bug Fix**
+* Fix a bug that suppressed output of the `rtptime` associated with metadata and with picture information coming from the audio source and passed on via the metadata pipe.
+
+**Other Changes**
+* Added some more information to the log whenever problems are detected with the proposed alsa device.
+
+Version 2.7.1 -- Development Version
+----
+**Bug Fix**
+* The new volume-extension code was not correctly setting the volume after a pause / resume. Fixed.
+
+Version 2.7 -- Development Version
+----
+**New Features**
+* Extend the volume range for some DACs. Background: some of the cheaper DACS have a very small volume range (that is, the ratio of the highest to the lowest volume, expressed in decibels, is very small). In some really cheap DACs it's only around 30 dB. That means that the difference betweeen the lowest and highest volume settings isn't large enough. With the new feature, if you set the `general` `volume_range_db` to more than the hardware mixer's range, Shairport Sync will combine the hardware mixer's range with a software attenuator to give the desired range. For example, suppose you want a volume range of 70 dB and the hardware mixer offers only 30 dB, then Shairport Sync will make up the other 40 dB with a software attenuator. One drawback is that, when the volume is being changed, there may be a slight delay (0.15 seconds by default) as the audio, whose volume may have been adjusted in software, propagates through the system. Another slight possible drawback is a slightly heavier load on the processor.
+* Check for underflow a little better when buffer aliasing occurs on very bad connections...
+* Add extra debug messages to the alsa back end to diagnose strange DACs.
+* Add configuration file for the `libao` back end -- to change the buffer size and the latency offset, same as for stdout.
+* Add `shairport-sync.exe` to `.gitignore`.
+* Add a check to support compilation on a CYGWIN platform.
+* Add `rtptime` tags to metadata and picture information and add two new metadata items to precede and follow the transmission of a picture. Background: it seems that metadata and picture information for the same item, e.g. a track, are normally tagged with a timestamp called the `rtptime`; if they refer to the same item, they will have the same `rtptime` tags. The update here is to add the `rtptime` value, if available, as data to the `mdst` and `mden` metadata items, which are sent before ("MetaData STart") and after ("MetaData ENd") a metadata sequence.
+In addition, similar tags -- `pcst` ("PiCture STart") and `pcen` ("PiCture ENd") are now sent before and after a picture with the `rtptime` value, if available, sent as data.
+By the way, the progress metadata (`prgr` for "PRoGRess"), which is sent just when a track starts, contains the same `rtptime` as its middle element.
+
+
+Version 2.6 -- Stable Version
+----
+This is basically version 2.4.2 with two small fixes. It's been bumped to 2.6 because (1) the new features added between 2.4.1 and 2.4.2 deserve more than just a bug-fix increment and (2) the development versions (2.5.x) should have lower numbers than the release versions, so that releases are always seen as upgrades. For example: 2.5.0.9 --> 2.6 looks like an upgrade, whereas 2.5.0.9 --> 2.4.2 looks like a downgrade.
+
+**Fixes**
+* For `systemd` users, the `shairport-sync.service` file is updated to point to the correct location of the shairport-sync application.
+* For Fedora users, the `shairport-sync.spec` file is updated to refer to 2.6.
+
+
+Version 2.4.2
+----
+This release has important enhancements, bug fixes and documentation updates. It also appears to bring compatiblity with Synology NAS devices.
+
+
+**New Features**
+* Source-specified Latencies. Shairport Sync now uses the latencies specified by the audio source. Background: the AirPlay protocol used by Shairport Sync allows the audio source to specify the exact delay or latency that should be applied to the audio stream. Until now, Shairport Sync ignored this information and used fixed preset latencies that were selected on the basis of the "User-Agent" setting. Using source-specified latencies means that Shairport Sync is able adapt automatically to different sources.
+Using source-specified latencies is now automatic unless non-standard static latencies have been specified in the configuration file or command line. Using non-standard latencies is usually done to compensate for delays in the back end of the system. For example, if the audio amplifier being driven by Shairport Sync has an inherent delay of its own -- as happens with many home theatre and surround sound systems -- then some users have reduced the latencies used by Shairport Sync to compensate. This usage is discouraged -- the `audio_backend_latency_offset` in the appropriate backend stanza (e.g. in the "alsa" stanza) should be used for this. Static latency settings are now deprecated, and will be removed in a future version of Shairport Sync.
+* Set Volume Range. This is a new setting that allows you to use just a portion of the full range of attenuation offered by a mixer. 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. It is for this reason that many domestic HiFi systems have a volume control range of only 60 to 80 dB.
+ Another possible reason to use this setting might be because the range specified by the mixer does not match the actual 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 35dB. The new `volume_range_db` setting in the `general` stanza 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 35. Using it in this case gives the volume control a much more useful range of settings.
+
+**Bug fixes**
+* Sometimes, especially when using Shairport Sync as a system output, it would not play the audio stream. This was caused by an improperly initialised variable. Fixed. Synology NAS devices now seem to be working with Shairport Sync.
+* Fix in the `shairport.c`: the USE_CUSTOM_LOCAL_STATE_DIR macro was still being used when it should have been USE_CUSTOM_PID_DIR.
+* Fix a crashing bug -- if metadata was enabled but a pipename was not supplied, boom.
+
+**Other Changes**
+* Initial timing accuracy improved. The estimate of when to play the starting frame of the audio sequence has improved significantly. This leads to fewer corrections being needed at the start.
+* Volume ratios expressed in decibels are now consistently denominated in voltage decibels rather than power decibels. The rationale is that the levels refer to voltage levels, and power is proportional to the square of voltage.
+Thus a ratio of levels of 65535 to 1 is 96.3 dB rather than the 48.15 dB used before.
+* The latency figure returned to the source as part of the response to an rtsp request packet is 11,025, which may (?) be meant to indicate the minimum latency the device is capable of.
+* An experimental handler for a GET_PARAMETER rtsp request has been added. It does nothing except log the occurence.
+* The RTSP request dispatcher now logs an event whenever an unrecognised rtsp has been made.
+
+
+Version 2.4.1
+----
+This release has three small bug fixes and some small documentation updates.
+
+**Bug Fixes**
+
+Changes from the previous stable version -- 2.4 -- are summarised here:
+ * The USE_CUSTOM_LOCAL_STATE_DIR macro was still being used when it should have been USE_CUSTOM_PID_DIR. This could affect users using a custom location for the PID directory.
+ * A compiler error has been fixed that occured if metadata was enabled and tinysvcmdns was included.
+ * A crash has been fixed that occured if metadata was enabled and a metadata pipename was not specified.
+(Thanks to the contributors who reported bugs.)
+
+**Small Changes**
+ * If a mixer being used to control volume does not have a control denominated in dB, a warning is logged and the mixer is not used.
+ * Slight revisions have been made to the configuration file `configure.ac` to make compilation on FreeBSD a little easier.
+
+Version 2.4
+----
+**Stable release**
+
+This stable release is the culmination of the 2.3.X sequence of development releases.
+
+**Change Summary**
+
+Changes from the previous stable version -- 2.2.5 -- are summarised here:
+ * Settings are now read from a configuration file. Command-line settings are supported but discouraged.
+ * Metadata is now supported -- it can be delivered to a unix pipe for processing by a helper application. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader.
+ * Raw PCM audio can be delivered to standard output ("stdout") or to a unix pipe. The internal architecture has changed considerably to support this.
+ * Support for compilation on OpenWrt back to Attitude Adjustment.
+ * Can play unencrypted audio streams -- complatible with, e.g. Whaale.
+ * Uses the libconfig library.
+ * Runs on a wider range of platforms, including Arch Linux and Fedora.
+ * Bug fixes.
+
+Please note that building instructions have changed slightly from the previous version.
+Also, the `-t hardware/software` option has been deprecated in the alsa back end.
+
+Version 2.3.13
+----
+**Note**
+* We're getting ready to release the development branch as the new, stable, master branch at 2.4. If you're packaging Shairport Sync, you might prefer to wait a short while as we add a little polish before the release.
+
+**Changes**
+* Harmonise version numbers on the release and on the `shairport.spec` file used in Fedora.
+
+Version 2.3.12
+----
+**Note**
+* We're getting ready to release the development branch as the new, stable, master branch at 2.4. If you're packaging Shairport Sync, you might prefer to wait a short while as we add a little polish before the release.
+
+**Changes**
+* `update-rc.d` has been removed from the installation script for System V because it causes problems for package makers. It's now noted in the user installation instructions.
+* The `alsa` group `mixer_type` setting is deprecated and you should stop using it. Its functionality has been subsumed into `mixer_name` – when you specify a `mixer_name` it automatically chooses the `hardware` mixer type.
+
+
+**Enhancements**
+* Larger range of interpolation. Shairport Sync was previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 frames. This contraint has been relaxed, and it is now able to make corrections of up to 1 in 352 frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows:
+ * No corrections happen for the first five seconds.
+ * Corrections of up to about 1 in 1000 for the next 25 seconds.
+ * Corrections of up to 1 in 352 thereafter.
+
+**Documentation Update**
+* Nearly there with updates concerning the configuration file.
+
+Version 2.3.11
+----
+Documentation Update
+* Beginning to update the `man` document to include information about the configuration file. It's pretty sparse, but it's a start.
+
+Version 2.3.10
+----
+Bug fix
+* The "pipe" backend used output code that would block if the pipe didn't have a reader. This has been replaced by non-blocking code. Here are some implications:
+ * When the pipe is created, Shairport Sync will not block if a reader isn't present.
+ * If the pipe doesn't have a reader when Shairport Sync wants to output to it, the output will be discarded.
+ * If a reader disappears while writing is occuring, the write will time out after five seconds.
+ * Shairport Sync will only close the pipe on termination.
+
+Version 2.3.9
+----
+* Bug fix
+ * Specifying the configuration file using a *relative* file path now works properly.
+ * The debug verbosity requested with `-v`, `-vv`, etc. is now honoured before the configuration file is read. It is read and honoured from when the command line arguments are scanned the first time to get a possible configuration file path.
+
+Version 2.3.8
+----
+* Annoying changes you must make
+ * You probably need to change your `./configure` arguments. The flag `with-initscript` has changed to `with-systemv`. It was previously enabled by default; now you must enable it explicitly.
+
+* Changes
+ * Added limited support for installing into `systemd` and Fedora systems. For `systemd` support, use the configuration flag `--with-systemd` in place of `--with-systemv`. The installation does not do everything needed, such as defining special users and groups.
+ * Renamed `with-initscript` configuration flag to `with-systemv` to describe its role more accurately.
+ * A System V startup script is no longer installed by default; if you want it, ask for it with the `--with-systemv` configuration flag.
+ * Added limited support for FreeBSD. You must specify `LDFLAGS='-I/usr/local/lib'` and `CPPFLAGS='-L/usr/local/include'` before running `./configure --with-foo etc.`
+ * Removed the `-configfile` annotation from the version string because it's no longer optional; it's always there.
+ * Removed the `dummy`, `pipe` and `stdout` backends from the standard build – they are now optional and are no longer automatically included in the build.
+
+* Bug fixes
+ * Allow more stack space to prevent a segfault in certain configurations (thanks to https://github.com/joerg-krause).
+ * Add missing header files(thanks to https://github.com/joerg-krause).
+ * Removed some (hopefully) mostly silent bugs from the configure.ac file.
+
+Version 2.3.7
+----
+* Changes
+ * Removed the two different buffer lengths for the alsa back end that made a brief appearance in 2.3.5.
+* Enhancements
+ * Command line arguments are now given precedence over config file settings. This conforms to standard unix practice.
+ * A `–without-pkg-config` configuration argument now allows for build systems, e.g. for older OpenWrt builds, that haven't fully implemented it. There is still some unhappiness in arch linux builds.
+* More
+ * Quite a bit of extra diagnostic code was written to investigate clock drift, DAC timings and so on. It was useful but has been commented out. If might be useful in the future.
+
+Version 2.3.5
+----
+* Changes
+ * The metadata item 'sndr' is no longer sent in metadata. It's been replaced by 'snam' and 'snua' -- see below.
+* Enhancements
+ * When a play session is initiated by a source, it attempts to reserve the player by sending an "ANNOUNCE" packet. Typically, a source device name and/or a source "user agent" is sent as part of the packet. The "user agent" is usually the name of the sending application along with some more information. If metadata is enabled, the source name, if provided, is emitted as a metadata item with the type `ssnc` and code `snam` and similarly the user agent, if provided, is sent with the type `ssnc` and code `snua`.
+ * Two default buffer lengths for ALSA -- default 6615 frames if a software volume control is used, to minimise the response time to pause and volume control changes; default 22050 frames if a hardware volume control is used, to give more resilience to timing problems, sudden processor loading, etc. This is especially useful if you are processing metadata and artwork on the same machine.
+ * Extra metadata: when a play session starts, the "Active-Remote" and "DACP-ID" fields -- information that can be used to identify the source -- are provided as metadata, with the type `ssnc` and the codes `acre` and `daid` respectively. The IDs are provided as strings.
+ * Unencrypted audio data. The iOS player "Whaale" attempts to send unencrypted audio, presumably to save processing effort; if unsuccessful, it will send encrypted audio as normal. Shairport Sync now recognises and handles unencrypted audio data. (Apparently it always advertised that it could process unencrypted audio!)
+ * Handle retransmitted audio in the control channel. When a packet of audio is missed, Shairport Sync will ask for it to be retransmitted. Normally the retransmitted audio comes back the audio channel, but "Whaale" sends it back in the control channel. (I think this is a bug in "Whaale".) Shairport Sync will now correctly handle retransmitted audio packets coming back in the control channel.
+* Bugfixes
+ * Generate properly-formed `..` items of information.
+
+Version 2.3.4
+----
+* Enhancement
+ * When a play session starts, Shairport Sync opens three UDP ports to communicate with the source. Until now, those ports could be any high numbered port. Now, they are located within a range of 100 port locations starting at port 6001. The starting port and the port range are settable by two new general settings in `/etc/shairport-sync.conf` -- `udp_port_base` (default 6001) and `udp_port_range` (default 100). To retain the previous behaviour, set the `udp_port_base` to `0`.
+* Bugfixes
+ * Fix an out-of-stack-space error that can occur in certain cases (thanks to https://github.com/joerg-krause).
+ * Fix a couple of compiler warnings (thanks to https://github.com/joerg-krause).
+ * Tidy up a couple of debug messages that were emitting misleading information.
+
+Version 2.3.3.2
+----
+* Bugfix -- fixed an error in the sample configuration file.
+
+Version 2.3.3.1
+----
+* Enhancement
+ * Metadata format has changed slightly -- the format of each item is now `........`, where the `..` part is present if the length is non-zero. The change is that everything is now enclosed in an `..` pair.
+
+Version 2.3.2 and 2.3.3
+----
+These releases were faulty and have been deleted.
+
+Version 2.3.1
+-----
+Some big changes "under the hood" have been made, leading to limited support for unsynchronised output to `stdout` or to a named pipe and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided.
+
+In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`.
+This gives more flexibility in adding features gives better compatability across different versions of Linux.
+Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file.
+
+Note that, for the present, settings in the configuration will have priority over command line options for Shairport Sync itself, in contravention of the normal unix convention. Audio back end command line options, i.e. those after the `--`, have priority over configuration file settings for the audio backends.
+
+In moving to the the use of a configuration file, some "housekeeping" is being done -- some logical corrections and other small changes are being made to option names and modes of operations, so the settings in the configuration file do not exactly match command line options.
+
+When `make install` is executed, a sample configuration is installed or updated at `/etc/shairport-sync.conf.sample`. The same file is also installed as `/etc/shairport-sync.conf` if that file doesn't already exist. To prevent the configuration files being installed, use the configuration option `--without-configfiles`.
+
+* Pesky Change You Must Do Something About
+
+If you are using metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`.
+
+* Enhancements
+ * Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`.
+ * New command-line option `-c ` or `--configfile=` allows you to specify a configuration file other than `/etc/shairport-sync.conf`.
+ * Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning.
+ The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated.
+ * New Option -- "Ignore Volume Control" ('ignore_volume_control'). If you set this to "yes", the output from Shairport Sync is always set at 100%. This is useful when you want to set the volume locally. Available via the settings file only.
+ * Statistics option correctly reports when no frames are received in a sampling interval and when output is not being synchronised.
+ * A new, supported audio back end called `stdout` provides raw 16-bit 44.1kHz stereo PCM output. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`.
+ * Support for the `pipe` back end has been enhanced to provide raw 16-bit 44.1kHz stereo PCM output to a named pipe. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`.
+ * Support for the `dummy` audio backend device continues. To activate, set `output_backend = "dummy"` in in the general section of the configuration. To include support for this back end, use the configuration option `--with-dummy`.
+ * Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that no stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow.
+ * New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. The `audio_backend_buffer_desired_length` default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting, so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. If you're using an alsa back end and are using a hardware mixers, this isn't a problem. The `audio_backend_latency_offset` allows you emit frames to the audio back end some time before or after the synchronised time. This would be useful, for example, if you are outputting to a device that takes 20 ms to process audio; yoou would specify a `audio_backend_latency_offset = -882`, where 882 is the number of frames in 20 ms, to compensate for the device delay.
+
+Version 2.3
+-----
+* Enhancements
+ * Adding the System V startup script (the "initscript") is now a configuration option. The default is to include it, so if you want to omit the installation of the initscript, add the configuration option `--without-initscript`.
+ * Metadata support is now a compile-time option: `--with-metadata`.
+ * A metadata feed has been added. Use the option `-M `, e.g. `-M /tmp`. Shairport Sync will provide metadata in a pipe called `/shairport-sync-metadata`. (This is changed in 2.3.1.) There's a sample metadata reader at https://github.com/mikebrady/shairport-sync-metadata-reader. The format of the metadata is a mixture of XML-style tags, 4-character codes and base64 data. Please look at `rtsp.c` and `player.c` for examples. Please note that the format of the metadata may change.
+Beware: there appears to be a serious bug in iTunes before 12.1.2, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images.
+
+* Bugfix
+ * Fix a bug when compiling for Arch Linux on Raspberry Pi 2 (thanks to https://github.com/joaodriessen).
+ * Fix a bug whereby if the ANNOUNCE and/or SETUP method fails, the play_lock mutex is never unlocked, thus blocking other clients from connecting. This can affect all types of users, but particularly Pulseaudio users. (Thanks to https://github.com/jclehner.)
+ * Modify the init script to start after all services are ready. Add in a commented-out sleep command if users find it necessary (thanks to https://github.com/BNoiZe).
+ * Two memory leaks fixed (thanks to https://github.com/pdgendt).
+ * An error handling time specifications for flushes was causing an audible glitch when pausing and resuming some tracks. This has been fixed (thanks to https://github.com/Hamster128).
+
+Version 2.2.5
+-----
+* Bugfixes
+ * Fix a segfault error that can occur in certain cases (thanks again to https://github.com/joerg-krause).
+ * Include header files in common.c (thanks again to https://github.com/joerg-krause).
+
+Version 2.2.4
+-----
+* Bugfixes
+ * Fix an out-of-stack-space error that can occur in certain cases (thanks to https://github.com/joerg-krause).
+ * Fix a couple of compiler warnings (thanks to https://github.com/joerg-krause).
+
+Version 2.2.3
+-----
+* NOTE: all the metadata stuff has been moved to the "development" branch. This will become the stable branch henceforward, with just bug fixes or minor enhancements. Apologies for the inconvenience.
+* Bugfixes
+ * Fix a bug when compiling for Arch Linux on Raspberry Pi 2 (thanks to https://github.com/joaodriessen).
+ * Fix a compiler warning (thanks to https://github.com/sdigit).
+
+Version 2.2.2
+-----
+* Enhancement
+ * An extra latency setting for forked-daapd sources -- 99,400 frames, settable via a new option `--forkedDaapdLatency`.
+
+Version 2.2.1
+-----
+* Bugfixes:
+ * If certain kinds of malformed RTSP packets were received, Shairport Sync would stop streaming. Now, it generally ignores faulty RTSP packets.
+ * The `with-pulseaudio` compile option wasn't including a required library. This is fixed. Note that the PulseAudio back end doesn't work properly and is just included in the application because it was there in the original shairport. Play with it for experimentation only.
+ * Fix typo in init.d script: "Headphones" -> "Headphone".
+* Extra documentation
+ * A brief note on how to compile `libsoxr` from source is included for the Raspberry Pi.
+
+Version 2.2
+-----
+* Enhancements:
+ * New password option: `--password=SECRET`
+ * New tolerance option: `--tolerance=FRAMES`. Use this option to specify the largest synchronisation error to allow before making corrections. The default is 88 frames, i.e. 2 milliseconds. The default tolerance is fine for streaming over wired ethernet; however, if some of the stream's path is via WiFi, or if the source is a third-party product, it may lead to much overcorrection -- i.e. the difference between "corrections" and "net correction" in the `--statistics` option. Increasing the tolerence may reduce the amount of overcorrection.
+
+Version 2.1.15
+-----
+* Changes to latency calculations:
+ * The default latency is now 88,200 frames, exactly 2 seconds. It was 99,400 frames. As before, the `-L` option allows you to set the default latency.
+ * The `-L` option is no longer deprecated.
+ * The `-L` option no longer overrides the `-A` or `-i` options.
+ * The default latency for iTunes is now 99,400 frames for iTunes 10 or later and 88,200 for earlier versions.
+ * The `-i` or `--iTunesLatency` option only applies to iTunes 10 or later sources.
+
+Version 2.1.14
+-----
+* Documentation update: add information about the `-m` audio backend option.
+The `-m` audio backend option allows you to specify the hardware mixer you are using. Not previously documented.
+Functionality of shairport-sync is unchanged.
+
+Version 2.1.13
+-----
+* Compilation change: Begin to use PKG_CHECK_MODULES (in configure.ac) to statically link some of the libraries used by shairport-sync. It is intended to make it easier to build in the buildroot system. While sufficient for that purpose, note that PKG_CHECK_MODULES is not used for checking all the libraries yet.
+Functionality of shairport-sync is unchanged.
+
+Version 2.1.12
+-----
+* Enhancement: `--statistics`
+ Statistics are periodically written to the console (or the logfile) if this command-line option is included. They are no longer produced in verbose (`-v`) mode.
+* Bugfixes for `tinysvcmdns`
+ * A bug that prevented the device's IP number(s) and port numbers being advertised when using `tinysvcmdns` has been fixed. (Cause: name needed to have a `.local` suffix.)
+ * Bugs causing the shairport service to semi-randomly disappear and reappear seem to be fixed. (Possible cause: incorrect timing settings when using `tinysvcmdns`.)
+
+Version 2.1.11
+-----
+* Enhancement
+ * A man page is now installed -- do `man shairport-sync` or see it here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/2.1/man/shairport-sync.html.
+
+Version 2.1.10
+-----
+* Bugfix
+ * A bug that caused the `-t` timeout value to be incorrectly assigned has been fixed. (Cause: `config.timeout` defined as `int64_t` instead on `int`.)
+
+Version 2.1.9
+-----
+* Bugfixes
+ * A bug that sometimes caused the initial volume setting to be ignored has been fixed. (Cause: setting volume before opening device.)
+ * a bug that caused shairport-sync to become unresponsive or unavailable has been fixed. (Cause: draining rather than flushing the alsa device before stopping.)
+
+Version 2.1.8:
+-----
+* Enhancements
+ * (This feature is intended to be useful to integrators.) Shairport Sync now the ability to immediately disconnect and reconnect to the sound output device while continuing to stream audio data from its client.
+Send a `SIGUSR2` to the shairport-sync process to disconnect or send it a `SIGHUP` to reconnect. If shairport-sync has been started as a daemon using `shairport-sync -d`, then executing `shairport-sync -D` or `--disconnectFromOutput` will request the daemon to disconnect, and executing `shairport-sync -R` or `--reconnectToOutput` will request it to reconnect.
+With this feature, you can allow Shairport Sync always to advertise and provide the streaming service, but still be able to disconnect it locally to enable other audio services to access the output device.
+
+* Annoying things you should know about if you're updating from a previous version:
+ * Options `--with-openssl`, `--with-polarssl` have been replaced with a new option `--with-ssl=
+
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 .
+
+
+
These are the settings available within the general group:
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:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.
+
+
+
+
+
+
+
+
+
These settings are for the new PulseAudio backend.
+
+
+
+
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.
+
+
+
+
There are no settings for the STDOUT backend.
+
+
+
There are no configuration file settings for the AO backend.
+
+
+
+
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:
+
+
+
+
+
+
+
+
+
+
+
+
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 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.
+
+
+
These command-line options are used by shairport-sync itself.
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.
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 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) or to a PulseAudio output stream (available on Linux).
+
+
A feature of shairport-sync is that the audio is played synchronously.
+ This means that if many devices are playing the same stream at the same
+ time, all the outputs will stay in step with one another.
+ This allows multiple devices to play the same source without getting out of phase with one another,
+ enabling, for example, simultaneous multi-room operation.
+
+
+
shairport-sync can be compiled to stream audio, without synchronisation, to a pipe, to stdout or to a libao output device (an "AO" device). It can also be compiled to stream metadata to a pipe or socket.
+
+
Settings can be made using the configuration file (recommended for all new installations) or by using command-line options.
+
+
+
+
+
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.
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.
+
+
+
+
+
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.
+
+
+
+
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.
+
+
+
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.
+
+
+
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.
+
+
+
+
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.
+
+
+
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_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 speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB.
+
+
+
+
+
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.
+
+
+
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".
+
+
+
+
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.
+
+
+
+
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.
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.
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.
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.
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 #!/bin/... as appropriate.
+
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.0 to -30.0 and -144.0 means "mute".
+
+
+
+
"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="yes";
+
+
This is an advanced setting and the default is "yes", which means that hardware mute will be implemented using a feature called a 'playback switch', where one is available;
+ set it to "no" to prevent the playback switch being used.
+
The motivation for this is to avoid using the alsa function call
+ "snd_mixer_selem_set_playback_switch_all", which is incorrectly implemented on certain soundcards, including the emulated card in VMWare Fusion 8.5.
+
+
+
"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.
+
+
+
"SESSIONCONTROL" SETTINGS
+
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.
+
+
+
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 #!/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 #!/bin/... as appropriate.
+
+
+
wait_for_completion="choice";
+
Set choice to "yes" to make shairport-sync wait until the programs specified in the run_this_before_play_begins,
+ run_this_after_play_ends and run_this_when_volume_is_set 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 seconds seconds before ending the session. Once the session has terminated, other devices can use it.
+ The default is 120 seconds.
+
+
+
+
+
+
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 #!/bin/sh (or
+ whatever is appropriate) 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 | --disconnectFromOutput
+
+ Disconnect the shairport-sync daemon from the output device and
+ exit. (Requires that the daemon has written its PID to an agreed
+ file -- see the -d option).
+
+
Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
+
+
+
+
+
-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.
+
+
+
+
+
-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 #!/bin/sh (or
+ whatever is appropriate) 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.
+
+
+
+
+
-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).
+
+
+
+
+
--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 | --reconnectToOutput
+
+ Reconnect the shairport-sync daemon to the output device and
+ exit. It may take a few seconds to synchronise. (Requires that
+ the daemon has written its PID to an agreed file -- see the -d
+ option).
+
+
Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
+
+
+
+
+
-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.
+
+
+
+
+
-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.
+
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:
This man page was written using xml2man by Oliver Kurth.
+
+
+
+
+
diff --git a/man/xmltoman.css b/man/xmltoman.css
new file mode 100644
index 0000000..a524934
--- /dev/null
+++ b/man/xmltoman.css
@@ -0,0 +1,36 @@
+/***
+ This is part of shairport-sync.
+ Copyright (c) Mike Brady 2014
+ 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.
+***/
+
+body { color: black; background-color: white; }
+a:link, a:visited { color: #900000; }
+h1 { text-transform:uppercase; font-size: 18pt; }
+p { margin-left:1cm; margin-right:1cm; }
+.cmd { font-family:monospace; }
+.file { font-family:monospace; }
+.arg { text-transform:uppercase; font-family:monospace; font-style: italic; }
+.opt { font-family:monospace; font-weight: bold; }
+.manref { font-family:monospace; }
+.option .optdesc { margin-left:2cm; }
diff --git a/man/xmltoman.dtd b/man/xmltoman.dtd
new file mode 100644
index 0000000..fece3c6
--- /dev/null
+++ b/man/xmltoman.dtd
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/man/xmltoman.xsl b/man/xmltoman.xsl
new file mode 100644
index 0000000..68a11fc
--- /dev/null
+++ b/man/xmltoman.xsl
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ ()
+
+
+
+
Name
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Synopsis
+
+
+
+
+
Synopsis
+
+
+
+
+
Description
+
+
+
+
+
Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ()
+
+
+ ()
+
+
+
+
+
+
+
+
+
diff --git a/mdns.c b/mdns.c
new file mode 100644
index 0000000..0651cdc
--- /dev/null
+++ b/mdns.c
@@ -0,0 +1,129 @@
+/*
+ * mDNS registration handler. This file is part of Shairport.
+ * Copyright (c) James Laird 2013
+ * 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 "mdns.h"
+#include "common.h"
+#include "config.h"
+#include
+#include
+#include
+#include
+
+#ifdef CONFIG_AVAHI
+extern mdns_backend mdns_avahi;
+extern mdns_backend mdns_external_avahi;
+#endif
+#ifdef CONFIG_HAVE_DNS_SD_H
+extern mdns_backend mdns_dns_sd;
+extern mdns_backend mdns_external_dns_sd;
+#endif
+#ifdef CONFIG_TINYSVCMDNS
+extern mdns_backend mdns_tinysvcmdns;
+#endif
+
+static mdns_backend *mdns_backends[] = {
+#ifdef CONFIG_AVAHI
+ &mdns_avahi,
+ &mdns_external_avahi,
+#endif
+#ifdef CONFIG_HAVE_DNS_SD_H
+ &mdns_dns_sd,
+ &mdns_external_dns_sd,
+#endif
+#ifdef CONFIG_TINYSVCMDNS
+ &mdns_tinysvcmdns,
+#endif
+ NULL};
+
+void mdns_register(void) {
+ char *mdns_service_name = alloca(strlen(config.service_name) + 14);
+ char *p = mdns_service_name;
+ int i;
+ for (i = 0; i < 6; i++) {
+ sprintf(p, "%02X", config.hw_addr[i]);
+ p += 2;
+ }
+ *p++ = '@';
+ strcpy(p, config.service_name);
+
+ mdns_backend **b = NULL;
+
+ if (config.mdns_name != NULL) {
+ for (b = mdns_backends; *b; b++) {
+ if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for
+ continue;
+ int error = (*b)->mdns_register(mdns_service_name, config.port);
+ if (error >= 0) {
+ config.mdns = *b;
+ }
+ break;
+ }
+
+ if (*b == NULL)
+ warn("%s mDNS backend not found");
+ } else {
+ for (b = mdns_backends; *b; b++) {
+ int error = (*b)->mdns_register(mdns_service_name, config.port);
+ if (error >= 0) {
+ config.mdns = *b;
+ break;
+ }
+ }
+ }
+
+ if (config.mdns == NULL)
+ die("Could not establish mDNS advertisement!");
+}
+
+void mdns_unregister(void) {
+ if (config.mdns) {
+ config.mdns->mdns_unregister();
+ }
+}
+
+void mdns_dacp_monitor(rtsp_conn_info *conn) {
+ if ((config.mdns) && (config.mdns->mdns_dacp_monitor)) {
+ int error = config.mdns->mdns_dacp_monitor(conn);
+ if (error) {
+ debug(1, "Error starting a DACP monitor.");
+ }
+ } else
+ debug(1, "Can't start a DACP monitor.");
+}
+
+void mdns_dacp_dont_monitor(rtsp_conn_info *conn) {
+ if ((config.mdns) && (config.mdns->mdns_dacp_dont_monitor)) {
+ config.mdns->mdns_dacp_dont_monitor(conn);
+ } else
+ debug(1, "Can't stop a DACP monitor.");
+}
+void mdns_ls_backends(void) {
+ mdns_backend **b = NULL;
+ printf("Available mDNS backends: \n");
+ for (b = mdns_backends; *b; b++) {
+ printf(" %s\n", (*b)->name);
+ }
+}
diff --git a/mdns.h b/mdns.h
new file mode 100644
index 0000000..a55eea9
--- /dev/null
+++ b/mdns.h
@@ -0,0 +1,49 @@
+#ifndef _MDNS_H
+#define _MDNS_H
+
+#include "config.h"
+#include
+#include
+
+extern int mdns_pid;
+
+void mdns_unregister(void);
+void mdns_register(void);
+void mdns_dacp_monitor(rtsp_conn_info *conn);
+void mdns_dacp_dont_monitor(rtsp_conn_info *conn);
+
+void mdns_ls_backends(void);
+
+typedef struct {
+ char *name;
+ int (*mdns_register)(char *apname, int port);
+ void (*mdns_unregister)(void);
+ int (*mdns_dacp_monitor)(rtsp_conn_info *conn);
+ void (*mdns_dacp_dont_monitor)(rtsp_conn_info *conn);
+} mdns_backend;
+
+#ifdef CONFIG_METADATA
+
+#define METADATA_EXPRESSION config.get_coverart ? "md=0,1,2" : "md=0,2"
+
+// #define MDNS_RECORD_WITH_METADATA \
+// "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", METADATA_EXPRESSION, "ss=16", \
+// "sr=44100", "vn=3", "txtvers=1", config.password ? "pw=true" : "pw=false"
+
+#define MDNS_RECORD_WITH_METADATA \
+ "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", \
+ METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", \
+ "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false"
+
+#endif
+
+// #define MDNS_RECORD_WITHOUT_METADATA \
+// "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", METADATA_EXPRESSION, "ss=16", "sr=44100", "vn=3", \
+// "txtvers=1", config.password ? "pw=true" : "pw=false"
+
+#define MDNS_RECORD_WITHOUT_METADATA \
+ "sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \
+ "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", \
+ config.password ? "pw=true" : "pw=false"
+
+#endif // _MDNS_H
diff --git a/mdns_avahi.c b/mdns_avahi.c
new file mode 100644
index 0000000..bb3ef0a
--- /dev/null
+++ b/mdns_avahi.c
@@ -0,0 +1,471 @@
+/*
+ * Embedded Avahi client. This file is part of Shairport.
+ * Copyright (c) James Laird 2013
+ * Additions for metadata and for detecting IPv6 Copyright (c) Mike Brady 2015
+ * 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
+#include
+
+#include "config.h"
+
+#include "common.h"
+#include "mdns.h"
+#include "rtsp.h"
+#ifdef CONFIG_DACP
+#include "dacp.h"
+#endif
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+typedef struct {
+ AvahiThreadedPoll *service_poll;
+ AvahiClient *service_client;
+ AvahiServiceBrowser *service_browser;
+} dacp_browser_struct;
+
+// static AvahiServiceBrowser *sb = NULL;
+static AvahiClient *client = NULL;
+// static AvahiClient *service_client = NULL;
+static AvahiEntryGroup *group = NULL;
+static AvahiThreadedPoll *tpoll = NULL;
+// static AvahiThreadedPoll *service_poll = NULL;
+
+static char *service_name = NULL;
+static int port = 0;
+
+static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface,
+ AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event,
+ const char *name, const char *type, const char *domain,
+ const char *host_name, const AvahiAddress *address, uint16_t port,
+ AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) {
+ assert(r);
+
+ rtsp_conn_info *conn = (rtsp_conn_info *)userdata;
+ dacp_browser_struct *dbs = (dacp_browser_struct *)conn->mdns_private_pointer;
+
+ /* Called whenever a service has been resolved successfully or timed out */
+ switch (event) {
+ case AVAHI_RESOLVER_FAILURE:
+ debug(3, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name,
+ type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+ break;
+ case AVAHI_RESOLVER_FOUND: {
+ char a[AVAHI_ADDRESS_STR_MAX], *t;
+ // debug(1, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain);
+ char *dacpid = strstr(name, "iTunes_Ctrl_");
+ if (dacpid) {
+ dacpid += strlen("iTunes_Ctrl_");
+ if (strcmp(dacpid, conn->dacp_id) == 0) {
+ if (conn->dacp_port != port) {
+ debug(3, "Client's DACP port: %u.", port);
+ conn->dacp_port = port;
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+ set_dacp_server_information(conn);
+#endif
+#ifdef CONFIG_METADATA
+ char portstring[20];
+ memset(portstring, 0, sizeof(portstring));
+ sprintf(portstring, "%u", port);
+ send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0);
+#endif
+ }
+ }
+ } else {
+ debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!");
+ }
+ }
+ }
+ avahi_service_resolver_free(r);
+}
+static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiBrowserEvent event, const char *name, const char *type,
+ const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
+ void *userdata) {
+ rtsp_conn_info *conn = (rtsp_conn_info *)userdata;
+ dacp_browser_struct *dbs = (dacp_browser_struct *)conn->mdns_private_pointer;
+ assert(b);
+ /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
+ switch (event) {
+ case AVAHI_BROWSER_FAILURE:
+ warn("avahi: browser failure.",
+ avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
+ avahi_threaded_poll_quit(tpoll);
+ break;
+ case AVAHI_BROWSER_NEW:
+ debug(3, "(Browser) NEW: service '%s' of type '%s' in domain '%s'.", name, type, domain);
+ /* We ignore the returned resolver object. In the callback
+ function we free it. If the server is terminated before
+ the callback function is called the server will free
+ the resolver for us. */
+ if (!(avahi_service_resolver_new(dbs->service_client, interface, protocol, name, type, domain,
+ AVAHI_PROTO_UNSPEC, 0, resolve_callback, userdata)))
+ debug(1, "Failed to resolve service '%s': %s.", name,
+ avahi_strerror(avahi_client_errno(dbs->service_client)));
+ break;
+ case AVAHI_BROWSER_REMOVE:
+ debug(3, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'.", name, type, domain);
+ char *dacpid = strstr(name, "iTunes_Ctrl_");
+ if (dacpid) {
+ dacpid += strlen("iTunes_Ctrl_");
+ if (strcmp(dacpid, conn->dacp_id) == 0) {
+ if (conn->dacp_id != 0) {
+ debug(1, "Client's DACP status withdrawn.");
+ conn->dacp_port = 0;
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+ set_dacp_server_information(conn); // this will have the effect of telling the scanner that the DACP server is no longer working
+#endif
+ }
+ }
+ } else {
+ debug(1, "Browse callback: Can't see a DACP string in a DACP Record!");
+ }
+
+ break;
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ // debug(1, "(Browser) %s.", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" :
+ // "ALL_FOR_NOW");
+ break;
+ }
+}
+
+static void register_service(AvahiClient *c);
+
+static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
+ AVAHI_GCC_UNUSED void *userdata) {
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ /* The entry group has been established successfully */
+ debug(1, "avahi: service '%s' successfully added.", service_name);
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION: {
+ char *n;
+
+ /* A service name collision with a remote service
+ * happened. Let's pick a new name */
+ n = avahi_alternative_service_name(service_name);
+ avahi_free(service_name);
+ service_name = n;
+
+ debug(2, "avahi: service name collision, renaming service to '%s'", service_name);
+
+ /* And recreate the services */
+ register_service(avahi_entry_group_get_client(g));
+ break;
+ }
+
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ debug(1, "avahi: entry group failure: %s",
+ avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ debug(2, "avahi: service '%s' group is not yet committed.", service_name);
+ break;
+
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ debug(2, "avahi: service '%s' group is registering.", service_name);
+ break;
+
+ default:
+ debug(1, "avahi: unhandled egroup state: %d", state);
+ break;
+ }
+}
+
+static void register_service(AvahiClient *c) {
+ debug(1, "avahi: register_service.");
+ if (!group)
+ group = avahi_entry_group_new(c, egroup_callback, NULL);
+ if (!group)
+ debug(2, "avahi: avahi_entry_group_new failed");
+ else {
+
+ if (!avahi_entry_group_is_empty(group))
+ return;
+
+ int ret;
+ AvahiIfIndex selected_interface;
+ if (config.interface != NULL)
+ selected_interface = config.interface_index;
+ else
+ selected_interface = AVAHI_IF_UNSPEC;
+#ifdef CONFIG_METADATA
+ if (config.metadata_enabled) {
+ ret = avahi_entry_group_add_service(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
+ service_name, config.regtype, NULL, NULL, port,
+ MDNS_RECORD_WITH_METADATA, NULL);
+ if (ret == 0)
+ debug(1, "avahi: request to add \"%s\" service with metadata", config.regtype);
+ } else {
+#endif
+ ret = avahi_entry_group_add_service(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
+ service_name, config.regtype, NULL, NULL, port,
+ MDNS_RECORD_WITHOUT_METADATA, NULL);
+ if (ret == 0)
+ debug(1, "avahi: request to add \"%s\" service without metadata", config.regtype);
+#ifdef CONFIG_METADATA
+ }
+#endif
+
+ if (ret < 0)
+ debug(1, "avahi: avahi_entry_group_add_service failed");
+ else {
+ ret = avahi_entry_group_commit(group);
+ if (ret < 0)
+ debug(1, "avahi: avahi_entry_group_commit failed");
+ }
+ }
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state,
+ AVAHI_GCC_UNUSED void *userdata) {
+ int err;
+
+ switch (state) {
+ case AVAHI_CLIENT_S_REGISTERING:
+ if (group)
+ avahi_entry_group_reset(group);
+ break;
+
+ case AVAHI_CLIENT_S_RUNNING:
+ register_service(c);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ err = avahi_client_errno(c);
+ debug(1, "avahi: client failure: %s", avahi_strerror(err));
+
+ if (err == AVAHI_ERR_DISCONNECTED) {
+ /* We have been disconnected, so lets reconnect */
+ avahi_client_free(c);
+ c = NULL;
+ group = NULL;
+
+ if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), AVAHI_CLIENT_NO_FAIL,
+ client_callback, userdata, &err))) {
+ warn("avahi: failed to create client object: %s", avahi_strerror(err));
+ avahi_threaded_poll_quit(tpoll);
+ }
+ } else {
+ warn("avahi: client failure: %s", avahi_strerror(err));
+ avahi_threaded_poll_quit(tpoll);
+ }
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ debug(2, "avahi: state is AVAHI_CLIENT_S_COLLISION...needs a rename: %s", service_name);
+ break;
+
+ case AVAHI_CLIENT_CONNECTING:
+ debug(2, "avahi: received AVAHI_CLIENT_CONNECTING");
+ break;
+
+ default:
+ debug(1, "avahi: unexpected and unhandled avahi client state: %d", state);
+ break;
+ }
+}
+
+static void service_client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
+ int err;
+
+ rtsp_conn_info *conn = (rtsp_conn_info *)userdata;
+ dacp_browser_struct *dbs = (dacp_browser_struct *)conn->mdns_private_pointer;
+
+ switch (state) {
+ case AVAHI_CLIENT_S_REGISTERING:
+ break;
+
+ case AVAHI_CLIENT_S_RUNNING:
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ err = avahi_client_errno(c);
+ debug(1, "avahi: service client failure: %s", avahi_strerror(err));
+
+ if (err == AVAHI_ERR_DISCONNECTED) {
+ /* We have been disconnected, so lets reconnect */
+ avahi_client_free(c);
+ c = NULL;
+
+ if (!(dbs->service_client =
+ avahi_client_new(avahi_threaded_poll_get(dbs->service_poll), AVAHI_CLIENT_NO_FAIL,
+ service_client_callback, userdata, &err))) {
+ warn("avahi: failed to create service client object: %s", avahi_strerror(err));
+ avahi_threaded_poll_quit(dbs->service_poll);
+ }
+ } else {
+ warn("avahi: service client failure: %s", avahi_strerror(err));
+ avahi_threaded_poll_quit(dbs->service_poll);
+ }
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ debug(2, "avahi: service client state is AVAHI_CLIENT_S_COLLISION...needs a rename: %s",
+ service_name);
+ break;
+
+ case AVAHI_CLIENT_CONNECTING:
+ debug(2, "avahi: service client received AVAHI_CLIENT_CONNECTING");
+ break;
+
+ default:
+ debug(1, "avahi: unexpected and unhandled avahi service client state: %d", state);
+ break;
+ }
+}
+
+static int avahi_register(char *srvname, int srvport) {
+ debug(1, "avahi: avahi_register.");
+ service_name = strdup(srvname);
+ port = srvport;
+
+ int err;
+ if (!(tpoll = avahi_threaded_poll_new())) {
+ warn("couldn't create avahi threaded tpoll!");
+ return -1;
+ }
+ if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), AVAHI_CLIENT_NO_FAIL,
+ client_callback, NULL, &err))) {
+ warn("couldn't create avahi client: %s!", avahi_strerror(err));
+ return -1;
+ }
+
+ if (avahi_threaded_poll_start(tpoll) < 0) {
+ warn("couldn't start avahi tpoll thread");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void avahi_unregister(void) {
+ debug(1, "avahi: avahi_unregister.");
+ if (tpoll)
+ avahi_threaded_poll_stop(tpoll);
+ tpoll = NULL;
+
+ if (service_name)
+ free(service_name);
+ service_name = NULL;
+}
+
+int avahi_dacp_monitor(rtsp_conn_info *conn) {
+
+ dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct));
+
+
+ if (dbs == NULL)
+ die("can not allocate a dacp_browser_struct.");
+
+ conn->mdns_private_pointer = (void *)dbs;
+
+ // create the threaded poll code
+ int err;
+ if (!(dbs->service_poll = avahi_threaded_poll_new())) {
+ warn("couldn't create avahi threaded service_poll!");
+ if (dbs) {
+ free((char *)dbs);
+ }
+ conn->mdns_private_pointer = NULL;
+ return -1;
+ }
+
+ // create the service client
+ if (!(dbs->service_client =
+ avahi_client_new(avahi_threaded_poll_get(dbs->service_poll), AVAHI_CLIENT_NO_FAIL,
+ service_client_callback, (void *)conn, &err))) {
+ warn("couldn't create avahi service client: %s!", avahi_strerror(err));
+ if (dbs) { // should free the threaded poll code
+ avahi_threaded_poll_free(dbs->service_poll);
+ free((char *)dbs);
+ }
+ conn->mdns_private_pointer = NULL;
+ return -1;
+ }
+
+ /* Create the service browser */
+ if (!(dbs->service_browser =
+ avahi_service_browser_new(dbs->service_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ "_dacp._tcp", NULL, 0, browse_callback, (void *)conn))) {
+ warn("Failed to create service browser: %s\n",
+ avahi_strerror(avahi_client_errno(dbs->service_client)));
+ if (dbs) { // should free the threaded poll code and the service client
+ avahi_client_free(dbs->service_client);
+ avahi_threaded_poll_free(dbs->service_poll);
+ free((char *)dbs);
+ }
+ conn->mdns_private_pointer = NULL;
+ return -1;
+ }
+ // start the polling thread
+ if (avahi_threaded_poll_start(dbs->service_poll) < 0) {
+ warn("couldn't start avahi service_poll thread");
+ if (dbs) { // should free the threaded poll code and the service client and the service browser
+ avahi_service_browser_free(dbs->service_browser);
+ avahi_client_free(dbs->service_client);
+ avahi_threaded_poll_free(dbs->service_poll);
+ free((char *)dbs);
+ }
+ conn->mdns_private_pointer = NULL;
+ return -1;
+ }
+ return 0;
+}
+
+void avahi_dacp_dont_monitor(rtsp_conn_info *conn) {
+ dacp_browser_struct *dbs = (dacp_browser_struct *)conn->mdns_private_pointer;
+ if (dbs) {
+ // stop and dispose of everything
+ if ((dbs)->service_poll)
+ avahi_threaded_poll_stop((dbs)->service_poll);
+ if ((dbs)->service_browser)
+ avahi_service_browser_free((dbs)->service_browser);
+ if ((dbs)->service_client)
+ avahi_client_free((dbs)->service_client);
+ if ((dbs)->service_poll)
+ avahi_threaded_poll_free((dbs)->service_poll);
+ free((char *)(dbs));
+ conn->mdns_private_pointer = NULL;
+ } else {
+ debug(1, "DHCP Monitor is not running.");
+ }
+}
+
+mdns_backend mdns_avahi = {.name = "avahi",
+ .mdns_register = avahi_register,
+ .mdns_unregister = avahi_unregister,
+ .mdns_dacp_monitor = avahi_dacp_monitor,
+ .mdns_dacp_dont_monitor = avahi_dacp_dont_monitor};
diff --git a/mdns_dns_sd.c b/mdns_dns_sd.c
new file mode 100644
index 0000000..722aadc
--- /dev/null
+++ b/mdns_dns_sd.c
@@ -0,0 +1,97 @@
+/*
+ * Embedded dns-sd client. This file is part of Shairport.
+ * Copyright (c) Paul Lietar 2013
+ * 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 "mdns.h"
+#include "common.h"
+#include
+#include
+#include
+#include
+
+static DNSServiceRef service;
+
+static int mdns_dns_sd_register(char *apname, int port) {
+ char *recordwithoutmetadata[] = {MDNS_RECORD_WITHOUT_METADATA, NULL};
+#ifdef CONFIG_METADATA
+ char *recordwithmetadata[] = {MDNS_RECORD_WITH_METADATA, NULL};
+#endif
+ char **record;
+#ifdef CONFIG_METADATA
+ if (config.metadata_enabled)
+ record = recordwithmetadata;
+ else
+#endif
+ record = recordwithoutmetadata;
+
+ uint16_t length = 0;
+ char **field;
+
+ // Concatenate string contained i record into buf.
+
+ for (field = record; *field; field++) {
+ length += strlen(*field) + 1; // One byte for length each time
+ }
+
+ char *buf = malloc(length * sizeof(char));
+ if (buf == NULL) {
+ warn("dns_sd: buffer record allocation failed");
+ return -1;
+ }
+
+ char *p = buf;
+
+ for (field = record; *field; field++) {
+ char *newp = stpcpy(p + 1, *field);
+ *p = newp - p - 1;
+ p = newp;
+ }
+
+ DNSServiceErrorType error;
+ error = DNSServiceRegister(&service, 0, kDNSServiceInterfaceIndexAny, apname, config.regtype, "",
+ NULL, htons((uint16_t)port), length, buf, NULL, NULL);
+
+ free(buf);
+
+ if (error == kDNSServiceErr_NoError)
+ return 0;
+ else {
+ warn("dns-sd: DNSServiceRegister error %d", error);
+ return -1;
+ }
+}
+
+static void mdns_dns_sd_unregister(void) {
+ if (service) {
+ DNSServiceRefDeallocate(service);
+ service = NULL;
+ }
+}
+
+mdns_backend mdns_dns_sd = {.name = "dns-sd",
+ .mdns_register = mdns_dns_sd_register,
+ .mdns_unregister = mdns_dns_sd_unregister,
+ .mdns_dacp_monitor = NULL,
+ .mdns_dacp_dont_monitor = NULL};
diff --git a/mdns_external.c b/mdns_external.c
new file mode 100644
index 0000000..5d24712
--- /dev/null
+++ b/mdns_external.c
@@ -0,0 +1,171 @@
+/*
+ * mDNS registration handler. This file is part of Shairport.
+ * Copyright (c) Paul Lietar 2013
+ * 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 "mdns.h"
+#include "common.h"
+#include
+#include
+#include
+#include
+#include
+
+int mdns_pid = 0;
+
+/*
+ * Do a fork followed by a execvp, handling execvp errors correctly.
+ * Return the pid of the new process upon success, or -1 if something failed.
+ * Check errno for error details.
+ */
+static int fork_execvp(const char *file, char *const argv[]) {
+ int execpipe[2];
+ int pid = 0;
+ if (pipe(execpipe) < 0) {
+ return -1;
+ }
+
+ if (fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC) < 0) {
+ close(execpipe[0]);
+ close(execpipe[1]);
+ return -1;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ close(execpipe[0]);
+ close(execpipe[1]);
+ return -1;
+ } else if (pid == 0) { // Child
+ close(execpipe[0]); // Close the read end
+ execvp(file, argv);
+
+ // If we reach this point then execve has failed.
+ // Write erno's value into the pipe and exit.
+ int ignore = write(execpipe[1], &errno, sizeof(errno));
+ debug(1, "execve has failed.");
+ _exit(-1);
+ return 0; // Just to make the compiler happy.
+ } else { // Parent
+ close(execpipe[1]); // Close the write end
+
+ int childErrno;
+ // Block until child closes the pipe or sends errno.
+ if (read(execpipe[0], &childErrno, sizeof(childErrno)) ==
+ sizeof(childErrno)) { // We received errno
+ errno = childErrno;
+ return -1;
+ } else { // Child closed the pipe. execvp was successful.
+ return pid;
+ }
+ }
+}
+
+static int mdns_external_avahi_register(char *apname, int port) {
+ char mdns_port[6];
+ sprintf(mdns_port, "%d", config.port);
+
+ char *argvwithoutmetadata[] = {
+ NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL};
+#ifdef CONFIG_METADATA
+ char *argvwithmetadata[] = {NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITH_METADATA,
+ NULL};
+#endif
+ char **argv;
+
+#ifdef CONFIG_METADATA
+ if (config.metadata_enabled)
+ argv = argvwithmetadata;
+ else
+#endif
+ argv = argvwithoutmetadata;
+
+ argv[0] = "avahi-publish-service";
+ int pid = fork_execvp(argv[0], argv);
+ if (pid >= 0) {
+ mdns_pid = pid;
+ return 0;
+ } else
+ warn("Calling %s failed !", argv[0]);
+
+ argv[0] = "mDNSPublish";
+ pid = fork_execvp(argv[0], argv);
+ if (pid >= 0) {
+ mdns_pid = pid;
+ return 0;
+ } else
+ warn("Calling %s failed !", argv[0]);
+
+ // If we reach here, both execvp calls failed.
+ return -1;
+}
+
+static int mdns_external_dns_sd_register(char *apname, int port) {
+ char mdns_port[6];
+ sprintf(mdns_port, "%d", config.port);
+
+ char *argvwithoutmetadata[] = {
+ NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL};
+
+#ifdef CONFIG_METADATA
+ char *argvwithmetadata[] = {NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITH_METADATA,
+ NULL};
+#endif
+
+ char **argv;
+#ifdef CONFIG_METADATA
+ if (config.metadata_enabled)
+ argv = argvwithmetadata;
+ else
+#endif
+
+ argv = argvwithoutmetadata;
+
+ int pid = fork_execvp(argv[0], argv);
+ if (pid >= 0) {
+ mdns_pid = pid;
+ return 0;
+ } else
+ warn("Calling %s failed !", argv[0]);
+
+ return -1;
+}
+
+static void kill_mdns_child(void) {
+ if (mdns_pid)
+ kill(mdns_pid, SIGTERM);
+ mdns_pid = 0;
+}
+
+mdns_backend mdns_external_avahi = {.name = "external-avahi",
+ .mdns_register = mdns_external_avahi_register,
+ .mdns_unregister = kill_mdns_child,
+ .mdns_dacp_monitor = NULL,
+ .mdns_dacp_dont_monitor = NULL};
+
+mdns_backend mdns_external_dns_sd = {.name = "external-dns-sd",
+ .mdns_register = mdns_external_dns_sd_register,
+ .mdns_unregister = kill_mdns_child,
+ .mdns_dacp_monitor = NULL,
+ .mdns_dacp_dont_monitor = NULL};
diff --git a/mdns_tinysvcmdns.c b/mdns_tinysvcmdns.c
new file mode 100644
index 0000000..238b68b
--- /dev/null
+++ b/mdns_tinysvcmdns.c
@@ -0,0 +1,168 @@
+/*
+ * mDNS registration handler. This file is part of Shairport.
+ * Copyright (c) Paul Lietar 2013
+ * 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 "mdns.h"
+#include "common.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "tinysvcmdns.h"
+
+static struct mdnsd *svr = NULL;
+
+static int mdns_tinysvcmdns_register(char *apname, int port) {
+ struct ifaddrs *ifalist;
+ struct ifaddrs *ifa;
+
+ svr = mdnsd_start();
+ if (svr == NULL) {
+ warn("tinysvcmdns: mdnsd_start() failed");
+ return -1;
+ }
+
+ // Thanks to Paul Lietar for this
+ // room for name + .local + NULL
+ char hostname[100 + 6];
+ gethostname(hostname, 99);
+ // according to POSIX, this may be truncated without a final NULL !
+ hostname[99] = 0;
+
+ // will not work if the hostname doesn't end in .local
+ char *hostend = hostname + strlen(hostname);
+ if ((strlen(hostname) < strlen(".local")) || (strcmp(hostend - 6, ".local") != 0)) {
+ strcat(hostname, ".local");
+ }
+
+ if (getifaddrs(&ifalist) < 0) {
+ warn("tinysvcmdns: getifaddrs() failed");
+ return -1;
+ }
+
+ ifa = ifalist;
+
+ // Look for an ipv4/ipv6 non-loopback interface to use as the main one.
+ for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next) {
+ // only check for the named interface, if specified
+ if ((config.interface == NULL) || (strcmp(config.interface, ifa->ifa_name) == 0)) {
+
+ if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr &&
+ ifa->ifa_addr->sa_family == AF_INET) {
+ uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
+
+ mdnsd_set_hostname(svr, hostname, main_ip); // TTL should be 120 seconds
+ break;
+ } else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr &&
+ ifa->ifa_addr->sa_family == AF_INET6) {
+ struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+
+ mdnsd_set_hostname_v6(svr, hostname, addr); // TTL should be 120 seconds
+ break;
+ }
+ }
+ }
+
+ if (ifa == NULL) {
+ warn("tinysvcmdns: no non-loopback ipv4 or ipv6 interface found");
+ return -1;
+ }
+
+ // Skip the first one, it was already added by set_hostname
+ for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces
+ continue;
+ // only check for the named interface, if specified
+ if ((config.interface == NULL) || (strcmp(config.interface, ifa->ifa_name) == 0)) {
+ switch (ifa->ifa_addr->sa_family) {
+ case AF_INET: { // ipv4
+ uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
+ struct rr_entry *a_e =
+ rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds
+ mdnsd_add_rr(svr, a_e);
+ } break;
+ case AF_INET6: { // ipv6
+ struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+ struct rr_entry *aaaa_e =
+ rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds
+ mdnsd_add_rr(svr, aaaa_e);
+ } break;
+ }
+ }
+ }
+
+ freeifaddrs(ifa);
+
+ char *txtwithoutmetadata[] = {MDNS_RECORD_WITHOUT_METADATA, NULL};
+#ifdef CONFIG_METADATA
+ char *txtwithmetadata[] = {MDNS_RECORD_WITH_METADATA, NULL};
+#endif
+ char **txt;
+
+#ifdef CONFIG_METADATA
+ if (config.metadata_enabled)
+ txt = txtwithmetadata;
+ else
+#endif
+
+ txt = txtwithoutmetadata;
+
+ if (config.regtype == NULL)
+ die("tinysvcmdns: regtype is null");
+
+ char *extendedregtype = malloc(strlen(config.regtype) + strlen(".local") + 1);
+
+ if (extendedregtype == NULL)
+ die("tinysvcmdns: could not allocated memory to request a Zeroconf service");
+
+ strcpy(extendedregtype, config.regtype);
+ strcat(extendedregtype, ".local");
+
+ struct mdns_service *svc =
+ mdnsd_register_svc(svr, apname, extendedregtype, port, NULL,
+ (const char **)txt); // TTL should be 75 minutes, i.e. 4500 seconds
+ mdns_service_destroy(svc);
+
+ free(extendedregtype);
+
+ return 0;
+}
+
+static void mdns_tinysvcmdns_unregister(void) {
+ if (svr) {
+ mdnsd_stop(svr);
+ svr = NULL;
+ }
+}
+
+mdns_backend mdns_tinysvcmdns = {.name = "tinysvcmdns",
+ .mdns_register = mdns_tinysvcmdns_register,
+ .mdns_unregister = mdns_tinysvcmdns_unregister,
+ .mdns_dacp_monitor = NULL,
+ .mdns_dacp_dont_monitor = NULL};
diff --git a/player.c b/player.c
new file mode 100644
index 0000000..8530f44
--- /dev/null
+++ b/player.c
@@ -0,0 +1,2591 @@
+/*
+ * Slave-clocked ALAC stream player. This file is part of Shairport.
+ * Copyright (c) James Laird 2011, 2013
+ * All rights reserved.
+ *
+ * Modifications for audio synchronisation
+ * and related work, copyright (c) Mike Brady 2014
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+
+#ifdef HAVE_LIBMBEDTLS
+#include
+#include
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+#include
+#include
+#endif
+
+#ifdef HAVE_LIBSSL
+#include
+#endif
+
+#ifdef HAVE_LIBSOXR
+#include
+#endif
+
+#ifdef CONFIG_CONVOLUTION
+#include
+#endif
+
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+#include "dacp.h"
+#include
+#endif
+
+#ifdef HAVE_DBUS
+#include "dbus-interface.h"
+#include "dbus-service.h"
+#endif
+
+#ifdef HAVE_MPRIS
+#include "mpris-interface.h"
+#include "mpris-player-interface.h"
+#include "mpris-service.h"
+#endif
+
+#include "common.h"
+#include "player.h"
+#include "rtp.h"
+#include "rtsp.h"
+
+#include "alac.h"
+
+#ifdef HAVE_APPLE_ALAC
+#include "apple_alac.h"
+#endif
+
+#include "loudness.h"
+
+// default buffer size
+// needs to be a power of 2 because of the way BUFIDX(seqno) works
+//#define BUFFER_FRAMES 512
+#define MAX_PACKET 2048
+
+// DAC buffer occupancy stuff
+#define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 600
+
+// static abuf_t audio_buffer[BUFFER_FRAMES];
+#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
+
+// make timestamps and seqnos definitely monotonic
+
+// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33
+// frames and continue up to 2^63-1 frames
+// if should never get into the negative range
+// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds or
+// over 50,000 years.
+// also, it won't reach zero until then, if ever, so we can safely say that a null monotonic
+// timestamp can mean something special
+int64_t monotonic_timestamp(uint32_t timestamp, rtsp_conn_info *conn) {
+ int64_t previous_value;
+ int64_t return_value;
+ if (conn->timestamp_epoch == 0) {
+ if (timestamp > conn->maximum_timestamp_interval)
+ conn->timestamp_epoch = 1;
+ else
+ conn->timestamp_epoch = 2;
+ previous_value = conn->timestamp_epoch;
+ previous_value <<= 32;
+ previous_value += timestamp;
+ } else {
+ previous_value = conn->timestamp_epoch;
+ previous_value <<= 32;
+ previous_value += conn->last_timestamp;
+ if (timestamp < conn->last_timestamp) {
+ // the incoming timestamp is less than the last one.
+ // if the difference is more than a minute, assume it's really from the next epoch
+ if ((conn->last_timestamp - timestamp) > conn->maximum_timestamp_interval)
+ conn->timestamp_epoch++;
+ } else {
+ // the incoming timestamp is greater than the last one.
+ // if the difference is more than a minute, assume it's really from the previous epoch
+ if ((timestamp - conn->last_timestamp) > conn->maximum_timestamp_interval)
+ conn->timestamp_epoch--;
+ }
+ }
+ conn->last_timestamp = timestamp;
+ return_value = conn->timestamp_epoch;
+ return_value <<= 32;
+ return_value += timestamp;
+ if (previous_value > return_value) {
+ if ((previous_value - return_value) > conn->maximum_timestamp_interval)
+ debug(2, "interval between successive rtptimes greater than allowed!");
+ } else {
+ if ((return_value - previous_value) > conn->maximum_timestamp_interval)
+ debug(2, "interval between successive rtptimes greater than allowed!");
+ }
+ if (return_value < 0)
+ debug(1, "monotonic rtptime is negative!");
+ return return_value;
+}
+
+static void ab_resync(rtsp_conn_info *conn) {
+ int i;
+ for (i = 0; i < BUFFER_FRAMES; i++) {
+ conn->audio_buffer[i].ready = 0;
+ conn->audio_buffer[i].sequence_number = 0;
+ }
+ conn->ab_synced = 0;
+ conn->last_seqno_read = -1;
+ conn->ab_buffering = 1;
+}
+
+// the sequence number is a 16-bit unsigned number which wraps pretty often
+// to work out if one seqno is 'after' another therefore depends whether wrap has occurred
+// this function works out the actual ordinate of the seqno, i.e. the distance up from
+// the zeroth element, at ab_read, taking due account of wrap.
+
+static inline seq_t SUCCESSOR(seq_t x) {
+ uint32_t p = x & 0xffff;
+ p += 1;
+ p = p & 0xffff;
+ return p;
+}
+
+static inline seq_t PREDECESSOR(seq_t x) {
+ uint32_t p = (x & 0xffff) + 0x10000;
+ p -= 1;
+ p = p & 0xffff;
+ return p;
+}
+
+// anything with ORDINATE in it must be proctected 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;
+}
+
+// now for 32-bit wrapping in timestamps
+
+// this returns true if the second arg is strictly after the first
+// on the assumption that the gap between them is never greater than (2^31)-1
+// Represent a and b in 64 bits
+static inline int seq32_order(uint32_t a, uint32_t b) {
+ if (a == b)
+ return 0;
+ int64_t A = a & 0xffffffff;
+ int64_t B = b & 0xffffffff;
+ int64_t C = B - A;
+ // if bit 31 is set, it means either b is before (i.e. less than) a or
+ // b is (2^31)-1 ahead of a.
+
+ // If we assume the gap between b and a should never reach 2 billion, then
+ // bit 31 == 0 means b is strictly after a
+ return (C & 0x80000000) == 0;
+}
+
+static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_conn_info *conn) {
+ // parameters: where the decoded stuff goes, its length in samples,
+ // the incoming packet, the length of the incoming packet in bytes
+ // destlen should contain the allowed max number of samples on entry
+
+ if (len > MAX_PACKET) {
+ warn("Incoming audio packet size is too large at %d; it should not exceed %d.", len,
+ MAX_PACKET);
+ return -1;
+ }
+ unsigned char packet[MAX_PACKET];
+ unsigned char packetp[MAX_PACKET];
+ assert(len <= MAX_PACKET);
+ int reply = 0; // everything okay
+ int outsize = conn->input_bytes_per_frame * (*destlen); // the size the output should be, in bytes
+ int toutsize = outsize;
+
+ if (conn->stream.encrypted) {
+ unsigned char iv[16];
+ int aeslen = len & ~0xf;
+ memcpy(iv, conn->stream.aesiv, sizeof(iv));
+#ifdef HAVE_LIBMBEDTLS
+ mbedtls_aes_crypt_cbc(&conn->dctx, MBEDTLS_AES_DECRYPT, aeslen, iv, buf, packet);
+#endif
+#ifdef HAVE_LIBPOLARSSL
+ aes_crypt_cbc(&conn->dctx, AES_DECRYPT, aeslen, iv, buf, packet);
+#endif
+#ifdef HAVE_LIBSSL
+ AES_cbc_encrypt(buf, packet, aeslen, &conn->aes, iv, AES_DECRYPT);
+#endif
+ memcpy(packet + aeslen, buf + aeslen, len - aeslen);
+#ifdef HAVE_APPLE_ALAC
+ if (config.use_apple_decoder) {
+ if (conn->decoder_in_use != 1 << decoder_apple_alac) {
+ debug(1, "Apple ALAC Decoder used on encrypted audio.");
+ conn->decoder_in_use = 1 << decoder_apple_alac;
+ }
+ apple_alac_decode_frame(packet, len, (unsigned char *)dest, &outsize);
+ outsize = outsize * 4; // bring the size to bytes
+ } else
+#endif
+ {
+ if (conn->decoder_in_use != 1 << decoder_hammerton) {
+ debug(1, "Hammerton Decoder used on encrypted audio.");
+ conn->decoder_in_use = 1 << decoder_hammerton;
+ }
+ alac_decode_frame(conn->decoder_info, packet, (unsigned char *)dest, &outsize);
+ }
+ } else {
+// not encrypted
+#ifdef HAVE_APPLE_ALAC
+ if (config.use_apple_decoder) {
+ if (conn->decoder_in_use != 1 << decoder_apple_alac) {
+ debug(1, "Apple ALAC Decoder used on unencrypted audio.");
+ conn->decoder_in_use = 1 << decoder_apple_alac;
+ }
+ apple_alac_decode_frame(buf, len, (unsigned char *)dest, &outsize);
+ outsize = outsize * 4; // bring the size to bytes
+ } else
+#endif
+ {
+ if (conn->decoder_in_use != 1 << decoder_hammerton) {
+ debug(1, "Hammerton Decoder used on unencrypted audio.");
+ conn->decoder_in_use = 1 << decoder_hammerton;
+ }
+ alac_decode_frame(conn->decoder_info, buf, dest, &outsize);
+ }
+ }
+
+ if (outsize > toutsize) {
+ debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- "
+ "truncated, but buffer overflow possible! Encrypted = %d.",
+ outsize, toutsize, 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).",
+ *destlen, outsize, conn->input_bytes_per_frame);
+ return reply;
+}
+
+static int init_decoder(int32_t fmtp[12], rtsp_conn_info *conn) {
+
+ // This is a guess, but the format of the fmtp looks identical to the format of an
+ // ALACSpecificCOnfig
+ // which is detailed in the file ALACMagicCookieDescription.txt in the Apple ALAC sample
+ // implementation
+ // Here it is:
+
+ /*
+ struct ALACSpecificConfig (defined in ALACAudioTypes.h)
+ abstract This struct is used to describe codec provided information about the encoded
+ Apple Lossless bitstream.
+ It must accompany the encoded stream in the containing audio file and be provided
+ to the decoder.
+
+ field frameLength uint32_t indicating the frames per packet
+ when
+ no
+ explicit
+ frames per packet setting is
+ present in the packet header. The
+ encoder frames per packet can be explicitly set
+ but for maximum compatibility, the
+ default encoder setting of 4096 should be used.
+
+ field compatibleVersion uint8_t indicating compatible version,
+ value must be set to 0
+
+ field bitDepth uint8_t describes the bit depth of the
+ source
+ PCM
+ data
+ (maximum
+ value = 32)
+
+ field pb uint8_t currently unused tuning
+ parametetbugr.
+ value should be set to 40
+
+ field mb uint8_t currently unused tuning parameter.
+ value should be set to 14
+
+ field kb uint8_t currently unused tuning parameter.
+ value should be set to 10
+
+ field numChannels uint8_t describes the channel count (1 =
+ mono,
+ 2
+ =
+ stereo,
+ etc...)
+ when channel layout info is not provided
+ in the 'magic cookie', a channel count > 2
+ describes a set of discreet channels
+ with no specific ordering
+
+ field maxRun uint16_t currently unused.
+ value should be set to 255
+
+ field maxFrameBytes uint32_t the maximum size of an Apple
+ Lossless
+ packet
+ within
+ the encoded stream.
+ value of 0 indicates unknown
+
+ field avgBitRate uint32_t the average bit rate in bits per
+ second
+ of
+ the
+ Apple
+ Lossless stream.
+ value of 0 indicates unknown
+
+ field sampleRate uint32_t sample rate of the encoded stream
+ */
+
+ // We are going to go on that basis
+
+ alac_file *alac;
+
+ conn->max_frames_per_packet = fmtp[1]; // number of audio frames per packet.
+
+ conn->input_rate = fmtp[11];
+ conn->input_num_channels = fmtp[7];
+ conn->input_bit_depth = fmtp[3];
+
+ conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8);
+
+ alac = alac_create(conn->input_bit_depth, conn->input_num_channels);
+ if (!alac)
+ return 1;
+ conn->decoder_info = alac;
+
+ alac->setinfo_max_samples_per_frame = conn->max_frames_per_packet;
+ alac->setinfo_7a = fmtp[2];
+ alac->setinfo_sample_size = conn->input_bit_depth;
+ alac->setinfo_rice_historymult = fmtp[4];
+ alac->setinfo_rice_initialhistory = fmtp[5];
+ alac->setinfo_rice_kmodifier = fmtp[6];
+ alac->setinfo_7f = fmtp[7];
+ alac->setinfo_80 = fmtp[8];
+ alac->setinfo_82 = fmtp[9];
+ alac->setinfo_86 = fmtp[10];
+ alac->setinfo_8a_rate = fmtp[11];
+ alac_allocate_buffers(alac);
+
+#ifdef HAVE_APPLE_ALAC
+ apple_alac_init(fmtp);
+#endif
+
+ return 0;
+}
+
+static void terminate_decoders(rtsp_conn_info *conn) {
+ alac_free(conn->decoder_info);
+#ifdef HAVE_APPLE_ALAC
+ apple_alac_terminate();
+#endif
+}
+
+static void init_buffer(rtsp_conn_info *conn) {
+ int i;
+ for (i = 0; i < BUFFER_FRAMES; i++)
+ conn->audio_buffer[i].data = malloc(
+ conn->input_bytes_per_frame * (conn->max_frames_per_packet + conn->max_frame_size_change));
+ ab_resync(conn);
+}
+
+static void free_audio_buffers(rtsp_conn_info *conn) {
+ int i;
+ for (i = 0; i < BUFFER_FRAMES; i++)
+ free(conn->audio_buffer[i].data);
+}
+
+void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len,
+ rtsp_conn_info *conn) {
+
+ // all timestamps are done at the output rate
+
+ int64_t ltimestamp = timestamp * conn->output_sample_ratio;
+
+ // ignore a request to flush that has been made before the first packet...
+ if (conn->packet_count == 0) {
+ pthread_mutex_lock(&conn->flush_mutex);
+ conn->flush_requested = 0;
+ conn->flush_rtp_timestamp = 0;
+ pthread_mutex_unlock(&conn->flush_mutex);
+ }
+
+ pthread_mutex_lock(&conn->ab_mutex);
+ conn->packet_count++;
+ conn->time_of_last_audio_packet = get_absolute_time_in_fp();
+ if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
+
+ // if (flush_rtp_timestamp != 0)
+ // debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp);
+
+ if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) {
+ debug(3,
+ "Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to "
+ "timestamp: %lld.",
+ seqno, ltimestamp, conn->flush_rtp_timestamp);
+ } else {
+ if ((conn->flush_rtp_timestamp != 0x0) &&
+ (ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time
+ conn->flush_rtp_timestamp = 0x0;
+
+ abuf_t *abuf = 0;
+
+ if (!conn->ab_synced) {
+ debug(2, "syncing to seqno %u.", seqno);
+ conn->ab_write = seqno;
+ conn->ab_read = seqno;
+ conn->ab_synced = 1;
+ }
+ if (conn->ab_write == seqno) { // expected packet
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ conn->ab_write = SUCCESSOR(seqno);
+ } else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
+ // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8)
+ // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and seqno:
+ // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno);
+ 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->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)) { // late but not yet played
+ conn->late_packets++;
+ abuf = conn->audio_buffer + BUFIDX(seqno);
+ } else { // too late.
+ conn->too_late_packets++;
+ }
+ // pthread_mutex_unlock(&ab_mutex);
+
+ if (abuf) {
+ int datalen = conn->max_frames_per_packet;
+ if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) {
+ abuf->ready = 1;
+ abuf->length = datalen;
+ abuf->timestamp = ltimestamp;
+ abuf->sequence_number = seqno;
+ } else {
+ debug(1, "Bad audio packet detected and discarded.");
+ abuf->ready = 0;
+ abuf->timestamp = 0;
+ abuf->sequence_number = 0;
+ }
+ }
+
+ // pthread_mutex_lock(&ab_mutex);
+ }
+ int rc = pthread_cond_signal(&conn->flowcontrol);
+ if (rc)
+ debug(1, "Error signalling flowcontrol.");
+ }
+ pthread_mutex_unlock(&conn->ab_mutex);
+}
+
+int32_t rand_in_range(int32_t exclusive_range_limit) {
+ static uint32_t lcg_prev = 12345;
+ // returns a pseudo random integer in the range 0 to (exclusive_range_limit-1) inclusive
+ int64_t sp = lcg_prev;
+ int64_t rl = exclusive_range_limit;
+ lcg_prev = lcg_prev * 69069 + 3; // crappy psrg
+ sp = sp * rl; // 64 bit calculation. INtersting part if above the 32 rightmost bits;
+ return sp >> 32;
+}
+
+static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume,
+ int dither, rtsp_conn_info *conn) {
+ int64_t hyper_sample = sample;
+ int result;
+
+ if (config.loudness) {
+ hyper_sample <<=
+ 32; // Do not apply volume as it has already been done with the Loudness DSP filter
+ } else {
+ int64_t hyper_volume = (int64_t)volume << 16;
+ hyper_sample =
+ hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to
+ // dither it down to its
+ // target resolution
+ }
+
+ // next, do dither, if necessary
+ if (dither) {
+
+ // add a TPDF dither -- see
+ // http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf
+ // and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25
+
+ // I think, for a 32 --> 16 bits, the range of
+ // random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from
+ // -32768 to +32767
+
+ // See the original paper at
+ // http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf
+ // by Lipshitz, Wannamaker and Vanderkooy, 1992.
+
+ int64_t dither_mask;
+ switch (format) {
+ case SPS_FORMAT_S32:
+ dither_mask = (int64_t)1 << (64 + 1 - 32);
+ break;
+ case SPS_FORMAT_S24:
+ case SPS_FORMAT_S24_3LE:
+ case SPS_FORMAT_S24_3BE:
+ dither_mask = (int64_t)1 << (64 + 1 - 24);
+ break;
+ case SPS_FORMAT_S16:
+ dither_mask = (int64_t)1 << (64 + 1 - 16);
+ break;
+ case SPS_FORMAT_S8:
+ case SPS_FORMAT_U8:
+ dither_mask = (int64_t)1 << (64 + 1 - 8);
+ break;
+ case SPS_FORMAT_UNKNOWN:
+ die("Unexpected SPS_FORMAT_UNKNOWN while calculating dither mask.");
+ }
+ dither_mask -= 1;
+ // int64_t r = r64i();
+ int64_t r = ranarray64i(); // use an array of precalculated pseudorandom numbers rather than
+ // calculating them on the fly. Should be easier on low-powered
+ // processors
+
+ int64_t tpdf = (r & dither_mask) - (conn->previous_random_number & dither_mask);
+ conn->previous_random_number = r;
+ // add dither, allowing for clipping
+ if (tpdf >= 0) {
+ if (INT64_MAX - tpdf >= hyper_sample)
+ hyper_sample += tpdf;
+ else
+ hyper_sample = INT64_MAX;
+ } else {
+ if (INT64_MIN - tpdf <= hyper_sample)
+ hyper_sample += tpdf;
+ else
+ hyper_sample = INT64_MIN;
+ }
+ // dither is complete here
+ }
+
+ // move the result to the desired position in the int64_t
+ char *op = *outp;
+ uint8_t byt;
+ switch (format) {
+ case SPS_FORMAT_S32:
+ hyper_sample >>= (64 - 32);
+ *(int32_t *)op = hyper_sample;
+ result = 4;
+ break;
+ case SPS_FORMAT_S24_3LE:
+ hyper_sample >>= (64 - 24);
+ byt = (uint8_t)hyper_sample;
+ *op++ = byt;
+ byt = (uint8_t)(hyper_sample >> 8);
+ *op++ = byt;
+ byt = (uint8_t)(hyper_sample >> 16);
+ *op++ = byt;
+ result = 3;
+ break;
+ case SPS_FORMAT_S24_3BE:
+ hyper_sample >>= (64 - 24);
+ byt = (uint8_t)(hyper_sample >> 16);
+ *op++ = byt;
+ byt = (uint8_t)(hyper_sample >> 8);
+ *op++ = byt;
+ byt = (uint8_t)hyper_sample;
+ *op++ = byt;
+ result = 3;
+ break;
+ case SPS_FORMAT_S24:
+ hyper_sample >>= (64 - 24);
+ *(int32_t *)op = hyper_sample;
+ result = 4;
+ break;
+ case SPS_FORMAT_S16:
+ hyper_sample >>= (64 - 16);
+ *(int16_t *)op = (int16_t)hyper_sample;
+ result = 2;
+ break;
+ case SPS_FORMAT_S8:
+ hyper_sample >>= (int8_t)(64 - 8);
+ *op = hyper_sample;
+ result = 1;
+ break;
+ case SPS_FORMAT_U8:
+ hyper_sample >>= (uint8_t)(64 - 8);
+ hyper_sample += 128;
+ *op = hyper_sample;
+ result = 1;
+ break;
+ case SPS_FORMAT_UNKNOWN:
+ die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples");
+ }
+
+ *outp += result;
+}
+
+// get the next frame, when available. return 0 if underrun/stream reset.
+static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
+ int16_t buf_fill;
+ uint64_t local_time_now;
+ // struct timespec tn;
+ abuf_t *abuf = 0;
+ int i;
+ abuf_t *curframe;
+ int notified_buffer_empty = 0; // diagnostic only
+
+ pthread_mutex_lock(&conn->ab_mutex);
+ int wait;
+ long dac_delay = 0; // long because alsa returns a long
+ do {
+ // get the time
+ local_time_now = get_absolute_time_in_fp(); // type okay
+
+ // if config.timeout (default 120) seconds have elapsed since the last audio packet was
+ // received, then we should stop.
+ // config.timeout of zero means don't check..., but iTunes may be confused by a long gap
+ // followed by a resumption...
+
+ if ((conn->time_of_last_audio_packet != 0) && (conn->stop == 0) &&
+ (config.dont_check_timeout == 0)) {
+ uint64_t ct = config.timeout; // go from int to 64-bit int
+ // if (conn->packet_count>500) { //for testing -- about 4 seconds of play first
+ if ((local_time_now > conn->time_of_last_audio_packet) &&
+ (local_time_now - conn->time_of_last_audio_packet >= ct << 32)) {
+ debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" "
+ "from RTSP conversation %d.",
+ conn->connection_number);
+ conn->stop = 1;
+ pthread_kill(conn->thread, SIGUSR1);
+ }
+ }
+ int rco = get_requested_connection_state_to_output();
+
+ if (conn->connection_state_to_output != rco) {
+ conn->connection_state_to_output = rco;
+ // change happening
+ if (conn->connection_state_to_output == 0) { // going off
+ pthread_mutex_lock(&conn->flush_mutex);
+ conn->flush_requested = 1;
+ pthread_mutex_unlock(&conn->flush_mutex);
+ }
+ }
+
+ pthread_mutex_lock(&conn->flush_mutex);
+ if (conn->flush_requested == 1) {
+ 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;
+ conn->flush_requested = 0;
+ }
+ pthread_mutex_unlock(&conn->flush_mutex);
+
+ uint32_t flush_limit = 0;
+ if (conn->ab_synced) {
+ do {
+ curframe = conn->audio_buffer + BUFIDX(conn->ab_read);
+ if ((conn->ab_read != conn->ab_write) &&
+ (curframe->ready)) { // it could be synced and empty, under
+ // exceptional circumstances, with the
+ // frame unused, thus apparently ready
+
+ 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.");
+ }
+ } else {
+ debug(1, "Inconsistent sequence numbers detected");
+ }
+ }
+
+ if ((conn->flush_rtp_timestamp != 0) &&
+ (curframe->timestamp <= conn->flush_rtp_timestamp)) {
+ debug(1, "Dropping flushed packet seqno %u, timestamp %lld", curframe->sequence_number,
+ curframe->timestamp);
+ curframe->ready = 0;
+ flush_limit++;
+ conn->ab_read = SUCCESSOR(conn->ab_read);
+ }
+ if (curframe->timestamp > conn->flush_rtp_timestamp)
+ conn->flush_rtp_timestamp = 0;
+ }
+ } while ((conn->flush_rtp_timestamp != 0) && (flush_limit <= 8820) && (curframe->ready == 0));
+
+ if (flush_limit == 8820) {
+ debug(1, "Flush hit the 8820 frame limit!");
+ flush_limit = 0;
+ }
+
+ curframe = conn->audio_buffer + BUFIDX(conn->ab_read);
+
+ if (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; // 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));
+
+// say we have started playing here
+#if defined(HAVE_MPRIS)
+ if ((conn->play_state != SST_stopped) && (conn->play_state != SST_playing)) {
+ conn->play_state = SST_playing;
+ debug(1, "MPRIS Playing");
+ media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Playing");
+ }
+#endif
+ if (reference_timestamp) { // if we have a reference time
+ // debug(1,"First frame seen with timestamp...");
+ conn->first_packet_timestamp =
+ curframe->timestamp; // we will keep buffering until we are
+ // supposed to start playing this
+ have_sent_prefiller_silence = 0;
+
+ // 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.
+
+ // It probably won't be the timestamp of our first packet, however, so we might have
+ // to do some calculations.
+
+ // To calculate when the first packet will be played, we figure out the exact time the
+ // packet should be played according to its timestamp and the reference time.
+ // We then need to add the desired latency, typically 88200 frames.
+
+ // Then we need to offset this by the backend latency offset. For example, if we knew
+ // 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);
+
+ int64_t delta = (conn->first_packet_timestamp - reference_timestamp) +
+ config.latency * conn->output_sample_ratio +
+ (int64_t)(config.audio_backend_latency_offset * config.output_rate);
+
+ if (delta >= 0) {
+ int64_t delta_fp_sec =
+ (delta << 32) / config.output_rate; // int64_t which is positive
+ conn->first_packet_time_to_play = reference_timestamp_time + delta_fp_sec;
+ } else {
+ int64_t abs_delta = -delta;
+ int64_t delta_fp_sec =
+ (abs_delta << 32) / config.output_rate; // int64_t which is positive
+ conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec;
+ }
+
+ if (local_time_now >= conn->first_packet_time_to_play) {
+ debug(
+ 1,
+ "First packet is late! It should have played before now. Flushing 0.1 seconds");
+ player_flush(conn->first_packet_timestamp + 4410 * conn->output_sample_ratio, conn);
+ }
+ }
+ }
+
+ if (conn->first_packet_time_to_play != 0) {
+ // recalculate conn->first_packet_time_to_play -- the latency might change
+ int64_t delta = (conn->first_packet_timestamp - reference_timestamp) +
+ config.latency * conn->output_sample_ratio +
+ (int64_t)(config.audio_backend_latency_offset * config.output_rate);
+
+ if (delta >= 0) {
+ int64_t delta_fp_sec =
+ (delta << 32) / config.output_rate; // int64_t which is positive
+ conn->first_packet_time_to_play = reference_timestamp_time + delta_fp_sec;
+ } else {
+ int64_t abs_delta = -delta;
+ int64_t delta_fp_sec =
+ (abs_delta << 32) / config.output_rate; // int64_t which is positive
+ conn->first_packet_time_to_play = reference_timestamp_time - delta_fp_sec;
+ }
+
+ // now, the size of the initial silence must be affected by the lead-in time.
+ // it must be somewhat less than the lead-in time so that dynamic adjustments can be
+ // made
+ // to compensate for delays due to paging, etc.
+ // The suggestion is that it should be at least 100 ms less than the lead-in time.
+
+ int64_t max_dac_delay = config.output_rate / 10; // so the lead-in time must be greater
+ // than this, say 0.2 sec, to allow for
+ // dynamic adjustment
+ int64_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC
+
+ if (local_time_now >= conn->first_packet_time_to_play) {
+ // debug(1,"Gone past starting time");
+ have_sent_prefiller_silence = 1;
+ conn->ab_buffering = 0;
+
+ // 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 (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;
+ int64_t 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);
+ // an audio_backend_silent_lead_in_time of less than zero means start filling ASAP
+ if ((lead_in_time < 0) || (lead_time <= lead_in_time)) {
+ // debug(1,"Checking");
+ if (config.output->delay) {
+ // conn->first_packet_time_to_play is definitely later than local_time_now
+ if (have_sent_prefiller_silence != 0) {
+ int resp = config.output->delay(&dac_delay);
+
+ if (resp != 0) {
+ debug(1, "Error %d getting dac_delay in buffer_get_frame.", resp);
+ dac_delay = 0;
+ }
+ } else {
+ dac_delay = 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;
+ if (exact_frame_gap < 0) {
+ // we've gone past the time...
+ // debug(1,"Run past time.");
+ // debug(1,"Run a bit past the exact start time by %lld frames, with time now of
+ // %llx, fpttp of %llx and dac_delay of %d and %d packets;
+ // flush.",-exact_frame_gap,tn,conn->first_packet_time_to_play,dac_delay,seq_diff(ab_read,
+ // ab_write));
+ 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) {
+ debug(2,
+ "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;
+ }
+ signed short *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
+ // ouotputting 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 {
+ memset(silence, 0, conn->output_bytes_per_frame * fs);
+ // 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);
+ free(silence);
+ }
+ }
+ have_sent_prefiller_silence =
+ 1; // even if we haven't sent silence because it's zero frames long...
+ }
+ } 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...
+
+ signed short *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) {
+ size_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, "Outputting %d frames of silence.", fs);
+ memset(silence, 0, conn->output_bytes_per_frame * fs);
+ 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;
+#ifdef CONFIG_METADATA
+ 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.
+
+ // To work out when the time is right, we need to take account of (1) the actual time the packet
+ // should be released,
+ // (2) the latency requested, (3) the audio backend latency offset and (4) the desired length of
+ // the audio backend's buffer
+
+ // The time is right if the current time is later or the same as
+ // The packet time + (latency + latency offset - backend_buffer_length).
+ // Note: the last three items are expressed in frames and must be converted to time.
+
+ int do_wait = 0; // don't wait unless we can really prove we must
+ if ((conn->ab_synced) && (curframe) && (curframe->ready) && (curframe->timestamp)) {
+ do_wait =
+ 1; // if the current frame exists and is ready, then wait unless it's time to let it go...
+ 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); // all types okay
+ reference_timestamp *= conn->output_sample_ratio;
+ if (reference_timestamp) { // if we have a reference time
+ int64_t packet_timestamp = curframe->timestamp; // types okay
+ int64_t delta = packet_timestamp - reference_timestamp;
+ int64_t offset =
+ config.latency * conn->output_sample_ratio +
+ (int64_t)(config.audio_backend_latency_offset * config.output_rate) -
+ config.audio_backend_buffer_desired_length *
+ config.output_rate; // all arguments are int32_t, so expression promotion okay
+ int64_t net_offset = delta + offset; // okay
+ uint64_t time_to_play = reference_timestamp_time; // type okay
+ if (net_offset >= 0) {
+ uint64_t net_offset_fp_sec =
+ (net_offset << 32) / config.output_rate; // int64_t which is positive
+ time_to_play += net_offset_fp_sec; // using the latency requested...
+ // debug(2,"Net Offset: %lld, adjusted: %lld.",net_offset,net_offset_fp_sec);
+ } else {
+ int64_t abs_net_offset = -net_offset;
+ uint64_t net_offset_fp_sec =
+ (abs_net_offset << 32) / config.output_rate; // int64_t which is positive
+ time_to_play -= net_offset_fp_sec;
+ // debug(2,"Net Offset: %lld, adjusted: -%lld.",net_offset,net_offset_fp_sec);
+ }
+
+ if (local_time_now >= time_to_play) {
+ do_wait = 0;
+ }
+ }
+ }
+ if (do_wait == 0)
+ if ((conn->ab_synced != 0) && (conn->ab_read == conn->ab_write)) { // the buffer is empty!
+ if (notified_buffer_empty == 0) {
+ debug(2, "Buffers exhausted.");
+ notified_buffer_empty = 1;
+ }
+ do_wait = 1;
+ }
+ wait = (conn->ab_buffering || (do_wait != 0) || (!conn->ab_synced)) &&
+ (!conn->player_thread_please_stop);
+
+ 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 *= 4 * 352; // four full 352-frame packets
+ time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN
+ 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;
+
+ struct timespec time_of_wakeup;
+ time_of_wakeup.tv_sec = sec;
+ time_of_wakeup.tv_nsec = nsec;
+ pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup);
+// int rc = pthread_cond_timedwait(&flowcontrol,&ab_mutex,&time_of_wakeup);
+// if (rc!=0)
+// debug(1,"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;
+ struct timespec time_to_wait;
+ time_to_wait.tv_sec = sec;
+ time_to_wait.tv_nsec = nsec;
+ pthread_cond_timedwait_relative_np(&conn->flowcontrol, &conn->ab_mutex, &time_to_wait);
+#endif
+ }
+ } while (wait);
+
+ if (conn->player_thread_please_stop) {
+ pthread_mutex_unlock(&conn->ab_mutex);
+ return 0;
+ }
+
+ seq_t read = conn->ab_read;
+
+ // check if t+8, t+16, t+32, t+64, t+128, ... (buffer_start_fill / 2)
+ // packets have arrived... last-chance resend
+
+ if (!conn->ab_buffering) {
+ for (i = 8; i < (seq_diff(conn->ab_read, conn->ab_write, conn->ab_read) / 2); i = (i * 2)) {
+ seq_t next = seq_sum(conn->ab_read, i);
+ abuf = conn->audio_buffer + BUFIDX(next);
+ if (!abuf->ready) {
+ rtp_request_resend(next, 1, conn);
+ // debug(1,"Resend %u.",next);
+ conn->resend_requests++;
+ }
+ }
+ }
+
+ if (!curframe->ready) {
+ // debug(1, "Supplying a silent frame for frame %u", read);
+ conn->missing_packets++;
+ curframe->timestamp = 0; // indicate a silent frame should be substituted
+ }
+ curframe->ready = 0;
+ conn->ab_read = SUCCESSOR(conn->ab_read);
+ pthread_mutex_unlock(&conn->ab_mutex);
+ return curframe;
+}
+
+static inline short shortmean(short a, short b) {
+ long al = (long)a;
+ long bl = (long)b;
+ long longmean = (al + bl) / 2;
+ short r = (short)longmean;
+ if (r != longmean)
+ debug(1, "Error calculating average of two shorts");
+ return r;
+}
+
+static inline int32_t mean_32(int32_t a, int32_t b) {
+ int64_t al = a;
+ int64_t bl = b;
+ int64_t mean = (al + bl) / 2;
+ int32_t r = (int32_t)mean;
+ if (r != mean)
+ debug(1, "Error calculating average of two int32_ts");
+ return r;
+}
+
+// this takes an array of signed 32-bit integers and (a) removes or inserts a frame as specified in
+// stuff,
+// (b) multiplies each sample by the fixedvolume (a 16-bit quantity)
+// (c) dithers the result to the output size 32/24/16/8 bits
+// (d) outputs the result in the approprate format
+// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32
+
+// stuff: 1 means add 1; 0 means do nothing; -1 means remove 1
+static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l_output_format,
+ char *outptr, int stuff, int dither, rtsp_conn_info *conn) {
+ int tstuff = stuff;
+ char *l_outptr = outptr;
+ if ((stuff > 1) || (stuff < -1) || (length < 100)) {
+ // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100.");
+ tstuff = 0; // if any of these conditions hold, don't stuff anything/
+ }
+
+ int i;
+ int stuffsamp = length;
+ if (tstuff)
+ // stuffsamp = rand() % (length - 1);
+ stuffsamp =
+ (rand() % (length - 2)) + 1; // ensure there's always a sample before and after the item
+
+ pthread_mutex_lock(&conn->vol_mutex);
+ for (i = 0; i < stuffsamp; i++) { // the whole frame, if no stuffing
+ process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ };
+ if (tstuff) {
+ if (tstuff == 1) {
+ // debug(3, "+++++++++");
+ // interpolate one sample
+ process_sample(mean_32(inptr[-2], inptr[0]), &l_outptr, l_output_format, conn->fix_volume,
+ dither, conn);
+ process_sample(mean_32(inptr[-1], inptr[1]), &l_outptr, l_output_format, conn->fix_volume,
+ dither, conn);
+ } else if (stuff == -1) {
+ // debug(3, "---------");
+ inptr++;
+ inptr++;
+ }
+
+ // if you're removing, i.e. stuff < 0, copy that much less over. If you're adding, do all the
+ // rest.
+ int remainder = length;
+ if (tstuff < 0)
+ remainder = remainder + tstuff; // don't run over the correct end of the output buffer
+
+ for (i = stuffsamp; i < remainder; i++) {
+ process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ process_sample(*inptr++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ }
+ }
+ pthread_mutex_unlock(&conn->vol_mutex);
+ conn->amountStuffed = tstuff;
+ return length + tstuff;
+}
+
+#ifdef HAVE_LIBSOXR
+// this takes an array of signed 32-bit integers and
+// (a) uses libsoxr to
+// resample the array to have one more or one less frame, as specified in
+// stuff,
+// (b) multiplies each sample by the fixedvolume (a 16-bit quantity)
+// (c) dithers the result to the output size 32/24/16/8 bits
+// (d) outputs the result in the approprate format
+// formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32
+
+static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length,
+ enum sps_format_t l_output_format, char *outptr, int stuff,
+ int dither, rtsp_conn_info *conn) {
+ if (scratchBuffer == NULL) {
+ die("soxr scratchBuffer not initialised.");
+ }
+ int tstuff = stuff;
+ if ((stuff > 1) || (stuff < -1) || (length < 100)) {
+ // debug(1, "Stuff argument to stuff_buffer must be from -1 to +1 and length >100.");
+ tstuff = 0; // if any of these conditions hold, don't stuff anything/
+ }
+
+ if (tstuff) {
+ // debug(1,"Stuff %d.",stuff);
+ soxr_io_spec_t io_spec;
+ io_spec.itype = SOXR_INT32_I;
+ io_spec.otype = SOXR_INT32_I;
+ io_spec.scale = 1.0; // this seems to crash if not = 1.0
+ io_spec.e = NULL;
+ io_spec.flags = 0;
+
+ size_t odone;
+
+ soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */
+ inptr, length, NULL, /* Input. */
+ scratchBuffer, length + tstuff, &odone, /* Output. */
+ &io_spec, /* Input, output and transfer spec. */
+ NULL, NULL); /* Default configuration.*/
+
+ if (error)
+ die("soxr error: %s\n", "error: %s\n", soxr_strerror(error));
+
+ if (odone > length + 1)
+ die("odone = %d!\n", odone);
+
+ int i;
+ int32_t *ip, *op;
+ ip = inptr;
+ op = scratchBuffer;
+
+ const int gpm = 5;
+ // keep the first (dpm) samples, to mitigate the Gibbs phenomenon
+ for (i = 0; i < gpm; i++) {
+ *op++ = *ip++;
+ *op++ = *ip++;
+ }
+
+ // keep the last (dpm) samples, to mitigate the Gibbs phenomenon
+ op = scratchBuffer + (length + tstuff - gpm) * sizeof(int32_t);
+ ip = inptr + (length - gpm) * sizeof(int32_t);
+ for (i = 0; i < gpm; i++) {
+ *op++ = *ip++;
+ *op++ = *ip++;
+ }
+
+ // now, do the volume, dither and formatting processing
+ ip = scratchBuffer;
+ char *l_outptr = outptr;
+ for (i = 0; i < length + tstuff; i++) {
+ process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ };
+
+ } else { // the whole frame, if no stuffing
+
+ // now, do the volume, dither and formatting processing
+ int32_t *ip = inptr;
+ char *l_outptr = outptr;
+ int i;
+
+ for (i = 0; i < length; i++) {
+ process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ process_sample(*ip++, &l_outptr, l_output_format, conn->fix_volume, dither, conn);
+ };
+ }
+ conn->amountStuffed = tstuff;
+ return length + tstuff;
+}
+#endif
+
+typedef struct stats { // statistics for running averages
+ int64_t sync_error, correction, drift;
+} stats_t;
+
+static void *player_thread_func(void *arg) {
+
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
+
+ conn->please_stop = 0;
+ conn->packet_count = 0;
+ conn->previous_random_number = 0;
+ conn->input_bytes_per_frame = 4;
+ conn->player_thread_please_stop = 0;
+ conn->decoder_in_use = 0;
+ conn->ab_buffering = 1;
+ conn->ab_synced = 0;
+ conn->first_packet_timestamp = 0;
+ conn->flush_requested = 0;
+ // conn->fix_volume = 0x10000;
+
+ int rc = pthread_mutex_init(&conn->ab_mutex, NULL);
+ if (rc)
+ debug(1, "Error initialising ab_mutex.");
+ rc = pthread_mutex_init(&conn->flush_mutex, NULL);
+ if (rc)
+ debug(1, "Error initialising flush_mutex.");
+ rc = pthread_mutex_init(&conn->vol_mutex, NULL);
+ if (rc)
+ debug(1, "Error initialising vol_mutex.");
+// set the flowcontrol condition variable to wait on a monotonic clock
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN
+ 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(&conn->flowcontrol, &attr);
+#endif
+#ifdef COMPILE_FOR_OSX
+ rc = pthread_cond_init(&conn->flowcontrol, NULL);
+#endif
+ if (rc)
+ debug(1, "Error initialising flowcontrol condition variable.");
+ config.output->start(config.output_rate, config.output_format);
+
+ init_decoder((int32_t *)&conn->stream.fmtp,
+ conn); // this sets up incoming rate, bit depth, channels
+ // must be after decoder init
+ init_buffer(conn);
+
+ if (conn->stream.encrypted) {
+#ifdef HAVE_LIBMBEDTLS
+ memset(&conn->dctx, 0, sizeof(mbedtls_aes_context));
+ mbedtls_aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128);
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+ memset(&conn->dctx, 0, sizeof(aes_context));
+ aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128);
+#endif
+
+#ifdef HAVE_LIBSSL
+ AES_set_decrypt_key(conn->stream.aeskey, 128, &conn->aes);
+#endif
+ }
+
+ conn->timestamp_epoch = 0; // indicate that the next timestamp will be the first one.
+ conn->maximum_timestamp_interval =
+ conn->input_rate * 60; // actually there shouldn't be more than about 13v
+ // seconds of a gap between successive rtptimes, at
+ // worst
+
+ conn->output_sample_ratio = config.output_rate / conn->input_rate;
+
+ // debug(1, "Output sample ratio is %d.", conn->output_sample_ratio);
+
+ conn->max_frame_size_change =
+ 500 * conn->output_sample_ratio; // we add or subtract one frame at the nominal
+ // rate, multiply it by the frame ratio.
+ // but, on some occasions, more than one frame could be added
+
+ switch (config.output_format) {
+ case SPS_FORMAT_S24_3LE:
+ case SPS_FORMAT_S24_3BE:
+ conn->output_bytes_per_frame = 6;
+ break;
+ case SPS_FORMAT_S24:
+ conn->output_bytes_per_frame = 8;
+ break;
+ case SPS_FORMAT_S32:
+ conn->output_bytes_per_frame = 8;
+ break;
+ default:
+ conn->output_bytes_per_frame = 4;
+ }
+
+ debug(1, "Output frame bytes is %d.", conn->output_bytes_per_frame);
+
+ // create and start the timing, control and audio receiver threads
+ pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
+ pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)conn);
+ pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)conn);
+ pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)conn);
+
+ conn->session_corrections = 0;
+ conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment
+
+ // check that there are enough buffers to accommodate the desired latency and the latency offset
+
+ int maximum_latency =
+ config.latency + (int)(config.audio_backend_latency_offset * config.output_rate);
+ if ((maximum_latency + (352 - 1)) / 352 + 10 > BUFFER_FRAMES)
+ die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame "
+ "packets may be accommodated.",
+ maximum_latency, BUFFER_FRAMES);
+ conn->connection_state_to_output = get_requested_connection_state_to_output();
+// this is about half a minute
+//#define trend_interval 3758
+#define trend_interval 1003
+
+ stats_t statistics[trend_interval];
+ int number_of_statistics, oldest_statistic, newest_statistic;
+ int at_least_one_frame_seen = 0;
+ int64_t tsum_of_sync_errors, tsum_of_corrections, tsum_of_insertions_and_deletions,
+ tsum_of_drifts;
+ int64_t previous_sync_error, previous_correction;
+ int64_t minimum_dac_queue_size = INT64_MAX;
+ int32_t minimum_buffer_occupancy = INT32_MAX;
+ int32_t maximum_buffer_occupancy = INT32_MIN;
+
+ time_t playstart = time(NULL);
+
+ conn->buffer_occupancy = 0;
+
+ int play_samples;
+ int64_t current_delay;
+ int play_number = 0;
+ conn->play_number_after_flush = 0;
+ // int last_timestamp = 0; // for debugging only
+ conn->time_of_last_audio_packet = 0;
+ // conn->shutdown_requested = 0;
+ number_of_statistics = oldest_statistic = newest_statistic = 0;
+ tsum_of_sync_errors = tsum_of_corrections = tsum_of_insertions_and_deletions = tsum_of_drifts = 0;
+
+ const int print_interval = trend_interval; // don't ask...
+ // I think it's useful to keep this prime to prevent it from falling into a pattern with some
+ // other process.
+
+ static char rnstate[256];
+ initstate(time(NULL), rnstate, 256);
+
+ signed short *inbuf, *tbuf, *silence;
+
+ int32_t *sbuf;
+
+ char *outbuf;
+
+ int inbuflength;
+
+ int output_bit_depth = 16; // default;
+
+ switch (config.output_format) {
+ case SPS_FORMAT_S8:
+ case SPS_FORMAT_U8:
+ output_bit_depth = 8;
+ break;
+ case SPS_FORMAT_S16:
+ output_bit_depth = 16;
+ break;
+ case SPS_FORMAT_S24:
+ case SPS_FORMAT_S24_3LE:
+ case SPS_FORMAT_S24_3BE:
+ output_bit_depth = 24;
+ break;
+ case SPS_FORMAT_S32:
+ output_bit_depth = 32;
+ break;
+ case SPS_FORMAT_UNKNOWN:
+ die("Unknown format choosing output bit depth");
+ }
+
+ debug(1, "Output bit depth is %d.", output_bit_depth);
+
+ if (conn->input_bit_depth > output_bit_depth) {
+ debug(1, "Dithering will be enabled because the input bit depth is greater than the output bit "
+ "depth");
+ }
+ if (conn->fix_volume != 0x10000) {
+ debug(1, "Dithering will be enabled because the output volume is being altered in software");
+ }
+
+ // 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));
+ tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio +
+ conn->max_frame_size_change));
+ if (tbuf == NULL)
+ die("Failed to allocate memory for the transition buffer.");
+ sbuf = 0;
+ if (config.packet_stuffing == ST_soxr) { // needed for stuffing
+ sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio +
+ conn->max_frame_size_change));
+ if (sbuf == NULL)
+ debug(1, "Failed to allocate memory for the sbuf buffer.");
+ }
+ // We might need an output buffer and a buffer of silence.
+ // The size of these dependents on the number of frames, the size of each frame and the maximum
+ // size change
+ outbuf = malloc(
+ conn->output_bytes_per_frame *
+ (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change));
+ if (outbuf == NULL)
+ die("Failed to allocate memory for an output buffer.");
+ silence = malloc(conn->output_bytes_per_frame * conn->max_frames_per_packet *
+ conn->output_sample_ratio);
+ if (silence == NULL)
+ die("Failed to allocate memory for a silence buffer.");
+ memset(silence, 0,
+ conn->output_bytes_per_frame * conn->max_frames_per_packet * conn->output_sample_ratio);
+ 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
+
+ // start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the
+ // port number
+ //mdns_dacp_monitor(conn->dacp_id, &conn->dacp_port, &conn->dacp_private);
+ mdns_dacp_monitor(conn);
+
+ conn->framesProcessedInThisEpoch = 0;
+ conn->framesGeneratedInThisEpoch = 0;
+ conn->correctionsRequestedInThisEpoch = 0;
+
+ if (config.statistics_requested) {
+ if ((config.output->delay)) {
+ if (config.no_sync == 0) {
+ inform("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");
+ } else {
+ inform("sync error in milliseconds, "
+ "total packets, "
+ "missing packets, "
+ "late packets, "
+ "too late packets, "
+ "resend requests, "
+ "min DAC queue size, "
+ "min buffer occupancy, "
+ "max buffer occupancy");
+ }
+ } else {
+ inform("sync error in milliseconds, "
+ "total packets, "
+ "missing packets, "
+ "late packets, "
+ "too late packets, "
+ "resend requests, "
+ "min buffer occupancy, "
+ "max buffer occupancy");
+ }
+ }
+
+ // set the default volume to whaterver it was before, as stored in the config airplay_volume
+ debug(1,"Set initial volume to %f.",config.airplay_volume);
+
+ player_volume(config.airplay_volume,conn);
+
+ uint64_t tens_of_seconds = 0;
+ while (!conn->player_thread_please_stop) {
+ abuf_t *inframe = buffer_get_frame(conn);
+ if (inframe) {
+ inbuf = inframe->data;
+ inbuflength = inframe->length;
+ if (inbuf) {
+ play_number++;
+ conn->play_number_after_flush++;
+ if (inframe->timestamp == 0) {
+ // debug(1,"Player has a supplied silent frame.");
+ conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
+ 0xffff); // manage the packet out of sequence minder
+ config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
+ } else if (conn->play_number_after_flush < 10) {
+ /*
+ int64_t difference = 0;
+ if (last_timestamp)
+ difference = inframe->timestamp - last_timestamp;
+ last_timestamp = inframe->timestamp;
+ debug(1, "Play number %d, monotonic timestamp %llx, difference
+ %lld.",conn->play_number_after_flush,inframe->timestamp,difference);
+ */
+ config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
+ } else {
+ int enable_dither = 0;
+ if ((conn->fix_volume != 0x10000) || (conn->input_bit_depth > output_bit_depth) ||
+ (config.playback_mode == ST_mono))
+ enable_dither = 1;
+
+ // here, let's transform the frame of data, if necessary
+
+ switch (conn->input_bit_depth) {
+ case 16: {
+ int i, j;
+ int16_t ls, rs;
+ int32_t ll, rl;
+ int16_t *inps = inbuf;
+ int16_t *outps = tbuf;
+ int32_t *outpl = (int32_t *)tbuf;
+ for (i = 0; i < inbuflength; i++) {
+ ls = *inps++;
+ rs = *inps++;
+
+ // here, do the mode stuff -- mono / reverse stereo / leftonly / rightonly
+ // also, raise the 16-bit samples to 32 bits.
+
+ switch (config.playback_mode) {
+ case ST_mono: {
+ int32_t both = ls + rs;
+ both = both << (16 - 1); // keep all 17 bits of the sum of the 16bit left and right
+ // -- the 17th bit will influence dithering later
+ ll = both;
+ rl = both;
+ } break;
+ case ST_reverse_stereo: {
+ ll = rs;
+ rl = ls;
+ ll = ll << 16;
+ rl = rl << 16;
+ } break;
+ case ST_left_only:
+ rl = ls;
+ ll = ls;
+ ll = ll << 16;
+ rl = rl << 16;
+ break;
+ case ST_right_only:
+ ll = rs;
+ rl = rs;
+ ll = ll << 16;
+ rl = rl << 16;
+ break;
+ case ST_stereo:
+ ll = ls;
+ rl = rs;
+ ll = ll << 16;
+ rl = rl << 16;
+ break; // nothing extra to do
+ }
+
+ // here, replicate the samples if you're upsampling
+
+ for (j = 0; j < conn->output_sample_ratio; j++) {
+ *outpl++ = ll;
+ *outpl++ = rl;
+ }
+ }
+
+ } break;
+ default:
+ die("Shairport Sync only supports 16 bit input");
+ }
+
+ inbuflength *= conn->output_sample_ratio;
+
+ // 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.
+ // So we calculate the timing error for the first frame in the DAC.
+ // If it's ahead of time, we add one audio frame to this frame to delay a subsequent frame
+ // 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;
+
+ 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); // types okay
+ reference_timestamp *= conn->output_sample_ratio;
+ int64_t rt, nt;
+ rt = reference_timestamp; // uint32_t to int64_t
+ nt = inframe->timestamp; // uint32_t to int64_t
+
+ 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;
+ int64_t td_in_frames = 0;
+ if (local_time_now >= reference_timestamp_time) {
+ // debug(1,"td is %lld.",td);
+ 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
+ }
+
+ // This is the timing error for the next audio frame in the DAC, if applicable
+ int64_t sync_error = 0;
+
+ int amount_to_stuff = 0;
+
+ // check sequencing
+ if (conn->last_seqno_read == -1)
+ conn->last_seqno_read =
+ inframe->sequence_number; // int32_t from seq_t, i.e. uint16_t, so okay.
+ else {
+ conn->last_seqno_read =
+ 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(1, "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
+
+ if (conn->buffer_occupancy < minimum_buffer_occupancy)
+ minimum_buffer_occupancy = conn->buffer_occupancy;
+
+ 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);
+ current_delay = l_delay;
+ if (resp == 0) { // no error
+ if (current_delay < 0) {
+ debug(1, "Underrun of %lld frames reported, but ignored.", current_delay);
+ current_delay =
+ 0; // could get a negative value if there was underrun, but ignore it.
+ }
+ if (current_delay < minimum_dac_queue_size) {
+ minimum_dac_queue_size = current_delay; // update for display later
+ }
+ } else {
+ debug(2, "Delay error %d when checking running latency.", resp);
+ }
+ }
+
+ if (resp >= 0) {
+
+ // 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 = td_in_frames + rt - (nt - current_delay); // all int64_t
+
+ // 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 - (config.latency * conn->output_sample_ratio +
+ (int64_t)(config.audio_backend_latency_offset *
+ config.output_rate)); // int64_t from int64_t - int32_t, so okay
+
+ // 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)
+ abs_sync_error = -abs_sync_error;
+
+ if ((config.no_sync == 0) && (inframe->timestamp != 0) &&
+ (!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) &&
+ (abs_sync_error > config.resyncthreshold * config.output_rate)) {
+ sync_error_out_of_bounds++;
+ } else {
+ sync_error_out_of_bounds = 0;
+ }
+
+ if (sync_error_out_of_bounds > 3) {
+ // debug(1, "New lost sync with source for %d consecutive packets -- flushing and "
+ // "resyncing. Error: %lld.",
+ // sync_error_out_of_bounds, sync_error);
+ sync_error_out_of_bounds = 0;
+
+ size_t filler_length =
+ config.resyncthreshold * config.output_rate; // number of samples
+ if ((sync_error > 0) && (sync_error > filler_length)) {
+ // debug(1, "Large positive sync error: %lld. Dropping the frame.", sync_error);
+ } else if ((sync_error < 0) && ((-sync_error) > filler_length)) {
+ // debug(1, "Large negative sync error: %lld. Inserting silence.", sync_error);
+ size_t silence_length = -sync_error;
+ if (silence_length > (filler_length * 5))
+ silence_length = filler_length * 5;
+
+ char *long_silence = malloc(conn->output_bytes_per_frame * silence_length);
+ if (long_silence == NULL)
+ die("Failed to allocate memory for a long_silence buffer of %d frames.",
+ silence_length);
+ memset(long_silence, 0, conn->output_bytes_per_frame * silence_length);
+ config.output->play((short *)long_silence, silence_length);
+ free(long_silence);
+ }
+ } else {
+
+ // before we finally commit to this frame, check its sequencing and timing
+
+ // require a certain error before bothering to fix it...
+ if (sync_error > config.tolerance * config.output_rate) { // int64_t > int, okay
+ amount_to_stuff = -1;
+ }
+ if (sync_error < -config.tolerance * config.output_rate) {
+ amount_to_stuff = 1;
+ }
+
+ // only allow stuffing if there is enough time to do it -- check DAC buffer...
+ if (current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) {
+ // debug(2,"DAC buffer too short(at %lld frames) to allow stuffing.",current_delay);
+ amount_to_stuff = 0;
+ }
+
+ // try to keep the corrections definitely below 1 in 1000 audio frames
+
+ // calculate the time elapsed since the play session started.
+
+ if (amount_to_stuff) {
+ 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
+
+ if (tp < 5)
+ amount_to_stuff = 0; // wait at least five seconds
+ /*
+ else if (tp < 30) {
+ if ((random() % 1000) >
+ 352) // keep it to about 1:1000 for the first thirty seconds
+ amount_to_stuff = 0;
+ }
+ */
+ }
+ }
+
+ if (config.no_sync != 0)
+ amount_to_stuff = 0; // no stuffing if it's been disabled
+
+ // Apply DSP here
+ if (config.loudness
+#ifdef CONFIG_CONVOLUTION
+ || config.convolution
+#endif
+ ) {
+ int32_t *tbuf32 = (int32_t *)tbuf;
+ float fbuf_l[inbuflength];
+ float fbuf_r[inbuflength];
+
+ // Deinterleave, and convert to float
+ int i;
+ for (i = 0; i < inbuflength; ++i) {
+ fbuf_l[i] = tbuf32[2 * i];
+ fbuf_r[i] = tbuf32[2 * i + 1];
+ }
+
+#ifdef CONFIG_CONVOLUTION
+ // Apply convolution
+ if (config.convolution) {
+ convolver_process_l(fbuf_l, inbuflength);
+ convolver_process_r(fbuf_r, inbuflength);
+
+ float gain = pow(10.0, config.convolution_gain / 20.0);
+ for (i = 0; i < inbuflength; ++i) {
+ fbuf_l[i] *= gain;
+ fbuf_r[i] *= gain;
+ }
+ }
+#endif
+
+ if (config.loudness) {
+ // Apply volume and loudness
+ // Volume must be applied here because the loudness filter will increase the
+ // signal level and it would saturate the int32_t otherwise
+ float gain = conn->fix_volume / 65536.0f;
+ float gain_db = 20 * log10(gain);
+ // debug(1, "Applying soft volume dB: %f k: %f", gain_db, gain);
+
+ for (i = 0; i < inbuflength; ++i) {
+ fbuf_l[i] = loudness_process(&loudness_l, fbuf_l[i] * gain);
+ fbuf_r[i] = loudness_process(&loudness_r, fbuf_r[i] * gain);
+ }
+ }
+
+ // Interleave and convert back to int32_t
+ for (i = 0; i < inbuflength; ++i) {
+ tbuf32[2 * i] = fbuf_l[i];
+ tbuf32[2 * i + 1] = fbuf_r[i];
+ }
+ }
+
+ switch (config.packet_stuffing) {
+ case ST_basic:
+ // if (amount_to_stuff) debug(1,"Basic stuff...");
+ play_samples =
+ stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format,
+ outbuf, amount_to_stuff, enable_dither, conn);
+ break;
+ case ST_soxr:
+#ifdef HAVE_LIBSOXR
+ // if (amount_to_stuff) debug(1,"Soxr stuff...");
+ play_samples = stuff_buffer_soxr_32((int32_t *)tbuf, (int32_t *)sbuf, inbuflength,
+ config.output_format, outbuf, amount_to_stuff,
+ enable_dither, conn);
+#endif
+ break;
+ }
+
+ /*
+ {
+ int co;
+ int is_silent=1;
+ short *p = outbuf;
+ for (co=0;coplay((short *)outbuf, play_samples); // remove the (short*)!
+ }
+
+ // check for loss of sync
+ // timestamp of zero means an inserted silent frame in place of a missing frame
+ /*
+ if ((config.no_sync == 0) && (inframe->timestamp != 0) &&
+ (!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) &&
+ (abs_sync_error > config.resyncthreshold * config.output_rate)) {
+ sync_error_out_of_bounds++;
+ // debug(1,"Sync error out of bounds: Error: %lld; previous error: %lld; DAC: %lld;
+ // timestamp: %llx, time now
+ //
+ %llx",sync_error,previous_sync_error,current_delay,inframe->timestamp,local_time_now);
+ if (sync_error_out_of_bounds > 3) {
+ debug(1, "Lost sync with source for %d consecutive packets -- flushing and "
+ "resyncing. Error: %lld.",
+ sync_error_out_of_bounds, sync_error);
+ sync_error_out_of_bounds = 0;
+ player_flush(nt, conn);
+ }
+ } else {
+ sync_error_out_of_bounds = 0;
+ }
+ */
+ }
+ } else {
+ // if there is no delay procedure, or it's not working or not allowed, there can be no
+ // synchronising
+ play_samples = stuff_buffer_basic_32((int32_t *)tbuf, inbuflength, config.output_format,
+ outbuf, 0, enable_dither, conn);
+ if (outbuf == NULL)
+ debug(1, "NULL outbuf to play -- skipping it.");
+ else
+ config.output->play((short *)outbuf, play_samples); // remove the (short*)!
+ }
+
+ // mark the frame as finished
+ inframe->timestamp = 0;
+ inframe->sequence_number = 0;
+
+ // debug(1,"Sync error %lld frames. Amount to stuff %d." ,sync_error,amount_to_stuff);
+
+ // new stats calculation. We want a running average of sync error, drift, adjustment,
+ // number of additions+subtractions
+
+ // this is a misleading hack -- the statistics should include some data on the number of
+ // 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 (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;
+ else
+ tsum_of_insertions_and_deletions += statistics[oldest_statistic].correction;
+ tsum_of_corrections -= 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;
+
+ if (number_of_statistics == 0)
+ statistics[newest_statistic].drift = 0;
+ else
+ 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;
+ if (conn->amountStuffed > 0) {
+ tsum_of_insertions_and_deletions += conn->amountStuffed;
+ } else {
+ tsum_of_insertions_and_deletions -= conn->amountStuffed;
+ }
+ tsum_of_corrections += conn->amountStuffed;
+ conn->session_corrections += conn->amountStuffed;
+
+ newest_statistic = (newest_statistic + 1) % trend_interval;
+ number_of_statistics++;
+ }
+ }
+ if (play_number % print_interval == 0) {
+ // we can now calculate running averages for sync error (frames), corrections (ppm),
+ // insertions plus deletions (ppm), drift (ppm)
+ double moving_average_sync_error = (1.0 * tsum_of_sync_errors) / number_of_statistics;
+ double moving_average_correction = (1.0 * tsum_of_corrections) / number_of_statistics;
+ double moving_average_insertions_plus_deletions =
+ (1.0 * tsum_of_insertions_and_deletions) / number_of_statistics;
+ double moving_average_drift = (1.0 * tsum_of_drifts) / number_of_statistics;
+ // if ((play_number/print_interval)%20==0)
+ if (config.statistics_requested) {
+ if (at_least_one_frame_seen) {
+ if ((config.output->delay)) {
+ if (config.no_sync == 0) {
+ inform(
+ " %*.1f," /* Sync error in milliseconds */
+ "%*.1f," /* net correction in ppm */
+ "%*.1f," /* corrections in ppm */
+ "%*d," /* total packets */
+ "%*llu," /* missing packets */
+ "%*llu," /* late packets */
+ "%*llu," /* too late packets */
+ "%*llu," /* resend requests */
+ "%*lli," /* min DAC queue size */
+ "%*d," /* min buffer occupancy */
+ "%*d", /* max buffer occupancy */
+ 9, /* should be 10, but there's an explicit space at the start to ensure
+ alignment */
+ 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);
+ } else {
+ inform(" %*.1f," /* Sync error in milliseconds */
+ "%*d," /* total packets */
+ "%*llu," /* missing packets */
+ "%*llu," /* late packets */
+ "%*llu," /* too late packets */
+ "%*llu," /* resend requests */
+ "%*lli," /* min DAC queue size */
+ "%*d," /* min buffer occupancy */
+ "%*d", /* max buffer occupancy */
+ 9, /* should be 10, but there's an explicit space at the start to ensure
+ alignment */
+ 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);
+ }
+ } else {
+ inform(" %*.1f," /* Sync error in milliseconds */
+ "%*d," /* total packets */
+ "%*llu," /* missing packets */
+ "%*llu," /* late packets */
+ "%*llu," /* too late packets */
+ "%*llu," /* resend requests */
+ "%*d," /* min buffer occupancy */
+ "%*d", /* max buffer occupancy */
+ 9, /* should be 10, but there's an explicit space at the start to ensure
+ alignment */
+ 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, maximum_buffer_occupancy);
+ }
+ } else {
+ inform("No frames received in the last sampling interval.");
+ }
+ }
+ minimum_dac_queue_size = INT64_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;
+ }
+ }
+ }
+ }
+
+ if (config.statistics_requested) {
+ int rawSeconds = (int)difftime(time(NULL), playstart);
+ int elapsedHours = rawSeconds / 3600;
+ int elapsedMin = (rawSeconds / 60) % 60;
+ int elapsedSec = rawSeconds % 60;
+ inform("Playback Stopped. Total playing time %02d:%02d:%02d.", elapsedHours, elapsedMin,
+ elapsedSec);
+ }
+
+ // stop watching for DACP port number stuff
+ mdns_dacp_dont_monitor(conn); // begin looking out for information about the client
+ // as a remote control. Specifically we might need
+ // the port number
+
+ if (config.output->stop)
+ config.output->stop();
+ usleep(100000); // allow this time to (?) allow the alsa subsystem to finish cleaning up after
+ // itself. 50 ms seems too short
+
+ debug(2, "Shut down audio, control and timing threads");
+ conn->please_stop = 1;
+ pthread_kill(rtp_audio_thread, SIGUSR1);
+ pthread_kill(rtp_control_thread, SIGUSR1);
+ pthread_kill(rtp_timing_thread, SIGUSR1);
+ pthread_join(rtp_timing_thread, NULL);
+ debug(3, "timing thread joined");
+ pthread_join(rtp_audio_thread, NULL);
+ debug(3, "audio thread joined");
+ pthread_join(rtp_control_thread, NULL);
+ debug(3, "control thread joined");
+ clear_reference_timestamp(conn);
+ conn->rtp_running = 0;
+
+ free_audio_buffers(conn);
+ terminate_decoders(conn);
+ // remove flow control and mutexes
+ rc = pthread_cond_destroy(&conn->flowcontrol);
+ if (rc)
+ debug(1, "Error destroying flowcontrol condition variable.");
+ rc = pthread_mutex_destroy(&conn->flush_mutex);
+ if (rc)
+ debug(1, "Error destroying flush_mutex variable.");
+ rc = pthread_mutex_destroy(&conn->ab_mutex);
+ if (rc)
+ debug(1, "Error destroying ab_mutex variable.");
+ rc = pthread_mutex_destroy(&conn->vol_mutex);
+ if (rc)
+ debug(1, "Error destroying vol_mutex variable.");
+
+ debug(1, "Player thread exit on RTSP conversation thread %d.", conn->connection_number);
+ if (conn->dacp_id) {
+ free(conn->dacp_id);
+ conn->dacp_id = NULL;
+ }
+ if (outbuf)
+ free(outbuf);
+ if (silence)
+ free(silence);
+ if (tbuf)
+ free(tbuf);
+ if (sbuf)
+ free(sbuf);
+ return 0;
+}
+
+// takes the volume as specified by the airplay protocol
+void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) {
+
+ // The volume ranges -144.0 (mute) or -30 -- 0. See
+ // http://git.zx2c4.com/Airtunes2/about/#setting-volume
+ // By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30
+ // equal increments. Since the human ear's response is roughly logarithmic, we imagine these to
+ // be power dB, i.e. from -30dB to 0dB.
+
+ // We may have a hardware mixer, and if so, we will give it priority.
+ // If a desired volume range is given, then we will try to accommodate it from
+ // the top of the hardware mixer's range downwards.
+
+ // If no desired volume range is given, we will use the native resolution of the hardware mixer,
+ // if any,
+ // or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0
+ // dB,
+ // corresponding to a multiplier of 1 to 65535.
+
+ // Otherwise, we will accommodate the desired volume range in the combination of the software and
+ // hardware mixer
+ // Intuitively (!), it seems best to give the hardware mixer as big a role as possible, so
+ // we will use its full range and then accommodate the rest of the attenuation in software.
+ // A problem is that we don't know whether the lowest hardware volume actually mutes the output
+ // so we must assume that it does, and for this reason, the volume control goes at the "bottom" of
+ // the adjustment range
+
+ // The dB range of a value from 1 to 65536 is about 96.3 dB (log10 of 65536 is 4.8164).
+ // Since the levels correspond with amplitude, they correspond to voltage, hence voltage dB,
+ // or 20 times the log of the ratio. Then multiplied by 100 for convenience.
+ // Thus, we ask our vol2attn function for an appropriate dB between -96.3 and 0 dB and translate
+ // it back to a number.
+
+ int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db,
+ max_db; // hw_range_db is a flag; if 0 means no mixer
+
+ if (config.output->parameters) {
+ audio_parameters audio_information;
+ // have a hardware mixer
+ config.output->parameters(&audio_information);
+ hw_max_db = audio_information.maximum_volume_dB;
+ hw_min_db = audio_information.minimum_volume_dB;
+ hw_range_db = hw_max_db - hw_min_db;
+ } else {
+ // don't have a hardware mixer
+ hw_max_db = hw_min_db = hw_range_db = 0;
+ }
+
+ int32_t sw_min_db = -9630;
+ int32_t sw_max_db = 0;
+ int32_t sw_range_db = sw_max_db - sw_min_db;
+ int32_t desired_range_db = 0; // this is also used as a flag; if 0 means no desired range
+
+ if (config.volume_range_db)
+ desired_range_db = (int32_t)trunc(config.volume_range_db * 100);
+
+ // This is wrong, I think -- it doesn't work properly if the volume range is composite and you
+ // want to set a maximum value which should affect the hardware mixer.
+
+ if (config.volume_max_db_set) {
+ if (hw_range_db) {
+ if (((config.volume_max_db * 100) < hw_max_db) &&
+ ((config.volume_max_db * 100) > hw_min_db)) {
+ hw_max_db = (int)config.volume_max_db * 100;
+ hw_range_db = hw_max_db - hw_min_db;
+ } else {
+ inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB "
+ "to %d dB. It will be ignored.",
+ (int)(hw_max_db / 100), (int)(hw_min_db / 100));
+ }
+ } else {
+ if (((config.volume_max_db * 100) < sw_max_db) &&
+ ((config.volume_max_db * 100) > sw_min_db)) {
+ sw_max_db = (int)config.volume_max_db * 100;
+ sw_range_db = sw_max_db - sw_min_db;
+ } else {
+ inform("The volume_max_db setting is out of range of the software attenuation's limits of "
+ "0 dB to -96.3 dB. It will be ignored.");
+ }
+ }
+ }
+
+ if (desired_range_db) {
+ // debug(1,"An attenuation range of %d is requested.",desired_range_db);
+ // we have a desired volume range.
+ if (hw_range_db) {
+ // we have a hardware mixer
+ if (hw_range_db >= desired_range_db) {
+ // the hardware mixer can accommodate the desired range
+ max_db = hw_max_db;
+ min_db = max_db - desired_range_db;
+ } else {
+ // we have a hardware mixer and a desired range greater than the mixer's range.
+ if ((hw_range_db + sw_range_db) < desired_range_db) {
+ inform("The volume attenuation range %f is greater than can be accommodated by the "
+ "hardware and software -- set to %f.",
+ config.volume_range_db, hw_range_db + sw_range_db);
+ desired_range_db = hw_range_db + sw_range_db;
+ }
+ min_db = hw_min_db;
+ max_db = min_db + desired_range_db;
+ }
+ } else {
+ // we have a desired volume range and no hardware mixer
+ if (sw_range_db < desired_range_db) {
+ inform("The volume attenuation range %f is greater than can be accommodated by the "
+ "software -- set to %f.",
+ config.volume_range_db, sw_range_db);
+ desired_range_db = sw_range_db;
+ }
+ max_db = sw_max_db;
+ min_db = max_db - desired_range_db;
+ }
+ } else {
+ // we do not have a desired volume range, so use the mixer's volume range, if there is one.
+ // debug(1,"No attenuation range requested.");
+ if (hw_range_db) {
+ min_db = hw_min_db;
+ max_db = hw_max_db;
+ } else {
+ min_db = sw_min_db;
+ max_db = sw_max_db;
+ }
+ }
+
+ /*
+ if (config.volume_max_db_set) {
+ if ((config.volume_max_db*100<=max_db) && (config.volume_max_db*100>=min_db)) {
+ debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db);
+ max_db = (int)(config.volume_max_db*100);
+ } else {
+ inform("The value of volume_max_db is invalid. It must be in the range %d to
+ %d.",max_db,min_db);
+ }
+ }
+ */
+ double hardware_attenuation, software_attenuation;
+ double scaled_attenuation = hw_min_db + sw_min_db;
+
+ // now, we can map the input to the desired output volume
+ if ((airplay_volume == -144.0) && (config.ignore_volume_control == 0)) {
+ // do a mute
+ // needed even with hardware mute, as when sound is unmuted it might otherwise be very loud.
+ hardware_attenuation = hw_min_db;
+ if (config.output->mute) {
+ // allow the audio material to reach the mixer, but mute the mixer
+ // it the mute is removed externally, the material with be there
+ software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and
+ // the max is +40, this will be -36
+ // (all by 100, of course)
+ // debug(1,"Mute, with hardware mute and software_attenuation set to
+ // %d.",software_attenuation);
+ config.output->mute(1); // use real mute if it's there
+ } else {
+ if (config.output->volume == NULL) { // if there is also no hardware volume control
+ software_attenuation = sw_min_db; // set any software output to zero too
+ // debug(1,"Mute, with no hardware mute and software_attenuation set to
+ // %d.",software_attenuation);
+ }
+ }
+ } else {
+ if (config.output->mute)
+ config.output->mute(0); // unmute mute if it's there
+ if (config.ignore_volume_control == 1)
+ scaled_attenuation = max_db;
+ else
+ scaled_attenuation = vol2attn(airplay_volume, max_db, min_db);
+ if (hw_range_db) {
+ // if there is a hardware mixer
+ if (scaled_attenuation <= hw_max_db) {
+ // the attenuation is so low that's it's in the hardware mixer's range
+ // debug(1,"Attenuation all taken care of by the hardware mixer.");
+ hardware_attenuation = scaled_attenuation;
+ software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and
+ // the max is +40, this will be -36
+ // (all by 100, of course)
+ } else {
+ // debug(1,"Attenuation taken care of by hardware and software mixer.");
+ hardware_attenuation = hw_max_db; // the hardware mixer is turned up full
+ software_attenuation = sw_max_db - (max_db - scaled_attenuation);
+ }
+ } else {
+ // if there is no hardware mixer, the scaled_volume is the software volume
+ // debug(1,"Attenuation all taken care of by the software mixer.");
+ software_attenuation = scaled_attenuation;
+ }
+ }
+
+ if ((config.output->volume) && (hw_range_db)) {
+ 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);
+ }
+ 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);
+
+ pthread_mutex_lock(&conn->vol_mutex);
+ conn->fix_volume = temp_fix_volume;
+ pthread_mutex_unlock(&conn->vol_mutex);
+
+ 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
+ char *dv = malloc(128); // will be freed in the metadata thread
+ if (dv) {
+ memset(dv, 0, 128);
+ if (config.ignore_volume_control == 1)
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.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
+
+ // here, store the volume for possible use in the future
+ config.airplay_volume = airplay_volume;
+}
+
+void player_volume(double airplay_volume, rtsp_conn_info *conn) {
+ command_set_volume(airplay_volume);
+
+#ifdef HAVE_DBUS
+ // A volume command has been sent from the client
+ // let's get the master volume from the DACP remote control
+
+ struct dacp_speaker_stuff speaker_info[50];
+ // we need the overall volume and the speakers information to get this device's relative volume to
+ // calculate the real volume
+
+ int32_t overall_volume = dacp_get_client_volume(conn);
+ // debug(1,"DACP Volume: %d.",overall_volume);
+ int speaker_count = dacp_get_speaker_list(conn, (dacp_spkr_stuff *)&speaker_info, 50);
+ // debug(1,"DACP Speaker Count: %d.",speaker_count);
+
+ // get our machine number
+ uint16_t *hn = (uint16_t *)config.hw_addr;
+ uint32_t *ln = (uint32_t *)(config.hw_addr + 2);
+ uint64_t t1 = ntohs(*hn);
+ uint64_t t2 = ntohl(*ln);
+ int64_t machine_number = (t1 << 32) + t2; // this form is useful
+
+ // Let's find our own speaker in the array and pick up its relative volume
+ int i;
+ int32_t relative_volume = 0;
+ for (i = 0; i < speaker_count; i++) {
+ if (speaker_info[i].speaker_number == machine_number) {
+ // debug(1,"Our speaker number found: %ld.",machine_number);
+ relative_volume = speaker_info[i].volume;
+ }
+ }
+ int32_t actual_volume = (overall_volume * relative_volume + 50) / 100;
+ // debug(1,"Overall volume: %d, relative volume: %d%, actual volume:
+ // %d.",overall_volume,relative_volume,actual_volume);
+ // debug(1,"Our actual speaker volume is %d.",actual_volume);
+ conn->dacp_volume = actual_volume; // this is needed to prevent a loop
+ shairport_sync_set_volume(SHAIRPORT_SYNC(shairportSyncSkeleton), actual_volume);
+#endif
+ player_volume_without_notification(airplay_volume, conn);
+}
+
+void player_flush(int64_t timestamp, rtsp_conn_info *conn) {
+ debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp);
+ pthread_mutex_lock(&conn->flush_mutex);
+ conn->flush_requested = 1;
+ // if (timestamp!=0)
+ conn->flush_rtp_timestamp = timestamp; // flush all packets up to (and including?) this
+ pthread_mutex_unlock(&conn->flush_mutex);
+ conn->play_segment_reference_frame = 0;
+ conn->play_number_after_flush = 0;
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('pfls', NULL, 0, 1);
+#endif
+#if defined(HAVE_MPRIS)
+ if ((conn->play_state != SST_stopped) && (conn->play_state != SST_paused))
+ conn->play_state = SST_paused;
+ debug(1, "MPRIS Paused");
+ media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Paused");
+#endif
+}
+
+int player_play(rtsp_conn_info *conn) {
+ // need to use conn in place of streram below. Need to put the stream as a parameter to he
+ if (conn->player_thread != NULL)
+ die("Trying to create a second player thread for this RTSP session");
+ if (config.buffer_start_fill > BUFFER_FRAMES)
+ die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill,
+ BUFFER_FRAMES);
+ command_start();
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('pbeg', NULL, 0, 1);
+#endif
+ pthread_t *pt = malloc(sizeof(pthread_t));
+ 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));
+ pthread_create(pt, &tattr, player_thread_func, (void *)conn);
+ pthread_attr_destroy(&tattr);
+#if defined(HAVE_MPRIS)
+ if (conn->play_state != SST_playing)
+ conn->play_state = SST_playing;
+ debug(1, "MPRIS Playing (play)");
+ media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Playing");
+#endif
+ return 0;
+}
+
+void player_stop(rtsp_conn_info *conn) {
+ if (conn->player_thread) {
+ conn->player_thread_please_stop = 1;
+ pthread_cond_signal(&conn->flowcontrol); // tell it to give up
+ pthread_kill(*conn->player_thread, SIGUSR1);
+ pthread_join(*conn->player_thread, NULL);
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('pend', NULL, 0, 1);
+#endif
+ command_stop();
+ free(conn->player_thread);
+ conn->player_thread = NULL;
+#if defined(HAVE_MPRIS)
+ if (conn->play_state != SST_stopped)
+ conn->play_state = SST_stopped;
+ debug(1, "MPRIS Stopped");
+ media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Stopped");
+#endif
+
+ } else {
+ debug(3, "player thread of RTSP conversation %d is already deleted.", conn->connection_number);
+ }
+}
diff --git a/player.h b/player.h
new file mode 100644
index 0000000..3c9f43d
--- /dev/null
+++ b/player.h
@@ -0,0 +1,208 @@
+#ifndef _PLAYER_H
+#define _PLAYER_H
+
+#include
+#include
+
+#include "config.h"
+#include "definitions.h"
+
+#ifdef HAVE_LIBMBEDTLS
+#include
+#include
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+#include
+#include
+#endif
+
+#ifdef HAVE_LIBSSL
+#include
+#endif
+
+#include "alac.h"
+#include "audio.h"
+
+#define time_ping_history 8
+
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+enum session_status_type {
+ SST_stopped = 0, // not playing anything
+ SST_paused, // paused
+ SST_playing,
+} sst_type;
+#endif
+
+typedef struct time_ping_record {
+ uint64_t local_to_remote_difference;
+ uint64_t dispersion;
+ uint64_t local_time;
+ uint64_t remote_time;
+} time_ping_record;
+
+typedef uint16_t seq_t;
+
+typedef struct audio_buffer_entry { // decoded audio packets
+ int ready;
+ int64_t timestamp;
+ seq_t sequence_number;
+ signed short *data;
+ int length; // the length of the decoded data
+} abuf_t;
+
+// default buffer size
+// needs to be a power of 2 because of the way BUFIDX(seqno) works
+#define BUFFER_FRAMES 512
+
+typedef struct {
+ int encrypted;
+ uint8_t aesiv[16], aeskey[16];
+ int32_t fmtp[12];
+} stream_cfg;
+
+typedef struct {
+ int connection_number; // for debug ID purposes, nothing else...
+ int64_t staticLatencyCorrection; // it seems iTunes needs some offset before it's more or less right. Odd.
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+ enum session_status_type play_state;
+#endif
+ int fd;
+ int authorized; // set if a password is required and has been supplied
+ stream_cfg stream;
+ SOCKADDR remote, local;
+ int stop;
+ int running;
+ pthread_t thread;
+
+ // pthread_t *ptp;
+ pthread_t *player_thread;
+
+ abuf_t audio_buffer[BUFFER_FRAMES];
+ int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate;
+ int input_bytes_per_frame, output_bytes_per_frame, output_sample_ratio;
+ int max_frame_size_change;
+ int64_t previous_random_number;
+ alac_file *decoder_info;
+ uint32_t please_stop;
+ uint64_t packet_count;
+ int connection_state_to_output;
+ int player_thread_please_stop;
+ int64_t first_packet_time_to_play, time_since_play_started; // nanoseconds
+ // stats
+ uint64_t missing_packets, late_packets, too_late_packets, resend_requests;
+ int decoder_in_use;
+ // debug variables
+ int32_t last_seqno_read;
+ // mutexes and condition variables
+ pthread_cond_t flowcontrol;
+ pthread_mutex_t ab_mutex, flush_mutex;
+ pthread_mutex_t vol_mutex;
+ int fix_volume;
+ uint32_t timestamp_epoch, last_timestamp,
+ maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2
+ // or 1.
+ int ab_buffering, ab_synced;
+ int64_t first_packet_timestamp;
+ int flush_requested;
+ int64_t flush_rtp_timestamp;
+ uint64_t time_of_last_audio_packet;
+ seq_t ab_read, ab_write;
+
+#ifdef HAVE_LIBMBEDTLS
+ mbedtls_aes_context dctx;
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+ aes_context dctx;
+#endif
+
+#ifdef HAVE_LIBSSL
+ AES_KEY aes;
+#endif
+
+#ifdef HAVE_DBUS
+ int32_t dacp_volume;
+#endif
+
+ int amountStuffed;
+
+ int32_t framesProcessedInThisEpoch;
+ int32_t framesGeneratedInThisEpoch;
+ int32_t correctionsRequestedInThisEpoch;
+ int64_t syncErrorsInThisEpoch;
+
+ // RTP stuff
+ // only one RTP session can be active at a time.
+ int rtp_running;
+
+ 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
+ // 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
+ int audio_socket; // our local [server] audio socket
+ int control_socket; // our local [server] control socket
+ int timing_socket; // local timing socket
+
+ int64_t reference_timestamp;
+ uint64_t reference_timestamp_time;
+ uint64_t remote_reference_timestamp_time;
+
+ // debug variables
+ int request_sent;
+
+ uint8_t time_ping_count;
+ struct time_ping_record time_pings[time_ping_history];
+
+ uint64_t departure_time; // dangerous -- this assumes that there will never be two timing
+ // request in flight at the same time
+
+ pthread_mutex_t reference_time_mutex;
+
+ uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks
+
+ int timing_sender_stop; // for asking the timing-sending thread to stop
+ int last_stuff_request;
+
+ int64_t play_segment_reference_frame;
+ uint64_t play_segment_reference_frame_remote_time;
+
+ int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative
+ int64_t session_corrections;
+
+ int play_number_after_flush;
+
+ // remote control stuff. The port to which to send commands is not specified, so you have to use
+ // mdns to find it.
+ // at present, only avahi can do this
+
+ 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 *mdns_private_pointer; // private storage (just a pointer) for the dacp_port resolver
+
+} rtsp_conn_info;
+
+int player_play(rtsp_conn_info *conn);
+void player_stop(rtsp_conn_info *conn);
+
+void player_volume(double f, rtsp_conn_info *conn);
+void player_volume_without_notification(double f, rtsp_conn_info *conn);
+void player_flush(int64_t timestamp, rtsp_conn_info *conn);
+void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len,
+ rtsp_conn_info *conn);
+
+int64_t monotonic_timestamp(uint32_t timestamp,
+ rtsp_conn_info *conn); // add an epoch to the timestamp. The monotonic
+// timestamp guaranteed to start between 2^32 2^33
+// frames and continue up to 2^64 frames
+// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds.
+// assumes, without checking, that successive timestamps in a series always span an interval of less
+// than one minute.
+
+#endif //_PLAYER_H
diff --git a/rtp.c b/rtp.c
new file mode 100644
index 0000000..22646d7
--- /dev/null
+++ b/rtp.c
@@ -0,0 +1,803 @@
+/*
+ * Apple RTP protocol handler. This file is part of Shairport.
+ * Copyright (c) James Laird 2013
+ * Copyright (c) Mike Brady 2014 -- 2017
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "common.h"
+#include "player.h"
+#include "rtp.h"
+
+void memory_barrier();
+
+void rtp_initialise(rtsp_conn_info *conn) {
+
+ conn->rtp_running = 0;
+ // initialise the timer mutex
+ int rc = pthread_mutex_init(&conn->reference_time_mutex, NULL);
+ if (rc)
+ debug(1, "Error initialising reference_time_mutex.");
+}
+
+void rtp_terminate(rtsp_conn_info *conn) {
+
+ // destroy the timer mutex
+ int rc = pthread_mutex_destroy(&conn->reference_time_mutex);
+ if (rc)
+ debug(1, "Error destroying reference_time_mutex variable.");
+}
+
+void *rtp_audio_receiver(void *arg) {
+ debug(2, "Audio receiver -- Server RTP thread starting.");
+
+ // we inherit the signal mask (SIGUSR1)
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
+
+ int32_t last_seqno = -1;
+ uint8_t packet[2048], *pktp;
+
+ uint64_t time_of_previous_packet_fp = 0;
+ float longest_packet_time_interval_us = 0.0;
+
+ // mean and variance calculations from "online_variance" algorithm at
+ // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
+
+ int32_t stat_n = 0;
+ float stat_mean = 0.0;
+ float stat_M2 = 0.0;
+
+ ssize_t nread;
+ while (conn->please_stop == 0) {
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(conn->audio_socket, &readfds);
+ do {
+ memory_barrier();
+ } while (conn->please_stop == 0 &&
+ pselect(conn->audio_socket + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
+ if (conn->please_stop != 0) {
+ break;
+ }
+ nread = recv(conn->audio_socket, packet, sizeof(packet), 0);
+
+ 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;
+ if (time_interval_us > longest_packet_time_interval_us)
+ longest_packet_time_interval_us = time_interval_us;
+ stat_n += 1;
+ float stat_delta = time_interval_us - stat_mean;
+ 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.",
+ 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;
+ longest_packet_time_interval_us = 0.0;
+ }
+ } else {
+ time_of_previous_packet_fp = local_time_now_fp;
+ }
+
+ if (nread < 0)
+ break;
+
+ ssize_t plen = nread;
+ uint8_t type = packet[1] & ~0x80;
+ if (type == 0x60 || type == 0x56) { // audio data / resend
+ pktp = packet;
+ if (type == 0x56) {
+ pktp += 4;
+ plen -= 4;
+ }
+ seq_t seqno = ntohs(*(unsigned short *)(pktp + 2));
+ // increment last_seqno and see if it's the same as the incoming seqno
+
+ if (last_seqno == -1)
+ last_seqno = seqno;
+ else {
+ last_seqno = (last_seqno + 1) & 0xffff;
+ // if (seqno != last_seqno)
+ // debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno);
+ last_seqno = seqno; // reset warning...
+ }
+ int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)), conn);
+
+ // if (packet[1]&0x10)
+ // debug(1,"Audio packet Extension bit set.");
+
+ pktp += 12;
+ plen -= 12;
+
+ // check if packet contains enough content to be reasonable
+ if (plen >= 16) {
+ player_put_packet(seqno, timestamp, pktp, plen, conn);
+ continue;
+ }
+ if (type == 0x56 && seqno == 0) {
+ debug(2, "resend-related request packet received, ignoring.");
+ continue;
+ }
+ debug(1, "Audio receiver -- Unknown RTP packet of type 0x%02X length %d seqno %d", type,
+ nread, seqno);
+ }
+ warn("Audio receiver -- Unknown RTP packet of type 0x%02X length %d.", type, nread);
+ }
+
+ debug(3, "Audio receiver -- Server RTP thread interrupted. terminating.");
+ close(conn->audio_socket);
+
+ return NULL;
+}
+
+void *rtp_control_receiver(void *arg) {
+ // we inherit the signal mask (SIGUSR1)
+
+ debug(2, "Control receiver -- Server RTP thread starting.");
+
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
+
+ conn->reference_timestamp = 0; // nothing valid received yet
+ uint8_t packet[2048], *pktp;
+ struct timespec tn;
+ uint64_t remote_time_of_sync, local_time_now, remote_time_now;
+ int64_t sync_rtp_timestamp, rtp_timestamp_less_latency;
+ ssize_t nread;
+ while (conn->please_stop == 0) {
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(conn->control_socket, &readfds);
+ do {
+ memory_barrier();
+ } while (conn->please_stop == 0 &&
+ pselect(conn->control_socket + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
+ if (conn->please_stop != 0) {
+ break;
+ }
+ 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)
+ break;
+
+ ssize_t plen = nread;
+ if (packet[1] == 0xd4) { // sync data
+ /*
+ char obf[4096];
+ char *obfp = obf;
+ int obfc;
+ for (obfc=0;obfclocal_to_remote_time_difference) { // need a time packet to be interchanged first...
+
+ remote_time_of_sync = (uint64_t)ntohl(*((uint32_t *)&packet[8])) << 32;
+ remote_time_of_sync += ntohl(*((uint32_t *)&packet[12]));
+
+ // debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync);
+
+ rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4])), conn);
+ sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])), conn);
+
+ if (config.use_negotiated_latencies) {
+ int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + conn->staticLatencyCorrection;
+ if (la != config.latency) {
+ config.latency = la;
+ debug(1,"Using negotiated latency of %lld frames and a static latency correction of %lld",sync_rtp_timestamp - rtp_timestamp_less_latency,conn->staticLatencyCorrection);
+ }
+ }
+
+ if (packet[0] & 0x10) {
+ // if it's a packet right after a flush or resume
+ sync_rtp_timestamp += 352; // add frame_size -- can't see a reference to this anywhere,
+ // but it seems to get everything into sync.
+ // it's as if the first sync after a flush or resume is the timing of the next packet
+ // after the one whose RTP is given. Weird.
+ }
+ pthread_mutex_lock(&conn->reference_time_mutex);
+ conn->remote_reference_timestamp_time = remote_time_of_sync;
+ conn->reference_timestamp_time =
+ remote_time_of_sync - conn->local_to_remote_time_difference;
+ conn->reference_timestamp = sync_rtp_timestamp;
+ pthread_mutex_unlock(&conn->reference_time_mutex);
+ // 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(1, "Sync packet received before we got a timing packet back.");
+ }
+ } else if (packet[1] == 0xd6) { // resent audio data in the control path -- whaale only?
+ // debug(1, "Control Port -- Retransmitted Audio Data Packet received.");
+ pktp = packet + 4;
+ plen -= 4;
+ seq_t seqno = ntohs(*(unsigned short *)(pktp + 2));
+
+ int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)), conn);
+
+ pktp += 12;
+ plen -= 12;
+
+ // check if packet contains enough content to be reasonable
+ if (plen >= 16) {
+ player_put_packet(seqno, timestamp, pktp, plen, conn);
+ continue;
+ } else {
+ debug(3, "Too-short retransmitted audio packet received in control port, ignored.");
+ }
+ } else
+ debug(1, "Control Port -- Unknown RTP packet of type 0x%02X length %d, ignored.", packet[1],
+ nread);
+ }
+
+ debug(3, "Control RTP thread interrupted. terminating.");
+ close(conn->control_socket);
+
+ return NULL;
+}
+
+void *rtp_timing_sender(void *arg) {
+ debug(2, "Timing sender thread starting.");
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
+ struct timing_request {
+ char leader;
+ char type;
+ uint16_t seqno;
+ uint32_t filler;
+ uint64_t origin, receive, transmit;
+ };
+
+ uint64_t request_number = 0;
+
+ struct timing_request req; // *not* a standard RTCP NACK
+
+ req.leader = 0x80;
+ req.type = 0xd2; // Timing request
+ req.filler = 0;
+ req.seqno = htons(7);
+
+ conn->time_ping_count = 0;
+
+ // we inherit the signal mask (SIGUSR1)
+ while (conn->timing_sender_stop == 0) {
+ // debug(1,"Send a timing request");
+
+ if (!conn->rtp_running)
+ debug(1, "rtp_timing_sender called without active stream in RTSP conversation thread %d!",
+ conn->connection_number);
+
+ // debug(1, "Requesting ntp timestamp exchange.");
+
+ req.filler = 0;
+ req.origin = req.receive = req.transmit = 0;
+
+ // clock_gettime(CLOCK_MONOTONIC,&dtt);
+ conn->departure_time = get_absolute_time_in_fp();
+ socklen_t msgsize = sizeof(struct sockaddr_in);
+#ifdef AF_INET6
+ if (conn->rtp_client_timing_socket.SAFAMILY == AF_INET6) {
+ msgsize = sizeof(struct sockaddr_in6);
+ }
+#endif
+ fd_set writefds;
+ FD_ZERO(&writefds);
+ FD_SET(conn->timing_socket, &writefds);
+ do {
+ memory_barrier();
+ } while (conn->timing_sender_stop == 0 &&
+ pselect(conn->timing_socket + 1, NULL, &writefds, NULL, NULL, &pselect_sigset) <= 0);
+ if (conn->timing_sender_stop != 0) {
+ break;
+ }
+ if (sendto(conn->timing_socket, &req, sizeof(req), 0,
+ (struct sockaddr *)&conn->rtp_client_timing_socket, msgsize) == -1) {
+ perror("Error sendto-ing to timing socket");
+ }
+ request_number++;
+ if (request_number <= 4)
+ usleep(500000);
+ else
+ sleep(3);
+ }
+ debug(3, "rtp_timing_sender thread interrupted. terminating.");
+ return NULL;
+}
+
+void *rtp_timing_receiver(void *arg) {
+ debug(2, "Timing receiver -- Server RTP thread starting.");
+ // we inherit the signal mask (SIGUSR1)
+ rtsp_conn_info *conn = (rtsp_conn_info *)arg;
+
+ uint8_t packet[2048], *pktp;
+ ssize_t nread;
+ conn->timing_sender_stop = 0;
+ pthread_t timer_requester;
+ pthread_create(&timer_requester, NULL, &rtp_timing_sender, arg);
+ // struct timespec att;
+ uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time, transit_time,
+ processing_time;
+ local_to_remote_time_jitters = 0;
+ local_to_remote_time_jitters_count = 0;
+ uint64_t first_remote_time = 0;
+ uint64_t first_local_time = 0;
+
+ uint64_t first_local_to_remote_time_difference = 0;
+ uint64_t first_local_to_remote_time_difference_time;
+ uint64_t l2rtd = 0;
+ while (conn->please_stop == 0) {
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(conn->timing_socket, &readfds);
+ do {
+ memory_barrier();
+ } while (conn->please_stop == 0 &&
+ pselect(conn->timing_socket + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
+ if (conn->please_stop != 0) {
+ break;
+ }
+ nread = recv(conn->timing_socket, packet, sizeof(packet), 0);
+ arrival_time = get_absolute_time_in_fp();
+ // clock_gettime(CLOCK_MONOTONIC,&att);
+
+ if (nread < 0)
+ break;
+
+ 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;
+
+ // uint64_t rtus = (return_time*1000000)>>32; debug(1,"Time ping turnaround time: %lld
+ // us.",rtus);
+
+ // distant_receive_time =
+ // ((uint64_t)ntohl(*((uint32_t*)&packet[16])))<<32+ntohl(*((uint32_t*)&packet[20]));
+
+ distant_receive_time = (uint64_t)ntohl(*((uint32_t *)&packet[16])) << 32;
+ distant_receive_time += ntohl(*((uint32_t *)&packet[20]));
+
+ // distant_transmit_time =
+ // ((uint64_t)ntohl(*((uint32_t*)&packet[24])))<<32+ntohl(*((uint32_t*)&packet[28]));
+
+ distant_transmit_time = (uint64_t)ntohl(*((uint32_t *)&packet[24])) << 32;
+ distant_transmit_time += ntohl(*((uint32_t *)&packet[28]));
+
+ processing_time = distant_transmit_time - distant_receive_time;
+
+ // debug(1,"Return trip time: %lluuS, remote processing time:
+ // %lluuS.",(return_time*1000000)>>32,(processing_time*1000000)>>32);
+
+ uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2;
+
+ unsigned int cc, chosen;
+ for (cc = time_ping_history - 1; cc > 0; cc--) {
+ conn->time_pings[cc] = conn->time_pings[cc - 1];
+ conn->time_pings[cc].dispersion = (conn->time_pings[cc].dispersion * 110) /
+ 100; // make the dispersions 'age' by this rational factor
+ }
+ // these are for diagnostics only -- not used
+ conn->time_pings[0].local_time = arrival_time;
+ conn->time_pings[0].remote_time = distant_transmit_time;
+
+ 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++;
+
+ 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 tld = conn->time_pings[0].dispersion;
+ chosen = 0;
+ for (cc = 1; cc < conn->time_ping_count; cc++)
+ if (conn->time_pings[cc].dispersion < tld) {
+ l2rtd = conn->time_pings[cc].local_to_remote_difference;
+ chosen = cc;
+ tld = conn->time_pings[cc].dispersion;
+ local_time_chosen = conn->time_pings[cc].local_time;
+ remote_time_chosen = conn->time_pings[cc].remote_time;
+ }
+ int64_t ji;
+
+ if (conn->time_ping_count > 1) {
+ if (l2rtd > conn->local_to_remote_time_difference) {
+ local_to_remote_time_jitters =
+ local_to_remote_time_jitters + l2rtd - conn->local_to_remote_time_difference;
+ ji = l2rtd - conn->local_to_remote_time_difference;
+ } else {
+ local_to_remote_time_jitters =
+ local_to_remote_time_jitters + conn->local_to_remote_time_difference - l2rtd;
+ ji = -(conn->local_to_remote_time_difference - l2rtd);
+ }
+ local_to_remote_time_jitters_count += 1;
+ }
+ // uncomment below to print jitter between client's clock and oour clock
+ // int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time difference
+ // with dispersion of %lld us with delta of %lld us",rtus,ji);
+
+ conn->local_to_remote_time_difference = l2rtd;
+ if (first_local_to_remote_time_difference == 0) {
+ first_local_to_remote_time_difference = conn->local_to_remote_time_difference;
+ first_local_to_remote_time_difference_time = get_absolute_time_in_fp();
+ }
+
+ int64_t clock_drift, clock_drift_in_usec;
+ double clock_drift_ppm = 0.0;
+ if (first_local_time == 0) {
+ first_local_time = local_time_chosen;
+ first_remote_time = remote_time_chosen;
+ clock_drift = 0;
+ } else {
+ uint64_t local_time_change = local_time_chosen - first_local_time;
+ uint64_t remote_time_change = remote_time_chosen - first_remote_time;
+
+ if (remote_time_change >= local_time_change)
+ clock_drift = remote_time_change - local_time_change;
+ else
+ clock_drift = -(local_time_change - remote_time_change);
+ if (clock_drift >= 0)
+ clock_drift_in_usec = (clock_drift * 1000000) >> 32;
+ else
+ clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32);
+ clock_drift_ppm = (1.0 * clock_drift_in_usec) / (local_time_change >> 32);
+ }
+
+ int64_t source_drift_usec;
+ if (conn->play_segment_reference_frame != 0) {
+ 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);
+ uint64_t frame_difference = 0;
+ if (reference_timestamp >= conn->play_segment_reference_frame)
+ frame_difference =
+ (uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame;
+ else // rollover
+ frame_difference = (uint64_t)reference_timestamp + 0x100000000 -
+ (uint64_t)conn->play_segment_reference_frame;
+ uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference << 32) / 44100);
+ uint64_t frame_time_difference_actual =
+ remote_reference_timestamp_time -
+ conn->play_segment_reference_frame_remote_time; // this is all done by reference to the
+ // sources' system clock
+ // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec
+ // actual",frame_difference, (frame_time_difference_calculated*1000000)>>32,
+ // (frame_time_difference_actual*1000000)>>32);
+ if (frame_time_difference_calculated >=
+ frame_time_difference_actual) // i.e. if the time it should have taken to send the
+ // packets is greater than the actual time difference
+ // measured on the source clock
+ // then the source DAC's clock is running fast relative to the source system clock
+ source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual;
+ else
+ // otherwise the source DAC's clock is running slow relative to the source system clock
+ source_drift_usec = -(frame_time_difference_actual - frame_time_difference_calculated);
+ } else
+ source_drift_usec = 0;
+ source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds
+
+ // long current_delay = 0;
+ // if (config.output->delay) {
+ // config.output->delay(¤t_delay);
+ //}
+ // Useful for troubleshooting:
+ // debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec %10.1lld\treturn_time_in_usec
+ // %10.1llu",
+ // clock_drift_ppm,
+ // chosen,
+ //(session_corrections*1000000)/44100,
+ // current_delay,
+ // source_drift_usec,
+ // buffer_occupancy,
+ //(return_time*1000000)>>32);
+
+ } else {
+ debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread);
+ }
+ }
+
+ debug(3, "Timing thread interrupted. terminating.");
+ conn->timing_sender_stop = 1;
+ void *retval;
+ pthread_kill(timer_requester, SIGUSR1);
+ debug(3, "Wait for timer requester to exit.");
+ pthread_join(timer_requester, &retval);
+ debug(3, "Closed and terminated timer requester thread.");
+ debug(3, "Timing RTP thread terminated.");
+ close(conn->timing_socket);
+
+ return NULL;
+}
+
+static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id, int *sock) {
+ // look for a port in the range, if any was specified.
+ int desired_port = config.udp_port_base;
+ int ret;
+
+ int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP);
+ if (local_socket == -1)
+ die("Could not allocate a socket.");
+ SOCKADDR myaddr;
+ do {
+ memset(&myaddr, 0, sizeof(myaddr));
+ if (ip_family == AF_INET) {
+ struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr;
+ sa->sin_family = AF_INET;
+ sa->sin_port = ntohs(desired_port);
+ inet_pton(AF_INET, self_ip_address, &(sa->sin_addr));
+ ret = bind(local_socket, (struct sockaddr *)sa, sizeof(struct sockaddr_in));
+ }
+#ifdef AF_INET6
+ if (ip_family == AF_INET6) {
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&myaddr;
+ sa6->sin6_family = AF_INET6;
+ sa6->sin6_port = ntohs(desired_port);
+ inet_pton(AF_INET6, self_ip_address, &(sa6->sin6_addr));
+ sa6->sin6_scope_id = scope_id;
+ ret = bind(local_socket, (struct sockaddr *)sa6, sizeof(struct sockaddr_in6));
+ }
+#endif
+
+ } while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) &&
+ (++desired_port < config.udp_port_base + config.udp_port_range));
+
+ // debug(1,"UDP port chosen: %d.",desired_port);
+
+ if (ret < 0) {
+ close(local_socket);
+ die("error: could not bind a UDP port! Check the udp_port_range is large enough (>= 10) or "
+ "check for restrictive firewall settings or a bad router!");
+ }
+
+ int sport;
+ SOCKADDR local;
+ socklen_t local_len = sizeof(local);
+ getsockname(local_socket, (struct sockaddr *)&local, &local_len);
+#ifdef AF_INET6
+ if (local.SAFAMILY == AF_INET6) {
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&local;
+ sport = ntohs(sa6->sin6_port);
+ } else
+#endif
+ {
+ struct sockaddr_in *sa = (struct sockaddr_in *)&local;
+ sport = ntohs(sa->sin_port);
+ }
+ fcntl(local_socket, F_SETFL, O_NONBLOCK);
+
+ *sock = local_socket;
+ return sport;
+}
+
+void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, int *lsport, int *lcport,
+ int *ltport, rtsp_conn_info *conn) {
+
+ // this gets the local and remote ip numbers (and ports used for the TCD stuff)
+ // we use the local stuff to specify the address we are coming from and
+ // we use the remote stuff to specify where we're goint to
+
+ if (conn->rtp_running)
+ die("rtp_setup called with active stream!");
+
+ debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport);
+
+ // print out what we know about the client
+ void *client_addr, *self_addr;
+ int client_port, self_port;
+ char client_port_str[64];
+ char self_addr_str[64];
+
+ conn->connection_ip_family =
+ remote->SAFAMILY; // keep information about the kind of ip of the client
+
+#ifdef AF_INET6
+ if (conn->connection_ip_family == AF_INET6) {
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote;
+ client_addr = &(sa6->sin6_addr);
+ client_port = ntohs(sa6->sin6_port);
+ sa6 = (struct sockaddr_in6 *)local;
+ self_addr = &(sa6->sin6_addr);
+ self_port = ntohs(sa6->sin6_port);
+ conn->self_scope_id = sa6->sin6_scope_id;
+ }
+#endif
+ if (conn->connection_ip_family == AF_INET) {
+ struct sockaddr_in *sa4 = (struct sockaddr_in *)remote;
+ client_addr = &(sa4->sin_addr);
+ client_port = ntohs(sa4->sin_port);
+ sa4 = (struct sockaddr_in *)local;
+ self_addr = &(sa4->sin_addr);
+ self_port = ntohs(sa4->sin_port);
+ }
+
+ inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string,
+ sizeof(conn->client_ip_string));
+ inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string,
+ sizeof(conn->self_ip_string));
+
+ debug(1, "Set up play connection from %s to self at %s on RTSP conversation thread %d.",
+ conn->client_ip_string, conn->self_ip_string, conn->connection_number);
+
+ // set up a the record of the remote's control socket
+ struct addrinfo hints;
+ struct addrinfo *servinfo;
+
+ memset(&conn->rtp_client_control_socket, 0, sizeof(conn->rtp_client_control_socket));
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = conn->connection_ip_family;
+ hints.ai_socktype = SOCK_DGRAM;
+ char portstr[20];
+ snprintf(portstr, 20, "%d", cport);
+ if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0)
+ die("Can't get address of client's control port");
+
+#ifdef AF_INET6
+ if (servinfo->ai_family == AF_INET6) {
+ memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
+ // ensure the scope id matches that of remote. this is needed for link-local addresses.
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_control_socket;
+ sa6->sin6_scope_id = conn->self_scope_id;
+ } else
+#endif
+ memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
+ freeaddrinfo(servinfo);
+
+ // set up a the record of the remote's timing socket
+ memset(&conn->rtp_client_timing_socket, 0, sizeof(conn->rtp_client_timing_socket));
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = conn->connection_ip_family;
+ hints.ai_socktype = SOCK_DGRAM;
+ snprintf(portstr, 20, "%d", tport);
+ if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0)
+ die("Can't get address of client's timing port");
+#ifdef AF_INET6
+ if (servinfo->ai_family == AF_INET6) {
+ memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
+ // ensure the scope id matches that of remote. this is needed for link-local addresses.
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_timing_socket;
+ sa6->sin6_scope_id = conn->self_scope_id;
+ } else
+#endif
+ memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
+ freeaddrinfo(servinfo);
+
+ // now, we open three sockets -- one for the audio stream, one for the timing and one for the
+ // control
+
+ *lsport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
+ &conn->audio_socket);
+ *lcport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
+ &conn->control_socket);
+ *ltport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
+ &conn->timing_socket);
+
+ debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport,
+ *ltport);
+
+ 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;
+}
+
+void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
+ uint64_t *remote_timestamp_time, rtsp_conn_info *conn) {
+ // types okay
+ pthread_mutex_lock(&conn->reference_time_mutex);
+ *timestamp = conn->reference_timestamp;
+ *timestamp_time = conn->reference_timestamp_time;
+ // if ((*timestamp == 0) && (*timestamp_time == 0)) {
+ // debug(1,"Reference timestamp is invalid.");
+ //}
+ *remote_timestamp_time = conn->remote_reference_timestamp_time;
+ pthread_mutex_unlock(&conn->reference_time_mutex);
+}
+
+void clear_reference_timestamp(rtsp_conn_info *conn) {
+ pthread_mutex_lock(&conn->reference_time_mutex);
+ conn->reference_timestamp = 0;
+ conn->reference_timestamp_time = 0;
+ pthread_mutex_unlock(&conn->reference_time_mutex);
+}
+
+void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
+ if (conn->rtp_running) {
+ // if (!request_sent) {
+ debug(3, "requesting resend of %d packets starting at %u.", count, first);
+ // request_sent = 1;
+ //}
+
+ char req[8]; // *not* a standard RTCP NACK
+ req[0] = 0x80;
+ req[1] = (char)0x55 | (char)0x80; // Apple 'resend'
+ *(unsigned short *)(req + 2) = htons(1); // our seqnum
+ *(unsigned short *)(req + 4) = htons(first); // missed seqnum
+ *(unsigned short *)(req + 6) = htons(count); // count
+ socklen_t msgsize = sizeof(struct sockaddr_in);
+#ifdef AF_INET6
+ if (conn->rtp_client_control_socket.SAFAMILY == AF_INET6) {
+ msgsize = sizeof(struct sockaddr_in6);
+ }
+#endif
+ if (sendto(conn->audio_socket, req, sizeof(req), 0,
+ (struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) {
+ perror("Error sendto-ing to audio socket");
+ }
+ } else {
+ // if (!request_sent) {
+ debug(2, "rtp_request_resend called without active stream!");
+ // request_sent = 1;
+ //}
+ }
+}
diff --git a/rtp.h b/rtp.h
new file mode 100644
index 0000000..641d962
--- /dev/null
+++ b/rtp.h
@@ -0,0 +1,28 @@
+#ifndef _RTP_H
+#define _RTP_H
+
+#include
+
+#include "player.h"
+
+void rtp_initialise(rtsp_conn_info *conn);
+void rtp_terminate(rtsp_conn_info *conn);
+
+void *rtp_audio_receiver(void *arg);
+void *rtp_control_receiver(void *arg);
+void *rtp_timing_receiver(void *arg);
+
+void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int controlport, int timingport,
+ int *local_server_port, int *local_control_port, int *local_timing_port,
+ rtsp_conn_info *conn);
+void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn);
+void rtp_request_client_pause(rtsp_conn_info *conn); // ask the client to pause
+
+void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
+ uint64_t *remote_timestamp_time, rtsp_conn_info *conn);
+void clear_reference_timestamp(rtsp_conn_info *conn);
+
+uint64_t static local_to_remote_time_jitters;
+uint64_t static local_to_remote_time_jitters_count;
+
+#endif // _RTP_H
diff --git a/rtsp.c b/rtsp.c
new file mode 100644
index 0000000..41a5a85
--- /dev/null
+++ b/rtsp.c
@@ -0,0 +1,2103 @@
+/*
+ * RTSP protocol handler. This file is part of Shairport.
+ * Copyright (c) James Laird 2013
+ * Modifications associated with audio synchronization, mutithreading and
+ * metadata handling copyright (c) Mike Brady 2014-2017
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+
+#ifdef HAVE_LIBSSL
+#include
+#endif
+
+#ifdef HAVE_LIBMBEDTLS
+#include
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+#include
+#endif
+
+#include "common.h"
+#include "player.h"
+#include "rtp.h"
+#include "rtsp.h"
+
+#ifdef AF_INET6
+#define INETx_ADDRSTRLEN INET6_ADDRSTRLEN
+#else
+#define INETx_ADDRSTRLEN INET_ADDRSTRLEN
+#endif
+
+#define METADATA_SNDBUF (4 * 1024 * 1024)
+
+enum rtsp_read_request_response {
+ rtsp_read_request_response_ok,
+ rtsp_read_request_response_immediate_shutdown_requested,
+ rtsp_read_request_response_bad_packet,
+ rtsp_read_request_response_channel_closed,
+ rtsp_read_request_response_error
+};
+
+// Mike Brady's part...
+static pthread_mutex_t barrier_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t play_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;
+
+// only one thread is allowed to use the player at once.
+// it monitors the request variable (at least when interrupted)
+// static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER;
+// static int please_shutdown = 0;
+// static pthread_t playing_thread = 0;
+
+static rtsp_conn_info **conns = NULL;
+
+int RTSP_connection_index = 0;
+
+void memory_barrier() {
+ pthread_mutex_lock(&barrier_mutex);
+ pthread_mutex_unlock(&barrier_mutex);
+}
+
+#ifdef CONFIG_METADATA
+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
+ uint32_t count; // number of items in the queue
+ uint32_t capacity; // maximum number of items
+ uint32_t toq; // first item to take
+ uint32_t eoq; // free space at end of queue
+ void *items; // a pointer to where the items are actually stored
+} pc_queue; // producer-consumer queue
+#endif
+
+typedef struct {
+ uint32_t referenceCount; // we might start using this...
+ int nheaders;
+ char *name[16];
+ char *value[16];
+
+ int contentlength;
+ char *content;
+
+ // for requests
+ char method[16];
+
+ // for responses
+ int respcode;
+} rtsp_message;
+
+#ifdef CONFIG_METADATA
+typedef struct {
+ uint32_t type;
+ uint32_t code;
+ char *data;
+ uint32_t length;
+ rtsp_message *carrier;
+} metadata_package;
+
+void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) {
+ 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);
+ the_queue->item_size = item_size;
+ the_queue->items = items;
+ the_queue->count = 0;
+ the_queue->capacity = number_of_items;
+ the_queue->toq = 0;
+ the_queue->eoq = 0;
+}
+
+int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
+ int block);
+
+int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) {
+ return send_metadata('ssnc', code, data, length, NULL, block);
+}
+
+int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
+ int rc;
+ if (the_queue) {
+ if (block == 0) {
+ rc = pthread_mutex_trylock(&the_queue->pc_queue_lock);
+ if (rc == EBUSY)
+ return EBUSY;
+ } else
+ rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
+ if (rc)
+ debug(1, "Error locking for pc_queue_add_item");
+ 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);
+
+ // 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!");
+ rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
+ if (rc)
+ debug(1, "Error signalling after pc_queue_add_item");
+ rc = pthread_mutex_unlock(&the_queue->pc_queue_lock);
+ if (rc)
+ debug(1, "Error unlocking for pc_queue_add_item");
+ } else {
+ debug(1, "Adding an item to a NULL queue");
+ }
+ return 0;
+}
+
+int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
+ int rc;
+ if (the_queue) {
+ rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
+ if (rc)
+ debug(1, "Error locking for pc_queue_get_item");
+ 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");
+ }
+ uint32_t i = the_queue->toq;
+ // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq;
+ void *p = the_queue->items + the_queue->item_size * i;
+ memcpy(the_stuff, p, the_queue->item_size);
+
+ // update the pointer
+ i++;
+ if (i == the_queue->capacity)
+ // fold pointer if necessary
+ i = 0;
+ the_queue->toq = i;
+ the_queue->count--;
+ rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal);
+ if (rc)
+ debug(1, "Error signalling after pc_queue_removed_item");
+ rc = pthread_mutex_unlock(&the_queue->pc_queue_lock);
+ if (rc)
+ debug(1, "Error unlocking for pc_queue_get_item");
+ } else {
+ debug(1, "Removing an item from a NULL queue");
+ }
+ return 0;
+}
+
+#endif
+
+void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread);
+
+void rtsp_request_shutdown_stream(void) {
+ debug(1, "Request to shut down all rtsp conversation threads");
+ ask_other_rtsp_conversation_threads_to_stop(0); // i.e. ask all playing threads to stop
+}
+
+// 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.");
+ }
+}
+
+static void cleanup_threads(void) {
+ void *retval;
+ int i;
+ // debug(2, "culling threads.");
+ for (i = 0; i < nconns;) {
+ if (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);
+ if (conns[i] == playing_conn)
+ playing_conn = NULL;
+ free(conns[i]);
+ nconns--;
+ if (nconns)
+ conns[i] = conns[nconns];
+ } else {
+ i++;
+ }
+ }
+}
+
+// ask all rtsp_conversation threads to stop -- there should be at most one, but
+// ya never know.
+
+void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread) {
+ int i;
+ debug(1, "asking playing threads to stop");
+ for (i = 0; i < nconns; i++) {
+ if (((except_this_thread == 0) || (pthread_equal(conns[i]->thread, except_this_thread) == 0)) &&
+ (conns[i]->running != 0)) {
+ conns[i]->stop = 1;
+ pthread_kill(conns[i]->thread, SIGUSR1);
+ }
+ }
+}
+
+// park a null at the line ending, and return the next line pointer
+// accept \r, \n, or \r\n
+static char *nextline(char *in, int inbuf) {
+ char *out = NULL;
+ while (inbuf) {
+ if (*in == '\r') {
+ *in++ = 0;
+ out = in;
+ }
+ if (*in == '\n') {
+ *in++ = 0;
+ out = in;
+ }
+
+ if (out)
+ break;
+
+ in++;
+ inbuf--;
+ }
+ return out;
+}
+
+static void msg_retain(rtsp_message *msg) {
+ if (msg) {
+ int rc = pthread_mutex_lock(&reference_counter_lock);
+ if (rc)
+ debug(1, "Error %d locking reference counter lock");
+ msg->referenceCount++;
+ rc = pthread_mutex_unlock(&reference_counter_lock);
+ if (rc)
+ debug(1, "Error %d unlocking reference counter lock");
+ } else {
+ debug(1, "null rtsp_message pointer passed to retain");
+ }
+}
+
+static rtsp_message *msg_init(void) {
+ rtsp_message *msg = malloc(sizeof(rtsp_message));
+ if (msg) {
+ memset(msg, 0, sizeof(rtsp_message));
+ msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
+ } else {
+ die("can not allocate memory for an rtsp_message.");
+ }
+ return msg;
+}
+
+static int msg_add_header(rtsp_message *msg, char *name, char *value) {
+ if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) {
+ warn("too many headers?!");
+ return 1;
+ }
+
+ msg->name[msg->nheaders] = strdup(name);
+ msg->value[msg->nheaders] = strdup(value);
+ msg->nheaders++;
+
+ return 0;
+}
+
+static char *msg_get_header(rtsp_message *msg, char *name) {
+ int i;
+ for (i = 0; i < msg->nheaders; i++)
+ if (!strcasecmp(msg->name[i], name))
+ return msg->value[i];
+ return NULL;
+}
+
+static void debug_print_msg_headers(int level, rtsp_message *msg) {
+ int i;
+ for (i = 0; i < msg->nheaders; i++) {
+ debug(level, " Type: \"%s\", content: \"%s\"", msg->name[i], msg->value[i]);
+ }
+}
+
+static void msg_free(rtsp_message *msg) {
+
+ if (msg) {
+ int rc = pthread_mutex_lock(&reference_counter_lock);
+ if (rc)
+ debug(1, "Error %d locking reference counter lock during msg_free()", rc);
+ msg->referenceCount--;
+ rc = pthread_mutex_unlock(&reference_counter_lock);
+ if (rc)
+ debug(1, "Error %d unlocking reference counter lock during msg_free()", rc);
+ if (msg->referenceCount == 0) {
+ int i;
+ for (i = 0; i < msg->nheaders; i++) {
+ free(msg->name[i]);
+ free(msg->value[i]);
+ }
+ if (msg->content)
+ free(msg->content);
+ free(msg);
+ } // else {
+ // debug(1,"rtsp_message reference count non-zero:
+ // %d!",msg->referenceCount);
+ //}
+ } else {
+ debug(1, "null rtsp_message pointer passed to msg_free()");
+ }
+}
+
+static int msg_handle_line(rtsp_message **pmsg, char *line) {
+ rtsp_message *msg = *pmsg;
+
+ if (!msg) {
+ msg = msg_init();
+ *pmsg = msg;
+ char *sp, *p;
+
+ // debug(1, "received request: %s", line);
+
+ p = strtok_r(line, " ", &sp);
+ if (!p)
+ goto fail;
+ strncpy(msg->method, p, sizeof(msg->method) - 1);
+
+ p = strtok_r(NULL, " ", &sp);
+ if (!p)
+ goto fail;
+
+ p = strtok_r(NULL, " ", &sp);
+ if (!p)
+ goto fail;
+ if (strcmp(p, "RTSP/1.0"))
+ goto fail;
+
+ return -1;
+ }
+
+ if (strlen(line)) {
+ char *p;
+ p = strstr(line, ": ");
+ if (!p) {
+ warn("bad header: >>%s<<", line);
+ goto fail;
+ }
+ *p = 0;
+ p += 2;
+ msg_add_header(msg, line, p);
+ debug(3, " %s: %s.", line, p);
+ return -1;
+ } else {
+ char *cl = msg_get_header(msg, "Content-Length");
+ if (cl)
+ return atoi(cl);
+ else
+ return 0;
+ }
+
+fail:
+ *pmsg = NULL;
+ msg_free(msg);
+ return 0;
+}
+
+static enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn,
+ rtsp_message **the_packet) {
+ enum rtsp_read_request_response reply = rtsp_read_request_response_ok;
+ ssize_t buflen = 512;
+ char *buf = malloc(buflen + 1);
+
+ rtsp_message *msg = NULL;
+
+ ssize_t nread;
+ ssize_t inbuf = 0;
+ int msg_size = -1;
+
+ while (msg_size < 0) {
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(conn->fd, &readfds);
+ do {
+ memory_barrier();
+ } while (conn->stop == 0 &&
+ pselect(conn->fd + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
+ if (conn->stop != 0) {
+ debug(3, "RTSP conversation thread %d shutdown requested.", conn->connection_number);
+ reply = rtsp_read_request_response_immediate_shutdown_requested;
+ goto shutdown;
+ }
+ nread = read(conn->fd, buf + inbuf, buflen - inbuf);
+
+ if (nread == 0) {
+ // a blocking read that returns zero means eof -- implies connection closed
+ debug(3, "RTSP conversation thread %d -- connection closed.", conn->connection_number);
+ reply = rtsp_read_request_response_channel_closed;
+ goto shutdown;
+ }
+
+ if (nread < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("read failure");
+ reply = rtsp_read_request_response_channel_closed;
+ goto shutdown;
+ }
+ inbuf += nread;
+
+ char *next;
+ while (msg_size < 0 && (next = nextline(buf, inbuf))) {
+ msg_size = msg_handle_line(&msg, buf);
+
+ if (!msg) {
+ warn("no RTSP header received");
+ reply = rtsp_read_request_response_bad_packet;
+ goto shutdown;
+ }
+
+ inbuf -= next - buf;
+ if (inbuf)
+ memmove(buf, next, inbuf);
+ }
+ }
+
+ if (msg_size > buflen) {
+ buf = realloc(buf, msg_size);
+ if (!buf) {
+ warn("too much content");
+ reply = rtsp_read_request_response_error;
+ goto shutdown;
+ }
+ buflen = msg_size;
+ }
+
+ uint64_t threshold_time =
+ get_absolute_time_in_fp() + ((uint64_t)5 << 32); // i.e. five seconds from now
+ int warning_message_sent = 0;
+
+ const size_t max_read_chunk = 50000;
+ while (inbuf < msg_size) {
+
+ // we are going to read the stream in chunks and time how long it takes to
+ // do so.
+ // If it's taking too long, (and we find out about it), we will send an
+ // error message as
+ // metadata
+
+ if (warning_message_sent == 0) {
+ uint64_t time_now = get_absolute_time_in_fp();
+ if (time_now > threshold_time) { // it's taking too long
+ debug(1, "Error receiving metadata from source -- transmission seems "
+ "to be stalled.");
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('stal', NULL, 0, 1);
+#endif
+ warning_message_sent = 1;
+ }
+ }
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(conn->fd, &readfds);
+ do {
+ memory_barrier();
+ } while (conn->stop == 0 &&
+ pselect(conn->fd + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
+ if (conn->stop != 0) {
+ debug(1, "RTSP shutdown requested.");
+ reply = rtsp_read_request_response_immediate_shutdown_requested;
+ goto shutdown;
+ }
+ ssize_t read_chunk = msg_size - inbuf;
+ if (read_chunk > max_read_chunk)
+ read_chunk = max_read_chunk;
+ nread = read(conn->fd, buf + inbuf, read_chunk);
+ if (!nread) {
+ reply = rtsp_read_request_response_error;
+ goto shutdown;
+ }
+ if (nread < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("read failure");
+ reply = rtsp_read_request_response_error;
+ goto shutdown;
+ }
+ inbuf += nread;
+ }
+
+ msg->contentlength = inbuf;
+ msg->content = buf;
+ *the_packet = msg;
+ return reply;
+
+shutdown:
+ if (msg) {
+ msg_free(msg); // which will free the content and everything else
+ }
+ // in case the message wasn't formed or wasn't fully initialised
+ if ((msg && (msg->content == NULL)) || (!msg))
+ free(buf);
+ *the_packet = NULL;
+ return reply;
+}
+
+static void msg_write_response(int fd, rtsp_message *resp) {
+ char pkt[1024];
+ int pktfree = sizeof(pkt);
+ char *p = pkt;
+ int i, n;
+
+ n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode,
+ resp->respcode == 200 ? "OK" : "Unauthorized");
+ // debug(1, "sending response: %s", pkt);
+ pktfree -= n;
+ p += n;
+
+ for (i = 0; i < resp->nheaders; i++) {
+ // debug(3, " %s: %s.", resp->name[i], resp->value[i]);
+ n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]);
+ pktfree -= n;
+ p += n;
+ if (pktfree <= 0)
+ die("Attempted to write overlong RTSP packet");
+ }
+
+ if (pktfree < 3)
+ die("Attempted to write overlong RTSP packet");
+
+ strcpy(p, "\r\n");
+ int ignore = write(fd, pkt, p - pkt + 2);
+}
+
+static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+ debug(3, "Connection %d: RECORD", conn->connection_number);
+ resp->respcode = 200;
+ // I think this is for telling the client what the absolute minimum latency
+ // actually is,
+ // and when the client specifies a latency, it should be added to this figure.
+
+ // Thus, AirPlay's latency figure of 77175, when added to 11025 gives you
+ // exactly 88200
+ // and iTunes' latency figure of 88553, when added to 11025 gives you 99578,
+ // pretty close to the 99400 we guessed.
+
+ msg_add_header(resp, "Audio-Latency", "11025");
+
+ char *p;
+ uint32_t rtptime = 0;
+ char *hdr = msg_get_header(req, "RTP-Info");
+
+ if (hdr) {
+ // debug(1,"FLUSH message received: \"%s\".",hdr);
+ // get the rtp timestamp
+ p = strstr(hdr, "rtptime=");
+ if (p) {
+ p = strchr(p, '=');
+ if (p) {
+ rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1
+ rtptime--;
+ // debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime);
+ player_flush(rtptime, conn);
+ }
+ }
+ }
+ usleep(500000);
+}
+
+static void handle_options(rtsp_conn_info *conn, rtsp_message *req, 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");
+}
+
+static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+ debug(3, "Connection %d: TEARDOWN", conn->connection_number);
+ // if (!rtsp_playing())
+ // debug(1, "This RTSP connection thread (%d) doesn't think it's playing, but "
+ // "it's sending a response to teardown anyway",conn->connection_number);
+ resp->respcode = 200;
+ msg_add_header(resp, "Connection", "close");
+
+ debug(3,
+ "TEARDOWN: synchronously terminating the player thread of RTSP conversation thread %d (2).",
+ conn->connection_number);
+ // if (rtsp_playing()) {
+ player_stop(conn);
+ debug(3, "TEARDOWN: successful termination of playing thread of RTSP conversation thread %d.",
+ conn->connection_number);
+ //}
+}
+
+static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+ debug(3, "Connection %d: FLUSH", conn->connection_number);
+ // if (!rtsp_playing())
+ // debug(1, "This RTSP conversation thread (%d) doesn't think it's playing, but "
+ // "it's sending a response to flush anyway",conn->connection_number);
+ char *p = NULL;
+ uint32_t rtptime = 0;
+ char *hdr = msg_get_header(req, "RTP-Info");
+
+ if (hdr) {
+ // debug(1,"FLUSH message received: \"%s\".",hdr);
+ // get the rtp timestamp
+ p = strstr(hdr, "rtptime=");
+ if (p) {
+ p = strchr(p, '=');
+ if (p)
+ rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1
+ }
+ }
+// debug(1,"RTSP Flush Requested: %u.",rtptime);
+#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;
+}
+
+static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+ debug(3, "Connection %d: SETUP", conn->connection_number);
+ int cport, tport;
+ int lsport, lcport, ltport;
+
+ char *ar = msg_get_header(req, "Active-Remote");
+ if (ar) {
+ debug(1, "Active-Remote string seen: \"%s\".", ar);
+ // get the active remote
+ char *p;
+ conn->dacp_active_remote = strtoul(ar, &p, 10);
+#ifdef CONFIG_METADATA
+ send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1);
+#endif
+ }
+
+ ar = msg_get_header(req, "DACP-ID");
+ if (ar) {
+ debug(1, "DACP-ID string seen: \"%s\".", ar);
+ conn->dacp_id = strdup(ar);
+#ifdef CONFIG_METADATA
+ send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1);
+#endif
+ }
+
+ // This latency-setting mechanism is deprecated and will be removed.
+ // If no non-standard latency is chosen, automatic negotiated latency setting
+ // is permitted.
+
+ // Select a static latency
+ // if iTunes V10 or later is detected, use the iTunes latency setting
+ // if AirPlay is detected, use the AirPlay latency setting
+ // for everything else, use the general latency setting, if given, or
+ // else use the default latency setting
+
+ config.latency = -1;
+
+ if (config.userSuppliedLatency)
+ config.latency = config.userSuppliedLatency;
+
+ char *ua = msg_get_header(req, "User-Agent");
+ if (ua == 0) {
+ debug(1, "No User-Agent string found in the SETUP message. Using latency "
+ "of %d frames.",
+ config.latency);
+ } else {
+ if (strstr(ua, "iTunes") == ua) {
+ int iTunesVersion = 0;
+ // now check it's version 10 or later
+ char *pp = strchr(ua, '/') + 1;
+ if (pp)
+ iTunesVersion = atoi(pp);
+ else
+ debug(2, "iTunes Version Number not found.");
+ if (iTunesVersion >= 10) {
+ debug(1, "User-Agent is iTunes 10 or better, (actual version is %d); "
+ "selecting the iTunes "
+ "latency of %d frames.",
+ iTunesVersion, config.iTunesLatency);
+ config.latency = config.iTunesLatency;
+ conn->staticLatencyCorrection = 11025;
+ }
+ } else if (strstr(ua, "AirPlay") == ua) {
+ debug(2, "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.",
+ config.AirPlayLatency);
+ config.latency = config.AirPlayLatency;
+ } else if (strstr(ua, "forked-daapd") == ua) {
+ debug(2, "User-Agent is forked-daapd; selecting the forked-daapd latency "
+ "of %d frames.",
+ config.ForkedDaapdLatency);
+ config.latency = config.ForkedDaapdLatency;
+ conn->staticLatencyCorrection = 11025;
+ } else {
+ debug(2, "Unrecognised User-Agent. Using latency of %d frames.", config.latency);
+ }
+ }
+
+ if (config.latency == -1) {
+ // this means that no static latency was set, so we'll allow it to be set
+ // dynamically
+ config.latency = 88198; // to be sure, to be sure -- make it slighty
+ // different from the default to ensure we get a
+ // debug message when set to 88200
+ config.use_negotiated_latencies = 1;
+ }
+ char *hdr = msg_get_header(req, "Transport");
+ if (!hdr)
+ goto error;
+
+ char *p;
+ p = strstr(hdr, "control_port=");
+ if (!p)
+ goto error;
+ p = strchr(p, '=') + 1;
+ cport = atoi(p);
+
+ p = strstr(hdr, "timing_port=");
+ if (!p)
+ goto error;
+ p = strchr(p, '=') + 1;
+ tport = atoi(p);
+
+ // rtsp_take_player();
+ rtp_setup(&conn->local, &conn->remote, cport, tport, &lsport, &lcport, <port, conn);
+ if (!lsport)
+ goto error;
+ char *q;
+ p = strstr(hdr, "control_port=");
+ if (p) {
+ q = strchr(p, ';'); // get past the control port entry
+ *p++ = 0;
+ if (q++)
+ strcat(hdr, q); // should unsplice the control port entry
+ }
+ p = strstr(hdr, "timing_port=");
+ if (p) {
+ q = strchr(p, ';'); // get past the timing port entry
+ *p++ = 0;
+ if (q++)
+ strcat(hdr, q); // should unsplice the timing port entry
+ }
+
+ player_play(conn); // the thread better be 0
+
+ char *resphdr = alloca(200);
+ *resphdr = 0;
+ sprintf(resphdr, "RTP/AVP/"
+ "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;"
+ "timing_port=%d;server_"
+ "port=%d",
+ lcport, ltport, lsport);
+
+ msg_add_header(resp, "Transport", resphdr);
+
+ msg_add_header(resp, "Session", "1");
+
+ resp->respcode = 200;
+ return;
+
+error:
+ warn("Error in setup request -- unlocking play lock on RTSP conversation thread %d.",
+ conn->connection_number);
+ playing_conn = NULL;
+ pthread_mutex_unlock(&play_lock);
+ resp->respcode = 451; // invalid arguments
+}
+
+static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+ debug(1, "Connection thread %d: IGNORE", conn->connection_number);
+ resp->respcode = 200;
+}
+
+static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
+ rtsp_message *resp) {
+ char *cp = req->content;
+ int cp_left = req->contentlength;
+ char *next;
+ while (cp_left && cp) {
+ next = nextline(cp, cp_left);
+ cp_left -= next - cp;
+
+ if (!strncmp(cp, "volume: ", 8)) {
+ float volume = atof(cp + 8);
+ debug(3, "AirPlay request to set volume to: %f.", volume);
+ player_volume(volume, conn);
+ } else
+#ifdef CONFIG_METADATA
+ if (!strncmp(cp, "progress: ", 10)) {
+ char *progress = cp + 10;
+ // debug(2, "progress: \"%s\"\n",
+ // progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second
+ send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1);
+ } else
+#endif
+ {
+ debug(1, "unrecognised parameter: \"%s\" (%d)\n", cp, strlen(cp));
+ }
+ cp = next;
+ }
+}
+
+#ifdef CONFIG_METADATA
+// Metadata is not used by shairport-sync.
+// Instead we send all metadata to a fifo pipe, so that other apps can listen to
+// the pipe and use the metadata.
+
+// We use two 4-character codes to identify each piece of data and we send the
+// data itself, if any,
+// in base64 form.
+
+// The first 4-character code, called the "type", is either:
+// 'core' for all the regular metadadata coming from iTunes, etc., or
+// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync
+// itself, such as
+// start/end delimiters, etc.
+
+// For 'core' metadata, the second 4-character code is the 4-character metadata
+// code coming from
+// iTunes etc.
+// For 'ssnc' metadata, the second 4-character code is used to distinguish the
+// messages.
+
+// Cover art is not tagged in the same way as other metadata, it seems, so is
+// sent as an 'ssnc' type
+// metadata message with the code 'PICT'
+// Here are the 'ssnc' codes defined so far:
+// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the
+// first few bytes to see
+// which.
+// 'pbeg' -- play stream begin. No arguments
+// 'pend' -- play stream end. No arguments
+// 'pfls' -- play stream flush. No arguments
+// 'prsm' -- play stream resume. No arguments
+// 'pvol' -- play volume. The volume is sent as a string --
+// "airplay_volume,volume,lowest_volume,highest_volume"
+// volume, lowest_volume and highest_volume are given in dB.
+// The "airplay_volume" is what's sent to the player, and is from
+// 0.00 down to -30.00,
+// with -144.00 meaning mute.
+// This is linear on the volume control slider of iTunes or iOS
+// AirPlay.
+// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP
+// timestamps for the start
+// of the current play sequence, the current play point and the end of the
+// play sequence.
+// I guess the timestamps wrap at 2^32.
+// 'mdst' -- a sequence of metadata is about to start; will have, as data,
+// the rtptime associated with the metadata, if available
+// 'mden' -- a sequence of metadata has ended; will have, as data, the
+// rtptime associated with the metadata, if available
+// 'pcst' -- a picture is about to be sent; will have, as data, the rtptime
+// associated with the picture, if available
+// 'pcen' -- a picture has been sent; will have, as data, the rtptime
+// associated with the metadata, if available
+// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session.
+// Specifically, it's the "X-Apple-Client-Name" string
+// 'snua' -- A "user agent" -- e.g. "iTunes/12..." -- has opened a play
+// session. Specifically, it's the "User-Agent" string
+// The next two two tokens are to facilitiate remote control of the source.
+// There is some information at http://nto.github.io/AirPlay.html about
+// remote control of the source.
+//
+// 'daid' -- this is the source's DACP-ID (if it has one -- it's not
+// guaranteed), useful if you want to remotely control the source. Use this
+// string to identify the source's remote control on the network.
+// 'acre' -- this is the source's Active-Remote token, necessary if you want
+// 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.
+// `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.
+// Notice that the number of items is different to the standard
+
+// including a simple base64 encoder to minimise malloc/free activity
+
+// From Stack Overflow, with thanks:
+// http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c
+// minor mods to make independent of C99.
+// more significant changes make it not malloc memory
+// needs to initialise the docoding table first
+
+// add _so to end of name to avoid confusion with polarssl's implementation
+
+static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
+
+static int mod_table[] = {0, 2, 1};
+
+// pass in a pointer to the data, its length, a pointer to the output buffer and
+// a pointer to an int
+// containing its maximum length
+// the actual length will be returned.
+
+char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data,
+ size_t *output_length) {
+
+ size_t calculated_output_length = 4 * ((input_length + 2) / 3);
+ if (calculated_output_length > *output_length)
+ return (NULL);
+ *output_length = calculated_output_length;
+
+ int i, j;
+ for (i = 0, j = 0; i < input_length;) {
+
+ uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
+ uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
+ uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;
+
+ uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+
+ encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
+ encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
+ encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
+ encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
+ }
+
+ for (i = 0; i < mod_table[input_length % 3]; i++)
+ encoded_data[*output_length - 1 - i] = '=';
+
+ return encoded_data;
+}
+
+// with thanks!
+//
+
+static int fd = -1;
+static int dirty = 0;
+pc_queue metadata_queue;
+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];
+
+static pthread_t metadata_thread;
+
+void metadata_create(void) {
+ if (config.metadata_enabled == 0)
+ return;
+
+ // Unlike metadata pipe, socket is opened once and stays open,
+ // so we can call it in create
+ if (config.metadata_sockaddr && config.metadata_sockport) {
+ metadata_sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (metadata_sock < 0) {
+ debug(1, "Could not open metadata socket");
+ } 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);
+ metadata_sockaddr.sin_port = htons(config.metadata_sockport);
+ metadata_sockmsg = malloc(config.metadata_sockmsglength);
+ if (metadata_sockmsg) {
+ memset(metadata_sockmsg, 0, config.metadata_sockmsglength);
+ } else {
+ die("Could not malloc metadata socket buffer");
+ }
+ }
+ }
+
+ size_t pl = strlen(config.metadata_pipename) + 1;
+
+ char *path = malloc(pl + 1);
+ snprintf(path, pl + 1, "%s", config.metadata_pipename);
+
+ if (mkfifo(path, 0644) && errno != EEXIST)
+ die("Could not create metadata FIFO %s", path);
+
+ free(path);
+}
+
+void metadata_open(void) {
+ if (config.metadata_enabled == 0)
+ return;
+
+ size_t pl = strlen(config.metadata_pipename) + 1;
+
+ 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);
+
+ free(path);
+}
+
+static void metadata_close(void) {
+ close(fd);
+ 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;
+
+ if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) {
+ char *ptr = metadata_sockmsg;
+ uint32_t v;
+ v = htonl(type);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(code);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ memcpy(ptr, data, length);
+ sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr,
+ sizeof(metadata_sockaddr));
+ } else if (metadata_sock >= 0) {
+ // send metadata in numbered chunks using the protocol:
+ // ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data)
+
+ uint32_t chunk_ix = 0;
+ uint32_t chunk_total = length / (config.metadata_sockmsglength - 24);
+ if (chunk_total * (config.metadata_sockmsglength - 24) < length) {
+ chunk_total++;
+ }
+ uint32_t remaining = length;
+ uint32_t v;
+ char *data_crsr = data;
+ do {
+ char *ptr = metadata_sockmsg;
+ memcpy(ptr, "ssncchnk", 8);
+ ptr += 8;
+ v = htonl(chunk_ix);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(chunk_total);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(type);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(code);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ uint32_t datalen = remaining;
+ if (datalen > config.metadata_sockmsglength - 24) {
+ datalen = config.metadata_sockmsglength - 24;
+ }
+ memcpy(ptr, data_crsr, datalen);
+ data_crsr += datalen;
+ sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0,
+ (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr));
+ chunk_ix++;
+ remaining -= datalen;
+ if (remaining == 0)
+ break;
+ } while (1);
+ }
+
+ // readers may go away and come back
+ if (fd < 0)
+ metadata_open();
+ if (fd < 0)
+ return;
+ char thestring[1024];
+ snprintf(thestring, 1024, "%x%x%u", type, code,
+ length);
+ ret = non_blocking_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));
+ if (ret < 0) {
+ // debug(1,"metadata_process error %d exit 2",ret);
+ return;
+ }
+ // here, we write the data in base64 form using our nice base64 encoder
+ // but, we break it into lines of 76 output characters, except for the last
+ // one.
+ // thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time
+ size_t remaining_count = length;
+ char *remaining_data = data;
+ size_t towrite_count;
+ char outbuf[76];
+ while ((remaining_count) && (ret >= 0)) {
+ size_t towrite_count = remaining_count;
+ if (towrite_count > 57)
+ towrite_count = 57;
+ size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit
+ if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) ==
+ NULL)
+ 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);
+ if (ret < 0) {
+ // debug(1,"metadata_process error %d exit 3",ret);
+ return;
+ }
+ remaining_data += towrite_count;
+ remaining_count -= towrite_count;
+ }
+ snprintf(thestring, 1024, "");
+ ret = non_blocking_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));
+ if (ret < 0) {
+ // debug(1,"metadata_process error %d exit 5",ret);
+ return;
+ }
+}
+
+void *metadata_thread_function(void *ignore) {
+ metadata_create();
+ metadata_package pack;
+ while (1) {
+ pc_queue_get_item(&metadata_queue, &pack);
+ if (config.metadata_enabled)
+ metadata_process(pack.type, pack.code, pack.data, pack.length);
+ if (pack.carrier)
+ msg_free(pack.carrier); // release the message
+ else if (pack.data)
+ free(pack.data);
+ }
+ pthread_exit(NULL);
+}
+
+void metadata_init(void) {
+ // 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);
+ int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
+ if (ret)
+ debug(1, "Failed to create metadata thread!");
+}
+
+int send_metadata(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
+ // NULL
+ // the rtsp_message is sent for 'core' messages, because it contains the data
+ // and must not be
+ // freed until the data has been read. So, it is passed to send_metadata to be
+ // retained,
+ // sent to the thread where metadata is processed and released (and probably
+ // freed).
+
+ // The rtsp_message is also sent for certain non-'core' messages.
+
+ // 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
+ // 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;
+ if (carrier)
+ msg_retain(carrier);
+ pack.carrier = carrier;
+ int rc = pc_queue_add_item(&metadata_queue, &pack, block);
+ if ((rc == EBUSY) && (carrier))
+ msg_free(carrier);
+ if (rc == EBUSY)
+ warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code);
+ return rc;
+}
+
+static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *req,
+ rtsp_message *resp) {
+ char *cp = req->content;
+ int cl = req->contentlength;
+
+ unsigned int off = 8;
+
+ uint32_t itag, vl;
+ while (off < cl) {
+ // pick up the metadata tag as an unsigned longint
+ memcpy(&itag, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */
+ itag = ntohl(itag);
+ off += sizeof(uint32_t);
+
+ // pick up the length of the data
+ memcpy(&vl, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */
+ vl = ntohl(vl);
+ off += sizeof(uint32_t);
+
+ // pass the data over
+ if (vl == 0)
+ send_metadata('core', itag, NULL, 0, NULL, 1);
+ else
+ send_metadata('core', itag, (char *)(cp + off), vl, req, 1);
+
+ // move on to the next item
+ off += vl;
+ }
+}
+
+#endif
+
+static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+ debug(3, "Connection %d: GET_PARAMETER", conn->connection_number);
+ resp->respcode = 200;
+}
+
+static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+ debug(3, "Connection %d: SET_PARAMETER", conn->connection_number);
+ // if (!req->contentlength)
+ // debug(1, "received empty SET_PARAMETER request.");
+
+ // debug_print_msg_headers(1,req);
+
+ char *ct = msg_get_header(req, "Content-Type");
+
+ if (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
+ // can be used
+ // to link items of metadata, including pictures, that refer to the same
+ // entity.
+ // If they refer to the same item, they have the same rtptime.
+ // So we send the rtptime before and after both the metadata items and the
+ // picture item
+ // get the rtptime
+ char *p = NULL;
+ char *hdr = msg_get_header(req, "RTP-Info");
+
+ if (hdr) {
+ p = strstr(hdr, "rtptime=");
+ if (p) {
+ p = strchr(p, '=');
+ }
+ }
+
+ // not all items have RTP-time stuff in them, which is okay
+
+ if (!strncmp(ct, "application/x-dmap-tagged", 25)) {
+ debug(3, "received metadata tags in SET_PARAMETER request.");
+ if (p == NULL)
+ debug(1, "Missing RTP-Time info for metadata");
+ if (p)
+ send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, 1); // metadata starting
+ else
+ send_metadata('ssnc', 'mdst', NULL, 0, NULL,
+ 0); // metadata starting, if rtptime is not available
+
+ handle_set_parameter_metadata(conn, req, resp);
+
+ if (p)
+ send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, 1); // metadata ending
+ else
+ send_metadata('ssnc', 'mden', NULL, 0, NULL,
+ 0); // metadata starting, if rtptime is not available
+
+ } else if (!strncmp(ct, "image", 5)) {
+ // Some server simply ignore the md field from the TXT record. If The
+ // config says 'please, do not include any cover art', we are polite and
+ // do not write them to the pipe.
+ if (config.get_coverart) {
+ // debug(1, "received image in SET_PARAMETER request.");
+ // note: the image/type tag isn't reliable, so it's not being sent
+ // -- best look at the first few bytes of the image
+ if (p == NULL)
+ debug(1, "Missing RTP-Time info for picture item");
+ if (p)
+ send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, 1); // picture starting
+ else
+ send_metadata('ssnc', 'pcst', NULL, 0, NULL,
+ 0); // picture starting, if rtptime is not available
+
+ send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1);
+
+ if (p)
+ send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, 1); // picture ending
+ else
+ send_metadata('ssnc', 'pcen', NULL, 0, NULL,
+ 0); // picture ending, if rtptime is not available
+ } else {
+ debug(1, "Ignore received picture item (include_cover_art = no).");
+ }
+ } else
+#endif
+ if (!strncmp(ct, "text/parameters", 15)) {
+ // debug(2, "received parameters in SET_PARAMETER request.");
+ handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress
+ } else {
+ debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", ct);
+ }
+ } else {
+ debug(1, "missing Content-Type header in SET_PARAMETER request.");
+ }
+ resp->respcode = 200;
+}
+
+static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+ debug(3, "Connection %d: ANNOUNCE", conn->connection_number);
+ int have_the_player = 0;
+
+ // interrupt session if permitted
+ if (pthread_mutex_trylock(&play_lock) == 0) {
+ have_the_player = 1;
+ } else {
+ int should_wait = 0;
+
+ if (!playing_conn)
+ die("Non existent playing_conn with play_lock enabled.");
+ debug(1, "RTSP Conversation thread %d already playing when asked by thread %d.",
+ playing_conn->connection_number, conn->connection_number);
+ if (playing_conn->stop) {
+ debug(1, "Playing connection is already shutting down; waiting for it...");
+ should_wait = 1;
+ } else if (config.allow_session_interruption == 1) {
+ // some other thread has the player ... ask it to relinquish the thread
+ debug(1, "ANNOUNCE: playing connection %d being interrupted by connection %d.",
+ playing_conn->connection_number, conn->connection_number);
+ if (playing_conn == conn) {
+ debug(1, "ANNOUNCE asking to stop itself.");
+ } else {
+ playing_conn->stop = 1;
+ memory_barrier();
+ pthread_kill(playing_conn->thread, SIGUSR1);
+ should_wait = 1;
+ }
+ }
+
+ if (should_wait) {
+ usleep(1000000); // here, it is possible for other connections to come in and nab the player.
+ debug(1, "Try to get the player now");
+ }
+ if (pthread_mutex_trylock(&play_lock) == 0)
+ have_the_player = 1;
+ else
+ debug(1, "ANNOUNCE failed to get the player");
+ }
+
+ if (have_the_player) {
+ playing_conn = conn; // the present connection is now playing
+ debug(3, "RTSP conversation thread %d has acquired play lock.", conn->connection_number);
+ resp->respcode = 456; // 456 - Header Field Not Valid for Resource
+ char *paesiv = NULL;
+ char *prsaaeskey = NULL;
+ char *pfmtp = NULL;
+ char *cp = req->content;
+ int cp_left = req->contentlength;
+ char *next;
+ while (cp_left && cp) {
+ next = nextline(cp, cp_left);
+ cp_left -= next - cp;
+
+ if (!strncmp(cp, "a=fmtp:", 7))
+ pfmtp = cp + 7;
+
+ if (!strncmp(cp, "a=aesiv:", 8))
+ paesiv = cp + 8;
+
+ if (!strncmp(cp, "a=rsaaeskey:", 12))
+ prsaaeskey = cp + 12;
+
+ cp = next;
+ }
+
+ if ((paesiv == NULL) && (prsaaeskey == NULL)) {
+ // debug(1,"Unencrypted session requested?");
+ conn->stream.encrypted = 0;
+ } else {
+ conn->stream.encrypted = 1;
+ // debug(1,"Encrypted session requested");
+ }
+
+ if (!pfmtp) {
+ warn("FMTP params missing from the following ANNOUNCE message:");
+ // print each line of the request content
+ // the problem is that nextline has replace all returns, newlines, etc. by
+ // NULLs
+ char *cp = req->content;
+ int cp_left = req->contentlength;
+ while (cp_left > 1) {
+ if (strlen(cp) != 0)
+ warn(" %s", cp);
+ cp += strlen(cp) + 1;
+ cp_left -= strlen(cp) + 1;
+ }
+ goto out;
+ }
+
+ if (conn->stream.encrypted) {
+ int len, keylen;
+ uint8_t *aesiv = base64_dec(paesiv, &len);
+ if (len != 16) {
+ warn("client announced aeskey of %d bytes, wanted 16", len);
+ free(aesiv);
+ goto out;
+ }
+ memcpy(conn->stream.aesiv, aesiv, 16);
+ free(aesiv);
+
+ uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len);
+ uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY);
+ free(rsaaeskey);
+ if (keylen != 16) {
+ warn("client announced rsaaeskey of %d bytes, wanted 16", keylen);
+ free(aeskey);
+ goto out;
+ }
+ memcpy(conn->stream.aeskey, aeskey, 16);
+ free(aeskey);
+ }
+ int i;
+ for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
+ conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t"));
+ // here we should check the sanity ot the fmtp values
+ // for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
+ // debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]);
+
+ char *hdr = msg_get_header(req, "X-Apple-Client-Name");
+ if (hdr) {
+ debug(1, "Play connection from device named \"%s\" on RTSP conversation thread %d.", hdr,
+ conn->connection_number);
+#ifdef CONFIG_METADATA
+ send_metadata('ssnc', 'snam', hdr, strlen(hdr), req, 1);
+#endif
+ }
+ hdr = msg_get_header(req, "User-Agent");
+ if (hdr) {
+ debug(1, "Play connection from user agent \"%s\" on RTSP conversation thread %d.", hdr,
+ conn->connection_number);
+#ifdef CONFIG_METADATA
+ send_metadata('ssnc', 'snua', hdr, strlen(hdr), req, 1);
+#endif
+ }
+ resp->respcode = 200;
+ } else {
+ resp->respcode = 453;
+ debug(1, "Already playing.");
+ }
+
+out:
+ if (resp->respcode != 200 && resp->respcode != 453) {
+ debug(1, "Error in handling ANNOUNCE on conversation thread %d. Unlocking the play lock.",
+ conn->connection_number);
+ playing_conn = NULL;
+ pthread_mutex_unlock(&play_lock);
+ }
+}
+
+static struct method_handler {
+ char *method;
+ void (*handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp);
+} method_handlers[] = {{"OPTIONS", handle_options},
+ {"ANNOUNCE", handle_announce},
+ {"FLUSH", handle_flush},
+ {"TEARDOWN", handle_teardown},
+ {"SETUP", handle_setup},
+ {"GET_PARAMETER", handle_get_parameter},
+ {"SET_PARAMETER", handle_set_parameter},
+ {"RECORD", handle_record},
+ {NULL, NULL}};
+
+static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) {
+ char *hdr = msg_get_header(req, "Apple-Challenge");
+ if (!hdr)
+ return;
+
+ SOCKADDR fdsa;
+ socklen_t sa_len = sizeof(fdsa);
+ getsockname(fd, (struct sockaddr *)&fdsa, &sa_len);
+
+ int chall_len;
+ uint8_t *chall = base64_dec(hdr, &chall_len);
+ if (chall == NULL)
+ die("null chall in apple_challenge");
+ uint8_t buf[48], *bp = buf;
+ int i;
+ memset(buf, 0, sizeof(buf));
+
+ if (chall_len > 16) {
+ warn("oversized Apple-Challenge!");
+ free(chall);
+ return;
+ }
+ memcpy(bp, chall, chall_len);
+ free(chall);
+ bp += chall_len;
+
+#ifdef AF_INET6
+ if (fdsa.SAFAMILY == AF_INET6) {
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(&fdsa);
+ memcpy(bp, sa6->sin6_addr.s6_addr, 16);
+ bp += 16;
+ } else
+#endif
+ {
+ struct sockaddr_in *sa = (struct sockaddr_in *)(&fdsa);
+ memcpy(bp, &sa->sin_addr.s_addr, 4);
+ bp += 4;
+ }
+
+ for (i = 0; i < 6; i++)
+ *bp++ = config.hw_addr[i];
+
+ int buflen, resplen;
+ buflen = bp - buf;
+ if (buflen < 0x20)
+ buflen = 0x20;
+
+ uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH);
+ char *encoded = base64_enc(challresp, resplen);
+ if (encoded == NULL)
+ die("could not allocate memory for \"encoded\"");
+ // strip the padding.
+ char *padding = strchr(encoded, '=');
+ if (padding)
+ *padding = 0;
+
+ msg_add_header(resp, "Apple-Response", encoded);
+ free(challresp);
+ free(encoded);
+}
+
+static char *make_nonce(void) {
+ uint8_t random[8];
+ int fd = open("/dev/random", O_RDONLY);
+ if (fd < 0)
+ die("could not open /dev/random!");
+ int ignore = read(fd, random, sizeof(random));
+ close(fd);
+ return base64_enc(random, 8);
+}
+
+static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
+
+ if (!config.password)
+ return 0;
+ if (!*nonce) {
+ *nonce = make_nonce();
+ goto authenticate;
+ }
+
+ char *hdr = msg_get_header(req, "Authorization");
+ if (!hdr || strncmp(hdr, "Digest ", 7))
+ goto authenticate;
+
+ char *realm = strstr(hdr, "realm=\"");
+ char *username = strstr(hdr, "username=\"");
+ char *response = strstr(hdr, "response=\"");
+ char *uri = strstr(hdr, "uri=\"");
+
+ if (!realm || !username || !response || !uri)
+ goto authenticate;
+
+ char *quote;
+ realm = strchr(realm, '"') + 1;
+ if (!(quote = strchr(realm, '"')))
+ goto authenticate;
+ *quote = 0;
+ username = strchr(username, '"') + 1;
+ if (!(quote = strchr(username, '"')))
+ goto authenticate;
+ *quote = 0;
+ response = strchr(response, '"') + 1;
+ if (!(quote = strchr(response, '"')))
+ goto authenticate;
+ *quote = 0;
+ uri = strchr(uri, '"') + 1;
+ if (!(quote = strchr(uri, '"')))
+ goto authenticate;
+ *quote = 0;
+
+ uint8_t digest_urp[16], digest_mu[16], digest_total[16];
+
+#ifdef HAVE_LIBSSL
+ MD5_CTX ctx;
+
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, username, strlen(username));
+ MD5_Update(&ctx, ":", 1);
+ MD5_Update(&ctx, realm, strlen(realm));
+ MD5_Update(&ctx, ":", 1);
+ MD5_Update(&ctx, config.password, strlen(config.password));
+ MD5_Final(digest_urp, &ctx);
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, req->method, strlen(req->method));
+ MD5_Update(&ctx, ":", 1);
+ MD5_Update(&ctx, uri, strlen(uri));
+ MD5_Final(digest_mu, &ctx);
+#endif
+
+#ifdef HAVE_LIBMBEDTLS
+ mbedtls_md5_context tctx;
+ mbedtls_md5_starts(&tctx);
+ mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username));
+ mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
+ mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm));
+ mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
+ mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password));
+ mbedtls_md5_finish(&tctx, digest_urp);
+ mbedtls_md5_starts(&tctx);
+ mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method));
+ mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
+ mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri));
+ mbedtls_md5_finish(&tctx, digest_mu);
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+ md5_context tctx;
+ md5_starts(&tctx);
+ md5_update(&tctx, (const unsigned char *)username, strlen(username));
+ md5_update(&tctx, (unsigned char *)":", 1);
+ md5_update(&tctx, (const unsigned char *)realm, strlen(realm));
+ md5_update(&tctx, (unsigned char *)":", 1);
+ md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password));
+ md5_finish(&tctx, digest_urp);
+ md5_starts(&tctx);
+ md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method));
+ md5_update(&tctx, (unsigned char *)":", 1);
+ md5_update(&tctx, (const unsigned char *)uri, strlen(uri));
+ md5_finish(&tctx, digest_mu);
+#endif
+
+ int i;
+ unsigned char buf[33];
+ for (i = 0; i < 16; i++)
+ sprintf((char *)buf + 2 * i, "%02x", digest_urp[i]);
+
+#ifdef HAVE_LIBSSL
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, buf, 32);
+ MD5_Update(&ctx, ":", 1);
+ MD5_Update(&ctx, *nonce, strlen(*nonce));
+ MD5_Update(&ctx, ":", 1);
+ for (i = 0; i < 16; i++)
+ sprintf((char *)buf + 2 * i, "%02x", digest_mu[i]);
+ MD5_Update(&ctx, buf, 32);
+ MD5_Final(digest_total, &ctx);
+#endif
+
+#ifdef HAVE_LIBMBEDTLS
+ mbedtls_md5_starts(&tctx);
+ mbedtls_md5_update(&tctx, buf, 32);
+ mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
+ mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce));
+ mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
+ for (i = 0; i < 16; i++)
+ sprintf((char *)buf + 2 * i, "%02x", digest_mu[i]);
+ mbedtls_md5_update(&tctx, buf, 32);
+ mbedtls_md5_finish(&tctx, digest_total);
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+ md5_starts(&tctx);
+ md5_update(&tctx, buf, 32);
+ md5_update(&tctx, (unsigned char *)":", 1);
+ md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce));
+ md5_update(&tctx, (unsigned char *)":", 1);
+ for (i = 0; i < 16; i++)
+ sprintf((char *)buf + 2 * i, "%02x", digest_mu[i]);
+ md5_update(&tctx, buf, 32);
+ md5_finish(&tctx, digest_total);
+#endif
+
+ for (i = 0; i < 16; i++)
+ sprintf((char *)buf + 2 * i, "%02x", digest_total[i]);
+
+ if (!strcmp(response, (const char *)buf))
+ return 0;
+ warn("Password authorization failed.");
+
+authenticate:
+ resp->respcode = 401;
+ int hdrlen = strlen(*nonce) + 40;
+ char *authhdr = malloc(hdrlen);
+ snprintf(authhdr, hdrlen, "Digest realm=\"raop\", nonce=\"%s\"", *nonce);
+ msg_add_header(resp, "WWW-Authenticate", authhdr);
+ free(authhdr);
+ return 1;
+}
+
+static void *rtsp_conversation_thread_func(void *pconn) {
+ rtsp_conn_info *conn = pconn;
+
+ rtp_initialise(conn);
+
+ rtsp_message *req, *resp;
+ char *hdr, *auth_nonce = NULL;
+
+ enum rtsp_read_request_response reply;
+
+ while (conn->stop == 0) {
+ reply = rtsp_read_request(conn, &req);
+ if (reply == rtsp_read_request_response_ok) {
+ debug(3, "RTSP thread %d received an RTSP Packet of type \"%s\":", conn->connection_number,
+ req->method),
+ debug_print_msg_headers(3, req);
+ resp = msg_init();
+ resp->respcode = 400;
+
+ apple_challenge(conn->fd, req, resp);
+ hdr = msg_get_header(req, "CSeq");
+ if (hdr)
+ msg_add_header(resp, "CSeq", hdr);
+ // msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog");
+ msg_add_header(resp, "Server", "AirTunes/105.1");
+
+ if ((conn->authorized == 1) || (rtsp_auth(&auth_nonce, req, resp)) == 0) {
+ conn->authorized = 1; // it must have been authorized or didn't need a password
+ struct method_handler *mh;
+ int method_selected = 0;
+ for (mh = method_handlers; mh->method; mh++) {
+ if (!strcmp(mh->method, req->method)) {
+ method_selected = 1;
+ mh->handler(conn, req, resp);
+ break;
+ }
+ }
+ if (method_selected == 0)
+ debug(1, "RTSP thread %d: Unrecognised and unhandled rtsp request \"%s\".",
+ conn->connection_number, req->method);
+ }
+ debug(3, "RTSP thread %d: RTSP Response:", conn->connection_number);
+ debug_print_msg_headers(3, resp);
+ fd_set writefds;
+ FD_ZERO(&writefds);
+ FD_SET(conn->fd, &writefds);
+ do {
+ memory_barrier();
+ } while (conn->stop == 0 &&
+ pselect(conn->fd + 1, NULL, &writefds, NULL, NULL, &pselect_sigset) <= 0);
+ if (conn->stop == 0) {
+ msg_write_response(conn->fd, resp);
+ }
+ msg_free(req);
+ msg_free(resp);
+ } else {
+ if ((reply == rtsp_read_request_response_immediate_shutdown_requested) ||
+ (reply == rtsp_read_request_response_channel_closed)) {
+ debug(3, "Synchronously terminate playing thread of RTSP conversation thread %d.",
+ conn->connection_number);
+ player_stop(conn);
+ debug(3, "Successful termination of playing thread of RTSP conversation thread %d.",
+ conn->connection_number);
+ debug(3, "Request termination of RTSP conversation thread %d.", conn->connection_number);
+ conn->stop = 1;
+ } else {
+ debug(1, "rtsp_read_request error %d, packet ignored.", (int)reply);
+ }
+ }
+ }
+
+ if (conn->fd > 0)
+ close(conn->fd);
+ if (auth_nonce)
+ free(auth_nonce);
+ rtp_terminate(conn);
+ if (playing_conn == conn) {
+ debug(3, "Unlocking play lock on RTSP conversation thread %d.", conn->connection_number);
+ playing_conn = NULL;
+ pthread_mutex_unlock(&play_lock);
+ }
+ debug(1, "RTSP conversation thread %d terminated.", conn->connection_number);
+ // please_shutdown = 0;
+ conn->running = 0;
+ return NULL;
+}
+
+// this function is not thread safe.
+static const char *format_address(struct sockaddr *fsa) {
+ static char string[INETx_ADDRSTRLEN];
+ void *addr;
+#ifdef AF_INET6
+ if (fsa->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(fsa);
+ addr = &(sa6->sin6_addr);
+ } else
+#endif
+ {
+ struct sockaddr_in *sa = (struct sockaddr_in *)(fsa);
+ addr = &(sa->sin_addr);
+ }
+ return inet_ntop(fsa->sa_family, addr, string, sizeof(string));
+}
+
+void rtsp_listen_loop(void) {
+ struct addrinfo hints, *info, *p;
+ char portstr[6];
+ int *sockfd = NULL;
+ int nsock = 0;
+ int i, ret;
+
+ playing_conn = NULL; // the data structure representing the connection that has the player.
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ snprintf(portstr, 6, "%d", config.port);
+
+ // debug(1,"listen socket port request is \"%s\".",portstr);
+
+ ret = getaddrinfo(NULL, portstr, &hints, &info);
+ if (ret) {
+ die("getaddrinfo failed: %s", gai_strerror(ret));
+ }
+
+ for (p = info; p; p = p->ai_next) {
+ ret = 0;
+ int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP);
+ int yes = 1;
+
+ // Handle socket open failures if protocol unavailable (or IPV6 not handled)
+ if (fd == -1) {
+ // debug(1, "Failed to get socket: fam=%d, %s\n", p->ai_family,
+ // strerror(errno));
+ continue;
+ }
+ // Set the RTSP socket to close on exec() of child processes
+ // otherwise background run_this_before_play_begins or run_this_after_play_ends commands
+ // that are sleeping prevent the daemon from being restarted because
+ // the listening RTSP port is still in use.
+ // See: https://github.com/mikebrady/shairport-sync/issues/329
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
+
+#ifdef IPV6_V6ONLY
+ // some systems don't support v4 access on v6 sockets, but some do.
+ // since we need to account for two sockets we might as well
+ // always.
+ if (p->ai_family == AF_INET6) {
+ ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes));
+ }
+#endif
+
+ if (!ret)
+ ret = bind(fd, p->ai_addr, p->ai_addrlen);
+
+ // one of the address families will fail on some systems that
+ // report its availability. do not complain.
+
+ if (ret) {
+ char *family;
+#ifdef AF_INET6
+ if (p->ai_family == AF_INET6) {
+ family = "IPv6";
+ } else
+#endif
+ family = "IPv4";
+ debug(1, "Unable to listen on %s port %d. The error is: \"%s\".", family, config.port,
+ strerror(errno));
+ continue;
+ }
+
+ listen(fd, 5);
+ nsock++;
+ sockfd = realloc(sockfd, nsock * sizeof(int));
+ sockfd[nsock - 1] = fd;
+ }
+
+ freeaddrinfo(info);
+
+ if (!nsock)
+ die("Could not establish a service on port %d -- program terminating. Is another instance of "
+ "Shairport Sync running on this device?",
+ config.port);
+
+ int maxfd = -1;
+ fd_set fds;
+ FD_ZERO(&fds);
+ for (i = 0; i < nsock; i++) {
+ if (sockfd[i] > maxfd)
+ maxfd = sockfd[i];
+ }
+
+ mdns_register();
+
+ // printf("Listening for connections.");
+ // shairport_startup_complete();
+
+ int acceptfd;
+ struct timeval tv;
+ while (1) {
+ tv.tv_sec = 300;
+ tv.tv_usec = 0;
+
+ for (i = 0; i < nsock; i++)
+ FD_SET(sockfd[i], &fds);
+
+ ret = select(maxfd + 1, &fds, 0, 0, &tv);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+
+ cleanup_threads();
+
+ acceptfd = -1;
+ for (i = 0; i < nsock; i++) {
+ if (FD_ISSET(sockfd[i], &fds)) {
+ acceptfd = sockfd[i];
+ break;
+ }
+ }
+ if (acceptfd < 0) // timeout
+ continue;
+
+ rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info));
+ if (conn == 0)
+ die("Couldn't allocate memory for an rtsp_conn_info record.");
+ memset(conn, 0, sizeof(rtsp_conn_info));
+ conn->connection_number = RTSP_connection_index++;
+ socklen_t slen = sizeof(conn->remote);
+
+ conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen);
+ if (conn->fd < 0) {
+ debug(1, "New RTSP connection on port %d not accepted:", config.port);
+ perror("failed to accept connection");
+ free(conn);
+ } else {
+ SOCKADDR *local_info = (SOCKADDR *)&conn->local;
+ socklen_t size_of_reply = sizeof(*local_info);
+ memset(local_info, 0, sizeof(SOCKADDR));
+ if (getsockname(conn->fd, (struct sockaddr *)local_info, &size_of_reply) == 0) {
+
+ // IPv4:
+ if (local_info->SAFAMILY == AF_INET) {
+ char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
+ char remote_ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
+ struct sockaddr_in *sa = (struct sockaddr_in *)local_info;
+ inet_ntop(AF_INET, &(sa->sin_addr), ip4, INET_ADDRSTRLEN);
+ unsigned short int tport = ntohs(sa->sin_port);
+ sa = (struct sockaddr_in *)&conn->remote;
+ inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN);
+ unsigned short int rport = ntohs(sa->sin_port);
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('clip', strdup(remote_ip4), strlen(remote_ip4), 1);
+ send_ssnc_metadata('svip', strdup(ip4), strlen(ip4), 1);
+#endif
+ debug(1, "New RTSP connection from %s:%u to self at %s:%u on conversation thread %d.",
+ remote_ip4, rport, ip4, tport, conn->connection_number);
+ }
+#ifdef AF_INET6
+ if (local_info->SAFAMILY == AF_INET6) {
+ // IPv6:
+
+ char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
+ char remote_ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
+ struct sockaddr_in6 *sa6 =
+ (struct sockaddr_in6 *)local_info; // pretend this is loaded with something
+ inet_ntop(AF_INET6, &(sa6->sin6_addr), ip6, INET6_ADDRSTRLEN);
+ u_int16_t tport = ntohs(sa6->sin6_port);
+
+ sa6 = (struct sockaddr_in6 *)&conn->remote; // pretend this is loaded with something
+ inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN);
+ u_int16_t rport = ntohs(sa6->sin6_port);
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('clip', strdup(remote_ip6), strlen(remote_ip6), 1);
+ send_ssnc_metadata('svip', strdup(ip6), strlen(ip6), 1);
+#endif
+ debug(1, "New RTSP connection from [%s]:%u to self at [%s]:%u on conversation thread %d.",
+ remote_ip6, rport, ip6, tport, conn->connection_number);
+ }
+#endif
+
+ } else {
+ debug(1, "Error figuring out Shairport Sync's own IP number.");
+ }
+ // usleep(500000);
+ // pthread_t rtsp_conversation_thread;
+ // conn->thread = rtsp_conversation_thread;
+ // conn->stop = 0; // record's memory has been zeroed
+ // conn->authorized = 0; // record's memory has been zeroed
+ fcntl(conn->fd, F_SETFL, O_NONBLOCK);
+
+ ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func,
+ conn); // also acts as a memory barrier
+ if (ret)
+ die("Failed to create RTSP receiver thread %d!", conn->connection_number);
+ debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number);
+ conn->running = 1; // this must happen before the thread is tracked
+ track_thread(conn);
+ }
+ }
+ perror("select");
+ die("fell out of the RTSP select loop");
+}
diff --git a/rtsp.h b/rtsp.h
new file mode 100644
index 0000000..eae28df
--- /dev/null
+++ b/rtsp.h
@@ -0,0 +1,24 @@
+#ifndef _RTSP_H
+#define _RTSP_H
+
+#include "player.h"
+
+rtsp_conn_info *playing_conn;
+
+void rtsp_listen_loop(void);
+// void rtsp_shutdown_stream(void);
+void rtsp_request_shutdown_stream(void);
+
+// initialise the metadata stuff
+
+void metadata_init(void);
+
+// sends metadata out to the metadata pipe, if enabled.
+// It is sent with the type 'ssnc' the given code, data and length
+// The handler at the other end must know what to do with the data
+// e.g. if it's malloced, to free it, etc.
+// nothing is done automatically
+
+int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block);
+
+#endif // _RTSP_H
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
new file mode 100644
index 0000000..7cafd84
--- /dev/null
+++ b/scripts/shairport-sync.conf
@@ -0,0 +1,146 @@
+// Sample Configuration File for Shairport Sync
+// Commented out settings are generally the defaults, except where noted.
+
+// General Settings
+general =
+{
+// name = "%H"; // This means "Hostname" -- see below. This is the name the service will advertise to iTunes.
+// The default is "Hostname" -- i.e. the machine's hostname with the first letter capitalised (ASCII only.)
+// You can use the following substitutions:
+// %h for the hostname,
+// %H for the Hostname (i.e. with first letter capitalised (ASCII only)),
+// %v for the version number, e.g. 3.0 and
+// %V for the full version string, e.g. 3.0-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
+// Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H".
+// password = "secret"; // leave this commented out if you don't want to require a password
+// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor.
+// 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.
+// 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 = 100; // 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.
+// statistics = "no"; // set to "yes" to print statistics in the log
+// 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
+// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose.
+
+// 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.
+// The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer).
+// Leave it commented out to use mixer's maximum volume.
+// run_this_when_volume_is_set = "/full/path/to/application/and/args"; // Run the specified application whenever the volume control is set or changed.
+// 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.
+// 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.15; // 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_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.
+// 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".
+};
+
+// Advanced parameters for controlling how a Shairport Sync runs
+sessioncontrol =
+{
+// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. It it's a script, include the #!... stuff on the first line
+// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. It it's a script, include the #!... stuff on the first line
+// wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing
+// allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source
+// session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again.
+};
+
+// Back End Settings
+
+// These are parameters for the "alsa" audio back end.
+alsa =
+{
+// output_device = "default"; // the name of the alsa output device. 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 = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability.
+// output_format = "S16"; // can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", 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".
+// 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"
+// mute_using_playback_switch = "no"; // Use this optional advanced setting to control whether the snd_mixer_selem_set_playback_switch_all call can be used for muting. Default is "no", for compatibility with other audio players.
+};
+
+// Parameters for the "sndio" audio back end. All are optional.
+sndio =
+{
+// device = "snd/0"; // optional setting to set the name of the output device. Default is the sndio system default.
+// rate = 44100; // optional setting which can be 44100, 88200, 176400 or 352800, but the device must have the capability. Default is 44100.
+// format = "S16"; // optional setting which can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
+// round = ; // advanced optional setting to set the period size near to this value
+// bufsz = ; // advanced optional setting to set the buffer size near to this value
+};
+
+// Parameters for the "pa" PulseAudio backend.
+pa =
+{
+// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active.
+};
+
+// Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done.
+pipe =
+{
+// name = "/path/to/pipe"; // there is no default pipe name for the output
+};
+
+// These are no configuration file parameters for the "stdout" audio back end. No interpolation is done.
+
+// These are no configuration file parameters for the "ao" audio back end. No interpolation is done.
+
+// Static latency settings are deprecated and the settings have been removed.
+
+dsp =
+{
+
+//////////////////////////////////////////
+// This convolution filter can be used to apply almost any correction to the audio signal, like frequency and phase correction.
+// For example you could measure (with a good microphone and a sweep-sine) the frequency response of your speakers + room,
+// and apply a correction to get a flat response curve.
+//////////////////////////////////////////
+//
+// convolution = "yes"; // Activate the convolution filter.
+// convolution_ir_file = "impulse.wav"; // Impulse Response file to be convolved to the audio stream
+// convolution_gain = -4.0; // Static gain applied to prevent clipping during the convolution process
+// convolution_max_length = 44100; // Truncate the input file to this length in order to save CPU.
+
+
+//////////////////////////////////////////
+// This loudness filter is used to compensate for human ear non linearity.
+// When the volume decreases, our ears loose more sentisitivity in the low range frequencies than in the mid range ones.
+// This filter aims at compensating for this loss, applying a variable gain to low frequencies depending on the volume.
+// More info can be found here: https://en.wikipedia.org/wiki/Equal-loudness_contour
+// For this filter to work properly, you should disable (or set to a fix value) all other volume control and only let shairport-sync control your volume.
+// The setting "loudness_reference_volume_db" should be set at the volume reported by shairport-sync when listening to music at a normal listening volume.
+//////////////////////////////////////////
+//
+// loudness = "yes"; // Activate the filter
+// loudness_reference_volume_db = -20.0; // Above this level the filter will have no effect anymore. Below this level it will gradually boost the low frequencies.
+
+};
+
+// How to deal with metadata, including artwork
+metadata =
+{
+// enabled = "no"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
+// include_cover_art = "no"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
+// pipe_name = "/tmp/shairport-sync-metadata";
+// pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up
+// socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes"
+// socket_port = 5555; // if socket_address is set, the port to send UDP packets to
+// socket_msglength = 65000; // the maximum packet size for any UDP metadata. This will be clipped to be between 500 or 65000. The default is 500.
+};
+
diff --git a/scripts/shairport-sync.freebsd b/scripts/shairport-sync.freebsd
new file mode 100644
index 0000000..9088bd8
--- /dev/null
+++ b/scripts/shairport-sync.freebsd
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+
+# PROVIDE: shairport_sync
+# REQUIRE: FILESYSTEMS DAEMON hostname avahi_daemon
+
+. /etc/rc.subr
+
+name="shairport_sync"
+rcvar="shairport_sync_enable"
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+
+shairport_sync_start()
+{
+ checkyesno shairport_sync_enable && echo "Starting shairport-sync." && \
+ su -m shairport-sync -c "/usr/local/bin/shairport-sync -d"
+}
+
+shairport_sync_stop()
+{
+ checkyesno shairport_sync_enable && echo "Stopping shairport-sync." && \
+ /usr/local/bin/shairport-sync -k
+}
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/scripts/shairport-sync.in b/scripts/shairport-sync.in
new file mode 100755
index 0000000..6bcfe9f
--- /dev/null
+++ b/scripts/shairport-sync.in
@@ -0,0 +1,175 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: shairport-sync
+# Required-Start: $all
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Shairport Synchronous AirPlay
+# Description: Implements a synchronous (multi-room-capable) AirPlay receiver
+### END INIT INFO
+
+# Author: Mike Brady
+#
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="AirPlay Synchronous Audio Service"
+NAME=shairport-sync
+DAEMON=@prefix@/bin/$NAME
+
+# We don't use the DAEMON_ARGS variable here because some of the identifiers may have spaces in them, and so are
+# impossible to pass as arguments.
+
+# Instead, we add the arguments directly to the relevant line in the do_start() function below
+
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+
+# This script is set to start running after all other services have started.
+# However, if you find that Shairport Sync is still being started before what it needs is ready,
+# uncomment the next line to get the script to wait for three seconds before attempting to start Shairport Sync.
+# sleep 3
+
+# Uncomment just one of the following start-stop-daemon lines, or comment them all out and add your own.
+# Shairport Sync will read settings from the configuration file (/etc/shairport-sync.conf by default) and will then apply any command line options.
+# In the default script, the first line is uncommented, selecting daemon mode (-d). Settings will be taken from the configuration file.
+# BTW, if you're using software volume control, you may have to use alsamixer or similar to set the output device's volume to its maximum level first
+# BTW2, you can use alsamixer to find device identifiers (e.g. hw:1) and mixer names (e.g. "Speaker"). No need to change ALSA's defaults.
+# BTW3, the argument after -a is simply the name the shairport service will be visible as.
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 2
+# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "Topping TP30 or Griffin iMic on Raspberry Pi" -- -d hw:1 -t hardware -c "PCM" || return 2
+# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "'3D Sound' USB Soundcard on Raspberry Pi" -- -d hw:1 -t hardware -c "Speaker" || return 2
+# BTW, that "3D Sound" USB soundcard sometimes has the mixer name "Headphone" rather than "Speaker" -- use alsamixer to check.
+# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "IQaudIO" -- -d hw:1 -t hardware -c "Playback Digital" || return 2
+# BTW, newer versions of IQaudIO have a different mixer name -- use alsamixer to check.
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/scripts/shairport-sync.service.in b/scripts/shairport-sync.service.in
new file mode 100644
index 0000000..d1dca5a
--- /dev/null
+++ b/scripts/shairport-sync.service.in
@@ -0,0 +1,15 @@
+[Unit]
+Description=Shairport Sync - AirPlay Audio Receiver
+After=sound.target
+Requires=avahi-daemon.service
+After=avahi-daemon.service
+Wants=network-online.target
+After=network.target network-online.target
+
+[Service]
+ExecStart=@prefix@/bin/shairport-sync
+User=shairport-sync
+Group=shairport-sync
+
+[Install]
+WantedBy=multi-user.target
diff --git a/shairport-sync.spec b/shairport-sync.spec
new file mode 100644
index 0000000..f2ff68f
--- /dev/null
+++ b/shairport-sync.spec
@@ -0,0 +1,122 @@
+Name: shairport-sync
+Version: 3.1.7
+Release: 1%{?dist}
+Summary: AirTunes emulator. Multi-Room with Audio Synchronisation
+# MIT licensed except for tinysvcmdns under BSD,
+# FFTConvolver/ under GPLv3+ and audio_sndio.c
+# under ISC
+License: MIT and BSD and GPLv3+ and ISC
+URL: https://github.com/mikebrady/shairport-sync
+Source0: https://github.com/mikebrady/%{name}/archive/%{version}/%{name}-%{version}.tar.gz
+
+%{?systemd_requires}
+BuildRequires: systemd
+BuildRequires: pkgconfig(libconfig)
+BuildRequires: pkgconfig(popt)
+BuildRequires: pkgconfig(openssl)
+BuildRequires: pkgconfig(libdaemon)
+BuildRequires: pkgconfig(avahi-core)
+BuildRequires: pkgconfig(alsa)
+BuildRequires: pkgconfig(soxr)
+BuildRequires: autoconf
+BuildRequires: automake
+
+%description
+Shairport Sync emulates an AirPort Express for the purpose of streaming audio
+ from iTunes, iPods, iPhones, iPads and AppleTVs. Audio played by a Shairport
+ Sync-powered device stays synchronised with the source and hence with similar
+ devices playing the same source. Thus, for example, synchronised multi-room
+ audio is possible without difficulty. (Hence the name Shairport Sync, BTW.)
+
+Shairport Sync does not support AirPlay video or photo streaming.
+
+%prep
+%setup -q
+
+%build
+autoreconf -i -f
+%configure --with-avahi --with-alsa --with-ssl=openssl --with-soxr
+%make_build
+
+%install
+%make_install
+rm %{buildroot}/etc/shairport-sync.conf.sample
+install -p -m644 -D scripts/shairport-sync.service %{buildroot}%{_unitdir}/%{name}.service
+
+%pre
+getent group %{name} &>/dev/null || groupadd --system %{name} >/dev/null
+getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
+ -d %{_localstatedir}/%{name} -m -g %{name} -s /sbin/nologin \
+ -G audio %{name} >/dev/null
+
+%post
+%systemd_post %{name}.service
+
+%preun
+%systemd_preun %{name}.service
+
+%postun
+%systemd_postun_with_restart %{name}.service
+
+%files
+%config(noreplace) /etc/shairport-sync.conf
+/usr/bin/shairport-sync
+/usr/share/man/man7/shairport-sync.7.gz
+%{_unitdir}/%{name}.service
+%doc README.md RELEASENOTES.md TROUBLESHOOTING.md
+%license LICENSES
+
+%changelog
+* Thu Dec 21 2017 Mike Brady 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
+- Bug fixes and better compatability with iOS 11.2 and mac OS 10.13.2.
+- Better AirPlay synchronisation.
+* Wed Sep 13 2017 Bill Peck 3.1.2-1
+- New upstream release
+- The default value for the alsa setting mute_using_playback_switch has
+ been changed to "no" for compatability with other audio players on the
+ same machine. Because of this you may need to unmute your audio device
+ if you are upgrading from an older release.
+- Fixed bugs that made Shairport Sync drop out or become unavailable when
+ playing YouTube videos, SoundCloud streams etc. from the Mac.
+* Sun Aug 20 2017 Bill Peck 3.1.1-1
+- A bug in the sndio backend has been fixed that caused problems on some
+ versions of Linux.
+- A change has been made to how Shairport Sync responds to a TEARDOWN request,
+ which should make it respond better to sequences of rapid termination and
+ restarting of play sessions. This can happen, for example, playing YouTube
+ videos in Safari or Chrome on a Mac.
+- Choosing soxr interpolation in the configuration file will now cause
+ Shairport Sync to terminate with a message if Shairport Sync has not been
+ compiled with SoX support.
+- Other small changes.
+* Thu Aug 17 2017 Bill Peck 3.1-1
+- new backend offering synchronised PulseAudio support.
+- 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
+- Many changes including 8- 16- 24- and 32-bit output
+* Fri Oct 21 2016 Mike Brady 2.8.6
+- Advertise self as ShairportSync rather than AirPort device 2.8.6
+* Sun Sep 25 2016 Mike Brady 2.8.5
+- Bug fixes and small enhancements 2.8.5
+* Sat May 28 2016 Mike Brady 2.8.4
+- Bug fixes and a few small enhancements 2.8.4
+* Fri Apr 15 2016 Mike Brady 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
+- Stability improvements and important bug fixes 2.8.1
+* Sat Jan 30 2016 Mike Brady 2.8.0
+- Enhancements and bug fixes 2.8.0
+* Sun Oct 18 2015 Mike Brady 2.6
+- Important enhancements and bug fixes 2.6
+* Thu Aug 27 2015 Mike Brady 2.4.1
+- Minor bug fixes 2.4.1
+* Thu Aug 27 2015 Mike Brady 2.4
+- Prepare for stable release 2.4
+* Wed Aug 26 2015 Mike Brady 2.3.13.1-1
+- Harmonise release numbers
+* Fri Jul 24 2015 Bill Peck 2.3.7-1
+- Initial spec file
diff --git a/shairport.c b/shairport.c
new file mode 100644
index 0000000..effc4cc
--- /dev/null
+++ b/shairport.c
@@ -0,0 +1,1569 @@
+/*
+ * Shairport, an Apple Airplay receiver
+ * Copyright (c) James Laird 2013
+ * All rights reserved.
+ * Modifications (c) Mike Brady 2014--2017
+ *
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+
+#ifdef HAVE_LIBMBEDTLS
+#include
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+#include
+#endif
+
+#ifdef HAVE_LIBSSL
+#include
+#endif
+
+#if defined(HAVE_DBUS)
+#include
+#endif
+
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+#include "dacp.h"
+#endif
+
+#ifdef HAVE_DBUS
+#include "dbus-service.h"
+#endif
+
+#ifdef HAVE_MPRIS
+#include "mpris-service.h"
+#endif
+
+#include "common.h"
+#include "mdns.h"
+#include "rtp.h"
+#include "rtsp.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef CONFIG_CONVOLUTION
+#include
+#endif
+
+static int shutting_down = 0;
+char configuration_file_path[4096 + 1];
+char actual_configuration_file_path[4096 + 1];
+
+void shairport_shutdown() {
+ if (shutting_down)
+ return;
+ shutting_down = 1;
+ mdns_unregister();
+ rtsp_request_shutdown_stream();
+ if (config.output)
+ config.output->deinit();
+}
+
+static void sig_ignore(int foo, siginfo_t *bar, void *baz) {}
+static void sig_shutdown(int foo, siginfo_t *bar, void *baz) {
+ debug(1, "shutdown requested...");
+ shairport_shutdown();
+ // daemon_log(LOG_NOTICE, "exit...");
+ daemon_retval_send(255);
+ daemon_pid_file_remove();
+ exit(0);
+}
+
+static void sig_child(int foo, siginfo_t *bar, void *baz) {
+ pid_t pid;
+ while ((pid = waitpid((pid_t)-1, 0, WNOHANG)) > 0) {
+ if (pid == mdns_pid && !shutting_down) {
+ die("MDNS child process died unexpectedly!");
+ }
+ }
+}
+
+static void sig_disconnect_audio_output(int foo, siginfo_t *bar, void *baz) {
+ debug(1, "disconnect audio output requested.");
+ set_requested_connection_state_to_output(0);
+}
+
+static void sig_connect_audio_output(int foo, siginfo_t *bar, void *baz) {
+ debug(1, "connect audio output requested.");
+ set_requested_connection_state_to_output(1);
+}
+
+// The following two functions are adapted slightly and with thanks from Jonathan Leffler's sample
+// code at
+// https://stackoverflow.com/questions/675039/how-can-i-create-directory-tree-in-c-linux
+
+int do_mkdir(const char *path, mode_t mode) {
+ struct stat st;
+ int status = 0;
+
+ if (stat(path, &st) != 0) {
+ /* Directory does not exist. EEXIST for race condition */
+ if (mkdir(path, mode) != 0 && errno != EEXIST)
+ status = -1;
+ } else if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ status = -1;
+ }
+
+ return (status);
+}
+
+// mkpath - ensure all directories in path exist
+// Algorithm takes the pessimistic view and works top-down to ensure
+// each directory in path exists, rather than optimistically creating
+// the last element and working backwards.
+
+int mkpath(const char *path, mode_t mode) {
+ char *pp;
+ char *sp;
+ int status;
+ char *copypath = strdup(path);
+
+ status = 0;
+ pp = copypath;
+ while (status == 0 && (sp = strchr(pp, '/')) != 0) {
+ if (sp != pp) {
+ /* Neither root nor double slash in path */
+ *sp = '\0';
+ status = do_mkdir(copypath, mode);
+ *sp = '/';
+ }
+ pp = sp + 1;
+ }
+ if (status == 0)
+ status = do_mkdir(path, mode);
+ free(copypath);
+ return (status);
+}
+
+char *get_version_string() {
+ char *version_string = malloc(200);
+ if (version_string) {
+ strcpy(version_string, PACKAGE_VERSION);
+#ifdef HAVE_LIBMBEDTLS
+ strcat(version_string, "-mbedTLS");
+#endif
+#ifdef HAVE_LIBPOLARSSL
+ strcat(version_string, "-PolarSSL");
+#endif
+#ifdef HAVE_LIBSSL
+ strcat(version_string, "-OpenSSL");
+#endif
+#ifdef CONFIG_TINYSVCMDNS
+ strcat(version_string, "-tinysvcmdns");
+#endif
+#ifdef CONFIG_AVAHI
+ strcat(version_string, "-Avahi");
+#endif
+#ifdef CONFIG_DNS_SD
+ strcat(version_string, "-dns_sd");
+#endif
+#ifdef CONFIG_ALSA
+ strcat(version_string, "-ALSA");
+#endif
+#ifdef CONFIG_SNDIO
+ strcat(version_string, "-sndio");
+#endif
+#ifdef CONFIG_AO
+ strcat(version_string, "-ao");
+#endif
+#ifdef CONFIG_PA
+ strcat(version_string, "-pa");
+#endif
+#ifdef CONFIG_SOUNDIO
+ strcat(version_string, "-soundio");
+#endif
+#ifdef CONFIG_DUMMY
+ strcat(version_string, "-dummy");
+#endif
+#ifdef CONFIG_STDOUT
+ strcat(version_string, "-stdout");
+#endif
+#ifdef CONFIG_PIPE
+ strcat(version_string, "-pipe");
+#endif
+#ifdef HAVE_LIBSOXR
+ strcat(version_string, "-soxr");
+#endif
+#ifdef CONFIG_CONVOLUTION
+ strcat(version_string, "-convolution");
+#endif
+#ifdef CONFIG_METADATA
+ strcat(version_string, "-metadata");
+#endif
+#ifdef HAVE_DBUS
+ strcat(version_string, "-dbus");
+#endif
+#ifdef HAVE_MPRIS
+ strcat(version_string, "-mpris");
+#endif
+ strcat(version_string, "-sysconfdir:");
+ strcat(version_string, SYSCONFDIR);
+ }
+ return version_string;
+}
+
+void print_version(void) {
+ char *version_string = get_version_string();
+ if (version_string) {
+ printf("%s\n", version_string);
+ free(version_string);
+ } else {
+ debug(1, "Can't print version string!");
+ }
+}
+
+void usage(char *progname) {
+ printf("Usage: %s [options...]\n", progname);
+ printf(" or: %s [options...] -- [audio output-specific options]\n", progname);
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help show this help.\n");
+ printf(" -d, --daemon daemonise.\n");
+ printf(" -j, --justDaemoniseNoPIDFile daemonise without a PID file.\n");
+ printf(" -V, --version show version information.\n");
+ printf(" -k, --kill kill the existing shairport daemon.\n");
+ printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n");
+ printf(" -R, --reconnectToOutput reconnect to the output device.\n");
+ printf(" -c, --configfile=FILE read configuration settings from FILE. Default is "
+ "/etc/shairport-sync.conf.\n");
+
+ printf("\n");
+ printf("The following general options are for backward compatibility. These and all new options "
+ "have settings in the configuration file, by default /etc/shairport-sync.conf:\n");
+ printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n");
+ printf(" -p, --port=PORT set RTSP listening port.\n");
+ printf(" -a, --name=NAME set advertised name.\n");
+ // printf(" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an "
+ // "AirPlay device.\n");
+ // printf(" The default is to set it automatically.\n");
+ // printf(" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes
+ // "
+ // "10 or later.\n");
+ // printf(" The default is to set it automatically.\n");
+ printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown "
+ "device.\n");
+ printf(" The default is to set it automatically.\n");
+ printf(" --forkedDaapdLatency=FRAMES [Deprecated] Set the latency for audio sent from "
+ "forked-daapd.\n");
+ printf(" The default is to set it automatically.\n");
+ printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, "
+ "where \n");
+ printf(" \"basic\" (default) inserts or deletes audio frames from "
+ "packet frames with low processor overhead, and \n");
+ printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- "
+ "moderate processor overhead.\n");
+ printf(
+ " \"soxr\" option only available if built with soxr support.\n");
+ printf(" -B, --on-start=PROGRAM run PROGRAM when playback is about to begin.\n");
+ printf(" -E, --on-stop=PROGRAM run PROGRAM when playback has ended.\n");
+ printf(" For -B and -E options, specify the full path to the program, "
+ "e.g. /usr/bin/logger.\n");
+ printf(" Executable scripts work, but must have #!/bin/sh (or "
+ "whatever) in the headline.\n");
+ printf(
+ " -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n");
+ printf(" -o, --output=BACKEND select audio output method.\n");
+ printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n");
+ printf(" if no mdns provider is specified,\n");
+ printf(" shairport tries them all until one works.\n");
+ printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. "
+ "Set to 0 to "
+ "stop resyncing.\n");
+ printf(" -t, --timeout=SECONDS go back to idle mode from play mode after a break in "
+ "communications of this many seconds (default 120). Set to 0 never to exit play mode.\n");
+ printf(" --statistics print some interesting statistics -- output to the logfile "
+ "if running as a daemon.\n");
+ printf(" --tolerance=TOLERANCE [Deprecated] allow a synchronization error of TOLERANCE "
+ "frames (default "
+ "88) before trying to correct it.\n");
+ printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a "
+ "password.\n");
+ printf(" --logOutputLevel log the output level setting -- useful for setting maximum "
+ "volume.\n");
+#ifdef CONFIG_METADATA
+ printf(" --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");
+#endif
+ printf("\n");
+ mdns_ls_backends();
+ printf("\n");
+ audio_ls_outputs();
+}
+
+int parse_options(int argc, char **argv) {
+ // there are potential memory leaks here -- it's called a second time, previously allocated
+ // strings will dangle.
+ char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */
+ char *stuffing = NULL; /* used for picking up the stuffing option */
+ signed char c; /* used for argument parsing */
+ int i = 0; /* used for tracking options */
+ int fResyncthreshold = (int)(config.resyncthreshold * 44100);
+ int fTolerance = (int)(config.tolerance * 44100);
+ poptContext optCon; /* context for parsing command-line options */
+ int daemonisewith = 0;
+ int daemonisewithout = 0;
+ struct poptOption optionsTable[] = {
+ {"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL},
+ {"disconnectFromOutput", 'D', POPT_ARG_NONE, NULL, 0, NULL},
+ {"reconnectToOutput", 'R', POPT_ARG_NONE, NULL, 0, NULL},
+ {"kill", 'k', POPT_ARG_NONE, NULL, 0, NULL},
+ {"daemon", 'd', POPT_ARG_NONE, &daemonisewith, 0, NULL},
+ {"justDaemoniseNoPIDFile", 'j', POPT_ARG_NONE, &daemonisewithout, 0, NULL},
+ {"configfile", 'c', POPT_ARG_STRING, &config.configfile, 0, NULL},
+ {"statistics", 0, POPT_ARG_NONE, &config.statistics_requested, 0, NULL},
+ {"logOutputLevel", 0, POPT_ARG_NONE, &config.logOutputLevel, 0, NULL},
+ {"version", 'V', POPT_ARG_NONE, NULL, 0, NULL},
+ {"port", 'p', POPT_ARG_INT, &config.port, 0, NULL},
+ {"name", 'a', POPT_ARG_STRING, &raw_service_name, 0, NULL},
+ {"output", 'o', POPT_ARG_STRING, &config.output_name, 0, NULL},
+ {"on-start", 'B', POPT_ARG_STRING, &config.cmd_start, 0, NULL},
+ {"on-stop", 'E', POPT_ARG_STRING, &config.cmd_stop, 0, NULL},
+ {"wait-cmd", 'w', POPT_ARG_NONE, &config.cmd_blocking, 0, NULL},
+ {"mdns", 'm', POPT_ARG_STRING, &config.mdns_name, 0, NULL},
+ {"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL},
+ {"AirPlayLatency", 'A', POPT_ARG_INT, &config.AirPlayLatency, 0, NULL},
+ {"iTunesLatency", 'i', POPT_ARG_INT, &config.iTunesLatency, 0, NULL},
+ {"forkedDaapdLatency", 'f', POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL},
+ {"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL},
+ {"resync", 'r', POPT_ARG_INT, &fResyncthreshold, 0, NULL},
+ {"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL},
+ {"password", 0, POPT_ARG_STRING, &config.password, 0, NULL},
+ {"tolerance", 'z', POPT_ARG_INT, &fTolerance, 0, NULL},
+#ifdef CONFIG_METADATA
+ {"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL},
+ {"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL},
+#endif
+ POPT_AUTOHELP{NULL, 0, 0, NULL, 0}};
+
+ // we have to parse the command line arguments to look for a config file
+ int optind;
+ optind = argc;
+ int j;
+ for (j = 0; j < argc; j++)
+ if (strcmp(argv[j], "--") == 0)
+ optind = j;
+
+ optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0);
+ poptSetOtherOptionHelp(optCon, "[OPTIONS]* ");
+
+ /* Now do options processing just to get a debug level */
+ debuglev = 0;
+ while ((c = poptGetNextOpt(optCon)) >= 0) {
+ switch (c) {
+ case 'v':
+ debuglev++;
+ break;
+ case 'D':
+ inform("Warning: the option -D or --disconnectFromOutput is deprecated.");
+ break;
+ case 'R':
+ inform("Warning: the option -R or --reconnectToOutput is deprecated.");
+ break;
+ case 'A':
+ inform("Warning: the option -A or --AirPlayLatency is deprecated. This setting is now "
+ "automatically received from the AirPlay device.");
+ break;
+ case 'i':
+ inform("Warning: the option -i or --iTunesLatency is deprecated. This setting is now "
+ "automatically received from iTunes");
+ break;
+ case 'f':
+ inform("Warning: the option --forkedDaapdLatency is deprecated. This setting is now "
+ "automatically received from forkedDaapd");
+ break;
+ case 'r':
+ inform("Warning: the option -r or --resync is deprecated. Please use the "
+ "\"resync_threshold_in_seconds\" setting in the config file instead.");
+ break;
+ case 'z':
+ inform("Warning: the option --tolerance is deprecated. Please use the "
+ "\"drift_tolerance_in_seconds\" setting in the config file instead.");
+ break;
+ }
+ }
+ if (c < -1) {
+ die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
+ }
+
+ if ((daemonisewith) && (daemonisewithout))
+ die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected "
+ "both!");
+ if ((daemonisewith) || (daemonisewithout)) {
+ config.daemonise = 1;
+ if (daemonisewith)
+ config.daemonise_store_pid = 1;
+ };
+
+ 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.airplay_volume = -18.0; // if no volume is ever set, default to initial default value if nothing else comes in first.
+
+ config_setting_t *setting;
+ const char *str = 0;
+ int value = 0;
+ double dvalue = 0.0;
+
+ // debug(1, "Looking for the configuration file \"%s\".", config.configfile);
+
+ config_init(&config_file_stuff);
+
+ 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);
+ } else {
+ debug(2, "Looking for configuration file at full path \"%s\"", config_file_real_path);
+ /* Read the file. If there is an error, report it and exit. */
+ if (config_read_file(&config_file_stuff, config_file_real_path)) {
+ // make config.cfg point to it
+ config.cfg = &config_file_stuff;
+ /* Get the Service Name. */
+ if (config_lookup_string(config.cfg, "general.name", &str)) {
+ raw_service_name = (char *)str;
+ }
+ int daemonisewithout = 0;
+ int daemonisewith = 0;
+ /* Get the Daemonize setting. */
+ if (config_lookup_string(config.cfg, "sessioncontrol.daemonize_with_pid_file", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ daemonisewith = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ daemonisewith = 1;
+ else
+ die("Invalid daemonize_with_pid_file option choice \"%s\". It should be \"yes\" or "
+ "\"no\"");
+ }
+
+ /* Get the Just_Daemonize setting. */
+ if (config_lookup_string(config.cfg, "sessioncontrol.daemonize_without_pid_file", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ daemonisewithout = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ daemonisewithout = 1;
+ else
+ die("Invalid daemonize_without_pid_file option choice \"%s\". It should be \"yes\" or "
+ "\"no\"");
+ }
+ if ((daemonisewith) && (daemonisewithout))
+ die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have "
+ "selected both!");
+ if ((daemonisewith) || (daemonisewithout)) {
+ config.daemonise = 1;
+ if (daemonisewith)
+ config.daemonise_store_pid = 1;
+ }
+ /* Get the directory path for the pid file created when the program is daemonised. */
+ if (config_lookup_string(config.cfg, "sessioncontrol.daemon_pid_dir", &str))
+ config.piddir = (char *)str;
+
+ /* Get the mdns_backend setting. */
+ if (config_lookup_string(config.cfg, "general.mdns_backend", &str))
+ config.mdns_name = (char *)str;
+
+ /* Get the output_backend setting. */
+ if (config_lookup_string(config.cfg, "general.output_backend", &str))
+ config.output_name = (char *)str;
+
+ /* Get the port setting. */
+ if (config_lookup_int(config.cfg, "general.port", &value)) {
+ if ((value < 0) || (value > 65535))
+ die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 5000",
+ value);
+ else
+ config.port = value;
+ }
+
+ /* Get the udp port base setting. */
+ if (config_lookup_int(config.cfg, "general.udp_port_base", &value)) {
+ if ((value < 0) || (value > 65535))
+ die("Invalid port number \"%sd\". It should be between 0 and 65535, default is 6001",
+ value);
+ else
+ config.udp_port_base = value;
+ }
+
+ /* Get the udp port range setting. This is number of ports that will be tried for free ports ,
+ * starting at the port base. Only three ports are needed. */
+ if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) {
+ if ((value < 0) || (value > 65535))
+ die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100",
+ value);
+ else
+ config.udp_port_range = value;
+ }
+
+ /* Get the password setting. */
+ if (config_lookup_string(config.cfg, "general.password", &str))
+ config.password = (char *)str;
+
+ if (config_lookup_string(config.cfg, "general.interpolation", &str)) {
+ if (strcasecmp(str, "basic") == 0)
+ config.packet_stuffing = ST_basic;
+ else if (strcasecmp(str, "soxr") == 0)
+#ifdef HAVE_LIBSOXR
+ config.packet_stuffing = ST_soxr;
+#else
+ die("The soxr option not available because this version of shairport-sync was built "
+ "without libsoxr "
+ "support. Change the \"general/interpolation\" setting in the configuration file.");
+#endif
+ else
+ die("Invalid interpolation option choice. It should be \"basic\" or \"soxr\"");
+ }
+
+ /* Get the statistics setting. */
+ if (config_lookup_string(config.cfg, "general.statistics", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.statistics_requested = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.statistics_requested = 1;
+ else
+ die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\"");
+ }
+
+ /* The old drift tolerance setting. */
+ if (config_lookup_int(config.cfg, "general.drift", &value)) {
+ inform("The drift setting is deprecated. Use "
+ "drift_tolerance_in_seconds instead");
+ config.tolerance = 1.0 * value / 44100;
+ }
+
+ /* The old resync setting. */
+ if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) {
+ inform("The resync_threshold setting is deprecated. Use "
+ "resync_threshold_in_seconds instead");
+ config.resyncthreshold = 1.0 * value / 44100;
+ }
+
+ /* Get the drift tolerance setting. */
+ if (config_lookup_float(config.cfg, "general.drift_tolerance_in_seconds", &dvalue))
+ config.tolerance = dvalue;
+
+ /* Get the resync setting. */
+ if (config_lookup_float(config.cfg, "general.resync_threshold_in_seconds", &dvalue))
+ config.resyncthreshold = dvalue;
+
+ /* Get the verbosity setting. */
+ if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) {
+ if ((value >= 0) && (value <= 3))
+ debuglev = value;
+ else
+ die("Invalid log verbosity setting option choice \"%d\". It should be between 0 and 3, "
+ "inclusive.",
+ value);
+ }
+
+ /* Get the ignore_volume_control setting. */
+ if (config_lookup_string(config.cfg, "general.ignore_volume_control", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.ignore_volume_control = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.ignore_volume_control = 1;
+ else
+ die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\"");
+ }
+
+ /* Get the optional volume_max_db setting. */
+ if (config_lookup_float(config.cfg, "general.volume_max_db", &dvalue)) {
+ // debug(1, "Max volume setting of %f dB", dvalue);
+ config.volume_max_db = dvalue;
+ config.volume_max_db_set = 1;
+ }
+
+ if (config_lookup_string(config.cfg, "general.run_this_when_volume_is_set", &str)) {
+ config.cmd_set_volume = (char *)str;
+ }
+
+ /* Get the playback_mode setting */
+ if (config_lookup_string(config.cfg, "general.playback_mode", &str)) {
+ if (strcasecmp(str, "stereo") == 0)
+ config.playback_mode = ST_stereo;
+ else if (strcasecmp(str, "mono") == 0)
+ config.playback_mode = ST_mono;
+ else if (strcasecmp(str, "reverse stereo") == 0)
+ config.playback_mode = ST_reverse_stereo;
+ else if (strcasecmp(str, "both left") == 0)
+ config.playback_mode = ST_left_only;
+ else if (strcasecmp(str, "both right") == 0)
+ config.playback_mode = ST_right_only;
+ else
+ die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", "
+ "\"reverse stereo\", \"both left\", \"both right\"");
+ }
+
+ /* Get the interface to listen on, if specified Default is all interfaces */
+ /* we keep the interface name and the index */
+
+ if (config_lookup_string(config.cfg, "general.interface", &str))
+ config.interface = strdup(str);
+
+ if (config_lookup_string(config.cfg, "general.interface", &str)) {
+ int specified_interface_found = 0;
+
+ struct if_nameindex *if_ni, *i;
+
+ if_ni = if_nameindex();
+ if (if_ni == NULL) {
+ debug(1, "Can't get a list of interface names.");
+ } else {
+ for (i = if_ni; !(i->if_index == 0 && i->if_name == NULL); i++) {
+ // printf("%u: %s\n", i->if_index, i->if_name);
+ if (strcmp(i->if_name, str) == 0) {
+ config.interface_index = i->if_index;
+ specified_interface_found = 1;
+ }
+ }
+ }
+
+ if_freenameindex(if_ni);
+
+ if (specified_interface_found == 0) {
+ inform(
+ "The mdns service interface \"%s\" was not found, so the setting has been ignored.",
+ config.interface);
+ free(config.interface);
+ config.interface = NULL;
+ config.interface_index = 0;
+ }
+ }
+
+ /* Get the regtype -- the service type and protocol, separated by a dot. Default is
+ * "_raop._tcp" */
+ if (config_lookup_string(config.cfg, "general.regtype", &str))
+ config.regtype = strdup(str);
+
+ /* Get the volume range, in dB, that should be used If not set, it means you just use the
+ * 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);
+ else
+ config.volume_range_db = value;
+ }
+
+ /* Get the alac_decoder setting. */
+ if (config_lookup_string(config.cfg, "general.alac_decoder", &str)) {
+ if (strcasecmp(str, "hammerton") == 0)
+ config.use_apple_decoder = 0;
+ else if (strcasecmp(str, "apple") == 0) {
+ if ((config.decoders_supported & 1 << decoder_apple_alac) != 0)
+ config.use_apple_decoder = 1;
+ else
+ inform("Support for the Apple ALAC decoder has not been compiled into this version of "
+ "Shairport Sync. The default decoder will be used.");
+ } else
+ die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"");
+ }
+
+ /* Get the default latency. Deprecated! */
+ if (config_lookup_int(config.cfg, "latencies.default", &value))
+ config.userSuppliedLatency = value;
+
+ /* Get the itunes latency. Deprecated! */
+ if (config_lookup_int(config.cfg, "latencies.itunes", &value))
+ config.iTunesLatency = value;
+
+ /* Get the AirPlay latency. Deprecated! */
+ if (config_lookup_int(config.cfg, "latencies.airplay", &value))
+ config.AirPlayLatency = value;
+
+ /* Get the forkedDaapd latency. Deprecated! */
+ if (config_lookup_int(config.cfg, "latencies.forkedDaapd", &value))
+ config.ForkedDaapdLatency = value;
+
+#ifdef CONFIG_METADATA
+ /* Get the metadata setting. */
+ if (config_lookup_string(config.cfg, "metadata.enabled", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.metadata_enabled = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.metadata_enabled = 1;
+ else
+ die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\"");
+ }
+
+ if (config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.get_coverart = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.get_coverart = 1;
+ else
+ die("Invalid metadata include_cover_art option choice \"%s\". It should be \"yes\" or "
+ "\"no\"");
+ }
+
+ if (config_lookup_string(config.cfg, "metadata.pipe_name", &str)) {
+ config.metadata_pipename = (char *)str;
+ }
+
+ if (config_lookup_string(config.cfg, "metadata.socket_address", &str)) {
+ config.metadata_sockaddr = (char *)str;
+ }
+ if (config_lookup_int(config.cfg, "metadata.socket_port", &value)) {
+ config.metadata_sockport = value;
+ }
+ config.metadata_sockmsglength = 500;
+ if (config_lookup_int(config.cfg, "metadata.socket_msglength", &value)) {
+ config.metadata_sockmsglength = value < 500 ? 500 : value > 65000 ? 65000 : value;
+ }
+
+#endif
+
+ if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) {
+ config.cmd_start = (char *)str;
+ }
+
+ if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_play_ends", &str)) {
+ config.cmd_stop = (char *)str;
+ }
+
+ if (config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.cmd_blocking = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.cmd_blocking = 1;
+ else
+ die("Invalid session control wait_for_completion option choice \"%s\". It should be "
+ "\"yes\" or \"no\"");
+ }
+
+ if (config_lookup_string(config.cfg, "sessioncontrol.before_play_begins_returns_output",
+ &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.cmd_start_returns_output = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.cmd_start_returns_output = 1;
+ else
+ die("Invalid session control before_play_begins_returns_output option choice \"%s\". It "
+ "should be "
+ "\"yes\" or \"no\"");
+ }
+
+ if (config_lookup_string(config.cfg, "sessioncontrol.allow_session_interruption", &str)) {
+ config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0
+ if (strcasecmp(str, "no") == 0)
+ config.allow_session_interruption = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.allow_session_interruption = 1;
+ else
+ die("Invalid session control allow_interruption option choice \"%s\". It should be "
+ "\"yes\" "
+ "or \"no\"");
+ }
+
+ if (config_lookup_int(config.cfg, "sessioncontrol.session_timeout", &value)) {
+ config.timeout = value;
+ config.dont_check_timeout = 0; // this is for legacy -- only set by -t 0
+ }
+
+#ifdef CONFIG_CONVOLUTION
+
+ if (config_lookup_string(config.cfg, "dsp.convolution", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.convolution = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.convolution = 1;
+ else
+ die("Invalid dsp.convolution. It should be \"yes\" or \"no\"");
+ }
+
+ if (config_lookup_float(config.cfg, "dsp.convolution_gain", &dvalue)) {
+ config.convolution_gain = dvalue;
+ if (dvalue > 10 || dvalue < -50)
+ die("Invalid value \"%f\" for dsp.convolution_gain. It should be between -50 and +10 dB",
+ dvalue);
+ }
+
+ config.convolution_max_length = 8192;
+ if (config_lookup_int(config.cfg, "dsp.convolution_max_length", &value)) {
+ config.convolution_max_length = value;
+
+ if (value < 1 || value > 200000)
+ die("dsp.convolution_max_length must be within 1 and 200000");
+ }
+
+ if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) {
+ config.convolution_ir_file = str;
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ }
+
+ if (config.convolution && config.convolution_ir_file == NULL) {
+ die("Convolution enabled but no convolution_ir_file provided");
+ }
+#endif
+
+ if (config_lookup_string(config.cfg, "dsp.loudness", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.loudness = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.loudness = 1;
+ else
+ 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)
+ die("Invalid value \"%f\" for dsp.loudness_reference_volume_db. It should be between "
+ "-100 and 0",
+ dvalue);
+ }
+
+ if (config.loudness == 1 && config_lookup_string(config.cfg, "alsa.mixer_control_name", &str))
+ die("Loudness activated but hardware volume is active. You must remove "
+ "\"alsa.mixer_control_name\" to use the loudness filter.");
+
+ } else {
+ if (config_error_type(&config_file_stuff) == CONFIG_ERR_FILE_IO)
+ debug(1, "Error reading configuration file \"%s\": \"%s\".",
+ config_error_file(&config_file_stuff), config_error_text(&config_file_stuff));
+ else {
+ die("Line %d of the configuration file \"%s\":\n%s", config_error_line(&config_file_stuff),
+ config_error_file(&config_file_stuff), config_error_text(&config_file_stuff));
+ }
+ }
+#if defined(HAVE_DBUS)
+ /* Get the dbus service sbus setting. */
+ if (config_lookup_string(config.cfg, "general.dbus_service_bus", &str)) {
+ if (strcasecmp(str, "system") == 0)
+ config.dbus_service_bus_type = DBT_system;
+ else if (strcasecmp(str, "session") == 0)
+ config.dbus_service_bus_type = DBT_session;
+ else
+ die("Invalid dbus_service_bus option choice \"%s\". It should be \"system\" (default) or "
+ "\"session\"");
+ }
+#endif
+
+#if defined(HAVE_MPRIS)
+ /* Get the mpris service sbus setting. */
+ if (config_lookup_string(config.cfg, "general.mpris_service_bus", &str)) {
+ if (strcasecmp(str, "system") == 0)
+ config.mpris_service_bus_type = DBT_system;
+ else if (strcasecmp(str, "session") == 0)
+ config.mpris_service_bus_type = DBT_session;
+ else
+ die("Invalid mpris_service_bus option choice \"%s\". It should be \"system\" (default) or "
+ "\"session\"");
+ }
+#endif
+
+ free(config_file_real_path);
+ }
+
+ // now, do the command line options again, but this time do them fully -- it's a unix convention
+ // that command line
+ // arguments have precedence over configuration file settings.
+
+ optind = argc;
+ for (j = 0; j < argc; j++)
+ if (strcmp(argv[j], "--") == 0)
+ optind = j;
+
+ optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0);
+ poptSetOtherOptionHelp(optCon, "[OPTIONS]* ");
+
+ /* Now do options processing, get portname */
+ int tdebuglev = 0;
+ while ((c = poptGetNextOpt(optCon)) >= 0) {
+ switch (c) {
+ case 'v':
+ tdebuglev++;
+ break;
+ case 't':
+ if (config.timeout == 0) {
+ config.dont_check_timeout = 1;
+ config.allow_session_interruption = 1;
+ } else {
+ config.dont_check_timeout = 0;
+ config.allow_session_interruption = 0;
+ }
+ break;
+#ifdef CONFIG_METADATA
+ case 'M':
+ config.metadata_enabled = 1;
+ break;
+ case 'g':
+ if (config.metadata_enabled == 0)
+ die("If you want to get cover art, you must also select the --metadata-pipename option.");
+ break;
+#endif
+ case 'S':
+ if (strcmp(stuffing, "basic") == 0)
+ config.packet_stuffing = ST_basic;
+ else if (strcmp(stuffing, "soxr") == 0)
+#ifdef HAVE_LIBSOXR
+ config.packet_stuffing = ST_soxr;
+#else
+ die("The soxr option not available because this version of shairport-sync was built "
+ "without libsoxr "
+ "support. Change the -S option setting.");
+#endif
+ else
+ die("Illegal stuffing option \"%s\" -- must be \"basic\" or \"soxr\"", stuffing);
+ break;
+ }
+ }
+ if (c < -1) {
+ die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
+ }
+
+#ifdef CONFIG_METADATA
+ if ((config.metadata_enabled == 1) && (config.metadata_pipename == NULL))
+ config.metadata_pipename = strdup("/tmp/shairport-sync-metadata");
+#endif
+
+ /* if the regtype hasn't been set, do it now */
+ if (config.regtype == NULL)
+ config.regtype = strdup("_raop._tcp");
+
+ if (tdebuglev != 0)
+ debuglev = tdebuglev;
+
+ /* if the Service Name wasn't specified, do it now */
+
+ if (raw_service_name == NULL)
+ raw_service_name = strdup("%H");
+
+ // now, do the substitutions in the service name
+ char hostname[100];
+ gethostname(hostname, 100);
+ char *i1 = str_replace(raw_service_name, "%h", hostname);
+ if ((hostname[0] >= 'a') && (hostname[0] <= 'z'))
+ hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter
+ char *i2 = str_replace(i1, "%H", hostname);
+ char *i3 = str_replace(i2, "%v", PACKAGE_VERSION);
+ char *vs = get_version_string();
+ config.service_name = str_replace(i3, "%V", vs);
+ free(i1);
+ free(i2);
+ free(i3);
+ free(vs);
+
+// now, check and calculate the pid directory
+#ifdef USE_CUSTOM_PID_DIR
+ char *use_this_pid_dir = PIDDIR;
+#else
+ char *use_this_pid_dir = "/var/run/shairport-sync";
+#endif
+ // debug(1,"config.piddir \"%s\".",config.piddir);
+ if (config.piddir)
+ use_this_pid_dir = config.piddir;
+ if (use_this_pid_dir)
+ config.computed_piddir = strdup(use_this_pid_dir);
+
+ return optind + 1;
+}
+
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+GMainLoop *loop;
+
+pthread_t dbus_thread;
+void *dbus_thread_func(void *arg) {
+ loop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(loop);
+ return NULL;
+}
+#endif
+
+void signal_setup(void) {
+ // mask off all signals before creating threads.
+ // this way we control which thread gets which signals.
+ // for now, we don't care which thread gets the following.
+ sigset_t set;
+ sigfillset(&set);
+ sigdelset(&set, SIGINT);
+ sigdelset(&set, SIGTERM);
+ sigdelset(&set, SIGHUP);
+ sigdelset(&set, SIGSTOP);
+ sigdelset(&set, SIGCHLD);
+ sigdelset(&set, SIGUSR2);
+ pthread_sigmask(SIG_BLOCK, &set, NULL);
+
+ // SIGUSR1 is used to interrupt a thread if blocked in pselect
+ pthread_sigmask(SIG_SETMASK, NULL, &pselect_sigset);
+ sigdelset(&pselect_sigset, SIGUSR1);
+
+ // setting this to SIG_IGN would prevent signalling any threads.
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = &sig_ignore;
+ sigaction(SIGUSR1, &sa, NULL);
+
+ sa.sa_flags = SA_SIGINFO | SA_RESTART;
+ sa.sa_sigaction = &sig_shutdown;
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ sa.sa_sigaction = &sig_disconnect_audio_output;
+ sigaction(SIGUSR2, &sa, NULL);
+
+ sa.sa_sigaction = &sig_connect_audio_output;
+ sigaction(SIGHUP, &sa, NULL);
+
+ sa.sa_sigaction = &sig_child;
+ sigaction(SIGCHLD, &sa, NULL);
+}
+
+// forked daemon lets the spawner know it's up and running OK
+// should be called only once!
+void shairport_startup_complete(void) {
+ if (config.daemonise) {
+ // daemon_ready();
+ }
+}
+
+const char *pid_file_proc(void) {
+
+ char fn[8192];
+ snprintf(fn, sizeof(fn), "%s/%s.pid", config.computed_piddir,
+ daemon_pid_file_ident ? daemon_pid_file_ident : "unknown");
+ // debug(1,"fn \"%s\".",fn);
+ return strdup(fn);
+}
+
+void exit_function() {
+ // debug(1, "exit function called...");
+ 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...
+}
+
+int main(int argc, char **argv) {
+
+ daemon_set_verbosity(LOG_DEBUG);
+ memset(&config, 0, sizeof(config)); // also clears all strings, BTW
+ atexit(exit_function);
+
+ // this is a bit weird, but apparently necessary
+ char *basec = strdup(argv[0]);
+ char *bname = basename(basec);
+ config.appName = strdup(bname);
+ if (config.appName == NULL)
+ die("can not allocate memory for the app name!");
+ free(basec);
+
+ // set defaults
+
+ // get thje endianness
+ union {
+ uint32_t u32;
+ uint8_t arr[4];
+ } xn;
+
+ xn.arr[0] = 0x44; /* Lowest-address byte */
+ xn.arr[1] = 0x33;
+ xn.arr[2] = 0x22;
+ xn.arr[3] = 0x11; /* Highest-address byte */
+
+ if (xn.u32 == 0x11223344)
+ endianness = SS_LITTLE_ENDIAN;
+ else if (xn.u32 == 0x33441122)
+ endianness = SS_PDP_ENDIAN;
+ else if (xn.u32 == 0x44332211)
+ endianness = SS_BIG_ENDIAN;
+ else
+ die("Can not recognise the endianness of the processor.");
+
+ // set non-zero / non-NULL default values here
+ // but note that audio back ends also have a chance to set defaults
+
+ strcpy(configuration_file_path, SYSCONFDIR);
+ // strcat(configuration_file_path, "/shairport-sync"); // thinking about adding a special
+ // shairport-sync directory
+ strcat(configuration_file_path, "/");
+ strcat(configuration_file_path, config.appName);
+ strcat(configuration_file_path, ".conf");
+ config.configfile = configuration_file_path;
+
+ config.statistics_requested = 0; // don't print stats in the log
+ config.latency = -1; // -1 means not set. 88200 works well. This is also reset in rtsp.c when play
+ // is about to start
+ config.userSuppliedLatency = 0; // zero means none supplied
+ config.iTunesLatency =
+ -1; // -1 means not supplied. 99400 seems to work pretty well for iTunes from Version 10 (?)
+ // upwards-- two left-ear headphones, one from the iMac jack, one
+ // from an NSLU2 running a cheap "3D Sound" USB Soundcard
+ config.AirPlayLatency =
+ -1; // -1 means not set. 88200 seems to work well for AirPlay -- Syncs sound and
+ // vision on AppleTV, but also used for iPhone/iPod/iPad sources
+ config.ForkedDaapdLatency = -1; // -1 means not set. 99400 seems to be right
+ config.resyncthreshold = 0.05; // 50 ms
+ config.timeout = 120; // this number of seconds to wait for [more] audio before switching to idle.
+ config.tolerance =
+ 0.002; // this number of seconds of timing error before attempting to correct it.
+ config.buffer_start_fill = 220;
+ config.port = 5000;
+ config.packet_stuffing = ST_basic; // simple interpolation or deletion
+ // char hostname[100];
+ // gethostname(hostname, 100);
+ // config.service_name = malloc(20 + 100);
+ // 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.udp_port_base = 6001;
+ config.udp_port_range = 100;
+ config.output_format = SPS_FORMAT_S16; // default
+ config.output_rate = 44100; // default
+ config.decoders_supported =
+ 1 << decoder_hammerton; // David Hammerton's decoder supported by default
+#ifdef HAVE_APPLE_ALAC
+ config.decoders_supported += 1 << decoder_apple_alac;
+#endif
+
+ // initialise random number generator
+
+ r64init(0);
+
+ // initialise the randomw number array
+
+ r64arrayinit();
+
+ /* Check if we are called with -V or --version parameter */
+ if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
+ print_version();
+ exit(1);
+ }
+
+ /* Check if we are called with -h or --help parameter */
+ if (argc >= 2 && ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0))) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ pid_t pid;
+
+ /* Reset signal handlers */
+ if (daemon_reset_sigs(-1) < 0) {
+ daemon_log(LOG_ERR, "Failed to reset all signal handlers: %s", strerror(errno));
+ return 1;
+ }
+
+ /* Unblock signals */
+ if (daemon_unblock_sigs(-1) < 0) {
+ daemon_log(LOG_ERR, "Failed to unblock all signals: %s", strerror(errno));
+ return 1;
+ }
+
+ // Point to a function to help locate where the PID file will go
+ // We always use this function because the default location
+ // is unsatisfactory. By default we want to use /var/run/shairport-sync/.
+ daemon_pid_file_proc = pid_file_proc;
+
+ /* Set indentification string for the daemon for both syslog and PID file */
+ daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]);
+
+ /* Check if we are called with -D or --disconnectFromOutput parameter */
+ if (argc >= 2 &&
+ ((strcmp(argv[1], "-D") == 0) || (strcmp(argv[1], "--disconnectFromOutput") == 0))) {
+ if ((pid = daemon_pid_file_is_running()) >= 0) {
+ if (kill(pid, SIGUSR2) != 0) { // try to send the signal
+ daemon_log(LOG_WARNING,
+ "Failed trying to send disconnectFromOutput command to daemon pid: %d: %s", pid,
+ strerror(errno));
+ }
+ } else {
+ daemon_log(LOG_WARNING,
+ "Can't send a disconnectFromOutput request -- Failed to find daemon: %s",
+ strerror(errno));
+ }
+ exit(1);
+ }
+
+ /* Check if we are called with -R or --reconnectToOutput parameter */
+ if (argc >= 2 &&
+ ((strcmp(argv[1], "-R") == 0) || (strcmp(argv[1], "--reconnectToOutput") == 0))) {
+ if ((pid = daemon_pid_file_is_running()) >= 0) {
+ if (kill(pid, SIGHUP) != 0) { // try to send the signal
+ daemon_log(LOG_WARNING,
+ "Failed trying to send reconnectToOutput command to daemon pid: %d: %s", pid,
+ strerror(errno));
+ }
+ } else {
+ daemon_log(LOG_WARNING, "Can't send a reconnectToOutput request -- Failed to find daemon: %s",
+ strerror(errno));
+ }
+ exit(1);
+ }
+
+ // parse arguments into config -- needed to locate pid_dir
+ int audio_arg = parse_options(argc, argv);
+
+ /* Check if we are called with -k or --kill parameter */
+ if (argc >= 2 && ((strcmp(argv[1], "-k") == 0) || (strcmp(argv[1], "--kill") == 0))) {
+ int ret;
+
+ /* 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
+ daemon_pid_file_remove();
+ return ret < 0 ? 1 : 0;
+ }
+
+ /* 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);
+ return 1;
+ }
+
+ // mDNS supports maximum of 63-character names (we append 13).
+ if (strlen(config.service_name) > 50) {
+ warn("Supplied name too long (max 50 characters)");
+ config.service_name[50] = '\0'; // truncate it and carry on...
+ }
+
+ /* here, daemonise with libdaemon */
+
+ if (config.daemonise) {
+ /* Prepare for return value passing from the initialization procedure of the daemon process */
+ if (daemon_retval_init() < 0) {
+ daemon_log(LOG_ERR, "Failed to create pipe.");
+ return 1;
+ }
+
+ /* Do the fork */
+ if ((pid = daemon_fork()) < 0) {
+
+ /* Exit on error */
+ daemon_retval_done();
+ return 1;
+
+ } else if (pid) { /* The parent */
+ int ret;
+
+ /* Wait for 20 seconds for the return value passed from the daemon process */
+ if ((ret = daemon_retval_wait(20)) < 0) {
+ daemon_log(LOG_ERR, "Could not receive return value from daemon process: %s",
+ strerror(errno));
+ return 255;
+ }
+
+ switch (ret) {
+ case 0:
+ break;
+ case 1:
+ daemon_log(LOG_ERR,
+ "daemon failed to launch: could not close open file descriptors after forking.");
+ break;
+ case 2:
+ daemon_log(LOG_ERR, "daemon failed to launch: could not create PID file.");
+ break;
+ case 3:
+ daemon_log(LOG_ERR, "daemon failed to launch: could not create or access PID directory.");
+ break;
+ default:
+ daemon_log(LOG_ERR, "daemon failed to launch, error %i.", ret);
+ }
+ return ret;
+ } else { /* The daemon */
+
+ /* Close FDs */
+ if (daemon_close_all(-1) < 0) {
+ daemon_log(LOG_ERR, "Failed to close all file descriptors: %s", strerror(errno));
+
+ /* Send the error condition to the parent process */
+ daemon_retval_send(1);
+ goto finish;
+ }
+
+ /* Create the PID file if required */
+ if (config.daemonise_store_pid) {
+ /* Create the PID directory if required -- we don't really care about the result */
+ printf("PID directory is \"%s\".", config.computed_piddir);
+ int result = mkpath(config.computed_piddir, 0700);
+ if ((result != 0) && (result != -EEXIST)) {
+ // error creating or accessing the PID file directory
+ daemon_retval_send(3);
+ goto finish;
+ }
+ if (daemon_pid_file_create() < 0) {
+ daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno));
+ daemon_retval_send(2);
+ goto finish;
+ }
+ }
+
+ /* Send OK to parent process */
+ daemon_retval_send(0);
+ }
+ /* end libdaemon stuff */
+ }
+
+ signal_setup();
+
+ // make sure the program can create files that group and world can read
+ umask(S_IWGRP | S_IWOTH);
+
+ config.output = audio_get_output(config.output_name);
+ if (!config.output) {
+ audio_ls_outputs();
+ die("Invalid audio output specified!");
+ }
+ config.output->init(argc - audio_arg, argv + audio_arg);
+
+ // daemon_log(LOG_NOTICE, "startup");
+
+ switch (endianness) {
+ case SS_LITTLE_ENDIAN:
+ debug(2, "The processor is running little-endian.");
+ break;
+ case SS_BIG_ENDIAN:
+ debug(2, "The processor is running big-endian.");
+ break;
+ case SS_PDP_ENDIAN:
+ debug(2, "The processor is running pdp-endian.");
+ break;
+ }
+
+ /* Mess around with the latency options */
+ // Basically, we used to rely on static latencies -- 99400 for iTunes 10 or later and forkedDaapd,
+ // 88200 for everything else
+ // Nowadays we allow the source to set the latency, which works out at 99651 for iTunes 10 and
+ // forkedDaapd and 88220 for everything else
+ // What we want to do here is allow the source to set the latency unless the user has specified an
+ // non-standard latency.
+ // If the user has specified a standard latency, we suggest to them to stop doing it.
+ // If they specify a non-standard latency, we suggest the user to use the
+ // audio_backend_latency_offset instead.
+
+ if (config.AirPlayLatency != -1) {
+ if (config.AirPlayLatency == 88200) {
+ inform("It is not necessary to set the AirPlay latency to 88200 -- you should remove this "
+ "setting or configuration option, as it is deprecated.");
+ config.AirPlayLatency = -1;
+ } else {
+ inform("The AirPlay latency setting is deprecated, as Shairport Sync can now get the correct "
+ "latency from the source.");
+ inform("Please remove this setting and use the relevant audio_backend_latency_offset "
+ "setting, if necessary, to compensate for delays elsewhere.");
+ }
+ }
+
+ if (config.iTunesLatency != -1) {
+ if (config.iTunesLatency == 99400) {
+ inform("It is not necessary to set the iTunes latency to 99400 -- you should remove this "
+ "setting or configuration option, as it is deprecated and ignored.");
+ config.iTunesLatency = -1;
+ } else {
+ inform("The iTunes latency setting is deprecated, as Shairport Sync can now get the correct "
+ "latency from the source.");
+ inform("Please remove this setting and use the relevant audio_backend_latency_offset "
+ "setting, if necessary, to compensate for delays elsewhere.");
+ }
+ }
+
+ if (config.ForkedDaapdLatency != -1) {
+ if (config.ForkedDaapdLatency == 99400) {
+ inform("It is not necessary to set the forkedDaapd latency to 99400 -- you should remove "
+ "this setting or configuration option, as it is deprecated and ignored.");
+ config.ForkedDaapdLatency = -1;
+ } else {
+ inform("The forkedDaapd latency setting is deprecated, as Shairport Sync can now get the "
+ "correct latency from the source.");
+ inform("Please remove this setting and use the relevant audio_backend_latency_offset "
+ "setting, if necessary, to compensate for delays elsewhere.");
+ }
+ }
+
+ if (config.userSuppliedLatency) {
+ inform("The default latency setting is deprecated, as Shairport Sync can now get the correct "
+ "latency from the source.");
+ inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, "
+ "if necessary, to compensate for delays elsewhere.");
+ }
+
+ /* print out version */
+
+ char *version_dbs = get_version_string();
+ if (version_dbs) {
+ debug(1, "Version: \"%s\"", version_dbs);
+ free(version_dbs);
+ } else {
+ debug(1, "Can't print the version information!");
+ }
+
+ /* Print out options */
+ debug(1, "statistics_requester status is %d.", config.statistics_requested);
+ debug(1, "daemon status is %d.", config.daemonise);
+ debug(1, "deamon pid file is \"%s\".", pid_file_proc());
+ debug(1, "rtsp listening port is %d.", config.port);
+ debug(1, "udp base port is %d.", config.udp_port_base);
+ debug(1, "udp port range is %d.", config.udp_port_range);
+ debug(1, "player name is \"%s\".", config.service_name);
+ debug(1, "backend is \"%s\".", config.output_name);
+ debug(1, "on-start action is \"%s\".", config.cmd_start);
+ debug(1, "on-stop action is \"%s\".", config.cmd_stop);
+ debug(1, "wait-cmd status is %d.", config.cmd_blocking);
+ debug(1, "on-start returns output is %d.", config.cmd_start_returns_output);
+ debug(1, "mdns backend \"%s\".", config.mdns_name);
+ debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency);
+ debug(2, "AirPlayLatency is %d.", config.AirPlayLatency);
+ debug(2, "iTunesLatency is %d.", config.iTunesLatency);
+ debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency);
+ debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing);
+ debug(1, "resync time is %f seconds.", config.resyncthreshold);
+ debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption);
+ debug(1, "busy timeout time is %d.", config.timeout);
+ debug(1, "drift tolerance is %f seconds.", config.tolerance);
+ debug(1, "password is \"%s\".", config.password);
+ debug(1, "ignore_volume_control is %d.", config.ignore_volume_control);
+ if (config.volume_max_db_set)
+ debug(1, "volume_max_db is %d.", config.volume_max_db);
+ else
+ debug(1, "volume_max_db is not set");
+ debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).",
+ config.playback_mode);
+ debug(1, "disable_synchronization is %d.", config.no_sync);
+ debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1);
+ debug(1, "output_rate is %d.", config.output_rate);
+ debug(1,
+ "output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S24_3LE, 6-S24_3BE, 7-S32).",
+ config.output_format);
+ debug(1, "audio backend desired buffer length is %f seconds.",
+ config.audio_backend_buffer_desired_length);
+ 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);
+ debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.",
+ config.volume_range_db);
+ 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);
+ debug(1, "alsa_use_playback_switch_for_mute is %d.", config.alsa_use_playback_switch_for_mute);
+ if (config.interface)
+ debug(1, "mdns service interface \"%s\" requested.", config.interface);
+ else
+ debug(1, "no special mdns service interface was requested.");
+ char *realConfigPath = realpath(config.configfile, NULL);
+ if (realConfigPath) {
+ debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile,
+ realConfigPath);
+ free(realConfigPath);
+ } else {
+ debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile);
+ }
+#ifdef CONFIG_METADATA
+ debug(1, "metadata enabled is %d.", config.metadata_enabled);
+ debug(1, "metadata pipename is \"%s\".", config.metadata_pipename);
+ debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr,
+ config.metadata_sockport);
+ debug(1, "metadata socket packet size is \"%d\".", config.metadata_sockmsglength);
+ debug(1, "get-coverart is %d.", config.get_coverart);
+#endif
+
+#ifdef CONFIG_CONVOLUTION
+ debug(1, "convolution is %d.", config.convolution);
+ debug(1, "convolution IR file is \"%s\"", config.convolution_ir_file);
+ debug(1, "convolution max length %d", config.convolution_max_length);
+ debug(1, "convolution gain is %f", config.convolution_gain);
+#endif
+ debug(1, "loudness is %d.", config.loudness);
+ debug(1, "loudness reference level is %f", config.loudness_reference_volume_db);
+
+ uint8_t ap_md5[16];
+
+#ifdef HAVE_LIBSSL
+ MD5_CTX ctx;
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, config.service_name, strlen(config.service_name));
+ MD5_Final(ap_md5, &ctx);
+#endif
+
+#ifdef HAVE_LIBMBEDTLS
+ mbedtls_md5_context tctx;
+ mbedtls_md5_starts(&tctx);
+ mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
+ mbedtls_md5_finish(&tctx, ap_md5);
+#endif
+
+#ifdef HAVE_LIBPOLARSSL
+ md5_context tctx;
+ md5_starts(&tctx);
+ md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
+ md5_finish(&tctx, ap_md5);
+#endif
+ memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr));
+#ifdef CONFIG_METADATA
+ metadata_init(); // create the metadata pipe if necessary
+#endif
+
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+ debug(1,"Requesting DACP Monitor");
+ dacp_monitor_start();
+#endif
+
+#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+ // Start up DBUS services after initial settings are all made
+ debug(1, "Starting up D-Bus services");
+ pthread_create(&dbus_thread, NULL, &dbus_thread_func, NULL);
+#ifdef HAVE_DBUS
+ start_dbus_service();
+#endif
+#ifdef HAVE_MPRIS
+ start_mpris_service();
+#endif
+#endif
+
+ daemon_log(LOG_INFO, "Successful Startup");
+ rtsp_listen_loop();
+
+ // should not reach this...
+ shairport_shutdown();
+finish:
+ daemon_log(LOG_NOTICE, "Unexpected exit...");
+ daemon_retval_send(255);
+ daemon_pid_file_remove();
+ return 1;
+}
diff --git a/tinysvcmdns.c b/tinysvcmdns.c
new file mode 100644
index 0000000..90f9c82
--- /dev/null
+++ b/tinysvcmdns.c
@@ -0,0 +1,1736 @@
+// This file is the concatenation of mdnsd.c and mdns.c
+// from tinysvcmdns with minor modifications
+// The code was taken from https://bitbucket.org/geekman/tinysvcmdns at revision e34b562
+
+/*
+ * tinysvcmdns - a tiny MDNS implementation for publishing services
+ * Copyright (C) 2011 Darell Tan
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "tinysvcmdns.h"
+#include "common.h"
+
+#define DEBUG_PRINTF(...) debug(3, __VA_ARGS__)
+#define log_message(level, ...) \
+ do { \
+ switch (level) { \
+ case LOG_ERR: \
+ warn(__VA_ARGS__); \
+ break; \
+ default: \
+ debug(3, __VA_ARGS__); \
+ } \
+ } while (0)
+
+//******************************************************//
+// mdns.c //
+//******************************************************//
+#include
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include
+#include
+#else
+#include
+#endif
+
+// See RFC 6762 Section 10 for an account of two TTLs -- 120 seconds for rrs with a host name as the
+// record's name
+// or a host name in the record's rdata
+// 75 minutes for everything else.
+// https://tools.ietf.org/html/rfc6762
+
+#define DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME 120
+#define DEFAULT_TTL 4500
+
+struct name_comp {
+ uint8_t *label; // label
+ size_t pos; // position in msg
+
+ struct name_comp *next;
+};
+
+// ----- label functions -----
+
+// duplicates a name
+inline uint8_t *dup_nlabel(const uint8_t *n) {
+ assert(n[0] <= 63); // prevent mis-use
+ return (uint8_t *)strdup((char *)n);
+}
+
+// duplicates a label
+uint8_t *dup_label(const uint8_t *label) {
+ int len = *label + 1;
+ if (len > 63)
+ return NULL;
+ uint8_t *newlabel = malloc(len + 1);
+ if (newlabel)
+ strncpy((char *)newlabel, (char *)label, len);
+ else
+ die("could not allocate memory for \"newlabel\" in tinysvcmdns");
+ newlabel[len] = '\0';
+ return newlabel;
+}
+
+uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2) {
+ int len1, len2;
+ uint8_t *s;
+
+ assert(n1[0] <= 63 && n2[0] <= 63); // detect misuse
+
+ len1 = strlen((char *)n1);
+ len2 = strlen((char *)n2);
+
+ s = malloc(len1 + len2 + 1);
+ if (s) {
+ strncpy((char *)s, (char *)n1, len1);
+ strncpy((char *)s + len1, (char *)n2, len2);
+ s[len1 + len2] = '\0';
+ } else {
+ die("can not allocate memory for \"s\" in tinysvcmdns");
+ }
+ return s;
+}
+
+// returns a human-readable name label in dotted form
+char *nlabel_to_str(const uint8_t *name) {
+ char *label, *labelp;
+ const uint8_t *p;
+ size_t buf_len = 256;
+
+ assert(name != NULL);
+
+ label = labelp = malloc(buf_len);
+
+ if (label) {
+ for (p = name; *p; p++) {
+ uint8_t label_len = *p;
+ if (buf_len <= label_len)
+ break;
+
+ strncpy(labelp, (char *)p + 1, label_len);
+ labelp += label_len;
+
+ *labelp = '.';
+ labelp++;
+
+ buf_len -= label_len + 1;
+
+ p += label_len;
+ }
+
+ // avoid writing NULL past end of buffer
+ if (buf_len == 0)
+ labelp--;
+
+ *labelp = '\0';
+ } else {
+ die("could not allocate memory for \"label\" in tinysvcmdns.c.");
+ }
+
+ return label;
+}
+
+// returns the length of a label field
+// does NOT uncompress the field, so it could be as small as 2 bytes
+// or 1 for the root
+static size_t label_len(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
+ uint8_t *p;
+ uint8_t *e = pkt_buf + pkt_len;
+ size_t len = 0;
+
+ for (p = pkt_buf + off; p < e; p++) {
+ if (*p == 0) {
+ return len + 1;
+ } else if ((*p & 0xC0) == 0xC0) {
+ return len + 2;
+ } else {
+ len += *p + 1;
+ p += *p;
+ }
+ }
+
+ return len;
+}
+
+// creates a label
+// free() after use
+uint8_t *create_label(const char *txt) {
+ int len;
+ uint8_t *s;
+
+ assert(txt != NULL);
+ len = strlen(txt);
+ if (len > 63)
+ return NULL;
+
+ s = malloc(len + 2);
+ if (s) {
+ s[0] = len;
+ strncpy((char *)s + 1, txt, len);
+ s[len + 1] = '\0';
+ } else {
+ die("can not allocate memory for \"s\" 2 in tinysvcmdns.");
+ }
+ return s;
+}
+
+// creates a uncompressed name label given a DNS name like "apple.b.com"
+// free() after use
+uint8_t *create_nlabel(const char *name) {
+ char *label;
+ char *p, *e, *lenpos;
+ int len = 0;
+
+ assert(name != NULL);
+
+ len = strlen(name);
+ label = malloc(len + 1 + 1);
+ if (label == NULL)
+ return NULL;
+
+ strncpy((char *)label + 1, name, len);
+ label[len + 1] = '\0';
+
+ p = label;
+ e = p + len;
+ lenpos = p;
+
+ while (p < e) {
+ *lenpos = 0;
+ char *dot = memchr(p + 1, '.', e - p - 1);
+ if (dot == NULL)
+ dot = e + 1;
+ *lenpos = dot - p - 1;
+
+ p = dot;
+ lenpos = dot;
+ }
+
+ return (uint8_t *)label;
+}
+
+// copies a label from the buffer into a newly-allocated string
+// free() after use
+static uint8_t *copy_label(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
+ int len;
+
+ if (off > pkt_len)
+ return NULL;
+
+ len = pkt_buf[off] + 1;
+ if (off + len > pkt_len) {
+ DEBUG_PRINTF("label length exceeds packet buffer\n");
+ return NULL;
+ }
+
+ return dup_label(pkt_buf + off);
+}
+
+// uncompresses a name
+// free() after use
+static uint8_t *uncompress_nlabel(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
+ uint8_t *p;
+ uint8_t *e = pkt_buf + pkt_len;
+ size_t len = 0;
+ char *str, *sp;
+ if (off >= pkt_len)
+ return NULL;
+
+ // calculate length of uncompressed label
+ for (p = pkt_buf + off; *p && p < e; p++) {
+ size_t llen = 0;
+ if ((*p & 0xC0) == 0xC0) {
+ uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]);
+ llen = *p2 + 1;
+ p = p2 + llen - 1;
+ } else {
+ llen = *p + 1;
+ p += llen - 1;
+ }
+ len += llen;
+ }
+
+ str = sp = malloc(len + 1);
+ if (str == NULL)
+ return NULL;
+
+ // FIXME: must merge this with above code
+ for (p = pkt_buf + off; *p && p < e; p++) {
+ size_t llen = 0;
+ if ((*p & 0xC0) == 0xC0) {
+ uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]);
+ llen = *p2 + 1;
+ strncpy(sp, (char *)p2, llen);
+ p = p2 + llen - 1;
+ } else {
+ llen = *p + 1;
+ strncpy(sp, (char *)p, llen);
+ p += llen - 1;
+ }
+ sp += llen;
+ }
+ *sp = '\0';
+
+ return (uint8_t *)str;
+}
+
+// ----- RR list & group functions -----
+
+const char *rr_get_type_name(enum rr_type type) {
+ switch (type) {
+ case RR_A:
+ return "A";
+ case RR_PTR:
+ return "PTR";
+ case RR_TXT:
+ return "TXT";
+ case RR_AAAA:
+ return "AAAA";
+ case RR_SRV:
+ return "SRV";
+ case RR_NSEC:
+ return "NSEC";
+ case RR_ANY:
+ return "ANY";
+ }
+ return NULL;
+}
+
+void rr_entry_destroy(struct rr_entry *rr) {
+ struct rr_data_txt *txt_rec;
+ assert(rr);
+
+ // check rr_type and free data elements
+ switch (rr->type) {
+ case RR_PTR:
+ if (rr->data.PTR.name)
+ free(rr->data.PTR.name);
+ // don't free entry
+ break;
+
+ case RR_TXT:
+ txt_rec = &rr->data.TXT;
+ while (txt_rec) {
+ struct rr_data_txt *next = txt_rec->next;
+ if (txt_rec->txt)
+ free(txt_rec->txt);
+
+ // only free() if it wasn't part of the struct
+ if (txt_rec != &rr->data.TXT)
+ free(txt_rec);
+
+ txt_rec = next;
+ }
+ break;
+
+ case RR_SRV:
+ if (rr->data.SRV.target)
+ free(rr->data.SRV.target);
+ break;
+
+ default:
+ // nothing to free
+ break;
+ }
+
+ free(rr->name);
+ free(rr);
+}
+
+// destroys an RR list (and optionally, items)
+void rr_list_destroy(struct rr_list *rr, char destroy_items) {
+ struct rr_list *rr_next;
+
+ for (; rr; rr = rr_next) {
+ rr_next = rr->next;
+ if (destroy_items)
+ rr_entry_destroy(rr->e);
+ free(rr);
+ }
+}
+
+int rr_list_count(struct rr_list *rr) {
+ int i = 0;
+ for (; rr; i++, rr = rr->next)
+ ;
+ return i;
+}
+
+struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr) {
+ struct rr_list *le = *rr_head, *pe = NULL;
+ for (; le; le = le->next) {
+ if (le->e == rr) {
+ if (pe == NULL) {
+ *rr_head = le->next;
+ free(le);
+ return rr;
+ } else {
+ pe->next = le->next;
+ free(le);
+ return rr;
+ }
+ }
+ pe = le;
+ }
+ return NULL;
+}
+
+// appends an rr_entry to an RR list
+// if the RR is already in the list, it will not be added
+// RRs are compared by memory location - not its contents
+// return value of 0 means item not added
+int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) {
+ struct rr_list *node = malloc(sizeof(struct rr_list));
+ if (node) {
+ node->e = rr;
+ node->next = NULL;
+
+ if (*rr_head == NULL) {
+ *rr_head = node;
+ } else {
+ struct rr_list *e = *rr_head, *taile;
+ for (; e; e = e->next) {
+ // already in list - don't add
+ if (e->e == rr) {
+ free(node);
+ return 0;
+ }
+ if (e->next == NULL)
+ taile = e;
+ }
+ taile->next = node;
+ }
+ } else {
+ die("can not allocate memory for \"node\" in tinysvcmdns.");
+ }
+ return 1;
+}
+
+#define FILL_RR_ENTRY(rr, _name, _type) \
+ rr->name = _name; \
+ rr->type = _type; \
+ rr->ttl = DEFAULT_TTL; \
+ rr->cache_flush = 1; \
+ rr->rr_class = 1;
+
+struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr) {
+ DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
+ if (rr) {
+ FILL_RR_ENTRY(rr, name, RR_A);
+ rr->data.A.addr = addr;
+ rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10
+ } else {
+ die("could not allocate an RR data structure in tinysvcmdns.c.");
+ }
+ return rr;
+}
+
+struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr) {
+ DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
+ if (rr) {
+ FILL_RR_ENTRY(rr, name, RR_AAAA);
+ rr->data.AAAA.addr = addr;
+ rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10
+ } else {
+ die("could not allocate an RR 2 data structure in tinysvcmdns.c.");
+ }
+ return rr;
+}
+
+struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target) {
+ DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
+ if (rr) {
+ FILL_RR_ENTRY(rr, name, RR_SRV);
+ rr->data.SRV.port = port;
+ rr->data.SRV.target = target;
+ } else {
+ die("could not allocate an RR 3 data structure in tinysvcmdns.c.");
+ }
+ return rr;
+}
+
+struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr) {
+ DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
+ if (rr) {
+ FILL_RR_ENTRY(rr, name, RR_PTR);
+ rr->cache_flush = 0; // PTRs shouldn't have their cache flush bit set
+ rr->data.PTR.entry = d_rr;
+ } else {
+ die("could not allocate an RR 4 data structure in tinysvcmdns.c.");
+ }
+ return rr;
+}
+
+struct rr_entry *rr_create(uint8_t *name, enum rr_type type) {
+ DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
+ if (rr) {
+ FILL_RR_ENTRY(rr, name, type);
+ } else {
+ die("could not allocate an RR 4 data structure in tinysvcmdns.c.");
+ }
+ return rr;
+}
+
+void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type) {
+ assert(rr_nsec->type = RR_NSEC);
+ assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap));
+
+ rr_nsec->data.NSEC.bitmap[type / 8] = 1 << (7 - (type % 8));
+}
+
+void rr_add_txt(struct rr_entry *rr_txt, const char *txt) {
+ struct rr_data_txt *txt_rec;
+ assert(rr_txt->type == RR_TXT);
+
+ txt_rec = &rr_txt->data.TXT;
+
+ // is current data filled?
+ if (txt_rec->txt == NULL) {
+ txt_rec->txt = create_label(txt);
+ return;
+ }
+
+ // find the last node
+ for (; txt_rec->next; txt_rec = txt_rec->next)
+ ;
+
+ // create a new empty node
+ txt_rec->next = malloc(sizeof(struct rr_data_txt));
+
+ txt_rec = txt_rec->next;
+ txt_rec->txt = create_label(txt);
+ txt_rec->next = NULL;
+}
+
+// adds a record to an rr_group
+void rr_group_add(struct rr_group **group, struct rr_entry *rr) {
+ struct rr_group *g;
+
+ assert(rr != NULL);
+
+ if (*group) {
+ g = rr_group_find(*group, rr->name);
+ if (g) {
+ rr_list_append(&g->rr, rr);
+ return;
+ }
+ }
+
+ MALLOC_ZERO_STRUCT(g, rr_group);
+ if (g) {
+ g->name = dup_nlabel(rr->name);
+ rr_list_append(&g->rr, rr);
+
+ // prepend to list
+ g->next = *group;
+ *group = g;
+ } else {
+ die("can not allocate memory for \"g\" in tinysvcmdns");
+ }
+}
+
+// finds a rr_group matching the given name
+struct rr_group *rr_group_find(struct rr_group *g, uint8_t *name) {
+ for (; g; g = g->next) {
+ if (cmp_nlabel(g->name, name) == 0)
+ return g;
+ }
+ return NULL;
+}
+
+struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type) {
+ struct rr_list *rr = rr_list;
+ for (; rr; rr = rr->next) {
+ if (rr->e->type == type && cmp_nlabel(rr->e->name, name) == 0)
+ return rr->e;
+ }
+ return NULL;
+}
+
+// looks for a matching entry in rr_list
+// if entry is a PTR, we need to check if the PTR target also matches
+struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry) {
+ struct rr_list *rr = rr_list;
+ for (; rr; rr = rr->next) {
+ if (rr->e->type == entry->type && cmp_nlabel(rr->e->name, entry->name) == 0) {
+ if (entry->type != RR_PTR) {
+ return rr->e;
+ } else if (cmp_nlabel(MDNS_RR_GET_PTR_NAME(entry), MDNS_RR_GET_PTR_NAME(rr->e)) == 0) {
+ // if it's a PTR, we need to make sure PTR target also matches
+ return rr->e;
+ }
+ }
+ }
+ return NULL;
+}
+
+void rr_group_destroy(struct rr_group *group) {
+ struct rr_group *g = group;
+
+ while (g) {
+ struct rr_group *nextg = g->next;
+ free(g->name);
+ rr_list_destroy(g->rr, 1);
+ free(g);
+ g = nextg;
+ }
+}
+
+uint8_t *mdns_write_u16(uint8_t *ptr, const uint16_t v) {
+ *ptr++ = (uint8_t)(v >> 8) & 0xFF;
+ *ptr++ = (uint8_t)(v >> 0) & 0xFF;
+ return ptr;
+}
+
+uint8_t *mdns_write_u32(uint8_t *ptr, const uint32_t v) {
+ *ptr++ = (uint8_t)(v >> 24) & 0xFF;
+ *ptr++ = (uint8_t)(v >> 16) & 0xFF;
+ *ptr++ = (uint8_t)(v >> 8) & 0xFF;
+ *ptr++ = (uint8_t)(v >> 0) & 0xFF;
+ return ptr;
+}
+
+uint16_t mdns_read_u16(const uint8_t *ptr) {
+ return ((ptr[0] & 0xFF) << 8) | ((ptr[1] & 0xFF) << 0);
+}
+
+uint32_t mdns_read_u32(const uint8_t *ptr) {
+ return ((ptr[0] & 0xFF) << 24) | ((ptr[1] & 0xFF) << 16) | ((ptr[2] & 0xFF) << 8) |
+ ((ptr[3] & 0xFF) << 0);
+}
+
+// initialize the packet for reply
+// clears the packet of list structures but not its list items
+void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id) {
+ // copy transaction ID
+ pkt->id = id;
+
+ // response flags
+ pkt->flags = MDNS_FLAG_RESP | MDNS_FLAG_AA;
+
+ rr_list_destroy(pkt->rr_qn, 0);
+ rr_list_destroy(pkt->rr_ans, 0);
+ rr_list_destroy(pkt->rr_auth, 0);
+ rr_list_destroy(pkt->rr_add, 0);
+
+ pkt->rr_qn = NULL;
+ pkt->rr_ans = NULL;
+ pkt->rr_auth = NULL;
+ pkt->rr_add = NULL;
+
+ pkt->num_qn = 0;
+ pkt->num_ans_rr = 0;
+ pkt->num_auth_rr = 0;
+ pkt->num_add_rr = 0;
+}
+
+// destroys an mdns_pkt struct, including its contents
+void mdns_pkt_destroy(struct mdns_pkt *p) {
+ rr_list_destroy(p->rr_qn, 1);
+ rr_list_destroy(p->rr_ans, 1);
+ rr_list_destroy(p->rr_auth, 1);
+ rr_list_destroy(p->rr_add, 1);
+
+ free(p);
+}
+
+// parse the MDNS questions section
+// stores the parsed data in the given mdns_pkt struct
+static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct mdns_pkt *pkt) {
+ const uint8_t *p = pkt_buf + off;
+ struct rr_entry *rr;
+ uint8_t *name;
+
+ assert(pkt != NULL);
+
+ rr = malloc(sizeof(struct rr_entry));
+ if (rr)
+ memset(rr, 0, sizeof(struct rr_entry));
+ else
+ die("could not allocate memory for \"rr\" in tinysvcmdns");
+
+ name = uncompress_nlabel(pkt_buf, pkt_len, off);
+ p += label_len(pkt_buf, pkt_len, off);
+ rr->name = name;
+
+ rr->type = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+
+ rr->unicast_query = (*p & 0x80) == 0x80;
+ rr->rr_class = mdns_read_u16(p) & ~0x80;
+ p += sizeof(uint16_t);
+
+ rr_list_append(&pkt->rr_qn, rr);
+
+ return p - (pkt_buf + off);
+}
+
+// parse the MDNS RR section
+// stores the parsed data in the given mdns_pkt struct
+static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct mdns_pkt *pkt) {
+ const uint8_t *p = pkt_buf + off;
+ const uint8_t *e = pkt_buf + pkt_len;
+ struct rr_entry *rr;
+ uint8_t *name;
+ size_t rr_data_len = 0;
+ struct rr_data_txt *txt_rec;
+ int parse_error = 0;
+
+ assert(pkt != NULL);
+
+ if (off > pkt_len)
+ return 0;
+
+ rr = malloc(sizeof(struct rr_entry));
+ if (rr)
+ memset(rr, 0, sizeof(struct rr_entry));
+ else
+ die("could not allocate memory for \"rr (2)\" in tinysvcmdns");
+
+ name = uncompress_nlabel(pkt_buf, pkt_len, off);
+ p += label_len(pkt_buf, pkt_len, off);
+ rr->name = name;
+
+ rr->type = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+
+ rr->cache_flush = (*p & 0x80) == 0x80;
+ rr->rr_class = mdns_read_u16(p) & ~0x80;
+ p += sizeof(uint16_t);
+
+ rr->ttl = mdns_read_u32(p);
+ p += sizeof(uint32_t);
+
+ // RR data
+ rr_data_len = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+
+ if (p + rr_data_len > e) {
+ DEBUG_PRINTF("rr_data_len goes beyond packet buffer: %lu > %lu\n", rr_data_len, e - p);
+ rr_entry_destroy(rr);
+ return 0;
+ }
+
+ e = p + rr_data_len;
+
+ // see if we can parse the RR data
+ switch (rr->type) {
+ case RR_A:
+ if (rr_data_len < sizeof(uint32_t)) {
+ DEBUG_PRINTF("invalid rr_data_len=%lu for A record\n", rr_data_len);
+ parse_error = 1;
+ break;
+ }
+ rr->data.A.addr = ntohl(mdns_read_u32(p)); /* addr already in net order */
+ p += sizeof(uint32_t);
+ break;
+
+ case RR_AAAA:
+ if (rr_data_len < sizeof(struct in6_addr)) {
+ DEBUG_PRINTF("invalid rr_data_len=%lu for AAAA record\n", rr_data_len);
+ parse_error = 1;
+ break;
+ }
+ rr->data.AAAA.addr = malloc(sizeof(struct in6_addr));
+ int i;
+ for (i = 0; i < sizeof(struct in6_addr); i++)
+ rr->data.AAAA.addr->s6_addr[i] = p[i];
+ p += sizeof(struct in6_addr);
+ break;
+
+ case RR_PTR:
+ rr->data.PTR.name = uncompress_nlabel(pkt_buf, pkt_len, p - pkt_buf);
+ if (rr->data.PTR.name == NULL) {
+ DEBUG_PRINTF("unable to parse/uncompress label for PTR name\n");
+ parse_error = 1;
+ break;
+ }
+ p += rr_data_len;
+ break;
+
+ case RR_TXT:
+ txt_rec = &rr->data.TXT;
+
+ // not supposed to happen, but we should handle it
+ if (rr_data_len == 0) {
+ DEBUG_PRINTF("WARN: rr_data_len for TXT is 0\n");
+ txt_rec->txt = create_label("");
+ break;
+ }
+
+ while (1) {
+ txt_rec->txt = copy_label(pkt_buf, pkt_len, p - pkt_buf);
+ if (txt_rec->txt == NULL) {
+ DEBUG_PRINTF("unable to copy label for TXT record\n");
+ parse_error = 1;
+ break;
+ }
+ p += txt_rec->txt[0] + 1;
+
+ if (p >= e)
+ break;
+
+ // allocate another record
+ txt_rec->next = malloc(sizeof(struct rr_data_txt));
+ txt_rec = txt_rec->next;
+ txt_rec->next = NULL;
+ }
+ break;
+
+ default:
+ // skip to end of RR data
+ p = e;
+ }
+
+ // if there was a parse error, destroy partial rr_entry
+ if (parse_error) {
+ rr_entry_destroy(rr);
+ return 0;
+ }
+
+ rr_list_append(&pkt->rr_ans, rr);
+
+ return p - (pkt_buf + off);
+}
+
+// parse a MDNS packet into an mdns_pkt struct
+struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len) {
+ uint8_t *p = pkt_buf;
+ size_t off;
+ struct mdns_pkt *pkt;
+ int i;
+
+ if (pkt_len < 12)
+ return NULL;
+
+ MALLOC_ZERO_STRUCT(pkt, mdns_pkt);
+
+ if (pkt == NULL)
+ die("cannot allocate memory for \"pkt\" in tinysvcmdns.c.");
+
+ // parse header
+ pkt->id = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+ pkt->flags = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+ pkt->num_qn = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+ pkt->num_ans_rr = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+ pkt->num_auth_rr = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+ pkt->num_add_rr = mdns_read_u16(p);
+ p += sizeof(uint16_t);
+
+ off = p - pkt_buf;
+
+ // parse questions
+ for (i = 0; i < pkt->num_qn; i++) {
+ size_t l = mdns_parse_qn(pkt_buf, pkt_len, off, pkt);
+ if (!l) {
+ DEBUG_PRINTF("error parsing question #%d\n", i);
+ mdns_pkt_destroy(pkt);
+ return NULL;
+ }
+
+ off += l;
+ }
+
+ // parse answer RRs
+ for (i = 0; i < pkt->num_ans_rr; i++) {
+ size_t l = mdns_parse_rr(pkt_buf, pkt_len, off, pkt);
+ if (!l) {
+ DEBUG_PRINTF("error parsing answer #%d\n", i);
+ mdns_pkt_destroy(pkt);
+ return NULL;
+ }
+
+ off += l;
+ }
+
+ // TODO: parse the authority and additional RR sections
+
+ return pkt;
+}
+
+// encodes a name (label) into a packet using the name compression scheme
+// encoded names will be added to the compression list for subsequent use
+static size_t mdns_encode_name(uint8_t *pkt_buf, size_t pkt_len, size_t off, const uint8_t *name,
+ struct name_comp *comp) {
+ struct name_comp *c, *c_tail = NULL;
+ uint8_t *p = pkt_buf + off;
+ size_t len = 0;
+
+ if (name) {
+ while (*name) {
+ // find match for compression
+ for (c = comp; c; c = c->next) {
+ if (cmp_nlabel(name, c->label) == 0) {
+ mdns_write_u16(p, 0xC000 | (c->pos & ~0xC000));
+ return len + sizeof(uint16_t);
+ }
+
+ if (c->next == NULL)
+ c_tail = c;
+ }
+
+ // copy this segment
+ int segment_len = *name + 1;
+ strncpy((char *)p, (char *)name, segment_len);
+
+ // cache the name for subsequent compression
+ DECL_MALLOC_ZERO_STRUCT(new_c, name_comp);
+
+ new_c->label = (uint8_t *)name;
+ new_c->pos = p - pkt_buf;
+ c_tail->next = new_c;
+
+ // advance to next name segment
+ p += segment_len;
+ len += segment_len;
+ name += segment_len;
+ }
+ }
+
+ *p = '\0'; // root "label"
+ len += 1;
+
+ return len;
+}
+
+// encodes an RR entry at the given offset
+// returns the size of the entire RR entry
+static size_t mdns_encode_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct rr_entry *rr,
+ struct name_comp *comp) {
+ uint8_t *p = pkt_buf + off, *p_data;
+ size_t l;
+ struct rr_data_txt *txt_rec;
+ uint8_t *label;
+ int i;
+
+ assert(off < pkt_len);
+
+ // name
+ l = mdns_encode_name(pkt_buf, pkt_len, off, rr->name, comp);
+ assert(l != 0);
+ p += l;
+
+ // type
+ p = mdns_write_u16(p, rr->type);
+
+ // class & cache flush
+ p = mdns_write_u16(p, (rr->rr_class & ~0x8000) | (rr->cache_flush << 15));
+
+ // TTL
+ p = mdns_write_u32(p, rr->ttl);
+
+ // data length (filled in later)
+ p += sizeof(uint16_t);
+
+ // start of data marker
+ p_data = p;
+
+ switch (rr->type) {
+ case RR_A:
+ /* htonl() needed coz addr already in net order */
+ p = mdns_write_u32(p, htonl(rr->data.A.addr));
+ break;
+
+ case RR_AAAA:
+ for (i = 0; i < sizeof(struct in6_addr); i++)
+ *p++ = rr->data.AAAA.addr->s6_addr[i];
+ break;
+
+ case RR_PTR:
+ label = rr->data.PTR.name ? rr->data.PTR.name : rr->data.PTR.entry->name;
+ p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, label, comp);
+ break;
+
+ case RR_TXT:
+ txt_rec = &rr->data.TXT;
+ for (; txt_rec; txt_rec = txt_rec->next) {
+ int len = txt_rec->txt[0] + 1;
+ strncpy((char *)p, (char *)txt_rec->txt, len);
+ p += len;
+ }
+ break;
+
+ case RR_SRV:
+ p = mdns_write_u16(p, rr->data.SRV.priority);
+
+ p = mdns_write_u16(p, rr->data.SRV.weight);
+
+ p = mdns_write_u16(p, rr->data.SRV.port);
+
+ p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, rr->data.SRV.target, comp);
+ break;
+
+ case RR_NSEC:
+ p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, rr->name, comp);
+
+ *p++ = 0; // bitmap window/block number
+
+ *p++ = sizeof(rr->data.NSEC.bitmap); // bitmap length
+
+ for (i = 0; i < sizeof(rr->data.NSEC.bitmap); i++)
+ *p++ = rr->data.NSEC.bitmap[i];
+
+ break;
+
+ default:
+ DEBUG_PRINTF("unhandled rr type 0x%02x\n", rr->type);
+ }
+
+ // calculate data length based on p
+ l = p - p_data;
+
+ // fill in the length
+ mdns_write_u16(p - l - sizeof(uint16_t), l);
+
+ return p - pkt_buf - off;
+}
+
+// encodes a MDNS packet from the given mdns_pkt struct into a buffer
+// returns the size of the entire MDNS packet
+size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len) {
+ struct name_comp *comp;
+ uint8_t *p = pkt_buf;
+ // uint8_t *e = pkt_buf + pkt_len;
+ size_t off;
+ int i;
+
+ assert(answer != NULL);
+ assert(pkt_len >= 12);
+
+ if (p == NULL)
+ return -1;
+
+ // this is an Answer - number of qns should be zero
+ assert(answer->num_qn == 0);
+
+ p = mdns_write_u16(p, answer->id);
+ p = mdns_write_u16(p, answer->flags);
+ p = mdns_write_u16(p, answer->num_qn);
+ p = mdns_write_u16(p, answer->num_ans_rr);
+ p = mdns_write_u16(p, answer->num_auth_rr);
+ p = mdns_write_u16(p, answer->num_add_rr);
+
+ off = p - pkt_buf;
+
+ // allocate list for name compression
+ comp = malloc(sizeof(struct name_comp));
+ if (comp == NULL)
+ return -1;
+ memset(comp, 0, sizeof(struct name_comp));
+
+ // dummy entry
+ comp->label = (uint8_t *)"";
+ comp->pos = 0;
+
+ // skip encoding of qn
+
+ struct rr_list *rr_set[] = {answer->rr_ans, answer->rr_auth, answer->rr_add};
+
+ // encode answer, authority and additional RRs
+ for (i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) {
+ struct rr_list *rr = rr_set[i];
+ for (; rr; rr = rr->next) {
+ size_t l = mdns_encode_rr(pkt_buf, pkt_len, off, rr->e, comp);
+ off += l;
+
+ if (off >= pkt_len) {
+ DEBUG_PRINTF("packet buffer too small\n");
+ return -1;
+ }
+ }
+ }
+
+ // free name compression list
+ while (comp) {
+ struct name_comp *c = comp->next;
+ free(comp);
+ comp = c;
+ }
+
+ return off;
+}
+
+//******************************************************//
+// mdnsd.c //
+//******************************************************//
+
+#ifdef _WIN32
+#include
+#include
+#define LOG_ERR 3
+#else
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * Define a proper IP socket level if not already done.
+ * Required to compile on OS X
+ */
+#ifndef SOL_IP
+#define SOL_IP IPPROTO_IP
+#endif
+
+#define MDNS_ADDR "224.0.0.251"
+#define MDNS_PORT 5353
+
+#define PACKET_SIZE 65536
+
+#define SERVICES_DNS_SD_NLABEL ((uint8_t *)"\x09_services\x07_dns-sd\x04_udp\x05local")
+
+struct mdnsd {
+ pthread_mutex_t data_lock;
+ int sockfd;
+ int notify_pipe[2];
+ int stop_flag;
+
+ struct rr_group *group;
+ struct rr_list *announce;
+ struct rr_list *services;
+ uint8_t *hostname;
+};
+
+struct mdns_service {
+ struct rr_list *entries;
+};
+
+/////////////////////////////////
+
+static int create_recv_sock() {
+ int sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0) {
+ log_message(LOG_ERR, "recv socket(): %m");
+ return sd;
+ }
+
+ int r = -1;
+
+ int on = 1;
+ if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))) < 0) {
+ log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %m");
+ return r;
+ }
+
+ /* bind to an address */
+ struct sockaddr_in serveraddr;
+ memset(&serveraddr, 0, sizeof(serveraddr));
+ serveraddr.sin_family = AF_INET;
+ serveraddr.sin_port = htons(MDNS_PORT);
+ serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */
+ if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) {
+ log_message(LOG_ERR, "recv bind(): %m");
+ }
+
+ // add membership to receiving socket
+ struct ip_mreq mreq;
+ memset(&mreq, 0, sizeof(struct ip_mreq));
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ mreq.imr_multiaddr.s_addr = inet_addr(MDNS_ADDR);
+ if ((r = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq))) < 0) {
+ log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %m");
+ return r;
+ }
+
+ // enable loopback in case someone else needs the data
+ if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&on, sizeof(on))) < 0) {
+ log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %m");
+ return r;
+ }
+
+#ifdef IP_PKTINFO
+ if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, (char *)&on, sizeof(on))) < 0) {
+ log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %m");
+ return r;
+ }
+#endif
+
+ return sd;
+}
+
+static ssize_t send_packet(int fd, const void *data, size_t len) {
+ static struct sockaddr_in toaddr;
+ if (toaddr.sin_family != AF_INET) {
+ memset(&toaddr, 0, sizeof(struct sockaddr_in));
+ toaddr.sin_family = AF_INET;
+ toaddr.sin_port = htons(MDNS_PORT);
+ toaddr.sin_addr.s_addr = inet_addr(MDNS_ADDR);
+ }
+
+ return sendto(fd, data, len, 0, (struct sockaddr *)&toaddr, sizeof(struct sockaddr_in));
+}
+
+// populate the specified list which matches the RR name and type
+// type can be RR_ANY, which populates all entries EXCEPT RR_NSEC
+static int populate_answers(struct mdnsd *svr, struct rr_list **rr_head, uint8_t *name,
+ enum rr_type type) {
+ int num_ans = 0;
+
+ // check if we have the records
+ pthread_mutex_lock(&svr->data_lock);
+ struct rr_group *ans_grp = rr_group_find(svr->group, name);
+ if (ans_grp == NULL) {
+ pthread_mutex_unlock(&svr->data_lock);
+ return num_ans;
+ }
+
+ // decide which records should go into answers
+ struct rr_list *n = ans_grp->rr;
+ for (; n; n = n->next) {
+ // exclude NSEC for RR_ANY
+ if (type == RR_ANY && n->e->type == RR_NSEC)
+ continue;
+
+ if ((type == n->e->type || type == RR_ANY) && cmp_nlabel(name, n->e->name) == 0) {
+ num_ans += rr_list_append(rr_head, n->e);
+ }
+ }
+
+ pthread_mutex_unlock(&svr->data_lock);
+
+ return num_ans;
+}
+
+// given a list of RRs, look up related records and add them
+static void add_related_rr(struct mdnsd *svr, struct rr_list *list, struct mdns_pkt *reply) {
+ for (; list; list = list->next) {
+ struct rr_entry *ans = list->e;
+
+ switch (ans->type) {
+ case RR_PTR:
+ // target host A, AAAA records
+ reply->num_add_rr += populate_answers(svr, &reply->rr_add, MDNS_RR_GET_PTR_NAME(ans), RR_ANY);
+ break;
+
+ case RR_SRV:
+ // target host A, AAAA records
+ reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->data.SRV.target, RR_ANY);
+
+ // perhaps TXT records of the same name?
+ // if we use RR_ANY, we risk pulling in the same RR_SRV
+ reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->name, RR_TXT);
+ break;
+
+ case RR_A:
+ case RR_AAAA:
+ reply->num_add_rr += populate_answers(svr, &reply->rr_add, ans->name, RR_NSEC);
+ break;
+
+ default:
+ // nothing to add
+ break;
+ }
+ }
+}
+
+// creates an announce packet given the type name PTR
+static void announce_srv(struct mdnsd *svr, struct mdns_pkt *reply, uint8_t *name) {
+ mdns_init_reply(reply, 0);
+
+ reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_PTR);
+
+ // remember to add the services dns-sd PTR too
+ reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, SERVICES_DNS_SD_NLABEL, RR_PTR);
+
+ // see if we can match additional records for answers
+ add_related_rr(svr, reply->rr_ans, reply);
+
+ // additional records for additional records
+ add_related_rr(svr, reply->rr_add, reply);
+}
+
+// processes the incoming MDNS packet
+// returns >0 if processed, 0 otherwise
+static int process_mdns_pkt(struct mdnsd *svr, struct mdns_pkt *pkt, struct mdns_pkt *reply) {
+ int i;
+
+ assert(pkt != NULL);
+
+ // is it standard query?
+ if ((pkt->flags & MDNS_FLAG_RESP) == 0 && MDNS_FLAG_GET_OPCODE(pkt->flags) == 0) {
+ mdns_init_reply(reply, pkt->id);
+
+ DEBUG_PRINTF("flags = %04x, qn = %d, ans = %d, add = %d\n", pkt->flags, pkt->num_qn,
+ pkt->num_ans_rr, pkt->num_add_rr);
+
+ // loop through questions
+ struct rr_list *qnl = pkt->rr_qn;
+ for (i = 0; i < pkt->num_qn; i++, qnl = qnl->next) {
+ struct rr_entry *qn = qnl->e;
+ int num_ans_added = 0;
+
+ char *namestr = nlabel_to_str(qn->name);
+ DEBUG_PRINTF("qn #%d: type %s (%02x) %s - ", i, rr_get_type_name(qn->type), qn->type,
+ namestr);
+ free(namestr);
+
+ // check if it's a unicast query - we ignore those
+ if (qn->unicast_query) {
+ DEBUG_PRINTF("skipping unicast query\n");
+ continue;
+ }
+
+ num_ans_added = populate_answers(svr, &reply->rr_ans, qn->name, qn->type);
+ reply->num_ans_rr += num_ans_added;
+
+ DEBUG_PRINTF("added %d answers\n", num_ans_added);
+ }
+
+ // remove our replies if they were already in their answers
+ struct rr_list *ans = NULL, *prev_ans = NULL;
+ for (ans = reply->rr_ans; ans;) {
+ struct rr_list *next_ans = ans->next;
+ struct rr_entry *known_ans = rr_entry_match(pkt->rr_ans, ans->e);
+
+ // discard answers that have at least half of the actual TTL
+ if (known_ans != NULL && known_ans->ttl >= ans->e->ttl / 2) {
+ char *namestr = nlabel_to_str(ans->e->name);
+ DEBUG_PRINTF("removing answer for %s\n", namestr);
+ free(namestr);
+
+ // check if list item is head
+ if (prev_ans == NULL)
+ reply->rr_ans = ans->next;
+ else
+ prev_ans->next = ans->next;
+ free(ans);
+
+ ans = prev_ans;
+
+ // adjust answer count
+ reply->num_ans_rr--;
+ }
+
+ prev_ans = ans;
+ ans = next_ans;
+ }
+
+ // see if we can match additional records for answers
+ add_related_rr(svr, reply->rr_ans, reply);
+
+ // additional records for additional records
+ add_related_rr(svr, reply->rr_add, reply);
+
+ DEBUG_PRINTF("\n");
+
+ return reply->num_ans_rr;
+ }
+
+ return 0;
+}
+
+int create_pipe(int handles[2]) {
+#ifdef _WIN32
+ SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock == INVALID_SOCKET) {
+ return -1;
+ }
+ struct sockaddr_in serv_addr;
+ memset(&serv_addr, 0, sizeof(serv_addr));
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_port = htons(0);
+ serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) {
+ closesocket(sock);
+ return -1;
+ }
+ if (listen(sock, 1) == SOCKET_ERROR) {
+ closesocket(sock);
+ return -1;
+ }
+ int len = sizeof(serv_addr);
+ if (getsockname(sock, (SOCKADDR *)&serv_addr, &len) == SOCKET_ERROR) {
+ closesocket(sock);
+ return -1;
+ }
+ if ((handles[1] = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
+ closesocket(sock);
+ return -1;
+ }
+ if (connect(handles[1], (struct sockaddr *)&serv_addr, len) == SOCKET_ERROR) {
+ closesocket(sock);
+ return -1;
+ }
+ if ((handles[0] = accept(sock, (struct sockaddr *)&serv_addr, &len)) == INVALID_SOCKET) {
+ closesocket((SOCKET)handles[1]);
+ handles[1] = INVALID_SOCKET;
+ closesocket(sock);
+ return -1;
+ }
+ closesocket(sock);
+ return 0;
+#else
+ return pipe(handles);
+#endif
+}
+
+int read_pipe(int s, char *buf, int len) {
+#ifdef _WIN32
+ int ret = recv(s, buf, len, 0);
+ if (ret < 0 && WSAGetLastError() == WSAECONNRESET) {
+ ret = 0;
+ }
+ return ret;
+#else
+ return read(s, buf, len);
+#endif
+}
+
+int write_pipe(int s, char *buf, int len) {
+#ifdef _WIN32
+ return send(s, buf, len, 0);
+#else
+ return write(s, buf, len);
+#endif
+}
+
+int close_pipe(int s) {
+#ifdef _WIN32
+ return closesocket(s);
+#else
+ return close(s);
+#endif
+}
+
+// main loop to receive, process and send out MDNS replies
+// also handles MDNS service announces
+static 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
+
+ void *pkt_buffer = malloc(PACKET_SIZE);
+
+ if (svr->notify_pipe[0] > max_fd)
+ max_fd = svr->notify_pipe[0];
+
+ struct mdns_pkt *mdns_reply = malloc(sizeof(struct mdns_pkt));
+ if (mdns_reply)
+ memset(mdns_reply, 0, sizeof(struct mdns_pkt));
+ else
+ die("could not allocate memory for \"mdns_reply\" in tinysvcmdns");
+
+ while (!svr->stop_flag) {
+ FD_ZERO(&sockfd_set);
+ FD_SET(svr->sockfd, &sockfd_set);
+ FD_SET(svr->notify_pipe[0], &sockfd_set);
+ select(max_fd + 1, &sockfd_set, NULL, NULL, NULL);
+
+ if (FD_ISSET(svr->notify_pipe[0], &sockfd_set)) {
+ // flush the notify_pipe
+ read_pipe(svr->notify_pipe[0], (char *)¬ify_buf, 1);
+ } else if (FD_ISSET(svr->sockfd, &sockfd_set)) {
+ struct sockaddr_in fromaddr;
+ socklen_t sockaddr_size = sizeof(struct sockaddr_in);
+
+ ssize_t recvsize = recvfrom(svr->sockfd, pkt_buffer, PACKET_SIZE, 0,
+ (struct sockaddr *)&fromaddr, &sockaddr_size);
+ if (recvsize < 0) {
+ log_message(LOG_ERR, "recv(): %m");
+ }
+
+ DEBUG_PRINTF("data from=%s size=%ld\n", inet_ntoa(fromaddr.sin_addr), (long)recvsize);
+ struct mdns_pkt *mdns = mdns_parse_pkt(pkt_buffer, recvsize);
+ if (mdns != NULL) {
+ if (process_mdns_pkt(svr, mdns, mdns_reply)) {
+ size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
+ send_packet(svr->sockfd, pkt_buffer, replylen);
+ } else if (mdns->num_qn == 0) {
+ DEBUG_PRINTF("(no questions in packet)\n\n");
+ }
+
+ mdns_pkt_destroy(mdns);
+ }
+ }
+
+ // send out announces
+ while (1) {
+ struct rr_entry *ann_e = NULL;
+
+ // extract from head of list
+ pthread_mutex_lock(&svr->data_lock);
+ if (svr->announce)
+ ann_e = rr_list_remove(&svr->announce, svr->announce->e);
+ pthread_mutex_unlock(&svr->data_lock);
+
+ if (!ann_e)
+ break;
+
+ char *namestr = nlabel_to_str(ann_e->name);
+ DEBUG_PRINTF("sending announce for %s\n", namestr);
+ free(namestr);
+
+ announce_srv(svr, mdns_reply, ann_e->name);
+
+ if (mdns_reply->num_ans_rr > 0) {
+ size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
+ send_packet(svr->sockfd, pkt_buffer, replylen);
+ }
+ }
+ }
+
+ // main thread terminating. send out "goodbye packets" for services
+ mdns_init_reply(mdns_reply, 0);
+
+ pthread_mutex_lock(&svr->data_lock);
+ struct rr_list *svc_le = svr->services;
+ for (; svc_le; svc_le = svc_le->next) {
+ // set TTL to zero
+ svc_le->e->ttl = 0;
+ mdns_reply->num_ans_rr += rr_list_append(&mdns_reply->rr_ans, svc_le->e);
+ }
+ pthread_mutex_unlock(&svr->data_lock);
+
+ // send out packet
+ if (mdns_reply->num_ans_rr > 0) {
+ size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
+ send_packet(svr->sockfd, pkt_buffer, replylen);
+ }
+
+ // destroy packet
+ mdns_init_reply(mdns_reply, 0);
+ free(mdns_reply);
+
+ free(pkt_buffer);
+
+ close_pipe(svr->sockfd);
+
+ svr->stop_flag = 2;
+}
+
+/////////////////////////////////////////////////////
+
+void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip) {
+ struct rr_entry *a_e = NULL, *nsec_e = NULL;
+
+ // currently can't be called twice
+ // dont ask me what happens if the IP changes
+ assert(svr->hostname == NULL);
+
+ a_e = rr_create_a(create_nlabel(hostname), ip); // 120 seconds automatically
+
+ nsec_e = rr_create(create_nlabel(hostname), RR_NSEC);
+ nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500)
+ rr_set_nsec(nsec_e, RR_A);
+
+ pthread_mutex_lock(&svr->data_lock);
+ svr->hostname = create_nlabel(hostname);
+ rr_group_add(&svr->group, a_e);
+ rr_group_add(&svr->group, nsec_e);
+ pthread_mutex_unlock(&svr->data_lock);
+}
+
+void mdnsd_set_hostname_v6(struct mdnsd *svr, const char *hostname, struct in6_addr *addr) {
+ struct rr_entry *aaaa_e = NULL, *nsec_e = NULL;
+
+ // currently can't be called twice
+ // dont ask me what happens if the IP changes
+ assert(svr->hostname == NULL);
+
+ aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); // 120 seconds automatically
+
+ nsec_e = rr_create(create_nlabel(hostname), RR_NSEC);
+ nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500)
+ rr_set_nsec(nsec_e, RR_AAAA);
+
+ pthread_mutex_lock(&svr->data_lock);
+ svr->hostname = create_nlabel(hostname);
+ rr_group_add(&svr->group, aaaa_e);
+ rr_group_add(&svr->group, nsec_e);
+ pthread_mutex_unlock(&svr->data_lock);
+}
+
+void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr) {
+ pthread_mutex_lock(&svr->data_lock);
+ rr_group_add(&svr->group, rr);
+ pthread_mutex_unlock(&svr->data_lock);
+}
+
+struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name,
+ const char *type, uint16_t port, const char *hostname,
+ const char *txt[]) {
+ struct rr_entry *txt_e = NULL, *srv_e = NULL, *ptr_e = NULL, *bptr_e = NULL;
+ uint8_t *target;
+ uint8_t *inst_nlabel, *type_nlabel, *nlabel;
+ struct mdns_service *service = malloc(sizeof(struct mdns_service));
+ if (service)
+ memset(service, 0, sizeof(struct mdns_service));
+ else
+ die("could not allocate memory for \"service\" in tinysvcmdns");
+ // combine service name
+ type_nlabel = create_nlabel(type);
+ inst_nlabel = create_label(instance_name);
+ if (inst_nlabel) {
+ nlabel = join_nlabel(inst_nlabel, type_nlabel);
+ } else {
+ die("could not allocate memory for \"inst_nlabel\" in tinysvcmdns");
+ }
+
+ // create TXT record
+ if (txt && *txt) {
+ txt_e = rr_create(dup_nlabel(nlabel), RR_TXT); // automatically 4500 seconds
+ rr_list_append(&service->entries, txt_e);
+
+ // add TXTs
+ for (; *txt; txt++)
+ rr_add_txt(txt_e, *txt);
+ }
+
+ // create SRV record
+ assert(hostname || svr->hostname); // either one as target
+ target = hostname ? create_nlabel(hostname) : dup_nlabel(svr->hostname);
+
+ srv_e = rr_create_srv(dup_nlabel(nlabel), port, target); // automatically 4500 seconds
+ rr_list_append(&service->entries, srv_e);
+
+ // create PTR record for type
+ ptr_e = rr_create_ptr(type_nlabel, srv_e); // automatically 4500 seconds
+
+ // create services PTR record for type
+ // this enables the type to show up as a "service"
+ bptr_e = rr_create_ptr(dup_nlabel(SERVICES_DNS_SD_NLABEL), ptr_e); // automatically 4500 seconds
+
+ // modify lists here
+ pthread_mutex_lock(&svr->data_lock);
+
+ if (txt_e)
+ rr_group_add(&svr->group, txt_e);
+ rr_group_add(&svr->group, srv_e);
+ rr_group_add(&svr->group, ptr_e);
+ rr_group_add(&svr->group, bptr_e);
+
+ // append PTR entry to announce list
+ rr_list_append(&svr->announce, ptr_e);
+ rr_list_append(&svr->services, ptr_e);
+
+ pthread_mutex_unlock(&svr->data_lock);
+
+ // don't free type_nlabel - it's with the PTR record
+ free(nlabel);
+ free(inst_nlabel);
+
+ // notify server
+ write_pipe(svr->notify_pipe[1], ".", 1);
+
+ return service;
+}
+
+void mdns_service_destroy(struct mdns_service *srv) {
+ assert(srv != NULL);
+ rr_list_destroy(srv->entries, 0);
+ free(srv);
+}
+
+struct mdnsd *mdnsd_start() {
+ pthread_t tid;
+ pthread_attr_t attr;
+
+ struct mdnsd *server = malloc(sizeof(struct mdnsd));
+ if (server)
+ memset(server, 0, sizeof(struct mdnsd));
+ else
+ die("could not allocate memory for \"server\" in tinysvcmdns");
+
+ if (create_pipe(server->notify_pipe) != 0) {
+ log_message(LOG_ERR, "pipe(): %m\n");
+ free(server);
+ return NULL;
+ }
+
+ server->sockfd = create_recv_sock();
+ if (server->sockfd < 0) {
+ log_message(LOG_ERR, "unable to create recv socket");
+ free(server);
+ return NULL;
+ }
+
+ pthread_mutex_init(&server->data_lock, NULL);
+
+ // init thread
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ if (pthread_create(&tid, &attr, (void *(*)(void *))main_loop, (void *)server) != 0) {
+ pthread_mutex_destroy(&server->data_lock);
+ free(server);
+ return NULL;
+ }
+
+ return server;
+}
+
+void mdnsd_stop(struct mdnsd *s) {
+ assert(s != NULL);
+
+ struct timeval tv = {
+ .tv_sec = 0, .tv_usec = 500 * 1000,
+ };
+
+ s->stop_flag = 1;
+ write_pipe(s->notify_pipe[1], ".", 1);
+
+ while (s->stop_flag != 2)
+ select(0, NULL, NULL, NULL, &tv);
+
+ close_pipe(s->notify_pipe[0]);
+ close_pipe(s->notify_pipe[1]);
+
+ pthread_mutex_destroy(&s->data_lock);
+ rr_group_destroy(s->group);
+ rr_list_destroy(s->announce, 0);
+ rr_list_destroy(s->services, 0);
+
+ if (s->hostname)
+ free(s->hostname);
+
+ free(s);
+}
diff --git a/tinysvcmdns.h b/tinysvcmdns.h
new file mode 100644
index 0000000..a8a19a9
--- /dev/null
+++ b/tinysvcmdns.h
@@ -0,0 +1,233 @@
+// This file is the concatenation of mdnsd.h and mdns.h
+// from tinysvcmdns with minor modifications
+// The code was taken from https://bitbucket.org/geekman/tinysvcmdns at revision e34b562
+
+/*
+ * tinysvcmdns - a tiny MDNS implementation for publishing services
+ * Copyright (C) 2011 Darell Tan
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TINYSVCMDNS_H
+
+//******************************************************//
+// mdns.h //
+//******************************************************//
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include
+#else
+#include
+#endif
+
+#define MALLOC_ZERO_STRUCT(x, type) \
+ x = malloc(sizeof(struct type)); \
+ if (x) \
+ memset(x, 0, sizeof(struct type));
+
+#define DECL_MALLOC_ZERO_STRUCT(x, type) struct type *MALLOC_ZERO_STRUCT(x, type)
+
+struct rr_data_srv {
+ uint16_t priority;
+ uint16_t weight;
+ uint16_t port;
+ uint8_t *target; // host
+};
+
+struct rr_data_txt {
+ struct rr_data_txt *next;
+ uint8_t *txt;
+};
+
+struct rr_data_nsec {
+ // uint8_t *name; // same as record
+
+ // NSEC occupies the 47th bit, 5 bytes
+ // uint8_t bitmap_len; // = 5
+ uint8_t bitmap[5]; // network order: first byte contains LSB
+};
+
+struct rr_data_ptr {
+ uint8_t *name; // NULL if entry is to be used
+ struct rr_entry *entry;
+};
+
+struct rr_data_a {
+ uint32_t addr;
+};
+
+struct rr_data_aaaa {
+ struct in6_addr *addr;
+};
+
+struct rr_entry {
+ uint8_t *name;
+
+ enum rr_type {
+ RR_A = 0x01,
+ RR_PTR = 0x0C,
+ RR_TXT = 0x10,
+ RR_AAAA = 0x1C,
+ RR_SRV = 0x21,
+ RR_NSEC = 0x2F,
+ RR_ANY = 0xFF,
+ } type;
+
+ uint32_t ttl;
+
+ // for use in Questions only
+ char unicast_query;
+
+ // for use in Answers only
+ char cache_flush;
+
+ uint16_t rr_class;
+
+ // RR data
+ union {
+ struct rr_data_nsec NSEC;
+ struct rr_data_srv SRV;
+ struct rr_data_txt TXT;
+ struct rr_data_ptr PTR;
+ struct rr_data_a A;
+ struct rr_data_aaaa AAAA;
+ } data;
+};
+
+struct rr_list {
+ struct rr_entry *e;
+ struct rr_list *next;
+};
+
+struct rr_group {
+ uint8_t *name;
+
+ struct rr_list *rr;
+
+ struct rr_group *next;
+};
+
+#define MDNS_FLAG_RESP (1 << 15) // Query=0 / Response=1
+#define MDNS_FLAG_AA (1 << 10) // Authoritative
+#define MDNS_FLAG_TC (1 << 9) // TrunCation
+#define MDNS_FLAG_RD (1 << 8) // Recursion Desired
+#define MDNS_FLAG_RA (1 << 7) // Recursion Available
+#define MDNS_FLAG_Z (1 << 6) // Reserved (zero)
+
+#define MDNS_FLAG_GET_RCODE(x) (x & 0x0F)
+#define MDNS_FLAG_GET_OPCODE(x) ((x >> 11) & 0x0F)
+
+// gets the PTR target name, either from "name" member or "entry" member
+#define MDNS_RR_GET_PTR_NAME(rr) \
+ (rr->data.PTR.name != NULL ? rr->data.PTR.name : rr->data.PTR.entry->name)
+
+struct mdns_pkt {
+ uint16_t id; // transaction ID
+ uint16_t flags;
+ uint16_t num_qn;
+ uint16_t num_ans_rr;
+ uint16_t num_auth_rr;
+ uint16_t num_add_rr;
+
+ struct rr_list *rr_qn; // questions
+ struct rr_list *rr_ans; // answer RRs
+ struct rr_list *rr_auth; // authority RRs
+ struct rr_list *rr_add; // additional RRs
+};
+
+struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len);
+
+void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id);
+size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len);
+
+void mdns_pkt_destroy(struct mdns_pkt *p);
+void rr_group_destroy(struct rr_group *group);
+struct rr_group *rr_group_find(struct rr_group *g, uint8_t *name);
+struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type);
+struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry);
+void rr_group_add(struct rr_group **group, struct rr_entry *rr);
+
+int rr_list_count(struct rr_list *rr);
+int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr);
+struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr);
+void rr_list_destroy(struct rr_list *rr, char destroy_items);
+
+struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr);
+struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target);
+struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr);
+struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr);
+struct rr_entry *rr_create(uint8_t *name, enum rr_type type);
+void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type);
+void rr_add_txt(struct rr_entry *rr_txt, const char *txt);
+
+const char *rr_get_type_name(enum rr_type type);
+
+uint8_t *create_label(const char *txt);
+uint8_t *create_nlabel(const char *name);
+char *nlabel_to_str(const uint8_t *name);
+uint8_t *dup_label(const uint8_t *label);
+uint8_t *dup_nlabel(const uint8_t *n);
+uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2);
+
+// compares 2 names
+static inline int cmp_nlabel(const uint8_t *L1, const uint8_t *L2) {
+ return strcmp((char *)L1, (char *)L2);
+}
+
+//******************************************************//
+// mdnsd.h //
+//******************************************************//
+
+struct mdnsd;
+struct mdns_service;
+
+// starts a MDNS responder instance
+// returns NULL if unsuccessful
+struct mdnsd *mdnsd_start();
+
+// stops the given MDNS responder instance
+void mdnsd_stop(struct mdnsd *s);
+
+// sets the hostname for the given MDNS responder instance
+void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip);
+
+// sets the hostname for the given MDNS responder instance, with an ipv6 address
+void mdnsd_set_hostname_v6(struct mdnsd *svr, const char *hostname, struct in6_addr *addr);
+
+// adds an additional RR
+void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr);
+
+// registers a service with the MDNS responder instance
+struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name,
+ const char *type, uint16_t port, const char *hostname,
+ const char *txt[]);
+
+// destroys the mdns_service struct returned by mdnsd_register_svc()
+void mdns_service_destroy(struct mdns_service *srv);
+
+#endif // _TINYSVCMDNS_H
--
cgit v1.2.3
From 5547ec3afc266345bbe93c3722485e47eb4b94ff Mon Sep 17 00:00:00 2001
From: Chris Boot
Date: Sat, 27 Jan 2018 10:53:58 +0000
Subject: Import shairport-sync_3.1.7-1~bpo9+1.debian.tar.xz
[dgit import tarball shairport-sync 3.1.7-1~bpo9+1 shairport-sync_3.1.7-1~bpo9+1.debian.tar.xz]
---
NEWS | 16 ++++++
changelog | 125 +++++++++++++++++++++++++++++++++++++++++++++++
compat | 1 +
control | 32 ++++++++++++
copyright | 94 +++++++++++++++++++++++++++++++++++
gbp.conf | 3 ++
patches/series | 1 +
patches/typo-fixes.patch | 57 +++++++++++++++++++++
rules | 41 ++++++++++++++++
shairport-sync.default | 11 +++++
shairport-sync.doc-base | 9 ++++
shairport-sync.docs | 3 ++
shairport-sync.init | 35 +++++++++++++
shairport-sync.install | 1 +
shairport-sync.postinst | 45 +++++++++++++++++
shairport-sync.postrm | 29 +++++++++++
shairport-sync.service | 20 ++++++++
shairport-sync.tmpfile | 1 +
source/format | 1 +
source/lintian-overrides | 2 +
watch | 6 +++
21 files changed, 533 insertions(+)
create mode 100644 NEWS
create mode 100644 changelog
create mode 100644 compat
create mode 100644 control
create mode 100644 copyright
create mode 100644 gbp.conf
create mode 100644 patches/series
create mode 100644 patches/typo-fixes.patch
create mode 100755 rules
create mode 100644 shairport-sync.default
create mode 100644 shairport-sync.doc-base
create mode 100644 shairport-sync.docs
create mode 100755 shairport-sync.init
create mode 100644 shairport-sync.install
create mode 100644 shairport-sync.postinst
create mode 100644 shairport-sync.postrm
create mode 100644 shairport-sync.service
create mode 100644 shairport-sync.tmpfile
create mode 100644 source/format
create mode 100644 source/lintian-overrides
create mode 100644 watch
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..ccb6fe0
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,16 @@
+shairport-sync (3.1.7-1) unstable; urgency=medium
+
+ The deprecated PulseAudio output plugin ("pulse") has been removed. If you
+ are using this output module, please migrate to the new PulseAudio module
+ called "pa".
+
+ -- Chris Boot Sun, 21 Jan 2018 12:44:34 +0000
+
+shairport-sync (3.1.3-1~exp1) experimental; urgency=medium
+
+ Shairport Sync 3.1 introduces a new PulseAudio output module called "pa".
+ The old module, called "pulse", is deprecated and will likely be removed in
+ a future release. If you are using the PulseAudio output you should migrate
+ to the new driver which, unlike the old module, supports synchronised audio.
+
+ -- Chris Boot Thu, 23 Nov 2017 12:13:27 +0000
diff --git a/changelog b/changelog
new file mode 100644
index 0000000..23c43bf
--- /dev/null
+++ b/changelog
@@ -0,0 +1,125 @@
+shairport-sync (3.1.7-1~bpo9+1) stretch-backports; urgency=medium
+
+ * Rebuild for stretch-backports.
+ * d/control: remove Rules-Requires-Root to work around failed builds with
+ pbuilder.
+ * d/gbp.conf: tell git-buildpackage about the stretch-backports branch.
+ * d/control: switch debhelper dependency to (>= 11~).
+
+ -- Chris Boot Sat, 27 Jan 2018 10:53:58 +0000
+
+shairport-sync (3.1.7-1) unstable; urgency=medium
+
+ * New upstream release:
+ - Deprecated PulseAudio output plugin has been removed; stop trying to
+ enable it in debian/rules and add a news entry.
+ * Switch to compat level 11.
+ * Bump standards version to 4.1.3:
+ - d/copyright: switch to https:// URI for Format field.
+
+ -- Chris Boot Sun, 21 Jan 2018 13:02:29 +0000
+
+shairport-sync (3.1.4-1) unstable; urgency=medium
+
+ * New upstream release:
+ - Fixes CVE-2017-12087 (Closes: #882508)
+ Please note that Debian binary packages are unaffected by this security
+ issue: the issue is present in a source file that is not built into the
+ binary included in Debian packages.
+ * Upload to unstable.
+
+ -- Chris Boot Fri, 24 Nov 2017 10:21:43 +0000
+
+shairport-sync (3.1.3-1~exp1) experimental; urgency=medium
+
+ * New upstream release.
+ * Refresh typo-fixes.patch.
+ * Bump standards version to 4.1.1:
+ - d/control: change Priority to optional
+ * d/copyright: updates for new authors, new vendored source and extended
+ copyright dates.
+ * Enable new "pa" PulseAudio driver:
+ - d/rules: add --with-pa to configure arguments
+ - d/NEWS: announce the new driver and the deprecation of the old "pulse"
+ driver.
+ * Enable new "convolution" filtering:
+ - d/rules: add --with-convolution to configure arguments
+ - d/control: add libsndfile1-dev to Build-Depends
+ * d/control: set Rules-Requires-Root: no.
+ * Upload to experimental.
+
+ -- Chris Boot Thu, 23 Nov 2017 09:47:28 +0000
+
+shairport-sync (3.0.2-1) unstable; urgency=medium
+
+ * Upload to unstable.
+
+ -- Chris Boot Mon, 31 Jul 2017 16:28:44 +0100
+
+shairport-sync (3.0.2-1~exp1) experimental; urgency=medium
+
+ * New upstream releae.
+ * Refresh typo-fixes.patch.
+ * Switch to debhelper 10:
+ - bump compat level and build dependency on debhelper
+ - remove dependency on dh-autoreconf and dh-systemd
+ - remove implied --with autoreconf,systemd from debian/rules
+ * Run wrap-and-sort -st
+ * Upload to experimental.
+
+ -- Chris Boot Mon, 17 Apr 2017 15:20:53 +0100
+
+shairport-sync (2.8.6-1) unstable; urgency=medium
+
+ * New upstream release.
+ * Refresh typo-fixes.patch.
+ * Remove Fix-a-compilation-error-in-audio_pulse-c.patch as it's included
+ upstream.
+
+ -- Chris Boot Fri, 11 Nov 2016 15:40:43 +0000
+
+shairport-sync (2.8.4-1) unstable; urgency=medium
+
+ * New upstream release.
+ * Refresh typo-fixes.patch.
+ * Import upstream patch Fix-a-compilation-error-in-audio_pulse-c.patch to
+ fix a compilation error in audio_pulse.c and a related tweak to
+ configure.ac.
+
+ -- Chris Boot Sat, 18 Jun 2016 11:13:08 +0100
+
+shairport-sync (2.8.3-1) unstable; urgency=medium
+
+ * New upstream release.
+ * Bump standards version to 3.9.8. No changes required.
+ * Refresh typo-fixes.patch; add another small typo fix.
+
+ -- Chris Boot Wed, 04 May 2016 10:28:58 +0100
+
+shairport-sync (2.8.1-1) unstable; urgency=medium
+
+ * New upstream release.
+ * Add avahi-daemon to Depends. (Closes: #816859)
+ * Bump standards version to 3.9.7. No changes required.
+ * Add Documentation option to systemd unit file.
+ * Install upstream TROUBLESHOOTING.md documentation file.
+
+ -- Chris Boot Sun, 20 Mar 2016 13:19:49 +0000
+
+shairport-sync (2.8.0-1) unstable; urgency=medium
+
+ * New upstream release.
+ * Refresh patches:
+ - build-man.patch: remove; applied upstream.
+ - manpage-typos.patch: remove; applied upstream.
+ - typo-fixes.patch: new patch to correct various other typos.
+ * Fix piuparts failure due to left over /var/lib/shairport-sync.
+ * debian/control: update Vcs-Git and Vcs-Browser to use https:// URLs.
+
+ -- Chris Boot Mon, 08 Feb 2016 12:29:02 +0000
+
+shairport-sync (2.6-1) unstable; urgency=low
+
+ * Initial release. (Closes: #743638)
+
+ -- Chris Boot Mon, 04 Jan 2016 20:00:31 +0000
diff --git a/compat b/compat
new file mode 100644
index 0000000..b4de394
--- /dev/null
+++ b/compat
@@ -0,0 +1 @@
+11
diff --git a/control b/control
new file mode 100644
index 0000000..d712672
--- /dev/null
+++ b/control
@@ -0,0 +1,32 @@
+Source: shairport-sync
+Section: sound
+Priority: optional
+Maintainer: Chris Boot
+Build-Depends:
+ debhelper (>= 11~),
+ libasound2-dev,
+ libavahi-client-dev,
+ libconfig-dev,
+ libdaemon-dev,
+ libpopt-dev,
+ libpulse-dev,
+ libsndfile1-dev,
+ libsoxr-dev,
+ libssl-dev,
+ xmltoman,
+Standards-Version: 4.1.3
+Homepage: https://github.com/mikebrady/shairport-sync
+Vcs-Git: https://anonscm.debian.org/git/collab-maint/shairport-sync.git
+Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/shairport-sync.git
+
+Package: shairport-sync
+Architecture: linux-any
+Depends: adduser, avahi-daemon, ${misc:Depends}, ${shlibs:Depends}
+Description: AirPlay audio player
+ Plays audio streamed from iTunes, iOS devices and third-party AirPlay
+ sources such as ForkedDaapd and 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 without difficulty.
+ .
+ Shairport Sync does not support AirPlay video or photo streaming.
diff --git a/copyright b/copyright
new file mode 100644
index 0000000..586e4ab
--- /dev/null
+++ b/copyright
@@ -0,0 +1,94 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: shairport-sync
+Source: https://github.com/mikebrady/shairport-sync/
+
+Files: *
+Copyright: 2011-2013 James Laird
+ 2014-2017 Mike Brady
+License: MIT
+
+Files: alac.c
+Copyright: 2005 David Hammerton
+License: MIT
+
+Files: audio_alsa.c
+Copyright: 2013 Muffinman
+ 2013 Sandro Cavazzoni ("Skaman")
+License: MIT
+
+Files: mdns_dns_sd.c mdns_external.c mdns_tinysvcmdns.c
+Copyright: 2013 Paul Lietar
+License: MIT
+
+Files: FFTConvolver/*
+Copyright: 2012, 2016 HiFi-LoFi
+License: MIT
+
+Files: audio_sndio.c
+Copyright: 2013 Dimitri Sokolyuk
+ 2014-2017 Mike Brady
+ 2017 Tobias Kortkamp
+License: ISC
+ Permission to use, copy, modify, and distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Files: tinysvcmdns.c tinysvcmdns.h
+Copyright: 2011 Darell Tan
+License: BSD-3-Clause
+ All rights reserved.
+ .
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Files: debian/*
+Copyright: 2015-2016 Chris Boot
+License: MIT
+
+License: MIT
+ 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.
diff --git a/gbp.conf b/gbp.conf
new file mode 100644
index 0000000..94fde45
--- /dev/null
+++ b/gbp.conf
@@ -0,0 +1,3 @@
+[buildpackage]
+debian-branch = stretch-backports
+dist = stretch
diff --git a/patches/series b/patches/series
new file mode 100644
index 0000000..e2748c6
--- /dev/null
+++ b/patches/series
@@ -0,0 +1 @@
+typo-fixes.patch
diff --git a/patches/typo-fixes.patch b/patches/typo-fixes.patch
new file mode 100644
index 0000000..74c2ba4
--- /dev/null
+++ b/patches/typo-fixes.patch
@@ -0,0 +1,57 @@
+Description: Correct various typos
+Author: Chris Boot
+Last-Update: 2016-02-07
+
+--- a/RELEASENOTES.md
++++ b/RELEASENOTES.md
+@@ -720,7 +720,7 @@
+ Some big changes "under the hood" have been made, leading to limited support for unsynchronised output to `stdout` or to a named pipe and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided.
+
+ In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`.
+-This gives more flexibility in adding features gives better compatability across different versions of Linux.
++This gives more flexibility in adding features gives better compatibility across different versions of Linux.
+ Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file.
+
+ Note that, for the present, settings in the configuration will have priority over command line options for Shairport Sync itself, in contravention of the normal unix convention. Audio back end command line options, i.e. those after the `--`, have priority over configuration file settings for the audio backends.
+--- a/common.h
++++ b/common.h
+@@ -103,7 +103,7 @@
+ int allow_session_interruption;
+ int timeout; // while in play mode, exit if no packets of audio come in for more than this number
+ // of seconds . Zero means never exit.
+- int dont_check_timeout; // this is used to maintain backward compatability with the old -t option
++ int dont_check_timeout; // this is used to maintain backward compatibility with the old -t option
+ // behaviour; only set by -t 0, cleared by everything else
+ char *output_name;
+ audio_output *output;
+--- a/shairport.c
++++ b/shairport.c
+@@ -1375,7 +1375,7 @@
+ /* Print out options */
+ debug(1, "statistics_requester status is %d.", config.statistics_requested);
+ debug(1, "daemon status is %d.", config.daemonise);
+- debug(1, "deamon pid file is \"%s\".", pid_file_proc());
++ debug(1, "daemon pid file is \"%s\".", pid_file_proc());
+ debug(1, "rtsp listening port is %d.", config.port);
+ debug(1, "udp base port is %d.", config.udp_port_base);
+ debug(1, "udp port range is %d.", config.udp_port_range);
+--- a/man/shairport-sync.7.xml
++++ b/man/shairport-sync.7.xml
+@@ -88,7 +88,7 @@
+ i.e. the System Configuration Directory.)
+
+
+-
Within the configuraton file, settings are organised into groups, for example, there is a "general" group of
++
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: