diff --git a/.gitignore b/.gitignore
index 09c6aa66..6f76aec5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ shairport-sync.core
/org.gnome.ShairportSync.service
/dbus-interface.*
/mpris-interface.*
+/lib_*.*
# Some eclipse project files
.cproject
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index 671695c9..879015ad 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -40,26 +40,26 @@ Close the file and carefully dismount and eject the two drives. Remove the SD ca
### Boot, Configure, Update
The first thing to do on a Pi would be to use the `raspi-config` tool to expand the file system to use the entire card. It might be useful to change the `hostname` too. Next, do the usual update and upgrade:
```
-# apt update
-# apt upgrade
+# apt-get update
+# apt-get upgrade
+# rpi-update
```
-**Note:** If you are following this guide but are using a full-size Rapsberry Pi with the built-in DAC, then it is a good idea to update to the Raspian release of October 2018 or later, as a number of changes have been made in the firmware that improve the built-in DAC.
### Shairport Sync
First, install the packages needed by Shairport Sync:
```
-# apt install build-essential git xmltoman autoconf automake libtool libdaemon-dev libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev
+# apt-get install build-essential git xmltoman autoconf automake libtool libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
```
Next, download Shairport Sync, configure it, compile and install it:
```
$ git clone https://github.com/mikebrady/shairport-sync.git
$ cd shairport-sync
$ autoreconf -fi
-$ ./configure --sysconfdir=/etc --with-alsa --with-avahi --with-ssl=openssl --with-systemd
+$ ./configure --sysconfdir=/etc --with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-systemd
$ make
$ sudo make install
```
-SoX interpolaton is not included, as the Pi Zero would not be fast enough. *Do not* enable Shairport Sync to automatically start at boot time -- startup is organised differently.
+*Do not* enable Shairport Sync to automatically start at boot time -- startup is organised differently.
Third, finish by configuring Shairport Sync.
Here are the important options for the Shairport Sync configuration file at `/etc/shairport-sync.conf`:
@@ -75,19 +75,20 @@ general =
alsa =
{
output_device = "hw:1"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
- output_format = "S32"; // can be "U8", "S8", "S16", "S24" or "S32", with be LE or BE depending on the processor, but the device must be capable of it
};
```
Two `general` settings are worth noting. First, the option to ignore the sending device's volume control is enabled -- this means that the car audio's volume control is the only one that affects the audio volume. Of course this is a matter of personal preference.
Second, the maximum output offered by the DAC to the AUX port of the car audio can be reduced if it is overloading the input circuits. Again, that's a matter for personal selection and adjustment.
-The `alsa` settings are specific to the Pimoroni PHAT -- it does not have a hardware mixer and it does have a 32-bit capability which is worth enabling.
+The `alsa` settings are for the Pimoroni PHAT -- it does not have a hardware mixer, so no `mixer_control_name` is given.
+
+Note that the DAC's 32-bit capability is automatically selected if available, so there is no need to set it here. Similarly, since `soxr` support is included in the build, `soxr` interpolation will be automatically enabled if the device is fast enough.
### Extra Packages
A number of packages to enable the Pi to work as a WiFi base station are needed:
```
-# apt install hostapd isc-dhcp-server
+# apt-get install hostapd isc-dhcp-server
```
Disable both of these services from starting at boot time (this is because we will launch them sequentially later on):
```
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9ae6b8e2..ab4d5442 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,8 +10,7 @@ Changes and additions in the development branch make their way eventually to the
Issue Reports
----
-Issue reports are welcome, but before you report an issue, please check that your version of Shairport Sync is up to date. You'll find details of releases at [Shairport Sync Releases](https://github.com/mikebrady/shairport-sync/releases).
-Then, 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 version and build configuration of Shairport Sync you are using (use `$ shairport-sync -V` to get this). Then, if possible, some diagnostic information from the log or logfile would be useful.
+Issue reports are welcome, but before you report an issue, please have a look though the existing [issues](https://github.com/mikebrady/shairport-sync/issues), both open and closed, and check for hints in the [TROUBLESHOOTING](TROUBLESHOOTING.md) page. It would be great to give some details of the device and version of Linux or FreeBSD in use along with the version of Shairport Sync you are using (use `$ shairport-sync -V` 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).
diff --git a/CYGWIN.md b/CYGWIN.md
index c128388e..80cb3517 100644
--- a/CYGWIN.md
+++ b/CYGWIN.md
@@ -1,34 +1,52 @@
-Installing to Cygwin
+Installing Shairport Sync into Cygwin
+====
+
+This guide is based on installing onto a fresh installation of Cygwin 2.895 (64-bit installation) running in Windows 10
+inside VMWare Fusion on a Mac.
+
+The end result is a new Windows Service called `CYGWIN Shairport Sync`, providing an AirPlay service by which iOS devices or other AirPlay sources on the network can play audio through the Windows device.
+
+Windows Firewall
----
+While getting everything working, it is suggested that you temporarily disable the Windows Firewall. Shairport Sync uses port 5000 for TCP and uses three ports for UDP, so you should leave a minimum of three, and preferably at least 10, open from 6001 upwards. The Bonjour Service, used in conjunction with the Avahi daemon, advertises Shairport Sync over a number of further ports. Once everything is working, the firewall can be re-enabled gradually.
-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.
+Setting up Windows
+----
+Set up Windows 10 and install all updates. Install the `Bonjour Service`, available from Apple in an installer called "Bonjour Print Services for Windows v2.0.2".
-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`.
+* Download and run `Bonjour Print Services for Windows v2.0.2`
+* After accepting conditions and clicking the `Install` button, the installer will do a preliminary installation, installing just the Bonjour Service. It will then pause, inviting you to install Bonjour Print Services. You can decline this, as the Bonjour Service will have been installed during the first part of the installation.
-* 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`
-
-Assuming the Cygwin setup program (`setup-x86_64.exe` in this case) is in your Downloads directory and the default directory is your home directory, the following command should work:
+* Check Bonjour Service is running. In Windows, open the `Services` desktop application and ensure that you can see `Bonjour Service` running.
+
+Setting up Cygwin
+----
+* Download the Cygwin installer from the [official website](https://cygwin.com/install.html). Save the installer in the Downloads folder.
+
+* Open a Windows `Command Prompt` window and enter the following multi-line command, omitting the `C:\Users\mike>` prompt:
```
-.\Downloads\setup-x86_64.exe -P pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,make,libao-devel,openssl-devel,libtool,git,wget,flex,bison
+C:\Users\mike> Downloads\setup-x86_64.exe -P cygrunsrv,dbus,avahi,avahi-tools,gnome-keyring,libavahi-client-devel,^
+libglib2.0-devel,openssl,pkg-config,autoconf,automake,clang,libdaemon-devel,popt-devel,^
+make,libao-devel,openssl-devel,libtool,git,wget,flex,bison
```
+This will do a complete installation of Cygwin and all necessary packages.
+* Set up the D-Bus and Avahi Services:
+Open a `Cygwin64 Terminal` window in Administrator mode. Enter the following command:
+```
+$ messagebus-config
+```
+Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN D-Bus system service`. Open it and start it.
+
+Next, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command:
+```
+$ /usr/sbin/avahi-daemon-config
+```
+Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Avahi service`. Open it and start it.
+
+The `libconfig` Library
+----
+Shairport Sync relies on a library – `libconfig` – that is not a Cygwin package, so it must be downloaded, compiled and installed:
* Download, configure, compile and install `libconfig`:
```
$ git clone https://github.com/hyperrealm/libconfig.git
@@ -39,13 +57,34 @@ $ make
$ make install
$ cd ..
```
-* Next, download, configure and compile Shairport Sync:
+
+Shairport Sync
+----
+* Download, configure and compile Shairport Sync:
```
$ git clone https://github.com/mikebrady/shairport-sync.git
$ cd shairport-sync
+$ git checkout development // this is temporary
$ autoreconf -fi
-$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl --with-tinysvcmdns
+$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure --with-ao --with-ssl=openssl \
+ --with-avahi --with-dbus-interface --with-libdaemon --sysconfdir=/etc --with-cygwin-service
$ make
$ make install
```
-* That's it. There should be a `shairport-sync.exe` file in your directory.
+* The last step above installs the `shairport-sync` application into `/usr/local/bin` and also installs a configuration file, a service configuration script and two D-Bus policy files.
+
+Shairport Sync Service
+----
+* To install Shairport Sync as a Cygwin Service, open (or return to) a `Cygwin64 Terminal` window in Administrator mode. Enter the following command:
+```
+$ shairport-sync-config
+```
+Answer `yes` to all queries. Open the Windows `Services` desktop application (if it's already open, refresh the screen contents: `Actions > Refresh`) and look for the `CYGWIN Shairport Sync` service. Open it and start it.
+
+An AirPlay player on the local network should now be able to see an AirPlay output device bearing the computer's Device Name, e.g. `DESKTOP-0RHGN0`. You can set a different name by changing the settings in the Shairport Sync configuration file, installed at `/etc/shairport-sync.conf`.
+
+Since Shairport Sync is now a Cygwin Service, you do not need to open Cygwin to launch it – it should launch automatically when Windows is booted up.
+
+Known Issues
+----
+* Shairport Sync cannot access the D-Bus system bus to make its D-Bus interface available. The cause of this problem is unknown. (While the Avahi daemon can access the D-Bus system bus, Shairport Sync can not. The two applications use different D-Bus libraries, so perhaps the issue lies there.)
diff --git a/FEDORA.md b/FEDORA.md
index ae581cbe..bb57d58b 100644
--- a/FEDORA.md
+++ b/FEDORA.md
@@ -3,22 +3,22 @@ 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
+# yum install make automake gcc gcc-c++ kernel-devel
+# yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-devel openssl-devel popt-devel soxr-devel
```
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
+$ 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
+# 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
+# systemctl enable shairport-sync.service
+# systemctl start shairport-sync.service
```
diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp
index 4589c8ed..f115cf85 100644
--- a/FFTConvolver/convolver.cpp
+++ b/FFTConvolver/convolver.cpp
@@ -4,10 +4,9 @@
#include
#include "FFTConvolver.h"
#include "Utilities.h"
-extern "C" {
-#include "../common.h"
-}
+extern "C" void die(const char *format, ...);
+extern "C" void debug(int level, const char *format, ...);
static fftconvolver::FFTConvolver convolver_l;
static fftconvolver::FFTConvolver convolver_r;
@@ -40,7 +39,7 @@ void convolver_init(const char* filename, int max_length)
float buffer_l[size];
float buffer_r[size];
- int i;
+ unsigned int i;
for (i=0; i/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
@@ -152,10 +197,14 @@ if INSTALL_SYSTEMD
[ -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
+ # Choose a uid and gid of 801 completely arbitrarity, except that it should be below 1000. FreeBSD doesn't seem to allow you to say "an ID in the range of..."
+ pw showgroup shairport-sync > /dev/null 2>&1 || pw addgroup -n shairport-sync -g 801 > /dev/null 2>&1
+ pw showuser shairport-sync > /dev/null 2>&1 || pw adduser -c "shairport-sync unprivileged user" -n shairport-sync -u 801 -s /usr/sbin/nologin -d /nonexistent > /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
+if INSTALL_CYGWIN_SERVICE
+ cp scripts/shairport-sync-config /usr/local/bin
+endif
diff --git a/OPENBSD.md b/OPENBSD.md
index 7f70881f..446e9674 100644
--- a/OPENBSD.md
+++ b/OPENBSD.md
@@ -45,7 +45,7 @@ Next, switch to the `development` branch, configure the build and compile it:
```
$ git checkout development
$ autoreconf -i -f
-$ ./configure --sysconfdir=/etc --with-avahi --with-ssl=openssl --with-sndio --with-os=openbsd
+$ ./configure --sysconfdir=/etc --with-avahi --with-ssl=openssl --with-libdaemon --with-sndio --with-os=openbsd
$ make
```
The application is called `shairport-sync`. Check that it's running correctly by executing the following command:
@@ -54,7 +54,7 @@ $ ./shairport-sync -V
```
This will execute the application and it will return its version information and terminate, for example:
```
-3.2-OpenSSL-Avahi-sndio-sysconfdir:/etc
+3.2-libdaemon-OpenSSL-Avahi-sndio-sysconfdir:/etc
```
There is no make install yet -- you're on your own.
diff --git a/README.md b/README.md
index 01e638f9..e32aaf84 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+
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.
@@ -6,7 +7,7 @@ Audio played by a Shairport Sync-powered device stays synchronised with the sour
Shairport Sync runs on Linux, FreeBSD and OpenBSD. 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.
+This document is being updated.
More Information
----------
@@ -34,7 +35,7 @@ What else?
* 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`.
+* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.20 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.
@@ -149,7 +150,7 @@ 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.
+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.
Debian, Ubuntu and Raspbian users can get the basics with:
@@ -187,6 +188,7 @@ $ autoreconf -i -f
- `--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-libdaemon` include a demonising library needed if you want to be able to demonise Shairport Sync with the `-d` option. Not needed for `systemd`-based systems which demonise programs differently.
- `--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.
@@ -242,7 +244,7 @@ SYNOPSIS
...
```
-If your system is definitely a `systemd` system, choose `--with-systemd` below. Otherwise, choose `--with-systemv`.
+If your system is definitely a `systemd` system, choose `--with-libdaemon --with-systemd` below. Otherwise, choose `--with-systemv`.
**Choose the location of the configuration file**
@@ -255,7 +257,7 @@ Here is a recommended set of configuration options suitable for Linux installati
`$ ./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`.
+* For installation into a System V system, replace the `--with-systemd` with `--with-libdaemon --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:**
@@ -312,14 +314,13 @@ For the ALSA backend you may need to (c) specify the output device to use and (d
Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card.
-Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room`, give the service the password `secret` and use `libsoxr` interpolation, then you should do the following:
+Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room` asd give the service the password `secret`, then you should do the following:
```
general =
{
name = "Front Room";
password = "secret";
- interpolation = "soxr";
// ... other general settings
};
```
@@ -430,11 +431,10 @@ alsa = {
};
```
-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`:
+Here is an example of 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 = {
@@ -590,4 +590,3 @@ If you are using WiFi, you should ensure that WiFi power management is off. See
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
index 94cee6b8..9181e846 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -1,3 +1,24 @@
+Version 3.3
+====
+Version 3.3 is focused on stability improvements and also offers a number of enhancements. Here is a selection of the most important:
+
+**Enhancements**
+* Automatic `alsa` output device speed and format selection. The greatest bit depth and the lowest multiple of 44,100 frames per second settings are chosen automatically by default. Manual selection is still available.
+* Automatic interpolation selection. If the CPU is fast enough, the better-quality `soxr` interpolation method is chosen. Otherwise `basic` interpolation is used. Manual selection is still available.
+* A new `active` state. New hooks are provided to execute programs before entering and after leaving the `active` state which covers sequences of play sessions separated by short intervals. This simplifies amplifier switch-on and switch-off, for example.
+* DAC crackle minimisation. This new feature is intended to minimise pops and crackles caused in some Digital to Analog Converters (DACs) when they transition between active and idle or standby operation. The new `alsa`-only `disable_standby_mode` prevents the DAC entering the standby mode by keeping it active, either permanently or while Shairport Sync is in the active state. This feature is switched off by default.
+* A new backend to interface Shairport Sync to [Jack Audio](http://jackaudio.org), thanks to the work of [Jörn Nettingsmeier](https://github.com/nettings).
+* A new [MQTT](https://en.wikipedia.org/wiki/MQTT) client interface, thanks to the work of [Till Zimmermann](https://github.com/tillz).
+* Changes in logging -- now you should add `-u` to direct log entries to STDERR (typically the console) rather than the system log. For example, to get logs of verbosity 1 to appear on the console: `$ shairport-sync -v -u`.
+* The help menu now lists all `alsa` output devices found.
+* Better support for big-endian CPUs.
+* Shairport Sync accepts AirPlay streams containing CD-quality uncompressed PCM.
+
+**Bug Fixes**
+* Lots of bugs – too many to list here – have been fixed that significantly improve the stability of Shairport Sync. The full list is in the Release Notes.
+
+For more details of enhancements and bug fixes, please see the [Release Notes for 3.3](https://github.com/mikebrady/shairport-sync/releases/tag/3.3). Special thanks to [gibman](https://github.com/gibman).
+
Version 3.2.2
====
Please see the [Release Notes for 3.2](https://github.com/mikebrady/shairport-sync/releases/tag/3.2).
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index 0215c702..262bca6b 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -7,39 +7,29 @@ In this brief document will be listed some problems and some solutions, some pro
1. Before starting, ensure that your software is up-to-date.
2. Set the `interpolation` in the `general` section of the configuration file to `basic` as the `soxr` setting can cause lower-powered devices to bog down at critical times, e.g. see [this report](https://github.com/mikebrady/shairport-sync/issues/631#issuecomment-366305203).
-### WiFi and Microwaves
-Microwaves can interfere with WiFi -- see [here](http://sciabc.us/F6Gaa) for example.
-### WiFi adapter running in power-saving/low-power mode
+### WiFi adapter running in power-saving / low-power mode
+
**Check Throughput**
-You can learn how to check your Wi-Fi's throughput by following this [tutorial](https://thepi.io/how-to-use-your-raspberry-pi-to-monitor-broadband-speed/).
+
+ You can check WiFi throughput using, for example, https://thepi.io/how-to-use-your-raspberry-pi-to-monitor-broadband-speed/
**Problem**
+
Shairport Sync is installed and running, but sometimes it disappears from the network, and sometimes it suffers from long dropouts.
**Possible Cause**
-This can be caused by lots of things, however, the most prominent cause is that the Wi-Fi adapter may be set to run in a low-power/power-saving mode; which after a certain period of time(usually a minute or so), if the adapter is not busy, the system will hold back the active use of the adapter.
-This is an unintended scenario where Shairport requires the Wi-Fi adapter at all times to function properly. Hence you need to turn off the power saving/low-power mode.
+This can be caused by lots of things, but one of them is that the WiFi adapter may be set to run in a low-power or power-saving mode. If it's not busy, then after a while it goes into a low-power mode. This is bad as the device needs to be always connected to the network to provide the AirPlay service. You need to turn off power-saving mode. How you do this varies with platform and with WiFi adapter – internet search is your friend. Here, for instance, is the command for the C.H.I.P. from Next Thing Co, which has built in WiFi and Linux and has the `iw` command installed:
-How you do this varies with platform to platform or adapter to adapter and therefore internet search is your friend or some closed issues on this repo has some more knowledge for you as well.
-
-However, below are the instructions for the onboard Wi-Fi chip that comes with Raspberry Pi, another popular chip(Realtek RTL8188CUS), and, the chip 8192cu.
-
-Moreover, if you want an in-depth look at Raspberry Pi Wi-Fi adapters, [here](https://www.elinux.org/RPi_USB_Wi-Fi_Adapters) is a good primer.
-
-#### Onboard Wi-Fi Chip
-
-Here, for instance, is the command for the C.H.I.P. from Next Thing Co., which has built in WiFi and Linux and has the `iw` command installed:
```
-# iw dev wlan0 set power_save off
+iw dev wlan0 set power_save off
```
-
Here is the command sequence for a Raspberry Pi 3, which has built-in WiFi:
-```
-# iwconfig wlan0 power off
-```
+```
+sudo iwconfig wlan0 power off
+```
Alternatively, (also for the Raspberry Pi), add the following line:
```
wireless-power off
@@ -47,8 +37,9 @@ wireless-power off
to the file `/etc/network/interfaces`.
Here is another option, suggested by [davidhq](https://github.com/davidhq) in [#653](https://github.com/mikebrady/shairport-sync/issues/653#issuecomment-391100620):
+
```
-# nano /etc/network/if-up.d/off-power-manager
+$ sudo nano /etc/network/if-up.d/off-power-manager
```
Type:
@@ -56,37 +47,12 @@ Type:
#!/bin/sh
/sbin/iwconfig wlan0 power off
```
-
Then:
```
-# chmod +x /etc/network/if-up.d/off-power-manager
+sudo chmod +x /etc/network/if-up.d/off-power-manager
```
-#### Realtek RTL8188CUS
-
-The chip `Realtek RTL8188CUS` is a popular one used by many Wi-Fi adapters. To disable its power-saving/low-power mode:
-
-- `sudo vim /etc/modprobe.d/r8188eu.conf`,
-- paste the below content into the file,
- - `options r8188eu rtw_power_mgnt=0 rtw_enusbss=0`
-- `sudo reboot`
-
-**After the reboot**, the power-saving/low-power mode should be disabled. You can check it by the following command. In return, it should output `0`.
-
-`cat /sys/module/r8188eu/parameters/rtw_power_mgnt`
-
-#### 8192cu
-
-Another chip you may encounter is `8192cu`. The process is similar to `Realtek RTL8188CUS` – just a simple name change.
-
-- `sudo vim /etc/modprobe.d/8192cu.conf`,
-- paste the below content into the file,
- - `options 8192cu rtw_power_mgnt=0 rtw_enusbss=0`
-- `sudo reboot`
-
-**After the reboot**, the power-saving/low-power mode should be disabled. You can check it by the following command. It should return the value `0`.
-
-`cat /sys/module/8192cu/parameters/rtw_power_mgnt`
+There are some more details in some the closed issues on this repository.
### Faulty WiFi
For an example of what it can take to track down a bad WiFi situation – in this case, a faulty WiFi adapter – please look at [this report](https://github.com/mikebrady/shairport-sync/issues/689).
diff --git a/UPDATING.md b/UPDATING.md
index 8b681868..31b87526 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -37,7 +37,7 @@ Please review the release notes to see if any configuration settings have been c
$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd --sysconfdir=/etc
#OR
#The following is the standard configuration for a Linux that uses the alsa backend and the older System V initialisation system:
-$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemv --sysconfdir=/etc
+$ ./configure --with-libdaemon --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemv --sysconfdir=/etc
$ make
$ sudo make install
diff --git a/activity_monitor.c b/activity_monitor.c
new file mode 100644
index 00000000..c6966bcb
--- /dev/null
+++ b/activity_monitor.c
@@ -0,0 +1,275 @@
+/*
+ * Activity Monitor
+ *
+ * Contains code to run an activity flag and associated timer
+ * A pthread implements a simple state machine with three states,
+ * "idle", "active" and "timing out".
+ *
+ *
+ * This file is part of Shairport Sync.
+ * Copyright (c) Mike Brady 2019
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include
+#include
+#include
+#include
+
+#include "config.h"
+
+#include "activity_monitor.h"
+#include "common.h"
+#include "rtsp.h"
+
+#ifdef CONFIG_DBUS_INTERFACE
+#include "dbus-service.h"
+#endif
+
+
+enum am_state state;
+enum ps_state { ps_inactive, ps_active } player_state;
+
+int activity_monitor_running = 0;
+
+pthread_t activity_monitor_thread;
+pthread_mutex_t activity_monitor_mutex;
+pthread_cond_t activity_monitor_cv;
+
+void going_active(int block) {
+ // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" :
+ // "out");
+ if (config.cmd_active_start)
+ command_execute(config.cmd_active_start, "", block);
+#ifdef CONFIG_METADATA
+ debug(2, "abeg"); // active mode begin
+ send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
+#endif
+
+#ifdef CONFIG_DBUS_INTERFACE
+ if (dbus_service_is_running())
+ shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
+#endif
+
+ if (config.disable_standby_mode == disable_standby_auto) {
+#ifdef CONFIG_DBUS_INTERFACE
+ if (dbus_service_is_running())
+ shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
+ else
+ config.keep_dac_busy = 1;
+#else
+ config.keep_dac_busy = 1;
+#endif
+ }
+}
+
+void going_inactive(int block) {
+ // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" :
+ // "out");
+ if (config.cmd_active_stop)
+ command_execute(config.cmd_active_stop, "", block);
+#ifdef CONFIG_METADATA
+ debug(2, "aend"); // active mode end
+ send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
+#endif
+
+#ifdef CONFIG_DBUS_INTERFACE
+ if (dbus_service_is_running())
+ shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
+#endif
+
+ if (config.disable_standby_mode == disable_standby_auto) {
+#ifdef CONFIG_DBUS_INTERFACE
+ if (dbus_service_is_running())
+ shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
+ else
+ config.keep_dac_busy = 0;
+#else
+ config.keep_dac_busy = 0;
+#endif
+ }
+}
+
+void activity_monitor_signify_activity(int active) {
+ // this could be pthread_cancelled and they is likely to be cancellation points in the
+ // hooked-on procedures
+ pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 1);
+ player_state = active == 0 ? ps_inactive : ps_active;
+ // Now, although we could simply let the state machine in the activity monitor thread
+ // look after eveything, we will change state here in two situations:
+ // 1. If the state machine is am_inactive and the player is ps_active
+ // we will change the state to am_active and execute the going_active() function.
+ // 2. If the state machine is am_active and the player is ps_inactive and
+ // the activity_idle_timeout is 0, then we will change the state to am_inactive and
+ // execute the going_inactive() function.
+ //
+ // The reason for all this is that we might want to perform the attached scripts
+ // and wait for them to complete before continuing. If they were perfomed in the
+ // activity monitor thread, then we couldn't wait for them to complete.
+
+ // Thus, the only time the thread will execute a going_... function is when a non-zero
+ // timeout actually matures.
+
+ if ((state == am_inactive) && (player_state == ps_active)) {
+ going_active(
+ config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay
+ } else if ((state == am_active) && (player_state == ps_inactive) &&
+ (config.active_state_timeout == 0.0)) {
+ going_inactive(
+ config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay
+ }
+
+ pthread_cond_signal(&activity_monitor_cv);
+ pthread_cleanup_pop(1); // release the mutex
+}
+
+void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) {
+ debug(3, "activity_monitor: thread exit.");
+ pthread_cond_destroy(&activity_monitor_cv);
+ pthread_mutex_destroy(&activity_monitor_mutex);
+}
+
+void *activity_monitor_thread_code(void *arg) {
+ int rc = pthread_mutex_init(&activity_monitor_mutex, NULL);
+ if (rc)
+ die("activity_monitor: error %d initialising activity_monitor_mutex.", rc);
+
+// set the flowcontrol condition variable to wait on a monotonic clock
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ pthread_condattr_t attr;
+ pthread_condattr_init(&attr);
+ pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
+ rc = pthread_cond_init(&activity_monitor_cv, &attr);
+ pthread_condattr_destroy(&attr);
+
+#endif
+#ifdef COMPILE_FOR_OSX
+ rc = pthread_cond_init(&activity_monitor_cv, NULL);
+#endif
+ if (rc)
+ die("activity_monitor: error %d initialising activity_monitor_cv.");
+ pthread_cleanup_push(activity_thread_cleanup_handler, arg);
+
+ uint64_t sec;
+ uint64_t nsec;
+ struct timespec time_for_wait;
+
+ state = am_inactive;
+ player_state = ps_inactive;
+
+ pthread_mutex_lock(&activity_monitor_mutex);
+ do {
+ switch (state) {
+ case am_inactive:
+ // debug(1,"am_state: am_inactive");
+ while (player_state != ps_active)
+ pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
+ state = am_active;
+ // going_active(); // this is done in activity_monitor_signify_activity
+ break;
+ case am_active:
+ // debug(1,"am_state: am_active");
+ while (player_state != ps_inactive)
+ pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
+ if (config.active_state_timeout == 0.0) {
+ state = am_inactive;
+ // going_inactive(); // this is done in activity_monitor_signify_activity
+ } else {
+ state = am_timing_out;
+
+ uint64_t time_to_wait_for_wakeup_fp =
+ (uint64_t)(config.active_state_timeout * 1000000); // resolution of microseconds
+ time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32;
+ time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000;
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp;
+ sec = time_of_wakeup_fp >> 32;
+ nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ time_for_wait.tv_sec = sec;
+ time_for_wait.tv_nsec = nsec;
+#endif
+#ifdef COMPILE_FOR_OSX
+ sec = time_to_wait_for_wakeup_fp >> 32;
+ nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ time_for_wait.tv_sec = sec;
+ time_for_wait.tv_nsec = nsec;
+#endif
+ }
+ break;
+ case am_timing_out:
+ // debug(1,"am_state: am_timing_out");
+ rc = 0;
+ while ((player_state != ps_active) && (rc != ETIMEDOUT)) {
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex,
+ &time_for_wait); // this is a pthread cancellation point
+#endif
+#ifdef COMPILE_FOR_OSX
+ rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex,
+ &time_for_wait);
+#endif
+ }
+ if (player_state == ps_active)
+ state = am_active; // player has gone active -- do nothing, because it's still active
+ else if (rc == ETIMEDOUT) {
+ state = am_inactive;
+ pthread_mutex_unlock(&activity_monitor_mutex);
+ going_inactive(0); // don't wait for completion -- it makes no sense
+ pthread_mutex_lock(&activity_monitor_mutex);
+ } else {
+ // activity monitor was woken up in the state am_timing_out, but not by a timeout and player
+ // is not in ps_active state
+ debug(1,
+ "activity monitor was woken up in the state am_timing_out, but didn't change state");
+ }
+ break;
+ default:
+ debug(1, "activity monitor in an illegal state!");
+ state = am_inactive;
+ break;
+ }
+ } while (1);
+ pthread_mutex_unlock(&activity_monitor_mutex);
+ pthread_cleanup_pop(0); // should never happen
+ pthread_exit(NULL);
+}
+
+enum am_state activity_status() {
+ return (state);
+}
+
+void activity_monitor_start() {
+ // debug(1,"activity_monitor_start");
+ pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL);
+ activity_monitor_running = 1;
+}
+
+void activity_monitor_stop() {
+ if (activity_monitor_running) {
+ debug(3, "activity_monitor_stop start...");
+ pthread_cancel(activity_monitor_thread);
+ pthread_join(activity_monitor_thread, NULL);
+ debug(2, "activity_monitor_stop complete");
+ }
+}
diff --git a/activity_monitor.h b/activity_monitor.h
new file mode 100644
index 00000000..d9e1ffd6
--- /dev/null
+++ b/activity_monitor.h
@@ -0,0 +1,8 @@
+#pragma once
+
+enum am_state { am_inactive, am_active, am_timing_out };
+
+void activity_monitor_start();
+void activity_monitor_stop();
+void activity_monitor_signify_activity(int active); // 0 means inactive, non-zero means active
+enum am_state activity_status(); // true if non inactive; false if inactive
diff --git a/audio.c b/audio.c
index 058cbfe7..78686376 100644
--- a/audio.c
+++ b/audio.c
@@ -1,6 +1,7 @@
/*
* Audio driver handler. This file is part of Shairport.
* Copyright (c) James Laird 2013
+ * Modifications (c) Mike Brady 2014 -- 2018
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -30,6 +31,9 @@
#include
#include
+#ifdef CONFIG_JACK
+extern audio_output audio_jack;
+#endif
#ifdef CONFIG_SNDIO
extern audio_output audio_sndio;
#endif
@@ -65,6 +69,9 @@ static audio_output *outputs[] = {
#ifdef CONFIG_PA
&audio_pa,
#endif
+#ifdef CONFIG_JACK
+ &audio_jack,
+#endif
#ifdef CONFIG_AO
&audio_ao,
#endif
@@ -99,14 +106,18 @@ audio_output *audio_get_output(char *name) {
void audio_ls_outputs(void) {
audio_output **out;
- printf("Available audio outputs:\n");
+ printf("Available audio backends:\n");
for (out = outputs; *out; out++)
printf(" %s%s\n", (*out)->name, out == outputs ? " (default)" : "");
for (out = outputs; *out; out++) {
printf("\n");
- printf("Options for output %s:\n", (*out)->name);
- (*out)->help();
+ if ((*out)->help) {
+ printf("Settings and options for the audio backend \"%s\":\n", (*out)->name);
+ (*out)->help();
+ } else {
+ printf("There are no settings or options for the audio backend \"%s\".\n", (*out)->name);
+ }
}
}
@@ -146,6 +157,21 @@ void parse_general_audio_options(void) {
}
}
+ /* Get the minumum buffer size for fancy interpolation setting in seconds. */
+ if (config_lookup_float(config.cfg,
+ "general.audio_backend_buffer_interpolation_threshold_in_seconds",
+ &dvalue)) {
+ if ((dvalue < 0) || (dvalue > config.audio_backend_buffer_desired_length)) {
+ die("Invalid audio_backend_buffer_interpolation_threshold_in_seconds value: \"%f\". It "
+ "should be between 0 and "
+ "audio_backend_buffer_desired_length_in_seconds of %.3f, default is %.3f seconds",
+ dvalue, config.audio_backend_buffer_desired_length,
+ config.audio_backend_buffer_interpolation_threshold_in_seconds);
+ } else {
+ config.audio_backend_buffer_interpolation_threshold_in_seconds = dvalue;
+ }
+ }
+
/* Get the latency offset (deprecated). */
if (config_lookup_int(config.cfg, "general.audio_backend_latency_offset", &value)) {
if ((value < -66150) || (value > 66150)) {
@@ -164,9 +190,9 @@ void parse_general_audio_options(void) {
/* Get the latency offset in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds",
&dvalue)) {
- if ((dvalue < -1.0) || (dvalue > 1.5)) {
+ if ((dvalue < -1.75) || (dvalue > 1.75)) {
die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It "
- "should be between -1.0 and +1.5, default is 0 seconds",
+ "should be between -1.75 and +1.75, default is 0 seconds",
dvalue);
} else {
config.audio_backend_latency_offset = dvalue;
diff --git a/audio.h b/audio.h
index dc7eb873..5fc5f391 100644
--- a/audio.h
+++ b/audio.h
@@ -18,13 +18,19 @@ typedef struct {
int (*init)(int argc, char **argv);
// at end of program
void (*deinit)(void);
+
+ int (*prepare)(void); // looks and sets stuff in the config data structure
void (*start)(int sample_rate, int sample_format);
// block of samples
- void (*play)(void *buf, int samples);
+ int (*play)(void *buf, int samples);
void (*stop)(void);
+ // may be null if no implemented
+ int (*is_running)(
+ void); // if implemented, will return 0 if everything is okay, non-zero otherwise
+
// may be null if not implemented
void (*flush)(void);
@@ -33,6 +39,8 @@ typedef struct {
// will change dynamically, so keep watching it. Implemented in ALSA only.
// returns a negative error code if there's a problem
int (*delay)(long *the_delay); // snd_pcm_sframes_t is a signed long
+ int (*rate_info)(uint64_t *elapsed_time,
+ uint64_t *frames_played); // use this to get the true rate of the DAC
// may be NULL, in which case soft volume is applied
void (*volume)(double vol);
@@ -41,7 +49,8 @@ typedef struct {
void (*parameters)(audio_parameters *info);
// may be NULL, in which case software muting is used.
- void (*mute)(int do_mute);
+ // also, will return a 1 if it is actually using the mute facility, 0 otherwise
+ int (*mute)(int do_mute);
} audio_output;
diff --git a/audio_alsa.c b/audio_alsa.c
index e2a53ccd..cd692f1d 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -1,7 +1,7 @@
/*
* libalsa output driver. This file is part of Shairport.
* Copyright (c) Muffinman, Skaman 2013
- * Copyright (c) Mike Brady 2014 -- 2018
+ * Copyright (c) Mike Brady 2014 -- 2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -27,54 +27,103 @@
#define ALSA_PCM_NEW_HW_PARAMS_API
-#include "audio.h"
-#include "common.h"
#include
+#include
#include
#include
#include
#include
#include
+#include "config.h"
+
+#include "common.h"
+#include "activity_monitor.h"
+#include "audio.h"
+
+enum alsa_backend_mode {
+ abm_disconnected,
+ abm_connected,
+ abm_playing
+} alsa_backend_state; // under the control of alsa_mutex
+
+typedef struct {
+ snd_pcm_format_t alsa_code;
+ int frame_size;
+} format_record;
+
static void help(void);
static int init(int argc, char **argv);
static void deinit(void);
static void start(int i_sample_rate, int i_sample_format);
-static void play(void *buf, int samples);
+static int play(void *buf, int samples);
static void stop(void);
static void flush(void);
int delay(long *the_delay);
-void do_mute(int request);
+int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played);
+void *alsa_buffer_monitor_thread_code(void *arg);
static void volume(double vol);
void do_volume(double vol);
+int prepare(void);
static void parameters(audio_parameters *info);
-static void mute(int do_mute);
+int mute(int do_mute); // returns true if it actually is allowed to use the mute
static double set_volume;
-static int output_method_signalled = 0;
+static int output_method_signalled = 0; // for reporting whether it's using mmap or not
+int delay_type_notified = -1; // for controlling the reporting of whether the output device can do precison delays (e.g. alsa->pulsaudio virtual devices can't)
+int use_monotonic_clock = 0; // this value will be set when the hardware is initialised
audio_output audio_alsa = {
.name = "alsa",
.help = &help,
.init = &init,
.deinit = &deinit,
+ .prepare = &prepare,
.start = &start,
.stop = &stop,
+ .is_running = NULL,
.flush = &flush,
.delay = &delay,
.play = &play,
- .mute = NULL, // a function will be provided if it can, and is allowed to, do hardware mute
- .volume = NULL, // a function will be provided if it can do hardware volume
- .parameters = ¶meters};
+ .rate_info = &get_rate_information,
+ .mute = NULL, // a function will be provided if it can, and is allowed to,
+ // do hardware mute
+ .volume = NULL, // a function will be provided if it can do hardware volume
+ .parameters = NULL}; // a function will be provided if it can do hardware volume
static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t alsa_mixer_mutex = PTHREAD_MUTEX_INITIALIZER;
-static unsigned int desired_sample_rate;
-static enum sps_format_t sample_format;
+pthread_t alsa_buffer_monitor_thread;
-static snd_pcm_t *alsa_handle = NULL;
+// for deciding when to activate mute
+// there are two sources of requests to mute -- the backend itself, e.g. when it
+// is flushing
+// and the player, e.g. when volume goes down to -144, i.e. mute.
+
+// we may not be allowed to use hardware mute, so we must reflect that too.
+
+int mute_requested_externally = 0;
+int mute_requested_internally = 0;
+
+// for tracking how long the output device has stalled
+uint64_t stall_monitor_start_time; // zero if not initialised / not started /
+ // zeroed by flush
+long stall_monitor_frame_count; // set to delay at start of time, incremented by
+ // any writes
+uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's
+ // an error
+
+static snd_output_t *output = NULL;
+int frame_size; // in bytes for interleaved stereo
+
+int alsa_device_initialised; // boolean to ensure the initialisation is only
+ // done once
+
+snd_pcm_t *alsa_handle = NULL;
static snd_pcm_hw_params_t *alsa_params = NULL;
+static snd_pcm_sw_params_t *alsa_swparams = NULL;
static snd_ctl_t *ctl = NULL;
static snd_ctl_elem_id_t *elem_id = NULL;
static snd_mixer_t *alsa_mix_handle = NULL;
@@ -90,19 +139,35 @@ static int alsa_mix_index = 0;
static int hardware_mixer = 0;
static int has_softvol = 0;
-static int volume_set_request = 0; // set when an external request is made to set the volume.
-int mute_request_pending = 0; // set when an external request is made to mute or unmute.
-int overriding_mute_state_requested = 0; // 1 = mute; 0 = unmute requested
-int mixer_volume_setting_gives_mute =
- 0; // set when it is discovered that particular mixer volume setting causes a mute.
-long alsa_mix_mute; // setting the volume to this value mutes output, if
- // mixer_volume_setting_gives_mute is true
+int64_t dither_random_number_store = 0;
+
+static int volume_set_request = 0; // set when an external request is made to set the volume.
+
+int mixer_volume_setting_gives_mute = 0; // set when it is discovered that
+ // particular mixer volume setting
+ // causes a mute.
+long alsa_mix_mute; // setting the volume to this value mutes output, if
+ // mixer_volume_setting_gives_mute is true
int volume_based_mute_is_active =
0; // set when muting is being done by a setting the volume to a magic value
-static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *,
+// use this to allow the use of snd_pcm_writei or snd_pcm_mmap_writei
+snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *,
snd_pcm_uframes_t) = snd_pcm_writei;
+
+int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay, enum yndk_type *using_update_timestamps);
+int standard_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay, enum yndk_type *using_update_timestamps);
+
+// use this to allow the use of standard or precision delay calculations, with standard the, uh, standard.
+int (*delay_and_status)(snd_pcm_state_t *state, snd_pcm_sframes_t *delay, enum yndk_type *using_update_timestamps) = standard_delay_and_status;
+
+int precision_delay_available() {
+ // this is very crude -- if the device is a hardware device, then it's assumed the delay is precise
+ const char *output_device_name = snd_pcm_name(alsa_handle);
+ return (strstr(output_device_name,"hw:") == output_device_name);
+}
+
// static int play_number;
// static int64_t accumulated_delay, accumulated_da_delay;
int alsa_characteristics_already_listed = 0;
@@ -110,17 +175,31 @@ int alsa_characteristics_already_listed = 0;
static snd_pcm_uframes_t period_size_requested, buffer_size_requested;
static int set_period_size_request, set_buffer_size_request;
+static uint64_t measurement_start_time;
+static uint64_t frames_played_at_measurement_start_time;
+
+static uint64_t measurement_time;
+static uint64_t frames_played_at_measurement_time;
+
+volatile uint64_t most_recent_write_time;
+
+static uint64_t frames_sent_for_playing;
+static uint64_t frame_index;
+static int measurement_data_is_valid;
+
static void help(void) {
- printf(" -d output-device set the output device [default*|...]\n"
- " -m mixer-device set the mixer device ['output-device'*|...]\n"
- " -c mixer-control set the mixer control [Master*|...]\n"
- " -i mixer-index set the mixer index [0*|...]\n"
- " *) default option\n");
+ printf(" -d output-device set the output device, default is \"default\".\n"
+ " -c mixer-control set the mixer control name, default is to use no mixer.\n"
+ " -m mixer-device set the mixer device, default is the output device.\n"
+ " -i mixer-index set the mixer index, default is 0.\n");
+ system("if [ -d /proc/asound ] ; then echo \" hardware output devices:\" ; ls -al /proc/asound/ 2>/dev/null | grep '\\->' | tr -s ' ' | cut -d ' ' -f 9 | while read line; do echo \" \\\"hw:$line\\\"\" ; done ; fi");
}
void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; }
+// assuming pthread cancellation is disabled
int open_mixer() {
+ int response = 0;
if (hardware_mixer) {
debug(3, "Open Mixer");
int ret = 0;
@@ -128,27 +207,41 @@ int open_mixer() {
snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl);
- if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0)
- die("Failed to open mixer");
- debug(3, "Mixer device name is \"%s\".", alsa_mix_dev);
- if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0)
- die("Failed to attach mixer");
- if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0)
- die("Failed to register mixer element");
-
- ret = snd_mixer_load(alsa_mix_handle);
- if (ret < 0)
- die("Failed to load mixer element");
- debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl);
- alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
- if (!alsa_mix_elem)
- die("Failed to find mixer element");
- return 1;
- } else {
- return 0;
+ if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) {
+ debug(1, "Failed to open mixer");
+ response = -1;
+ } else {
+ debug(3, "Mixer device name is \"%s\".", alsa_mix_dev);
+ if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) {
+ debug(1, "Failed to attach mixer");
+ response = -2;
+ } else {
+ if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) {
+ debug(1, "Failed to register mixer element");
+ response = -3;
+ } else {
+ ret = snd_mixer_load(alsa_mix_handle);
+ if (ret < 0) {
+ debug(1, "Failed to load mixer element");
+ response = -4;
+ } else {
+ debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl);
+ alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
+ if (!alsa_mix_elem) {
+ warn("failed to find mixer control \"%s\".", alsa_mix_ctrl);
+ response = -5;
+ } else {
+ response = 1; // we found a hardware mixer and successfully opened it
+ }
+ }
+ }
+ }
+ }
}
+ return response;
}
+// assuming pthread cancellation is disabled
void close_mixer() {
if (alsa_mix_handle) {
snd_mixer_close(alsa_mix_handle);
@@ -156,6 +249,7 @@ void close_mixer() {
}
}
+// assuming pthread cancellation is disabled
void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double vol) {
if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 0) != 0) {
debug(1, "Can't set playback volume accurately to %f dB.", vol);
@@ -165,353 +259,136 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v
}
}
-static int init(int argc, char **argv) {
- debug_mutex_lock(&alsa_mutex, 1000, 1);
- // debug(2,"audio_alsa init called.");
- const char *str;
- int value;
- // double dvalue;
+void actual_close_alsa_device() {
+ debug(1, "actual close");
+ if (alsa_handle) {
+ int derr;
+ if ((derr = snd_pcm_hw_free(alsa_handle)))
+ debug(1, "Error %d (\"%s\") freeing the output device hardware while "
+ "closing it.",
+ derr, snd_strerror(derr));
- // set up default values first
- set_period_size_request = 0;
- set_buffer_size_request = 0;
- config.alsa_use_hardware_mute = 0; // don't use it by default
-
- config.audio_backend_latency_offset = 0;
- config.audio_backend_buffer_desired_length = 0.15;
-
- // get settings from settings file first, allow them to be overridden by
- // command line options
-
- // do the "general" audio options. Note, these options are in the "general" stanza!
- parse_general_audio_options();
-
- if (config.cfg != NULL) {
-
- /* Get the Output Device Name. */
- if (config_lookup_string(config.cfg, "alsa.output_device", &str)) {
- alsa_out_dev = (char *)str;
- }
-
- /* Get the Mixer Type setting. */
-
- if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) {
- inform("The alsa mixer_type setting is deprecated and has been ignored. "
- "FYI, using the \"mixer_control_name\" setting automatically "
- "chooses a hardware mixer.");
- }
-
- /* Get the Mixer Device Name. */
- if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) {
- alsa_mix_dev = (char *)str;
- }
-
- /* Get the Mixer Control Name. */
- if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) {
- alsa_mix_ctrl = (char *)str;
- hardware_mixer = 1;
- }
-
- /* Get the disable_synchronization setting. */
- if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) {
- if (strcasecmp(str, "no") == 0)
- config.no_sync = 0;
- else if (strcasecmp(str, "yes") == 0)
- config.no_sync = 1;
- else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\"");
- }
- }
-
- /* Get the mute_using_playback_switch setting. */
- if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) {
- inform("The alsa \"mute_using_playback_switch\" setting is deprecated. "
- "Please use the \"use_hardware_mute_if_available\" setting instead.");
- if (strcasecmp(str, "no") == 0)
- config.alsa_use_hardware_mute = 0;
- else if (strcasecmp(str, "yes") == 0)
- config.alsa_use_hardware_mute = 1;
- else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid mute_using_playback_switch option choice \"%s\". It should be \"yes\" or "
- "\"no\"");
- }
- }
-
- /* Get the use_hardware_mute_if_available setting. */
- if (config_lookup_string(config.cfg, "alsa.use_hardware_mute_if_available", &str)) {
- if (strcasecmp(str, "no") == 0)
- config.alsa_use_hardware_mute = 0;
- else if (strcasecmp(str, "yes") == 0)
- config.alsa_use_hardware_mute = 1;
- else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid use_hardware_mute_if_available option choice \"%s\". It should be \"yes\" or "
- "\"no\"");
- }
- }
-
- /* Get the output format, using the same names as aplay does*/
- if (config_lookup_string(config.cfg, "alsa.output_format", &str)) {
- if (strcasecmp(str, "S16") == 0)
- config.output_format = SPS_FORMAT_S16;
- else if (strcasecmp(str, "S24") == 0)
- config.output_format = SPS_FORMAT_S24;
- else if (strcasecmp(str, "S24_3LE") == 0)
- config.output_format = SPS_FORMAT_S24_3LE;
- else if (strcasecmp(str, "S24_3BE") == 0)
- config.output_format = SPS_FORMAT_S24_3BE;
- else if (strcasecmp(str, "S32") == 0)
- config.output_format = SPS_FORMAT_S32;
- else if (strcasecmp(str, "U8") == 0)
- config.output_format = SPS_FORMAT_U8;
- else if (strcasecmp(str, "S8") == 0)
- config.output_format = SPS_FORMAT_S8;
- else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", "
- "\"S24_3LE\", \"S24_3BE\" or "
- "\"S32\"",
- str);
- }
- }
-
- /* Get the output rate, which must be a multiple of 44,100*/
- if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) {
- debug(1, "alsa output rate is %d frames per second", value);
- switch (value) {
- case 44100:
- case 88200:
- case 176400:
- case 352800:
- config.output_rate = value;
- break;
- default:
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800", value);
- }
- }
-
- /* Get the use_mmap_if_available setting. */
- if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) {
- if (strcasecmp(str, "no") == 0)
- config.no_mmap = 1;
- else if (strcasecmp(str, "yes") == 0)
- config.no_mmap = 0;
- else {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\"");
- }
- }
- /* Get the optional period size value */
- if (config_lookup_int(config.cfg, "alsa.period_size", &value)) {
- set_period_size_request = 1;
- debug(1, "Value read for period size is %d.", value);
- if (value < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid alsa period size setting \"%d\". It "
- "must be greater than 0.",
- value);
- } else {
- period_size_requested = value;
- }
- }
-
- /* Get the optional buffer size value */
- if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) {
- set_buffer_size_request = 1;
- debug(1, "Value read for buffer size is %d.", value);
- if (value < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid alsa buffer size setting \"%d\". It "
- "must be greater than 0.",
- value);
- } else {
- buffer_size_requested = value;
- }
- }
+ if ((derr = snd_pcm_close(alsa_handle)))
+ debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr));
+ alsa_handle = NULL;
}
-
- optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
- argv--; // so we shift the arguments to satisfy getopt()
- argc++;
- // some platforms apparently require optreset = 1; - which?
- int opt;
- while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) {
- switch (opt) {
- case 'd':
- alsa_out_dev = optarg;
- break;
-
- case 't':
- inform("The alsa backend -t option is deprecated and has been ignored. "
- "FYI, using the -c option automatically chooses a hardware "
- "mixer.");
- break;
-
- case 'm':
- alsa_mix_dev = optarg;
- break;
- case 'c':
- alsa_mix_ctrl = optarg;
- hardware_mixer = 1;
- break;
- case 'i':
- alsa_mix_index = strtol(optarg, NULL, 10);
- break;
- default:
- help();
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid audio option -%c specified", opt);
- }
- }
-
- if (optind < argc) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Invalid audio argument: %s", argv[optind]);
- }
-
- debug(1, "alsa output device name is \"%s\".", alsa_out_dev);
-
- if (hardware_mixer) {
-
- if (alsa_mix_dev == NULL)
- alsa_mix_dev = alsa_out_dev;
-
- // Open mixer
-
- open_mixer();
-
- if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
- 0)
- debug(1, "Can't read mixer's [linear] min and max volumes.");
- else {
- if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) ==
- 0) {
-
- audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
- // For instance, the Raspberry Pi does this
- debug(1, "Lowest dB value is a mute");
- mixer_volume_setting_gives_mute = 1;
- alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be necessary -- it's always
- // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
- // debug(1, "Try minimum volume + 1 as lowest true attenuation value");
- if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
- &alsa_mix_mindb) != 0)
- debug(1, "Can't get dB value corresponding to a minimum volume + 1.");
- }
- debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
- (1.0 * alsa_mix_maxdb) / 100.0);
- } else {
- // use the linear scale and do the db conversion ourselves
- debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have "
- "a dB volume scale.",
- alsa_mix_ctrl);
-
- if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Cannot open control \"%s\"", alsa_mix_dev);
- }
- if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Cannot allocate memory for control \"%s\"", alsa_mix_dev);
- }
- snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
- snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
-
- if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
- debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl,
- (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
- has_softvol = 1;
- audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- } else {
- debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl);
- }
-
- /*
- debug(1, "Min and max volumes are %d and
- %d.",alsa_mix_minv,alsa_mix_maxv);
- alsa_mix_maxdb = 0;
- if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
- alsa_mix_mindb =
- -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
- else if (alsa_mix_maxv!=0)
- alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
- audio_alsa.volume = &linear_volume; // insert the linear volume function
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- debug(1,"Max and min dB calculated are %d and
- %d.",alsa_mix_maxdb,alsa_mix_mindb);
- */
- }
- }
- if (((config.alsa_use_hardware_mute == 1) &&
- (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
- mixer_volume_setting_gives_mute) {
- audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff
- // debug(1, "Has mixer and mute ability we will use.");
- } else {
- // debug(1, "Has mixer but not using hardware mute.");
- }
- close_mixer();
- } else {
- // debug(1, "Has no mixer and thus no hardware mute.");
- }
-
- alsa_mix_handle = NULL;
- debug_mutex_unlock(&alsa_mutex, 3);
- return 0;
}
-static void deinit(void) {
- // debug(2,"audio_alsa deinit called.");
- stop();
-}
+// This array is a sequence of the output rates to be tried if automatic speed selection is requested.
+// There is no benefit to upconverting the frame rate, other than for compatibility.
+// The lowest rate that the DAC is capable of is chosen.
-int open_alsa_device(void) {
+unsigned int auto_speed_output_rates[] = {
+ 44100,
+ 88200,
+ 176400,
+ 352800,
+};
+
+// This array is of all the formats known to Shairport Sync, in order of the SPS_FORMAT definitions, with their equivalent alsa codes and their frame sizes.
+// If just one format is requested, then its entry is searched for in the array and checked on the device
+// If auto format is requested, then each entry in turn is tried until a working format is found.
+// So, it should be in the search order.
+
+ format_record fr[] = {
+ {SND_PCM_FORMAT_UNKNOWN,0}, // unknown
+ {SND_PCM_FORMAT_S8,2},
+ {SND_PCM_FORMAT_U8,2},
+ {SND_PCM_FORMAT_S16,4},
+ {SND_PCM_FORMAT_S16_LE,4},
+ {SND_PCM_FORMAT_S16_BE,4},
+ {SND_PCM_FORMAT_S24,4},
+ {SND_PCM_FORMAT_S24_LE,8},
+ {SND_PCM_FORMAT_S24_BE,8},
+ {SND_PCM_FORMAT_S24_3LE,6},
+ {SND_PCM_FORMAT_S24_3BE,6},
+ {SND_PCM_FORMAT_S32,8},
+ {SND_PCM_FORMAT_S32_LE,8},
+ {SND_PCM_FORMAT_S32_BE,8},
+ {SND_PCM_FORMAT_UNKNOWN,0}, // auto
+ {SND_PCM_FORMAT_UNKNOWN,0}, // illegal
+ };
+
+ // This array is the sequence of formats to be tried if automatic selection of the format is requested.
+ // Ideally, audio should pass through Shairport Sync unaltered, apart from occasional interpolation.
+ // If the user chooses a hardware mixer, then audio could go straight through, unaltered, as signed 16 bit stereo.
+ // However, the user might, at any point, select an option that requires modification, such as stereo to mono mixing,
+ // additional volume attenuation, convolution, and so on. For this reason,
+ // we look for the greatest depth the DAC is capable of, since upconverting it is completely lossless.
+ // If audio processing is required, then the dither that must be added will
+ // be added at the lowest possible level.
+ // Hence, selecting the greatest bit depth is always either beneficial or neutral.
+
+ enum sps_format_t auto_format_check_sequence[] = {
+ SPS_FORMAT_S32,
+ SPS_FORMAT_S32_LE,
+ SPS_FORMAT_S32_BE,
+ SPS_FORMAT_S24,
+ SPS_FORMAT_S24_LE,
+ SPS_FORMAT_S24_BE,
+ SPS_FORMAT_S24_3LE,
+ SPS_FORMAT_S24_3BE,
+ SPS_FORMAT_S16,
+ SPS_FORMAT_S16_LE,
+ SPS_FORMAT_S16_BE,
+ SPS_FORMAT_S8,
+ SPS_FORMAT_U8,
+ };
+
+// assuming pthread cancellation is disabled
+// if do_auto_setting is true and auto format or auto speed has been requested,
+// select the settings as appropriate and store them
+int actual_open_alsa_device(int do_auto_setup) {
// the alsa mutex is already acquired when this is called
-
const snd_pcm_uframes_t minimal_buffer_headroom =
352 * 2; // we accept this much headroom in the hardware buffer, but we'll
// accept less
/*
const snd_pcm_uframes_t requested_buffer_headroom =
minimal_buffer_headroom + 2048; // we ask for this much headroom in the
- // hardware buffer, but we'll accept less
+ // hardware buffer, but we'll accept
+ less
*/
int ret, dir = 0;
- unsigned int my_sample_rate = desired_sample_rate;
+ unsigned int actual_sample_rate; // this will be given the rate requested and will be given the actual rate
// snd_pcm_uframes_t frames = 441 * 10;
snd_pcm_uframes_t actual_buffer_length;
snd_pcm_access_t access;
- // ensure no calls are made to the alsa device enquiring about the buffer length if
+ // ensure no calls are made to the alsa device enquiring about the buffer
+ // length if
// synchronisation is disabled.
if (config.no_sync != 0)
audio_alsa.delay = NULL;
- // ensure no calls are made to the alsa device enquiring about the buffer length if
+ // ensure no calls are made to the alsa device enquiring about the buffer
+ // length if
// synchronisation is disabled.
if (config.no_sync != 0)
audio_alsa.delay = NULL;
ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0);
- if (ret < 0)
- return (ret);
+ if (ret < 0) {
+ if (ret == -ENOENT) {
+ warn("the alsa output_device \"%s\" can not be found.", alsa_out_dev);
+ } else {
+ char errorstring[1024];
+ strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
+ warn("alsa: error %d (\"%s\") opening alsa device \"%s\".", ret, (char *)errorstring, alsa_out_dev);
+ }
+ return ret;
+ }
snd_pcm_hw_params_alloca(&alsa_params);
+ snd_pcm_sw_params_alloca(&alsa_swparams);
ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- ;
die("audio_alsa: Broken configuration for device \"%s\": no configurations "
- "available",
- alsa_out_dev);
+ "available",
+ alsa_out_dev);
+ return ret;
}
if ((config.no_mmap == 0) &&
@@ -534,70 +411,118 @@ int open_alsa_device(void) {
ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, access);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev,
- snd_strerror(ret));
- }
- snd_pcm_format_t sf;
- switch (sample_format) {
- case SPS_FORMAT_S8:
- sf = SND_PCM_FORMAT_S8;
- break;
- case SPS_FORMAT_U8:
- sf = SND_PCM_FORMAT_U8;
- break;
- case SPS_FORMAT_S16:
- sf = SND_PCM_FORMAT_S16;
- break;
- case SPS_FORMAT_S24:
- sf = SND_PCM_FORMAT_S24;
- break;
- case SPS_FORMAT_S24_3LE:
- sf = SND_PCM_FORMAT_S24_3LE;
- break;
- case SPS_FORMAT_S24_3BE:
- sf = SND_PCM_FORMAT_S24_3BE;
- break;
- case SPS_FORMAT_S32:
- sf = SND_PCM_FORMAT_S32;
- break;
- default:
- debug_mutex_unlock(&alsa_mutex, 3);
- sf = SND_PCM_FORMAT_S16; // this is just to quieten a compiler warning
- die("Unsupported output format at audio_alsa.c");
- }
- ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf);
- if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Sample format %d not available for device \"%s\": %s", sample_format,
- alsa_out_dev, snd_strerror(ret));
+ warn("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev,
+ snd_strerror(ret));
+ return ret;
}
+
ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev,
- snd_strerror(ret));
+ warn("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev,
+ snd_strerror(ret));
+ return ret;
}
- ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir);
- if (ret < 0) {
- die("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate,
- snd_strerror(ret));
+ snd_pcm_format_t sf;
+
+ if ((do_auto_setup == 0) || (config.output_format_auto_requested == 0)) { // no auto format
+ if ((config.output_format > SPS_FORMAT_UNKNOWN) && (config.output_format < SPS_FORMAT_AUTO)) {
+ sf = fr[config.output_format].alsa_code;
+ frame_size = fr[config.output_format].frame_size;
+ } else {
+ warn("alsa: unexpected output format %d. Set to S16_LE.",config.output_format);
+ config.output_format = SPS_FORMAT_S16_LE;
+ sf = fr[config.output_format].alsa_code;
+ frame_size = fr[config.output_format].frame_size;
+ }
+ ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf);
+ if (ret < 0) {
+ warn("audio_alsa: Alsa sample format %d not available for device \"%s\": %s", sf,
+ alsa_out_dev, snd_strerror(ret));
+ return ret;
+ }
+ } else { // auto format
+ int number_of_formats_to_try;
+ enum sps_format_t *formats;
+ formats = auto_format_check_sequence;
+ number_of_formats_to_try = sizeof(auto_format_check_sequence)/sizeof(sps_format_t);
+ int i = 0;
+ int format_found = 0;
+ enum sps_format_t trial_format = SPS_FORMAT_UNKNOWN;
+ while ((i < number_of_formats_to_try) && (format_found == 0)) {
+ trial_format = formats[i];
+ sf = fr[trial_format].alsa_code;
+ frame_size = fr[trial_format].frame_size;
+ ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf);
+ if (ret == 0)
+ format_found = 1;
+ else
+ i++;
+ }
+ if (ret == 0) {
+ config.output_format = trial_format;
+ debug(1,"alsa: output format chosen is \"%s\".",sps_format_description_string(config.output_format));
+ } else {
+ warn("audio_alsa: Could not automatically set the output format for device \"%s\": %s",
+ alsa_out_dev, snd_strerror(ret));
+ return ret;
+ }
}
-
+
+ if ((do_auto_setup == 0) || (config.output_rate_auto_requested == 0)) { // no auto format
+ actual_sample_rate = config.output_rate; // this is the requested rate -- it'll be changed to the actual rate
+ ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &actual_sample_rate, &dir);
+ if (ret < 0) {
+ warn("audio_alsa: Rate %iHz not available for playback: %s", config.output_rate,
+ snd_strerror(ret));
+ return ret;
+ }
+ } else {
+ int number_of_speeds_to_try;
+ unsigned int *speeds;
+
+ speeds = auto_speed_output_rates;
+ number_of_speeds_to_try = sizeof(auto_speed_output_rates)/sizeof(int);
+
+ int i = 0;
+ int speed_found = 0;
+
+ while ((i < number_of_speeds_to_try) && (speed_found == 0)) {
+ actual_sample_rate = speeds[i];
+ ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &actual_sample_rate, &dir);
+ if (ret == 0) {
+ speed_found = 1;
+ if (actual_sample_rate != speeds[i])
+ warn("Speed requested: %d. Speed available: %d.",speeds[i],actual_sample_rate);
+ } else {
+ i++;
+ }
+ }
+ if (ret == 0) {
+ config.output_rate = actual_sample_rate;
+ debug(1,"alsa: output speed chosen is %d.",config.output_rate);
+ } else {
+ warn("audio_alsa: Could not automatically set the output rate for device \"%s\": %s",
+ alsa_out_dev, snd_strerror(ret));
+ return ret;
+ }
+ }
+
if (set_period_size_request != 0) {
debug(1, "Attempting to set the period size");
ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested,
&dir);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: cannot set period size of %lu: %s", period_size_requested,
- snd_strerror(ret));
+ warn("audio_alsa: cannot set period size of %lu: %s", period_size_requested,
+ snd_strerror(ret));
+ return ret;
+ } else {
snd_pcm_uframes_t actual_period_size;
snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir);
if (actual_period_size != period_size_requested)
- inform("Actual period size set to a different value than requested. Requested: %lu, actual "
+ inform("Actual period size set to a different value than requested. "
+ "Requested: %lu, actual "
"setting: %lu",
period_size_requested, actual_period_size);
}
@@ -607,35 +532,91 @@ int open_alsa_device(void) {
debug(1, "Attempting to set the buffer size to %lu", buffer_size_requested);
ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested,
- snd_strerror(ret));
+ warn("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested,
+ snd_strerror(ret));
+ return ret;
+ } else {
+ snd_pcm_uframes_t actual_buffer_size;
+ snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
+ if (actual_buffer_size != buffer_size_requested)
+ inform("Actual period size set to a different value than requested. "
+ "Requested: %lu, actual "
+ "setting: %lu",
+ buffer_size_requested, actual_buffer_size);
}
- snd_pcm_uframes_t actual_buffer_size;
- snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
- if (actual_buffer_size != buffer_size_requested)
- inform("Actual period size set to a different value than requested. Requested: %lu, actual "
- "setting: %lu",
- buffer_size_requested, actual_buffer_size);
}
ret = snd_pcm_hw_params(alsa_handle, alsa_params);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev,
- snd_strerror(ret));
+ warn("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev,
+ snd_strerror(ret));
+ return ret;
}
- if (my_sample_rate != desired_sample_rate) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("Can't set the D/A converter to %d.", desired_sample_rate);
+ // check parameters after attempting to set them
+
+ if (set_period_size_request != 0) {
+ snd_pcm_uframes_t actual_period_size;
+ snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir);
+ if (actual_period_size != period_size_requested)
+ inform("Actual period size set to a different value than requested. "
+ "Requested: %lu, actual "
+ "setting: %lu",
+ period_size_requested, actual_period_size);
}
+ if (set_buffer_size_request != 0) {
+ snd_pcm_uframes_t actual_buffer_size;
+ snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
+ if (actual_buffer_size != buffer_size_requested)
+ inform("Actual period size set to a different value than requested. "
+ "Requested: %lu, actual "
+ "setting: %lu",
+ buffer_size_requested, actual_buffer_size);
+ }
+
+ if (actual_sample_rate != config.output_rate) {
+ warn("Can't set the D/A converter to sample rate %d.", config.output_rate);
+ return -EINVAL;
+ }
+
+ use_monotonic_clock = snd_pcm_hw_params_is_monotonic(alsa_params);
+
ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length);
if (ret < 0) {
- debug_mutex_unlock(&alsa_mutex, 3);
- die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev,
- snd_strerror(ret));
+ warn("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev,
+ snd_strerror(ret));
+ return ret;
+ }
+
+ ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams);
+ if (ret < 0) {
+ warn("audio_alsa: Unable to get current sw parameters for device \"%s\": "
+ "%s.",
+ alsa_out_dev, snd_strerror(ret));
+ return ret;
+ }
+
+ ret = snd_pcm_sw_params_set_tstamp_mode(alsa_handle, alsa_swparams, SND_PCM_TSTAMP_ENABLE);
+ if (ret < 0) {
+ warn("audio_alsa: Can't enable timestamp mode of device: \"%s\": %s.", alsa_out_dev,
+ snd_strerror(ret));
+ return ret;
+ }
+
+ /* write the sw parameters */
+ ret = snd_pcm_sw_params(alsa_handle, alsa_swparams);
+ if (ret < 0) {
+ warn("audio_alsa: Unable to set software parameters of device: \"%s\": %s.", alsa_out_dev,
+ snd_strerror(ret));
+ return ret;
+ }
+
+ ret = snd_pcm_prepare(alsa_handle);
+ if (ret < 0) {
+ warn("audio_alsa: Unable to prepare the device: \"%s\": %s.", alsa_out_dev,
+ snd_strerror(ret));
+ return ret;
}
if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) {
@@ -661,10 +642,21 @@ int open_alsa_device(void) {
buffer_size);
}
*/
- debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend buffer "
+ debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend "
+ "buffer "
"length (%ld) you have chosen.",
actual_buffer_length, config.audio_backend_buffer_desired_length);
}
+
+
+ if (config.use_precision_timing == YNA_YES)
+ delay_and_status = precision_delay_and_status;
+ else if (config.use_precision_timing == YNA_AUTO) {
+ if (precision_delay_available()) {
+ delay_and_status = precision_delay_and_status;
+ debug(2,"alsa: precision timing selected for \"auto\" mode");
+ }
+ }
if (alsa_characteristics_already_listed == 0) {
alsa_characteristics_already_listed = 1;
@@ -681,14 +673,17 @@ int open_alsa_device(void) {
debug(log_level, "PCM handle name = '%s'", snd_pcm_name(alsa_handle));
- // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
- // if (ret < 0) {
- // die("audio_alsa: Cannpot get configuration for device \"%s\": no
+ // ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
+ // if (ret < 0) {
+ // die("audio_alsa: Cannpot get configuration for
+ // device
+ //\"%s\":
+ // no
// configurations
//"
- // "available",
- // alsa_out_dev);
- // }
+ // "available",
+ // alsa_out_dev);
+ // }
debug(log_level, "alsa device parameters:");
@@ -793,144 +788,933 @@ int open_alsa_device(void) {
break;
}
}
-
- return (0);
+ return 0;
}
-static void start(int i_sample_rate, int i_sample_format) {
- // debug(2,"audio_alsa start called.");
- if (i_sample_rate == 0)
- desired_sample_rate = 44100; // default
- else
- desired_sample_rate = i_sample_rate; // must be a variable
+int open_alsa_device(int do_auto_setup) {
+ int result;
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ result = actual_open_alsa_device(do_auto_setup);
+ pthread_setcancelstate(oldState, NULL);
+ return result;
+}
- if (i_sample_format == 0)
- sample_format = SPS_FORMAT_S16; // default
- else
- sample_format = i_sample_format;
+int do_alsa_device_init_if_needed() {
+ int response = 0;
+ // do any alsa device initialisation (general case) if needed
+ // at present, this is only needed if a hardware mixer is being used
+ // if there's a hardware mixer, it needs to be initialised before first use
+ if (alsa_device_initialised == 0) {
+ alsa_device_initialised = 1;
+ if (hardware_mixer) {
+ debug(2, "alsa: hardware mixer init");
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+
+ if (alsa_mix_dev == NULL)
+ alsa_mix_dev = alsa_out_dev;
+
+ // Now, start trying to initialise the alsa device with the settings
+ // obtained
+ pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
+ if (open_mixer() == 1) {
+ if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv,
+ &alsa_mix_maxv) < 0)
+ debug(1, "Can't read mixer's [linear] min and max volumes.");
+ else {
+ if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
+ &alsa_mix_maxdb) == 0) {
+
+ audio_alsa.volume = &volume; // insert the volume function now we
+ // know it can do dB stuff
+ audio_alsa.parameters = ¶meters; // likewise the parameters stuff
+ if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
+ // For instance, the Raspberry Pi does this
+ debug(1, "Lowest dB value is a mute");
+ mixer_volume_setting_gives_mute = 1;
+ alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be
+ // necessary -- it's
+ // always
+ // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
+ // debug(1, "Try minimum volume + 1 as lowest true attenuation
+ // value");
+ if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
+ &alsa_mix_mindb) != 0)
+ debug(1, "Can't get dB value corresponding to a minimum volume "
+ "+ 1.");
+ }
+ debug(3, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
+ (1.0 * alsa_mix_maxdb) / 100.0);
+ } else {
+ // use the linear scale and do the db conversion ourselves
+ warn("The hardware mixer specified -- \"%s\" -- does not have "
+ "a dB volume scale.",
+ alsa_mix_ctrl);
+
+ if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
+ warn("Cannot open control \"%s\"", alsa_mix_dev);
+ response = -1;
+ }
+ if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
+ debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
+ elem_id = NULL;
+ response = -2;
+ } else {
+ snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
+ snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
+
+ if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
+ debug(1, "alsa: hardware mixer \"%s\" selected, with dB volume "
+ "from %f to %f.",
+ alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0,
+ (1.0 * alsa_mix_maxdb) / 100.0);
+ has_softvol = 1;
+ audio_alsa.volume = &volume; // insert the volume function now
+ // we know it can do dB stuff
+ audio_alsa.parameters = ¶meters; // likewise the parameters stuff
+ } else {
+ debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl);
+ }
+ }
+ /*
+ debug(1, "Min and max volumes are %d and
+ %d.",alsa_mix_minv,alsa_mix_maxv);
+ alsa_mix_maxdb = 0;
+ if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
+ alsa_mix_mindb =
+ -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
+ else if (alsa_mix_maxv!=0)
+ alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
+ audio_alsa.volume = &linear_volume; // insert the linear volume
+ function
+ audio_alsa.parameters = ¶meters; // likewise the parameters
+ stuff
+ debug(1,"Max and min dB calculated are %d and
+ %d.",alsa_mix_maxdb,alsa_mix_mindb);
+ */
+ }
+ }
+ if (((config.alsa_use_hardware_mute == 1) &&
+ (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
+ mixer_volume_setting_gives_mute) {
+ audio_alsa.mute = &mute; // insert the mute function now we know it
+ // can do muting stuff
+ // debug(1, "Has mixer and mute ability we will use.");
+ } else {
+ // debug(1, "Has mixer but not using hardware mute.");
+ }
+ close_mixer();
+ }
+ debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
+ pthread_cleanup_pop(0);
+ pthread_setcancelstate(oldState, NULL);
+ }
+ }
+ return response;
+}
+
+static int init(int argc, char **argv) {
+ // for debugging
+ snd_output_stdio_attach(&output, stdout, 0);
+
+ // debug(2,"audio_alsa init called.");
+ int response = 0; // this will be what we return to the caller.
+ alsa_device_initialised = 0;
+ const char *str;
+ int value;
+ // double dvalue;
+
+ // set up default values first
+
+ alsa_backend_state = abm_disconnected; // startup state
+ debug(2, "alsa: init() -- alsa_backend_state => abm_disconnected.");
+ set_period_size_request = 0;
+ set_buffer_size_request = 0;
+ config.alsa_use_hardware_mute = 0; // don't use it by default
+
+ config.audio_backend_latency_offset = 0;
+ config.audio_backend_buffer_desired_length = 0.200;
+ config.audio_backend_buffer_interpolation_threshold_in_seconds =
+ 0.120; // below this, basic interpolation will be used to save time.
+ config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem
+ config.audio_backend_silence_threshold =
+ 0.040; // start sending silent frames if the delay goes below this time
+ config.audio_backend_silence_scan_interval = 0.004; // check silence threshold this often
+
+ stall_monitor_error_threshold =
+ (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds;
+ stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form
+ debug(1,
+ "stall_monitor_error_threshold is 0x%" PRIx64 ", with alsa_maximum_stall_time of %f sec.",
+ stall_monitor_error_threshold, config.alsa_maximum_stall_time);
+
+ stall_monitor_start_time = 0;
+ stall_monitor_frame_count = 0;
+
+ config.disable_standby_mode = disable_standby_off;
+ config.keep_dac_busy = 0;
+ config.use_precision_timing = YNA_AUTO;
+
+ // get settings from settings file first, allow them to be overridden by
+ // command line options
+
+ // do the "general" audio options. Note, these options are in the "general"
+ // stanza!
+ parse_general_audio_options();
+
+ if (config.cfg != NULL) {
+ double dvalue;
+
+ /* Get the Output Device Name. */
+ if (config_lookup_string(config.cfg, "alsa.output_device", &str)) {
+ alsa_out_dev = (char *)str;
+ }
+
+ /* Get the Mixer Type setting. */
+
+ if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) {
+ inform("The alsa mixer_type setting is deprecated and has been ignored. "
+ "FYI, using the \"mixer_control_name\" setting automatically "
+ "chooses a hardware mixer.");
+ }
+
+ /* Get the Mixer Device Name. */
+ if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) {
+ alsa_mix_dev = (char *)str;
+ }
+
+ /* Get the Mixer Control Name. */
+ if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) {
+ alsa_mix_ctrl = (char *)str;
+ hardware_mixer = 1;
+ }
+
+ /* Get the disable_synchronization setting. */
+ if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.no_sync = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.no_sync = 1;
+ else {
+ warn("Invalid disable_synchronization option choice \"%s\". It should "
+ "be \"yes\" or "
+ "\"no\". It is set to \"no\".");
+ config.no_sync = 0;
+ }
+ }
+
+ /* Get the mute_using_playback_switch setting. */
+ if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) {
+ inform("The alsa \"mute_using_playback_switch\" setting is deprecated. "
+ "Please use the \"use_hardware_mute_if_available\" setting instead.");
+ if (strcasecmp(str, "no") == 0)
+ config.alsa_use_hardware_mute = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.alsa_use_hardware_mute = 1;
+ else {
+ warn("Invalid mute_using_playback_switch option choice \"%s\". It "
+ "should be \"yes\" or "
+ "\"no\". It is set to \"no\".");
+ config.alsa_use_hardware_mute = 0;
+ }
+ }
+
+ /* Get the use_hardware_mute_if_available setting. */
+ if (config_lookup_string(config.cfg, "alsa.use_hardware_mute_if_available", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.alsa_use_hardware_mute = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.alsa_use_hardware_mute = 1;
+ else {
+ warn("Invalid use_hardware_mute_if_available option choice \"%s\". It "
+ "should be \"yes\" or "
+ "\"no\". It is set to \"no\".");
+ config.alsa_use_hardware_mute = 0;
+ }
+ }
+
+
+ /* Get the output format, using the same names as aplay does*/
+ if (config_lookup_string(config.cfg, "alsa.output_format", &str)) {
+ if (strcasecmp(str, "S16") == 0)
+ config.output_format = SPS_FORMAT_S16;
+ else if (strcasecmp(str, "S16_LE") == 0)
+ config.output_format = SPS_FORMAT_S16_LE;
+ else if (strcasecmp(str, "S16_BE") == 0)
+ config.output_format = SPS_FORMAT_S16_BE;
+ else if (strcasecmp(str, "S24") == 0)
+ config.output_format = SPS_FORMAT_S24;
+ else if (strcasecmp(str, "S24_LE") == 0)
+ config.output_format = SPS_FORMAT_S24_LE;
+ else if (strcasecmp(str, "S24_BE") == 0)
+ config.output_format = SPS_FORMAT_S24_BE;
+ else if (strcasecmp(str, "S24_3LE") == 0)
+ config.output_format = SPS_FORMAT_S24_3LE;
+ else if (strcasecmp(str, "S24_3BE") == 0)
+ config.output_format = SPS_FORMAT_S24_3BE;
+ else if (strcasecmp(str, "S32") == 0)
+ config.output_format = SPS_FORMAT_S32;
+ else if (strcasecmp(str, "S32_LE") == 0)
+ config.output_format = SPS_FORMAT_S32_LE;
+ else if (strcasecmp(str, "S32_BE") == 0)
+ config.output_format = SPS_FORMAT_S32_BE;
+ else if (strcasecmp(str, "U8") == 0)
+ config.output_format = SPS_FORMAT_U8;
+ else if (strcasecmp(str, "S8") == 0)
+ config.output_format = SPS_FORMAT_S8;
+ else if (strcasecmp(str, "auto") == 0)
+ config.output_format_auto_requested = 1;
+ else {
+ warn("Invalid output format \"%s\". It should be \"U8\", \"S8\", "
+ "\"S16\", \"S24\", \"S24_LE\", \"S24_BE\", "
+ "\"S24_3LE\", \"S24_3BE\" or "
+ "\"S32\", \"S32_LE\", \"S32_BE\". It is set to \"%s\".",
+ sps_format_description_string(config.output_format));
+ }
+ }
+
+ if (config_lookup_string(config.cfg, "alsa.output_rate", &str)) {
+ if (strcasecmp(str, "auto") == 0) {
+ config.output_rate_auto_requested = 1;
+ } else {
+ /* Get the output rate, which must be a multiple of 44,100*/
+ if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) {
+ debug(1, "alsa output rate is %d frames per second", value);
+ switch (value) {
+ case 44100:
+ case 88200:
+ case 176400:
+ case 352800:
+ config.output_rate = value;
+ break;
+ default:
+ warn("Invalid output rate \"%d\". It should be \"auto\" or a multiple of 44,100 up "
+ "to 352,800. It is "
+ "set to %d.",
+ value,config.output_rate);
+ }
+ }
+ }
+ }
+
+ /* Get the use_mmap_if_available setting. */
+ if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.no_mmap = 1;
+ else if (strcasecmp(str, "yes") == 0)
+ config.no_mmap = 0;
+ else {
+ warn("Invalid use_mmap_if_available option choice \"%s\". It should be "
+ "\"yes\" or \"no\". "
+ "It is set to \"yes\".");
+ config.no_mmap = 0;
+ }
+ }
+ /* Get the optional period size value */
+ if (config_lookup_int(config.cfg, "alsa.period_size", &value)) {
+ set_period_size_request = 1;
+ debug(1, "Value read for period size is %d.", value);
+ if (value < 0) {
+ warn("Invalid alsa period size setting \"%d\". It "
+ "must be greater than 0. No setting is made.",
+ value);
+ set_period_size_request = 0;
+ } else {
+ period_size_requested = value;
+ }
+ }
+
+ /* Get the optional buffer size value */
+ if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) {
+ set_buffer_size_request = 1;
+ debug(1, "Value read for buffer size is %d.", value);
+ if (value < 0) {
+ warn("Invalid alsa buffer size setting \"%d\". It "
+ "must be greater than 0. No setting is made.",
+ value);
+ set_buffer_size_request = 0;
+ } else {
+ buffer_size_requested = value;
+ }
+ }
+
+ /* Get the optional alsa_maximum_stall_time setting. */
+ if (config_lookup_float(config.cfg, "alsa.maximum_stall_time", &dvalue)) {
+ if (dvalue < 0.0) {
+ warn("Invalid alsa maximum write time setting \"%f\". It "
+ "must be greater than 0. Default is \"%f\". No setting is made.",
+ dvalue, config.alsa_maximum_stall_time);
+ } else {
+ config.alsa_maximum_stall_time = dvalue;
+ }
+ }
+
+
+ /* Get the optional disable_standby_mode setting. */
+ if (config_lookup_string(config.cfg, "alsa.disable_standby_mode", &str)) {
+ if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) || (strcasecmp(str, "never") == 0))
+ config.disable_standby_mode = disable_standby_off;
+ else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) || (strcasecmp(str, "always") == 0)) {
+ config.disable_standby_mode = disable_standby_always;
+ config.keep_dac_busy = 1;
+ } else if (strcasecmp(str, "auto") == 0)
+ config.disable_standby_mode = disable_standby_auto;
+ else {
+ warn("Invalid disable_standby_mode option choice \"%s\". It should be "
+ "\"always\", \"auto\" or \"never\". "
+ "It is set to \"never\".");
+ }
+ }
+
+
+ if (config_lookup_string(config.cfg, "alsa.use_precision_timing", &str)) {
+ if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) || (strcasecmp(str, "never") == 0))
+ config.use_precision_timing = YNA_NO;
+ else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) || (strcasecmp(str, "always") == 0)) {
+ config.use_precision_timing = YNA_YES;
+ config.keep_dac_busy = 1;
+ } else if (strcasecmp(str, "auto") == 0)
+ config.use_precision_timing = YNA_AUTO;
+ else {
+ warn("Invalid use_precision_timing option choice \"%s\". It should be "
+ "\"yes\", \"auto\" or \"no\". "
+ "It is set to \"auto\".");
+ }
+ }
+
+ debug(1, "alsa: disable_standby_mode is \"%s\".", config.disable_standby_mode == disable_standby_off ? "never" : config.disable_standby_mode == disable_standby_always ? "always" : "auto");
+ }
+
+ optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
+ argv--; // so we shift the arguments to satisfy getopt()
+ argc++;
+ // some platforms apparently require optreset = 1; - which?
+ int opt;
+ while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) {
+ switch (opt) {
+ case 'd':
+ alsa_out_dev = optarg;
+ break;
+
+ case 't':
+ inform("The alsa backend -t option is deprecated and has been ignored. "
+ "FYI, using the -c option automatically chooses a hardware "
+ "mixer.");
+ break;
+
+ case 'm':
+ alsa_mix_dev = optarg;
+ break;
+ case 'c':
+ alsa_mix_ctrl = optarg;
+ hardware_mixer = 1;
+ break;
+ case 'i':
+ alsa_mix_index = strtol(optarg, NULL, 10);
+ break;
+ default:
+ warn("Invalid audio option \"-%c\" specified -- ignored.", opt);
+ help();
+ }
+ }
+
+ if (optind < argc) {
+ warn("Invalid audio argument: \"%s\" -- ignored", argv[optind]);
+ }
+
+ debug(1, "alsa: output device name is \"%s\".", alsa_out_dev);
+
+ // so, now, if the option to keep the DAC running has been selected, start a
+ // thread to monitor the
+ // length of the queue
+ // if the queue gets too short, stuff it with silence
+
+ most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code
+ pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL);
+
+ return response;
+}
+
+static void deinit(void) {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ // debug(2,"audio_alsa deinit called.");
+ stop();
+ debug(2, "Cancel buffer monitor thread.");
+ pthread_cancel(alsa_buffer_monitor_thread);
+ debug(3, "Join buffer monitor thread.");
+ pthread_join(alsa_buffer_monitor_thread, NULL);
+ pthread_setcancelstate(oldState, NULL);
+}
+
+int set_mute_state() {
+ int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 10000, 0);
+ if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) &&
+ (open_mixer() == 1)) {
+ response = 0; // okay if actually using the mute facility
+ debug(2, "alsa: actually set_mute_state");
+ int mute = 0;
+ if ((mute_requested_externally != 0) || (mute_requested_internally != 0))
+ mute = 1;
+ if (mute == 1) {
+ debug(2, "alsa: hardware mute switched on");
+ if (snd_mixer_selem_has_playback_switch(alsa_mix_elem))
+ snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0);
+ else {
+ volume_based_mute_is_active = 1;
+ do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute);
+ }
+ } else {
+ debug(2, "alsa: hardware mute switched off");
+ if (snd_mixer_selem_has_playback_switch(alsa_mix_elem))
+ snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1);
+ else {
+ volume_based_mute_is_active = 0;
+ do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume);
+ }
+ }
+ close_mixer();
+ }
+ debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
+ pthread_cleanup_pop(0); // release the mutex
+ pthread_setcancelstate(oldState, NULL);
+ return response;
+}
+
+static void start(__attribute__((unused)) int i_sample_rate, __attribute__((unused)) int i_sample_format) {
+ debug(3, "audio_alsa start called.");
+
+ frame_index = 0;
+ measurement_data_is_valid = 0;
+
+ stall_monitor_start_time = 0;
+ stall_monitor_frame_count = 0;
+ if (alsa_device_initialised == 0) {
+ debug(2, "alsa: start() calling do_alsa_device_init_if_needed.");
+ do_alsa_device_init_if_needed();
+ }
+}
+
+int standard_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay, enum yndk_type *using_update_timestamps) {
+ int ret = 0;
+ if (using_update_timestamps)
+ *using_update_timestamps = YNDK_NO;
+ *state = snd_pcm_state(alsa_handle);
+ if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) {
+ ret = snd_pcm_delay(alsa_handle,delay);
+ } else {
+ // not running, thus no delay information, thus can't check for frame
+ // rates
+ frame_index = 0; // we'll be starting over...
+ measurement_data_is_valid = 0;
+ *delay = 0;
+ }
+
+ stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush
+ stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes
+
+ return ret;
+}
+
+int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay, enum yndk_type *using_update_timestamps) {
+ snd_pcm_status_t *alsa_snd_pcm_status;
+ snd_pcm_status_alloca(&alsa_snd_pcm_status);
+
+ if (using_update_timestamps)
+ *using_update_timestamps = YNDK_DONT_KNOW;
+
+ struct timespec tn; // time now
+ snd_htimestamp_t update_timestamp; // actually a struct timespec
+
+ int ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status);
+ if (ret == 0) {
+
+ // must be 1.1 or later to use snd_pcm_status_get_driver_htstamp
+#if SND_LIB_MINOR == 0
+ snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp);
+#else
+ snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &update_timestamp);
+#endif
+
+ *state = snd_pcm_status_get_state(alsa_snd_pcm_status);
+
+ if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) {
+
+ uint64_t update_timestamp_ns =
+ update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec;
+
+ // if the update_timestamp is zero, we take this to mean that the device doesn't report
+ // interrupt timings. (It could be that it's not a real hardware device.)
+ // so we switch to getting the delay the regular way
+ // i.e. using snd_pcm_delay ()
+ if (using_update_timestamps) {
+ if (update_timestamp_ns == 0)
+ *using_update_timestamps = YNDK_NO;
+ else
+ *using_update_timestamps = YNDK_YES;
+ }
+
+// user information
+ if (update_timestamp_ns == 0) {
+ if (delay_type_notified != 1) {
+ inform("Note: the alsa output device \"%s\" is not capable of high precision delay timing.", snd_pcm_name(alsa_handle));
+ debug(1,"alsa: delay_and_status must use snd_pcm_delay() to calculate delay");
+ delay_type_notified = 1;
+ }
+ } else {
+// diagnostic
+ if (delay_type_notified != 0) {
+ debug(2,"alsa: delay_and_status using snd_pcm_status_get_delay() to calculate delay");
+ delay_type_notified = 0;
+ }
+ }
+
+ if (update_timestamp_ns == 0) {
+ ret = snd_pcm_delay (alsa_handle,delay);
+ } else {
+ *delay = snd_pcm_status_get_delay(alsa_snd_pcm_status);
+
+/*
+// It seems that the alsa library uses CLOCK_REALTIME before 1.0.28, even though
+// the check for monotonic returns true. Might have to watch out for this.
+ #if SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR < 28
+ clock_gettime(CLOCK_REALTIME, &tn);
+ #else
+ clock_gettime(CLOCK_MONOTONIC, &tn);
+ #endif
+*/
+
+ if (use_monotonic_clock)
+ clock_gettime(CLOCK_MONOTONIC, &tn);
+ else
+ clock_gettime(CLOCK_REALTIME, &tn);
+
+ uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec;
+
+ // see if it's stalled
+
+ if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == *delay)) {
+ // hasn't outputted anything since the last call to delay()
+
+ if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) ||
+ ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) {
+ debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64
+ ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64
+ ", stall_monitor_error_threshold %" PRIX64 ".",
+ time_now_ns, update_timestamp_ns, stall_monitor_start_time,
+ stall_monitor_error_threshold);
+ debug(2, "DAC seems to have stalled with time_now: %lx,%lx"
+ ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
+ ", stall_monitor_error_threshold %" PRIX64 ".",
+ tn.tv_sec, tn.tv_nsec, update_timestamp.tv_sec, update_timestamp.tv_nsec, stall_monitor_start_time,
+ stall_monitor_error_threshold);
+ ret = sps_extra_code_output_stalled;
+ }
+ } else {
+ stall_monitor_start_time = update_timestamp_ns;
+ stall_monitor_frame_count = *delay;
+ }
+
+ if (ret == 0) {
+ uint64_t delta = time_now_ns - update_timestamp_ns;
+
+ uint64_t frames_played_since_last_interrupt =
+ ((uint64_t)config.output_rate * delta) / 1000000000;
+ snd_pcm_sframes_t frames_played_since_last_interrupt_sized =
+ frames_played_since_last_interrupt;
+
+ *delay = *delay - frames_played_since_last_interrupt_sized;
+ }
+ }
+ } else { // not running, thus no delay information, thus can't check for
+ // stall
+ *delay = 0;
+ stall_monitor_start_time = 0; // zero if not initialised / not started / zeroed by flush
+ stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes
+
+ // not running, thus no delay information, thus can't check for frame
+ // rates
+ frame_index = 0; // we'll be starting over...
+ measurement_data_is_valid = 0;
+ }
+ } else {
+ debug(1, "alsa: can't get device's status.");
+ }
+ return ret;
}
int delay(long *the_delay) {
- // snd_pcm_sframes_t is a signed long -- hence the return of a "long"
- int reply;
- // debug(3,"audio_alsa delay called.");
- if (alsa_handle == NULL) {
- return -ENODEV;
- } else {
- debug_mutex_lock(&alsa_mutex, 10000, 1);
- int derr;
- if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING) {
- *the_delay = 0; // just to see what happens
- reply = snd_pcm_delay(alsa_handle, the_delay);
- if (reply != 0) {
- debug(1, "Error %d in delay(): \"%s\". Delay reported is %d frames.", reply,
- snd_strerror(reply), *the_delay);
- snd_pcm_recover(alsa_handle, reply, 1);
- }
- } else if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) {
- *the_delay = 0;
- reply = 0; // no error
- } else {
- if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) {
- *the_delay = 0;
- reply = 0; // no error
- } else {
- reply = -EIO;
- debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle));
- }
- if ((derr = snd_pcm_prepare(alsa_handle))) {
- snd_pcm_recover(alsa_handle, derr, 1);
- debug(1, "Error preparing after delay error: \"%s\".", snd_strerror(derr));
- }
- }
- debug_mutex_unlock(&alsa_mutex, 3);
- // here, occasionally pretend there's a problem with pcm_get_delay()
- // if ((random() % 100000) < 3) // keep it pretty rare
- // reply = -EPERM; // pretend something bad has happened
- return reply;
+ // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or
+ // SND_PCM_STATE_PREPARED
+ // or SND_PCM_STATE_DRAINING
+ // and returns the actual delay if running or 0 if prepared in *the_delay
+
+ // otherwise return an error code
+ // the error code could be a Unix errno code or a snderror code, or
+ // the sps_extra_code_output_stalled or the
+ // sps_extra_code_output_state_cannot_make_ready codes
+ int ret = 0;
+ *the_delay = 0;
+ if (alsa_handle == NULL)
+ ret = ENODEV;
+ else {
+ int oldState;
+
+ snd_pcm_state_t state;
+ snd_pcm_sframes_t my_delay = 0; // this initialisation is to silence a clang warning
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0);
+
+ ret = delay_and_status(&state, &my_delay, NULL);
+
+ debug_mutex_unlock(&alsa_mutex, 0);
+ pthread_cleanup_pop(0);
+ pthread_setcancelstate(oldState, NULL);
+
+ *the_delay = my_delay; // note: snd_pcm_sframes_t is a long
}
+ return ret;
}
-static void play(void *buf, int samples) {
+int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) {
+ int response = 0; // zero means okay
+ if (measurement_data_is_valid) {
+ *elapsed_time = measurement_time - measurement_start_time;
+ *frames_played = frames_played_at_measurement_time - frames_played_at_measurement_start_time;
+ } else {
+ *elapsed_time = 0;
+ *frames_played = 0;
+ response = -1;
+ }
+ return response;
+}
+
+int do_play(void *buf, int samples) {
+ // assuming the alsa_mutex has been acquired
// debug(3,"audio_alsa play called.");
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+
+ snd_pcm_state_t state;
+ snd_pcm_sframes_t my_delay;
+ int ret = delay_and_status(&state, &my_delay, NULL);
+
+ if (ret == 0) { // will be non-zero if an error or a stall
+
+ if ((samples != 0) && (buf != NULL)) {
+
+ // jut check the state of the DAC
+
+ if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) &&
+ (state != SND_PCM_STATE_XRUN)) {
+ debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state);
+ }
+
+ // debug(3, "write %d frames.", samples);
+ ret = alsa_pcm_write(alsa_handle, buf, samples);
+ if (ret == samples) {
+ stall_monitor_frame_count += samples;
+
+ if (frame_index == 0) {
+ frames_sent_for_playing = samples;
+ } else {
+ frames_sent_for_playing += samples;
+ }
+
+ const uint64_t start_measurement_from_this_frame =
+ (2 * config.output_rate) / 352; // two seconds of frames
+
+ frame_index++;
+
+ if ((frame_index == start_measurement_from_this_frame) ||
+ ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) {
+
+ measurement_time = get_absolute_time_in_fp();
+ frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples;
+
+ if (frame_index == start_measurement_from_this_frame) {
+ // debug(1, "Start frame counting");
+ frames_played_at_measurement_start_time = frames_played_at_measurement_time;
+ measurement_start_time = measurement_time;
+ measurement_data_is_valid = 1;
+ }
+ }
+ } else {
+ frame_index = 0;
+ measurement_data_is_valid = 0;
+ if (ret == -EPIPE) { /* underrun */
+ debug(1, "alsa: underrun while writing %d samples to alsa device.", samples);
+ ret = snd_pcm_recover(alsa_handle, ret, debuglev > 0 ? 1 : 0);
+ if (ret < 0) {
+ warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(ret));
+ }
+ } else if (ret == -ESTRPIPE) { /* suspended */
+ debug(1, "alsa: suspended while writing %d samples to alsa device.", samples);
+ while ((ret = snd_pcm_resume(alsa_handle)) == -EAGAIN) {
+ sleep(1); /* wait until the suspend flag is released */
+ if (ret < 0) {
+ warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, "
+ "snd_pcm_prepare() "
+ "failed: %s.",
+ snd_strerror(ret));
+ }
+ }
+ } else {
+ char errorstring[1024];
+ strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
+ debug(1, "alsa: error %d (\"%s\") writing %d samples to alsa device.", ret, (char *)errorstring, samples);
+ }
+
+ }
+ }
+ } else {
+ debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* "
+ "%d for play.",
+ ret, state);
+ frame_index = 0;
+ measurement_data_is_valid = 0;
+ }
+
+ pthread_setcancelstate(oldState, NULL);
+ return ret;
+}
+
+int do_open(int do_auto_setup) {
int ret = 0;
+ if (alsa_backend_state != abm_disconnected)
+ debug(1, "alsa: do_open() -- opening the output device when it is already "
+ "connected");
if (alsa_handle == NULL) {
- debug_mutex_lock(&alsa_mutex, 10000, 1);
- ret = open_alsa_device();
+ // debug(1,"alsa: do_open() -- opening the output device");
+ ret = open_alsa_device(do_auto_setup);
if (ret == 0) {
+ mute_requested_internally = 0;
if (audio_alsa.volume)
do_volume(set_volume);
- if (audio_alsa.mute)
- do_mute(0);
+ if (audio_alsa.mute) {
+ debug(2, "do_open() set_mute_state");
+ set_mute_state(); // the mute_requested_externally flag will have been
+ // set accordingly
+ // do_mute(0); // complete unmute
+ }
+
+ alsa_backend_state = abm_connected; // only do this if it really opened it.
}
- debug_mutex_unlock(&alsa_mutex, 3);
- }
- if (ret == 0) {
- debug_mutex_lock(&alsa_mutex, 10000, 1);
- // snd_pcm_sframes_t current_delay = 0;
- int err;
- if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) {
- if ((err = snd_pcm_prepare(alsa_handle))) {
- snd_pcm_recover(alsa_handle, err, 1);
- debug(1, "Error preparing after underrun: \"%s\".", snd_strerror(err));
- }
- }
- if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
- (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) {
- if (buf == NULL)
- debug(1, "NULL buffer passed to pcm_writei -- skipping it");
- if (samples == 0)
- debug(1, "empty buffer being passed to pcm_writei -- skipping it");
- if ((samples != 0) && (buf != NULL)) {
- err = alsa_pcm_write(alsa_handle, buf, samples);
- if (err < 0) {
- debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples,
- snd_strerror(err));
- snd_pcm_recover(alsa_handle, err, 1);
- }
- }
- } else {
- debug(1, "Error -- ALSA device in incorrect state (%d) for play.",
- snd_pcm_state(alsa_handle));
- if ((err = snd_pcm_prepare(alsa_handle))) {
- snd_pcm_recover(alsa_handle, err, 1);
- debug(1, "Error preparing after play error: \"%s\".", snd_strerror(err));
- }
- }
- debug_mutex_unlock(&alsa_mutex, 3);
+ } else {
+ debug(1, "alsa: do_open() -- output device already open.");
}
+ return ret;
}
-static void flush(void) {
- // debug(2,"audio_alsa flush called.");
- debug_mutex_lock(&alsa_mutex, 10000, 1);
- int derr;
- do_mute(1);
+int do_close() {
+ if (alsa_backend_state == abm_disconnected)
+ debug(1, "alsa: do_close() -- closing the output device when it is already "
+ "disconnected");
+ int derr = 0;
if (alsa_handle) {
-
+ // debug(1,"alsa: do_close() -- closing the output device");
if ((derr = snd_pcm_drop(alsa_handle)))
debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr));
-
if ((derr = snd_pcm_hw_free(alsa_handle)))
debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr));
// flush also closes the device
+ debug(2, "alsa: do_close() -- closing alsa handle");
if ((derr = snd_pcm_close(alsa_handle)))
debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr));
-
alsa_handle = NULL;
+ } else {
+ debug(1, "alsa: do_close() -- output device already closed.");
}
+ alsa_backend_state = abm_disconnected;
+ return derr;
+}
+
+int play(void *buf, int samples) {
+
+ // play() will change the state of the alsa_backend_mode to abm_playing
+ // also, if the present alsa_backend_state is abm_disconnected, then first the
+ // DAC must be
+ // connected
+
+ // debug(3,"audio_alsa play called.");
+ int ret = 0;
+
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0);
+
+ if (alsa_backend_state == abm_disconnected) {
+ ret = do_open(0); // don't try to auto setup
+ if (ret == 0)
+ debug(2, "alsa: play() -- opened output device");
+ }
+
+ if (ret == 0) {
+ if (alsa_backend_state != abm_playing) {
+ debug(2, "alsa: play() -- alsa_backend_state => abm_playing");
+ alsa_backend_state = abm_playing;
+
+ // mute_requested_internally = 0; // stop requesting a mute for backend's own
+ // reasons, which might have been a flush
+ //debug(2, "play() set_mute_state");
+ //set_mute_state(); // try to action the request and return a status
+ // do_mute(0); // unmute for backend's reason
+ }
+ ret = do_play(buf, samples);
+ }
+
+ debug_mutex_unlock(&alsa_mutex, 0);
+ pthread_cleanup_pop(0); // release the mutex
+ return ret;
+}
+
+int prepare(void) {
+ // this will leave the DAC open / connected.
+ int ret = 0;
+
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0);
+
+ if (alsa_backend_state == abm_disconnected) {
+ ret = do_open(1); // do auto setup
+ if (ret == 0)
+ debug(2, "alsa: prepare() -- opened output device");
+
+ }
+
+ debug_mutex_unlock(&alsa_mutex, 0);
+ pthread_cleanup_pop(0); // release the mutex
+ return ret;
+}
+
+static void flush(void) {
+ // debug(2,"audio_alsa flush called.");
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1);
+ // mute_requested_internally = 1; // request a mute for backend's reasons
+ // debug(2, "flush() set_mute_state");
+ // set_mute_state();
+ // do_mute(1); // mute for backend's own reasons
+ if (alsa_backend_state != abm_disconnected) { // must be playing or connected...
+ if (config.keep_dac_busy != 0) {
+ debug(2, "alsa: flush() -- alsa_backend_state => abm_connected.");
+ alsa_backend_state = abm_connected;
+ } else {
+ debug(2, "alsa: flush() -- closing the output device");
+ do_close(); // will change the state to disconnected
+ debug(2, "alsa: flush() -- alsa_backend_state => abm_disconnected.");
+ }
+ } else
+ debug(3, "alsa: flush() -- called on a disconnected alsa backend");
debug_mutex_unlock(&alsa_mutex, 3);
+ pthread_cleanup_pop(0); // release the mutex
}
static void stop(void) {
// debug(2,"audio_alsa stop called.");
- // when we want to stop, we want the alsa device
- // to be closed immediately -- we may even be killing the thread, so we
- // don't wish to wait
- // so we should flush first
- flush(); // flush will also close the device
- // close_alsa_device();
+ flush(); // flush will also close the device if appropriate
}
static void parameters(audio_parameters *info) {
@@ -938,10 +1722,14 @@ static void parameters(audio_parameters *info) {
info->maximum_volume_dB = alsa_mix_maxdb;
}
-void do_volume(double vol) { // caller is assumed to have the alsa_mutex when using this function
+void do_volume(double vol) { // caller is assumed to have the alsa_mutex when
+ // using this function
debug(3, "Setting volume db to %f.", vol);
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
set_volume = vol;
- if (volume_set_request && open_mixer()) {
+ pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
+ if (volume_set_request && (open_mixer() == 1)) {
if (has_softvol) {
if (ctl && elem_id) {
snd_ctl_elem_value_t *value;
@@ -970,13 +1758,14 @@ void do_volume(double vol) { // caller is assumed to have the alsa_mutex when us
volume_set_request = 0; // any external request that has been made is now satisfied
close_mixer();
}
+ debug_mutex_unlock(&alsa_mixer_mutex, 3);
+ pthread_cleanup_pop(0); // release the mutex
+ pthread_setcancelstate(oldState, NULL);
}
void volume(double vol) {
- debug_mutex_lock(&alsa_mutex, 1000, 1);
volume_set_request = 1; // an external request has been made to set the volume
do_volume(vol);
- debug_mutex_unlock(&alsa_mutex, 3);
}
/*
@@ -986,7 +1775,8 @@ static void linear_volume(double vol) {
if (hardware_mixer && alsa_mix_handle) {
double linear_volume = pow(10, vol);
// debug(1,"Linear volume is %f.",linear_volume);
- long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume;
+ long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) *
+linear_volume;
// debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol);
if (alsa_mix_handle) {
if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0)
@@ -997,55 +1787,111 @@ static void linear_volume(double vol) {
}
*/
-static void mute(int mute_state_requested) {
- // debug(1,"External Mute Request: %d",mute_state_requested);
- debug_mutex_lock(&alsa_mutex, 10000, 1);
- mute_request_pending = 1;
- overriding_mute_state_requested = mute_state_requested;
- do_mute(mute_state_requested);
- debug_mutex_unlock(&alsa_mutex, 3);
+int mute(int mute_state_requested) { // these would be for external reasons, not
+ // because of the
+ // state of the backend.
+ mute_requested_externally = mute_state_requested; // request a mute for external reasons
+ debug(2, "mute(%d) set_mute_state", mute_state_requested);
+ return set_mute_state();
}
+/*
+void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void
+*arg) {
+ debug(1, "alsa: alsa_buffer_monitor_thread_cleanup_function called.");
+}
+*/
-void do_mute(int mute_state_requested) {
-
- // if a mute is requested now, then
- // if an external mute request is in place, leave everything muted
- // otherwise, if an external mute request is pending, action it
- // otherwise, action the do_mute request
-
- int local_mute_state_requested =
- overriding_mute_state_requested; // go with whatever was asked by the external "mute" call
-
- // The mute state requested will be actioned unless mute_request_pending is set
- // If it is set, then that the pending request will be actioned.
- // If the hardware isn't there, or we are not allowed to use it, nothing will be done
- // The caller must have the alsa mutex
-
- if (config.alsa_use_hardware_mute == 1) {
- if (mute_request_pending == 0)
- local_mute_state_requested = mute_state_requested;
- if (open_mixer()) {
- if (local_mute_state_requested) {
- // debug(1,"Playback Switch mute actually done");
- if (snd_mixer_selem_has_playback_switch(alsa_mix_elem))
- snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0);
- else {
- // debug(1,"Activating volume-based mute.");
- volume_based_mute_is_active = 1;
- do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute);
+void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
+ int okb = -1;
+ while (1) {
+ if (okb != config.keep_dac_busy) {
+ debug(2,"keep_dac_busy is now \"%s\"",config.keep_dac_busy == 0 ? "no" : "yes");
+ okb = config.keep_dac_busy;
+ }
+ if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) {
+ debug(2, "alsa: alsa_buffer_monitor_thread_code() calling "
+ "do_alsa_device_init_if_needed.");
+ do_alsa_device_init_if_needed();
+ }
+ int sleep_time_ms = (int)(config.audio_backend_silence_scan_interval * 1000);
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0);
+ // check possible state transitions here
+ if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) {
+ // open the dac and move to abm_connected mode
+ if (do_open(1) == 0) // no automatic setup of rate and speed if necessary
+ debug(2, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; "
+ "alsa_backend_state => abm_connected");
+ } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) {
+ stall_monitor_start_time = 0;
+ frame_index = 0;
+ measurement_data_is_valid = 0;
+ debug(2, "alsa: alsa_buffer_monitor_thread_code() -- closing the output "
+ "device");
+ do_close();
+ debug(2, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state "
+ "=> abm_disconnected");
+ }
+ // now, if the backend is not in the abm_disconnected state
+ // and config.keep_dac_busy is true (at the present, this has to be the case
+ // to be in the
+ // abm_connected state in the first place...) then do the silence-filling
+ // thing, if needed, and if the output device is capable of precision delay.
+ if ((alsa_backend_state != abm_disconnected) && (config.keep_dac_busy != 0) && precision_delay_available()) {
+ int reply;
+ long buffer_size = 0;
+ snd_pcm_state_t state;
+ uint64_t present_time = get_absolute_time_in_fp();
+ if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) {
+ reply = delay_and_status(&state, &buffer_size, NULL);
+ if (reply != 0) {
+ buffer_size = 0;
+ char errorstring[1024];
+ strerror_r(-reply, (char *)errorstring, sizeof(errorstring));
+ debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply,
+ (char *)errorstring);
}
- } else if (overriding_mute_state_requested == 0) {
- // debug(1,"Playback Switch unmute actually done");
- if (snd_mixer_selem_has_playback_switch(alsa_mix_elem))
- snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1);
- else {
- // debug(1,"Deactivating volume-based mute.");
- volume_based_mute_is_active = 0;
- do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume);
+ long buffer_size_threshold =
+ (long)(config.audio_backend_silence_threshold * config.output_rate);
+ if (buffer_size < buffer_size_threshold) {
+ uint64_t sleep_time_in_fp = sleep_time_ms;
+ sleep_time_in_fp = sleep_time_in_fp << 32;
+ sleep_time_in_fp = sleep_time_in_fp / 1000;
+ // debug(1,"alsa: sleep_time: %d ms or 0x%" PRIx64 " in fp
+ // form.",sleep_time_ms,sleep_time_in_fp); int frames_of_silence =
+ // (config.output_rate *
+ // sleep_time_ms * 2) / 1000;
+ int frames_of_silence = 1024;
+ size_t size_of_silence_buffer = frames_of_silence * frame_size;
+ // debug(1, "alsa: alsa_buffer_monitor_thread_code -- silence buffer
+ // length: %u bytes.",
+ // size_of_silence_buffer);
+ void *silence = malloc(size_of_silence_buffer);
+ if (silence == NULL) {
+ debug(1, "alsa: alsa_buffer_monitor_thread_code -- failed to "
+ "allocate memory for a "
+ "silent frame buffer.");
+ } else {
+ pthread_cleanup_push(malloc_cleanup, silence);
+ int use_dither = 0;
+ if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) &&
+ (config.airplay_volume != 0.0))
+ use_dither = 1;
+ dither_random_number_store =
+ generate_zero_frames(silence, frames_of_silence, config.output_format,
+ use_dither, // i.e. with dither
+ dither_random_number_store);
+ // debug(1,"Play %d frames of silence with most_recent_write_time of
+ // %" PRIx64 ".",
+ // frames_of_silence,most_recent_write_time);
+ do_play(silence, frames_of_silence);
+ pthread_cleanup_pop(1);
+ }
}
}
- close_mixer();
}
+ debug_mutex_unlock(&alsa_mutex, 0);
+ pthread_cleanup_pop(0); // release the mutex
+ usleep(sleep_time_ms * 1000); // has a cancellation point in it
}
- mute_request_pending = 0;
+ pthread_exit(NULL);
}
diff --git a/audio_ao.c b/audio_ao.c
index 8a78e880..38953b69 100644
--- a/audio_ao.c
+++ b/audio_ao.c
@@ -121,7 +121,7 @@ static void deinit(void) {
static void start(__attribute__((unused)) int sample_rate,
__attribute__((unused)) int sample_format) {}
-static void play(void *buf, int samples) { ao_play(dev, buf, samples * 4); }
+static int play(void *buf, int samples) { return ao_play(dev, buf, samples * 4); }
static void stop(void) {}
@@ -129,8 +129,10 @@ audio_output audio_ao = {.name = "ao",
.help = &help,
.init = &init,
.deinit = &deinit,
+ .prepare = NULL,
.start = &start,
.stop = &stop,
+ .is_running = NULL,
.flush = NULL,
.delay = NULL,
.play = &play,
diff --git a/audio_dummy.c b/audio_dummy.c
index 5f55bab1..483dbe42 100644
--- a/audio_dummy.c
+++ b/audio_dummy.c
@@ -48,18 +48,21 @@ static void start(int sample_rate, __attribute__((unused)) int sample_format) {
debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
}
-static void play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {}
+static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {
+ return 0;
+}
static void stop(void) { debug(1, "dummy audio stopped\n"); }
-static void help(void) { printf(" There are no options for dummy audio.\n"); }
audio_output audio_dummy = {.name = "dummy",
- .help = &help,
+ .help = NULL,
.init = &init,
.deinit = &deinit,
+ .prepare = NULL,
.start = &start,
.stop = &stop,
+ .is_running = NULL,
.flush = NULL,
.delay = NULL,
.play = &play,
diff --git a/audio_jack.c b/audio_jack.c
new file mode 100644
index 00000000..746d3d61
--- /dev/null
+++ b/audio_jack.c
@@ -0,0 +1,350 @@
+/*
+ * jack output driver. This file is part of Shairport Sync.
+ * Copyright (c) 2019 Mike Brady ,
+ * Jörn Nettingsmeier
+ *
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#include "audio.h"
+#include "common.h"
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+// Two-channel, 16bit audio:
+static const int bytes_per_frame = 4;
+// Four seconds buffer -- should be plenty
+#define buffer_size (44100 * 4 * bytes_per_frame)
+
+static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+int jack_init(int, char **);
+void jack_deinit(void);
+void jack_start(int, int);
+int play(void *, int);
+void jack_stop(void);
+int jack_delay(long *);
+void jack_flush(void);
+
+audio_output audio_jack = {.name = "jack",
+ .help = NULL,
+ .init = &jack_init,
+ .deinit = &jack_deinit,
+ .prepare = NULL,
+ .start = &jack_start,
+ .stop = NULL,
+ .is_running = NULL,
+ .flush = &jack_flush,
+ .delay = &jack_delay,
+ .play = &play,
+ .volume = NULL,
+ .parameters = NULL,
+ .mute = NULL};
+
+// This also affects deinterlacing.
+// So make it exactly the number of incoming audio channels!
+#define NPORTS 2
+static jack_port_t *port[NPORTS];
+static const char* port_name[NPORTS] = { "out_L", "out_R" };
+
+static jack_client_t *client;
+static jack_nframes_t sample_rate;
+static jack_nframes_t jack_latency;
+
+static jack_ringbuffer_t *jackbuf;
+static int flush_please = 0;
+
+static jack_latency_range_t latest_latency_range[NPORTS];
+static int64_t time_of_latest_transfer;
+
+
+static inline jack_default_audio_sample_t sample_conv(short sample) {
+ // It sounds correct, but I don't understand it.
+ // Zero int needs to be zero float. Check.
+ // Plus 32767 int is 1.0. Check.
+ // Minus 32767 int is -0.99997. And here my brain shuts down.
+ // In my head, it should be 1.0, and we should tolerate an overflow
+ // at minus 32768. But I'm sure there's a textbook explanation somewhere.
+ return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX));
+}
+
+static void deinterleave_and_convert(const char *interleaved_input_buffer,
+ jack_default_audio_sample_t* jack_output_buffer[],
+ jack_nframes_t offset,
+ jack_nframes_t nframes) {
+ jack_nframes_t f;
+ // We're dealing with 16bit audio here:
+ short *ifp = (short *)interleaved_input_buffer;
+ // Zero-copy, we're working directly on the target and destination buffers,
+ // so deal with an offset for the second part of the input ringbuffer
+ for (f = offset; f < (nframes + offset); f++) {
+ for (int i = 0; i < NPORTS; i++) {
+ jack_output_buffer[i][f] = sample_conv(*ifp++);
+ }
+ }
+}
+
+// This is the JACK process callback. We don't decide when it runs.
+// It must be hard-realtime safe (i.e. fully deterministic, with constant CPU
+// usage. No calls to anything that could ever block: no syscalls, no screen
+// output, no file access, no mutexes...
+// The JACK ringbuffer we use to get the data in here is explicitly lock-free.
+static int process(jack_nframes_t nframes, __attribute__((unused)) void *arg) {
+ jack_default_audio_sample_t *buffer[NPORTS];
+ // Expect an array of two elements because of possible ringbuffer wrap-around:
+ jack_ringbuffer_data_t v[2] = { 0 };
+ jack_nframes_t i, thisbuf;
+ int frames_written = 0;
+ int frames_required = 0;
+
+ for (i = 0; i < NPORTS; i++) {
+ buffer[i] = (jack_default_audio_sample_t *)jack_port_get_buffer(port[i], nframes);
+ }
+ if (flush_please) {
+ // We just move the read pointer ahead without doing anything with the data.
+ jack_ringbuffer_read_advance(jackbuf, jack_ringbuffer_read_space(jackbuf));
+ flush_please = 0;
+ // Since we don't change nframes, the whole buffer will be zeroed later.
+ } else {
+ jack_ringbuffer_get_read_vector(jackbuf, v);
+ for (i = 0; i < 2; i++) {
+ thisbuf = v[i].len / bytes_per_frame;
+ if (thisbuf > nframes) {
+ frames_required = nframes;
+ } else {
+ frames_required = thisbuf;
+ }
+ deinterleave_and_convert(v[i].buf, buffer, frames_written, frames_required);
+ frames_written += frames_required;
+ nframes -= frames_required;
+ }
+ jack_ringbuffer_read_advance(jackbuf, frames_written * bytes_per_frame);
+ }
+ // If there are any more frames to put into the buffer, fill them with
+ // silence. This is a critical underflow situation. Let's at least keep the JACK
+ // graph humming along while preventing the motorboat sound of a repeating buffer.
+ while (nframes > 0) {
+ for (i = 0; i < NPORTS; i++) {
+ buffer[i][frames_written] = 0.0;
+ }
+ frames_written++;
+ nframes--;
+ }
+ return 0; // Tell JACK that all is well.
+}
+
+// This is the JACK graph reorder callback. Now we know some JACK connections
+// have changed, so we recompute the latency.
+static int graph(__attribute__((unused)) void * arg) {
+ int latency = 0;
+ debug(2, "JACK graph reorder callback called.");
+ for (int i=0; i> 32;
+ // debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check);
+ // jack_latency is set by the graph() callback, it's the average of the maximum
+ // latencies of all our output ports. Adjust this constant baseline delay according
+ // to the buffer fill level:
+ *the_delay = jack_latency + audio_occupancy_now - frames_processed_since_latest_latency_check;
+ // debug(1,"reporting a delay of %d frames",*the_delay);
+ return 0;
+}
+
+int play(void *buf, int samples) {
+ // debug(1,"jack_play of %d samples.",samples);
+ // copy the samples into the queue
+ size_t bytes_to_transfer, bytes_transferred;
+ bytes_to_transfer = samples * bytes_per_frame;
+ // It's ok to lock here since we're not in the realtime callback:
+ pthread_mutex_lock(&buffer_mutex);
+ bytes_transferred = jack_ringbuffer_write(jackbuf, buf, bytes_to_transfer);
+ time_of_latest_transfer = get_absolute_time_in_fp();
+ pthread_mutex_unlock(&buffer_mutex);
+ if (bytes_transferred < bytes_to_transfer) {
+ warn("JACK ringbuffer overrun. Only wrote %d of %d bytes.",
+ bytes_transferred, bytes_to_transfer);
+ }
+ return 0;
+}
diff --git a/audio_pa.c b/audio_pa.c
index b46054d4..e6a5b576 100644
--- a/audio_pa.c
+++ b/audio_pa.c
@@ -70,6 +70,10 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
// set up default values first
config.audio_backend_buffer_desired_length = 0.35;
+ config.audio_backend_buffer_interpolation_threshold_in_seconds =
+ 0.02; // below this, soxr interpolation will not occur -- it'll be basic interpolation
+ // instead.
+
config.audio_backend_latency_offset = 0;
// get settings from settings file
@@ -85,6 +89,11 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
if (config_lookup_string(config.cfg, "pa.application_name", &str)) {
config.pa_application_name = (char *)str;
}
+
+ /* Get the PulseAudio sink name. */
+ if (config_lookup_string(config.cfg, "pa.sink", &str)) {
+ config.pa_sink = (char *)str;
+ }
}
// finish collecting settings
@@ -177,8 +186,19 @@ static void start(__attribute__((unused)) int sample_rate,
// PA_STREAM_AUTO_TIMING_UPDATE;
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
- // Connect stream to the default audio output sink
- if (pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) != 0)
+ int connect_result;
+
+ if (config.pa_sink) {
+ // Connect stream to the sink specified in the config
+ connect_result =
+ pa_stream_connect_playback(stream, config.pa_sink, &buffer_attr, stream_flags, NULL, NULL);
+ } else {
+ // Connect stream to the default audio output sink
+ connect_result =
+ pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL);
+ }
+
+ if (connect_result != 0)
die("could not connect to the pulseaudio playback stream -- the error message is \"%s\".",
pa_strerror(pa_context_errno(context)));
@@ -197,7 +217,7 @@ static void start(__attribute__((unused)) int sample_rate,
pa_threaded_mainloop_unlock(mainloop);
}
-static void play(void *buf, int samples) {
+static int play(void *buf, int samples) {
// debug(1,"pa_play of %d samples.",samples);
// copy the samples into the queue
size_t bytes_to_transfer = samples * 2 * 2;
@@ -223,6 +243,7 @@ static void play(void *buf, int samples) {
pa_stream_cork(stream, 0, stream_success_cb, mainloop);
pa_threaded_mainloop_unlock(mainloop);
}
+ return 0;
}
int pa_delay(long *the_delay) {
@@ -278,14 +299,14 @@ static void stop(void) {
pa_stream_disconnect(stream);
}
-static void help(void) { printf(" no settings.\n"); }
-
audio_output audio_pa = {.name = "pa",
- .help = &help,
+ .help = NULL,
.init = &init,
.deinit = &deinit,
+ .prepare = NULL,
.start = &start,
.stop = &stop,
+ .is_running = NULL,
.flush = &flush,
.delay = &pa_delay,
.play = &play,
diff --git a/audio_pipe.c b/audio_pipe.c
index c080277d..f0ced371 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -46,15 +46,22 @@ int warned = 0;
static void start(__attribute__((unused)) int sample_rate,
__attribute__((unused)) int sample_format) {
- // this will leave fd as -1 if a reader hasn't been attached
+
+
+ // this will leave fd as -1 if a reader hasn't been attached to the pipe
+ // we check that it's not a "real" error though. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO open for reading."
fd = open(pipename, O_WRONLY | O_NONBLOCK);
- if ((fd < -1) && (warned == 0)) {
+ if ((fd == -1) && (errno != ENXIO) && (warned == 0)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "pipe: start -- error %d (\"%s\") opening the pipe named \"%s\".", errno, (char*)errorstring, pipename);
warn("Error %d opening the pipe named \"%s\".", errno, pipename);
warned = 1;
}
}
-static void play(void *buf, int samples) {
+static int play(void *buf, int samples) {
// if the file is not open, try to open it.
char errorstring[1024];
if (fd == -1) {
@@ -68,11 +75,12 @@ static void play(void *buf, int samples) {
warn("Error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
warned = 1;
}
- } else if ((fd == -1) && (warned == 0)) {
+ } else if ((fd == -1) && (errno != ENXIO) && (warned == 0)) {
strerror_r(errno, (char *)errorstring, 1024);
warn("Error %d opening the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
warned = 1;
}
+ return warned;
}
static void stop(void) {
@@ -126,14 +134,16 @@ static void deinit(void) {
close(fd);
}
-static void help(void) { printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); }
+static void help(void) { printf(" specify the pathname of the pipe to write to.\n"); }
audio_output audio_pipe = {.name = "pipe",
.help = &help,
.init = &init,
.deinit = &deinit,
+ .prepare = NULL,
.start = &start,
.stop = &stop,
+ .is_running = NULL,
.flush = NULL,
.delay = NULL,
.play = &play,
diff --git a/audio_sndio.c b/audio_sndio.c
index d9cef65e..a591add4 100644
--- a/audio_sndio.c
+++ b/audio_sndio.c
@@ -33,7 +33,7 @@ static int init(int, char **);
static void onmove_cb(void *, int);
static void deinit(void);
static void start(int, int);
-static void play(void *, int);
+static int play(void *, int);
static void stop(void);
static void onmove_cb(void *, int);
static int delay(long *);
@@ -43,8 +43,10 @@ audio_output audio_sndio = {.name = "sndio",
.help = &help,
.init = &init,
.deinit = &deinit,
+ .prepare = NULL,
.start = &start,
.stop = &stop,
+ .is_running = NULL,
.flush = &flush,
.delay = &delay,
.play = &play,
@@ -64,20 +66,21 @@ struct sio_par par;
struct sndio_formats {
const char *name;
enum sps_format_t fmt;
-
+ unsigned int rate;
unsigned int bits;
unsigned int bps;
unsigned int sig;
unsigned int le;
};
-static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 8, 1, 1, SIO_LE_NATIVE},
- {"U8", SPS_FORMAT_U8, 8, 1, 0, SIO_LE_NATIVE},
- {"S16", SPS_FORMAT_S16, 16, 2, 1, SIO_LE_NATIVE},
- {"S24", SPS_FORMAT_S24, 24, 4, 1, SIO_LE_NATIVE},
- {"S24_3LE", SPS_FORMAT_S24_3LE, 24, 3, 1, 1},
- {"S24_3BE", SPS_FORMAT_S24_3BE, 24, 3, 1, 0},
- {"S32", SPS_FORMAT_S32, 24, 4, 1, SIO_LE_NATIVE}};
+static struct sndio_formats formats[] = {{"S8", SPS_FORMAT_S8, 44100, 8, 1, 1, SIO_LE_NATIVE},
+ {"U8", SPS_FORMAT_U8, 44100, 8, 1, 0, SIO_LE_NATIVE},
+ {"S16", SPS_FORMAT_S16, 44100, 16, 2, 1, SIO_LE_NATIVE},
+ {"AUTOMATIC", SPS_FORMAT_S16, 44100, 16, 2, 1, SIO_LE_NATIVE}, // TODO: make this really automatic?
+ {"S24", SPS_FORMAT_S24, 44100, 24, 4, 1, SIO_LE_NATIVE},
+ {"S24_3LE", SPS_FORMAT_S24_3LE, 44100, 24, 3, 1, 1},
+ {"S24_3BE", SPS_FORMAT_S24_3BE, 44100, 24, 3, 1, 0},
+ {"S32", SPS_FORMAT_S32, 44100, 24, 4, 1, SIO_LE_NATIVE}};
static void help() { printf(" -d output-device set the output device [default*|...]\n"); }
@@ -98,6 +101,9 @@ static int init(int argc, char **argv) {
devname = SIO_DEVANY;
config.audio_backend_buffer_desired_length = 1.0;
+ config.audio_backend_buffer_interpolation_threshold_in_seconds =
+ 0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation
+ // instead.
config.audio_backend_latency_offset = 0;
// get settings from settings file
@@ -142,7 +148,7 @@ static int init(int argc, char **argv) {
}
if (!found)
die("Invalid output format \"%s\". Should be one of: S8, U8, S16, S24, "
- "S24_3LE, S24_3BE, S32",
+ "S24_3LE, S24_3BE, S32, Automatic",
tmp);
}
}
@@ -170,6 +176,7 @@ static int init(int argc, char **argv) {
written = played = 0;
time_of_last_onmove_cb = 0;
at_least_one_onmove_cb_seen = 0;
+
for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
if (formats[i].fmt == config.output_format) {
@@ -180,19 +187,20 @@ static int init(int argc, char **argv) {
break;
}
}
-
+
if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par))
die("sndio: failed to set audio parameters");
for (i = 0, found = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
if (formats[i].bits == par.bits && formats[i].bps == par.bps && formats[i].sig == par.sig &&
- formats[i].le == par.le) {
+ formats[i].le == par.le && formats[i].rate == par.rate) {
config.output_format = formats[i].fmt;
found = 1;
break;
}
}
if (!found)
- die("sndio: failed to negotiate audio parameters");
+ die("sndio: could not set output device to the required format and rate.");
+
framesize = par.bps * par.pchan;
config.output_rate = par.rate;
@@ -222,12 +230,13 @@ static void start(__attribute__((unused)) int sample_rate,
pthread_mutex_unlock(&sndio_mutex);
}
-static void play(void *buf, int frames) {
+static int play(void *buf, int frames) {
if (frames > 0) {
pthread_mutex_lock(&sndio_mutex);
written += sio_write(hdl, buf, frames * framesize);
pthread_mutex_unlock(&sndio_mutex);
}
+ return 0;
}
static void stop() {
diff --git a/audio_soundio.c b/audio_soundio.c
index c46e00d5..7983733b 100644
--- a/audio_soundio.c
+++ b/audio_soundio.c
@@ -27,8 +27,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m
int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer);
int fill_count = fill_bytes / outstream->bytes_per_frame;
- debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
- "outstream->bytes_per_frame: %d",
+ debug(3,
+ "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
+ "outstream->bytes_per_frame: %d",
frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame);
if (frame_count_min > fill_count) {
@@ -167,7 +168,7 @@ static void start(int sample_rate, int sample_format) {
debug(1, "libsoundio output started\n");
}
-static void play(void *buf, int samples) {
+static int play(void *buf, int samples) {
// int err;
int free_bytes = soundio_ring_buffer_free_count(ring_buffer);
int written_bytes = 0;
@@ -186,6 +187,7 @@ static void play(void *buf, int samples) {
soundio_ring_buffer_advance_write_ptr(ring_buffer, write_bytes);
debug(3, "[<<---] Written to buffer : %d\n", written_bytes);
}
+ return 0;
}
static void parameters(audio_parameters *info) {
@@ -214,8 +216,10 @@ audio_output audio_soundio = {.name = "soundio",
.help = &help,
.init = &init,
.deinit = &deinit,
+ .prepare = NULL,
.start = &start,
.stop = &stop,
+ .is_running = NULL,
.flush = &flush,
.delay = NULL,
.play = &play,
diff --git a/audio_stdout.c b/audio_stdout.c
index 3e27ad15..00ded166 100644
--- a/audio_stdout.c
+++ b/audio_stdout.c
@@ -43,7 +43,7 @@ static void start(__attribute__((unused)) int sample_rate,
fd = STDOUT_FILENO;
}
-static void play(void *buf, int samples) {
+static int play(void *buf, int samples) {
char errorstring[1024];
int warned = 0;
int rc = write(fd, buf, samples * 4);
@@ -52,6 +52,7 @@ static void play(void *buf, int samples) {
warn("Error %d writing to stdout: \"%s\".", errno, errorstring);
warned = 1;
}
+ return rc;
}
static void stop(void) {
@@ -73,14 +74,14 @@ static void deinit(void) {
// don't close stdout
}
-static void help(void) { printf(" stdout takes no arguments\n"); }
-
audio_output audio_stdout = {.name = "stdout",
- .help = &help,
+ .help = NULL,
.init = &init,
.deinit = &deinit,
+ .prepare = NULL,
.start = &start,
.stop = &stop,
+ .is_running = NULL,
.flush = NULL,
.delay = NULL,
.play = &play,
diff --git a/common.c b/common.c
index e6974bfc..b097ddab 100644
--- a/common.c
+++ b/common.c
@@ -2,6 +2,7 @@
* Utility routines. This file is part of Shairport.
* Copyright (c) James Laird 2013
* The volume to attenuation function vol2attn copyright (c) Mike Brady 2014
+ * Further changes and additions (c) Mike Brady 2014 -- 2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -46,7 +47,7 @@
#include
#endif
-#ifdef HAVE_LIBSSL
+#ifdef CONFIG_OPENSSL
#include
#include
#include
@@ -54,7 +55,7 @@
#include
#endif
-#ifdef HAVE_LIBPOLARSSL
+#ifdef CONFIG_POLARSSL
#include "polarssl/ctr_drbg.h"
#include "polarssl/entropy.h"
#include
@@ -67,37 +68,100 @@
#endif
#endif
-#ifdef HAVE_LIBMBEDTLS
+#ifdef CONFIG_MBEDTLS
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include
#include
#include
#include
-
#endif
+#ifdef CONFIG_LIBDAEMON
#include
+#else
+#include
+#endif
#ifdef CONFIG_ALSA
void set_alsa_out_dev(char *);
#endif
+const char * sps_format_description_string_array[] = {"unknown", "S8", "U8" ,"S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid" };
+
+const char * sps_format_description_string(enum sps_format_t format) {
+ if ((format >= SPS_FORMAT_UNKNOWN) && (format <= SPS_FORMAT_AUTO))
+ return sps_format_description_string_array[format];
+ else
+ return sps_format_description_string_array[SPS_FORMAT_INVALID];
+}
+
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
static volatile int requested_connection_state_to_output = 1;
+// this stuff is to direct logging to syslog via libdaemon or directly
+// alternatively you can direct it to stderr using a command line option
+
+#ifdef CONFIG_LIBDAEMON
+static void (*sps_log)(int prio, const char *t, ...) = daemon_log;
+#else
+static void (*sps_log)(int prio, const char *t, ...) = syslog;
+#endif
+
+void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
+ char s[1024];
+ va_list args;
+ va_start(args, t);
+ vsnprintf(s, sizeof(s), t, args);
+ va_end(args);
+ fprintf(stderr,"%s\n",s);
+}
+
+void log_to_stderr() {
+ sps_log = do_sps_log;
+}
+
shairport_cfg config;
-int debuglev = 0;
+volatile int debuglev = 0;
sigset_t pselect_sigset;
+int usleep_uncancellable(useconds_t usec) {
+ int response;
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
+ response = usleep(usec);
+ pthread_setcancelstate(oldState, NULL);
+ return response;
+}
+
+static uint16_t UDPPortIndex = 0;
+
+void resetFreeUDPPort() {
+ debug(3, "Resetting UDP Port Suggestion to %u", config.udp_port_base);
+ UDPPortIndex = 0;
+}
+
+uint16_t nextFreeUDPPort() {
+ if (UDPPortIndex == 0)
+ UDPPortIndex = config.udp_port_base;
+ else if (UDPPortIndex == (config.udp_port_base + config.udp_port_range - 1))
+ UDPPortIndex = config.udp_port_base + 3; // avoid wrapping back to the first three, as they can
+ // be assigned by resetFreeUDPPort without checking
+ else
+ UDPPortIndex++;
+ return UDPPortIndex;
+}
+
int get_requested_connection_state_to_output() { return requested_connection_state_to_output; }
void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; }
void die(const char *format, ...) {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
char s[1024];
s[0] = 0;
uint64_t time_now = get_absolute_time_in_fp();
@@ -113,18 +177,20 @@ void die(const char *format, ...) {
va_end(args);
if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
- daemon_log(LOG_EMERG, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s);
+ sps_log(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s);
else if ((debuglev) && (config.debugger_show_relative_time))
- daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tsl, s);
+ sps_log(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s);
else if ((debuglev) && (config.debugger_show_elapsed_time))
- daemon_log(LOG_EMERG, "% 20.9f|*fatal error: %s", tss, s);
+ sps_log(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s);
else
- daemon_log(LOG_EMERG, "fatal error: %s", s);
- shairport_shutdown();
- exit(1);
+ sps_log(LOG_ERR, "fatal error: %s", s);
+ pthread_setcancelstate(oldState, NULL);
+ abort(); // exit() doesn't always work, by heaven.
}
void warn(const char *format, ...) {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
char s[1024];
s[0] = 0;
uint64_t time_now = get_absolute_time_in_fp();
@@ -138,20 +204,22 @@ void warn(const char *format, ...) {
va_start(args, format);
vsnprintf(s, sizeof(s), format, args);
va_end(args);
-
if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
- daemon_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s);
+ sps_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s);
else if ((debuglev) && (config.debugger_show_relative_time))
- daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s);
+ sps_log(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s);
else if ((debuglev) && (config.debugger_show_elapsed_time))
- daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s);
+ sps_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s);
else
- daemon_log(LOG_WARNING, "%s", s);
+ sps_log(LOG_WARNING, "%s", s);
+ pthread_setcancelstate(oldState, NULL);
}
void debug(int level, const char *format, ...) {
if (level > debuglev)
return;
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
char s[1024];
s[0] = 0;
uint64_t time_now = get_absolute_time_in_fp();
@@ -166,23 +234,41 @@ void debug(int level, const char *format, ...) {
vsnprintf(s, sizeof(s), format, args);
va_end(args);
if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
- daemon_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
+ sps_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
else if (config.debugger_show_relative_time)
- daemon_log(LOG_DEBUG, "% 20.9f|%s", tsl, s);
+ sps_log(LOG_DEBUG, "% 20.9f|%s", tsl, s);
else if (config.debugger_show_elapsed_time)
- daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s);
+ sps_log(LOG_DEBUG, "% 20.9f|%s", tss, s);
else
- daemon_log(LOG_DEBUG, "%s", s);
+ sps_log(LOG_DEBUG, "%s", s);
+ pthread_setcancelstate(oldState, NULL);
}
void inform(const char *format, ...) {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
char s[1024];
s[0] = 0;
+ uint64_t time_now = get_absolute_time_in_fp();
+ uint64_t time_since_start = time_now - fp_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
+ fp_time_at_last_debug_message = time_now;
+ uint64_t divisor = (uint64_t)1 << 32;
+ double tss = 1.0 * time_since_start / divisor;
+ double tsl = 1.0 * time_since_last_debug_message / divisor;
va_list args;
va_start(args, format);
vsnprintf(s, sizeof(s), format, args);
va_end(args);
- daemon_log(LOG_INFO, "%s", s);
+ if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
+ sps_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
+ else if ((debuglev) && (config.debugger_show_relative_time))
+ sps_log(LOG_INFO, "% 20.9f|%s", tsl, s);
+ else if ((debuglev) && (config.debugger_show_elapsed_time))
+ sps_log(LOG_INFO, "% 20.9f|%s", tss, s);
+ else
+ sps_log(LOG_INFO, "%s", s);
+ pthread_setcancelstate(oldState, NULL);
}
// The following two functions are adapted slightly and with thanks from Jonathan Leffler's sample
@@ -233,7 +319,7 @@ int mkpath(const char *path, mode_t mode) {
return (status);
}
-#ifdef HAVE_LIBMBEDTLS
+#ifdef CONFIG_MBEDTLS
char *base64_enc(uint8_t *input, int length) {
char *buf = NULL;
size_t dlen = 0;
@@ -286,7 +372,7 @@ uint8_t *base64_dec(char *input, int *outlen) {
}
#endif
-#ifdef HAVE_LIBPOLARSSL
+#ifdef CONFIG_POLARSSL
char *base64_enc(uint8_t *input, int length) {
char *buf = NULL;
size_t dlen = 0;
@@ -339,8 +425,10 @@ uint8_t *base64_dec(char *input, int *outlen) {
}
#endif
-#ifdef HAVE_LIBSSL
+#ifdef CONFIG_OPENSSL
char *base64_enc(uint8_t *input, int length) {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
BIO *bmem, *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
@@ -348,7 +436,7 @@ char *base64_enc(uint8_t *input, int length) {
b64 = BIO_push(b64, bmem);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_write(b64, input, length);
- BIO_flush(b64);
+ (void) BIO_flush(b64);
BIO_get_mem_ptr(b64, &bptr);
char *buf = (char *)malloc(bptr->length);
@@ -359,12 +447,15 @@ char *base64_enc(uint8_t *input, int length) {
buf[bptr->length - 1] = 0;
}
- BIO_free_all(bmem);
+ BIO_free_all(b64);
+ pthread_setcancelstate(oldState, NULL);
return buf;
}
uint8_t *base64_dec(char *input, int *outlen) {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
BIO *bmem, *b64;
int inlen = strlen(input);
@@ -377,7 +468,7 @@ uint8_t *base64_dec(char *input, int *outlen) {
BIO_write(bmem, input, inlen);
while (inlen++ & 3)
BIO_write(bmem, "=", 1);
- BIO_flush(bmem);
+ (void) BIO_flush(bmem);
int bufsize = strlen(input) * 3 / 4 + 1;
uint8_t *buf = malloc(bufsize);
@@ -385,9 +476,10 @@ uint8_t *base64_dec(char *input, int *outlen) {
nread = BIO_read(b64, buf, bufsize);
- BIO_free_all(bmem);
+ BIO_free_all(b64);
*outlen = nread;
+ pthread_setcancelstate(oldState, NULL);
return buf;
}
#endif
@@ -417,10 +509,11 @@ static char super_secret_key[] =
"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
"-----END RSA PRIVATE KEY-----\0";
-#ifdef HAVE_LIBSSL
+#ifdef CONFIG_OPENSSL
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
- static RSA *rsa = NULL;
-
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
+ RSA *rsa = NULL;
if (!rsa) {
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
@@ -438,11 +531,13 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
default:
die("bad rsa mode");
}
+ RSA_free(rsa);
+ pthread_setcancelstate(oldState, NULL);
return out;
}
#endif
-#ifdef HAVE_LIBMBEDTLS
+#ifdef CONFIG_MBEDTLS
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
mbedtls_pk_context pkctx;
mbedtls_rsa_context *trsa;
@@ -499,7 +594,7 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
}
#endif
-#ifdef HAVE_LIBPOLARSSL
+#ifdef CONFIG_POLARSSL
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
rsa_context trsa;
const char *pers = "rsa_encrypt";
@@ -554,7 +649,26 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
}
#endif
+int config_set_lookup_bool(config_t *cfg, char *where, int *dst) {
+ const char *str = 0;
+ if (config_lookup_string(cfg, where, &str)) {
+ if (strcasecmp(str, "no") == 0) {
+ (*dst) = 0;
+ return 1;
+ } else if (strcasecmp(str, "yes") == 0) {
+ (*dst) = 1;
+ return 1;
+ } else {
+ die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+}
+
void command_set_volume(double volume) {
+ // this has a cancellation point if waiting is enabled
if (config.cmd_set_volume) {
/*Spawn a child to run the program.*/
pid_t pid = fork();
@@ -582,7 +696,7 @@ void command_set_volume(double volume) {
execv(argV[0], argV);
warn("Execution of on-set-volume command \"%s\" failed to start", config.cmd_set_volume);
// debug(1, "Error executing on-set-volume command %s", config.cmd_set_volume);
- exit(127); /* only if execv fails */
+ exit(EXIT_FAILURE); /* only if execv fails */
}
}
@@ -601,6 +715,7 @@ void command_set_volume(double volume) {
}
void command_start(void) {
+ // this has a cancellation point if waiting is enabled or a response is awaited
if (config.cmd_start) {
pid_t pid;
int pipes[2];
@@ -636,14 +751,15 @@ void command_start(void) {
execv(argV[0], argV);
warn("Execution of on-start command failed to start");
debug(1, "Error executing on-start command %s", config.cmd_start);
- exit(127); /* only if execv fails */
+ exit(EXIT_FAILURE); /* only if execv fails */
}
} else {
if (config.cmd_blocking || config.cmd_start_returns_output) { /* pid!=0 means parent process
and if blocking is true, wait for
process to finish */
pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
- if (rc != pid) {
+ if ((rc != pid) && (errno != ECHILD)) {
+ // In this context, ECHILD means that the child process has already completed, I think!
warn("Execution of on-start command returned an error.");
debug(1, "on-start command %s finished with error %d", config.cmd_start, errno);
}
@@ -666,40 +782,54 @@ void command_start(void) {
}
}
}
+void command_execute(const char *command, const char *extra_argument, const int block) {
+ // this has a cancellation point if waiting is enabled
+ if (command) {
+ char new_command_buffer[1024];
+ char *full_command = (char *)command;
+ if (extra_argument != NULL) {
+ memset(new_command_buffer, 0, sizeof(new_command_buffer));
+ snprintf(new_command_buffer, sizeof(new_command_buffer), "%s %s", command, extra_argument);
+ full_command = new_command_buffer;
+ }
-void command_stop(void) {
- if (config.cmd_stop) {
/*Spawn a child to run the program.*/
pid_t pid = fork();
if (pid == 0) { /* child process */
int argC;
char **argV;
- // debug(1,"on-stop command found.");
- if (poptParseArgvString(config.cmd_stop, &argC, (const char ***)&argV) !=
+ if (poptParseArgvString(full_command, &argC, (const char ***)&argV) !=
0) // note that argV should be free()'d after use, but we expect this fork to exit
// eventually.
- debug(1, "Can't decipher on-stop command arguments");
+ debug(1, "Can't decipher command arguments in \"%s\".", full_command);
else {
- // debug(1,"Executing on-stop command %s",config.cmd_stop);
+ // debug(1,"Executing command %s",full_command);
execv(argV[0], argV);
- warn("Execution of on-stop command failed to start");
- debug(1, "Error executing on-stop command %s", config.cmd_stop);
- exit(127); /* only if execv fails */
+ warn("Execution of command \"%s\" failed to start", full_command);
+ debug(1, "Error executing command \"%s\".", full_command);
+ exit(EXIT_FAILURE); /* only if execv fails */
}
} else {
- if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
+ if (block) { /* pid!=0 means parent process and if blocking is true, wait for
process to finish */
pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
- if (rc != pid) {
- warn("Execution of on-stop command returned an error.");
- debug(1, "Stop command %s finished with error %d", config.cmd_stop, errno);
+ if ((rc != pid) && (errno != ECHILD)) {
+ // In this context, ECHILD means that the child process has already completed, I think!
+ warn("Execution of command \"%s\" returned an error.", full_command);
+ debug(1, "Command \"%s\" finished with error %d", full_command, errno);
}
}
- // debug(1,"Continue after on-stop command");
+ // debug(1,"Continue after on-unfixable command");
}
}
}
+void command_stop(void) {
+ // this has a cancellation point if waiting is enabled
+ if (config.cmd_stop)
+ command_execute(config.cmd_stop, "", config.cmd_blocking);
+}
+
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
uint32_t uatoi(const char *nptr) {
@@ -732,21 +862,17 @@ double flat_vol2attn(double vol, long max_db, long min_db) {
double vol2attn(double vol, long max_db, long min_db) {
-// We use a little coordinate geometry to build a transfer function from the volume passed in to the
-// device's dynamic range.
-// (See the diagram in the documents folder.)
-// The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out"
-// which will be from the bottom of the range to the top.
-// We build the transfer function from one or more lines. We characterise each line with two
-// numbers:
-// the first is where on x the line starts when y=0 (x can be from 0 to -30); the second is where on
-// y the line stops when when x is -30.
-// thus, if the line was characterised as {0,-30}, it would be an identity transfer.
-// Assuming, for example, a dynamic range of lv=-60 to hv=0
-// Typically we'll use three lines -- a three order transfer function
-// First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
-// Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
-// Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30
+ // We use a little coordinate geometry to build a transfer function from the volume passed in to
+ // the device's dynamic range. (See the diagram in the documents folder.) The x axis is the
+ // "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from
+ // the bottom of the range to the top. We build the transfer function from one or more lines. We
+ // characterise each line with two numbers: the first is where on x the line starts when y=0 (x
+ // can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the
+ // line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a
+ // dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer
+ // function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
+ // Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
+ // Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30
#define order 3
@@ -838,7 +964,8 @@ uint64_t get_absolute_time_in_fp() {
return time_now_fp;
}
-ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
+ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout) {
+ // timeout is in milliseconds
void *ibuf = (void *)buf;
size_t bytes_remaining = count;
int rc = 1;
@@ -847,7 +974,7 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
// check that we can do some writing
ufds[0].fd = fd;
ufds[0].events = POLLOUT;
- rc = poll(ufds, 1, 5000);
+ rc = poll(ufds, 1, timeout);
if (rc < 0) {
// debug(1, "non-blocking write error waiting for pipe to become ready for writing...");
} else if (rc == 0) {
@@ -858,20 +985,24 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
ssize_t bytes_written = write(fd, ibuf, bytes_remaining);
if (bytes_written == -1) {
// debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno));
- rc = -1;
+ rc = bytes_written; // to imitate the return from write()
} else {
ibuf += bytes_written;
bytes_remaining -= bytes_written;
}
}
}
- if (rc == 0)
+ if (rc > 0)
return count - bytes_remaining; // this is just to mimic a normal write/3.
else
return rc;
// return write(fd,buf,count);
}
+ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
+ return non_blocking_write_with_timeout(fd,buf,count,5000); // default is 5 seconds.
+}
+
/* from
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
*/
@@ -948,9 +1079,7 @@ uint64_t r64u() { return (ranval(&rx)); }
int64_t r64i() { return (ranval(&rx) >> 1); }
/* generate an array of 64-bit random numbers */
-const int ranarraylength = 1009; // these will be 8-byte numbers.
-
-uint64_t *ranarray;
+const int ranarraylength = 1009 * 203; // these will be 8-byte numbers.
int ranarraynext;
@@ -975,9 +1104,11 @@ uint64_t ranarrayval() {
void r64arrayinit() { ranarrayinit(); }
-uint64_t ranarray64u() { return (ranarrayval()); }
+// uint64_t ranarray64u() { return (ranarrayval()); }
+uint64_t ranarray64u() { return (ranval(&rx)); }
-int64_t ranarray64i() { return (ranarrayval() >> 1); }
+// int64_t ranarray64i() { return (ranarrayval() >> 1); }
+int64_t ranarray64i() { return (ranval(&rx) >> 1); }
uint32_t nctohl(const uint8_t *p) { // read 4 characters from *p and do ntohl on them
// this is to avoid possible aliasing violations
@@ -1020,6 +1151,8 @@ void sps_nanosleep(const time_t sec, const long nanosec) {
int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
const char *debugmessage, int debuglevel) {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
struct timespec tn;
clock_gettime(CLOCK_REALTIME, &tn);
uint64_t tnfpsec = tn.tv_sec;
@@ -1048,19 +1181,23 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
timeoutTime.tv_sec = time_then;
timeoutTime.tv_nsec = time_then_nsec;
-
+ int64_t start_time = get_absolute_time_in_fp();
int r = pthread_mutex_timedlock(mutex, &timeoutTime);
+ int64_t et = get_absolute_time_in_fp() - start_time;
- if ((r != 0) && (debugmessage != NULL)) {
+ if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
+ et = (et * 1000000) >> 32; // microseconds
char errstr[1000];
if (r == ETIMEDOUT)
debug(debuglevel,
- "waiting for a mutex, maximum expected time of %d microseconds exceeded \"%s\".",
- dally_time, debugmessage);
+ "timed out waiting for a mutex, having waiting %f seconds, with a maximum "
+ "waiting time of %d microseconds. \"%s\".",
+ (1.0 * et) / 1000000, dally_time, debugmessage);
else
debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
}
+ pthread_setcancelstate(oldState, NULL);
return r;
}
#endif
@@ -1068,17 +1205,20 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
const char *debugmessage, int debuglevel) {
- useconds_t time_to_wait = dally_time;
+ // this is not pthread_cancellation safe because is contains a cancellation point
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
+ int time_to_wait = dally_time;
int r = pthread_mutex_trylock(mutex);
while ((r == EBUSY) && (time_to_wait > 0)) {
- useconds_t st = time_to_wait;
+ int st = time_to_wait;
if (st > 1000)
st = 1000;
- sps_nanosleep(0, st * 1000);
+ sps_nanosleep(0, st * 1000); // this contains a cancellation point
time_to_wait -= st;
r = pthread_mutex_trylock(mutex);
}
- if ((r != 0) && (debugmessage != NULL)) {
+ if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
char errstr[1000];
if (r == EBUSY) {
debug(debuglevel,
@@ -1090,17 +1230,23 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
}
}
+ pthread_setcancelstate(oldState, NULL);
return r;
}
#endif
-int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *filename,
- const int line, int debuglevel) {
+int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexname,
+ const char *filename, const int line, int debuglevel) {
+ if ((debuglevel > debuglev) || (debuglevel == 0))
+ return pthread_mutex_lock(mutex);
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
uint64_t time_at_start = get_absolute_time_in_fp();
char dstring[1000];
memset(dstring, 0, sizeof(dstring));
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
- debug(3, "debug_mutex_lock at \"%s\".", dstring);
+ if (debuglevel != 0)
+ debug(3, "mutex_lock \"%s\" at \"%s\".", mutexname, dstring); // only if you really ask for it!
int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel);
if (result == ETIMEDOUT) {
result = pthread_mutex_lock(mutex);
@@ -1108,37 +1254,54 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
uint64_t divisor = (uint64_t)1 << 32;
double delay = 1.0 * time_delay / divisor;
debug(debuglevel,
- "debug_mutex_lock at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.", dstring,
- (1.0 * dally_time) / 1000000, delay);
+ "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.",
+ mutexname, dstring, (1.0 * dally_time) / 1000000, delay);
}
+ pthread_setcancelstate(oldState, NULL);
return result;
}
-int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int line,
- int debuglevel) {
+int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const char *filename,
+ const int line, int debuglevel) {
+ if ((debuglevel > debuglev) || (debuglevel == 0))
+ return pthread_mutex_unlock(mutex);
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
char dstring[1000];
char errstr[512];
memset(dstring, 0, sizeof(dstring));
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
- debug(debuglevel, "debug_mutex_unlock at \"%s\".", dstring);
+ debug(debuglevel, "mutex_unlock \"%s\" at \"%s\".", mutexname, dstring);
int r = pthread_mutex_unlock(mutex);
- if (r != 0)
- debug(1, "error %d: \"%s\" unlocking a mutex: \"%s\".", r,
- strerror_r(r, errstr, sizeof(errstr)), dstring);
+ if ((debuglevel != 0) && (r != 0))
+ debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r,
+ strerror_r(r, errstr, sizeof(errstr)), mutexname, dstring);
+ pthread_setcancelstate(oldState, NULL);
return r;
}
+void malloc_cleanup(void *arg) {
+ // debug(1, "malloc cleanup called.");
+ free(arg);
+}
+
+void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); }
+
char *get_version_string() {
- char *version_string = malloc(200);
+ char *version_string = malloc(1024);
if (version_string) {
strcpy(version_string, PACKAGE_VERSION);
-#ifdef HAVE_LIBMBEDTLS
- strcat(version_string, "-mbedTLS");
+
+#ifdef CONFIG_LIBDAEMON
+ strcat(version_string, "-libdaemon");
#endif
-#ifdef HAVE_LIBPOLARSSL
+#ifdef CONFIG_MBEDTLS
+ strcat(version_string, "-mbedTLS");
+#endif
+#ifdef CONFIG_POLARSSL
strcat(version_string, "-PolarSSL");
#endif
-#ifdef HAVE_LIBSSL
+#ifdef CONFIG_OPENSSL
strcat(version_string, "-OpenSSL");
#endif
#ifdef CONFIG_TINYSVCMDNS
@@ -1150,6 +1313,9 @@ char *get_version_string() {
#ifdef CONFIG_DNS_SD
strcat(version_string, "-dns_sd");
#endif
+#ifdef CONFIG_EXTERNAL_MDNS
+ strcat(version_string, "-external_mdns");
+#endif
#ifdef CONFIG_ALSA
strcat(version_string, "-ALSA");
#endif
@@ -1174,7 +1340,7 @@ char *get_version_string() {
#ifdef CONFIG_PIPE
strcat(version_string, "-pipe");
#endif
-#ifdef HAVE_LIBSOXR
+#ifdef CONFIG_SOXR
strcat(version_string, "-soxr");
#endif
#ifdef CONFIG_CONVOLUTION
@@ -1183,10 +1349,13 @@ char *get_version_string() {
#ifdef CONFIG_METADATA
strcat(version_string, "-metadata");
#endif
-#ifdef HAVE_DBUS
+#ifdef CONFIG_MQTT
+ strcat(version_string, "-mqtt");
+#endif
+#ifdef CONFIG_DBUS_INTERFACE
strcat(version_string, "-dbus");
#endif
-#ifdef HAVE_MPRIS
+#ifdef CONFIG_MPRIS_INTERFACE
strcat(version_string, "-mpris");
#endif
strcat(version_string, "-sysconfdir:");
@@ -1194,3 +1363,150 @@ char *get_version_string() {
}
return version_string;
}
+
+int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format,
+ int with_dither, int64_t random_number_in) {
+ // return the last random number used
+ // assuming the buffer has been assigned
+
+ int64_t previous_random_number = random_number_in;
+ char *p = outp;
+ size_t sample_number;
+ for (sample_number = 0; sample_number < number_of_frames * 2; sample_number++) {
+
+ int64_t hyper_sample = 0;
+ // 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 = 0;
+ switch (format) {
+ case SPS_FORMAT_S32:
+ case SPS_FORMAT_S32_LE:
+ case SPS_FORMAT_S32_BE:
+ dither_mask = (int64_t)1 << (64 + 1 - 32);
+ break;
+ case SPS_FORMAT_S24:
+ case SPS_FORMAT_S24_LE:
+ case SPS_FORMAT_S24_BE:
+ case SPS_FORMAT_S24_3LE:
+ case SPS_FORMAT_S24_3BE:
+ dither_mask = (int64_t)1 << (64 + 1 - 24);
+ break;
+ case SPS_FORMAT_S16:
+ case SPS_FORMAT_S16_LE:
+ case SPS_FORMAT_S16_BE:
+ 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.");
+ break;
+ case SPS_FORMAT_AUTO:
+ die("Unexpected SPS_FORMAT_AUTO while calculating dither mask.");
+ break;
+ case SPS_FORMAT_INVALID:
+ die("Unexpected SPS_FORMAT_INVALID while calculating dither mask.");
+ break;
+ }
+ dither_mask -= 1;
+ // int64_t r = r64i();
+ int64_t r = ranarray64i();
+
+ int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask);
+
+ // add dither if permitted -- no need to check for clipping, as the sample is, uh, zero
+
+ if (with_dither != 0)
+ hyper_sample += tpdf;
+
+ // move the result to the desired position in the int64_t
+ char *op = p;
+ int result; // this is the length of the sample
+
+ 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_LE:
+ hyper_sample >>= (64 - 16);
+ byt = (uint8_t)hyper_sample;
+ *op++ = byt;
+ byt = (uint8_t)(hyper_sample >> 8);
+ *op++ = byt;
+ result = 2;
+ break;
+ case SPS_FORMAT_S16_BE:
+ hyper_sample >>= (64 - 16);
+ byt = (uint8_t)(hyper_sample >> 8);
+ *op++ = byt;
+ byt = (uint8_t)hyper_sample;
+ *op++ = byt;
+ result = 2;
+ 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;
+ default:
+ result = 0; // stop a compiler warning
+ die("Unexpected SPS_FORMAT_UNKNOWN while outputting samples");
+ }
+ p += result;
+ previous_random_number = r;
+ }
+ // hack
+ // memset(outp,0,number_of_frames * 4);
+ return previous_random_number;
+}
diff --git a/common.h b/common.h
index 87493cb1..ae7f5cd3 100644
--- a/common.h
+++ b/common.h
@@ -2,6 +2,7 @@
#define _COMMON_H
#include
+#include
#include
#include
#include
@@ -22,13 +23,30 @@
#define SAFAMILY sa_family
#endif
-#if defined(HAVE_DBUS) || defined(HAVE_MPRIS)
+#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
enum dbus_session_type {
DBT_system = 0, // use the session bus
DBT_session, // use the system bus
} dbt_type;
#endif
+#define sps_extra_code_output_stalled 32768
+#define sps_extra_code_output_state_cannot_make_ready 32769
+
+// yeah/no/auto
+enum yna_type {
+ YNA_AUTO = -1,
+ YNA_NO = 0,
+ YNA_YES = 1
+} yna_type;
+
+// yeah/no/dont-care
+enum yndk_type {
+ YNDK_DONT_KNOW = -1,
+ YNDK_NO = 0,
+ YNDK_YES = 1
+} yndk_type;
+
enum endian_type {
SS_LITTLE_ENDIAN = 0,
SS_PDP_ENDIAN,
@@ -38,6 +56,7 @@ enum endian_type {
enum stuffing_type {
ST_basic = 0, // straight deletion or insertion of a frame in a 352-frame packet
ST_soxr, // use libsoxr to make a 352 frame packet one frame longer or shorter
+ ST_auto, // use soxr if compiled for it and if the soxr_index is low enough
} s_type;
enum playback_mode_type {
@@ -58,6 +77,12 @@ enum decoders_supported_type {
decoder_apple_alac,
} decoders_supported_type;
+enum disable_standby_mode_type {
+ disable_standby_off = 0,
+ disable_standby_auto,
+ disable_standby_always
+};
+
// the following enum is for the formats recognised -- currently only S16LE is recognised for input,
// so these are output only for the present
@@ -66,23 +91,37 @@ enum sps_format_t {
SPS_FORMAT_S8,
SPS_FORMAT_U8,
SPS_FORMAT_S16,
+ SPS_FORMAT_S16_LE,
+ SPS_FORMAT_S16_BE,
SPS_FORMAT_S24,
+ SPS_FORMAT_S24_LE,
+ SPS_FORMAT_S24_BE,
SPS_FORMAT_S24_3LE,
SPS_FORMAT_S24_3BE,
SPS_FORMAT_S32,
+ SPS_FORMAT_S32_LE,
+ SPS_FORMAT_S32_BE,
+ SPS_FORMAT_AUTO,
+ SPS_FORMAT_INVALID,
} sps_format_t;
+const char * sps_format_description_string(enum sps_format_t format);
+
typedef struct {
config_t *cfg;
+ int endianness;
double airplay_volume; // stored here for reloading when necessary
char *appName; // normally the app is called shairport-syn, but it may be symlinked
char *password;
char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running
// on host %h"
+
#ifdef CONFIG_PA
char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in
// the Sound Preferences in most desktop Linuxes.
-// Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
+ // Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
+
+ char *pa_sink; // the name (or id) of the sink that Shairport Sync will play on.
#endif
#ifdef CONFIG_METADATA
int metadata_enabled;
@@ -91,6 +130,22 @@ typedef struct {
int metadata_sockport;
size_t metadata_sockmsglength;
int get_coverart;
+#endif
+#ifdef CONFIG_MQTT
+ int mqtt_enabled;
+ char *mqtt_hostname;
+ int mqtt_port;
+ char *mqtt_username;
+ char *mqtt_password;
+ char *mqtt_capath;
+ char *mqtt_cafile;
+ char *mqtt_certfile;
+ char *mqtt_keyfile;
+ char *mqtt_topic;
+ int mqtt_publish_raw;
+ int mqtt_publish_parsed;
+ int mqtt_publish_cover;
+ int mqtt_enable_remote;
#endif
uint8_t hw_addr[6];
int port;
@@ -114,28 +169,33 @@ typedef struct {
char *mdns_name;
mdns_backend *mdns;
int buffer_start_fill;
- int64_t userSuppliedLatency; // overrides all other latencies -- use with caution
- int64_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual
- // total latency
+ uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution
+ uint32_t fixedLatencyOffset; // add this to all automatic latencies supplied to get the actual
+ // total latency
// the total latency will be limited to the min and max-latency values, if supplied
+#ifdef CONFIG_LIBDAEMON
int daemonise;
int daemonise_store_pid; // don't try to save a PID file
char *piddir;
char *computed_piddir; // the actual pid directory to create, if any
+ char *pidfile;
+#endif
int logOutputLevel; // log output level
int debugger_show_elapsed_time; // in the debug message, display the time since startup
int debugger_show_relative_time; // in the debug message, display the time since the last one
int statistics_requested, use_negotiated_latencies;
enum playback_mode_type playback_mode;
- char *cmd_start, *cmd_stop, *cmd_set_volume;
+ char *cmd_start, *cmd_stop, *cmd_set_volume, *cmd_unfixable;
+ char *cmd_active_start, *cmd_active_stop;
int cmd_blocking, cmd_start_returns_output;
double tolerance; // allow this much drift before attempting to correct it
enum stuffing_type packet_stuffing;
+ int soxr_delay_index;
+ int soxr_delay_threshold; // the soxr delay must be less or equal to this for soxr interpolation to be enabled under the auto setting
int decoders_supported;
int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by
// David Hammerton
- char *pidfile;
// char *logfile;
// char *errfile;
char *configfile;
@@ -145,14 +205,27 @@ typedef struct {
int interface_index; // only valid if the interface string is non-NULL
double audio_backend_buffer_desired_length; // this will be the length in seconds of the
// audio backend buffer -- the DAC buffer for ALSA
+ double audio_backend_buffer_interpolation_threshold_in_seconds; // below this, soxr interpolation
+ // will not occur -- it'll be
+ // basic interpolation instead.
+ double audio_backend_silence_threshold; // below this, silence will be added to the output buffer
+ double audio_backend_silence_scan_interval; // check the threshold this often
+
double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any
// fixed latency there might be in the audio path
double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
- uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
- // native range.
- enum sps_format_t output_format;
+ double active_state_timeout; // the amount of time from when play ends to when the system leaves
+ // into the "active" mode.
+ uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
+ // native range.
+ int volume_range_hw_priority; // when extending the volume range by combining sw and hw attenuators, lowering the volume, use all the hw attenuation before using
+ // sw attenuation
enum volume_control_profile_type volume_control_profile;
- int output_rate;
+
+ int output_format_auto_requested; // true if the configuration requests auto configuration
+ enum sps_format_t output_format;
+ int output_rate_auto_requested; // true if the configuration requests auto configuration
+ unsigned int output_rate;
#ifdef CONFIG_CONVOLUTION
int convolution;
@@ -164,14 +237,19 @@ typedef struct {
int loudness;
float loudness_reference_volume_db;
int alsa_use_hardware_mute;
-#if defined(HAVE_DBUS)
+ double alsa_maximum_stall_time;
+ enum disable_standby_mode_type disable_standby_mode;
+ volatile int keep_dac_busy;
+ enum yna_type use_precision_timing; // defaults to no
+
+#if defined(CONFIG_DBUS_INTERFACE)
enum dbus_session_type dbus_service_bus_type;
#endif
-#if defined(HAVE_MPRIS)
+#if defined(CONFIG_MPRIS_INTERFACE)
enum dbus_session_type mpris_service_bus_type;
#endif
-#ifdef HAVE_METADATA_HUB
+#ifdef CONFIG_METADATA_HUB
char *cover_art_cache_dir;
int scan_interval_when_active; // number of seconds between DACP server scans when playing
// something (1)
@@ -185,6 +263,11 @@ typedef struct {
int disable_resend_requests; // set this to stop resend request being made for missing packets
double diagnostic_drop_packet_fraction; // pseudo randomly drop this fraction of packets, for
// debugging. Currently audio packets only...
+#ifdef CONFIG_JACK
+ char *jack_client_name;
+ char *jack_autoconnect_pattern;
+#endif
+
} shairport_cfg;
uint32_t nctohl(const uint8_t *p); // read 4 characters from *p and do ntohl on them
@@ -192,12 +275,16 @@ uint16_t nctohs(const uint8_t *p); // read 2 characters from *p and do ntohs on
void memory_barrier();
+void log_to_stderr(); // call this to director logging to stderr;
+
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
int get_requested_connection_state_to_output();
void set_requested_connection_state_to_output(int v);
+ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout); // timeout in milliseconds
+
ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places
/* from
@@ -211,11 +298,24 @@ void r64init(uint64_t seed);
uint64_t r64u();
int64_t r64i();
+uint64_t *ranarray;
void r64arrayinit();
uint64_t ranarray64u();
int64_t ranarray64i();
-extern int debuglev;
+// if you are breaking in to a session, you need to avoid the ports of the current session
+// if you are law-abiding, then you can reuse the ports.
+// so, you can reset the free UDP ports minder when you're legit, and leave it otherwise
+
+// the downside of using different ports each time is that it might make the firewall
+// rules a bit more complex, as they need to allow more than the minimum three ports.
+// a range of 10 is suggested anyway
+
+void resetFreeUDPPort();
+uint16_t nextFreeUDPPort();
+
+volatile int debuglev;
+
void die(const char *format, ...);
void warn(const char *format, ...);
void inform(const char *format, ...);
@@ -244,20 +344,24 @@ uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
-long endianness;
uint32_t uatoi(const char *nptr);
+// this is for allowing us to cancel the whole program
+pthread_t main_thread_id;
+
shairport_cfg config;
config_t config_file_stuff;
+int config_set_lookup_bool(config_t *cfg, char *where, int *dst);
+
void command_start(void);
void command_stop(void);
+void command_execute(const char *command, const char *extra_argument, const int block);
void command_set_volume(double volume);
int mkpath(const char *path, mode_t mode);
void shairport_shutdown();
-// void shairport_startup_complete(void);
extern sigset_t pselect_sigset;
@@ -266,19 +370,30 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
const char *debugmessage, int debuglevel);
// wait for the specified time, checking every 20 milliseconds, and block if it can't acquire the
// lock
-int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *filename,
- const int line, int debuglevel);
+int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexName,
+ const char *filename, const int line, int debuglevel);
-#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, __FILE__, __LINE__, d)
+#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d)
-int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *filename, const int line,
- int debuglevel);
+int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexName, const char *filename,
+ const int line, int debuglevel);
-#define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, __FILE__, __LINE__, d)
+#define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, #mu, __FILE__, __LINE__, d)
+
+void pthread_cleanup_debug_mutex_unlock(void *arg);
+
+#define pthread_cleanup_debug_mutex_lock(mu, t, d) \
+ if (_debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d) == 0) \
+ pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock, (void *)mu)
char *get_version_string(); // mallocs a string space -- remember to free it afterwards
void sps_nanosleep(const time_t sec,
const long nanosec); // waits for this time, even through interruptions
+int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_format_t format,
+ int with_dither, int64_t random_number_in);
+
+void malloc_cleanup(void *arg);
+
#endif // _COMMON_H
diff --git a/configure.ac b/configure.ac
index 37f8a732..3df35d40 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,10 +2,11 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.2.2], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
+AC_PROG_RANLIB
# Derived from the Avahi configure.ac file
# Specifying the OS type, defaulting to linux.
@@ -32,25 +33,17 @@ if test "x${with_os}" = xopenbsd ; then
AC_CHECK_LIB([c],[clock_gettime], , AC_MSG_ERROR(libc needed))
fi
+AM_CONDITIONAL([BUILD_FOR_LINUX], [test "x${with_os}" = xlinux ])
AM_CONDITIONAL([BUILD_FOR_FREEBSD], [test "x${with_os}" = xfreebsd ])
-
AM_CONDITIONAL([BUILD_FOR_OPENBSD], [test "x${with_os}" = xopenbsd ])
+
##### Some build systems are not fully using pkg-config, so we can use the flag ${with_pkg_config} on a case-by-case basis
##### to control how to deal with them
AC_ARG_WITH([pkg_config],
[ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=yes])
-AC_MSG_RESULT(>>Including libdaemon)
-if test "x${with_pkg_config}" = xyes ; then
- PKG_CHECK_MODULES(
- [DAEMON], [libdaemon],
- [LIBS="${DAEMON_LIBS} ${LIBS}"])
-else
- AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed))
-fi
-
##### The following check for the pthreads library doesn't put the compiler into the correct pthread mode
##### so we add the -pthread compilation flag in AMFLAGS in the Makefile.am as well.
@@ -62,18 +55,15 @@ if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
[POPT], [popt],
[CFLAGS="${POPT_CFLAGS} ${CFLAGS}"
- LIBS="${POPT_LIBS} ${LIBS}"
- AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])])
+ LIBS="${POPT_LIBS} ${LIBS}"])
else
AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed))
- AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])
fi
AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], )
AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "xyes" ])
AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], )
-
AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "xyes" ])
AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], )
@@ -95,6 +85,13 @@ AC_ARG_WITH([freebsd-service],
[ --with-freebsd-service = install a FreeBSD startup script during a make install], , )
AM_CONDITIONAL([INSTALL_FREEBSD_SERVICE], [test "x$with_freebsd_service" = "xyes"])
+AC_ARG_WITH([cygwin-service],
+[ --with-cygwin-service = install a CYGWIN config script during a make install], , )
+AM_CONDITIONAL([INSTALL_CYGWIN_SERVICE], [test "x$with_cygwin_service" = "xyes"])
+
+AC_ARG_WITH([external-mdns],[ --with-external-mdns = support the use of 'avahi-publish-service' or 'mDNSPublish' to advertise the service on Bonjour/ZeroConf ],[ AC_MSG_RESULT(>>Including external mdns support) AC_DEFINE([CONFIG_EXTERNAL_MDNS], 1, [Needed by the compiler.]) ], )
+AM_CONDITIONAL([USE_EXTERNAL_MDNS], [test "x$with_external_mdns" = "xyes" ])
+
# Add the libconfig package
if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
@@ -111,19 +108,16 @@ AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "xyes"])
# Look for Apple ALAC flag
AC_ARG_WITH(apple-alac, [ --with-apple-alac = include support for the Apple ALAC decoder],
[AC_MSG_RESULT(>>Including the Apple ALAC Decoder)
- HAS_APPLE_ALAC=1
- AM_CONDITIONAL([USE_APPLE_ALAC], [test 0])
- AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Needed by the compiler.])
+ AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Include support for using the Apple ALAC Decoder])
+ REQUESTED_APPLE_ALAC=1
if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
[ALAC], [alac],
- [LIBS="${ALAC_LIBS} ${LIBS}"
- AC_DEFINE([HAVE_APPLE_ALAC],[1],[Define to 1 if you are using the Apple ALAC Decoder])])
+ [LIBS="${ALAC_LIBS} ${LIBS}"])
else
AC_CHECK_LIB([alac], [BitBufferInit], , AC_MSG_ERROR(Apple ALAC Decoder support requires the alac library!))
- AC_DEFINE([HAVE_APPLE_ALAC],[1],[Define to 1 if you have the Apple ALAC library])
fi ])
-AM_CONDITIONAL([USE_APPLE_ALAC], [test "x$HAS_APPLE_ALAC" = "x1"])
+AM_CONDITIONAL([USE_APPLE_ALAC], [test "x$REQUESTED_APPLE_ALAC" = "x1"])
# Look for piddir flag
AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory in which to write the PID file.], [
@@ -133,10 +127,26 @@ AC_ARG_WITH(piddir, [ --with-piddir= Specify a pathname to a directory
AC_MSG_ERROR(when you use the --with-piddir directive you must specify the pathname of the directory into which the PID file will be written)
fi
AC_MSG_RESULT(${with_piddir})
- HAS_CUSTOMPIDDIR=1
+ REQUESTED_CUSTOMPIDDIR=1
AC_SUBST(CUSTOM_PID_DIR,["${with_piddir}"])
- AC_DEFINE([USE_CUSTOM_PID_DIR],1,[Hook up special proc to libdaemon to point to this directory])], )
-AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$HAS_CUSTOMPIDDIR" = "x1"])
+ AC_DEFINE([DEFINED_CUSTOM_PID_DIR],1,[Hook up special proc to libdaemon to point to this directory])], )
+AM_CONDITIONAL([USE_CUSTOMPIDDIR], [test "x$REQUESTED_CUSTOMPIDDIR" = "x1"])
+
+
+# Look for libdaemon
+AC_ARG_WITH(libdaemon, [--with-libdaemon = include support for daemonising in non-systemd systems], REQUESTED_LIBDAEMON=1, )
+if test "x$REQUESTED_LIBDAEMON" = "x1"; then
+ AC_MSG_RESULT(>>Including libdaemon support for older systems)
+ AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon])
+ if test "x${with_pkg_config}" = xyes ; then
+ PKG_CHECK_MODULES(
+ [DAEMON], [libdaemon],
+ [LIBS="${DAEMON_LIBS} ${LIBS}"])
+ else
+ AC_CHECK_LIB([daemon],[daemon_fork], , AC_MSG_ERROR(libdaemon needed))
+ fi
+fi
+AM_CONDITIONAL([USE_LIBDAEMON], [test "x$REQUESTED_LIBDAEMON" = "x1"])
# Check --with-ssl=argument
AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=polarssl (deprecated) for encryption services ], [
@@ -146,19 +156,17 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
AC_MSG_ERROR(choose "openssl", "mbedtls" or "polarssl" encryption)
fi
if test "x${with_ssl}" = xopenssl ; then
+ AC_DEFINE([CONFIG_OPENSSL], 1, [Use the OpenSSL libraries for encryption and encoding and decoding])
if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
[SSL], [libssl,libcrypto],
- [LIBS="${SSL_LIBS} ${LIBS}"
- AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto])
- AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])])
+ [LIBS="${SSL_LIBS} ${LIBS}"])
else
AC_CHECK_LIB([crypto], [main], , AC_MSG_ERROR(libcrypto selected but the library cannot be found!))
AC_CHECK_LIB([ssl], [main], , AC_MSG_ERROR(libssl selected but the library cannot be found!))
- AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto])
- AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])
fi
elif test "x${with_ssl}" = xmbedtls ; then
+ AC_DEFINE([CONFIG_MBEDTLS], 1, [Use the mbed TLS libraries for encryption and encoding and decoding])
AC_CHECK_LIB([mbedtls],[mbedtls_ssl_init],,
[AC_MSG_ERROR([mbed tls support requires the mbedtls library -- libmbedtls-dev suggested],1)])
AC_CHECK_LIB([mbedcrypto], [mbedtls_entropy_func],,
@@ -166,6 +174,7 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
AC_CHECK_LIB([mbedx509], [mbedtls_pk_init],,
[AC_MSG_ERROR([mbed tls support requires the mbedx509 library -- libmbedx509-0 suggested],1)])
elif test "x${with_ssl}" = xpolarssl ; then
+ AC_DEFINE([CONFIG_POLARSSL], 1, [Use the PolarSSL libraries for encryption and encoding and decoding])
AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL is selected but the library cannot be found and is deprecated. Consider selecting mbed TLS instead using --with-ssl=mbedtls.))
else
AC_MSG_ERROR(unknown option "${with_ssl}"." Please choose with "openssl", "mbedtls" or "polarssl")
@@ -175,13 +184,26 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl, --with-ssl=mbedtls or --with-ssl=p
# Look for soxr flag
AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation], [
AC_MSG_RESULT(>>Including support for soxr-based interpolation)
- AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!))], )
+ AC_DEFINE([CONFIG_SOXR], 1, [Include support for using the SoX Resampler library for interpolation])
+ if test "x${with_pkg_config}" = xyes ; then
+ PKG_CHECK_MODULES(
+ [SOXR], [soxr],
+ [LIBS="${SOXR_LIBS} ${LIBS}"],
+ [AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!)])
+ else
+ AC_CHECK_LIB([avutil],[av_get_cpu_flags])
+ if test "x${ac_cv_lib_avutil_av_get_cpu_flags}" = xyes ; then
+ # soxr may link against libavutil, depending on the architecture, but for the sake of simplicity link with it if it is found
+ AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!), [-lavutil])
+ else
+ AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!))
+ fi
+ fi
+], )
-# Look for metadata flag -- set flag for conditional compilation
+# Look for metadata flag and resolve it further down the script
AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [
- AC_MSG_RESULT(>>Including metadata support)
- AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])], )
-AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"])
+ REQUESTED_METADATA=1], )
# What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation
# and the relevant link files are added.
@@ -189,64 +211,76 @@ AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"])
# Look for avahi flag
AC_ARG_WITH(avahi, [ --with-avahi = choose Avahi-based mDNS support], [
AC_MSG_RESULT(>>Including Avahi mDNS support)
- HAS_AVAHI=1
+ REQUESTED_AVAHI=1
AC_DEFINE([CONFIG_AVAHI], 1, [Needed by the compiler.])
AC_CHECK_LIB([avahi-client], [avahi_client_new], , AC_MSG_ERROR(Avahi support requires the avahi-client library!))
AC_CHECK_LIB([avahi-common],[avahi_strerror], , AC_MSG_ERROR(Avahi support requires the avahi-common library!))], )
-AM_CONDITIONAL([USE_AVAHI], [test "x$HAS_AVAHI" = "x1"])
+AM_CONDITIONAL([USE_AVAHI], [test "x$REQUESTED_AVAHI" = "x1"])
# Look for tinysvcmdns flag
AC_ARG_WITH(tinysvcmdns, [ --with-tinysvcmdns = choose tinysvcmdns-based mDNS support], [
AC_MSG_RESULT(>>Including tinysvcmdns mDNS support)
- HAS_TINYSVCMDNS=1
+ REQUESTED_TINYSVCMDNS=1
AC_DEFINE([CONFIG_TINYSVCMDNS], 1, [Needed by the compiler.])], )
-AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$HAS_TINYSVCMDNS" = "x1"])
+AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$REQUESTED_TINYSVCMDNS" = "x1"])
# Look for ALSA flag
AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)],
[AC_MSG_RESULT(>>Including an ALSA back end)
- HAS_ALSA=1
- AM_CONDITIONAL([USE_ALSA], [test 0])
+ REQUESTED_ALSA=1
AC_DEFINE([CONFIG_ALSA], 1, [Needed by the compiler.])
if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
[ALSA], [alsa],
- [LIBS="${ALSA_LIBS} ${LIBS}"
- AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])])
+ [LIBS="${ALSA_LIBS} ${LIBS}"])
else
AC_CHECK_LIB([asound], [snd_pcm_open], , AC_MSG_ERROR(ALSA support requires the asound library!))
- AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])
fi ])
-AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"])
+AM_CONDITIONAL([USE_ALSA], [test "x$REQUESTED_ALSA" = "x1"])
+
+# Look for jack flag
+AC_ARG_WITH(jack, [ --with-jack = include a Jack Audio Connection Kit (jack) backend], [
+ AC_MSG_RESULT(>>Including a Jack Audio Connection Kit (jack) back end)
+ REQUESTED_JACK=1
+ AC_DEFINE([CONFIG_JACK], 1, [Needed by the compiler.])
+ if test "x${with_pkg_config}" = xyes ; then
+ PKG_CHECK_MODULES(
+ [JACK], [jack],
+ [LIBS="${JACK_LIBS} ${LIBS}"],
+ [AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!)])
+ else
+ AC_CHECK_LIB([jack], [jack_client_open], , AC_MSG_ERROR(Jack Audio Connection Kit support requires the jack library -- libjack-dev suggested!))
+ fi ])
+AM_CONDITIONAL([USE_JACK], [test "x$REQUESTED_JACK" = "x1"])
# Look for SNDIO flag
AC_ARG_WITH(sndio, [ --with-sndio = choose SNDIO API support], [
AC_MSG_RESULT(>>Including a SNDIO back end)
- HAS_SNDIO=1
+ REQUESTED_SNDIO=1
AC_DEFINE([CONFIG_SNDIO], 1, [Needed by the compiler.])
AC_CHECK_LIB([sndio], [sio_open], , AC_MSG_ERROR(SNDIO support requires the sndio library -- libsndio-dev suggested))], )
-AM_CONDITIONAL([USE_SNDIO], [test "x$HAS_SNDIO" = "x1"])
+AM_CONDITIONAL([USE_SNDIO], [test "x$REQUESTED_SNDIO" = "x1"])
# Look for AO flag
AC_ARG_WITH(ao, [ --with-ao = choose AO (Audio Output?) API support. N.B. no synchronisation -- so underflow or overflow is inevitable!], [
AC_MSG_RESULT(>>Including an AO back end. N.B. no synchronisation -- so underflow or overflow is inevitable!)
- HAS_AO=1
+ REQUESTED_AO=1
AC_DEFINE([CONFIG_AO], 1, [Needed by the compiler.])
AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library -- libao-dev suggested))], )
-AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"])
+AM_CONDITIONAL([USE_AO], [test "x$REQUESTED_AO" = "x1"])
# Look for Soundio flag
AC_ARG_WITH(soundio, [ --with-soundio = choose soundio API support.], [
AC_MSG_RESULT(>>Including an soundio back end)
- HAS_SOUNDIO=1
+ REQUESTED_SOUNDIO=1
AC_DEFINE([CONFIG_SOUNDIO], 1, [Needed by the compiler.])
AC_CHECK_LIB([soundio], [soundio_create], , AC_MSG_ERROR(soundio support requires the soundio library -- libsoundio-dev suggested))], )
-AM_CONDITIONAL([USE_SOUNDIO], [test "x$HAS_SOUNDIO" = "x1"])
+AM_CONDITIONAL([USE_SOUNDIO], [test "x$REQUESTED_SOUNDIO" = "x1"])
# Look for pulseaudio flag
AC_ARG_WITH(pa, [ --with-pa = choose PulseAudio support.], [
AC_MSG_RESULT(>>Including a PulseAudio back end.)
- HAS_PA=1
+ REQUESTED_PA=1
AC_DEFINE([CONFIG_PA], 1, [Needed by the compiler.])
if test "x${with_pkg_config}" = xyes ; then
PKG_CHECK_MODULES(
@@ -256,74 +290,86 @@ AC_ARG_WITH(pa, [ --with-pa = choose PulseAudio support.], [
AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the libpulse library!))
AC_CHECK_LIB([pulse], [pa_stream_peek], , AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library.))
fi ])
-AM_CONDITIONAL([USE_PA], [test "x$HAS_PA" = "x1"])
+AM_CONDITIONAL([USE_PA], [test "x$REQUESTED_PA" = "x1"])
# Look for Convolution flag
AC_ARG_WITH(convolution, [ --with-convolution = choose audio DSP convolution support], [
AC_MSG_RESULT(>>Including convolution support)
- HAS_CONVOLUTION=1
+ REQUESTED_CONVOLUTION=1
AM_INIT_AUTOMAKE([subdir-objects])
AC_DEFINE([CONFIG_CONVOLUTION], 1, [Needed by the compiler.])
AC_CHECK_LIB([sndfile], [sf_open], , AC_MSG_ERROR(Convolution support requires the sndfile library!))], )
-AM_CONDITIONAL([USE_CONVOLUTION], [test "x$HAS_CONVOLUTION" = "x1"])
+AM_CONDITIONAL([USE_CONVOLUTION], [test "x$REQUESTED_CONVOLUTION" = "x1"])
# Look for dns_sd flag
AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [
AC_MSG_RESULT(>>Including dns_sd for mDNS support)
- HAS_DNS_SD=1
- AC_DEFINE([CONFIG_HAVE_DNS_SD_H], 1, [Needed by the compiler.])
+ REQUESTED_DNS_SD=1
+ AC_DEFINE([CONFIG_DNS_SD], 1, [Needed by the compiler.])
AC_SEARCH_LIBS([DNSServiceRefDeallocate], [dns_sd], , AC_MSG_ERROR(dns_sd support requires the dns_sd library!))], )
-AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"])
+AM_CONDITIONAL([USE_DNS_SD], [test "x$REQUESTED_DNS_SD" = "x1"])
# Look for dbus flag
AC_ARG_WITH(dbus-interface, [ --with-dbus-interface = include support for the native Shairport Sync D-Bus interface], [
AC_MSG_RESULT(>>Including dbus support)
- HAS_DBUS=1
- AC_DEFINE([HAVE_DBUS], 1, [Needed by the compiler.])
+ AC_DEFINE([CONFIG_DBUS_INTERFACE], 1, [Include support for the native Shairport Sync D-Bus interface])
+ REQUESTED_DBUS=1
PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support requires the glib 2.0 library -- libglib2.0-dev suggested!)])
], )
-AM_CONDITIONAL([USE_DBUS], [test "x$HAS_DBUS" = "x1"])
+AM_CONDITIONAL([USE_DBUS], [test "x$REQUESTED_DBUS" = "x1"])
# Look for dbus test client flag
AC_ARG_WITH(dbus-test-client, [ --with-dbus-test-client = compile dbus test client], [
AC_MSG_RESULT(>>Including dbus test client)
- HAS_DBUS_CLIENT=1
- AC_DEFINE([HAVE_DBUS_CLIENT], 1, [Needed by the compiler.])
+ REQUESTED_DBUS_CLIENT=1
PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus client support requires the glib 2.0 library -- libglib2.0-dev suggested!)])
], )
-AM_CONDITIONAL([USE_DBUS_CLIENT], [test "x$HAS_DBUS_CLIENT" = "x1"])
+AM_CONDITIONAL([USE_DBUS_CLIENT], [test "x$REQUESTED_DBUS_CLIENT" = "x1"])
# Look for mpris flag
-AC_ARG_WITH(mpris-interface, [ --with-mpris-interface = include support for a Shairport Sync D-Bus interface conforming to the MPRIS standard], [
+AC_ARG_WITH(mpris-interface, [ --with-mpris-interface = include support for a D-Bus interface conforming to the MPRIS standard], [
AC_MSG_RESULT(>>Including the MPRIS D-Bus Interface)
- HAS_MPRIS=1
- AC_DEFINE([HAVE_MPRIS], 1, [Needed by the compiler.])
+ AC_DEFINE([CONFIG_MPRIS_INTERFACE], 1, [Include support for a D-Bus interface conforming to the MPRIS standard])
+ REQUESTED_MPRIS=1
PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support for mpris requires the glib 2.0 library -- libglib2.0-dev suggested!)])
], )
-AM_CONDITIONAL([USE_MPRIS], [test "x$HAS_MPRIS" = "x1"])
+AM_CONDITIONAL([USE_MPRIS], [test "x$REQUESTED_MPRIS" = "x1"])
# Look for mpris test client flag
AC_ARG_WITH(mpris-test-client, [ --with-mpris-test-client = compile mpris test client], [
AC_MSG_RESULT(>>Including mpris test client)
- HAS_MPRIS_CLIENT=1
- AC_DEFINE([HAVE_MPRIS_CLIENT], 1, [Needed by the compiler.])
+ REQUESTED_MPRIS_CLIENT=1
PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(mpris client support requires the glib 2.0 library -- libglib2.0-dev suggested!)])
], )
-AM_CONDITIONAL([USE_MPRIS_CLIENT], [test "x$HAS_MPRIS_CLIENT" = "x1"])
+AM_CONDITIONAL([USE_MPRIS_CLIENT], [test "x$REQUESTED_MPRIS_CLIENT" = "x1"])
-#AM_CONDITIONAL([USE_DBUS_CORE_AND_DACP], [test "x$HAS_MPRIS" = "x1" -o "x$HAS_DBUS" = "x1"])
+# Look for mqtt flag
+AC_ARG_WITH(mqtt-client, [ --with-mqtt-client = include a client for MQTT -- the Message Queuing Telemetry Transport protocol], [
+ AC_DEFINE([CONFIG_MQTT], 1, [Include a client for MQTT, the Message Queuing Telemetry Transport protocol])
+ AC_MSG_RESULT(>>Including MQTT support)
+ REQUESTED_MQTT=1
+ AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], , AC_MSG_ERROR(MQTT support requires the mosquitto library -- libmosquitto-dev suggested!))
+ ],)
+AM_CONDITIONAL([USE_MQTT], [test "x$REQUESTED_MQTT" = "x1"])
-if test "x$HAS_MPRIS" = "x1" -o "x$HAS_DBUS" = "x1" ; then
- AC_MSG_RESULT(>>Including the metadata hub)
- HAS_METADATA_HUB=1
- AC_DEFINE([HAVE_METADATA_HUB], 1, [Needed by the compiler.])
- AC_MSG_RESULT(>>Including the DACP client)
- HAS_DACP_CLIENT=1
- AC_DEFINE([HAVE_DACP_CLIENT], 1, [Needed by the compiler.])
+if test "x$REQUESTED_MQTT" = "x1" && test "x$REQUESTED_AVAHI" != "x1"; then
+ AC_MSG_WARN([>>MQTT needs Avahi to allow remote control functionality. Only Metadata publishing will be supported])
fi
-AM_CONDITIONAL([USE_METADATA_HUB], [test "x$HAS_METADATA_HUB" = "x1"])
-AM_CONDITIONAL([USE_DACP_CLIENT], [test "x$HAS_DACP_CLIENT" = "x1"])
+if test "x$REQUESTED_MPRIS" = "x1" || test "x$REQUESTED_DBUS" = "x1" || test "x$REQUESTED_MQTT" = "x1"; then
+ AC_MSG_RESULT(>>Including extended metadata and DACP client support)
+ REQUESTED_EXTENDED_METADATA_SUPPORT=1
+ AC_DEFINE([CONFIG_METADATA_HUB], 1, [Needed by the compiler.])
+ AC_DEFINE([CONFIG_DACP_CLIENT], 1, [Needed by the compiler.])
+fi
+AM_CONDITIONAL([USE_METADATA_HUB], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"])
+AM_CONDITIONAL([USE_DACP_CLIENT], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"])
+
+if test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1" || test "x$REQUESTED_METADATA" = "x1"; then
+ AC_MSG_RESULT(>>Including metadata support)
+ AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])
+fi
+AM_CONDITIONAL([USE_METADATA], [test "x$REQUESTED_METADATA" = "x1"])
if test "x${with_systemd}" = xyes ; then
# Find systemd unit dir
@@ -340,9 +386,6 @@ if test "x${with_systemd}" = xyes ; then
[with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
[AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
- AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
-else
- AM_CONDITIONAL([HAVE_SYSTEMD], false)
fi
# Look for xmltoman
diff --git a/dacp.c b/dacp.c
index 53b76616..cea3e7f5 100644
--- a/dacp.c
+++ b/dacp.c
@@ -1,6 +1,6 @@
/*
* DACP protocol handler. This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2017
+ * Copyright (c) Mike Brady 2017 -- 2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -58,8 +58,10 @@ typedef struct {
void *port_monitor_private_storage;
} dacp_server_record;
+int dacp_monitor_initialised = 0;
pthread_t dacp_monitor_thread;
dacp_server_record dacp_server;
+void *mdns_dacp_monitor_private_storage_pointer;
// HTTP Response data/funcs (See the tinyhttp example.cpp file for more on this.)
struct HttpResponse {
@@ -90,8 +92,7 @@ void response_body(void *opaque, const char *data, int size) {
if (t)
response->body = t;
else {
- debug(1, "Can't allocate any more space for parser.\n");
- exit(-1);
+ die("dacp: can't allocate any more space for parser.");
}
}
memcpy(response->body + response->size, data, size);
@@ -110,7 +111,10 @@ static void response_code(void *opaque, int code) {
}
static const struct http_funcs responseFuncs = {
- response_realloc, response_body, response_header, response_code,
+ response_realloc,
+ response_body,
+ response_header,
+ response_code,
};
// static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -119,6 +123,30 @@ static pthread_mutex_t dacp_conversation_lock;
static pthread_mutex_t dacp_server_information_lock;
static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
+void addrinfo_cleanup(void *arg) {
+ // debug(1, "addrinfo cleanup called.");
+ struct addrinfo **info = (struct addrinfo **)arg;
+ freeaddrinfo(*info);
+}
+
+void mutex_lock_cleanup(void *arg) {
+ pthread_mutex_t *m = (pthread_mutex_t *)arg;
+ if (pthread_mutex_unlock(m))
+ debug(1, "Error releasing mutex.");
+}
+
+void connect_cleanup(void *arg) {
+ int *fd = (int *)arg;
+ // debug(2, "dacp_send_command: close socket %d.",*fd);
+ close(*fd);
+}
+
+void http_cleanup(void *arg) {
+ // debug(1, "http cleanup called.");
+ struct http_roundtripper *rt = (struct http_roundtripper *)arg;
+ http_free(rt);
+}
+
int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// will malloc space for the body or set it to NULL -- the caller should free it.
@@ -163,13 +191,14 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
response.code = 498; // Bad Address information for the DACP server
} else {
-
+ uint64_t start_time = get_absolute_time_in_fp();
+ pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
// only do this one at a time -- not sure it is necessary, but better safe than sorry
int mutex_reply = sps_pthread_mutex_timedlock(&dacp_conversation_lock, 2000000, command, 1);
// int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
if (mutex_reply == 0) {
- // debug(1,"dacp_conversation_lock acquired for command \"%s\".",command);
+ pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
@@ -178,18 +207,21 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1, "DACP socket could not be created -- error %d: \"%s\".",errno,strerror(errno));
response.code = 497; // Can't establish a socket to the DACP server
} else {
+ pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
+ // debug(2, "dacp_send_command: open socket %d.",sockfd);
+
struct timeval tv;
- tv.tv_sec = 2;
- tv.tv_usec = 0;
+ tv.tv_sec = 0;
+ tv.tv_usec = 80000;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "Error %d setting receive timeout for DACP service.", errno);
+ debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "Error %d setting send timeout for DACP service.", errno);
+ debug(1, "dacp_send_command: error %d setting send timeout.", errno);
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
- debug(3, "DACP connect failed with errno %d.", errno);
+ debug(3, "dacp_send_command: connect failed with errno %d.", errno);
response.code = 496; // Can't connect to the DACP server
} else {
// debug(1,"DACP connect succeeded.");
@@ -199,20 +231,32 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id);
// Send command
- // debug(1,"DACP connect message: \"%s\".",message);
- if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "Error %d setting send timeout for DACP service.", errno);
- if (send(sockfd, message, strlen(message), 0) != (ssize_t)strlen(message)) {
- // debug(1, "Send failed");
+ debug(3, "dacp_send_command: \"%s\".", command);
+ ssize_t wresp = send(sockfd, message, strlen(message), 0);
+ if (wresp == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring);
+ struct linger so_linger;
+ so_linger.l_onoff = 1; // "true"
+ so_linger.l_linger = 0;
+ int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
+ if (err)
+ debug(1, "Could not set the dacp socket to abort due to a write error on closing.");
+ }
+ if (wresp != (ssize_t)strlen(message)) {
+ // debug(1, "dacp_send_command: send failed.");
response.code = 493; // Client failed to send a message
} else {
response.body = malloc(2048); // it can resize this if necessary
response.malloced_size = 2048;
+ pthread_cleanup_push(malloc_cleanup, response.body);
struct http_roundtripper rt;
http_init(&rt, responseFuncs, &response);
+ pthread_cleanup_push(http_cleanup, &rt);
int needmore = 1;
int looperror = 0;
@@ -221,12 +265,24 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
while (needmore && !looperror) {
const char *data = buffer;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "Error %d setting receive timeout for DACP service.", errno);
- int ndata = recv(sockfd, buffer, sizeof(buffer), 0);
+ debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
+ ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0);
// debug(3, "Received %d bytes: \"%s\".", ndata, buffer);
if (ndata <= 0) {
- debug(1, "dacp_send_command -- error receiving response for command \"%s\".",
- command);
+ if (ndata == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,
+ (char *)errorstring);
+ struct linger so_linger;
+ so_linger.l_onoff = 1; // "true"
+ so_linger.l_linger = 0;
+ int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
+ if (err)
+ debug(1,
+ "Could not set the dacp socket to abort due to a read error on closing.");
+ }
+
free(response.body);
response.body = NULL;
response.malloced_size = 0;
@@ -244,28 +300,41 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
}
if (http_iserror(&rt)) {
- debug(1, "Error parsing data.");
+ debug(3, "dacp_send_command: error parsing data.");
free(response.body);
response.body = NULL;
response.malloced_size = 0;
response.size = 0;
}
// debug(1,"Size of response body is %d",response.size);
- http_free(&rt);
+ pthread_cleanup_pop(1); // this should call http_cleanup
+ // http_free(&rt);
+ pthread_cleanup_pop(
+ 0); // this should *not* free the malloced buffer -- just pop the malloc cleanup
}
}
- close(sockfd);
- // debug(1,"DACP socket closed.");
+ pthread_cleanup_pop(1); // this should close the socket
+ // close(sockfd);
+ // debug(1,"DACP socket closed.");
}
- pthread_mutex_unlock(&dacp_conversation_lock);
+ pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
+ // pthread_mutex_unlock(&dacp_conversation_lock);
// debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size);
// debug(1,"dacp_conversation_lock released.");
} else {
- debug(3, "Could not acquire a lock on the dacp transmit/receive section when attempting to "
- "send the command \"%s\". Possible timeout?",
+ debug(3,
+ "dacp_send_command: could not acquire a lock on the dacp transmit/receive section "
+ "when attempting to "
+ "send the command \"%s\". Possible timeout?",
command);
response.code = 494; // This client is already busy
}
+ pthread_cleanup_pop(1); // this should free the addrinfo
+ // freeaddrinfo(res);
+ uint64_t et = get_absolute_time_in_fp() - start_time;
+ et = (et * 1000000) >> 32; // microseconds
+ debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
+ (1.0 * et) / 1000000, response.code, command);
}
*body = response.body;
*bodysize = response.size;
@@ -290,12 +359,10 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) {
// as the conn's connection number
// this is to signify that the player has stopped, but only if another thread (with a different
// index) hasn't already taken over the dacp service
- sps_pthread_mutex_timedlock(
- &dacp_server_information_lock, 500000,
- "set_dacp_server_information couldn't get DACP server information lock in 0.5 second!.", 2);
+ debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
if (dacp_server.players_connection_thread_index == conn->connection_number)
dacp_server.players_connection_thread_index = 0;
- pthread_mutex_unlock(&dacp_server_information_lock);
+ debug_mutex_unlock(&dacp_server_information_lock, 3);
}
// this will be running on the thread of its caller, not of the conversation thread...
@@ -305,14 +372,12 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) {
// Thus, we can keep the DACP port that might have previously been discovered
void set_dacp_server_information(rtsp_conn_info *conn) {
// debug(1, "set_dacp_server_information");
- sps_pthread_mutex_timedlock(
- &dacp_server_information_lock, 500000,
- "set_dacp_server_information couldn't get DACP server information lock in 0.5 second!.", 2);
+ debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
dacp_server.players_connection_thread_index = conn->connection_number;
if ((conn->dacp_id == NULL) || (strcmp(conn->dacp_id, dacp_server.dacp_id) != 0)) {
if (conn->dacp_id)
- strncpy(dacp_server.dacp_id, conn->dacp_id, sizeof(dacp_server.dacp_id));
+ strncpy(dacp_server.dacp_id, conn->dacp_id, sizeof(dacp_server.dacp_id)-1);
else
dacp_server.dacp_id[0] = '\0';
dacp_server.port = 0;
@@ -320,14 +385,10 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
dacp_server.connection_family = conn->connection_ip_family;
dacp_server.scope_id = conn->self_scope_id;
strncpy(dacp_server.ip_string, conn->client_ip_string, INET6_ADDRSTRLEN);
- debug(2, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
+ debug(3, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
dacp_server.ip_string, dacp_server.dacp_id);
- if (dacp_server.port_monitor_private_storage) // if there's is a monitor already active...
- mdns_dacp_dont_monitor(dacp_server.port_monitor_private_storage); // let it go.
- dacp_server.port_monitor_private_storage =
- mdns_dacp_monitor(dacp_server.dacp_id); // create a new one for us if a DACP-ID is provided,
- // otherwise will return a NULL
+ mdns_dacp_monitor_set_id(dacp_server.dacp_id);
metadata_hub_modify_prolog();
int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
@@ -353,26 +414,25 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
}
dacp_server.active_remote_id = conn->dacp_active_remote; // even if the dacp_id remains the same,
// the active remote will change.
- debug(2, "set_dacp_server_information set active-remote id to %" PRIu32 ".",
+ debug(3, "set_dacp_server_information set active-remote id to %" PRIu32 ".",
dacp_server.active_remote_id);
pthread_cond_signal(&dacp_server_information_cv);
- pthread_mutex_unlock(&dacp_server_information_lock);
+ debug_mutex_unlock(&dacp_server_information_lock, 3);
}
void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
- debug(2, "dacp_monitor_port_update_callback with Remote ID \"%s\" and port number %d.", dacp_id,
- port);
- sps_pthread_mutex_timedlock(
- &dacp_server_information_lock, 500000,
- "dacp_monitor_port_update_callback couldn't get DACP server information lock in 0.5 second!.",
- 2);
+ debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
+ debug(3,
+ "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port "
+ "number %d.",
+ dacp_id, dacp_server.dacp_id, port);
if (strcmp(dacp_id, dacp_server.dacp_id) == 0) {
dacp_server.port = port;
if (port == 0)
dacp_server.scan_enable = 0;
else {
dacp_server.scan_enable = 1;
- debug(2, "dacp_monitor_port_update_callback enables scan");
+ // debug(2, "dacp_monitor_port_update_callback enables scan");
}
// metadata_hub_modify_prolog();
// int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
@@ -382,8 +442,14 @@ void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
debug(1, "dacp port monitor reporting on an out-of-use remote.");
}
pthread_cond_signal(&dacp_server_information_cv);
+ debug_mutex_unlock(&dacp_server_information_lock, 3);
+}
+
+void dacp_monitor_thread_code_cleanup(__attribute__((unused)) void *arg) {
+ // debug(1, "dacp_monitor_thread_code_cleanup called.");
pthread_mutex_unlock(&dacp_server_information_lock);
}
+
void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
int scan_index = 0;
// char server_reply[10000];
@@ -397,14 +463,18 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
sps_pthread_mutex_timedlock(
&dacp_server_information_lock, 500000,
"dacp_monitor_thread_code couldn't get DACP server information lock in 0.5 second!.", 2);
+ int32_t the_volume;
+
+ pthread_cleanup_push(dacp_monitor_thread_code_cleanup, NULL);
if (dacp_server.scan_enable == 0) {
metadata_hub_modify_prolog();
int ch = (metadata_store.dacp_server_active != 0) ||
(metadata_store.advanced_dacp_server_active != 0);
metadata_store.dacp_server_active = 0;
metadata_store.advanced_dacp_server_active = 0;
- debug(2, "setting dacp_server_active and advanced_dacp_server_active to 0 with an update "
- "flag value of %d",
+ debug(2,
+ "setting dacp_server_active and advanced_dacp_server_active to 0 with an update "
+ "flag value of %d",
ch);
metadata_hub_modify_epilog(ch);
while (dacp_server.scan_enable == 0) {
@@ -416,7 +486,6 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
idle_scan_count = 0;
}
scan_index++;
- int32_t the_volume;
result = dacp_get_volume(&the_volume); // just want the http code
if ((result == 496) || (result == 403) || (result == 501)) {
@@ -435,10 +504,12 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
if ((bad_result_count == config.scan_max_bad_response_count) ||
(idle_scan_count == config.scan_max_inactive_count)) {
- debug(1, "DACP server status scanning stopped.");
+ debug(2, "DACP server status scanning stopped.");
dacp_server.scan_enable = 0;
}
- pthread_mutex_unlock(&dacp_server_information_lock);
+ pthread_cleanup_pop(1);
+
+ // pthread_mutex_unlock(&dacp_server_information_lock);
// debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
// dacp_server.ip_string, dacp_server.port, scan_index);
@@ -764,7 +835,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
sleep(config.scan_interval_when_inactive);
}
}
- debug(1, "DACP monitor thread exiting.");
+ debug(1, "DACP monitor thread exiting -- should never happen.");
pthread_exit(NULL);
}
@@ -787,6 +858,8 @@ void dacp_monitor_start() {
rc = pthread_mutex_init(&dacp_conversation_lock, &mta);
if (rc)
debug(1, "Error creating the DACP Conversation Lock Mutex Init");
+ // else
+ // debug(1, "DACP Conversation Lock Mutex Init");
rc = pthread_mutexattr_destroy(&mta);
if (rc)
@@ -813,7 +886,20 @@ void dacp_monitor_start() {
debug(1, "Error creating the DACP Server Information Lock Attr Destroy");
memset(&dacp_server, 0, sizeof(dacp_server_record));
+
pthread_create(&dacp_monitor_thread, NULL, dacp_monitor_thread_code, NULL);
+ dacp_monitor_initialised = 1;
+}
+
+void dacp_monitor_stop() {
+ if (dacp_monitor_initialised) { // only if it's been started and initialised
+ debug(2, "dacp_monitor_stop");
+ pthread_cancel(dacp_monitor_thread);
+ pthread_join(dacp_monitor_thread, NULL);
+ pthread_mutex_destroy(&dacp_server_information_lock);
+ debug(3, "DACP Conversation Lock Mutex Destroyed");
+ pthread_mutex_destroy(&dacp_conversation_lock);
+ }
}
uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
@@ -857,10 +943,14 @@ int dacp_get_client_volume(int32_t *result) {
debug(1, "Too short a response from getproperty?properties=dmcp.volume");
}
// debug(1, "Overall Volume is %d.", overall_volume);
+ }
+
+ if (server_reply) {
+ // debug(1, "Freeing response memory.");
free(server_reply);
- } /* else {
- debug(1, "Unexpected response %d to dacp volume control request", response);
- } */
+ server_reply = NULL;
+ }
+
if (result) {
*result = overall_volume;
// debug(1,"dacp_get_client_volume returns: %" PRId32 ".",overall_volume);
@@ -914,12 +1004,13 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
sp -= item_size;
le -= 8;
speaker_index++;
- if (speaker_index == max_size_of_array)
+ if (speaker_index == max_size_of_array) {
return 413; // Payload Too Large -- too many speakers
+ }
speaker_info[speaker_index].active = 0;
speaker_info[speaker_index].speaker_number = 0;
speaker_info[speaker_index].volume = 0;
- speaker_info[speaker_index].name = NULL;
+ speaker_info[speaker_index].name[0] = '\0';
} else {
le -= item_size + 8;
char *t;
@@ -929,8 +1020,10 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
switch (type) {
case 'minm':
t = sp - item_size;
- speaker_info[speaker_index].name = strndup(t, item_size);
- // debug(1," \"%s\"",speaker_info[speaker_index].name);
+ strncpy((char *)&speaker_info[speaker_index].name, t,
+ sizeof(speaker_info[speaker_index].name));
+ speaker_info[speaker_index].name[sizeof(speaker_info[speaker_index].name) - 1] =
+ '\0'; // just in case
break;
case 'cmvo':
t = sp - item_size;
@@ -995,7 +1088,12 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
free(server_reply);
server_reply = NULL;
} else {
- debug(1, "Unexpected response %d to dacp speakers request", response);
+ // debug(1, "Unexpected response %d to dacp speakers request", response);
+ if (server_reply) {
+ debug(1, "Freeing response memory.");
+ free(server_reply);
+ server_reply = NULL;
+ }
}
if (actual_speaker_count)
*actual_speaker_count = speaker_count;
diff --git a/dacp.h b/dacp.h
index 09dd477d..ce1e1278 100644
--- a/dacp.h
+++ b/dacp.h
@@ -10,10 +10,11 @@ typedef struct dacp_speaker_stuff {
int64_t speaker_number;
int active;
int32_t volume;
- char *name; // this is really just for debugging
+ char name[128]; // this is really just for debugging
} dacp_spkr_stuff;
void dacp_monitor_start();
+void dacp_monitor_stop();
uint32_t dacp_tlv_crawl(
char **p,
@@ -26,6 +27,9 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_array, int max_size_of_array,
void set_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread that
// the dacp server information has been set
// or changed
+void relinquish_dacp_server_information(rtsp_conn_info *conn); // tell the DACP conversation thread
+ // that the player thread is no
+ // longer associated with it.
void dacp_monitor_port_update_callback(
char *dacp_id, uint16_t port); // a callback to say the port is no longer in use
int send_simple_dacp_command(const char *command);
diff --git a/dbus-service.c b/dbus-service.c
index ebcd3371..47c5b846 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -15,10 +15,14 @@
#include "dbus-service.h"
+int service_is_running = 0;
+
ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL;
ShairportSyncRemoteControl *shairportSyncRemoteControlSkeleton = NULL;
ShairportSyncAdvancedRemoteControl *shairportSyncAdvancedRemoteControlSkeleton = NULL;
+guint ownerID = 0;
+
void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
char response[100];
const char *th;
@@ -43,16 +47,15 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairport_sync_advanced_remote_control_set_available(shairportSyncAdvancedRemoteControlSkeleton,
FALSE);
}
-
+
if (argc->progress_string) {
- // debug(1, "Check progress string");
- th = shairport_sync_remote_control_get_progress_string(
- shairportSyncRemoteControlSkeleton);
- if ((th == NULL) || (strcasecmp(th, argc->progress_string) != 0)) {
- // debug(1, "Progress string should be changed");
- shairport_sync_remote_control_set_progress_string(
- shairportSyncRemoteControlSkeleton, argc->progress_string);
- }
+ // debug(1, "Check progress string");
+ th = shairport_sync_remote_control_get_progress_string(shairportSyncRemoteControlSkeleton);
+ if ((th == NULL) || (strcasecmp(th, argc->progress_string) != 0)) {
+ // debug(1, "Progress string should be changed");
+ shairport_sync_remote_control_set_progress_string(shairportSyncRemoteControlSkeleton,
+ argc->progress_string);
+ }
}
switch (argc->player_state) {
@@ -162,7 +165,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
if ((argc->track_metadata) && (argc->track_metadata->item_id)) {
char trackidstring[128];
// debug(1, "Set ID using mper ID: \"%u\".",argc->item_id);
- snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u", argc->track_metadata->item_id);
+ snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
+ argc->track_metadata->item_id);
GVariant *trackid = g_variant_new("o", trackidstring);
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
}
@@ -376,14 +380,27 @@ gboolean notify_verbosity_callback(ShairportSyncDiagnostics *skeleton,
return TRUE;
}
+gboolean notify_disable_standby_callback(ShairportSync *skeleton,
+ __attribute__((unused)) gpointer user_data) {
+ // debug(1, "\"notify_disable_standby_callback\" called.");
+ if (shairport_sync_get_disable_standby(skeleton)) {
+ debug(1, ">> activating disable standby");
+ config.keep_dac_busy = 1;
+ } else {
+ debug(1, ">> deactivating disable standby");
+ config.keep_dac_busy = 0;
+ }
+ return TRUE;
+}
+
gboolean notify_loudness_filter_active_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
- debug(1, "\"notify_loudness_filter_active_callback\" called.");
+ // debug(1, "\"notify_loudness_filter_active_callback\" called.");
if (shairport_sync_get_loudness_filter_active(skeleton)) {
- debug(1, "activating loudness filter");
+ debug(1, ">> activating loudness filter");
config.loudness = 1;
} else {
- debug(1, "deactivating loudness filter");
+ debug(1, ">> deactivating loudness filter");
config.loudness = 0;
}
return TRUE;
@@ -393,19 +410,62 @@ gboolean notify_loudness_threshold_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
gdouble th = shairport_sync_get_loudness_threshold(skeleton);
if ((th <= 0.0) && (th >= -100.0)) {
- debug(1, "Setting loudness threshhold to %f.", th);
+ debug(1, ">> setting loudness threshhold to %f.", th);
config.loudness_reference_volume_db = th;
} else {
- debug(1, "Invalid loudness threshhold: %f. Ignored.", th);
+ debug(1, ">> invalid loudness threshhold: %f. Ignored.", th);
shairport_sync_set_loudness_threshold(skeleton, config.loudness_reference_volume_db);
}
return TRUE;
}
+gboolean notify_drift_tolerance_callback(ShairportSync *skeleton,
+ __attribute__((unused)) gpointer user_data) {
+ gdouble dt = shairport_sync_get_drift_tolerance(skeleton);
+ if ((dt >= 0.0) && (dt <= 2.0)) {
+ debug(1, ">> setting drift tolerance to %f seconds", dt);
+ config.tolerance = dt;
+ } else {
+ debug(1, ">> invalid drift tolerance: %f seconds. Ignored.", dt);
+ shairport_sync_set_drift_tolerance(skeleton, config.tolerance);
+ }
+ return TRUE;
+}
+
+gboolean notify_disable_standby_mode_callback(ShairportSync *skeleton,
+ __attribute__((unused)) gpointer user_data) {
+ char *th = (char *)shairport_sync_get_disable_standby_mode(skeleton);
+ if ((strcasecmp(th, "no") == 0) || (strcasecmp(th, "off") == 0) || (strcasecmp(th, "never") == 0)) {
+ config.disable_standby_mode = disable_standby_off;
+ config.keep_dac_busy = 0;
+ } else if ((strcasecmp(th, "yes") == 0) || (strcasecmp(th, "on") == 0) || (strcasecmp(th, "always") == 0)) {
+ config.disable_standby_mode = disable_standby_always;
+ config.keep_dac_busy = 1;
+ } else if (strcasecmp(th, "auto") == 0)
+ config.disable_standby_mode = disable_standby_auto;
+ else {
+ warn("An unrecognised disable_standby_mode: \"%s\" was requested via D-Bus interface.", th);
+ switch (config.disable_standby_mode) {
+ case disable_standby_off:
+ shairport_sync_set_disable_standby_mode(skeleton, "off");
+ break;
+ case disable_standby_always:
+ shairport_sync_set_disable_standby_mode(skeleton, "always");
+ break;
+ case disable_standby_auto:
+ shairport_sync_set_disable_standby_mode(skeleton, "auto");
+ break;
+ default:
+ break;
+ }
+ }
+ return TRUE;
+}
+
gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
-#ifdef HAVE_APPLE_ALAC
+#ifdef CONFIG_APPLE_ALAC
if (strcasecmp(th, "hammerton") == 0)
config.use_apple_decoder = 0;
else if (strcasecmp(th, "apple") == 0)
@@ -436,11 +496,13 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
gboolean notify_interpolation_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
char *th = (char *)shairport_sync_get_interpolation(skeleton);
-#ifdef HAVE_LIBSOXR
+#ifdef CONFIG_SOXR
if (strcasecmp(th, "basic") == 0)
config.packet_stuffing = ST_basic;
else if (strcasecmp(th, "soxr") == 0)
config.packet_stuffing = ST_soxr;
+ else if (strcasecmp(th, "auto") == 0)
+ config.packet_stuffing = ST_auto;
else {
warn("An unrecognised interpolation method: \"%s\" was requested via the D-Bus interface.", th);
switch (config.packet_stuffing) {
@@ -450,6 +512,9 @@ gboolean notify_interpolation_callback(ShairportSync *skeleton,
case ST_soxr:
shairport_sync_set_interpolation(skeleton, "soxr");
break;
+ case ST_auto:
+ shairport_sync_set_interpolation(skeleton, "auto");
+ break;
default:
debug(1, "This should never happen!");
shairport_sync_set_interpolation(skeleton, "basic");
@@ -544,6 +609,18 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto
return TRUE;
}
+static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
+ __attribute__((unused)) const gchar *command,
+ __attribute__((unused)) gpointer user_data) {
+ debug(1, "quit requested (native interface)");
+ if (main_thread_id)
+ debug(1, "Cancelling main thread results in %d.", pthread_cancel(main_thread_id));
+ else
+ debug(1, "Main thread ID is NULL.");
+ shairport_sync_complete_quit(skeleton, invocation);
+ return TRUE;
+}
+
static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
const gchar *command,
__attribute__((unused)) gpointer user_data) {
@@ -582,12 +659,20 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
G_CALLBACK(notify_interpolation_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::alacdecoder",
G_CALLBACK(notify_alacdecoder_callback), NULL);
- g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile",
+ g_signal_connect(shairportSyncSkeleton, "notify::disable-standby-mode",
+ G_CALLBACK(notify_disable_standby_mode_callback), NULL);
+ g_signal_connect(shairportSyncSkeleton, "notify::volume-control-profile",
G_CALLBACK(notify_volume_control_profile_callback), NULL);
+ g_signal_connect(shairportSyncSkeleton, "notify::disable-standby",
+ G_CALLBACK(notify_disable_standby_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active",
G_CALLBACK(notify_loudness_filter_active_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold",
G_CALLBACK(notify_loudness_threshold_callback), NULL);
+ g_signal_connect(shairportSyncSkeleton, "notify::drift-tolerance",
+ G_CALLBACK(notify_drift_tolerance_callback), NULL);
+
+ g_signal_connect(shairportSyncSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL);
g_signal_connect(shairportSyncSkeleton, "handle-remote-command",
G_CALLBACK(on_handle_remote_command), NULL);
@@ -644,23 +729,62 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
shairport_sync_set_loudness_threshold(SHAIRPORT_SYNC(shairportSyncSkeleton),
config.loudness_reference_volume_db);
+ shairport_sync_set_drift_tolerance(SHAIRPORT_SYNC(shairportSyncSkeleton), config.tolerance);
-#ifdef HAVE_APPLE_ALAC
- if (config.use_apple_decoder == 0)
+#ifdef CONFIG_APPLE_ALAC
+ if (config.use_apple_decoder == 0) {
shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton");
- else
+ debug(1, ">> ALACDecoder set to \"hammerton\"");
+ } else {
shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "apple");
+ debug(1, ">> ALACDecoder set to \"apple\"");
+ }
#else
shairport_sync_set_alacdecoder(SHAIRPORT_SYNC(shairportSyncSkeleton), "hammerton");
+ debug(1, ">> ALACDecoder set to \"hammerton\"");
+
#endif
-#ifdef HAVE_SOXR
- if (config.packet_stuffing == ST_basic)
+ shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
+ debug(1, ">> Active set to \"false\"");
+
+ switch (config.disable_standby_mode) {
+ case disable_standby_off:
+ shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "off");
+ debug(1, ">> disable standby mode set to \"off\"");
+ break;
+ case disable_standby_always:
+ shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "always");
+ debug(1, ">> disable standby mode set to \"always\"");
+ break;
+ case disable_standby_auto:
+ shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "auto");
+ debug(1, ">> disable standby mode set to \"auto\"");
+ break;
+ default:
+ debug(1,"invalid disable_standby mode!");
+ break;
+ }
+
+#ifdef CONFIG_SOXR
+ if (config.packet_stuffing == ST_basic) {
shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic");
- else
+ debug(1, ">> interpolation set to \"basic\" (soxr support built in)");
+ } else if (config.packet_stuffing == ST_auto) {
+ shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "auto");
+ debug(1, ">> interpolation set to \"auto\" (soxr support built in)");
+ } else {
shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "soxr");
+ debug(1, ">> interpolation set to \"soxr\"");
+ }
#else
- shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic");
+ if (config.packet_stuffing == ST_basic) {
+ shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "basic");
+ debug(1, ">> interpolation set to \"basic\" (no soxr support)");
+ } else if (config.packet_stuffing == ST_auto) {
+ shairport_sync_set_interpolation(SHAIRPORT_SYNC(shairportSyncSkeleton), "auto");
+ debug(1, ">> interpolation set to \"auto\" (no soxr support)");
+ }
#endif
if (config.volume_control_profile == VCP_standard)
@@ -668,6 +792,12 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
else
shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "flat");
+ if (config.keep_dac_busy == 0) {
+ shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
+ } else {
+ shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
+ }
+
if (config.loudness == 0) {
shairport_sync_set_loudness_filter_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
} else {
@@ -725,6 +855,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
debug(1, "Shairport Sync native D-Bus service started at \"%s\" on the %s bus.", name,
(config.dbus_service_bus_type == DBT_session) ? "session" : "system");
+ service_is_running = 1;
}
static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
@@ -760,7 +891,20 @@ int start_dbus_service() {
dbus_bus_type = G_BUS_TYPE_SESSION;
// debug(1, "Looking for a Shairport Sync native D-Bus interface \"org.gnome.ShairportSync\" on
// the %s bus.",(config.dbus_service_bus_type == DBT_session) ? "session" : "system");
- g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE, NULL,
- on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL);
+ ownerID = g_bus_own_name(dbus_bus_type, "org.gnome.ShairportSync", G_BUS_NAME_OWNER_FLAGS_NONE,
+ NULL, on_dbus_name_acquired, on_dbus_name_lost, NULL, NULL);
return 0; // this is just to quieten a compiler warning
}
+
+void stop_dbus_service() {
+ debug(2, "stopping dbus service");
+ if (ownerID)
+ g_bus_unown_name(ownerID);
+ else
+ debug(1, "Zero OwnerID for \"org.gnome.ShairportSync\".");
+ service_is_running = 0;
+}
+
+int dbus_service_is_running() {
+ return service_is_running;
+}
diff --git a/dbus-service.h b/dbus-service.h
index ea6e9765..5cfc02e2 100644
--- a/dbus-service.h
+++ b/dbus-service.h
@@ -6,5 +6,7 @@
ShairportSync *shairportSyncSkeleton;
int start_dbus_service();
+void stop_dbus_service();
+int dbus_service_is_running();
#endif /* #ifndef DBUS_SERVICE_H */
diff --git a/documents/sample dbus commands b/documents/sample dbus commands
index 0a7c398d..23804159 100644
--- a/documents/sample dbus commands
+++ b/documents/sample dbus commands
@@ -20,4 +20,9 @@ dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSy
#Remote Control commands include: Play, Pause, PlayPause, Resume, Stop, Next, Previous, VolumeUp, VolumeDown, ToggleMute, FastForward, Rewind, ShuffleSongs
# Set Volume using Advanced Remote Control
-dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50
\ No newline at end of file
+dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50
+
+# Get Drift Tolerance
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:DriftTolerance
+# Set Drift Tolerance to 1 millisecond
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:DriftTolerance variant:double:0.001
diff --git a/man/shairport-sync.7 b/man/shairport-sync.7
index 9ff7dd19..01163330 100644
--- a/man/shairport-sync.7
+++ b/man/shairport-sync.7
@@ -2,30 +2,30 @@
.SH NAME
shairport-sync \- Synchronised Audio Player for iTunes / AirPlay
.SH SYNOPSIS
-\fBshairport-sync [-djvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--get-cover-art]\fB [--logOutputLevel]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB
-
-shairport-sync -D\fB
+\fBshairport-sync [-djvuw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--get-cover-art]\fB [--logOutputLevel]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB
shairport-sync -k\fB
shairport-sync -h\fB
-shairport-sync -R\fB
-
shairport-sync -V\fB
\f1
.SH DESCRIPTION
-shairport-sync plays audio streamed from iTunes or from an AirPlay device to an ALSA compatible audio output device (available on Linux and FreeBSD) , to a "sndio" output device (available on OpenBSD, FreeBSD and Linux) or to a PulseAudio output stream (available on Linux).
+Shairport Sync plays audio streamed from iTunes or from an AirPlay device to an ALSA-compatible audio output device (available on Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and Linux), to a PulseAudio output stream or to Jack Audio.
-A feature of shairport-sync is that it offers full audio synchronisation. Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source. This means that if many devices are playing the same stream at the same time, all the outputs will stay in 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 offers full audio synchronisation. Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source. This means that if many devices are playing the same stream at the same time, all the outputs will stay in synchrony with one another. This allows multiple devices to play the same source without getting out of step with one another, enabling, for example, simultaneous multi-room operation.
-shairport-sync can be compiled to stream audio 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.
+Shairport Sync can stream synchronised audio to a unix pipe or to standard output, or to audio systems that do not provide timing information. This could perhaps be described as partial audio synchronisation, where synchronised audio is provided by Shairport Sync, but what happens to it in the subsequent processing chain, before it reaches the listener's ear, is outside the control of shairport-sync.
-shairport-sync can be compiled to stream metadata to a pipe or socket.
+Shairport Sync can be compiled to stream metadata, including cover art, to a pipe or socket.
+
+Shairport Sync can be compiled to offer a standard MPRIS interface, a "native" D-Bus interface and an MQTT client interface. Through these interfaces, it can provide metadata, including cover art, and can offer remote control of the audio source.
Settings can be made using the configuration file (recommended for all new installations) or by using command-line options.
+
+The name of the Shairport Sync executable is \fBshairport-sync\f1. Both names are used in these man pages.
.SH CONFIGURATION FILE SETTINGS
-You should use the configuration file for setting up shairport-sync. This file is usually \fIshairport-sync.conf\f1 and is generally located in the System Configuration Directory, which is normally the \fI/etc\f1 directory in Linux or the \fI/usr/local/etc\f1 directory in BSD unixes. You may need to have root privileges to modify it.
+You should use the configuration file for setting up Shairport Sync. This file is usually \fIshairport-sync.conf\f1 and is generally located in the System Configuration Directory, which is normally the \fI/etc\f1 directory in Linux or the \fI/usr/local/etc\f1 directory in BSD unixes. You may need to have root privileges to modify it.
(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command \fI$ shairport-sync -V\f1. One of the items in the output string is the value of the \fBsysconfdir\f1, i.e. the System Configuration Directory.)
@@ -77,15 +77,12 @@ Require the password \fIpassword\f1 to connect to the service. If you leave this
\fBinterpolation=\f1\fI"mode"\f1\fB;\f1
Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly in synchrony with the player. The default mode, "basic", is normally almost completely inaudible. The alternative mode, "soxr", is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled.
.TP
-\fBstatistics=\f1\fI"setting"\f1\fB;\f1
-Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
+\fBoutput_backend=\f1\fI"backend"\f1\fB;\f1
+shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends -- the default is the first on this list. Only the "alsa", "sndio" and "pa" backends support synchronisation.
.TP
\fBmdns_backend=\f1\fI"backend"\f1\fB;\f1
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 \fIbackend\f1. The default is "avahi". Perform the command \fBshairport-sync -h\f1 to get a list of available mDNS modules.
.TP
-\fBoutput_backend=\f1\fI"backend"\f1\fB;\f1
-shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends -- the default is the first on this list. Only the "alsa", "sndio" and "pa" backends support synchronisation.
-.TP
\fBport=\f1\fIportnumber\f1\fB;\f1
Use this to specify the \fIportnumber\f1 shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.
.TP
@@ -101,15 +98,9 @@ Allow playback to drift up to \fIseconds\f1 out of exact synchronization before
\fBresync_threshold_in_seconds=\f1\fIthreshold\f1\fB;\f1
Resynchronise if timings differ by more than \fIthreshold\f1 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 \fBresync_threshold\f1 setting.
.TP
-\fBlog_verbosity=\f1\fI0\f1\fB;\f1
-Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
-.TP
\fBignore_volume_control=\f1\fI"choice"\f1\fB;\f1
Set this \fIchoice\f1 to \fI"yes"\f1 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 \fI"no"\f1.
.TP
-\fBvolume_max_db=\f1\fIdBvalue\f1\fB;\f1
-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.
-.TP
\fBvolume_range_db=\f1\fIdBvalue\f1\fB;\f1
Use this \fIdBvalue\f1 to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume.
@@ -121,39 +112,98 @@ As a third example, you can actually extend the range provided by a mixer. Many
If you omit this setting, the native range of the mixer is used.
.TP
+\fBvolume_max_db=\f1\fIdBvalue\f1\fB;\f1
+Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting specifies the maximum setting permissible in the software mixer, which has an attenuation range from 0.0 dB down to -96.3 dB.
+.TP
+\fBvolume_control_profile=\f1\fI"choice"\f1\fB;\f1
+Use this advanced setting to specify how the airplay volume is transferred to the mixer volume. The \fI"standard"\f1 profile, which is the default, makes the volume change more quickly at lower volumes and slower at higher volumes. Choose the \fI"flat"\f1 profile to makes the volume change at the same rate at all volume levels.
+.TP
+\fBvolume_range_combined_hardware_priority=\f1 \fI"choice"\f1\fB;\f1
+Use this advanced setting to specify how to combine the hardware attenuator with software attenuation to provide a greater attenuation range than the hardware attenuator alone can provide. Choosing \fI"yes"\f1 means that when attenuation is required, the hardware attenuator will be used in preference. If more attenuation than it can provide is needed, the hardware attenuator is set to its greatest attenuation and software attenuation is added.
+
+For example, if 40 dB of attenuation is required and the hardware attenuator offers a maximum of 30 dB, then the hardware attenuator will be set to give 30 dB attenuation and 10 dB of software attenuation will be added.
+
+Unfortunately, certain hardware attenuators will mute at their greatest attenuation, so can't be combined with software attenuation in this way. Choosing \fI"no"\f1 means that software attenuation is used to bring the remaining attenuation required into the range offered by the hardware attenuator. This is the default.
+.TP
+\fBrun_this_when_volume_is_set=\f1 \fI"/full/path/to/application/and/args"\f1\fB;\f1
+Here you can specify a program and its arguments that will be run when the volume is set or changed. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
+
+The desired AirPlay volume is appended to the end of the command line -- leave a space at the end of the command line you specify here if you want it treated as an extra argument. AirPlay volume goes from 0.0 to -30.0 and -144.0 means "mute".
+.TP
\fBregtype=\f1\fI"regTypeString"\f1\fB;\f1
Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is \fI"_raop._tcp"\f1.
.TP
\fBplayback_mode=\f1\fI"mode"\f1\fB;\f1
-The \fImode\f1 can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
-.TP
-\fBinterface=\f1\fI"name"\f1\fB;\f1
-Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
+The \fImode\f1 can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo". Note that dither will be added to the signal in the mono mode.
.TP
\fBalac_decoder=\f1\fI"decodername"\f1\fB;\f1
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.
.TP
-\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1
+\fBinterface=\f1\fI"name"\f1\fB;\f1
+Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
+.TP
+\fBaudio_backend_latency_offset_in_seconds=\f1 \fIoffset_in_seconds\f1\fB;\f1
Set this \fIoffset_in_seconds\f1 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.
.TP
-\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIlength_in_seconds\f1\fB;\f1
+\fBaudio_backend_buffer_desired_length_in_seconds=\f1 \fIlength_in_seconds\f1\fB;\f1
Use this \fIlength_in_seconds\f1 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.
.TP
-\fBaudio_backend_silent_lead_in_time=\f1\fIlead_in_time_in_seconds\f1\fB;\f1
+\fBaudio_backend_buffer_interpolation_threshold_in_seconds=\f1 \fItime_in_seconds\f1\fB;\f1
+This is an advanced feature. If the length of the audio backend buffer size drops below this, it's a sign that shairport sync can not process frames of audio quickly enough. It this threshold is reached, shairport sync will stop using time-consuming interpolation like soxr to avoid underruns.
+.TP
+\fBaudio_backend_silent_lead_in_time=\f1 \fIlead_in_time_in_seconds\f1\fB;\f1
This is an advanced setting. Use the \fIlead_in_time_in_seconds\f1 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.
.TP
-\fBrun_this_when_volume_is_set=\f1\fI"/full/path/to/application/and/args"\f1\fB;\f1
-Here you can specify a program and its arguments that will be run when the volume is set or changed. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
+\fBdbus_service_bus=\f1 \fI"bus_name"\f1\fB;\f1
+If shairport sync is compiled with the D-Bus interface, it can offer it on the \fI"system"\f1 or the \fI"session"\f1 D-Bus "bus". Use this to specify which. The default is to use the "system" bus.
+.TP
+\fBmpris_service_bus=\f1 \fI"bus_name"\f1\fB;\f1
+If shairport sync is compiled with the MPRIS interface, it can offer the service on the \fI"system"\f1 or the \fI"session"\f1 D-Bus "bus". Use this to specify which. The default is to use the "system" bus.
+.TP
+\fB"SESSIONCONTROL" SETTINGS\f1
+.TP
+\fBrun_this_before_play_begins=\f1\fI"/path/to/application and args"\f1\fB;\f1
+Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
+.TP
+\fBrun_this_after_play_ends=\f1\fI"/path/to/application and args"\f1\fB;\f1
+Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
+.TP
+\fBrun_this_before_entering_active_state=\f1\fI"/path/to/application and args"\f1\fB;\f1
+Here you can specify a program and its arguments that will be run just before shairport-sync goes active.
-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".
+Shairport Sync goes "active" when a play session starts. When the play session ends, the system will stay active until the time specified in the \fBactive_state_timeout\f1 setting elapses. If a new play session starts before that, the system will remain active. Otherwise, the system will go inactive.
+
+Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
+.TP
+\fBrun_this_after_exiting_active_state=\f1\fI"/path/to/application and args"\f1\fB;\f1
+Here you can specify a program and its arguments that will be run just after shairport-sync goes inactive (see the previous entry for an explanation of the idea). Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
+.TP
+\fBactive_state_timeout=\f1\fIseconds\f1\fB;\f1
+After a play session has ended, the system will remain active for \fIseconds\f1 seconds. If a new play session starts before this time has elapsed, the system will remain active. However, if no new session starts in the interval, the system will go inactive at the end of it. The default is 10 seconds.
+.TP
+\fBrun_this_if_an_unfixable_error_is_detected=\f1\fI"/path/to/application and args"\f1\fB;\f1
+Here you can specify a program and its arguments that will be run if the system detects an unfixable error. At present, there are two types of unfixable errors. One is where a play session cannot be terminated. The second is if an output device has "stalled" -- that is, if an output device refuses to accept any more output frames.
+
+Although the first problem could, in principle, be fixed by restarting Shairport Sync, it is usually caused by a malfunctioning output device. Typically, the most reliable way to recover from either of these errors is to reboot the entire machine.
+
+Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
+.TP
+\fBwait_for_completion=\f1\fI"choice"\f1\fB;\f1
+Set \fIchoice\f1 to "yes" to make shairport-sync wait until the programs specified in the \fBrun_this_...\f1 settings have completed execution before continuing. The default is "no".
+.TP
+\fBallow_session_interruption=\f1\fI"choice"\f1\fB;\f1
+If \fBchoice\f1 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.
+.TP
+\fBsession_timeout=\f1\fIseconds\f1\fB;\f1
+If a play session has been established and the source disappears without warning (such as a device going out of range of a network) then wait for the number of seconds specified before ending the session. Once the session has terminated, other devices can use it. The default is 120 seconds.
.TP
\fB"ALSA" SETTINGS\f1
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 \fBalsamixer\f1 or \fBaplay\f1 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 \fBalsa\f1 group settings:
@@ -190,11 +240,21 @@ Use this optional advanced setting to set the alsa buffer size near to this valu
Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is \fI"yes"\f1.
.TP
\fBmute_using_playback_switch=\f1\fI"no"\f1\fB;\f1
-This is an advanced setting and the default is \fI"no"\f1. If it is set to \fI"yes"\f1, hardware mute will be implemented using a feature called a 'playback switch', where one is available. Set it to \fI"no"\f1 to prevent the playback switch being used.
+This is an advanced setting and the default is \fI"no"\f1. If it is set to \fI"yes"\f1, hardware mute will be used where it is available. Set it to \fI"no"\f1 to prevent the hardware mute being used.
If Shairport Sync is sharing the output device with other applications, it is best to leave this set to \fI"no"\f1 for compatibility with those applications.
-Another motivation for this is to allow the alsa function call "snd_mixer_selem_set_playback_switch_all" to be avoided. It is incorrectly implemented on certain soundcards, including the emulated card in VMWare Fusion 8.5.
+Another motivation for this is to allow the ALSA function call "snd_mixer_selem_set_playback_switch_all" to be avoided. It is incorrectly implemented on certain soundcards, including the emulated card in VMWare Fusion 8.5.
+.TP
+\fBmaximum_stall_time=\f1\fIseconds\f1\fB;\f1
+If an output device fails to accept any audio frames for more than the time, in seconds, specified here (0.2 seconds by default), it is considered to have malfunctioned. It will result in the \fBrun_this_if_an_unfixable_error_is_detected\f1 program, if any, being called.
+
+Implemented for the ALSA back end only.
+.TP
+\fBdisable_standby_mode=\f1\fI"never"\f1\fB;\f1
+Shairport Sync has a "Disable Standby" feature to eliminate certain faint-but-annoying audible pops and clicks. When activsted, it prevents an output device from entering standby mode and thus it minimises standby/busy transitions, which can sometimes be heard. Use this setting to control when the Disable Standby feature is active: "never" means it will never be activated, "always" means it will be active as soon as shairport-sync starts running, and "auto" means it will be active while shairport-sync is in the "active" state.
+
+Shairport Sync goes "active" when a play session starts. When the play session ends, the system will stay active until the time specified in the active_state_timeout setting elapses. If a new play session starts before that, the system will remain active. Otherwise, the system will go inactive.
.TP
\fB"SNDIO" SETTINGS\f1
These settings are for the SNDIO back end, used to communicate with audio output devices in the SNDIO system.
@@ -263,23 +323,13 @@ If \fBsocket_address\f1 is set, use \fIport\f1 to specify the port to send UDP p
\fBsocket_msglength=\f1\fI65000\f1\fB;\f1
The maximum packet size for any UDP metadata. This must be between 500 or 65000. The default is 500.
.TP
-\fB"SESSIONCONTROL" SETTINGS\f1
-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.
+\fB"DIAGNOSTICS" SETTINGS\f1
.TP
-\fBrun_this_before_play_begins=\f1\fI"/path/to/application and args"\f1\fB;\f1
-Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
+\fBstatistics=\f1\fI"setting"\f1\fB;\f1
+Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
.TP
-\fBrun_this_after_play_ends=\f1\fI"/path/to/application and args"\f1\fB;\f1
-Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard shebang \fI#!/bin/...\f1 as appropriate.
-.TP
-\fBwait_for_completion=\f1\fI"choice"\f1\fB;\f1
-Set \fIchoice\f1 to "yes" to make shairport-sync wait until the programs specified in the \fBrun_this_before_play_begins\f1, \fBrun_this_after_play_ends\f1 and \fBrun_this_when_volume_is_set\f1 have completed execution before continuing. The default is "no".
-.TP
-\fBallow_session_interruption=\f1\fI"choice"\f1\fB;\f1
-If \fBchoice\f1 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.
-.TP
-\fBsession_timeout=\f1\fIseconds\f1\fB;\f1
-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 \fIseconds\f1 seconds before ending the session. Once the session has terminated, other devices can use it. The default is 120 seconds.
+\fBlog_verbosity=\f1\fI0\f1\fB;\f1
+Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
.SH OPTIONS
This section is about the command-line options available in shairport-sync.
@@ -306,13 +356,8 @@ If you want shairport-sync to wait until the command has completed before starti
\fB-c \f1\fIfilename\f1\fB | --configfile=\f1\fIfilename\f1
Read configuration settings from \fIfilename\f1. The default is to read them from the \fIshairport-sync.conf\f1 in the System Configuration Directory -- \fI/etc\f1 in Linux, \fI/usr/local/etc\f1 in BSD unixes. For information about configuration settings, see the "Configuration File Settings" section above.
.TP
-\fB-D | --disconnectFromOutput\f1
-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 \fB-d\f1 option).
-
-Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
-.TP
\fB-d | --daemon\f1
-Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time. See also the \fB-j\f1 option.
+Instruct shairport-sync to demonise itself. It will write its Process ID (PID) to a file, usually at \fI/var/run/shairport-sync/shairport-sync.pid\f1, which is used by the \fB-k\f1, \fB-D\f1 and \fB-R\f1 options to locate the daemon at a later time. See also the \fB-j\f1 option. Only available if shaiport-sync has been compiled with libdaemon support.
.TP
\fB-E \f1\fIprogram\f1\fB | --on-stop=\f1\fIprogram\f1
Execute \fIprogram\f1 when playback has ended. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have the appropriate shebang (\fI#!/bin/sh\f1 in the headline.
@@ -328,10 +373,10 @@ Please note that cover art data may be very large, and may place too great a bur
Print brief help message and exit.
.TP
\fB-j\f1
-Instruct shairport-sync to demonise itself. Unlike the \fB-d\f1 option, it will not write a Process ID (PID) to a file -- it will just (hence the "j") demonise itself.
+Instruct shairport-sync to demonise itself. Unlike the \fB-d\f1 option, it will not write a Process ID (PID) to a file -- it will just (hence the "j") demonise itself. Only available if shaiport-sync has been compiled with libdaemon support.
.TP
\fB-k | --kill\f1
-Kill the shairport-sync daemon and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option).
+Kill the shairport-sync daemon and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option. Only available if shaiport-sync has been compiled with libdaemon support.)
.TP
\fB--logOutputLevel\f1
Use this to log the volume level when the volume is changed. It may be useful if you are trying to determine a suitable value for the maximum volume level. Not available as a configuration file setting.
@@ -356,11 +401,6 @@ Listen for play requests on \fIport\f1. The default is to use port 5000.
\fB--password=\f1\fIsecret\f1
Require the password \fIsecret\f1 to be able to connect and stream to the service.
.TP
-\fB-R | --reconnectToOutput\f1
-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 \fB-d\f1 option).
-
-Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
-.TP
\fB-r \f1\fIthreshold\f1\fB | --resync=\f1\fIthreshold\f1
Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation. This setting is deprecated and will be removed in a future version of shairport-sync.
.TP
@@ -378,6 +418,9 @@ When shairport-sync plays an audio stream, it starts a play session and will ret
\fB--tolerance=\f1\fIframes\f1
Allow playback to be up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections. This setting is deprecated and will be removed in a future version of shairport-sync.
.TP
+\fB-u\f1
+If you are running shairport-sync from the command line and want logs to appear there, use this option. Otherwise, logs may go to the system log.
+.TP
\fB-V | --version\f1
Print version information and exit.
.TP
diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml
index 6be15b18..199e97fd 100644
--- a/man/shairport-sync.7.xml
+++ b/man/shairport-sync.7.xml
@@ -4,7 +4,7 @@
- shairport-sync [-djvw]
+ shairport-sync [-djvuw][-a name][-A latency][-B command]
@@ -55,41 +55,64 @@
[--tolerance=frames][-- audio_backend_options]
- shairport-sync -Dshairport-sync -kshairport-sync -h
- shairport-sync -Rshairport-sync -V
-
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 it offers full audio synchronisation.
- Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source.
+
Shairport Sync plays
+ audio streamed from iTunes
+ or from an AirPlay device to an ALSA-compatible audio output device (available on
+ Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
+ Linux), to a PulseAudio output stream or to Jack Audio.
+
+
Shairport Sync offers full audio synchronisation.
+ Full audio synchronisation means that audio is played on the output device at exactly
+ the time specified by the audio source.
This means that if many devices are playing the same stream at the same
- time, all the outputs will stay in 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.
+ time, all the outputs will stay in synchrony with one another.
+ This allows multiple devices to play the same source without getting out of step with
+ one another, enabling, for example, simultaneous multi-room operation.
-
shairport-sync can be compiled to stream audio 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.
-
shairport-sync can be compiled to stream metadata to a pipe or socket.
+
Shairport Sync can stream synchronised audio to a unix
+ pipe or to standard output, or to audio systems that do not provide timing
+ information. This could perhaps be described as partial audio synchronisation, where
+ synchronised audio is provided by Shairport Sync, but what happens to it in the
+ subsequent processing chain, before it reaches the listener's ear, is outside the
+ control of shairport-sync.
+
Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
+ or socket.
+
Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
+ D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
+ metadata, including cover art, and can offer remote control of the audio source.
+
+
Settings can be made using the configuration file (recommended for all new
+ installations) or by using command-line options.
+
+
The name of the Shairport Sync executable is shairport-sync.
+ Both names are used in these man pages.
-
Settings can be made using the configuration file (recommended for all new installations) or by using command-line options.
-
+
-
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 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,
+
(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
+
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:
@@ -105,10 +128,18 @@
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.
+
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.
+
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
@@ -126,20 +157,26 @@
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),
+ %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.
+ %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.
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 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 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.
+
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.
+
@@ -398,86 +737,87 @@
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 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.
+
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.
Kill the shairport-sync daemon and exit. (Requires that the daemon has
- written its PID to an agreed file -- see the -d option).
+ written its PID to an agreed file -- see the -d option. Only available if
+ shaiport-sync has been compiled with libdaemon support.)
@@ -607,10 +947,13 @@
@@ -619,8 +962,9 @@
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
+ in the directory you specify. If you add the --get-cover-art
+ then cover art will be sent through the pipe too. See
for a sample metadata reader.
@@ -635,7 +979,8 @@
-
These command-line options are passed to the chosen audio backend. The audio backend options are
+
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.
+ 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.
+ 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.
@@ -777,8 +1128,8 @@
-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.
+ 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.
@@ -787,8 +1138,8 @@
-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,
+ 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.
@@ -801,8 +1152,10 @@
-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.
+ 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.
@@ -821,14 +1174,13 @@
"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" ).
+ 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:
+
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 it offers full audio synchronisation.
- Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source.
+
Shairport Sync plays
+ audio streamed from iTunes
+ or from an AirPlay device to an ALSA-compatible audio output device (available on
+ Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
+ Linux), to a PulseAudio output stream or to Jack Audio.
+
+
Shairport Sync offers full audio synchronisation.
+ Full audio synchronisation means that audio is played on the output device at exactly
+ the time specified by the audio source.
This means that if many devices are playing the same stream at the same
- time, all the outputs will stay in 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.
+ time, all the outputs will stay in synchrony with one another.
+ This allows multiple devices to play the same source without getting out of step with
+ one another, enabling, for example, simultaneous multi-room operation.
-
shairport-sync can be compiled to stream audio 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.
-
shairport-sync can be compiled to stream metadata to a pipe or socket.
+
Shairport Sync can stream synchronised audio to a unix
+ pipe or to standard output, or to audio systems that do not provide timing
+ information. This could perhaps be described as partial audio synchronisation, where
+ synchronised audio is provided by Shairport Sync, but what happens to it in the
+ subsequent processing chain, before it reaches the listener's ear, is outside the
+ control of shairport-sync.
+
Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
+ or socket.
+
Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
+ D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
+ metadata, including cover art, and can offer remote control of the audio source.
+
+
Settings can be made using the configuration file (recommended for all new
+ installations) or by using command-line options.
+
+
The name of the Shairport Sync executable is shairport-sync.
+ Both names are used in these man pages.
-
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 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,
+
(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
+
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:
@@ -86,10 +107,18 @@
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.
+
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.
+
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
@@ -107,20 +136,26 @@
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),
+ %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.
+ %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.
+
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
+
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.
@@ -133,39 +168,60 @@
-
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.
+
output_backend="backend";
+
shairport-sync has a number of modules of code ("backends") through which
+ audio is output. Normally, the first audio backend that works is selected. This
+ setting forces the selection of the specific audio backend. Perform the
+ command shairport-sync -h to get a list of available audio backends -- the
+ default is the first on this list. Only the "alsa", "sndio" and "pa" backends support
+ synchronisation.
mdns_backend="backend";
-
shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS backend. The default is "avahi". Perform the command shairport-sync -h to get a list of available mDNS modules.
-
-
-
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.
+
shairport-sync has a number of modules of code ("backends") for
+ interacting with the mDNS service to be used to advertise itself. Normally, the first
+ mDNS backend that works is selected. This setting forces the selection of the specific
+ mDNS backend. The default is "avahi". Perform the command
+ shairport-sync -h to get a list of available mDNS modules.
+
port=portnumber;
-
Use this to specify the portnumber shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.
+
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.
+
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.
+
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.
+
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.
@@ -176,201 +232,482 @@
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.
-
+
Set this choice to "yes" if you want the volume to
+ be at 100% no matter what the source's volume control is set to.
+ This might be useful if you want to set the volume on the output device, independently
+ of the setting at the source. The default is "no".
volume_range_db=dBvalue;
-
Use this dBvalue to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume.
-
-
For example, if a mixer has a minimum volume of -80 dB and a maximum of +20 dB, you might wish to use only 60 dB of the 100 dB available.
- This might be because the sound becomes inaudible at the lowest setting and unbearably loud at the highest setting --
+
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.
+
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.
+ 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 specify a volume range greater than the range of the mixer, software
+ attenuation and hardware attenuation will be combined to give the specified range.
If you omit this setting, the native range of the mixer is used.
+
+
+
volume_max_db=dBvalue;
+
Specify the maximum output level to be used with the hardware mixer, if
+ used. If no hardware mixed is used, this setting specifies the maximum setting
+ permissible in the software mixer, which has an attenuation range from 0.0 dB down to
+ -96.3 dB.
+
+
+
+
+
volume_control_profile="choice";
+
Use this advanced setting to specify how the airplay volume is transferred
+ to the mixer volume. The "standard" profile, which is the default, makes
+ the volume change more quickly at lower volumes and slower at higher volumes. Choose
+ the "flat" profile to makes the volume change at the same rate at all
+ volume levels.
+
Use this advanced setting to specify how to combine the hardware
+ attenuator with software attenuation to provide a greater attenuation range than the
+ hardware attenuator alone can provide. Choosing "yes" means that when
+ attenuation is required, the hardware attenuator will be used in preference.
+ If more attenuation than it can provide is needed, the hardware attenuator is set to
+ its greatest attenuation and software attenuation is added.
+
For example, if 40 dB of attenuation is required and the hardware attenuator
+ offers a maximum of 30 dB, then the hardware attenuator will be set to give 30 dB
+ attenuation and 10 dB of software attenuation will be added.
+
Unfortunately, certain hardware attenuators will mute at their greatest
+ attenuation, so can't be combined with software attenuation in this way. Choosing
+ "no" means that software attenuation is used to bring the remaining
+ attenuation required into the range offered by the hardware attenuator.
+ This is the default.
+
Here you can specify a program and its arguments that will be run when the
+ volume is set or changed. Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as appropriate.
+
The desired AirPlay volume is appended to the end of the command line -- leave a
+ space at the end of the command line you specify here if you want it treated as an
+ extra argument.
+ AirPlay volume goes from 0.0 to -30.0 and -144.0 means "mute".
+
+
regtype="regTypeString";
-
Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
+
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.
+
The mode can be "stereo", "mono", "reverse stereo", "both left"
+ or "both right". Default is "stereo". Note that dither will be added to the signal in
+ the mono mode.
alac_decoder="decodername";
-
This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work.
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 appropriate shebang #!/bin/....
-
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".
+
This can be "hammerton" or "apple". This advanced setting allows you to
+ choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio
+ Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the
+ configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be
+ present for this to work.
+
+
interface="name";
+
Use this advanced setting if you want to confine Shairport Sync to the
+ named interface. Leave it commented out to get the default bahaviour.
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 feature. If the length of the audio backend buffer
+ size drops below this, it's a sign that shairport sync can not process frames of audio
+ quickly enough. It this threshold is reached, shairport sync will stop using
+ time-consuming interpolation like soxr to avoid underruns.
This is an advanced setting. Use the lead_in_time_in_seconds to
+ set the desired length of the period of silence (a "silent lead-in") played before a
+ play session begins.
+
The purpose of this silent lead-in is to give the backend sufficient time to
+ prepare for operation and to make an estimate (and, importantly, to correct the
+ estimate) of the exact time at which to begin playing audio to achieve initial
+ synchronisation. The value can be from 0.0 up to a maximum of either 4.0 seconds. The
+ actual duration will be close to the setting but can not exceed the latency set by the
+ client, usually 2 seconds or a little more.
+
If the value chosen is too short for synchronised backends such as the ALSA, sndio
+ or PA backends, then audio will not be synchronised correctly at the start of play.
+ The default is to have a silent lead-in of approximately the same time as the latency
+ set by the client.
+
+
+
+
dbus_service_bus=
+ "bus_name";
+
If shairport sync is compiled with the D-Bus interface, it can offer it on
+ the "system" or the "session" D-Bus "bus".
+ Use this to specify which. The default is to use the "system" bus.
+
+
+
+
mpris_service_bus=
+ "bus_name";
+
If shairport sync is compiled with the MPRIS interface, it can offer the
+ service on the "system" or the "session" D-Bus "bus".
+ Use this to specify which. The default is to use the "system" bus.
+
+
+
"SESSIONCONTROL" SETTINGS
+
+
+
run_this_before_play_begins="/path/to/application and
+ args";
+
Here you can specify a program and its arguments that will be run just
+ before a play session begins. Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
+ appropriate.
+
+
+
+
run_this_after_play_ends="/path/to/application and
+ args";
+
Here you can specify a program and its arguments that will be run just
+ after a play session ends. Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
+ appropriate.
+
+
+
+
run_this_before_entering_active_state="/path/to/application and
+ args";
+
Here you can specify a program and its arguments that will be run just
+ before shairport-sync goes active.
+
+
Shairport Sync goes "active" when a play session starts. When the play
+ session ends, the system will stay active until the time
+ specified in the active_state_timeout setting elapses.
+ If a new play session starts before that, the system will remain active. Otherwise,
+ the system will go inactive.
+
+
+
Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
+ appropriate.
+
+
+
+
run_this_after_exiting_active_state="/path/to/application and
+ args";
+
Here you can specify a program and its arguments that will be run just
+ after shairport-sync goes inactive (see the previous entry for an explanation
+ of the idea).
+ Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
+ appropriate.
+
+
+
+
active_state_timeout=seconds;
+
After a play session has ended, the system will remain active for
+ seconds seconds. If a new play session starts before this time has elapsed,
+ the system will remain active. However, if no new session starts in the interval, the
+ system will go inactive at the end of it. The default is 10 seconds.
+
+
+
+
run_this_if_an_unfixable_error_is_detected="/path/to/application
+ and args";
+
Here you can specify a program and its arguments that will be run if the
+ system detects an unfixable error. At present, there are two types of
+ unfixable errors. One is where a play session cannot be terminated.
+ The second is if an output device has "stalled" -- that is, if an output device
+ refuses to accept any more output frames.
+
Although the first problem could, in principle, be fixed by restarting
+ Shairport Sync, it is usually caused by a malfunctioning output device.
+ Typically, the most reliable way to recover from either of these errors
+ is to reboot the entire machine.
+
Be careful to include the full path to the application.
+ The application must be marked as executable and, if it is a script, its first line
+ must begin with the standard shebang #!/bin/... as
+ appropriate.
+
+
+
+
wait_for_completion="choice";
+
Set choice to "yes" to make shairport-sync wait until the
+ programs specified in the run_this_... settings have
+ completed execution before continuing. The default is "no".
+
+
+
+
allow_session_interruption="choice";
+
If choice is set to "yes", then another source will be able to
+ interrupt an existing play session and start a new one.
+ When set to "no" (the default), other devices attempting to interrupt a session will
+ fail, receiving a busy signal.
+
+
+
+
session_timeout=seconds;
+
If a play session has been established and the source disappears without
+ warning (such as a device going out of range of a network)
+ then wait for the number of seconds specified before ending the session.
+ Once the session has terminated, other devices can use it. The default is 120
+ seconds.
+
+
"ALSA" SETTINGS
-
These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system.
- (By the way, you can use tools such as alsamixer or aplay to discover what devices are available.)
- Use these settings to select the output device and the mixer control to be used to control the output volume.
- You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the alsa group settings:
+
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".
+
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.
+
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.
+ 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.
+
By default, the mixer is assumed to be output_device. Use this setting to
+ specify a device other than the output device.
output_rate=frame rate;
-
Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available.
+
Use this setting to specify the frame rate to output to the ALSA device.
+ Allowable values are 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.
+
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.
+
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.
+
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 this optional advanced setting to set the alsa buffer size near to
+ this value.
+
use_mmap_if_available="yes";
-
Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes".
+
Use this optional advanced setting to control whether MMAP-based output
+ is used to communicate with the DAC. Default is "yes".
mute_using_playback_switch="no";
-
This is an advanced setting and the default is "no". If it is set to "yes", hardware mute will be implemented using a feature called a 'playback switch', where one is available. Set it to "no" to prevent the playback switch being used.
-
If Shairport Sync is sharing the output device with other applications, it is best to leave this set to "no" for compatibility with those applications.
-
Another motivation for this is to allow the alsa function call
- "snd_mixer_selem_set_playback_switch_all" to be avoided. It is incorrectly implemented on certain soundcards, including the emulated card in VMWare Fusion 8.5.
+
This is an advanced setting and the default is "no". If it is set to
+ "yes", hardware mute will be used where it is available.
+ Set it to "no" to prevent the hardware mute being used.
+
If Shairport Sync is sharing the output device with other applications, it is best
+ to leave this set to "no" for compatibility with those applications.
+
Another motivation for this is to allow the ALSA function call
+ "snd_mixer_selem_set_playback_switch_all" to be avoided. It is incorrectly implemented
+ on certain soundcards, including the emulated card in VMWare Fusion 8.5.
-
"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.
-
+
+
maximum_stall_time=seconds;
+
If an output device fails to accept any audio frames for more than the
+ time, in seconds, specified here (0.2 seconds by default),
+ it is considered to have malfunctioned. It will result in the
+ run_this_if_an_unfixable_error_is_detected program,
+ if any, being called.
+
Implemented for the ALSA back end only.
+
+
+
+
+
disable_standby_mode="never";
+
+
Shairport Sync has a "Disable Standby" feature to eliminate certain
+ faint-but-annoying audible pops and clicks. When activsted, it prevents
+ an output device from entering standby mode and thus it minimises standby/busy
+ transitions, which can sometimes be heard. Use this setting to control when the
+ Disable Standby feature is active: "never" means it will never be activated, "always"
+ means it will be active as soon as shairport-sync starts running, and "auto"
+ means it will be active while shairport-sync is in the "active" state.
+
Shairport Sync goes "active" when a play session starts. When the play
+ session ends, the system will stay active until the time
+ specified in the active_state_timeout setting elapses.
+ If a new play session starts before that, the system will remain active. Otherwise,
+ the system will go inactive.
+
+
+
+
+
"SNDIO" SETTINGS
+
These settings are for the SNDIO back end, used to communicate with audio output
+ devices in the SNDIO system.
+
+
+
device="snd/0";
+
Use this optional setting to specify the name of the output device, e.g.
+ "snd/0". The default is to use the SNDIO system's default.
+
+
+
+
rate=44100;
+
Use this optional setting to specify the output rate in frames per second.
+ Valid rates are 44100, 88200, 176400 or 352800.
+ The output device must have the capability to accept data at the specified rate. The
+ default is 44100.
+
+
+
+
format="S16";
+
Use this optional setting to specify the output format. Allowable values
+ are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32".
+ The device must have the capability to accept the format you specify.
"S" means
+ signed; "U" means unsigned; BE means big-endian and LE means little-endian.
+ Except where stated (using *LE or *BE), endianness matches that of the processor. The
+ default is "S16".
+ Since the SNDIO backend does not use a hardware mixer for volume control, dither will
+ be introduced into the output if it is less than full volume.
+ Thus, (unless you are ignoring the volume control setting),
+ consider using 32- or 24-bit output if your device is capable of it, to get the lowest
+ possible levels of dither.
+
Please note that 32- or 24-bit has not been extensively tested on
+ SNDIO.
+
+
+
+
round=value;
+
Use this optional advanced setting to specify the period size of the SNDIO
+ channel. If omitted, a SNDIO system default value will be used.
+
+
+
+
bufsiz=value;
+
Use this optional advanced setting to specify the buffer size of the SNDIO
+ channel. If omitted, a SNDIO system default value will be used.
+
"PA" SETTINGS
These settings are for the new PulseAudio backend.
+
application_name="Shairport Sync";
-
Use this to set the name to appear in the Sounds "Applications" tab when Shairport Sync is active. The default is the name "Shairport Sync".
+
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.
+
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.
+
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
@@ -379,76 +716,76 @@
"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:
-
+
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".
+
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.
+
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.
+ 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.
+
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".
+
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.
+
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.
+
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.
+
"DIAGNOSTICS" SETTINGS
+
+
statistics="setting";
+
Use this setting to enable ("yes") or disable ("no") the output
+ of some statistical information on the console or in the log. The default is to
+ disable statistics.
+
-
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 appropriate shebang #!/bin/....
-
-
-
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 appropriate shebang #!/bin/....
-
-
-
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.
+
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.
@@ -457,10 +794,11 @@
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.
+
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.
@@ -478,16 +816,20 @@
-
-a service name | --name=service name
+
-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),
+ %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.
+ %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.
@@ -496,8 +838,8 @@
Execute program when playback is about to begin. Specify the
full path to the program, e.g. /usr/bin/logger.
- Executable scripts can be used, but they must have the appropriate shebang
- #!/bin/sh in the headline.
+ Executable scripts can be used, but they must have the appropriate shebang
+ (#!/bin/sh in the headline.
If you want shairport-sync to wait until the command has
completed before starting to play, select the -w option as well.
@@ -507,30 +849,23 @@
-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.
+ 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.
+ the daemon at a later time. See also the -j option. Only available if
+ shaiport-sync has been compiled with libdaemon support.
@@ -539,8 +874,8 @@
Execute program when playback has ended. Specify the
full path to the program, e.g. /usr/bin/logger.
- Executable scripts can be used, but they must have the appropriate shebang
- #!/bin/sh in the headline.
+ Executable scripts can be used, but they must have the appropriate shebang
+ (#!/bin/sh in the headline.
If you want shairport-sync to wait until the command has
completed before continuing, select the -w option as well.
@@ -567,8 +902,9 @@
-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.
+ Instruct shairport-sync to demonise itself. Unlike the -d option, it will
+ not write a Process ID (PID) to a file -- it will just (hence the "j") demonise
+ itself. Only available if shaiport-sync has been compiled with libdaemon support.
@@ -576,15 +912,17 @@
-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).
+ written its PID to an agreed file -- see the -d option. Only available if
+ shaiport-sync has been compiled with libdaemon support.)
--logOutputLevel
- Use this to log the volume level when the volume is changed. It may be useful if you are trying to
- determine a suitable value for the maximum volume level. Not available as a configuration file setting.
+ 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.
@@ -592,10 +930,13 @@
-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
+ 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.
+
Please note that this feature is deprecated and will be removed in a future version
+ of shairport-sync.
@@ -604,8 +945,8 @@
Listen for metadata coming from the source and send it, along with metadata from
shairport-sync itself, to a pipe called shairport-sync-metadata
- in the directory you specify. If you add the --get-cover-art then
- cover art will be sent through the pipe too. See https://github.com/mikebrady/shairport-sync-metadata-reader
+ 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.
@@ -620,7 +961,8 @@
-
-o outputbackend | --output=outputbackend
+
-o outputbackend |
+ --output=outputbackend
Force the use of the specified output backend to play the audio.
The default is to try the first one.
@@ -638,30 +980,20 @@
--password=secret
- Require the password secret to be able to connect and stream to the service.
+ 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.
+ milliseconds. Specify 0 to disable resynchronisation. This setting is
+ deprecated and will be removed in a future version of shairport-sync.
@@ -706,10 +1038,22 @@
--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.
+ Allow playback to be up to frames out of exact synchronization before
+ attempting to correct it.
+ The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is
+ that overcorrection will occur.
+ Overcorrection is when more corrections (insertions and deletions) are made than are
+ strictly necessary to keep the stream in sync. Use the --statistics option
+ to monitor correction levels. Corrections should not greatly exceed net corrections.
+ This setting is deprecated and will be removed in a future version of shairport-sync.
+
+
+
+
+
-u
+
+ If you are running shairport-sync from the command line and want logs to appear there,
+ use this option. Otherwise, logs may go to the system log.
@@ -737,17 +1081,21 @@
Audio Backend Options
-
These command-line options are passed to the chosen audio backend. The audio backend options are
+
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.
+ 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.
+ 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.
@@ -764,8 +1112,8 @@
-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.
+ 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.
@@ -774,8 +1122,8 @@
-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,
+ 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.
@@ -788,8 +1136,10 @@
-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.
+ 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.
@@ -810,14 +1160,13 @@
"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" ).
+ 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:
+
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: