Merge branch 'development' 3.3rc7+ to 'master'
This commit is contained in:
@@ -32,6 +32,7 @@ shairport-sync.core
|
||||
/org.gnome.ShairportSync.service
|
||||
/dbus-interface.*
|
||||
/mpris-interface.*
|
||||
/lib_*.*
|
||||
|
||||
# Some eclipse project files
|
||||
.cproject
|
||||
|
||||
+10
-9
@@ -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):
|
||||
```
|
||||
|
||||
+1
-2
@@ -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).
|
||||
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#include <sndfile.h>
|
||||
#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<size; ++i)
|
||||
{
|
||||
buffer_l[i] = buffer[2*i+0];
|
||||
|
||||
+3
-3
@@ -49,7 +49,7 @@ Install the packages that are needed for Shairport Sync to be downloaded and bui
|
||||
```
|
||||
# pkg install git autotools pkgconf popt libconfig openssl sndio alsa-utils
|
||||
```
|
||||
Omit `alsa-utils` if you're not using ALSA. Likewise, omit `sndio` if you don't intend to use the `sndio` subsystem.
|
||||
Add `alsa-utils` if you're wish to use ALSA. Omit `sndio` if you don't intend to use the `sndio` subsystem.
|
||||
|
||||
Now, download Shairport Sync from GitHub:
|
||||
```
|
||||
@@ -60,10 +60,10 @@ Next, configure the build and compile it:
|
||||
|
||||
```
|
||||
$ autoreconf -i -f
|
||||
$ ./configure --with-avahi --with-ssl=openssl --with-alsa --with-sndio --with-os=freebsd --with-freebsd-service
|
||||
./configure --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
|
||||
$ make
|
||||
```
|
||||
Omit `--with-alsa` if you don't want to include the ALSA back end. Omit the `--with-sndio` if you don't want the `sndio` back end. Omit the `--with-freebsd-service` if you don't want to install a FreeBSD startup script, runtime folder and user and group -- see below for more details.
|
||||
Add `--with-alsa` if you wish to include the ALSA back end. Omit the `--with-sndio` if you don't want the `sndio` back end. Omit the `--with-freebsd-service` if you don't want to install a FreeBSD startup script, runtime folder and user and group -- see below for more details.
|
||||
|
||||
Installation
|
||||
----
|
||||
|
||||
+14
-15
@@ -7,11 +7,10 @@ In the commands below, note the convention that a `#` prompt means you are in su
|
||||
### Configure and Update
|
||||
Do the usual update and upgrade:
|
||||
```
|
||||
# apt update
|
||||
# apt upgrade
|
||||
# apt-get update
|
||||
# apt-get upgrade
|
||||
# rpi-update
|
||||
```
|
||||
**Note:** The upgrade step above will automatically update to the most recent stable Rapsberry Pi firmware.
|
||||
|
||||
(Separately, if you haven't done so already, consider using the `raspi-config` tool to expand the file system to use the entire card.)
|
||||
|
||||
### Turn Off WiFi Power Management
|
||||
@@ -19,14 +18,14 @@ If you are using WiFi, you should turn off WiFi Power Management:
|
||||
```
|
||||
# iwconfig wlan0 power off
|
||||
```
|
||||
WiFi Power Management will put the WiFi system in low-power mode when the WiFi system is considered inactive, and in this mode it may not respond to events initiated from the network, such as AirPlay requests. Hence, WiFi Power Management should be turned off. (See [TROUBLESHOOTING.md](https://github.com/mikebrady/shairport-sync/blob/master/TROUBLESHOOTING.md#wifi-adapter-running-in-power-saving--low-power-mode) for more details.)
|
||||
WiFi Power Management will put the WiFi system in low-power mode when the WiFi system considered inactive, and in this mode it may not respond to events initiated from the network, such as AirPlay requests. Hence, WiFi Power Management should be turned off. (See [TROUBLESHOOTING.md](https://github.com/mikebrady/shairport-sync/blob/master/TROUBLESHOOTING.md#wifi-adapter-running-in-power-saving--low-power-mode) for more details.)
|
||||
|
||||
Reboot the Pi.
|
||||
|
||||
### Remove Old Copies and Old Startup Scripts
|
||||
Before you begin building Shairport Sync, it's best to search for and remove any existing copies of the application, called `shairport-sync`. Use the command `$ which -a shairport-sync` to find them. For example, if `shairport-sync` has been installed previously, this might happen:
|
||||
### Remove Old Copies
|
||||
Before you begin building Shairport Sync, it's best to search for and remove any existing copies of the application, called `shairport-sync`. Use the command `$ which shairport-sync` to find them. For example, if `shairport-sync` has been installed previously, this might happen:
|
||||
```
|
||||
$ which -a shairport-sync
|
||||
$ which shairport-sync
|
||||
/usr/local/bin/shairport-sync
|
||||
```
|
||||
Remove it as follows:
|
||||
@@ -35,22 +34,20 @@ Remove it as follows:
|
||||
```
|
||||
Do this until no more copies of `shairport-sync` are found.
|
||||
|
||||
You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed if necessary.
|
||||
|
||||
### Build and Install
|
||||
Okay, now let's get the tools and sources for building and installing 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-soxr --with-avahi --with-ssl=openssl --with-systemd
|
||||
$ make
|
||||
$ sudo make install
|
||||
```
|
||||
@@ -61,7 +58,7 @@ Now to configure Shairport Sync. Here are the important options for the Shairpor
|
||||
// Sample Configuration File for Shairport Sync on a Raspberry Pi using the built-in audio DAC
|
||||
general =
|
||||
{
|
||||
volume_range_db = 60;
|
||||
volume_range_db = 60;
|
||||
};
|
||||
|
||||
alsa =
|
||||
@@ -71,6 +68,8 @@ alsa =
|
||||
};
|
||||
|
||||
```
|
||||
The `volume_range_db = 60;` setting makes Shairport Sync use only the usable part of the built-in audio card mixer's attenuation range.
|
||||
|
||||
The next step is to enable Shairport Sync to start automatically on boot up:
|
||||
```
|
||||
# systemctl enable shairport-sync
|
||||
@@ -79,6 +78,6 @@ Finally, either reboot the Pi or start the `shairport-sync` service:
|
||||
```
|
||||
# systemctl start shairport-sync
|
||||
```
|
||||
The Shairport Sync AirPlay service should now appear on the network with a service name made from the Pi's hostname with the first letter capitalised, e.g. hostname `raspberrypi` gives a service name `Raspberrypi`. You can change the service name and set a password in the configuration file. BTW, you should *never* use an important password as the AirPlay password for a Shairport Sync player – the password is stored in Shairport Sync's configuration file in plain text and is thus completely vulnerable.
|
||||
The Shairport Sync AirPlay service should now appear on the network with a service name made from the Pi's hostname with the first letter capitalised, e.g. hostname `raspberrypi` gives a service name `Raspberrypi`. You can change the service name and set a password in the configuration file.
|
||||
|
||||
Connect and enjoy...
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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.
|
||||
|
||||
In general, a log verbosity of 2 is adequate (`-vv`, or the relevant entry in the configuration file), and it's usually helpful if statistics have been enabled (`--statistics` on the command line, or the relevant entry in the configuration file).
|
||||
|
||||
If you are pasting in code or a log file, format it as code by preceding it and following it with a line containing exactly three backquotes and nothing else ([see here](https://guides.github.com/features/mastering-markdown/) for more on formatting):
|
||||
|
||||
\`\`\`
|
||||
```
|
||||
code or log file entries
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
|
||||
The Raspbian image at the time of writing is the `May 2016` version, with the release date of `2016-05-10`. It does not include `libsoxr`, but it is available as a package via `apt-get`.
|
||||
|
||||
Alternatively, `libsoxr`, part of [The Sox Resampler Library](https://sourceforge.net/projects/soxr/), is very easy to compile. Here are very brief instructions to download, compile and install it:
|
||||
Alternatively, `libsoxr` is very easy to compile. Here are very brief instructions to download, compile and install it:
|
||||
|
||||
* Install `cmake`. This is used in the building of libsoxr. On Linuxes such as Debian/Ubuntu/Raspbian:
|
||||
```
|
||||
@@ -14,14 +14,14 @@ On FreeBSD:
|
||||
|
||||
* Download the `libsoxr source`:
|
||||
```
|
||||
$ git clone git://git.code.sf.net/p/soxr/code soxr-code
|
||||
$ git clone git://git.code.sf.net/p/soxr/code libsoxr
|
||||
```
|
||||
* `cd` into the `soxr-code` directory and start the build process:
|
||||
* `cd` into the `libsoxr` directory and start the build process:
|
||||
```
|
||||
$ cd soxr-code
|
||||
$ cd libsoxr
|
||||
$ ./go
|
||||
```
|
||||
Be patient! This takes a long time on a first-generation Raspberry Pi -- it looks like it gets stuck around 40% or 50%, but it will finish if you let it.
|
||||
Be patient! This takes a long time on a Raspberry Pi -- it looks like it gets stuck around 40% or 50%, but it will finish if you let it.
|
||||
|
||||
Having compiled `libsoxr`, it must now must be installed:
|
||||
```
|
||||
@@ -32,7 +32,7 @@ Finally, for Shairport Sync to be able to locate `libsoxr` during compilation, y
|
||||
|
||||
On Linuxes such as Debian/Ubuntu/Raspbian:
|
||||
```
|
||||
# ldconfig
|
||||
# ldconfig -v
|
||||
```
|
||||
On FreeBSD you must add the location of the `soxr.pc` file to the `PKG_CONFIG_PATH`, if it exists, and define it otherwise. Here is what you do if it doesn't already exist:
|
||||
```
|
||||
|
||||
+67
-18
@@ -1,19 +1,25 @@
|
||||
SUBDIRS = man
|
||||
ARFLAGS = cr
|
||||
lib_dbus_interface_a_CFLAGS = -pthread
|
||||
lib_mpris_interface_a_CFLAGS = -pthread
|
||||
|
||||
|
||||
bin_PROGRAMS = shairport-sync
|
||||
|
||||
# See below for the flags for the test client program
|
||||
|
||||
shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c
|
||||
shairport_sync_SOURCES = shairport.c rtsp.c mdns.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c
|
||||
|
||||
AM_CFLAGS = -Wno-multichar -Wall -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
if BUILD_FOR_FREEBSD
|
||||
AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\" -O2
|
||||
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
else
|
||||
if BUILD_FOR_OPENBSD
|
||||
AM_CPPFLAGS = -I/usr/local/include -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CFLAGS = -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
else
|
||||
AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CXXFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
AM_CFLAGS = -Wno-multichar -Wall -Wextra -Wno-clobbered -Wno-psabi -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -34,10 +40,18 @@ if USE_TINYSVCMDNS
|
||||
shairport_sync_SOURCES += mdns_tinysvcmdns.c tinysvcmdns.c
|
||||
endif
|
||||
|
||||
if USE_EXTERNAL_MDNS
|
||||
shairport_sync_SOURCES += mdns_external.c
|
||||
endif
|
||||
|
||||
if USE_ALSA
|
||||
shairport_sync_SOURCES += audio_alsa.c
|
||||
endif
|
||||
|
||||
if USE_JACK
|
||||
shairport_sync_SOURCES += audio_jack.c
|
||||
endif
|
||||
|
||||
if USE_SNDIO
|
||||
shairport_sync_SOURCES += audio_sndio.c
|
||||
endif
|
||||
@@ -68,7 +82,7 @@ endif
|
||||
|
||||
if USE_CONVOLUTION
|
||||
shairport_sync_SOURCES += FFTConvolver/AudioFFT.cpp FFTConvolver/FFTConvolver.cpp FFTConvolver/Utilities.cpp FFTConvolver/convolver.cpp
|
||||
AM_CXXFLAGS = -std=c++11
|
||||
AM_CXXFLAGS += -std=c++11
|
||||
endif
|
||||
|
||||
if USE_DNS_SD
|
||||
@@ -79,6 +93,10 @@ if USE_METADATA_HUB
|
||||
shairport_sync_SOURCES += metadata_hub.c
|
||||
endif
|
||||
|
||||
if USE_MQTT
|
||||
shairport_sync_SOURCES += mqtt.c
|
||||
endif
|
||||
|
||||
if USE_DACP_CLIENT
|
||||
shairport_sync_SOURCES += dacp.c tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c
|
||||
endif
|
||||
@@ -86,29 +104,41 @@ endif
|
||||
BUILT_SOURCES =
|
||||
noinst_HEADERS =
|
||||
CLEANFILES =
|
||||
shairport_sync_LDADD =
|
||||
noinst_LIBRARIES =
|
||||
|
||||
if USE_DBUS
|
||||
shairport_sync_SOURCES += dbus-service.c dbus-interface.c
|
||||
shairport_sync_LDADD += lib_dbus_interface.a
|
||||
noinst_LIBRARIES += lib_dbus_interface.a
|
||||
lib_dbus_interface_a_SOURCES = dbus-interface.c
|
||||
shairport_sync_SOURCES += dbus-service.c
|
||||
BUILT_SOURCES += dbus-interface.h dbus-interface.c
|
||||
# We don't want to install this header
|
||||
noinst_HEADERS += $(BUILT_SOURCES)
|
||||
# Correctly clean the generated headers, but keep the xml description
|
||||
CLEANFILES += $(BUILT_SOURCES)
|
||||
|
||||
dbus-interface.h dbus-interface.c: org.gnome.ShairportSync.xml
|
||||
dbus-interface.c: org.gnome.ShairportSync.xml
|
||||
gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-interface org.gnome.ShairportSync.xml
|
||||
dbus-interface.h: dbus-interface.c
|
||||
touch dbus-interface.h
|
||||
endif
|
||||
|
||||
if USE_MPRIS
|
||||
shairport_sync_SOURCES += mpris-service.c mpris-interface.c
|
||||
shairport_sync_LDADD += lib_mpris_interface.a
|
||||
noinst_LIBRARIES += lib_mpris_interface.a
|
||||
lib_mpris_interface_a_SOURCES = mpris-interface.c
|
||||
shairport_sync_SOURCES += mpris-service.c
|
||||
BUILT_SOURCES += mpris-interface.h mpris-interface.c
|
||||
# We don't want to install this header
|
||||
noinst_HEADERS += $(BUILT_SOURCES)
|
||||
# Correctly clean the generated headers, but keep the xml description
|
||||
CLEANFILES += $(BUILT_SOURCES)
|
||||
|
||||
mpris-interface.h mpris-interface.c: org.mpris.MediaPlayer2.xml
|
||||
mpris-interface.c: org.mpris.MediaPlayer2.xml
|
||||
gdbus-codegen --interface-prefix org.mpris --generate-c-code mpris-interface org.mpris.MediaPlayer2.xml
|
||||
mpris-interface.h: mpris-interface.c
|
||||
touch mpris-interface.h
|
||||
endif
|
||||
|
||||
noinst_PROGRAMS =
|
||||
@@ -116,32 +146,47 @@ noinst_PROGRAMS =
|
||||
if USE_DBUS_CLIENT
|
||||
#Make it, but don't install it anywhere
|
||||
noinst_PROGRAMS += shairport-sync-dbus-test-client
|
||||
shairport_sync_dbus_test_client_SOURCES = dbus-interface.c dbus-interface.h shairport-sync-dbus-test-client.c
|
||||
shairport_sync_dbus_test_client_SOURCES = shairport-sync-dbus-test-client.c
|
||||
shairport_sync_dbus_test_client_LDADD = lib_dbus_interface.a
|
||||
endif
|
||||
|
||||
if USE_MPRIS_CLIENT
|
||||
#Make it, but don't install it anywhere
|
||||
noinst_PROGRAMS += shairport-sync-mpris-test-client
|
||||
shairport_sync_mpris_test_client_SOURCES = mpris-interface.c mpris-interface.h shairport-sync-mpris-test-client.c
|
||||
shairport_sync_mpris_test_client_SOURCES = shairport-sync-mpris-test-client.c
|
||||
shairport_sync_mpris_test_client_LDADD = lib_mpris_interface.a
|
||||
endif
|
||||
|
||||
install-exec-hook:
|
||||
if BUILD_FOR_LINUX
|
||||
DBUS_POLICY_DIR=$(DESTDIR)/etc/dbus-1/system.d
|
||||
else
|
||||
DBUS_POLICY_DIR=$(DESTDIR)$(sysconfdir)/dbus-1/system.d
|
||||
endif
|
||||
if INSTALL_CONFIG_FILES
|
||||
[ -e $(DESTDIR)$(sysconfdir) ] || mkdir $(DESTDIR)$(sysconfdir)
|
||||
cp scripts/shairport-sync.conf $(DESTDIR)$(sysconfdir)/shairport-sync.conf.sample
|
||||
[ -f $(DESTDIR)$(sysconfdir)/shairport-sync.conf ] || cp scripts/shairport-sync.conf $(DESTDIR)$(sysconfdir)/shairport-sync.conf
|
||||
if USE_DBUS
|
||||
cp scripts/shairport-sync-dbus-policy.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-dbus.conf
|
||||
[ -e $(DBUS_POLICY_DIR) ] || mkdir -p $(DBUS_POLICY_DIR)
|
||||
if INSTALL_CYGWIN_SERVICE
|
||||
cp scripts/shairport-sync-dbus-policy-cygwin.conf $(DBUS_POLICY_DIR)/shairport-sync-dbus.conf
|
||||
else
|
||||
cp scripts/shairport-sync-dbus-policy.conf $(DBUS_POLICY_DIR)/shairport-sync-dbus.conf
|
||||
endif
|
||||
endif
|
||||
if USE_MPRIS
|
||||
cp scripts/shairport-sync-mpris-policy.conf $(DESTDIR)$(sysconfdir)/dbus-1/system.d/shairport-sync-mpris.conf
|
||||
[ -e $(DBUS_POLICY_DIR) ] || mkdir -p $(DBUS_POLICY_DIR)
|
||||
if INSTALL_CYGWIN_SERVICE
|
||||
cp scripts/shairport-sync-mpris-policy-cygwin.conf $(DBUS_POLICY_DIR)/shairport-sync-mpris.conf
|
||||
else
|
||||
cp scripts/shairport-sync-mpris-policy.conf $(DBUS_POLICY_DIR)/shairport-sync-mpris.conf
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
if INSTALL_SYSTEMV
|
||||
getent group shairport-sync &>/dev/null || groupadd -r shairport-sync >/dev/null
|
||||
getent passwd shairport-sync &> /dev/null || useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync >/dev/null
|
||||
[ -e /var/run/shairport-sync ] || mkdir -p /var/run/shairport-sync
|
||||
chown shairport-sync:shairport-sync /var/run/shairport-sync
|
||||
[ -e $(DESTDIR)$(sysconfdir)/init.d ] || mkdir -p $(DESTDIR)$(sysconfdir)/init.d
|
||||
[ -f $(DESTDIR)$(sysconfdir)/init.d/shairport-sync ] || cp scripts/shairport-sync $(DESTDIR)$(sysconfdir)/init.d/
|
||||
endif
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
+14
-48
@@ -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).
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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 <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+1429
-583
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -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,
|
||||
|
||||
+6
-3
@@ -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,
|
||||
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* jack output driver. This file is part of Shairport Sync.
|
||||
* Copyright (c) 2019 Mike Brady <mikebrady@iercom.net>,
|
||||
* Jörn Nettingsmeier <nettings@luchtbeweging.nl>
|
||||
*
|
||||
* 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 <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
|
||||
// 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<NPORTS; i++) {
|
||||
jack_port_get_latency_range(port[i], JackPlaybackLatency, &latest_latency_range[i]);
|
||||
debug(2, "JACK latency for port %s\tmin: %d\t max: %d",
|
||||
port_name[i], latest_latency_range[i].min, latest_latency_range[i].max);
|
||||
latency += latest_latency_range[i].max;
|
||||
}
|
||||
latency /= NPORTS;
|
||||
jack_latency = latency;
|
||||
debug(1, "Average maximum JACK latency across all ports: %d", jack_latency);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This the function JACK will call in case of an error in the library.
|
||||
static void error(const char *desc) {
|
||||
warn("JACK error: \"%s\"", desc);
|
||||
}
|
||||
|
||||
// This is the function JACK will call in case of a non-critical event in the library.
|
||||
static void info(const char *desc) {
|
||||
inform("JACK information: \"%s\"", desc);
|
||||
}
|
||||
|
||||
int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
|
||||
int i;
|
||||
config.audio_backend_latency_offset = 0;
|
||||
config.audio_backend_buffer_desired_length = 0.500;
|
||||
// Below this, soxr interpolation will not occur -- it'll be basic interpolation
|
||||
// instead.
|
||||
config.audio_backend_buffer_interpolation_threshold_in_seconds = 0.25;
|
||||
|
||||
// Do the "general" audio options. Note, these options are in the "general" stanza!
|
||||
parse_general_audio_options();
|
||||
|
||||
// Now the options specific to the backend, from the "jack" stanza:
|
||||
if (config.cfg != NULL) {
|
||||
const char *str;
|
||||
if (config_lookup_string(config.cfg, "jack.client_name", &str)) {
|
||||
config.jack_client_name = (char *)str;
|
||||
}
|
||||
if (config_lookup_string(config.cfg, "jack.autoconnect_pattern", &str)) {
|
||||
config.jack_autoconnect_pattern = (char *)str;
|
||||
}
|
||||
}
|
||||
if (config.jack_client_name == NULL)
|
||||
config.jack_client_name = strdup("shairport-sync");
|
||||
|
||||
jackbuf = jack_ringbuffer_create(buffer_size);
|
||||
if (jackbuf == NULL)
|
||||
die("Can't allocate %d bytes for the JACK ringbuffer.", buffer_size);
|
||||
// Lock the ringbuffer into memory so that it never gets paged out, which would
|
||||
// break realtime constraints.
|
||||
jack_ringbuffer_mlock(jackbuf);
|
||||
// This mutex should not be necessary, but removing it causes segfaults on
|
||||
// shutdown. Apparently, there are multiple threads in the main program trying
|
||||
// to do stuff. FIXME: Try to consolidate into one thread and get rid of this lock.
|
||||
pthread_mutex_lock(&client_mutex);
|
||||
jack_status_t status;
|
||||
client = jack_client_open(config.jack_client_name, JackNoStartServer, &status);
|
||||
if (!client) {
|
||||
die("Could not start JACK server. JackStatus is %x", status);
|
||||
}
|
||||
sample_rate = jack_get_sample_rate(client);
|
||||
if (sample_rate != 44100) {
|
||||
die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync."
|
||||
" Must be 44100 Hz.", sample_rate);
|
||||
}
|
||||
jack_set_process_callback(client, &process, NULL);
|
||||
jack_set_graph_order_callback(client, &graph, NULL);
|
||||
jack_set_error_function(&error);
|
||||
jack_set_info_function(&info);
|
||||
for (i=0; i < NPORTS; i++) {
|
||||
port[i] = jack_port_register(client, port_name[i], JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
}
|
||||
if (jack_activate(client)) {
|
||||
die("Could not activate %s JACK client.", config.jack_client_name);
|
||||
} else {
|
||||
debug(2, "JACK client %s activated sucessfully.", config.jack_client_name);
|
||||
}
|
||||
if (config.jack_autoconnect_pattern != NULL) {
|
||||
inform("config.jack_autoconnect_pattern is %s. If you see the program die after this,"
|
||||
"you made a syntax error.", config.jack_autoconnect_pattern);
|
||||
// Sadly, this will throw a segfault if the user provides a syntactically incorrect regex.
|
||||
// I've reported it to the jack-devel mailing list, they're in a better place to fix it.
|
||||
const char** port_list = jack_get_ports(client, config.jack_autoconnect_pattern,
|
||||
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput);
|
||||
if (port_list != NULL) {
|
||||
for (i = 0; i < NPORTS ; i++) {
|
||||
char* full_port_name[NPORTS];
|
||||
full_port_name[i] = malloc(sizeof(char) * jack_port_name_size());
|
||||
sprintf(full_port_name[i], "%s:%s", config.jack_client_name, port_name[i]);
|
||||
if (port_list[i] != NULL) {
|
||||
int err;
|
||||
debug(2, "Connecting %s to %s.", full_port_name[i], port_list[i]);
|
||||
err = jack_connect(client, full_port_name[i], port_list[i]);
|
||||
switch (err) {
|
||||
case EEXIST:
|
||||
inform("The requested connection from %s to %s already exists.",
|
||||
full_port_name[i], port_list[i]);
|
||||
break;
|
||||
case 0:
|
||||
// success
|
||||
break;
|
||||
default:
|
||||
warn("JACK error no. %d occured while trying to connect %s to %s.",
|
||||
err, full_port_name[i], port_list[i]);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
inform("No matching port found in %s to connect %s to. You may not hear audio.",
|
||||
config.jack_autoconnect_pattern, full_port_name[i]);
|
||||
}
|
||||
free(full_port_name[i]);
|
||||
}
|
||||
while (port_list[i++] != NULL) {
|
||||
inform("Additional matching port %s found. Check that the connections are what you intended.");
|
||||
}
|
||||
jack_free(port_list);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&client_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void jack_deinit() {
|
||||
pthread_mutex_lock(&client_mutex);
|
||||
if (jack_deactivate(client))
|
||||
warn("Error deactivating jack client");
|
||||
if (jack_client_close(client))
|
||||
warn("Error closing jack client");
|
||||
pthread_mutex_unlock(&client_mutex);
|
||||
jack_ringbuffer_free(jackbuf);
|
||||
}
|
||||
|
||||
void jack_start(__attribute__((unused)) int i_sample_rate,
|
||||
__attribute__((unused)) int i_sample_format) {
|
||||
// Nothing to do, JACK client has already been set up at jack_init().
|
||||
// Also, we have no say over the sample rate or sample format of JACK,
|
||||
// We convert the 16bit samples to float, and die if the sample rate is != 44k1.
|
||||
// FIXME: later, resampling would be nice. Fold into soxr if possible.
|
||||
}
|
||||
|
||||
void jack_flush() {
|
||||
debug(2, "Only the consumer can safely flush a lock-free ringbuffer. Asking the"
|
||||
" process callback to do it...");
|
||||
flush_please = 1;
|
||||
}
|
||||
|
||||
int jack_delay(long *the_delay) {
|
||||
// Semantics change: we now look at the last transfer into the lock-free
|
||||
// ringbuffer, not into the jack buffers directly (because locking those would
|
||||
// violate real-time constraints). On average, that should lead to just a
|
||||
// constant additional latency.
|
||||
// Without the mutex, we could get the time of what is the last transfer of data
|
||||
// to a jack buffer, but then a transfer could occur and we would get the buffer
|
||||
// occupancy after another transfer had occurred, so we could "lose" a full transfer
|
||||
// (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds)
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
int64_t time_now = get_absolute_time_in_fp();
|
||||
int64_t delta = time_now - time_of_latest_transfer;
|
||||
size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame;
|
||||
debug(2, "audio_occupancy_now is %d.", audio_occupancy_now);
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
|
||||
int64_t frames_processed_since_latest_latency_check = (delta * 44100) >> 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;
|
||||
}
|
||||
+27
-6
@@ -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,
|
||||
|
||||
+15
-5
@@ -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,
|
||||
|
||||
+23
-14
@@ -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() {
|
||||
|
||||
+7
-3
@@ -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,
|
||||
|
||||
+5
-4
@@ -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,
|
||||
|
||||
@@ -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 <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
#include <openssl/evp.h>
|
||||
@@ -54,7 +55,7 @@
|
||||
#include <openssl/rsa.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
#include "polarssl/ctr_drbg.h"
|
||||
#include "polarssl/entropy.h"
|
||||
#include <polarssl/base64.h>
|
||||
@@ -67,37 +68,100 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#include "mbedtls/ctr_drbg.h"
|
||||
#include "mbedtls/entropy.h"
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/version.h>
|
||||
#include <mbedtls/x509.h>
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LIBDAEMON
|
||||
#include <libdaemon/dlog.h>
|
||||
#else
|
||||
#include <syslog.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define _COMMON_H
|
||||
|
||||
#include <libconfig.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/socket.h>
|
||||
@@ -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
|
||||
|
||||
+127
-84
@@ -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=<pathname> Specify a pathname to a directory in which to write the PID file.], [
|
||||
@@ -133,10 +127,26 @@ AC_ARG_WITH(piddir, [ --with-piddir=<pathname> 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+171
-27
@@ -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;
|
||||
}
|
||||
|
||||
@@ -6,5 +6,7 @@
|
||||
ShairportSync *shairportSyncSkeleton;
|
||||
|
||||
int start_dbus_service();
|
||||
void stop_dbus_service();
|
||||
int dbus_service_is_running();
|
||||
|
||||
#endif /* #ifndef DBUS_SERVICE_H */
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
+104
-61
@@ -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
|
||||
|
||||
+626
-272
File diff suppressed because it is too large
Load Diff
+629
-278
File diff suppressed because it is too large
Load Diff
@@ -34,27 +34,32 @@
|
||||
|
||||
#ifdef CONFIG_AVAHI
|
||||
extern mdns_backend mdns_avahi;
|
||||
extern mdns_backend mdns_external_avahi;
|
||||
#endif
|
||||
#ifdef CONFIG_HAVE_DNS_SD_H
|
||||
#ifdef CONFIG_DNS_SD
|
||||
extern mdns_backend mdns_dns_sd;
|
||||
extern mdns_backend mdns_external_dns_sd;
|
||||
#endif
|
||||
#ifdef CONFIG_TINYSVCMDNS
|
||||
extern mdns_backend mdns_tinysvcmdns;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EXTERNAL_MDNS
|
||||
extern mdns_backend mdns_external_avahi;
|
||||
extern mdns_backend mdns_external_dns_sd;
|
||||
#endif
|
||||
|
||||
static mdns_backend *mdns_backends[] = {
|
||||
#ifdef CONFIG_AVAHI
|
||||
&mdns_avahi,
|
||||
&mdns_external_avahi,
|
||||
#endif
|
||||
#ifdef CONFIG_HAVE_DNS_SD_H
|
||||
#ifdef CONFIG_DNS_SD
|
||||
&mdns_dns_sd,
|
||||
&mdns_external_dns_sd,
|
||||
#endif
|
||||
#ifdef CONFIG_TINYSVCMDNS
|
||||
&mdns_tinysvcmdns,
|
||||
#endif
|
||||
#ifdef CONFIG_EXTERNAL_MDNS
|
||||
&mdns_external_avahi,
|
||||
&mdns_external_dns_sd,
|
||||
#endif
|
||||
NULL};
|
||||
|
||||
@@ -96,34 +101,38 @@ void mdns_register(void) {
|
||||
|
||||
if (config.mdns == NULL)
|
||||
die("Could not establish mDNS advertisement!");
|
||||
|
||||
mdns_dacp_monitor_start(); // create a dacp monitor thread
|
||||
}
|
||||
|
||||
void mdns_unregister(void) {
|
||||
mdns_dacp_monitor_stop();
|
||||
if (config.mdns) {
|
||||
config.mdns->mdns_unregister();
|
||||
}
|
||||
}
|
||||
|
||||
void *mdns_dacp_monitor(char *dacp_id) {
|
||||
void *reply = NULL;
|
||||
if ((dacp_id != NULL) && (*dacp_id != '\0')) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor)) {
|
||||
reply = config.mdns->mdns_dacp_monitor(dacp_id);
|
||||
if (reply == NULL) {
|
||||
debug(1, "Error starting a DACP monitor.");
|
||||
}
|
||||
} else
|
||||
debug(3, "Can't start a DACP monitor -- none registered.");
|
||||
}
|
||||
return reply;
|
||||
void mdns_dacp_monitor_start(void) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor_start)) {
|
||||
config.mdns->mdns_dacp_monitor_start();
|
||||
} else
|
||||
debug(3, "Can't start a DACP monitor -- no mdns_dacp_monitor start registered.");
|
||||
}
|
||||
|
||||
void mdns_dacp_dont_monitor(void *userdata) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_dont_monitor)) {
|
||||
config.mdns->mdns_dacp_dont_monitor(userdata);
|
||||
void mdns_dacp_monitor_stop() {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor_stop)) {
|
||||
config.mdns->mdns_dacp_monitor_stop();
|
||||
} else
|
||||
debug(3, "Can't stop a DACP monitor -- none registered.");
|
||||
debug(3, "Can't stop a DACP monitor -- no mdns_dacp_monitor_stop registered.");
|
||||
}
|
||||
|
||||
void mdns_dacp_monitor_set_id(const char *dacp_id) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor_set_id)) {
|
||||
config.mdns->mdns_dacp_monitor_set_id(dacp_id);
|
||||
} else
|
||||
debug(3, "Can't set dacp_id -- no mdns_dacp_set_id registered.");
|
||||
}
|
||||
|
||||
void mdns_ls_backends(void) {
|
||||
mdns_backend **b = NULL;
|
||||
printf("Available mDNS backends: \n");
|
||||
|
||||
@@ -9,8 +9,9 @@ extern int mdns_pid;
|
||||
|
||||
void mdns_unregister(void);
|
||||
void mdns_register(void);
|
||||
void *mdns_dacp_monitor(char *dacp_id);
|
||||
void mdns_dacp_dont_monitor(void *userdata);
|
||||
void mdns_dacp_monitor_start();
|
||||
void mdns_dacp_monitor_stop(void);
|
||||
void mdns_dacp_monitor_set_id(const char *dacp_id);
|
||||
|
||||
void mdns_ls_backends(void);
|
||||
|
||||
@@ -18,8 +19,9 @@ typedef struct {
|
||||
char *name;
|
||||
int (*mdns_register)(char *apname, int port);
|
||||
void (*mdns_unregister)(void);
|
||||
void *(*mdns_dacp_monitor)(char *);
|
||||
void (*mdns_dacp_dont_monitor)(void *);
|
||||
void (*mdns_dacp_monitor_start)();
|
||||
void (*mdns_dacp_monitor_set_id)(const char *);
|
||||
void (*mdns_dacp_monitor_stop)();
|
||||
} mdns_backend;
|
||||
|
||||
#ifdef CONFIG_METADATA
|
||||
|
||||
+112
-107
@@ -33,7 +33,7 @@
|
||||
#include "common.h"
|
||||
#include "mdns.h"
|
||||
#include "rtsp.h"
|
||||
#ifdef HAVE_DACP_CLIENT
|
||||
#ifdef CONFIG_DACP_CLIENT
|
||||
#include "dacp.h"
|
||||
#endif
|
||||
#include <string.h>
|
||||
@@ -61,6 +61,8 @@ typedef struct {
|
||||
char *dacp_id;
|
||||
} dacp_browser_struct;
|
||||
|
||||
dacp_browser_struct private_dbs;
|
||||
|
||||
// static AvahiServiceBrowser *sb = NULL;
|
||||
static AvahiClient *client = NULL;
|
||||
// static AvahiClient *service_client = NULL;
|
||||
@@ -84,33 +86,35 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
|
||||
|
||||
/* Called whenever a service has been resolved successfully or timed out */
|
||||
switch (event) {
|
||||
case AVAHI_RESOLVER_FAILURE:
|
||||
debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name,
|
||||
type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
|
||||
break;
|
||||
case AVAHI_RESOLVER_FOUND: {
|
||||
// char a[AVAHI_ADDRESS_STR_MAX], *t;
|
||||
// debug(1, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain);
|
||||
case AVAHI_RESOLVER_FAILURE:
|
||||
debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name,
|
||||
type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
|
||||
break;
|
||||
case AVAHI_RESOLVER_FOUND: {
|
||||
// char a[AVAHI_ADDRESS_STR_MAX], *t;
|
||||
debug(3, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain);
|
||||
if (dbs->dacp_id) {
|
||||
char *dacpid = strstr(name, "iTunes_Ctrl_");
|
||||
if (dacpid) {
|
||||
dacpid += strlen("iTunes_Ctrl_");
|
||||
if (strcmp(dacpid, dbs->dacp_id) == 0) {
|
||||
debug(3, "Client's DACP port: %u.", port);
|
||||
#ifdef HAVE_DACP_CLIENT
|
||||
debug(3, "Client \"%s\"'s DACP port: %u.", dbs->dacp_id, port);
|
||||
#ifdef CONFIG_DACP_CLIENT
|
||||
dacp_monitor_port_update_callback(dacpid, port);
|
||||
#endif
|
||||
#ifdef CONFIG_METADATA
|
||||
#endif
|
||||
#ifdef CONFIG_METADATA
|
||||
char portstring[20];
|
||||
memset(portstring, 0, sizeof(portstring));
|
||||
snprintf(portstring, sizeof(portstring), "%u", port);
|
||||
send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// debug(1,"service resolver freed by resolve_callback");
|
||||
check_avahi_response(1, avahi_service_resolver_free(r));
|
||||
}
|
||||
@@ -128,7 +132,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
|
||||
avahi_threaded_poll_quit(tpoll);
|
||||
break;
|
||||
case AVAHI_BROWSER_NEW:
|
||||
debug(3, "(Browser) NEW: service '%s' of type '%s' in domain '%s'.", name, type, domain);
|
||||
// debug(1, "(Browser) NEW: service '%s' of type '%s' in domain '%s'.", name, type, domain);
|
||||
/* We ignore the returned resolver object. In the callback
|
||||
function we free it. If the server is terminated before
|
||||
the callback function is called the server will free
|
||||
@@ -140,11 +144,11 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
|
||||
break;
|
||||
case AVAHI_BROWSER_REMOVE:
|
||||
debug(3, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'.", name, type, domain);
|
||||
#ifdef HAVE_DACP_CLIENT
|
||||
#ifdef CONFIG_DACP_CLIENT
|
||||
char *dacpid = strstr(name, "iTunes_Ctrl_");
|
||||
if (dacpid) {
|
||||
dacpid += strlen("iTunes_Ctrl_");
|
||||
if (strcmp(dacpid, dbs->dacp_id) == 0)
|
||||
if ((dbs->dacp_id) && (strcmp(dacpid, dbs->dacp_id) == 0))
|
||||
dacp_monitor_port_update_callback(dbs->dacp_id, 0); // say the port is withdrawn
|
||||
} else {
|
||||
debug(1, "Browse callback: Can't see a DACP string in a DACP Record!");
|
||||
@@ -164,47 +168,47 @@ static void register_service(AvahiClient *c);
|
||||
static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
|
||||
AVAHI_GCC_UNUSED void *userdata) {
|
||||
switch (state) {
|
||||
case AVAHI_ENTRY_GROUP_ESTABLISHED:
|
||||
/* The entry group has been established successfully */
|
||||
debug(1, "avahi: service '%s' successfully added.", service_name);
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_ESTABLISHED:
|
||||
/* The entry group has been established successfully */
|
||||
debug(1, "avahi: service '%s' successfully added.", service_name);
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_COLLISION: {
|
||||
char *n;
|
||||
case AVAHI_ENTRY_GROUP_COLLISION: {
|
||||
char *n;
|
||||
|
||||
/* A service name collision with a remote service
|
||||
* happened. Let's pick a new name */
|
||||
debug(1, "avahi name collision -- look for another");
|
||||
n = avahi_alternative_service_name(service_name);
|
||||
if (service_name)
|
||||
avahi_free(service_name);
|
||||
else
|
||||
debug(1, "avahi attempt to free a NULL service name");
|
||||
service_name = n;
|
||||
/* A service name collision with a remote service
|
||||
* happened. Let's pick a new name */
|
||||
debug(1, "avahi name collision -- look for another");
|
||||
n = avahi_alternative_service_name(service_name);
|
||||
if (service_name)
|
||||
avahi_free(service_name);
|
||||
else
|
||||
debug(1, "avahi attempt to free a NULL service name");
|
||||
service_name = n;
|
||||
|
||||
debug(2, "avahi: service name collision, renaming service to '%s'", service_name);
|
||||
debug(2, "avahi: service name collision, renaming service to '%s'", service_name);
|
||||
|
||||
/* And recreate the services */
|
||||
register_service(avahi_entry_group_get_client(g));
|
||||
break;
|
||||
}
|
||||
/* And recreate the services */
|
||||
register_service(avahi_entry_group_get_client(g));
|
||||
break;
|
||||
}
|
||||
|
||||
case AVAHI_ENTRY_GROUP_FAILURE:
|
||||
debug(1, "avahi: entry group failure: %s",
|
||||
avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_FAILURE:
|
||||
debug(1, "avahi: entry group failure: %s",
|
||||
avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||
debug(2, "avahi: service '%s' group is not yet committed.", service_name);
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||
debug(2, "avahi: service '%s' group is not yet committed.", service_name);
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
||||
debug(2, "avahi: service '%s' group is registering.", service_name);
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
||||
debug(2, "avahi: service '%s' group is registering.", service_name);
|
||||
break;
|
||||
|
||||
default:
|
||||
debug(1, "avahi: unhandled egroup state: %d", state);
|
||||
break;
|
||||
default:
|
||||
debug(1, "avahi: unhandled egroup state: %d", state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,45 +389,40 @@ static int avahi_register(char *srvname, int srvport) {
|
||||
static void avahi_unregister(void) {
|
||||
debug(1, "avahi: avahi_unregister.");
|
||||
if (tpoll) {
|
||||
debug(1, "avahi: stop the threaded poll.");
|
||||
avahi_threaded_poll_stop(tpoll);
|
||||
|
||||
if (client) {
|
||||
debug(1, "avahi: free the client.");
|
||||
avahi_client_free(client);
|
||||
client = NULL;
|
||||
} else {
|
||||
debug(1, "avahi attempting to unregister a NULL client");
|
||||
}
|
||||
debug(1, "avahi: free the threaded poll.");
|
||||
avahi_threaded_poll_free(tpoll);
|
||||
tpoll = NULL;
|
||||
} else {
|
||||
debug(1, "No avahi threaded poll.");
|
||||
}
|
||||
|
||||
if (service_name)
|
||||
if (service_name) {
|
||||
debug(1, "avahi: free the service name.");
|
||||
free(service_name);
|
||||
else
|
||||
} else
|
||||
debug(1, "avahi attempt to free NULL service name");
|
||||
service_name = NULL;
|
||||
}
|
||||
|
||||
void *avahi_dacp_monitor(char *dacp_id) {
|
||||
dacp_browser_struct *dbs = (dacp_browser_struct *)malloc(sizeof(dacp_browser_struct));
|
||||
|
||||
if (dbs == NULL)
|
||||
die("can not allocate a dacp_browser_struct.");
|
||||
|
||||
char *t = strdup(dacp_id);
|
||||
if (t)
|
||||
dbs->dacp_id = t;
|
||||
else {
|
||||
die("can not allocate a dacp_id string in dacp_browser_struct.");
|
||||
}
|
||||
void avahi_dacp_monitor_start(void) {
|
||||
dacp_browser_struct *dbs = &private_dbs;
|
||||
memset((void *)&private_dbs, 0, sizeof(dacp_browser_struct));
|
||||
|
||||
// create the threaded poll code
|
||||
int err;
|
||||
if (!(dbs->service_poll = avahi_threaded_poll_new())) {
|
||||
warn("couldn't create avahi threaded service_poll!");
|
||||
free(dbs->dacp_id);
|
||||
free((char *)dbs);
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// create the service client
|
||||
@@ -432,66 +431,72 @@ void *avahi_dacp_monitor(char *dacp_id) {
|
||||
service_client_callback, (void *)dbs, &err))) {
|
||||
warn("couldn't create avahi service client: %s!", avahi_strerror(err));
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
free(dbs->dacp_id);
|
||||
free((char *)dbs);
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the service browser */
|
||||
if (!(dbs->service_browser =
|
||||
avahi_service_browser_new(dbs->service_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
|
||||
"_dacp._tcp", NULL, 0, browse_callback, (void *)dbs))) {
|
||||
warn("failed to create avahi service browser: %s\n",
|
||||
avahi_strerror(avahi_client_errno(dbs->service_client)));
|
||||
avahi_client_free(dbs->service_client);
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
free(dbs->dacp_id);
|
||||
free((char *)dbs);
|
||||
return NULL;
|
||||
}
|
||||
// start the polling thread
|
||||
if (avahi_threaded_poll_start(dbs->service_poll) < 0) {
|
||||
warn("couldn't start avahi service_poll thread");
|
||||
avahi_service_browser_free(dbs->service_browser);
|
||||
avahi_client_free(dbs->service_client);
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
free(dbs->dacp_id);
|
||||
free((char *)dbs);
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
debug(3, "Avahi DACP monitor successfully started");
|
||||
return (void *)dbs;
|
||||
return;
|
||||
}
|
||||
|
||||
void avahi_dacp_dont_monitor(void *userdata) {
|
||||
debug(3,"avahi_dacp_dont_monitor");
|
||||
if (userdata) {
|
||||
dacp_browser_struct *dbs = (dacp_browser_struct *)userdata;
|
||||
// stop and dispose of everything
|
||||
/*if (dbs->service_poll)
|
||||
avahi_threaded_poll_stop((dbs)->service_poll);
|
||||
*/
|
||||
if (dbs->service_poll) {
|
||||
avahi_threaded_poll_stop(dbs->service_poll);
|
||||
void avahi_dacp_monitor_set_id(const char *dacp_id) {
|
||||
debug(3, "avahi_dacp_dont_monitor");
|
||||
dacp_browser_struct *dbs = &private_dbs;
|
||||
|
||||
if (dbs->dacp_id)
|
||||
free(dbs->dacp_id);
|
||||
if (dacp_id == NULL)
|
||||
dbs->dacp_id = NULL;
|
||||
else {
|
||||
char *t = strdup(dacp_id);
|
||||
if (t) {
|
||||
dbs->dacp_id = t;
|
||||
avahi_threaded_poll_lock(dbs->service_poll);
|
||||
if (dbs->service_browser)
|
||||
avahi_service_browser_free(dbs->service_browser);
|
||||
if (dbs->service_client)
|
||||
avahi_client_free(dbs->service_client);
|
||||
avahi_threaded_poll_unlock(dbs->service_poll);
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
|
||||
if (!(dbs->service_browser =
|
||||
avahi_service_browser_new(dbs->service_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
|
||||
"_dacp._tcp", NULL, 0, browse_callback, (void *)dbs))) {
|
||||
warn("failed to create avahi service browser: %s\n",
|
||||
avahi_strerror(avahi_client_errno(dbs->service_client)));
|
||||
}
|
||||
avahi_threaded_poll_unlock(dbs->service_poll);
|
||||
} else {
|
||||
warn("avahi_dacp_set_id: can not allocate a dacp_id string in dacp_browser_struct.");
|
||||
}
|
||||
free(dbs->dacp_id);
|
||||
free(userdata);
|
||||
debug(3, "Avahi DACP monitor successfully stopped");
|
||||
} else {
|
||||
debug(1, "Avahi DACP Monitor is not running.");
|
||||
debug(3, "Search for DACP ID \"%s\".", t);
|
||||
}
|
||||
debug(3,"avahi_dacp_dont_monitor exit");
|
||||
}
|
||||
|
||||
void avahi_dacp_monitor_stop() {
|
||||
debug(3, "avahi_dacp_dont_monitor");
|
||||
dacp_browser_struct *dbs = &private_dbs;
|
||||
// stop and dispose of everything
|
||||
if (dbs->service_poll) {
|
||||
avahi_threaded_poll_stop(dbs->service_poll);
|
||||
avahi_threaded_poll_lock(dbs->service_poll);
|
||||
if (dbs->service_browser)
|
||||
avahi_service_browser_free(dbs->service_browser);
|
||||
if (dbs->service_client)
|
||||
avahi_client_free(dbs->service_client);
|
||||
avahi_threaded_poll_unlock(dbs->service_poll);
|
||||
avahi_threaded_poll_free(dbs->service_poll);
|
||||
}
|
||||
free(dbs->dacp_id);
|
||||
debug(3, "Avahi DACP monitor successfully stopped");
|
||||
}
|
||||
|
||||
mdns_backend mdns_avahi = {.name = "avahi",
|
||||
.mdns_register = avahi_register,
|
||||
.mdns_unregister = avahi_unregister,
|
||||
.mdns_dacp_monitor = avahi_dacp_monitor,
|
||||
.mdns_dacp_dont_monitor = avahi_dacp_dont_monitor};
|
||||
.mdns_dacp_monitor_start = avahi_dacp_monitor_start,
|
||||
.mdns_dacp_monitor_set_id = avahi_dacp_monitor_set_id,
|
||||
.mdns_dacp_monitor_stop = avahi_dacp_monitor_stop};
|
||||
|
||||
+4
-3
@@ -24,8 +24,8 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "mdns.h"
|
||||
#include "common.h"
|
||||
#include "mdns.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <dns_sd.h>
|
||||
#include <stdlib.h>
|
||||
@@ -93,5 +93,6 @@ static void mdns_dns_sd_unregister(void) {
|
||||
mdns_backend mdns_dns_sd = {.name = "dns-sd",
|
||||
.mdns_register = mdns_dns_sd_register,
|
||||
.mdns_unregister = mdns_dns_sd_unregister,
|
||||
.mdns_dacp_monitor = NULL,
|
||||
.mdns_dacp_dont_monitor = NULL};
|
||||
.mdns_dacp_monitor_start = NULL,
|
||||
.mdns_dacp_monitor_set_id = NULL,
|
||||
.mdns_dacp_monitor_stop = NULL};
|
||||
|
||||
+38
-39
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* mDNS registration handler. This file is part of Shairport.
|
||||
* Copyright (c) Paul Lietar 2013
|
||||
* Amendments and updates copyright (c) Mike Brady 2014 -- 2019
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
@@ -41,46 +42,42 @@ int mdns_pid = 0;
|
||||
*/
|
||||
static int fork_execvp(const char *file, char *const argv[]) {
|
||||
int execpipe[2];
|
||||
int pid = 0;
|
||||
if (pipe(execpipe) < 0) {
|
||||
return -1;
|
||||
}
|
||||
int response = -1;
|
||||
if (pipe(execpipe) >= 0) {
|
||||
|
||||
if (fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC) < 0) {
|
||||
close(execpipe[0]);
|
||||
close(execpipe[1]);
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC) < 0) {
|
||||
close(execpipe[0]);
|
||||
close(execpipe[1]);
|
||||
} else {
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
close(execpipe[0]);
|
||||
close(execpipe[1]);
|
||||
return -1;
|
||||
} else if (pid == 0) { // Child
|
||||
close(execpipe[0]); // Close the read end
|
||||
execvp(file, argv);
|
||||
int pid = fork();
|
||||
if (pid < 0) {
|
||||
close(execpipe[0]);
|
||||
close(execpipe[1]);
|
||||
} else if (pid == 0) { // Child
|
||||
close(execpipe[0]); // Close the read end
|
||||
execvp(file, argv);
|
||||
// If we reach this point then execve has failed.
|
||||
// Write erno's value into the pipe and exit.
|
||||
if (write(execpipe[1], &errno, sizeof(errno)) != sizeof(errno))
|
||||
debug(1,
|
||||
"Execve has failed and there was a further error writing an error message, duh.");
|
||||
die("mdns_external: execve has failed.");
|
||||
} else { // Parent
|
||||
close(execpipe[1]); // Close the write end
|
||||
|
||||
// If we reach this point then execve has failed.
|
||||
// Write erno's value into the pipe and exit.
|
||||
if (write(execpipe[1], &errno, sizeof(errno)) != sizeof(errno))
|
||||
debug(1, "Execve has failed and there was a further error writing an error message, duh.");
|
||||
debug(1, "execve has failed.");
|
||||
_exit(-1);
|
||||
return 0; // Just to make the compiler happy.
|
||||
} else { // Parent
|
||||
close(execpipe[1]); // Close the write end
|
||||
|
||||
int childErrno;
|
||||
// Block until child closes the pipe or sends errno.
|
||||
if (read(execpipe[0], &childErrno, sizeof(childErrno)) ==
|
||||
sizeof(childErrno)) { // We received errno
|
||||
errno = childErrno;
|
||||
return -1;
|
||||
} else { // Child closed the pipe. execvp was successful.
|
||||
return pid;
|
||||
int childErrno;
|
||||
// Block until child closes the pipe or sends errno.
|
||||
if (read(execpipe[0], &childErrno, sizeof(childErrno)) ==
|
||||
sizeof(childErrno)) { // We received errno
|
||||
errno = childErrno;
|
||||
} else {
|
||||
response = pid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
static int mdns_external_avahi_register(char *apname, __attribute__((unused)) int port) {
|
||||
@@ -162,11 +159,13 @@ static void kill_mdns_child(void) {
|
||||
mdns_backend mdns_external_avahi = {.name = "external-avahi",
|
||||
.mdns_register = mdns_external_avahi_register,
|
||||
.mdns_unregister = kill_mdns_child,
|
||||
.mdns_dacp_monitor = NULL,
|
||||
.mdns_dacp_dont_monitor = NULL};
|
||||
.mdns_dacp_monitor_start = NULL,
|
||||
.mdns_dacp_monitor_set_id = NULL,
|
||||
.mdns_dacp_monitor_stop = NULL};
|
||||
|
||||
mdns_backend mdns_external_dns_sd = {.name = "external-dns-sd",
|
||||
.mdns_register = mdns_external_dns_sd_register,
|
||||
.mdns_unregister = kill_mdns_child,
|
||||
.mdns_dacp_monitor = NULL,
|
||||
.mdns_dacp_dont_monitor = NULL};
|
||||
.mdns_dacp_monitor_start = NULL,
|
||||
.mdns_dacp_monitor_set_id = NULL,
|
||||
.mdns_dacp_monitor_stop = NULL};
|
||||
|
||||
+3
-2
@@ -164,5 +164,6 @@ static void mdns_tinysvcmdns_unregister(void) {
|
||||
mdns_backend mdns_tinysvcmdns = {.name = "tinysvcmdns",
|
||||
.mdns_register = mdns_tinysvcmdns_register,
|
||||
.mdns_unregister = mdns_tinysvcmdns_unregister,
|
||||
.mdns_dacp_monitor = NULL,
|
||||
.mdns_dacp_dont_monitor = NULL};
|
||||
.mdns_dacp_monitor_start = NULL,
|
||||
.mdns_dacp_monitor_set_id = NULL,
|
||||
.mdns_dacp_monitor_stop = NULL};
|
||||
|
||||
+81
-48
@@ -45,18 +45,21 @@
|
||||
#include "dacp.h"
|
||||
#include "metadata_hub.h"
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#include <mbedtls/version.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
#include <polarssl/md5.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
#include <openssl/md5.h>
|
||||
#endif
|
||||
|
||||
int metadata_hub_initialised = 0;
|
||||
|
||||
pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store
|
||||
|
||||
@@ -90,10 +93,31 @@ void metadata_hub_release_track_metadata(struct track_metadata_bundle *track_met
|
||||
}
|
||||
}
|
||||
|
||||
void metadata_hub_release_track_artwork(void) {
|
||||
// debug(1,"release track artwork");
|
||||
release_char_string(&metadata_store.cover_art_pathname);
|
||||
}
|
||||
|
||||
void metadata_hub_init(void) {
|
||||
// debug(1, "Metadata bundle initialisation.");
|
||||
memset(&metadata_store, 0, sizeof(metadata_store));
|
||||
track_metadata = NULL;
|
||||
metadata_hub_initialised = 1;
|
||||
}
|
||||
|
||||
void metadata_hub_stop(void) {
|
||||
if (metadata_hub_initialised) {
|
||||
debug(2, "metadata_hub_stop.");
|
||||
metadata_hub_release_track_artwork();
|
||||
if (metadata_store.track_metadata) {
|
||||
metadata_hub_release_track_metadata(metadata_store.track_metadata);
|
||||
metadata_store.track_metadata = NULL;
|
||||
}
|
||||
if (track_metadata) {
|
||||
metadata_hub_release_track_metadata(track_metadata);
|
||||
track_metadata = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_metadata_watcher(metadata_watcher fn, void *userdata) {
|
||||
@@ -108,6 +132,26 @@ void add_metadata_watcher(metadata_watcher fn, void *userdata) {
|
||||
}
|
||||
}
|
||||
|
||||
void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) {
|
||||
// debug(1, "metadata_hub_unlock_hub_mutex_cleanup called.");
|
||||
pthread_rwlock_unlock(&metadata_hub_re_lock);
|
||||
}
|
||||
|
||||
void run_metadata_watchers(void) {
|
||||
int i;
|
||||
// debug(1, "locking metadata hub for reading");
|
||||
pthread_rwlock_rdlock(&metadata_hub_re_lock);
|
||||
pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL);
|
||||
for (i = 0; i < number_of_watchers; i++) {
|
||||
if (metadata_store.watchers[i]) {
|
||||
metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]);
|
||||
}
|
||||
}
|
||||
// debug(1, "unlocking metadata hub for reading");
|
||||
// pthread_rwlock_unlock(&metadata_hub_re_lock);
|
||||
pthread_cleanup_pop(1);
|
||||
}
|
||||
|
||||
void metadata_hub_modify_prolog(void) {
|
||||
// always run this before changing an entry or a sequence of entries in the metadata_hub
|
||||
// debug(1, "locking metadata hub for writing");
|
||||
@@ -118,24 +162,6 @@ void metadata_hub_modify_prolog(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void metadata_hub_release_track_artwork(void) {
|
||||
// debug(1,"release track artwork");
|
||||
release_char_string(&metadata_store.cover_art_pathname);
|
||||
}
|
||||
|
||||
void run_metadata_watchers(void) {
|
||||
int i;
|
||||
// debug(1, "locking metadata hub for reading");
|
||||
pthread_rwlock_rdlock(&metadata_hub_re_lock);
|
||||
for (i = 0; i < number_of_watchers; i++) {
|
||||
if (metadata_store.watchers[i]) {
|
||||
metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]);
|
||||
}
|
||||
}
|
||||
// debug(1, "unlocking metadata hub for reading");
|
||||
pthread_rwlock_unlock(&metadata_hub_re_lock);
|
||||
}
|
||||
|
||||
void metadata_hub_modify_epilog(int modified) {
|
||||
// always run this after changing an entry or a sequence of entries in the metadata_hub
|
||||
// debug(1, "unlocking metadata hub for writing");
|
||||
@@ -150,7 +176,7 @@ void metadata_hub_modify_epilog(int modified) {
|
||||
|
||||
if ((metadata_store.dacp_server_active == 0) &&
|
||||
(metadata_store.dacp_server_has_been_active != 0)) {
|
||||
debug(1, "dacp_scanner going inactive -- release track metadata and artwork");
|
||||
debug(2, "dacp_scanner going inactive -- release track metadata and artwork");
|
||||
if (metadata_store.track_metadata) {
|
||||
m = 1;
|
||||
metadata_hub_release_track_metadata(metadata_store.track_metadata);
|
||||
@@ -197,23 +223,30 @@ char *metadata_write_image_file(const char *buf, int len) {
|
||||
char *path = NULL; // this will be what is returned
|
||||
|
||||
uint8_t img_md5[16];
|
||||
// uint8_t ap_md5[16];
|
||||
// uint8_t ap_md5[16];
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
MD5_CTX ctx;
|
||||
MD5_Init(&ctx);
|
||||
MD5_Update(&ctx, buf, len);
|
||||
MD5_Final(img_md5, &ctx);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
mbedtls_md5_context tctx;
|
||||
mbedtls_md5_starts(&tctx);
|
||||
mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
|
||||
mbedtls_md5_finish(&tctx, img_md5);
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#if MBEDTLS_VERSION_MINOR >= 7
|
||||
mbedtls_md5_context tctx;
|
||||
mbedtls_md5_starts_ret(&tctx);
|
||||
mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len);
|
||||
mbedtls_md5_finish_ret(&tctx, img_md5);
|
||||
#else
|
||||
mbedtls_md5_context tctx;
|
||||
mbedtls_md5_starts(&tctx);
|
||||
mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
|
||||
mbedtls_md5_finish(&tctx, img_md5);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
md5_context tctx;
|
||||
md5_starts(&tctx);
|
||||
md5_update(&tctx, (const unsigned char *)buf, len);
|
||||
@@ -469,27 +502,15 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
|
||||
track_metadata = (struct track_metadata_bundle *)malloc(sizeof(struct track_metadata_bundle));
|
||||
if (track_metadata == NULL)
|
||||
die("Could not allocate memory for track metadata.");
|
||||
memset(track_metadata, 0, sizeof(struct track_metadata_bundle)); // now we have a valid track
|
||||
// metadata space, but the
|
||||
// metadata itself
|
||||
// might turin out to be invalid. Specifically, YouTube on iOS can generate a sequence of
|
||||
// track metadata that is invalid.
|
||||
// it is distinguished by having an item_id of zero.
|
||||
memset(track_metadata, 0, sizeof(struct track_metadata_bundle));
|
||||
break;
|
||||
case 'mden':
|
||||
if (track_metadata) {
|
||||
if ((track_metadata->item_id_received == 0) || (track_metadata->item_id != 0)) {
|
||||
// i.e. it's only invalid if it has definitely been given an item_id of zero
|
||||
metadata_hub_modify_prolog();
|
||||
metadata_hub_release_track_metadata(metadata_store.track_metadata);
|
||||
metadata_store.track_metadata = track_metadata;
|
||||
track_metadata = NULL;
|
||||
metadata_hub_modify_epilog(1);
|
||||
} else {
|
||||
debug(1, "The track information received is invalid -- dropping it");
|
||||
metadata_hub_release_track_metadata(track_metadata);
|
||||
track_metadata = NULL;
|
||||
}
|
||||
metadata_hub_modify_prolog();
|
||||
metadata_hub_release_track_metadata(metadata_store.track_metadata);
|
||||
metadata_store.track_metadata = track_metadata;
|
||||
track_metadata = NULL;
|
||||
metadata_hub_modify_epilog(1);
|
||||
}
|
||||
debug(2, "MH Metadata stream processing end.");
|
||||
break;
|
||||
@@ -539,6 +560,18 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
|
||||
break;
|
||||
// these could tell us about play / pause etc. but will only occur if metadata is enabled, so
|
||||
// we'll just ignore them
|
||||
case 'abeg': {
|
||||
metadata_hub_modify_prolog();
|
||||
int changed = (metadata_store.active_state != AM_ACTIVE);
|
||||
metadata_store.active_state = AM_ACTIVE;
|
||||
metadata_hub_modify_epilog(changed);
|
||||
} break;
|
||||
case 'aend': {
|
||||
metadata_hub_modify_prolog();
|
||||
int changed = (metadata_store.active_state != AM_INACTIVE);
|
||||
metadata_store.active_state = AM_INACTIVE;
|
||||
metadata_hub_modify_epilog(changed);
|
||||
} break;
|
||||
case 'pbeg': {
|
||||
metadata_hub_modify_prolog();
|
||||
int changed = (metadata_store.player_state != PS_PLAYING);
|
||||
|
||||
+8
-1
@@ -12,6 +12,11 @@ enum play_status_type {
|
||||
PS_PLAYING,
|
||||
} play_status_type;
|
||||
|
||||
enum active_state_type {
|
||||
AM_INACTIVE = 0,
|
||||
AM_ACTIVE,
|
||||
} active_state_type;
|
||||
|
||||
enum shuffle_status_type {
|
||||
SS_NOT_AVAILABLE = 0,
|
||||
SS_OFF,
|
||||
@@ -56,7 +61,7 @@ typedef struct metadata_bundle {
|
||||
char *client_ip; // IP number used by the audio source (i.e. the "client"), which is also the DACP
|
||||
// server
|
||||
char *server_ip; // IP number used by Shairport Sync
|
||||
char *progress_string; // progress string, emitted by the source from time to time
|
||||
char *progress_string; // progress string, emitted by the source from time to time
|
||||
int player_thread_active; // true if a play thread is running
|
||||
int dacp_server_active; // true if there's a reachable DACP server (assumed to be the Airplay
|
||||
// client) ; false otherwise
|
||||
@@ -77,6 +82,7 @@ typedef struct metadata_bundle {
|
||||
|
||||
enum play_status_type
|
||||
player_state; // this is the state of the actual player itself, which can be a bit noisy.
|
||||
enum active_state_type active_state;
|
||||
|
||||
int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the
|
||||
// speaker volume control
|
||||
@@ -92,6 +98,7 @@ struct metadata_bundle metadata_store;
|
||||
void add_metadata_watcher(metadata_watcher fn, void *userdata);
|
||||
|
||||
void metadata_hub_init(void);
|
||||
void metadata_hub_stop(void);
|
||||
void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length);
|
||||
void metadata_hub_reset_track_metadata(void);
|
||||
void metadata_hub_release_track_artwork(void);
|
||||
|
||||
+14
-3
@@ -123,7 +123,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
|
||||
} else 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);
|
||||
}
|
||||
@@ -182,6 +183,14 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
|
||||
// media_player2_player_set_volume(mprisPlayerPlayerSkeleton, metadata_store.speaker_volume);
|
||||
}
|
||||
|
||||
static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
debug(1, "quit requested (MPRIS interface).");
|
||||
pthread_cancel(main_thread_id);
|
||||
media_player2_complete_quit(skeleton, invocation);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
||||
__attribute__((unused)) gpointer user_data) {
|
||||
send_simple_dacp_command("nextitem");
|
||||
@@ -242,7 +251,7 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
|
||||
|
||||
media_player2_set_desktop_entry(mprisPlayerSkeleton, "shairport-sync");
|
||||
media_player2_set_identity(mprisPlayerSkeleton, "Shairport Sync");
|
||||
media_player2_set_can_quit(mprisPlayerSkeleton, FALSE);
|
||||
media_player2_set_can_quit(mprisPlayerSkeleton, TRUE);
|
||||
media_player2_set_can_raise(mprisPlayerSkeleton, FALSE);
|
||||
media_player2_set_has_track_list(mprisPlayerSkeleton, FALSE);
|
||||
media_player2_set_supported_uri_schemes(mprisPlayerSkeleton, empty_string_array);
|
||||
@@ -260,6 +269,8 @@ static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *nam
|
||||
media_player2_player_set_can_seek(mprisPlayerPlayerSkeleton, FALSE);
|
||||
media_player2_player_set_can_control(mprisPlayerPlayerSkeleton, TRUE);
|
||||
|
||||
g_signal_connect(mprisPlayerSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL);
|
||||
|
||||
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play", G_CALLBACK(on_handle_play), NULL);
|
||||
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-pause", G_CALLBACK(on_handle_pause), NULL);
|
||||
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play-pause", G_CALLBACK(on_handle_play_pause),
|
||||
@@ -291,7 +302,7 @@ static void on_mpris_name_lost(__attribute__((unused)) GDBusConnection *connecti
|
||||
// name,(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
|
||||
pid_t pid = getpid();
|
||||
char interface_name[256] = "";
|
||||
snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid);
|
||||
snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid);
|
||||
GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM;
|
||||
if (config.mpris_service_bus_type == DBT_session)
|
||||
mpris_bus_type = G_BUS_TYPE_SESSION;
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "player.h"
|
||||
#include "rtsp.h"
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
#include "dacp.h"
|
||||
#include "mqtt.h"
|
||||
#include <mosquitto.h>
|
||||
|
||||
// this holds the mosquitto client
|
||||
struct mosquitto *global_mosq = NULL;
|
||||
char *topic = NULL;
|
||||
int connected = 0;
|
||||
|
||||
// mosquitto logging
|
||||
void _cb_log(__attribute__((unused)) struct mosquitto *mosq, __attribute__((unused)) void *userdata,
|
||||
int level, const char *str) {
|
||||
switch (level) {
|
||||
case MOSQ_LOG_DEBUG:
|
||||
debug(1, str);
|
||||
break;
|
||||
case MOSQ_LOG_INFO:
|
||||
debug(2, str);
|
||||
break;
|
||||
case MOSQ_LOG_NOTICE:
|
||||
debug(3, str);
|
||||
break;
|
||||
case MOSQ_LOG_WARNING:
|
||||
inform(str);
|
||||
break;
|
||||
case MOSQ_LOG_ERR: {
|
||||
die("MQTT: Error: %s\n", str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mosquitto message handler
|
||||
void on_message(__attribute__((unused)) struct mosquitto *mosq,
|
||||
__attribute__((unused)) void *userdata, const struct mosquitto_message *msg) {
|
||||
|
||||
// null-terminate the payload
|
||||
char payload[msg->payloadlen + 1];
|
||||
memcpy(payload, msg->payload, msg->payloadlen);
|
||||
payload[msg->payloadlen] = 0;
|
||||
|
||||
debug(1, "[MQTT]: received Message on topic %s: %s\n", msg->topic, payload);
|
||||
|
||||
// All recognized commands
|
||||
char *commands[] = {"command", "beginff", "beginrew", "mutetoggle", "nextitem",
|
||||
"previtem", "pause", "playpause", "play", "stop",
|
||||
"playresume", "shuffle_songs", "volumedown", "volumeup", NULL};
|
||||
|
||||
int it = 0;
|
||||
|
||||
// send command if it's a valid one
|
||||
while (commands[it] != NULL) {
|
||||
if ((size_t)msg->payloadlen >= strlen(commands[it]) &&
|
||||
strncmp(msg->payload, commands[it], strlen(commands[it])) == 0) {
|
||||
debug(1, "[MQTT]: DACP Command: %s\n", commands[it]);
|
||||
send_simple_dacp_command(commands[it]);
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void on_disconnect(__attribute__((unused)) struct mosquitto *mosq,
|
||||
__attribute__((unused)) void *userdata, __attribute__((unused)) int rc) {
|
||||
connected = 0;
|
||||
debug(1, "[MQTT]: disconnected");
|
||||
}
|
||||
|
||||
void on_connect(struct mosquitto *mosq, __attribute__((unused)) void *userdata,
|
||||
__attribute__((unused)) int rc) {
|
||||
connected = 1;
|
||||
debug(1, "[MQTT]: connected");
|
||||
|
||||
// subscribe if requested
|
||||
if (config.mqtt_enable_remote) {
|
||||
char remotetopic[strlen(config.mqtt_topic) + 8];
|
||||
snprintf(remotetopic, strlen(config.mqtt_topic) + 8, "%s/remote", config.mqtt_topic);
|
||||
mosquitto_subscribe(mosq, NULL, remotetopic, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to publish under a topic and automatically append the main topic
|
||||
void mqtt_publish(char *topic, char *data, uint32_t length) {
|
||||
char fulltopic[strlen(config.mqtt_topic) + strlen(topic) + 3];
|
||||
snprintf(fulltopic, strlen(config.mqtt_topic) + strlen(topic) + 2, "%s/%s", config.mqtt_topic,
|
||||
topic);
|
||||
debug(1, "[MQTT]: publishing under %s", fulltopic);
|
||||
|
||||
int rc;
|
||||
if ((rc = mosquitto_publish(global_mosq, NULL, fulltopic, length, data, 0, 0)) !=
|
||||
MOSQ_ERR_SUCCESS) {
|
||||
switch (rc) {
|
||||
case MOSQ_ERR_NO_CONN:
|
||||
debug(1, "[MQTT]: Publish failed: not connected to broker");
|
||||
break;
|
||||
default:
|
||||
debug(1, "[MQTT]: Publish failed: unknown error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handler for incoming metadata
|
||||
void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length) {
|
||||
if (global_mosq == NULL || connected != 1) {
|
||||
debug(3, "[MQTT]: Client not connected, skipping metadata handling");
|
||||
return;
|
||||
}
|
||||
if (config.mqtt_publish_raw) {
|
||||
uint32_t val;
|
||||
char topic[] = "____/____";
|
||||
|
||||
val = htonl(type);
|
||||
memcpy(topic, &val, 4);
|
||||
val = htonl(code);
|
||||
memcpy(topic + 5, &val, 4);
|
||||
mqtt_publish(topic, data, length);
|
||||
}
|
||||
if (config.mqtt_publish_parsed) {
|
||||
if (type == 'core') {
|
||||
switch (code) {
|
||||
case 'asar':
|
||||
mqtt_publish("artist", data, length);
|
||||
break;
|
||||
case 'asal':
|
||||
mqtt_publish("album", data, length);
|
||||
break;
|
||||
case 'minm':
|
||||
mqtt_publish("title", data, length);
|
||||
break;
|
||||
case 'asgn':
|
||||
mqtt_publish("genre", data, length);
|
||||
break;
|
||||
case 'asfm':
|
||||
mqtt_publish("format", data, length);
|
||||
break;
|
||||
}
|
||||
} else if (type == 'ssnc') {
|
||||
switch (code) {
|
||||
case 'asal':
|
||||
mqtt_publish("songalbum", data, length);
|
||||
break;
|
||||
case 'pvol':
|
||||
mqtt_publish("volume", data, length);
|
||||
break;
|
||||
case 'clip':
|
||||
mqtt_publish("client_ip", data, length);
|
||||
break;
|
||||
case 'abeg':
|
||||
mqtt_publish("active_start", data, length);
|
||||
break;
|
||||
case 'aend':
|
||||
mqtt_publish("active_end", data, length);
|
||||
break;
|
||||
case 'pbeg':
|
||||
mqtt_publish("play_start", data, length);
|
||||
break;
|
||||
case 'pend':
|
||||
mqtt_publish("play_end", data, length);
|
||||
break;
|
||||
case 'pfls':
|
||||
mqtt_publish("play_flush", data, length);
|
||||
break;
|
||||
case 'prsm':
|
||||
mqtt_publish("play_resume", data, length);
|
||||
break;
|
||||
case 'PICT':
|
||||
if (config.mqtt_publish_cover) {
|
||||
mqtt_publish("cover", data, length);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int initialise_mqtt() {
|
||||
debug(1, "Initialising MQTT");
|
||||
if (config.mqtt_hostname == NULL) {
|
||||
debug(1, "[MQTT]: Not initialized, as the hostname is not set");
|
||||
return 0;
|
||||
}
|
||||
int keepalive = 60;
|
||||
mosquitto_lib_init();
|
||||
if (!(global_mosq = mosquitto_new(config.service_name, true, NULL))) {
|
||||
die("[MQTT]: FATAL: Could not create mosquitto object! %d\n", global_mosq);
|
||||
}
|
||||
|
||||
if (config.mqtt_cafile != NULL || config.mqtt_capath != NULL || config.mqtt_certfile != NULL ||
|
||||
config.mqtt_keyfile != NULL) {
|
||||
if (mosquitto_tls_set(global_mosq, config.mqtt_cafile, config.mqtt_capath, config.mqtt_certfile,
|
||||
config.mqtt_keyfile, NULL) != MOSQ_ERR_SUCCESS) {
|
||||
die("[MQTT]: TLS Setup failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.mqtt_username != NULL || config.mqtt_password != NULL) {
|
||||
if (mosquitto_username_pw_set(global_mosq, config.mqtt_username, config.mqtt_password) !=
|
||||
MOSQ_ERR_SUCCESS) {
|
||||
die("[MQTT]: Username/Password set failed");
|
||||
}
|
||||
}
|
||||
mosquitto_log_callback_set(global_mosq, _cb_log);
|
||||
|
||||
if (config.mqtt_enable_remote) {
|
||||
mosquitto_message_callback_set(global_mosq, on_message);
|
||||
}
|
||||
|
||||
mosquitto_disconnect_callback_set(global_mosq, on_disconnect);
|
||||
mosquitto_connect_callback_set(global_mosq, on_connect);
|
||||
if (mosquitto_connect(global_mosq, config.mqtt_hostname, config.mqtt_port, keepalive)) {
|
||||
inform("[MQTT]: Could not establish a mqtt connection");
|
||||
}
|
||||
if (mosquitto_loop_start(global_mosq) != MOSQ_ERR_SUCCESS) {
|
||||
inform("[MQTT]: Could start MQTT Main loop");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef MQTT_H
|
||||
#define MQTT_H
|
||||
#include <mosquitto.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int initialise_mqtt();
|
||||
void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length);
|
||||
void mqtt_publish(char *topic, char *data, uint32_t length);
|
||||
void mqtt_setup();
|
||||
void on_connect(struct mosquitto *mosq, void *userdata, int rc);
|
||||
void on_disconnect(struct mosquitto *mosq, void *userdata, int rc);
|
||||
void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg);
|
||||
void _cb_log(struct mosquitto *mosq, void *userdata, int level, const char *str);
|
||||
#endif /* #ifndef MQTT_H */
|
||||
@@ -1,8 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
|
||||
<interface name="org.gnome.ShairportSync">
|
||||
<method name="Quit"/>
|
||||
<property name='Active' type='b' access='read'/>
|
||||
<property name="DisableStandby" type="b" access="readwrite" />
|
||||
<property name="DisableStandbyMode" type="s" access="readwrite" />
|
||||
<property name="LoudnessFilterActive" type="b" access="readwrite" />
|
||||
<property name="LoudnessThreshold" type="d" access="readwrite" />
|
||||
<property name="DriftTolerance" type="d" access="readwrite" />
|
||||
<method name="RemoteCommand">
|
||||
<arg name="command" type="s" direction="in" />
|
||||
</method>
|
||||
|
||||
@@ -7,30 +7,32 @@
|
||||
#include "config.h"
|
||||
#include "definitions.h"
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/havege.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
#include <polarssl/aes.h>
|
||||
#include <polarssl/havege.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
#include <openssl/aes.h>
|
||||
#endif
|
||||
|
||||
#include "alac.h"
|
||||
#include "audio.h"
|
||||
|
||||
#define time_ping_history 8
|
||||
#define time_ping_history 128 // at 1 per three seconds, approximately six minutes of records
|
||||
|
||||
typedef struct time_ping_record {
|
||||
uint64_t local_to_remote_difference;
|
||||
uint64_t dispersion;
|
||||
uint64_t local_time;
|
||||
uint64_t remote_time;
|
||||
int sequence_number;
|
||||
int chosen;
|
||||
} time_ping_record;
|
||||
|
||||
typedef uint16_t seq_t;
|
||||
@@ -38,7 +40,7 @@ typedef uint16_t seq_t;
|
||||
typedef struct audio_buffer_entry { // decoded audio packets
|
||||
int ready;
|
||||
int resend_level;
|
||||
int64_t timestamp;
|
||||
// int64_t timestamp;
|
||||
seq_t sequence_number;
|
||||
uint32_t given_timestamp; // for debugging and checking
|
||||
signed short *data;
|
||||
@@ -46,7 +48,7 @@ typedef struct audio_buffer_entry { // decoded audio packets
|
||||
} abuf_t;
|
||||
|
||||
// default buffer size
|
||||
// This eeds to be a power of 2 because of the way BUFIDX(seqno) works.
|
||||
// This needs to be a power of 2 because of the way BUFIDX(seqno) works.
|
||||
// 512 is the minimum for normal operation -- it gives 512*352/44100 or just over 4 seconds of
|
||||
// buffers.
|
||||
// For at least 10 seconds, you need to go to 2048.
|
||||
@@ -59,43 +61,76 @@ typedef struct audio_buffer_entry { // decoded audio packets
|
||||
|
||||
#define BUFFER_FRAMES 1024
|
||||
|
||||
enum audio_stream_type {
|
||||
ast_unknown,
|
||||
ast_uncompressed, // L16/44100/2
|
||||
ast_apple_lossless,
|
||||
} ast_type;
|
||||
|
||||
typedef struct {
|
||||
int encrypted;
|
||||
uint8_t aesiv[16], aeskey[16];
|
||||
int32_t fmtp[12];
|
||||
enum audio_stream_type type;
|
||||
} stream_cfg;
|
||||
|
||||
typedef struct {
|
||||
int connection_number; // for debug ID purposes, nothing else...
|
||||
int resend_interval; // this is really just for debugging
|
||||
int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency
|
||||
int64_t latency; // the actual latency used for this play session
|
||||
int64_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero
|
||||
// otherwise
|
||||
int64_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero
|
||||
// otherwise
|
||||
int connection_number; // for debug ID purposes, nothing else...
|
||||
int resend_interval; // this is really just for debugging
|
||||
int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency
|
||||
uint32_t latency; // the actual latency used for this play session
|
||||
uint32_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero
|
||||
// otherwise
|
||||
uint32_t maximum_latency; // set if an a=max-latency: line appears in the ANNOUNCE message; zero
|
||||
// otherwise
|
||||
int software_mute_enabled; // if we don't have a real mute that we can use
|
||||
|
||||
int fd;
|
||||
int authorized; // set if a password is required and has been supplied
|
||||
int authorized; // set if a password is required and has been supplied
|
||||
char *auth_nonce; // the session nonce, if needed
|
||||
stream_cfg stream;
|
||||
SOCKADDR remote, local;
|
||||
int stop;
|
||||
int running;
|
||||
pthread_t thread, timer_requester;
|
||||
volatile int stop;
|
||||
volatile int running;
|
||||
volatile uint64_t watchdog_bark_time;
|
||||
volatile int watchdog_barks; // number of times the watchdog has timed out and done something
|
||||
int unfixable_error_reported; // set when an unfixable error command has been executed.
|
||||
|
||||
// pthread_t *ptp;
|
||||
time_t playstart;
|
||||
pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread,
|
||||
player_watchdog_thread;
|
||||
|
||||
// buffers to delete on exit
|
||||
signed short *tbuf;
|
||||
int32_t *sbuf;
|
||||
char *outbuf;
|
||||
|
||||
// for holding the output rate information until printed out at the end of a session
|
||||
double frame_rate;
|
||||
int frame_rate_status;
|
||||
|
||||
// for holding input rate information until printed out at the end of a session
|
||||
|
||||
double input_frame_rate;
|
||||
int input_frame_rate_starting_point_is_valid;
|
||||
|
||||
uint64_t frames_inward_measurement_start_time;
|
||||
uint32_t frames_inward_frames_received_at_measurement_start_time;
|
||||
|
||||
uint64_t frames_inward_measurement_time;
|
||||
uint32_t frames_inward_frames_received_at_measurement_time;
|
||||
|
||||
// other stuff...
|
||||
pthread_t *player_thread;
|
||||
pthread_rwlock_t player_thread_lock; // used to control access by "outsiders"
|
||||
|
||||
abuf_t audio_buffer[BUFFER_FRAMES];
|
||||
int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate;
|
||||
unsigned int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate;
|
||||
int input_bytes_per_frame, output_bytes_per_frame, output_sample_ratio;
|
||||
int max_frame_size_change;
|
||||
int64_t previous_random_number;
|
||||
alac_file *decoder_info;
|
||||
uint64_t packet_count;
|
||||
uint64_t packet_count_since_flush;
|
||||
int connection_state_to_output;
|
||||
int player_thread_please_stop;
|
||||
uint64_t first_packet_time_to_play;
|
||||
int64_t time_since_play_started; // nanoseconds
|
||||
// stats
|
||||
@@ -105,7 +140,7 @@ typedef struct {
|
||||
int32_t last_seqno_read;
|
||||
// mutexes and condition variables
|
||||
pthread_cond_t flowcontrol;
|
||||
pthread_mutex_t ab_mutex, flush_mutex;
|
||||
pthread_mutex_t ab_mutex, flush_mutex, volume_control_mutex;
|
||||
int fix_volume;
|
||||
uint32_t timestamp_epoch, last_timestamp,
|
||||
maximum_timestamp_interval; // timestamp_epoch of zero means not initialised, could start at 2
|
||||
@@ -113,19 +148,19 @@ typedef struct {
|
||||
int ab_buffering, ab_synced;
|
||||
int64_t first_packet_timestamp;
|
||||
int flush_requested;
|
||||
int64_t flush_rtp_timestamp;
|
||||
uint32_t flush_rtp_timestamp;
|
||||
uint64_t time_of_last_audio_packet;
|
||||
seq_t ab_read, ab_write;
|
||||
|
||||
#ifdef HAVE_LIBMBEDTLS
|
||||
#ifdef CONFIG_MBEDTLS
|
||||
mbedtls_aes_context dctx;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
#ifdef CONFIG_POLARSSL
|
||||
aes_context dctx;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
#ifdef CONFIG_OPENSSL
|
||||
AES_KEY aes;
|
||||
#endif
|
||||
|
||||
@@ -161,27 +196,46 @@ typedef struct {
|
||||
uint16_t local_timing_port;
|
||||
|
||||
int64_t latency_delayed_timestamp; // this is for debugging only...
|
||||
int64_t reference_timestamp;
|
||||
uint64_t reference_timestamp_time;
|
||||
|
||||
// this is what connects an rtp timestamp to the remote time
|
||||
|
||||
uint32_t reference_timestamp;
|
||||
uint64_t remote_reference_timestamp_time;
|
||||
|
||||
// used as the initials values for calculating the rate at which the source thinks it's sending
|
||||
// frames
|
||||
uint32_t initial_reference_timestamp;
|
||||
uint64_t initial_reference_time;
|
||||
double remote_frame_rate;
|
||||
|
||||
// the ratio of the following should give us the operating rate, nominally 44,100
|
||||
int64_t reference_to_previous_frame_difference;
|
||||
uint64_t reference_to_previous_time_difference;
|
||||
|
||||
// debug variables
|
||||
int request_sent;
|
||||
|
||||
uint8_t time_ping_count;
|
||||
int time_ping_count;
|
||||
struct time_ping_record time_pings[time_ping_history];
|
||||
|
||||
uint64_t departure_time; // dangerous -- this assumes that there will never be two timing
|
||||
// request in flight at the same time
|
||||
|
||||
pthread_mutex_t reference_time_mutex;
|
||||
pthread_mutex_t watchdog_mutex;
|
||||
|
||||
double local_to_remote_time_gradient; // if no drift, this would be exactly 1.0; likely it's
|
||||
// slightly above or below.
|
||||
int local_to_remote_time_gradient_sample_count; // the number of samples used to calculate the
|
||||
// gradient
|
||||
// add the following to the local time to get the remote time modulo 2^64
|
||||
uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks
|
||||
uint64_t local_to_remote_time_difference_measurement_time; // when the above was calculated
|
||||
|
||||
int last_stuff_request;
|
||||
|
||||
int64_t play_segment_reference_frame;
|
||||
uint64_t play_segment_reference_frame_remote_time;
|
||||
// int64_t play_segment_reference_frame;
|
||||
// uint64_t play_segment_reference_frame_remote_time;
|
||||
|
||||
int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative
|
||||
int64_t session_corrections;
|
||||
@@ -197,16 +251,22 @@ typedef struct {
|
||||
// zero
|
||||
uint32_t dacp_active_remote; // key to send to the remote controller
|
||||
void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled.
|
||||
|
||||
int enable_dither; // needed for filling silences before play actually starts
|
||||
int64_t dac_buffer_queue_minimum_length;
|
||||
} rtsp_conn_info;
|
||||
|
||||
uint32_t modulo_32_offset(uint32_t from, uint32_t to);
|
||||
uint64_t modulo_64_offset(uint64_t from, uint64_t to);
|
||||
|
||||
int player_play(rtsp_conn_info *conn);
|
||||
int player_stop(rtsp_conn_info *conn);
|
||||
|
||||
void player_volume(double f, rtsp_conn_info *conn);
|
||||
void player_volume_without_notification(double f, rtsp_conn_info *conn);
|
||||
void player_flush(int64_t timestamp, rtsp_conn_info *conn);
|
||||
void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data,
|
||||
int len, rtsp_conn_info *conn);
|
||||
void player_flush(uint32_t timestamp, rtsp_conn_info *conn);
|
||||
void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, int len,
|
||||
rtsp_conn_info *conn);
|
||||
int64_t monotonic_timestamp(uint32_t timestamp,
|
||||
rtsp_conn_info *conn); // add an epoch to the timestamp. The monotonic
|
||||
// timestamp guaranteed to start between 2^32 2^33
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Apple RTP protocol handler. This file is part of Shairport.
|
||||
* Copyright (c) James Laird 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
|
||||
@@ -45,8 +45,8 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
uint64_t local_to_remote_time_jitters;
|
||||
uint64_t local_to_remote_time_jitters_count;
|
||||
uint64_t local_to_remote_time_jitter;
|
||||
uint64_t local_to_remote_time_jitter_count;
|
||||
|
||||
void rtp_initialise(rtsp_conn_info *conn) {
|
||||
conn->rtp_time_of_last_resend_request_error_fp = 0;
|
||||
@@ -58,21 +58,47 @@ void rtp_initialise(rtsp_conn_info *conn) {
|
||||
}
|
||||
|
||||
void rtp_terminate(rtsp_conn_info *conn) {
|
||||
|
||||
conn->reference_timestamp = 0;
|
||||
// destroy the timer mutex
|
||||
int rc = pthread_mutex_destroy(&conn->reference_time_mutex);
|
||||
if (rc)
|
||||
debug(1, "Error destroying reference_time_mutex variable.");
|
||||
}
|
||||
|
||||
void rtp_audio_receiver_cleanup_handler(void *arg) {
|
||||
debug(3, "Audio Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(1,"shutdown audio socket.");
|
||||
shutdown(conn->audio_socket,SHUT_RDWR);
|
||||
debug(1,"close audio socket.");
|
||||
close(conn->audio_socket);
|
||||
debug(3, "Audio Receiver Cleanup Successful.");
|
||||
uint64_t local_to_remote_time_difference_now(rtsp_conn_info *conn) {
|
||||
// this is an attempt to compensate for clock drift since the last time ping that was used
|
||||
// so, if we have a non-zero clock drift, we will calculate the drift there would
|
||||
// be from the time of the last time ping
|
||||
uint64_t local_time_now_fp = get_absolute_time_in_fp();
|
||||
uint64_t time_since_last_local_to_remote_time_difference_measurement =
|
||||
local_time_now_fp - conn->local_to_remote_time_difference_measurement_time;
|
||||
|
||||
uint64_t remote_time_since_last_local_to_remote_time_difference_measurement =
|
||||
(uint64_t)(conn->local_to_remote_time_gradient *
|
||||
time_since_last_local_to_remote_time_difference_measurement);
|
||||
|
||||
double drift;
|
||||
if (remote_time_since_last_local_to_remote_time_difference_measurement >=
|
||||
time_since_last_local_to_remote_time_difference_measurement)
|
||||
drift = (1.0 * (remote_time_since_last_local_to_remote_time_difference_measurement -
|
||||
time_since_last_local_to_remote_time_difference_measurement)) /
|
||||
(uint64_t)0x100000000;
|
||||
else
|
||||
drift = -((1.0 * (time_since_last_local_to_remote_time_difference_measurement -
|
||||
remote_time_since_last_local_to_remote_time_difference_measurement)) /
|
||||
(uint64_t)0x100000000);
|
||||
|
||||
// double interval_ms =
|
||||
// 1.0*(((time_since_last_local_to_remote_time_difference_measurement)*1000)>>32);
|
||||
// debug(1,"Measurement drift is %.2f microseconds (0x%" PRIx64 " in 64-bit fp) over %.2f
|
||||
// milliseconds with drift of %.2f
|
||||
// ppm.",drift*1000000,(uint64_t)(drift*(uint64_t)0x100000000),interval_ms,(1.0-conn->local_to_remote_time_gradient)*1000000);
|
||||
// return conn->local_to_remote_time_difference + (uint64_t)(drift*(uint64_t 0x100000000));
|
||||
return conn->local_to_remote_time_difference + (uint64_t)(drift * (uint64_t)0x100000000);
|
||||
}
|
||||
|
||||
void rtp_audio_receiver_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||
debug(3, "Audio Receiver Cleanup Done.");
|
||||
}
|
||||
|
||||
void *rtp_audio_receiver(void *arg) {
|
||||
@@ -92,10 +118,13 @@ void *rtp_audio_receiver(void *arg) {
|
||||
float stat_mean = 0.0;
|
||||
float stat_M2 = 0.0;
|
||||
|
||||
int frame_count = 0;
|
||||
ssize_t nread;
|
||||
while (1) {
|
||||
nread = recv(conn->audio_socket, packet, sizeof(packet), 0);
|
||||
|
||||
frame_count++;
|
||||
|
||||
uint64_t local_time_now_fp = get_absolute_time_in_fp();
|
||||
if (time_of_previous_packet_fp) {
|
||||
float time_interval_us =
|
||||
@@ -108,8 +137,9 @@ void *rtp_audio_receiver(void *arg) {
|
||||
stat_mean += stat_delta / stat_n;
|
||||
stat_M2 += stat_delta * (time_interval_us - stat_mean);
|
||||
if (stat_n % 2500 == 0) {
|
||||
debug(2, "Packet reception interval stats: mean, standard deviation and max for the last "
|
||||
"2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",
|
||||
debug(2,
|
||||
"Packet reception interval stats: mean, standard deviation and max for the last "
|
||||
"2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",
|
||||
stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us);
|
||||
stat_n = 0;
|
||||
stat_mean = 0.0;
|
||||
@@ -134,6 +164,19 @@ void *rtp_audio_receiver(void *arg) {
|
||||
// increment last_seqno and see if it's the same as the incoming seqno
|
||||
|
||||
if (type == 0x60) { // regular audio data
|
||||
|
||||
/*
|
||||
char obf[4096];
|
||||
char *obfp = obf;
|
||||
int obfc;
|
||||
for (obfc=0;obfc<plen;obfc++) {
|
||||
snprintf(obfp, 3, "%02X", pktp[obfc]);
|
||||
obfp+=2;
|
||||
};
|
||||
*obfp=0;
|
||||
debug(1,"Audio Packet Received: \"%s\"",obf);
|
||||
*/
|
||||
|
||||
if (last_seqno == -1)
|
||||
last_seqno = seqno;
|
||||
else {
|
||||
@@ -147,7 +190,9 @@ void *rtp_audio_receiver(void *arg) {
|
||||
}
|
||||
|
||||
uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4));
|
||||
int64_t timestamp = monotonic_timestamp(actual_timestamp, conn);
|
||||
|
||||
// uint32_t ssid = ntohl(*(uint32_t *)(pktp + 8));
|
||||
// debug(1, "Audio packet SSID: %08X,%u", ssid,ssid);
|
||||
|
||||
// if (packet[1]&0x10)
|
||||
// debug(1,"Audio packet Extension bit set.");
|
||||
@@ -159,7 +204,7 @@ void *rtp_audio_receiver(void *arg) {
|
||||
if (plen >= 16) {
|
||||
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
|
||||
(drand48() > config.diagnostic_drop_packet_fraction))
|
||||
player_put_packet(seqno, actual_timestamp, timestamp, pktp, plen, conn);
|
||||
player_put_packet(seqno, actual_timestamp, pktp, plen, conn);
|
||||
else
|
||||
debug(3, "Dropping audio packet %u to simulate a bad connection.", seqno);
|
||||
continue;
|
||||
@@ -188,14 +233,8 @@ void *rtp_audio_receiver(void *arg) {
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void rtp_control_handler_cleanup_handler(void *arg) {
|
||||
debug(3, "Control Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(1,"shutdown control socket.");
|
||||
shutdown(conn->control_socket,SHUT_RDWR);
|
||||
debug(1,"close control socket.");
|
||||
close(conn->control_socket);
|
||||
debug(3, "Control Receiver Cleanup Successful.");
|
||||
void rtp_control_handler_cleanup_handler(__attribute__((unused)) void *arg) {
|
||||
debug(3, "Control Receiver Cleanup Done.");
|
||||
}
|
||||
|
||||
void *rtp_control_receiver(void *arg) {
|
||||
@@ -206,7 +245,7 @@ void *rtp_control_receiver(void *arg) {
|
||||
uint8_t packet[2048], *pktp;
|
||||
// struct timespec tn;
|
||||
uint64_t remote_time_of_sync;
|
||||
int64_t sync_rtp_timestamp;
|
||||
uint32_t sync_rtp_timestamp;
|
||||
ssize_t nread;
|
||||
while (1) {
|
||||
nread = recv(conn->control_socket, packet, sizeof(packet), 0);
|
||||
@@ -246,22 +285,21 @@ void *rtp_control_receiver(void *arg) {
|
||||
// There must be more to it -- there something missing.
|
||||
|
||||
// In addition, it seems that if the value of the short represented by the second
|
||||
// pair of bytes in the packe is 7
|
||||
// pair of bytes in the packet is 7
|
||||
// then an extra time lag is expected to be added, presumably by
|
||||
// the AirPort Express.
|
||||
|
||||
// Best guess is that this delay is 11,025 frames.
|
||||
|
||||
// uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
|
||||
// uint32_t rt = nctohl(&packet[16]); // raw timestamp
|
||||
uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
|
||||
uint32_t rt = nctohl(&packet[16]); // raw timestamp
|
||||
|
||||
// uint32_t fl = nctohs(&packet[2]); //
|
||||
uint32_t fl = nctohs(&packet[2]); //
|
||||
|
||||
// debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and
|
||||
%u,
|
||||
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
|
||||
// debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
|
||||
respectively.",monotonic_timestamp(rt, conn),monotonic_timestamp(rtlt, conn));
|
||||
debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u,
|
||||
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
|
||||
//debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
|
||||
respectively.",monotonic_timestamp(rt, conn),monotonic_timestamp(rtlt, conn));
|
||||
}
|
||||
*/
|
||||
if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged
|
||||
@@ -272,14 +310,14 @@ void *rtp_control_receiver(void *arg) {
|
||||
|
||||
// debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync);
|
||||
|
||||
sync_rtp_timestamp = monotonic_timestamp(nctohl(&packet[16]), conn);
|
||||
int64_t rtp_timestamp_less_latency = monotonic_timestamp(nctohl(&packet[4]), conn);
|
||||
sync_rtp_timestamp = nctohl(&packet[16]);
|
||||
uint32_t rtp_timestamp_less_latency = nctohl(&packet[4]);
|
||||
|
||||
// debug(1,"Sync timestamp is %u.",ntohl(*((uint32_t *)&packet[16])));
|
||||
|
||||
if (config.userSuppliedLatency) {
|
||||
if (config.userSuppliedLatency != conn->latency) {
|
||||
debug(1, "Using the user-supplied latency: %" PRId64 ".",
|
||||
debug(1, "Using the user-supplied latency: %" PRIu32 ".",
|
||||
config.userSuppliedLatency);
|
||||
}
|
||||
conn->latency = config.userSuppliedLatency;
|
||||
@@ -298,9 +336,14 @@ void *rtp_control_receiver(void *arg) {
|
||||
// Sigh, it would be nice to have a published protocol...
|
||||
|
||||
uint16_t flags = nctohs(&packet[2]);
|
||||
int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency;
|
||||
// debug(3, "Latency derived just from the sync packet is %" PRId64 " frames.", la);
|
||||
if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353)) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion >= 371))) {
|
||||
uint32_t la = sync_rtp_timestamp - rtp_timestamp_less_latency; // note, this might
|
||||
// loop around in
|
||||
// modulo. Not sure if
|
||||
// you'll get an error!
|
||||
// debug(3, "Latency derived just from the sync packet is %" PRIu32 " frames.", la);
|
||||
|
||||
if ((flags == 7) || ((conn->AirPlayVersion > 0) && (conn->AirPlayVersion <= 353)) ||
|
||||
((conn->AirPlayVersion > 0) && (conn->AirPlayVersion >= 371))) {
|
||||
la += config.fixedLatencyOffset;
|
||||
// debug(3, "A fixed latency offset of %d frames has been added, giving a latency of
|
||||
// "
|
||||
@@ -314,51 +357,84 @@ void *rtp_control_receiver(void *arg) {
|
||||
if ((conn->minimum_latency) && (conn->minimum_latency > la))
|
||||
la = conn->minimum_latency;
|
||||
|
||||
const int max_frames = ((3 * BUFFER_FRAMES * 352) / 4) - 11025;
|
||||
const uint32_t max_frames = ((3 * BUFFER_FRAMES * 352) / 4) - 11025;
|
||||
|
||||
if ((la < 0) || (la > max_frames)) {
|
||||
warn("An out-of-range latency request of %" PRId64
|
||||
" frames was ignored. Must be %d frames or less (44,100 frames per second). "
|
||||
"Latency remains at %" PRId64 " frames.",
|
||||
if (la > max_frames) {
|
||||
warn("An out-of-range latency request of %" PRIu32
|
||||
" frames was ignored. Must be %" PRIu32
|
||||
" frames or less (44,100 frames per second). "
|
||||
"Latency remains at %" PRIu32 " frames.",
|
||||
la, max_frames, conn->latency);
|
||||
} else {
|
||||
|
||||
if (la != conn->latency) {
|
||||
conn->latency = la;
|
||||
debug(3, "New latency detected: %" PRId64 ", sync latency: %" PRId64
|
||||
", minimum latency: %" PRId64 ", maximum "
|
||||
"latency: %" PRId64 ", fixed offset: %" PRId64 ".",
|
||||
debug(3,
|
||||
"New latency detected: %" PRIu32 ", sync latency: %" PRIu32
|
||||
", minimum latency: %" PRIu32 ", maximum "
|
||||
"latency: %" PRIu32 ", fixed offset: %" PRIu32 ".",
|
||||
la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency,
|
||||
conn->maximum_latency, config.fixedLatencyOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 1);
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
|
||||
|
||||
if (conn->initial_reference_time == 0) {
|
||||
if (conn->packet_count_since_flush > 0) {
|
||||
conn->initial_reference_time = remote_time_of_sync;
|
||||
conn->initial_reference_timestamp = sync_rtp_timestamp;
|
||||
}
|
||||
} else {
|
||||
uint64_t remote_frame_time_interval =
|
||||
conn->remote_reference_timestamp_time -
|
||||
conn->initial_reference_time; // here, this should never be zero
|
||||
if (remote_frame_time_interval) {
|
||||
conn->remote_frame_rate =
|
||||
(1.0 * (conn->reference_timestamp - conn->initial_reference_timestamp)) /
|
||||
remote_frame_time_interval; // an IEEE double calculation with a 32-bit
|
||||
// numerator and 64-bit denominator
|
||||
// integers
|
||||
conn->remote_frame_rate = conn->remote_frame_rate *
|
||||
(uint64_t)0x100000000; // this should just change the
|
||||
// [binary] exponent in the IEEE
|
||||
// FP representation; the
|
||||
// mantissa should be unaffected.
|
||||
} else {
|
||||
conn->remote_frame_rate = 0.0; // use as a flag.
|
||||
}
|
||||
}
|
||||
|
||||
// this is for debugging
|
||||
// uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time;
|
||||
// int64_t old_reference_timestamp = conn->reference_timestamp;
|
||||
uint64_t old_remote_reference_time = conn->remote_reference_timestamp_time;
|
||||
uint32_t old_reference_timestamp = conn->reference_timestamp;
|
||||
// int64_t old_latency_delayed_timestamp = conn->latency_delayed_timestamp;
|
||||
|
||||
conn->remote_reference_timestamp_time = remote_time_of_sync;
|
||||
conn->reference_timestamp_time =
|
||||
remote_time_of_sync - conn->local_to_remote_time_difference;
|
||||
// conn->reference_timestamp_time =
|
||||
// remote_time_of_sync - local_to_remote_time_difference_now(conn);
|
||||
conn->reference_timestamp = sync_rtp_timestamp;
|
||||
conn->latency_delayed_timestamp = rtp_timestamp_less_latency;
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 3);
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 0);
|
||||
|
||||
conn->reference_to_previous_time_difference =
|
||||
remote_time_of_sync - old_remote_reference_time;
|
||||
if (old_reference_timestamp == 0)
|
||||
conn->reference_to_previous_frame_difference = 0;
|
||||
else
|
||||
conn->reference_to_previous_frame_difference =
|
||||
sync_rtp_timestamp - old_reference_timestamp;
|
||||
|
||||
// int64_t delayed_frame_difference = rtp_timestamp_less_latency -
|
||||
// old_latency_delayed_timestamp;
|
||||
|
||||
// this is for debugging
|
||||
/*
|
||||
uint64_t time_difference = remote_time_of_sync - old_remote_reference_time;
|
||||
int64_t reference_frame_difference = sync_rtp_timestamp - old_reference_timestamp;
|
||||
int64_t delayed_frame_difference = rtp_timestamp_less_latency -
|
||||
old_latency_delayed_timestamp;
|
||||
|
||||
if (old_remote_reference_time)
|
||||
debug(1,"Time difference: %" PRIu64 " reference and delayed frame differences: %"
|
||||
PRId64 "
|
||||
and %" PRId64 ", giving rates of %f and %f respectively.",
|
||||
(time_difference*1000000)>>32,reference_frame_difference,delayed_frame_difference,(1.0*(reference_frame_difference*10000000))/((time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((time_difference*10000000)>>32));
|
||||
PRId64 " and %" PRId64 ", giving rates _at source!!_ of %f and %f respectively.",
|
||||
(conn->reference_to_previous_time_difference*1000000)>>32,conn->reference_to_previous_frame_difference,delayed_frame_difference,
|
||||
(1.0*(conn->reference_to_previous_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32),(1.0*(delayed_frame_difference*10000000))/((conn->reference_to_previous_time_difference*10000000)>>32));
|
||||
else
|
||||
debug(1,"First sync received");
|
||||
*/
|
||||
@@ -381,14 +457,13 @@ void *rtp_control_receiver(void *arg) {
|
||||
debug(3, "Control Receiver -- Retransmitted Audio Data Packet %u received.", seqno);
|
||||
|
||||
uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4));
|
||||
int64_t timestamp = monotonic_timestamp(actual_timestamp, conn);
|
||||
|
||||
pktp += 12;
|
||||
plen -= 12;
|
||||
|
||||
// check if packet contains enough content to be reasonable
|
||||
if (plen >= 16) {
|
||||
player_put_packet(seqno, actual_timestamp, timestamp, pktp, plen, conn);
|
||||
player_put_packet(seqno, actual_timestamp, pktp, plen, conn);
|
||||
continue;
|
||||
} else {
|
||||
debug(3, "Too-short retransmitted audio packet received in control port, ignored.");
|
||||
@@ -409,7 +484,13 @@ void *rtp_control_receiver(void *arg) {
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void rtp_timing_sender_cleanup_handler(void *arg) {
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(3, "Connection %d: Timing Sender Cleanup.", conn->connection_number);
|
||||
}
|
||||
|
||||
void *rtp_timing_sender(void *arg) {
|
||||
pthread_cleanup_push(rtp_timing_sender_cleanup_handler, arg);
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
struct timing_request {
|
||||
char leader;
|
||||
@@ -469,19 +550,21 @@ void *rtp_timing_sender(void *arg) {
|
||||
usleep(3000000);
|
||||
}
|
||||
debug(3, "rtp_timing_sender thread interrupted. This should never happen.");
|
||||
pthread_cleanup_pop(0); // don't execute anything here.
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void rtp_timing_receiver_cleanup_handler(void *arg) {
|
||||
debug(3, "Timing Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(3, "Cancel Timing Requester.");
|
||||
pthread_cancel(conn->timer_requester);
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
debug(3, "Join Timing Requester.");
|
||||
pthread_join(conn->timer_requester, NULL);
|
||||
debug(1,"shutdown timing socket.");
|
||||
shutdown(conn->timing_socket,SHUT_RDWR);
|
||||
debug(1,"close timing socket.");
|
||||
close(conn->timing_socket);
|
||||
debug(3, "Timing Receiver Cleanup Successful.");
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
}
|
||||
|
||||
void *rtp_timing_receiver(void *arg) {
|
||||
@@ -493,14 +576,21 @@ void *rtp_timing_receiver(void *arg) {
|
||||
pthread_create(&conn->timer_requester, NULL, &rtp_timing_sender, arg);
|
||||
// struct timespec att;
|
||||
uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time;
|
||||
local_to_remote_time_jitters = 0;
|
||||
local_to_remote_time_jitters_count = 0;
|
||||
local_to_remote_time_jitter = 0;
|
||||
local_to_remote_time_jitter_count = 0;
|
||||
// uint64_t first_remote_time = 0;
|
||||
uint64_t first_local_time = 0;
|
||||
// uint64_t first_local_time = 0;
|
||||
|
||||
uint64_t first_local_to_remote_time_difference = 0;
|
||||
// uint64_t first_local_to_remote_time_difference_time;
|
||||
// uint64_t l2rtd = 0;
|
||||
int sequence_number = 0;
|
||||
|
||||
// for getting mean and sd of return times
|
||||
int32_t stat_n = 0;
|
||||
double stat_mean = 0.0;
|
||||
double stat_M2 = 0.0;
|
||||
|
||||
while (1) {
|
||||
nread = recv(conn->timing_socket, packet, sizeof(packet), 0);
|
||||
|
||||
@@ -522,7 +612,7 @@ void *rtp_timing_receiver(void *arg) {
|
||||
obfp+=2;
|
||||
};
|
||||
*obfp=0;
|
||||
//debug(1,"Timing Packet Received: \"%s\"",obf);
|
||||
debug(1,"Timing Packet Received: \"%s\"",obf);
|
||||
*/
|
||||
|
||||
// arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000;
|
||||
@@ -548,23 +638,43 @@ void *rtp_timing_receiver(void *arg) {
|
||||
distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32;
|
||||
distant_transmit_time += nctohl(&packet[28]);
|
||||
|
||||
// processing_time = distant_transmit_time - distant_receive_time;
|
||||
uint64_t remote_processing_time = 0;
|
||||
|
||||
// debug(1,"Return trip time: %lluuS, remote processing time:
|
||||
// %lluuS.",(return_time*1000000)>>32,(processing_time*1000000)>>32);
|
||||
if (distant_transmit_time >= distant_receive_time)
|
||||
remote_processing_time = distant_transmit_time - distant_receive_time;
|
||||
else {
|
||||
debug(1, "Yikes: distant_transmit_time is before distant_receive_time; remote "
|
||||
"processing time set to zero.");
|
||||
}
|
||||
// debug(1,"Return trip time: %" PRIu64 " uS, remote processing time: %" PRIu64 "
|
||||
// uS.",(return_time*1000000)>>32,(remote_processing_time*1000000)>>32);
|
||||
|
||||
uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2;
|
||||
|
||||
unsigned int cc;
|
||||
// remove the remote processing time from the record of the return time, as long at the
|
||||
// processing time looks sensible.
|
||||
|
||||
if (remote_processing_time < return_time)
|
||||
return_time -= remote_processing_time;
|
||||
else
|
||||
debug(1, "Remote processing time greater than return time -- ignored.");
|
||||
|
||||
int cc;
|
||||
for (cc = time_ping_history - 1; cc > 0; cc--) {
|
||||
conn->time_pings[cc] = conn->time_pings[cc - 1];
|
||||
// if ((conn->time_ping_count) && (conn->time_ping_count < 10))
|
||||
// conn->time_pings[cc].dispersion =
|
||||
// conn->time_pings[cc].dispersion * pow(2.14,
|
||||
// 1.0/conn->time_ping_count);
|
||||
conn->time_pings[cc].dispersion =
|
||||
(conn->time_pings[cc].dispersion * 110) /
|
||||
100; // make the dispersions 'age' by this rational factor
|
||||
}
|
||||
// these are for diagnostics only -- not used
|
||||
// these are used for doing a least squares calculation to get the drift
|
||||
conn->time_pings[0].local_time = arrival_time;
|
||||
conn->time_pings[0].remote_time = distant_transmit_time;
|
||||
conn->time_pings[0].sequence_number = sequence_number++;
|
||||
conn->time_pings[0].chosen = 0;
|
||||
|
||||
conn->time_pings[0].local_to_remote_difference =
|
||||
local_time_by_remote_clock - arrival_time;
|
||||
@@ -572,127 +682,155 @@ void *rtp_timing_receiver(void *arg) {
|
||||
if (conn->time_ping_count < time_ping_history)
|
||||
conn->time_ping_count++;
|
||||
|
||||
uint64_t local_time_chosen = arrival_time;
|
||||
// here, calculate the mean and standard deviation of the return times
|
||||
|
||||
// mean and variance calculations from "online_variance" algorithm at
|
||||
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
|
||||
|
||||
double rtfus = 1.0 * ((return_time * 1000000) >> 32);
|
||||
stat_n += 1;
|
||||
double stat_delta = rtfus - stat_mean;
|
||||
stat_mean += stat_delta / stat_n;
|
||||
stat_M2 += stat_delta * (rtfus - stat_mean);
|
||||
// debug(1, "Timing packet return time stats: current, mean and standard deviation over
|
||||
// %d packets: %.1f, %.1f, %.1f (microseconds).",
|
||||
// stat_n,rtfus,stat_mean, sqrtf(stat_M2 / (stat_n - 1)));
|
||||
|
||||
// here, pick the record with the least dispersion, and record that it's been chosen
|
||||
|
||||
// uint64_t local_time_chosen = arrival_time;
|
||||
// uint64_t remote_time_chosen = distant_transmit_time;
|
||||
// now pick the timestamp with the lowest dispersion
|
||||
uint64_t l2rtd = conn->time_pings[0].local_to_remote_difference;
|
||||
uint64_t lt = conn->time_pings[0].local_time;
|
||||
uint64_t tld = conn->time_pings[0].dispersion;
|
||||
// chosen = 0;
|
||||
int chosen = 0;
|
||||
for (cc = 1; cc < conn->time_ping_count; cc++)
|
||||
if (conn->time_pings[cc].dispersion < tld) {
|
||||
chosen = cc;
|
||||
l2rtd = conn->time_pings[cc].local_to_remote_difference;
|
||||
// chosen = cc;
|
||||
lt = conn->time_pings[cc].local_time;
|
||||
tld = conn->time_pings[cc].dispersion;
|
||||
local_time_chosen = conn->time_pings[cc].local_time;
|
||||
// local_time_chosen = conn->time_pings[cc].local_time;
|
||||
// remote_time_chosen = conn->time_pings[cc].remote_time;
|
||||
}
|
||||
// int64_t ji;
|
||||
// debug(1,"Record %d has the lowest dispersion with %0.2f us
|
||||
// dispersion.",chosen,1.0*((tld * 1000000) >> 32));
|
||||
conn->time_pings[chosen].chosen = 1; // record the fact that it has been used for timing
|
||||
|
||||
/*
|
||||
// calculate the jitter -- the absolute time between the current
|
||||
local_to_remote_time_difference and the new one and add it to the total jitter count
|
||||
int64_t ji;
|
||||
int64_t ltd =0; // local time difference for the jitter
|
||||
|
||||
if (conn->time_ping_count > 1) {
|
||||
if (l2rtd > conn->local_to_remote_time_difference) {
|
||||
local_to_remote_time_jitters =
|
||||
local_to_remote_time_jitters + l2rtd - conn->local_to_remote_time_difference;
|
||||
// ji = l2rtd - conn->local_to_remote_time_difference;
|
||||
local_to_remote_time_jitter =
|
||||
local_to_remote_time_jitter + l2rtd - conn->local_to_remote_time_difference;
|
||||
ji = l2rtd - conn->local_to_remote_time_difference; // this is the difference
|
||||
between the present local-to-remote-time-difference and the new one, i.e. the jitter
|
||||
step
|
||||
} else {
|
||||
local_to_remote_time_jitters =
|
||||
local_to_remote_time_jitters + conn->local_to_remote_time_difference - l2rtd;
|
||||
// ji = -(conn->local_to_remote_time_difference - l2rtd);
|
||||
local_to_remote_time_jitter =
|
||||
local_to_remote_time_jitter + conn->local_to_remote_time_difference - l2rtd;
|
||||
ji = -(conn->local_to_remote_time_difference - l2rtd);
|
||||
}
|
||||
local_to_remote_time_jitters_count += 1;
|
||||
local_to_remote_time_jitter_count += 1;
|
||||
}
|
||||
// uncomment below to print jitter between client's clock and oour clock
|
||||
// int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time
|
||||
// difference
|
||||
// with dispersion of %lld us with delta of %lld us",rtus,ji);
|
||||
if (conn->local_to_remote_time_difference_measurement_time < lt)
|
||||
ltd = lt-conn->local_to_remote_time_difference_measurement_time;
|
||||
else
|
||||
ltd = -(conn->local_to_remote_time_difference_measurement_time-lt);
|
||||
|
||||
if (ltd) {
|
||||
debug(1,"Jitter: %" PRId64 " microseconds in %" PRId64 " microseconds.", (ji *
|
||||
(int64_t)1000000)>>32, (ltd * (int64_t)1000000)>>32);
|
||||
debug(1,"Source clock to local clock drift: %.2f ppm.",((1.0*ji)/ltd)*1000000.0);
|
||||
}
|
||||
// uncomment below to print jitter between client's clock and our clock
|
||||
|
||||
if (ji) {
|
||||
int64_t rtus = (tld*1000000)>>32;
|
||||
debug(1,"Choosing time difference[%d] with dispersion of %" PRId64 " us with an
|
||||
adjustment of %" PRId64 " us",chosen, rtus, (ji*1000000)>>32);
|
||||
}
|
||||
*/
|
||||
conn->local_to_remote_time_difference =
|
||||
l2rtd; // make this the new local-to-remote-time-difference
|
||||
conn->local_to_remote_time_difference_measurement_time = lt; // done at this time.
|
||||
|
||||
conn->local_to_remote_time_difference = l2rtd;
|
||||
if (first_local_to_remote_time_difference == 0) {
|
||||
first_local_to_remote_time_difference = conn->local_to_remote_time_difference;
|
||||
// first_local_to_remote_time_difference_time = get_absolute_time_in_fp();
|
||||
}
|
||||
|
||||
// int64_t clock_drift;
|
||||
// int64_t clock_drift_in_usec;
|
||||
// double clock_drift_ppm = 0.0;
|
||||
if (first_local_time == 0) {
|
||||
first_local_time = local_time_chosen;
|
||||
// first_remote_time = remote_time_chosen;
|
||||
// clock_drift = 0;
|
||||
// here, let's try to use the timing pings that were selected because of their short
|
||||
// return times to
|
||||
// estimate a figure for drift between the local clock (x) and the remote clock (y)
|
||||
|
||||
// if we plug in a local interval, we will get back what that is in remote time
|
||||
|
||||
// calculate the line of best fit for relating the local time and the remote time
|
||||
// we will calculate the slope, which is the drift
|
||||
// see https://www.varsitytutors.com/hotmath/hotmath_help/topics/line-of-best-fit
|
||||
|
||||
uint64_t y_bar = 0; // remote timestamp average
|
||||
uint64_t x_bar = 0; // local timestamp average
|
||||
int sample_count = 0;
|
||||
|
||||
// approximate time in seconds to let the system settle down
|
||||
const int settling_time = 60;
|
||||
// number of points to have for calculating a valid drift
|
||||
const int sample_point_minimum = 8;
|
||||
for (cc = 0; cc < conn->time_ping_count; cc++)
|
||||
if ((conn->time_pings[cc].chosen) &&
|
||||
(conn->time_pings[cc].sequence_number >
|
||||
(settling_time / 3))) { // wait for a approximate settling time
|
||||
y_bar += (conn->time_pings[cc].remote_time >>
|
||||
12); // precision is down to 1/4th of a microsecond
|
||||
x_bar += (conn->time_pings[cc].local_time >> 12);
|
||||
sample_count++;
|
||||
}
|
||||
if (sample_count > sample_point_minimum) {
|
||||
y_bar = y_bar / sample_count;
|
||||
x_bar = x_bar / sample_count;
|
||||
|
||||
int64_t xid, yid;
|
||||
int64_t mtl, mbl;
|
||||
mtl = 0;
|
||||
mbl = 0;
|
||||
for (cc = 0; cc < conn->time_ping_count; cc++)
|
||||
if ((conn->time_pings[cc].chosen) &&
|
||||
(conn->time_pings[cc].sequence_number > (settling_time / 3))) {
|
||||
|
||||
uint64_t slt = conn->time_pings[cc].local_time >> 12;
|
||||
if (slt > x_bar)
|
||||
xid = slt - x_bar;
|
||||
else
|
||||
xid = -(x_bar - slt);
|
||||
|
||||
uint64_t srt = conn->time_pings[cc].remote_time >> 12;
|
||||
if (srt > y_bar)
|
||||
yid = srt - y_bar;
|
||||
else
|
||||
yid = -(y_bar - srt);
|
||||
|
||||
mtl = mtl + xid * yid;
|
||||
mbl = mbl + xid * xid;
|
||||
}
|
||||
conn->local_to_remote_time_gradient_sample_count = sample_count;
|
||||
if (mbl)
|
||||
conn->local_to_remote_time_gradient = (1.0 * mtl) / mbl;
|
||||
else {
|
||||
conn->local_to_remote_time_gradient = 1.0;
|
||||
debug(1,"rtp_timing_receiver: mbl is 0");
|
||||
}
|
||||
} else {
|
||||
// uint64_t local_time_change = local_time_chosen - first_local_time;
|
||||
// uint64_t remote_time_change = remote_time_chosen - first_remote_time;
|
||||
|
||||
/*
|
||||
if (remote_time_change >= local_time_change)
|
||||
clock_drift = remote_time_change - local_time_change;
|
||||
else
|
||||
clock_drift = -(local_time_change - remote_time_change);
|
||||
*/
|
||||
/*
|
||||
if (clock_drift >= 0)
|
||||
clock_drift_in_usec = (clock_drift * 1000000) >> 32;
|
||||
else
|
||||
clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32);
|
||||
*/
|
||||
|
||||
// clock_drift_ppm = (1.0 * clock_drift_in_usec) / (local_time_change >> 32);
|
||||
conn->local_to_remote_time_gradient = 1.0;
|
||||
}
|
||||
|
||||
int64_t source_drift_usec;
|
||||
if (conn->play_segment_reference_frame != 0) {
|
||||
int64_t reference_timestamp;
|
||||
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
|
||||
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
|
||||
&remote_reference_timestamp_time, conn);
|
||||
uint64_t frame_difference = 0;
|
||||
if (reference_timestamp >= conn->play_segment_reference_frame)
|
||||
frame_difference =
|
||||
(uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame;
|
||||
else // rollover
|
||||
frame_difference = (uint64_t)reference_timestamp + 0x100000000 -
|
||||
(uint64_t)conn->play_segment_reference_frame;
|
||||
uint64_t frame_time_difference_calculated =
|
||||
(((uint64_t)frame_difference << 32) / 44100);
|
||||
uint64_t frame_time_difference_actual =
|
||||
remote_reference_timestamp_time -
|
||||
conn->play_segment_reference_frame_remote_time; // this is all done by reference
|
||||
// to
|
||||
// the
|
||||
// sources' system clock
|
||||
// debug(1,"%llu frames since play started, %llu usec calculated, %llu usec
|
||||
// actual",frame_difference, (frame_time_difference_calculated*1000000)>>32,
|
||||
// (frame_time_difference_actual*1000000)>>32);
|
||||
if (frame_time_difference_calculated >=
|
||||
frame_time_difference_actual) // i.e. if the time it should have taken to send the
|
||||
// packets is greater than the actual time difference
|
||||
// measured on the source clock
|
||||
// then the source DAC's clock is running fast relative to the source system clock
|
||||
source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual;
|
||||
else
|
||||
// otherwise the source DAC's clock is running slow relative to the source system
|
||||
// clock
|
||||
source_drift_usec =
|
||||
-(frame_time_difference_actual - frame_time_difference_calculated);
|
||||
} else
|
||||
source_drift_usec = 0;
|
||||
source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds
|
||||
|
||||
// long current_delay = 0;
|
||||
// if (config.output->delay) {
|
||||
// config.output->delay(¤t_delay);
|
||||
//}
|
||||
// Useful for troubleshooting:
|
||||
// debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec
|
||||
// %10.1lld\treturn_time_in_usec
|
||||
// %10.1llu",
|
||||
// clock_drift_ppm,
|
||||
// chosen,
|
||||
//(session_corrections*1000000)/44100,
|
||||
// current_delay,
|
||||
// source_drift_usec,
|
||||
// buffer_occupancy,
|
||||
//(return_time*1000000)>>32);
|
||||
// debug(1,"local to remote time gradient is %12.2f ppm, based on %d
|
||||
// samples.",conn->local_to_remote_time_gradient*1000000,sample_count);
|
||||
} else {
|
||||
debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.",
|
||||
(return_time * 1000000) >> 32);
|
||||
@@ -717,14 +855,28 @@ void *rtp_timing_receiver(void *arg) {
|
||||
static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id,
|
||||
int *sock) {
|
||||
// look for a port in the range, if any was specified.
|
||||
uint16_t desired_port = config.udp_port_base;
|
||||
int ret = 0;
|
||||
|
||||
int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (local_socket == -1)
|
||||
die("Could not allocate a socket.");
|
||||
|
||||
/*
|
||||
int val = 1;
|
||||
ret = setsockopt(local_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
|
||||
if (ret < 0) {
|
||||
char errorstring[1024];
|
||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||
debug(1, "Error %d: \"%s\". Couldn't set SO_REUSEADDR");
|
||||
}
|
||||
*/
|
||||
|
||||
SOCKADDR myaddr;
|
||||
int tryCount = 0;
|
||||
uint16_t desired_port;
|
||||
do {
|
||||
tryCount++;
|
||||
desired_port = nextFreeUDPPort();
|
||||
memset(&myaddr, 0, sizeof(myaddr));
|
||||
if (ip_family == AF_INET) {
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr;
|
||||
@@ -745,14 +897,20 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s
|
||||
#endif
|
||||
|
||||
} while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) &&
|
||||
(++desired_port < config.udp_port_base + config.udp_port_range));
|
||||
(tryCount < config.udp_port_range));
|
||||
|
||||
// debug(1,"UDP port chosen: %d.",desired_port);
|
||||
|
||||
if (ret < 0) {
|
||||
close(local_socket);
|
||||
die("error: could not bind a UDP port! Check the udp_port_range is large enough (>= 10) or "
|
||||
"check for restrictive firewall settings or a bad router!");
|
||||
char errorstring[1024];
|
||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||
die("error %d: \"%s\". Could not bind a UDP port! Check the udp_port_range is large enough -- "
|
||||
"it must be "
|
||||
"at least 3, and 10 or more is suggested -- or "
|
||||
"check for restrictive firewall settings or a bad router! UDP base is %u, range is %u and "
|
||||
"current suggestion is %u.",
|
||||
errno, errorstring, config.udp_port_base, config.udp_port_range, desired_port);
|
||||
}
|
||||
|
||||
uint16_t sport;
|
||||
@@ -897,26 +1055,151 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport
|
||||
}
|
||||
}
|
||||
|
||||
void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
|
||||
void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time,
|
||||
uint64_t *remote_timestamp_time, rtsp_conn_info *conn) {
|
||||
// types okay
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 1);
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
|
||||
*timestamp = conn->reference_timestamp;
|
||||
*timestamp_time = conn->reference_timestamp_time;
|
||||
*remote_timestamp_time = conn->remote_reference_timestamp_time;
|
||||
*timestamp_time =
|
||||
conn->remote_reference_timestamp_time - local_to_remote_time_difference_now(conn);
|
||||
// if ((*timestamp == 0) && (*timestamp_time == 0)) {
|
||||
// debug(1,"Reference timestamp is invalid.");
|
||||
//}
|
||||
*remote_timestamp_time = conn->remote_reference_timestamp_time;
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 3);
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 0);
|
||||
}
|
||||
|
||||
void clear_reference_timestamp(rtsp_conn_info *conn) {
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 1);
|
||||
conn->reference_timestamp = 0;
|
||||
conn->reference_timestamp_time = 0;
|
||||
conn->remote_reference_timestamp_time = 0;
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 3);
|
||||
}
|
||||
|
||||
int have_timestamp_timing_information(rtsp_conn_info *conn) {
|
||||
if (conn->reference_timestamp == 0)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
// set this to zero to use the rates supplied by the sources, which might not always be completely
|
||||
// right...
|
||||
const int use_nominal_rate = 0; // specify whether to use the nominal input rate, usually 44100 fps
|
||||
|
||||
int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn) {
|
||||
int result = 1;
|
||||
uint32_t fs = conn->input_rate;
|
||||
*frames = fs;
|
||||
uint64_t one_fp = (uint64_t)(0x100000000); // one second in fp form
|
||||
*time = one_fp;
|
||||
if ((conn->initial_reference_time) && (conn->initial_reference_timestamp)) {
|
||||
// uint32_t local_frames = conn->reference_timestamp - conn->initial_reference_timestamp;
|
||||
uint32_t local_frames =
|
||||
modulo_32_offset(conn->initial_reference_timestamp, conn->reference_timestamp);
|
||||
uint64_t local_time = conn->remote_reference_timestamp_time - conn->initial_reference_time;
|
||||
if ((local_frames == 0) || (local_time == 0) || (use_nominal_rate)) {
|
||||
result = 1;
|
||||
} else {
|
||||
double calculated_frame_rate = conn->input_rate;
|
||||
if (local_time)
|
||||
calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp;
|
||||
else
|
||||
debug(1,"sanitised_source_rate_information: local_time is zero");
|
||||
if ((local_time == 0) || ((calculated_frame_rate / conn->input_rate) > 1.002) ||
|
||||
((calculated_frame_rate / conn->input_rate) < 0.998)) {
|
||||
debug(3, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate);
|
||||
result = 1;
|
||||
} else {
|
||||
*frames = local_frames;
|
||||
*time = local_time;
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// the timestamp is a timestamp calculated at the input rate
|
||||
// the reference timestamps are denominated in terms of the input rate
|
||||
|
||||
int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn) {
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
|
||||
int result = 0;
|
||||
uint64_t time_difference;
|
||||
uint32_t frame_difference;
|
||||
result = sanitised_source_rate_information(&frame_difference, &time_difference, conn);
|
||||
|
||||
uint64_t timestamp_interval_time;
|
||||
uint64_t remote_time_of_timestamp;
|
||||
uint32_t timestamp_interval = modulo_32_offset(conn->reference_timestamp, timestamp);
|
||||
if (timestamp_interval <=
|
||||
conn->input_rate * 3600) { // i.e. timestamp was really after the reference timestamp
|
||||
timestamp_interval_time = (timestamp_interval * time_difference) /
|
||||
frame_difference; // this is the nominal time, based on the
|
||||
// fps specified between current and
|
||||
// previous sync frame.
|
||||
remote_time_of_timestamp = conn->remote_reference_timestamp_time +
|
||||
timestamp_interval_time; // based on the reference timestamp time
|
||||
// plus the time interval calculated based
|
||||
// on the specified fps.
|
||||
} else { // i.e. timestamp was actually before the reference timestamp
|
||||
timestamp_interval =
|
||||
modulo_32_offset(timestamp, conn->reference_timestamp); // fix the calculation
|
||||
timestamp_interval_time = (timestamp_interval * time_difference) /
|
||||
frame_difference; // this is the nominal time, based on the
|
||||
// fps specified between current and
|
||||
// previous sync frame.
|
||||
remote_time_of_timestamp = conn->remote_reference_timestamp_time -
|
||||
timestamp_interval_time; // based on the reference timestamp time
|
||||
// plus the time interval calculated based
|
||||
// on the specified fps.
|
||||
}
|
||||
*time = remote_time_of_timestamp - local_to_remote_time_difference_now(conn);
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) {
|
||||
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
|
||||
int result = 0;
|
||||
|
||||
uint64_t time_difference;
|
||||
uint32_t frame_difference;
|
||||
result = sanitised_source_rate_information(&frame_difference, &time_difference, conn);
|
||||
|
||||
// first, get from [local] time to remote time.
|
||||
uint64_t remote_time = time + local_to_remote_time_difference_now(conn);
|
||||
// next, get the remote time interval from the remote_time to the reference time
|
||||
uint64_t time_interval;
|
||||
|
||||
// here, we calculate the time interval, in terms of remote time
|
||||
uint64_t offset = modulo_64_offset(conn->remote_reference_timestamp_time, remote_time);
|
||||
int reference_time_was_earlier = (offset <= (uint64_t)0x100000000 * 3600);
|
||||
if (reference_time_was_earlier) // if we haven't had a reference within the last hour, it'll be
|
||||
// taken as afterwards
|
||||
time_interval = remote_time - conn->remote_reference_timestamp_time;
|
||||
else
|
||||
time_interval = conn->remote_reference_timestamp_time - remote_time;
|
||||
|
||||
// now, convert the remote time interval into frames using the frame rate we have observed or
|
||||
// which has been nominated
|
||||
uint32_t frame_interval = 0;
|
||||
if (time_difference)
|
||||
frame_interval = (time_interval * frame_difference) / time_difference;
|
||||
else
|
||||
debug(1,"local_time_to_frame: time_difference is zero");
|
||||
if (reference_time_was_earlier) {
|
||||
// debug(1,"Frame interval is %" PRId64 " frames.",frame_interval);
|
||||
*frame = (conn->reference_timestamp + frame_interval);
|
||||
} else {
|
||||
// debug(1,"Frame interval is %" PRId64 " frames.",-frame_interval);
|
||||
*frame = (conn->reference_timestamp - frame_interval);
|
||||
}
|
||||
debug_mutex_unlock(&conn->reference_time_mutex, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
|
||||
if (conn->rtp_running) {
|
||||
// if (!request_sent) {
|
||||
@@ -957,8 +1240,9 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
|
||||
(struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) {
|
||||
char em[1024];
|
||||
strerror_r(errno, em, sizeof(em));
|
||||
debug(1, "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a "
|
||||
"second.",
|
||||
debug(1,
|
||||
"Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a "
|
||||
"second.",
|
||||
errno, em);
|
||||
conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
|
||||
} else {
|
||||
|
||||
@@ -17,8 +17,17 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t controlport, uint16_t
|
||||
void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn);
|
||||
void rtp_request_client_pause(rtsp_conn_info *conn); // ask the client to pause
|
||||
|
||||
void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
|
||||
void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time,
|
||||
uint64_t *remote_timestamp_time, rtsp_conn_info *conn);
|
||||
void clear_reference_timestamp(rtsp_conn_info *conn);
|
||||
|
||||
int have_timestamp_timing_information(rtsp_conn_info *conn);
|
||||
|
||||
int get_frame_play_time(int64_t timestamp, int sample_ratio, uint64_t *time_to_play);
|
||||
|
||||
int frame_to_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *conn);
|
||||
int local_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn);
|
||||
|
||||
int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_conn_info *conn);
|
||||
|
||||
#endif // _RTP_H
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
#include "player.h"
|
||||
|
||||
rtsp_conn_info *playing_conn;
|
||||
rtsp_conn_info **conns;
|
||||
|
||||
void rtsp_listen_loop(void);
|
||||
// void rtsp_shutdown_stream(void);
|
||||
void rtsp_request_shutdown_stream(void);
|
||||
|
||||
// initialise the metadata stuff
|
||||
void cancel_all_RTSP_threads(void);
|
||||
|
||||
// initialise and completely delete the metadata stuff
|
||||
|
||||
void metadata_init(void);
|
||||
void metadata_stop(void);
|
||||
|
||||
// sends metadata out to the metadata pipe, if enabled.
|
||||
// It is sent with the type 'ssnc' the given code, data and length
|
||||
|
||||
Executable
+169
@@ -0,0 +1,169 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# shairport-sync-config, Copyright 2019 Mike Brady
|
||||
#
|
||||
# Based, with thanks, on:
|
||||
# avahi-daemon-config, Copyright 2011 Yaakov Selkowitz
|
||||
#
|
||||
# This file is part of the Cygwin port of shairport-sync.
|
||||
|
||||
# ======================================================================
|
||||
# Initialization
|
||||
# ======================================================================
|
||||
PROGNAME=$(basename $0)
|
||||
_tdir=$(dirname $0)
|
||||
PROGDIR=$(cd $_tdir && pwd)
|
||||
|
||||
CSIH_SCRIPT=/usr/share/csih/cygwin-service-installation-helper.sh
|
||||
|
||||
# Subdirectory where the new package is being installed
|
||||
PREFIX=/usr/local/bin
|
||||
|
||||
# Directory where the config files are stored
|
||||
SYSCONFDIR=/etc
|
||||
LOGDIR=/var/log
|
||||
SOCKDIR=/var/run
|
||||
|
||||
source ${CSIH_SCRIPT}
|
||||
|
||||
# ======================================================================
|
||||
# Routine: install_service
|
||||
# Install shairport-sync as a service
|
||||
# ======================================================================
|
||||
install_service() {
|
||||
|
||||
if csih_is_nt
|
||||
then
|
||||
|
||||
# Check if shairport-sync is installed and remove on user request.
|
||||
if cygrunsrv -Q shairport-sync > /dev/null 2>&1
|
||||
then
|
||||
csih_warning "The shairport-sync service is already installed."
|
||||
echo
|
||||
if csih_request "Do you want to reinstall it with different args?"
|
||||
then
|
||||
cygrunsrv -E shairport-sync
|
||||
cygrunsrv -R shairport-sync
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install shairport-sync service if it is not already installed
|
||||
if ! cygrunsrv -Q shairport-sync > /dev/null 2>&1
|
||||
then
|
||||
echo
|
||||
csih_warning "The following function requires administrator privileges!"
|
||||
if csih_request "Do you want to install shairport-sync as a service?"
|
||||
then
|
||||
if cygrunsrv -I shairport-sync --disp "CYGWIN Shairport Sync" --desc "AirPlay Audio. Use your computer's speakers or headphones to listen to audio from your iOS devices or other AirPlay sources on your network." --path $PREFIX/shairport-sync --dep avahi-daemon --dep messagebus
|
||||
then
|
||||
echo
|
||||
csih_inform "The shairport-sync service has been installed under the LocalSystem"
|
||||
csih_inform "account (also known as SYSTEM). To start the service now, call"
|
||||
csih_inform "\`net start shairport-sync' or \`cygrunsrv -S shairport-sync'. Otherwise,"
|
||||
csih_inform "it will start automatically after the next reboot."
|
||||
echo
|
||||
csih_inform "Check ${SYSCONFDIR}/shairport-sync.conf first, if it suits your needs."
|
||||
fi
|
||||
fi # user allowed us to install shairport-sync
|
||||
fi # shairport-sync already installed
|
||||
fi # csih_is_nt
|
||||
} # --- End of install_service --- #
|
||||
|
||||
|
||||
# ======================================================================
|
||||
# Main Entry Point
|
||||
# ======================================================================
|
||||
|
||||
|
||||
# Check how the script has been started. If
|
||||
# (1) it has been started by giving the full path and
|
||||
# that path is /etc/postinstall, OR
|
||||
# (2) Otherwise, if the environment variable
|
||||
# CONFIG_AUTO_ANSWER_NO is set
|
||||
# then set auto_answer to "no". This allows automatic
|
||||
# creation of the config files in /etc w/o overwriting
|
||||
# them if they already exist. In both cases, color
|
||||
# escape sequences are suppressed, so as to prevent
|
||||
# cluttering setup's logfiles.
|
||||
if [ "$PROGDIR" = "/etc/postinstall" ]
|
||||
then
|
||||
csih_auto_answer="no"
|
||||
csih_disable_color
|
||||
fi
|
||||
if [ -n "${CONFIG_AUTO_ANSWER_NO}" ]
|
||||
then
|
||||
csih_auto_answer="no"
|
||||
csih_disable_color
|
||||
fi
|
||||
|
||||
|
||||
# ======================================================================
|
||||
# Parse options
|
||||
# ======================================================================
|
||||
while :
|
||||
do
|
||||
case $# in
|
||||
0)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
|
||||
option=$1
|
||||
shift
|
||||
|
||||
case "$option" in
|
||||
-d | --debug )
|
||||
set -x
|
||||
csih_trace_on
|
||||
;;
|
||||
|
||||
-y | --yes )
|
||||
csih_auto_answer=yes
|
||||
;;
|
||||
|
||||
-n | --no )
|
||||
csih_auto_answer=no
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "usage: ${PROGNAME} [OPTION]..."
|
||||
echo
|
||||
echo "This script creates a basic shairport-sync configuration."
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " --debug -d Enable shell's debug output."
|
||||
echo " --yes -y Answer all questions with \"yes\" automatically."
|
||||
echo " --no -n Answer all questions with \"no\" automatically."
|
||||
echo
|
||||
exit 1
|
||||
;;
|
||||
|
||||
esac
|
||||
done
|
||||
|
||||
# ======================================================================
|
||||
# Action!
|
||||
# ======================================================================
|
||||
|
||||
# Check for ${SYSCONFDIR} directory
|
||||
csih_make_dir "${SYSCONFDIR}" "Cannot create global configuration files."
|
||||
chmod 775 "${SYSCONFDIR}"
|
||||
setfacl -m u:system:rwx "${SYSCONFDIR}"
|
||||
|
||||
# Check for ${LOGDIR} directory
|
||||
csih_make_dir "${LOGDIR}" "Syslogging using shairport-sync will not work."
|
||||
chmod 775 "${LOGDIR}"
|
||||
setfacl -m u:system:rwx "${LOGDIR}"
|
||||
|
||||
# Check for ${SOCKDIR} directory
|
||||
csih_make_dir "${SOCKDIR}" "SOCKET files of running processes will not be created."
|
||||
chmod 775 "${SOCKDIR}"
|
||||
setfacl -m u:system:rwx "${SOCKDIR}"
|
||||
|
||||
# maybe: csih_auto_answer=no will skip,
|
||||
# interactive user will get a chance to override
|
||||
install_service
|
||||
|
||||
|
||||
echo
|
||||
echo "Configuration finished. Have fun!"
|
||||
@@ -0,0 +1,18 @@
|
||||
<!-- initial version, based on /etc/dbus-1/system.d/avahi-dbus.conf, with thanks -->
|
||||
<!DOCTYPE busconfig PUBLIC
|
||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
|
||||
<!-- Only user SYSTEM can own the Shairport Sync service -->
|
||||
<policy user="SYSTEM">
|
||||
<allow own="org.gnome.ShairportSync"/>
|
||||
</policy>
|
||||
|
||||
<!-- Allow anyone to invoke methods on Shairport Sync server -->
|
||||
<policy context="default">
|
||||
<allow send_destination="org.gnome.ShairportSync"/>
|
||||
<allow receive_sender="org.gnome.ShairportSync"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!-- initial version, based on /etc/dbus-1/system.d/avahi-dbus.conf, with thanks -->
|
||||
<!DOCTYPE busconfig PUBLIC
|
||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
|
||||
<!-- Only user "SYSTEM" can own the Shairport Sync MPRIS service -->
|
||||
<policy user="SYSTEM">
|
||||
<allow own="org.mpris.MediaPlayer2.ShairportSync"/>
|
||||
</policy>
|
||||
|
||||
<!-- Allow anyone to invoke methods on Shairport Sync MPRIS server -->
|
||||
<policy context="default">
|
||||
<allow send_destination="org.mpris.MediaPlayer2.ShairportSync"/>
|
||||
<allow receive_sender="org.mpris.MediaPlayer2.ShairportSync"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
||||
+88
-16
@@ -1,5 +1,7 @@
|
||||
// Sample Configuration File for Shairport Sync
|
||||
// Commented out settings are generally the defaults, except where noted.
|
||||
// Some sections are operative only if Shairport Sync has been built with the right configuration flags.
|
||||
// See the individual sections for details.
|
||||
|
||||
// General Settings
|
||||
general =
|
||||
@@ -10,15 +12,15 @@ general =
|
||||
// %h for the hostname,
|
||||
// %H for the Hostname (i.e. with first letter capitalised (ASCII only)),
|
||||
// %v for the version number, e.g. 3.0 and
|
||||
// %V for the full version string, e.g. 3.0-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
|
||||
// %V for the full version string, e.g. 3.3-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
|
||||
// Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H".
|
||||
// password = "secret"; // leave this commented out if you don't want to require a password
|
||||
// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor.
|
||||
// interpolation = "auto"; // aka "stuffing". Default is "auto". Alternatives are "basic" or "soxr". Choose "soxr" only if you have a reasonably fast processor and Shairport Sync has been built with "soxr" support.
|
||||
// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one.
|
||||
// mdns_backend = "avahi"; // Run "shairport-sync -h" to get a list of all mdns_backends. The default is the first one.
|
||||
// port = 5000; // Listen for service requests on this port
|
||||
// udp_port_base = 6001; // start allocating UDP ports from this port number when needed
|
||||
// udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state.
|
||||
// udp_port_range = 10; // look for free ports in this number of places, starting at the UDP port base. Allow at least 10, though only three are needed in a steady state.
|
||||
// drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it
|
||||
// resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it
|
||||
// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to.
|
||||
@@ -29,6 +31,7 @@ general =
|
||||
// volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume.
|
||||
// "standard" makes the volume change more quickly at lower volumes and slower at higher volumes.
|
||||
// "flat" makes the volume change at the same rate at all volumes.
|
||||
// volume_range_combined_hardware_priority = "no"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware mixer first, then the built-in software attenuator.
|
||||
// run_this_when_volume_is_set = "/full/path/to/application/and/args"; // Run the specified application whenever the volume control is set or changed.
|
||||
// The desired AirPlay volume is appended to the end of the command line – leave a space if you want it treated as an extra argument.
|
||||
// AirPlay volume goes from 0 to -30 and -144 means "mute".
|
||||
@@ -37,10 +40,12 @@ general =
|
||||
// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
|
||||
// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose
|
||||
// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple.
|
||||
// If you build Shairport Sync with the flag --with-apple-alac, the Apple ALAC decoder will be chosen by default.
|
||||
// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically.
|
||||
|
||||
// audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
|
||||
// audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
|
||||
// audio_backend_buffer_desired_length_in_seconds = 0.2; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response time to volume changes becomes annoying. Default is 0.15 seconds in the alsa backend, 0.35 seconds in the pa backend and 1.0 seconds otherwise.
|
||||
// audio_backend_buffer_interpolation_threshold_in_seconds = 0.075; // Advanced feature. If the buffer size drops below this, stop using time-consuming interpolation like soxr to avoid dropouts due to underrun.
|
||||
// audio_backend_silent_lead_in_time = 2.0; // This optional advanced setting, from 0.0 and 4.0 seconds, sets the length of the period of silence that precedes the start of the audio. The default is the latency, usually 2.0 seconds. Values greater than the latency are ignored. Values that are too low will affect initial synchronisation.
|
||||
// dbus_service_bus = "system"; // The Shairport Sync dbus interface, if selected at compilation, will appear
|
||||
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
|
||||
@@ -48,12 +53,22 @@ general =
|
||||
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
|
||||
};
|
||||
|
||||
// Advanced parameters for controlling how Shairport Sync runs a play session
|
||||
// Advanced parameters for controlling how Shairport Sync stays active and how it runs a session
|
||||
sessioncontrol =
|
||||
{
|
||||
// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
|
||||
// "active" state starts when play begins and ends when the active_state_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed.
|
||||
// run_this_before_entering_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
// run_this_after_exiting_active_state = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
// active_state_timeout = 10.0; // wait for this number of seconds after play ends before leaving the active state, unless another play session begins.
|
||||
|
||||
// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it. An error code-string is passed as the last argument.
|
||||
// Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem.
|
||||
// You could hook on a program to do this automatically, but beware -- the device may then power off and restart without warning!
|
||||
// wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing
|
||||
|
||||
// allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source
|
||||
// session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again.
|
||||
};
|
||||
@@ -61,21 +76,28 @@ sessioncontrol =
|
||||
// Back End Settings
|
||||
|
||||
// These are parameters for the "alsa" audio back end.
|
||||
// For this section to be operative, Shairport Sync must be built with the following configuration flag:
|
||||
// --with-alsa
|
||||
alsa =
|
||||
{
|
||||
// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
|
||||
// mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software.
|
||||
// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this.
|
||||
// output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability.
|
||||
// output_format = "S16"; // can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
|
||||
// output_rate = "auto"; // can be "auto", 44100, 88200, 176400 or 352800, but the device must have the capability.
|
||||
// output_format = "auto"; // can be "auto", ""U8", "S8", "S16", "S16_LE", "S16_BE", "S24", "S24_LE", "S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE" or "S32_BE" but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
|
||||
// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no".
|
||||
// period_size = <number>; // Use this optional advanced setting to set the alsa period size near to this value
|
||||
// buffer_size = <number>; // Use this optional advanced setting to set the alsa buffer size near to this value
|
||||
// use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes"
|
||||
// use_hardware_mute_if_available = "no"; // Use this optional advanced setting to control whether the hardware in the DAC is used for muting. Default is "no", for compatibility with other audio players.
|
||||
// maximum_stall_time = 0.200; // Use this optional advanced setting to control how long to wait for data to be consumed by the output device before considering it an error. It should never approach 200 ms.
|
||||
// use_precision_timing = "auto"; // If the output device is a real hardware device, precision timing will be used, which is needed for "disable_standby_mode" below. Choose "no" for more compatible standard timing, choose yes to force the use of precision timing, which may cause problems.
|
||||
// disable_standby_mode = "never"; // This setting prevents the DAC from entering the standby mode. Some DACs make small "popping" noises when they go in and out of standby mode. Settings can be: "always", "auto" or "never". Default is "never", but only for backwards compatibility. The "auto" setting prevents entry to standby mode while Shairport Sync is in the "active" mode. You can use "yes" instead of "always" and "no" instead of "never". Needs precision timing to be available.
|
||||
};
|
||||
|
||||
// Parameters for the "sndio" audio back end. All are optional.
|
||||
// For this section to be operative, Shairport Sync must be built with the following configuration flag:
|
||||
// --with-sndio
|
||||
sndio =
|
||||
{
|
||||
// device = "snd/0"; // optional setting to set the name of the output device. Default is the sndio system default.
|
||||
@@ -86,23 +108,46 @@ sndio =
|
||||
};
|
||||
|
||||
// Parameters for the "pa" PulseAudio backend.
|
||||
// For this section to be operative, Shairport Sync must be built with the following configuration flag:
|
||||
// --with-pa
|
||||
pa =
|
||||
{
|
||||
// application_name = "Shairport Sync"; //Set this to the name that should appear in the Sounds "Applications" tab when Shairport Sync is active.
|
||||
};
|
||||
|
||||
// Parameters for the "jack" JACK Audio Connection Kit backend.
|
||||
// For this section to be operative, Shairport Sync must be built with the following configuration flag:
|
||||
// --with-jack
|
||||
jack =
|
||||
{
|
||||
// client_name = "shairport-sync"; // Set this to the name of the client that should appear in "Connections" when Shairport Sync is active.
|
||||
// autoconnect_pattern = ""; // Set this to a POSIX regular expression pattern that describes the ports you would like to connect to
|
||||
// automatically. Examples:
|
||||
// "system:playback_[12]"
|
||||
// "some_app_[0-9]*:in-[LR]"
|
||||
// "jack_mixer:in_2[78]"
|
||||
// Beware: if you make a syntax error, libjack might crash. In that case, fix it and start over.
|
||||
// For a good overview, look here: https://www.ibm.com/support/knowledgecenter/SS8NLW_11.0.1/com.ibm.swg.im.infosphere.dataexpl.engine.doc/c_posix-regex-examples.html
|
||||
};
|
||||
|
||||
// Parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done.
|
||||
// For this section to be operative, Shairport Sync must have been built with the following configuration flag:
|
||||
// --with-pipe
|
||||
pipe =
|
||||
{
|
||||
// name = "/path/to/pipe"; // there is no default pipe name for the output
|
||||
};
|
||||
|
||||
// These are no configuration file parameters for the "stdout" audio back end. No interpolation is done.
|
||||
// There are no configuration file parameters for the "stdout" audio back end. No interpolation is done.
|
||||
// To include support for the "stdout" backend, Shairport Sync must be built with the following configuration flag:
|
||||
// --with-stdout
|
||||
|
||||
// These are no configuration file parameters for the "ao" audio back end. No interpolation is done.
|
||||
|
||||
// Static latency settings are deprecated and the settings have been removed.
|
||||
// There are no configuration file parameters for the "ao" audio back end. No interpolation is done.
|
||||
// To include support for the "ao" backend, Shairport Sync must be built with the following configuration flag:
|
||||
// --with-ao
|
||||
|
||||
// For this section to be operative, Shairport Sync must be built with the following configuration flag:
|
||||
// --with-convolution
|
||||
dsp =
|
||||
{
|
||||
|
||||
@@ -112,7 +157,7 @@ dsp =
|
||||
// and apply a correction to get a flat response curve.
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// convolution = "yes"; // Activate the convolution filter.
|
||||
// convolution = "no"; // Set this to "yes" to activate the convolution filter.
|
||||
// convolution_ir_file = "impulse.wav"; // Impulse Response file to be convolved to the audio stream
|
||||
// convolution_gain = -4.0; // Static gain applied to prevent clipping during the convolution process
|
||||
// convolution_max_length = 44100; // Truncate the input file to this length in order to save CPU.
|
||||
@@ -127,16 +172,19 @@ dsp =
|
||||
// The setting "loudness_reference_volume_db" should be set at the volume reported by shairport-sync when listening to music at a normal listening volume.
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// loudness = "yes"; // Activate the filter
|
||||
// loudness = "no"; // Set this to "yes" to activate the loudness filter
|
||||
// loudness_reference_volume_db = -20.0; // Above this level the filter will have no effect anymore. Below this level it will gradually boost the low frequencies.
|
||||
|
||||
};
|
||||
|
||||
// How to deal with metadata, including artwork
|
||||
// For this section to be operative, Shairport Sync must be built with at one (or more) of the following configuration flags:
|
||||
// --with-metadata, --with-dbus-interface, --with-mpris-interface or --with-mqtt-client.
|
||||
// In those cases, "enabled" and "include_cover_art" will both be "yes" by default
|
||||
metadata =
|
||||
{
|
||||
// enabled = "no"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
|
||||
// include_cover_art = "no"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
|
||||
// enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
|
||||
// include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
|
||||
// pipe_name = "/tmp/shairport-sync-metadata";
|
||||
// pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up
|
||||
// socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes"
|
||||
@@ -144,6 +192,30 @@ metadata =
|
||||
// socket_msglength = 65000; // the maximum packet size for any UDP metadata. This will be clipped to be between 500 or 65000. The default is 500.
|
||||
};
|
||||
|
||||
// How to enable the MQTT-metadata/remote-service
|
||||
// For this section to be operative, Shairport Sync must be built with the following configuration flag:
|
||||
// --with-mqtt-client
|
||||
mqtt =
|
||||
{
|
||||
// enabled = "no"; // set this to yes to enable the mqtt-metadata-service
|
||||
// hostname = "iot.eclipse.org"; // Hostname of the MQTT Broker
|
||||
// port = "1883";
|
||||
// username = NULL; //set this to a string with your username, to enable username authentication
|
||||
// password = NULL; //set this to a string with your password, to enable username & password authentication
|
||||
// capath = NULL; //set this to the folder with the CA-Certificates to be accepted for the server certificate. If not set, TLS is not used
|
||||
// cafile = NULL; //this may be used as an (exclusive) alternative to capath with a single file for all ca-certificates
|
||||
// certfile = NULL; //set this to a string to a user certificate to enable MQTT Client certificates. keyfile must also be set!
|
||||
// keyfile = NULL; //private key for MQTT Client authentication
|
||||
// topic = NULL; //MQTT topic where this instance of shairport-sync should publish. If not set, the general.name value is used.
|
||||
// publish_raw = "no"; //whether to publish all available metadata under the codes given in the 'metadata' docs.
|
||||
// publish_parsed = "no"; //whether to publish a small (but useful) subset of metadata under human-understandable topics
|
||||
// Currently published topics:artist,album,title,genre,format,songalbum,volume,client_ip,
|
||||
// Additionally, empty messages at the topics play_start,play_end,play_flush,play_resume are published
|
||||
// publish_cover = "no"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker
|
||||
// enable_remote = "no"; //whether to remote control via MQTT. RC is available under `topic`/remote.
|
||||
// Available commands are "command", "beginff", "beginrew", "mutetoggle", "nextitem", "previtem", "pause", "playpause", "play", "stop", "playresume", "shuffle_songs", "volumedown", "volumeup"
|
||||
}
|
||||
|
||||
// Diagnostic settings. These are for diagnostic and debugging only. Normally you sould leave them commented out
|
||||
diagnostics =
|
||||
{
|
||||
@@ -151,6 +223,6 @@ diagnostics =
|
||||
// statistics = "no"; // set to "yes" to print statistics in the log
|
||||
// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose.
|
||||
// log_show_time_since_startup = "no"; // set this to yes if you want the time since startup in the debug message -- seconds down to nanoseconds
|
||||
// log_show_time_since_last_message = "no"; // set this to yes if you want the time since the last debug message in the debug message -- seconds down to nanoseconds
|
||||
// log_show_time_since_last_message = "yes"; // set this to yes if you want the time since the last debug message in the debug message -- seconds down to nanoseconds
|
||||
// drop_this_fraction_of_audio_packets = 0.0; // use this to simulate a noisy network where this fraction of UDP packets are lost in transmission. E.g. a value of 0.001 would mean an average of 0.1% of packets are lost, which is actually quite a high figure.
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ stop_cmd="${name}_stop"
|
||||
|
||||
shairport_sync_start()
|
||||
{
|
||||
checkyesno shairport_sync_enable && echo "Starting shairport-sync." && \
|
||||
checkyesno shairport_sync_enable && echo "Starting shairport-sync." && \
|
||||
su -m shairport-sync -c "/usr/local/bin/shairport-sync -d"
|
||||
}
|
||||
|
||||
|
||||
+12
-25
@@ -23,8 +23,7 @@ DAEMON=@prefix@/bin/$NAME
|
||||
# impossible to pass as arguments.
|
||||
|
||||
# Instead, we add the arguments directly to the relevant line in the do_start() function below
|
||||
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
PIDFILE=/var/run/$NAME/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
@@ -48,31 +47,19 @@ do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
# 1 if PID directory didn't exist and couldn't be created with approproate permission
|
||||
# 2 if daemon was already running
|
||||
# 3 if daemon could not be started
|
||||
[ -e /var/run/shairport-sync ] || ( mkdir -p /var/run/shairport-sync && chown shairport-sync:shairport-sync /var/run/shairport-sync ) || return 1
|
||||
start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null || return 2
|
||||
|
||||
# This script is set to start running after all other services have started.
|
||||
# However, if you find that Shairport Sync is still being started before what it needs is ready,
|
||||
# uncomment the next line to get the script to wait for three seconds before attempting to start Shairport Sync.
|
||||
# sleep 3
|
||||
# This script is set to start running after all other services have started.
|
||||
# However, if you find that Shairport Sync is still being started before what it needs is ready,
|
||||
# uncomment the next line to get the script to wait for three seconds before attempting to start Shairport Sync.
|
||||
# sleep 3
|
||||
|
||||
# Uncomment just one of the following start-stop-daemon lines, or comment them all out and add your own.
|
||||
# Shairport Sync will read settings from the configuration file (/etc/shairport-sync.conf by default) and will then apply any command line options.
|
||||
# In the default script, the first line is uncommented, selecting daemon mode (-d). Settings will be taken from the configuration file.
|
||||
# BTW, if you're using software volume control, you may have to use alsamixer or similar to set the output device's volume to its maximum level first
|
||||
# BTW2, you can use alsamixer to find device identifiers (e.g. hw:1) and mixer names (e.g. "Speaker"). No need to change ALSA's defaults.
|
||||
# BTW3, the argument after -a is simply the name the shairport service will be visible as.
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 2
|
||||
# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "Topping TP30 or Griffin iMic on Raspberry Pi" -- -d hw:1 -t hardware -c "PCM" || return 2
|
||||
# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "'3D Sound' USB Soundcard on Raspberry Pi" -- -d hw:1 -t hardware -c "Speaker" || return 2
|
||||
# BTW, that "3D Sound" USB soundcard sometimes has the mixer name "Headphone" rather than "Speaker" -- use alsamixer to check.
|
||||
# start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d -a "IQaudIO" -- -d hw:1 -t hardware -c "Playback Digital" || return 2
|
||||
# BTW, newer versions of IQaudIO have a different mixer name -- use alsamixer to check.
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
# Settings from the configuration file (@sysconfdir@/shairport-sync.conf by default)
|
||||
start-stop-daemon -c shairport-sync:shairport-sync --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -d || return 3
|
||||
}
|
||||
|
||||
#
|
||||
|
||||
@@ -97,7 +97,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if (argc > 2) {
|
||||
poptPrintHelp(optCon, stderr, 0);
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Now do options processing */
|
||||
|
||||
@@ -70,7 +70,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if (argc > 2) {
|
||||
poptPrintHelp(optCon, stderr, 0);
|
||||
exit(1);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Now do options processing */
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
Name: shairport-sync
|
||||
Version: 3.2.2
|
||||
Version: 3.3
|
||||
Release: 1%{?dist}
|
||||
Summary: AirTunes emulator. Multi-Room with Audio Synchronisation
|
||||
# MIT licensed except for tinysvcmdns under BSD,
|
||||
@@ -14,7 +14,6 @@ BuildRequires: systemd
|
||||
BuildRequires: pkgconfig(libconfig)
|
||||
BuildRequires: pkgconfig(popt)
|
||||
BuildRequires: pkgconfig(openssl)
|
||||
BuildRequires: pkgconfig(libdaemon)
|
||||
BuildRequires: pkgconfig(avahi-core)
|
||||
BuildRequires: pkgconfig(alsa)
|
||||
BuildRequires: pkgconfig(soxr)
|
||||
@@ -67,6 +66,8 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
|
||||
%license LICENSES
|
||||
|
||||
%changelog
|
||||
* Fri May 24 2019 Mike Brady <mikebrady@eircom.net) 3.3
|
||||
- Audio enhancements, stability improvements, MQTT and Jack Audio interfaces.
|
||||
* Sun Oct 14 2018 Mike Brady <mikebrady@eircom.net) 3.2.2
|
||||
- Compatibility with iOS 12 and mac OS Mojave AirPlay latencies. Minor bug fix.
|
||||
* Fri Jul 13 2018 Mike Brady <mikebrady@eircom.net) 3.2.1
|
||||
|
||||
+562
-223
File diff suppressed because it is too large
Load Diff
+32
-9
@@ -6,6 +6,8 @@
|
||||
* tinysvcmdns - a tiny MDNS implementation for publishing services
|
||||
* Copyright (C) 2011 Darell Tan
|
||||
* All rights reserved.
|
||||
* Updated many times by Mike Brady (c) 2014 -- 2019
|
||||
* Includes fixes for CVE-12087 and CVE-2017-12130
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -81,6 +83,8 @@ struct name_comp {
|
||||
|
||||
// duplicates a name
|
||||
inline uint8_t *dup_nlabel(const uint8_t *n) {
|
||||
if (n == NULL)
|
||||
return NULL;
|
||||
assert(n[0] <= 63); // prevent mis-use
|
||||
return (uint8_t *)strdup((char *)n);
|
||||
}
|
||||
@@ -125,8 +129,8 @@ char *nlabel_to_str(const uint8_t *name) {
|
||||
const uint8_t *p;
|
||||
size_t buf_len = 256;
|
||||
|
||||
assert(name != NULL);
|
||||
|
||||
if (name == NULL)
|
||||
return NULL;
|
||||
label = labelp = malloc(buf_len);
|
||||
|
||||
if (label) {
|
||||
@@ -186,7 +190,9 @@ uint8_t *create_label(const char *txt) {
|
||||
int len;
|
||||
uint8_t *s;
|
||||
|
||||
assert(txt != NULL);
|
||||
// assert(txt != NULL);
|
||||
if (txt == NULL)
|
||||
return NULL;
|
||||
len = strlen(txt);
|
||||
if (len > 63)
|
||||
return NULL;
|
||||
@@ -416,7 +422,7 @@ int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) {
|
||||
if (*rr_head == NULL) {
|
||||
*rr_head = node;
|
||||
} else {
|
||||
struct rr_list *e = *rr_head, *taile;
|
||||
struct rr_list *e = *rr_head, *taile = NULL;
|
||||
for (; e; e = e->next) {
|
||||
// already in list - don't add
|
||||
if (e->e == rr) {
|
||||
@@ -426,7 +432,10 @@ int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) {
|
||||
if (e->next == NULL)
|
||||
taile = e;
|
||||
}
|
||||
taile->next = node;
|
||||
if (taile)
|
||||
taile->next = node;
|
||||
else
|
||||
DEBUG_PRINTF("taile not given a value.\n");
|
||||
}
|
||||
} else {
|
||||
die("can not allocate memory for \"node\" in tinysvcmdns.");
|
||||
@@ -500,7 +509,7 @@ struct rr_entry *rr_create(uint8_t *name, enum rr_type type) {
|
||||
}
|
||||
|
||||
void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type) {
|
||||
assert(rr_nsec->type = RR_NSEC);
|
||||
assert((rr_nsec->type = RR_NSEC));
|
||||
assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap));
|
||||
|
||||
rr_nsec->data.NSEC.bitmap[type / 8] = 1 << (7 - (type % 8));
|
||||
@@ -675,9 +684,12 @@ static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
|
||||
if (rr)
|
||||
memset(rr, 0, sizeof(struct rr_entry));
|
||||
else
|
||||
die("could not allocate memory for \"rr\" in tinysvcmdns");
|
||||
goto err;
|
||||
|
||||
name = uncompress_nlabel(pkt_buf, pkt_len, off);
|
||||
if (name == NULL)
|
||||
goto err;
|
||||
|
||||
p += label_len(pkt_buf, pkt_len, off);
|
||||
rr->name = name;
|
||||
|
||||
@@ -691,6 +703,10 @@ static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
|
||||
rr_list_append(&pkt->rr_qn, rr);
|
||||
|
||||
return p - (pkt_buf + off);
|
||||
|
||||
err:
|
||||
free(rr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// parse the MDNS RR section
|
||||
@@ -713,10 +729,13 @@ static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
|
||||
if (rr)
|
||||
memset(rr, 0, sizeof(struct rr_entry));
|
||||
else
|
||||
die("could not allocate memory for \"rr (2)\" in tinysvcmdns");
|
||||
goto err;
|
||||
|
||||
name = uncompress_nlabel(pkt_buf, pkt_len, off);
|
||||
p += label_len(pkt_buf, pkt_len, off);
|
||||
if (name == NULL)
|
||||
goto err;
|
||||
|
||||
// parse the MDNS RR section p += label_len(pkt_buf, pkt_len, off);
|
||||
rr->name = name;
|
||||
|
||||
rr->type = mdns_read_u16(p);
|
||||
@@ -819,6 +838,10 @@ static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, struct
|
||||
rr_list_append(&pkt->rr_ans, rr);
|
||||
|
||||
return p - (pkt_buf + off);
|
||||
|
||||
err:
|
||||
free(rr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// parse a MDNS packet into an mdns_pkt struct
|
||||
|
||||
Reference in New Issue
Block a user