Merge branch 'unstable' into development

This commit is contained in:
Mike Brady
2019-02-12 11:03:56 +00:00
33 changed files with 1923 additions and 1661 deletions
+5 -1
View File
@@ -8,7 +8,7 @@ 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
if BUILD_FOR_FREEBSD
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
@@ -40,6 +40,10 @@ 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
+2 -540
View File
@@ -6,544 +6,6 @@ Audio played by a Shairport Sync-powered device stays synchronised with the sour
Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming.
This is the unstable "development" branch. Changes and updates are incorporated into this branch quickly. To access the stable version, where changes are made after due time, please switch to the "master" branch.
More Information
----------
Shairport Sync offers *full audio synchronisation*, a feature of AirPlay that previous implementations do not provide. Full audio synchronisation means that audio is played on the output device at exactly the time specified by the audio source. To accomplish this, Shairport Sync needs access to audio systems – such as `alsa` on Linux and `sndio` on FreeBSD – that provide very accurate timing information about output devices. Shairport Sync must have direct access to the output device used, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem).
Alternatively, Shairport Sync works well with PulseAudio, a widely used sound server found on many desktop Linuxes. While the timing information is not as accurate as that of `alsa` or `sndio`, it is often impractical to remove or disable PulseAudio. In that case, the `pa` backend can be used. An older backend for PulseAudio called `pulse` does not support synchronisation and is deprecated.
For other use cases, Shairport Sync can provide synchronised audio output to a unix pipe or to standard output, or to audio systems that do not provide timing information. This could perhaps be described as *partial audio synchronisation*, where synchronised audio is provided by Shairport Sync, but what happens to it in the subsequent processing chain, before it reaches the listener's ear, is outside the control of Shairport Sync.
For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki.
Synchronisation, Latency, "Stuffing"
---------
The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on.
As mentioned previously, Shairport Sync implements full audio synchronisation when used with `alsa`, `sndio` or PulseAudio systems. This is done by monitoring the timestamps present in data coming from the audio source and the timing information from the audio system, e.g. `alsa`. To maintain the latency required for exact synchronisation, if the output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”.
Stuffing is not done for partial audio synchronisation – the audio samples are simply presented at exactly the right time to the next stage in the processing chain.
Timestamps are referenced relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. So, another thing Shairport Sync has to do is to synchronize the source clock and the local clock, and it does this usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols.
What else?
--------------
* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range.
* Hardware Mute — Shairport Sync can mute properly if the hardware supports it.
* Support for the Apple ALAC decoder.
* Output bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits.
* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds with `alsa`, 0.35 seconds with `sndio`.
* Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.)
* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
* A native [MQTT](https://en.wikipedia.org/wiki/MQTT) interface for metadata and/or controls.
* Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync.
* Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available.
Heritage
-------
Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/development/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0.
Status
------
Shairport Sync works on a wide variety of Linux devices and FreeBSD. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Stretch, Jessie and Wheezy, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input.
Shairport Sync will work with PulseAudio, which is installed in many desktop Linuxes. PulseAudio normally runs in the *user mode* but can be configured to run in *system mode*, though this is not recommended. Shairport Sync can work with it in either mode.
Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card – see the note below on configuring the Raspberry Pi to make best use of it. It runs well on the Raspberry Pi Zero W with a suitable USB or I2S card.
Shairport Sync runs natively on FreeBSD using the `sndio` sound system.
Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux, Fedora and FreeBSD inside VMWare Fusion on a Mac, but synchronisation is inaccurate — possibly because the sound card is being emulated.
In addition, Shairport Sync can output to standard output, pipes, `soundio` and `ao` devices using appropriate backends.
For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution.
Building And Installing the Development Version
---------------------
See [here](https://github.com/mikebrady/shairport-sync/blob/development/INSTALL.md) for simple installation instructions for modern Linux systems like Rapsberry Pis.
The following procedures will build and install the `shairport-sync` application into your system.
**Remove Old Versions of Shairport Sync and its Startup Scripts**
You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
**FreeBSD**
To build Shairport Sync from sources on FreeBSD please refer to [FREEBSD.md](https://github.com/mikebrady/shairport-sync/blob/master/FREEBSD.md).
**Determine The Configuration Needed**
Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary:
```
$ pactl info
Server String: unix:/run/user/1000/pulse/native
Library Protocol Version: 30
Server Protocol Version: 30
Is Local: yes
Client Index: 9
Tile Size: 65472
User Name: mike
Host Name: ubuntu
Server Name: pulseaudio
Server Version: 8.0
Default Sample Specification: s16le 2ch 44100Hz
Default Channel Map: front-left,front-right
Default Sink: alsa_output.pci-0000_02_02.0.analog-stereo
Default Source: alsa_input.pci-0000_02_02.0.analog-stereo
Cookie: 96f9:3e8d
$
```
If PulseAudio in not installed, you'll get something like this:
```
$ pactl info
-bash: pactl: command not found
$
```
If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend.
If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it.
**Building**
To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions.
The following libraries are required:
* OpenSSL or mbed TLS (PolarSSL is supported but deprecated)
* Avahi
* ALSA and/or PulseAudio
* libdaemon
* autoconf
* automake
* libtool
* libpopt
* libconfig
Optional:
* libsoxr
* libalac (This is a library containing the Apple ALAC decoder.)
* libmosquitto (This is to enable the mqtt-metadata-interface.)
Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up.
Debian, Ubuntu and Raspbian users can get the basics with:
- `# apt-get install build-essential git xmltoman` – these may already be installed.
- `# apt-get install autoconf automake libtool libdaemon-dev libpopt-dev libconfig-dev`
- `# apt-get install libasound2-dev` for the ALSA libraries
- `# apt-get install libpulse-dev` for the PulseAudio libraries
- `# apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended).
- `# apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise.
- `# apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported.
- `# apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md).
If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information.
**Download Shairport Sync:**
```
$ git clone https://github.com/mikebrady/shairport-sync.git
```
Next, `cd` into the shairport-sync directory and execute the following commands:
```
$ git checkout development
$ autoreconf -i -f
```
(Note that the `autoreconf...` step may take some time on less powerful machines.)
**Choose the appropriate `--with-*` options:**
(Don't worry -- there's a recommended set of configuration options further down.)
- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality.
- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed.
- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout).
- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe.
- `--with-soundio` include an optional backend module to enable raw audio to be output through the soundio system.
- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15.
- `--with-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL.
- `--with-soxr` for libsoxr-based resampling.
- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location.
- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader.
- `--with-mqtt-client` to add support for publishing metadata and remote control via [MQTT](https://en.wikipedia.org/wiki/MQTT).
- `--with-configfiles` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten.
- `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use.
- `--with-apple-alac` to include the Apple ALAC Decoder.
- `--with-convolution` to include a convolution filter that can be used to apply effects such as frequency and phase correction, and a loudness filter that compensates for human ear non-linearity. Requires `libsndfile`.
- `--with-systemd` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on `systemd`-based Linuxes. Default is not to to install.
- `--with-systemv` to include a script to create a Shairport Sync service that can optionally launch automatically at startup on System V based Linuxes. Default is not to to install.
**Determine if it's a `systemd` or a "System V" installation:**
If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.)
There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system.
The easiest way to look at the first few lines of the `init` manual. Enter the command:
```
$ man init
```
In a `systemd` system, the top lines of the `man` page make it clear that it's a `systemd` system, as follows (from Ubuntu 16.04):
```
SYSTEMD(1) systemd SYSTEMD(1)
NAME
systemd, init - systemd system and service manager
...
```
Other init systems will look considerably different. For instance, in an `upstart` system, the top lines of the `man` page indicate it's using `upstart` system, as follows (from Ubuntu 14.04):
```
init(8) System Manager's Manual init(8)
NAME
init - Upstart process management daemon
SYNOPSIS
init [OPTION]...
...
```
In a System V system, the top lines of the `man` page are as follows (from Debian 7.11):
```
INIT(8) Linux System Administrator's Manual INIT(8)
NAME
init, telinit - process control initialization
SYNOPSIS
/sbin/init [ -a ] [ -s ] [ -b ] [ -z xxx ] [ 0123456Ss ]
/sbin/telinit [ -t SECONDS ] [ 0123456sSQqabcUu ]
/sbin/telinit [ -e VAR[=VAL] ]
...
```
If your system is definitely a `systemd` system, choose `--with-systemd` below. Otherwise, choose `--with-systemv`.
**Choose the location of the configuration file**
A final consideration is the location of the configuration file `shairport-sync.conf`. This will be placed in the directory specified by the `sysconfdir` configuration variable, which defaults to `/usr/local/etc`. This is normal in BSD Unixes, but is unusual in Linux. Hence, for Linux installations, you need to set the `sysconfdir` variable to `/etc` using the configuration setting `--sysconfdir=/etc`.
**Sample `./configure` command with parameters for a typical Linux `systemd` installation:**
Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and later, and Raspbian Stretch and Jessie. It specifies both the ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot:
`$ ./configure --sysconfdir=/etc --with-alsa --with-pa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd`
* Omit the `--with-soxr` if the libsoxr library is not available.
* For installation into a System V system, replace the `--with-systemd` with `--with-systemv`.
* If you intend to use Shairport Sync with PulseAudio in the standard user mode, it can not be a system service, so you should omit both `--with-systemd` and `--with-systemv`.
**Build and Install the Application:**
Enter:
```
$ make
```
to build the application.
Assuming you have used the `./configure` settings suggested above, you can now install the application:
```
$ sudo make install
```
The user and group `shairport-sync` will be created if necessary, and a runtime folder will be created at `/var/run/shairport-sync` if you have chosen `--with-systemv`. In addition, a `man` page, a default configuration file and automatic power-on startup script will be installed.
**Complete installation into to a `systemd` system**
If you have chosen the `--with-systemd` configuration option, then, to enable Shairport Sync to start automatically at system startup, enter:
```
$ sudo systemctl enable shairport-sync
```
**Complete installation into a System V system**
If you have chosen the `--with-systemv` configuration option, enter:
```
$ sudo update-rc.d shairport-sync defaults 90 10
```
**Man Page**
You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html
Configuring Shairport Sync
--------
There are two logically distinct parts to getting Shairport Sync to run properly on your machine: (1) starting and stopping it and (2) ensuring it has the right settings.
**(1) Starting and Stopping:**
Starting and stopping Shairport Sync automatically is taken care of differently in different versions of Linux – see the previous section for an example of installing into a `systemd` or a System V based system.
If you are using PulseAudio in the standard "user" mode, you can not start Shairport Sync automatically at boot time – it must be started when the user logs in to a GUI session. Various GUI tools exist to enable you to build a Startup Application. However, there does not appear to be a similar tool for terminating the program when you log out. Also, it the GUI switches to another user, the user's PulseAudio session is disconnected from the output device.
**(2) Settings:**
To get the best from Shairport Sync, you’ll need to (a) give Shairport Sync a service name by which it will be seen in iTunes etc. and (b) specify the backend you wish to use - `alsa` for the ALSA backend, or `pa` for the PulseAudio back end. If only one backend is included at compilation, or if the backend is ALSA, there is no need to explicitly specify the backend.
For the ALSA backend you may need to (c) specify the output device to use and (d) specify the name of the mixer volume control to use to control the output level. To get values for (b) and (c) you might need to explore the ALSA output devices with a program like `alsamixer` or similar.
Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf` (note that in FreeBSD it will be at `/usr/local/etc/shairport-sync.conf`). When you run `$sudo make install`, a sample configuration file is installed or updated at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` in FreeBSD). This contains all the setting groups and all the settings available, but they all are commented out (comments begin with `//`) so that default values are used. The file contains explanations of the settings, useful hints and suggestions. In addition, if the file doesn't already exist, a default configuration is installed, which should work in almost any system with a sound card.
Settings in the configuration file are grouped. For instance, there is a `general` group within which you can use the `name` tag to set the service name. Suppose you wanted to set the name of the service to `Front Room`, give the service the password `secret` and use `libsoxr` interpolation, then you should do the following:
```
general =
{
name = "Front Room";
password = "secret";
interpolation = "soxr";
// ... other general settings
};
```
(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.)
**Important:** You should *never* use an important password as the AirPlay password for a Shairport Sync player -- the password is stored in Shairport Sync's configuration file in plain text and is thus completely vulnerable.
No backend is specified here, so it will default to the `alsa` backend if more than one back end has been compiled. To route the output to PulseAudio, add:
```
output_backend = "pa";
```
to the `general` stanza.
The `alsa` group is used to specify properties of the output device. The most obvious setting is the name of the output device which you can set using the `output_device` tag.
The following `alsa` group settings are very important for maximum performance. If your audio device has a mixer that can be use to control the volume, then Shairport Sync can use it to give instant response to volume and mute commands and it can offload some work from the processor.
* The `mixer_control_name` tag allows you to specify the name of the mixer volume control.
* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is on the `output_device`, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`:
```
alsa =
{
output_device = "hw:0,5";
mixer_device = "hw:0";
mixer_control_name = "PCM";
// ... other alsa settings
};
```
The `pa` group is used to specify settings relevant to the PulseAudio backend. You can set the "Application Name" that will appear in the "Sound" control panel. You can also set the sink name that audio will be pushed to. Here's an example `pa` group configuration:
```
pa =
{
application_name = "Shairport Sync - Bedroom";
sink = "server_bedroom"; // Sink name or ID. If not specified, the default sink will be used.
};
```
Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing.
Note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line. (This behaviour may be different from other Shairports.)
Shairport Sync can run a program whenever the volume is set or changed. You specify them using the `general` group settings `run_this_when_volume_changes`. This is to facilitate situations where something has to be done when the volume changes, e.g. adjust an external mixer value. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the command has been completed before continuing. Again, please note that the full path to the program must be specified, and script files will not be executed unless they are marked as executable and have the appropriate shebang `#!/bin/...` as the first line.
Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatibility, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method.
**Raspberry Pi**
The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line:
```
audio_pwm_mode=2
```
to `/boot/config.txt` and reboot.
Apart from a loud click when used for the first time after power-up, it is quite adequate for casual listening. A problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 50 dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 50, the top 50 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -46dB, below which muting will occur.
From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume.
Another setting to consider is the `general` `drift_tolerance_in_seconds` setting: you should set it to a larger tolerance, say 10 milliseconds – `drift_tolerance_in_seconds=0.010;` – to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC.
*Command Line Arguments*
As previously mentioned, you can use command line arguments to provide settings to Shairport Sync as before, though newer settings will only be available via the configuration file. For full information, please read the Shairport Sync `man` page, also available at http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html.
Apart from the following options, all command line options can be replaced by settings in the configuration file. Here is a brief description of command line options that are not replicated by settings in the settings file.
* The `-c` option allows you to specify the location of the configuration file.
* The `-V` option gives you version information about Shairport Sync and then quits.
* The `-d` option causes Shairport Sync to properly daemonise itself, that is, to run in the background. You may need sudo privileges for this.
* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this.
The System V init script at `/etc/init.d/shairport-sync` has a bare minimum :
`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file.
Examples
--------
Here are some examples of complete configuration files.
```
general = {
name = "Joe's Stereo";
};
alsa = {
output_device = "hw:0";
};
```
This gives the service a particular name — "Joe's Stereo" and specifies that audio device hw:0 be used.
For best results with the `alsa` backend — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the mixer control to be used as the `mixer_control_name`.
Here is an example for for a Raspberry Pi using its internal soundcard — device hw:0 — that drives the headphone jack:
```
general = {
name = "Mike's Boombox";
};
alsa = {
output_device = "hw:0";
mixer_control_name = "PCM";
};
```
Here is an example of using soxr-based resampling and driving a Topping TP30 Digital Amplifier, which has an integrated USB DAC and which is connected as audio device `hw:1`:
```
general = {
name = "Kitchen";
interpolation = "soxr";
};
alsa = {
output_device = "hw:1";
mixer_control_name = "PCM";
};
```
For a cheapo "3D Sound" USB card (Stereo output and input only) on a Raspberry Pi:
```
general = {
name = "Front Room";
};
alsa = {
output_device = "hw:1";
mixer_control_name = "Speaker";
};
```
For a first generation Griffin iMic on a Raspberry Pi:
```
general = {
name = "Attic";
};
alsa = {
output_device = "hw:1";
mixer_control_name = "PCM";
};
```
For an NSLU2, which has no internal sound card, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus:
On an NSLU2, to drive a first generation Griffin iMic:
```
general = {
name = "Den";
};
alsa = {
mixer_control_name = "PCM";
};
```
On an NSLU2, to drive the "3D Sound" USB card:
```
general = {
name = "TV Room";
};
alsa = {
mixer_control_name = "Speaker";
};
```
Finally, here is an example of using the PulseAudio backend:
```
general = {
name = "Zoe's Computer";
output_backend = "pa";
};
```
Metadata
--------
Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
**Metadata broadcasting over UDP**
As an alternative to sending metadata to a pipe, the `socket_address` and `socket_port` tags may be set in the metadata group to cause Shairport Sync to broadcast UDP packets containing the track metadata.
The advantage of UDP is that packets can be sent to a single listener or, if a multicast address is used, to multiple listeners. It also allows metadata to be routed to a different host. However UDP has a maximum packet size of about 65000 bytes; while large enough for most data, Cover Art will often exceed this value. Any metadata exceeding this limit will not be sent over the socket interface. The maximum packet size may be set with the `socket_msglength` tag to any value between 500 and 65000 to control this - lower values may be used to ensure that each UDP packet is sent in a single network frame. The default is 500. Other than this restriction, metadata sent over the socket interface is identical to metadata sent over the pipe interface.
The UDP metadata format is very simple - the first four bytes are the metadata *type*, and the next four bytes are the metadata *code* (both are sent in network byte order - see https://github.com/mikebrady/shairport-sync-metadata-reader for a definition of those terms). The remaining bytes of the packet, if any, make up the raw value of the metadata.
Latency
-------
Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer.
Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them.
Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset_in_seconds`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -0.1.
Resynchronisation
-------------
Shairport Sync actively maintains synchronisation with the source.
If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above.
* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting.
Tolerance
---------
Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections.
* You can vary the tolerance with the `general` `drift_tolerance_in_seconds` setting.
Some Statistics
---------------
If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file:
```
sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy
```
This will be followed by the statistics themselves at regular intervals, for example:
```
-1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270
-2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267
-2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268
-2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267
-2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268
-2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267
-2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267
-2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266
```
"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made.
"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm.
"Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections.
For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+.
It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway...
"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up.
WiFi Issues
---------
If you are using WiFi, you should ensure that WiFi power management is off. See [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for more details.
Troubleshooting
---------------
Please refer to [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md) for a few hints, contributed by users.
**Warning**
This is the "unstable" branch. It is used for very preliminary unfinished or untested code and can change at any time and without warning.
+274
View File
@@ -0,0 +1,274 @@
/*
* 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_while_active) {
#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_while_active) {
#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) {
pthread_mutex_lock(&activity_monitor_mutex);
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 coldn'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_mode_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_mutex_unlock(&activity_monitor_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;
int rc;
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_mode_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_mode_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(1, "activity_monitor_stop start...");
pthread_cancel(activity_monitor_thread);
pthread_join(activity_monitor_thread, NULL);
debug(1, "activity_monitor_stop complete");
}
}
+8
View File
@@ -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
+22 -3
View File
@@ -106,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);
}
}
}
@@ -153,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)) {
+2 -1
View File
@@ -47,7 +47,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;
+640 -538
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -54,10 +54,9 @@ static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int s
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,
.start = &start,
+7 -4
View File
@@ -27,7 +27,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string.h>
#include <unistd.h>
#include <jack/jack.h>
@@ -301,6 +300,9 @@ void *open_client_if_necessary_thread_function(void *arg) {
int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
config.audio_backend_latency_offset = 0;
config.audio_backend_buffer_desired_length = 0.500;
config.audio_backend_buffer_interpolation_threshold_in_seconds =
0.25; // below this, soxr interpolation will not occur -- it'll be basic interpolation
// instead.
config.jack_auto_client_open_interval = 1; // check every second
// get settings from settings file first, allow them to be overridden by
@@ -332,8 +334,9 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
* we should try. */
if (config_lookup_int(config.cfg, "jack.auto_client_open_interval", &value)) {
if ((value < 0) || (value > 300))
debug(1, "Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, "
"default is %d.",
debug(1,
"Invalid jack auto_client_open_interval \"%sd\". It should be between 0 and 300, "
"default is %d.",
value, config.jack_auto_client_open_interval);
else
config.jack_auto_client_open_interval = value;
@@ -430,4 +433,4 @@ void jack_stop(void) {
// debug(1, "jack stop");
if (config.jack_auto_client_disconnect)
jack_close();
}
}
+5 -3
View File
@@ -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
@@ -296,10 +300,8 @@ 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,
.start = &start,
+1 -1
View File
@@ -127,7 +127,7 @@ 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,
+3
View File
@@ -99,6 +99,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
+3 -2
View File
@@ -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) {
+1 -3
View File
@@ -74,10 +74,8 @@ 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,
.start = &start,
+97 -59
View File
@@ -2,7 +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 (c) Mike Brady 2014 -- 2018
* Further changes and additions (c) Mike Brady 2014 -- 2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -75,10 +75,13 @@
#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 *);
@@ -114,7 +117,8 @@ 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;
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;
@@ -142,13 +146,24 @@ 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);
#ifdef CONFIG_LIBDAEMON
daemon_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);
daemon_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);
daemon_log(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s);
else
daemon_log(LOG_EMERG, "fatal error: %s", s);
daemon_log(LOG_ERR, "fatal error: %s", s);
#else
syslog(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s);
else if ((debuglev) && (config.debugger_show_relative_time))
syslog(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s);
else if ((debuglev) && (config.debugger_show_elapsed_time))
syslog(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s);
else
syslog(LOG_ERR, "fatal error: %s", s);
#endif
pthread_setcancelstate(oldState, NULL);
exit(1);
}
@@ -169,7 +184,7 @@ void warn(const char *format, ...) {
va_start(args, format);
vsnprintf(s, sizeof(s), format, args);
va_end(args);
#ifdef CONFIG_LIBDAEMON
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);
else if ((debuglev) && (config.debugger_show_relative_time))
@@ -178,6 +193,16 @@ void warn(const char *format, ...) {
daemon_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s);
else
daemon_log(LOG_WARNING, "%s", s);
#else
if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
syslog(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s);
else if ((debuglev) && (config.debugger_show_relative_time))
syslog(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s);
else if ((debuglev) && (config.debugger_show_elapsed_time))
syslog(LOG_WARNING, "% 20.9f|*warning: %s", tss, s);
else
syslog(LOG_WARNING, "%s", s);
#endif
pthread_setcancelstate(oldState, NULL);
}
@@ -199,6 +224,7 @@ void debug(int level, const char *format, ...) {
va_start(args, format);
vsnprintf(s, sizeof(s), format, args);
va_end(args);
#ifdef CONFIG_LIBDAEMON
if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
daemon_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
else if (config.debugger_show_relative_time)
@@ -207,6 +233,16 @@ void debug(int level, const char *format, ...) {
daemon_log(LOG_DEBUG, "% 20.9f|%s", tss, s);
else
daemon_log(LOG_DEBUG, "%s", s);
#else
if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
syslog(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
else if (config.debugger_show_relative_time)
syslog(LOG_DEBUG, "% 20.9f|%s", tsl, s);
else if (config.debugger_show_elapsed_time)
syslog(LOG_DEBUG, "% 20.9f|%s", tss, s);
else
syslog(LOG_DEBUG, "%s", s);
#endif
pthread_setcancelstate(oldState, NULL);
}
@@ -226,7 +262,7 @@ void inform(const char *format, ...) {
va_start(args, format);
vsnprintf(s, sizeof(s), format, args);
va_end(args);
#ifdef CONFIG_LIBDAEMON
if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
daemon_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
else if ((debuglev) && (config.debugger_show_relative_time))
@@ -235,6 +271,17 @@ void inform(const char *format, ...) {
daemon_log(LOG_INFO, "% 20.9f|%s", tss, s);
else
daemon_log(LOG_INFO, "%s", s);
#else
if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
syslog(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
else if ((debuglev) && (config.debugger_show_relative_time))
syslog(LOG_INFO, "% 20.9f|%s", tsl, s);
else if ((debuglev) && (config.debugger_show_elapsed_time))
syslog(LOG_INFO, "% 20.9f|%s", tss, s);
else
syslog(LOG_INFO, "%s", s);
#endif
pthread_setcancelstate(oldState, NULL);
}
@@ -394,6 +441,8 @@ uint8_t *base64_dec(char *input, int *outlen) {
#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());
@@ -412,12 +461,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);
@@ -438,10 +490,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
@@ -473,6 +525,8 @@ static char super_secret_key[] =
#ifdef CONFIG_OPENSSL
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
RSA *rsa = NULL;
if (!rsa) {
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
@@ -491,6 +545,8 @@ 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
@@ -716,7 +772,8 @@ void command_start(void) {
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);
}
@@ -739,7 +796,7 @@ void command_start(void) {
}
}
}
void command_execute(const char *command, const char *extra_argument) {
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];
@@ -767,10 +824,11 @@ void command_execute(const char *command, const char *extra_argument) {
exit(127); /* 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) {
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);
}
@@ -782,36 +840,8 @@ void command_execute(const char *command, const char *extra_argument) {
void command_stop(void) {
// this has a cancellation point if waiting is enabled
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) !=
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");
else {
// debug(1,"Executing on-stop command %s",config.cmd_stop);
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 */
}
} else {
if (config.cmd_blocking) { /* 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);
}
}
// debug(1,"Continue after on-stop command");
}
}
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
@@ -846,17 +876,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
@@ -1160,15 +1190,18 @@ 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 ((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);
@@ -1256,6 +1289,11 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha
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() {
+28 -6
View File
@@ -30,7 +30,8 @@ enum dbus_session_type {
} dbt_type;
#endif
#define sps_extra_errno_output_stalled 32768
#define sps_extra_code_output_stalled 32768
#define sps_extra_code_output_state_cannot_make_ready 32769
enum endian_type {
SS_LITTLE_ENDIAN = 0,
@@ -61,6 +62,12 @@ enum decoders_supported_type {
decoder_apple_alac,
} decoders_supported_type;
enum disable_standby_mode_type {
disable_standby_off = 0,
disable_standby_while_active,
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
@@ -140,10 +147,13 @@ typedef struct {
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
@@ -151,13 +161,13 @@ typedef struct {
int statistics_requested, use_negotiated_latencies;
enum playback_mode_type playback_mode;
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 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;
@@ -167,11 +177,21 @@ 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.
double active_mode_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 sps_format_t output_format;
enum volume_control_profile_type volume_control_profile;
int output_rate;
@@ -187,6 +207,7 @@ typedef struct {
float loudness_reference_volume_db;
int alsa_use_hardware_mute;
double alsa_maximum_stall_time;
enum disable_standby_mode_type disable_standby_mode;
volatile int keep_dac_busy;
#if defined(CONFIG_DBUS_INTERFACE)
@@ -302,13 +323,12 @@ 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);
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;
@@ -341,4 +361,6 @@ void sps_nanosleep(const time_t sec,
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
+37 -13
View File
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
AC_INIT([shairport-sync], [3.3d41], [mikebrady@eircom.net])
AC_INIT([shairport-sync], [3.3d52], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
@@ -44,14 +44,35 @@ AM_CONDITIONAL([BUILD_FOR_OPENBSD], [test "x${with_os}" = xopenbsd ])
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
# Look for libdaemon
AC_ARG_WITH(libdaemon, [ --with-libdaemon = include support for daemonising in older non-systemd systems],
if test "x${with_libdaemon}" = xyes ; then
[AC_MSG_RESULT(>>Including libdaemon support for older systems)
AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon])
REQUESTED_LIBDAEMON=1
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,
#default is to include libdaemon
[AC_MSG_RESULT(>>Including libdaemon support for older systems)
AC_DEFINE([CONFIG_LIBDAEMON], 1, [Include libdaemon])
REQUESTED_LIBDAEMON=1
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
]
)
AM_CONDITIONAL([USE_LIBDAEMON], [test "x$REQUESTED_LIBDAEMON" = "x1"])
##### 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.
@@ -73,7 +94,6 @@ AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG
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 +115,9 @@ 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([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(
@@ -175,14 +198,15 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation
AC_DEFINE([CONFIG_SOXR], 1, [Include support for using the SoX Resampler library for interpolation])
PKG_CHECK_MODULES(
[SOXR], [soxr],
[LIBS="${SOXR_LIBS} ${LIBS}"])
[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 requested but libsoxr not found!), [-lavutil])
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 requested but libsoxr not found!))
AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requires the libsoxr library -- libsoxr-dev suggested!))
fi
fi
], )
+66 -30
View File
@@ -58,6 +58,7 @@ 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;
@@ -111,7 +112,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;
@@ -127,9 +131,9 @@ void addrinfo_cleanup(void *arg) {
}
void mutex_lock_cleanup(void *arg) {
// debug(1, "mutex lock cleanup called.");
pthread_mutex_t *m = (pthread_mutex_t *)arg;
pthread_mutex_unlock(m);
if (pthread_mutex_unlock(m))
debug(1, "Error releasing mutex.");
}
void connect_cleanup(void *arg) {
@@ -144,11 +148,6 @@ void http_cleanup(void *arg) {
http_free(rt);
}
void malloc_cleanup(void *arg) {
// debug(1, "malloc cleanup called.");
free(arg);
}
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.
@@ -193,6 +192,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
response.code = 498; // Bad Address information for the DACP server
} else {
uint64_t start_time = get_absolute_time_in_fp();
pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
// only do this one at a time -- not sure it is necessary, but better safe than sorry
@@ -200,7 +200,6 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
if (mutex_reply == 0) {
pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock);
// debug(1,"dacp_conversation_lock acquired for command \"%s\".",command);
// make a socket:
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
@@ -213,8 +212,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// 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, "dacp_send_command: error %d setting receive timeout.", errno);
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
@@ -223,7 +222,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
// debug(2, "dacp_send_command: 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.");
@@ -233,9 +232,21 @@ 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(3,"dacp_send_command: connect message: \"%s\".",message);
if (send(sockfd, message, strlen(message), 0) != (ssize_t)strlen(message)) {
debug(1, "dacp_send_command: 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 {
@@ -256,11 +267,23 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
const char *data = buffer;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
int ndata = recv(sockfd, buffer, sizeof(buffer), 0);
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;
@@ -300,14 +323,19 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size);
// debug(1,"dacp_conversation_lock released.");
} else {
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?",
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;
@@ -395,8 +423,9 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
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.",
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;
@@ -444,8 +473,9 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
(metadata_store.advanced_dacp_server_active != 0);
metadata_store.dacp_server_active = 0;
metadata_store.advanced_dacp_server_active = 0;
debug(2, "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) {
@@ -829,6 +859,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)
@@ -857,14 +889,18 @@ void dacp_monitor_start() {
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() {
debug(1, "dacp_monitor_stop");
pthread_cancel(dacp_monitor_thread);
pthread_join(dacp_monitor_thread, NULL);
pthread_mutex_destroy(&dacp_server_information_lock);
pthread_mutex_destroy(&dacp_conversation_lock);
if (dacp_monitor_initialised) { // only if it's been started and initialised
debug(1, "dacp_monitor_stop");
pthread_cancel(dacp_monitor_thread);
pthread_join(dacp_monitor_thread, NULL);
pthread_mutex_destroy(&dacp_server_information_lock);
debug(1, "DACP Conversation Lock Mutex Destroyed");
pthread_mutex_destroy(&dacp_conversation_lock);
}
}
uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
+90 -8
View File
@@ -15,6 +15,8 @@
#include "dbus-service.h"
int service_is_running = 0;
ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL;
ShairportSyncRemoteControl *shairportSyncRemoteControlSkeleton = NULL;
ShairportSyncAdvancedRemoteControl *shairportSyncAdvancedRemoteControlSkeleton = NULL;
@@ -378,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;
@@ -395,10 +410,10 @@ 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;
@@ -408,15 +423,45 @@ 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.", dt);
debug(1, ">> setting drift tolerance to %f seconds", dt);
config.tolerance = dt;
} else {
debug(1, "Invalid drift tolerance: %f. Ignored.", dt);
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, "while active") == 0)
config.disable_standby_mode = disable_standby_while_active;
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_while_active:
shairport_sync_set_disable_standby_mode(skeleton, "while active");
break;
default:
break;
}
}
return TRUE;
}
gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
@@ -609,8 +654,12 @@ 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",
@@ -691,6 +740,27 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
#endif
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_while_active:
shairport_sync_set_disable_standby_mode(SHAIRPORT_SYNC(shairportSyncSkeleton), "while active");
debug(1, ">> disable standby mode set to \"while active\"");
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");
@@ -710,6 +780,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 {
@@ -767,6 +843,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,
@@ -813,4 +890,9 @@ void stop_dbus_service() {
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;
}
+1
View File
@@ -7,5 +7,6 @@ ShairportSync *shairportSyncSkeleton;
int start_dbus_service();
void stop_dbus_service();
int dbus_service_is_running();
#endif /* #ifndef DBUS_SERVICE_H */
+7 -2
View File
@@ -34,7 +34,6 @@
#ifdef CONFIG_AVAHI
extern mdns_backend mdns_avahi;
extern mdns_backend mdns_external_avahi;
#endif
#ifdef CONFIG_DNS_SD
extern mdns_backend mdns_dns_sd;
@@ -44,10 +43,13 @@ extern mdns_backend mdns_external_dns_sd;
extern mdns_backend mdns_tinysvcmdns;
#endif
#ifdef CONFIG_EXTERNAL_MDNS
extern mdns_backend mdns_external_avahi;
#endif
static mdns_backend *mdns_backends[] = {
#ifdef CONFIG_AVAHI
&mdns_avahi,
&mdns_external_avahi,
#endif
#ifdef CONFIG_DNS_SD
&mdns_dns_sd,
@@ -55,6 +57,9 @@ static mdns_backend *mdns_backends[] = {
#endif
#ifdef CONFIG_TINYSVCMDNS
&mdns_tinysvcmdns,
#endif
#ifdef CONFIG_EXTERNAL_MDNS
&mdns_external_avahi,
#endif
NULL};
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -24,8 +24,8 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "mdns.h"
#include "common.h"
#include "mdns.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+46 -33
View File
@@ -46,6 +46,7 @@
#include "metadata_hub.h"
#ifdef CONFIG_MBEDTLS
#include <mbedtls/version.h>
#include <mbedtls/md5.h>
#endif
@@ -57,6 +58,8 @@
#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
@@ -99,18 +102,21 @@ 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) {
debug(1, "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;
if (metadata_hub_initialised) {
debug(1, "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;
}
}
}
@@ -217,9 +223,9 @@ 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 CONFIG_SSL
#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, buf, len);
@@ -227,10 +233,17 @@ char *metadata_write_image_file(const char *buf, int len) {
#endif
#ifdef CONFIG_MBEDTLS
mbedtls_md5_context tctx;
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
mbedtls_md5_finish(&tctx, img_md5);
#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 CONFIG_POLARSSL
@@ -489,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;
@@ -559,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);
+6
View File
@@ -12,6 +12,11 @@ enum play_status_type {
PS_PLAYING,
} play_status_type;
enum active_mode_type {
AM_INACTIVE = 0,
AM_ACTIVE,
} active_mode_type;
enum shuffle_status_type {
SS_NOT_AVAILABLE = 0,
SS_OFF,
@@ -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_mode_type active_state;
int speaker_volume; // this is the actual speaker volume, allowing for the main volume and the
// speaker volume control
+3
View File
@@ -2,6 +2,9 @@
<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" />
+253 -233
View File
@@ -4,7 +4,7 @@
* All rights reserved.
*
* Modifications for audio synchronisation
* and related work, copyright (c) Mike Brady 2014 -- 2018
* and related work, copyright (c) Mike Brady 2014 -- 2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -89,13 +89,15 @@
#include "loudness.h"
#include "activity_monitor.h"
// default buffer size
// needs to be a power of 2 because of the way BUFIDX(seqno) works
//#define BUFFER_FRAMES 512
#define MAX_PACKET 2048
// DAC buffer occupancy stuff
#define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 600
#define DAC_BUFFER_QUEUE_MINIMUM_LENGTH 2500
// static abuf_t audio_buffer[BUFFER_FRAMES];
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
@@ -473,7 +475,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
abuf_t *abuf = 0;
if (!conn->ab_synced) {
// if this is the first packetÉ
// if this is the first packet...
debug(3, "syncing to seqno %u.", seqno);
conn->ab_write = seqno;
conn->ab_read = seqno;
@@ -495,7 +497,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
}
if (conn->ab_write ==
seqno) { // if this is the expected packet (which could be the first packetÉ)
seqno) { // if this is the expected packet (which could be the first packet...)
uint64_t reception_time = get_absolute_time_in_fp();
if (conn->input_frame_rate_starting_point_is_valid == 0) {
if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) {
@@ -624,6 +626,16 @@ int32_t rand_in_range(int32_t exclusive_range_limit) {
static inline void process_sample(int32_t sample, char **outp, enum sps_format_t format, int volume,
int dither, rtsp_conn_info *conn) {
/*
{
static int old_volume = 0;
if (volume != old_volume) {
debug(1,"Volume is now %d.",volume);
old_volume = volume;
}
}
*/
int64_t hyper_sample = sample;
int result = 0;
@@ -846,12 +858,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
curframe = NULL; // this will be returned and will cause the loop to go around again
conn->initial_reference_time = 0;
conn->initial_reference_timestamp = 0;
}
if ((conn->flush_rtp_timestamp != 0) &&
(modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) >
conn->input_rate / 5) &&
(modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) <
conn->input_rate * 10)) {
} else if ((conn->flush_rtp_timestamp != 0) &&
(modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) >
conn->input_rate / 5) &&
(modulo_32_offset(conn->flush_rtp_timestamp, curframe->given_timestamp) <
conn->input_rate * 10)) {
debug(3, "Dropping flush request in buffer_get_frame");
conn->flush_rtp_timestamp = 0;
}
@@ -1017,7 +1028,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
// this might happen if a big clock adjustment was made at just the wrong
// time.
debug(1, "Run a bit past the exact start time by %" PRId64 " frames with a DAC delay of %ld frames.",
debug(1, "Run a bit past the exact start time by %" PRId64
" frames with a DAC delay of %ld frames.",
-exact_frame_gap, dac_delay);
if (config.output->flush)
config.output->flush();
@@ -1084,11 +1096,11 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
1; // even if we haven't sent silence because it's zero frames long...
}
} else {
if ((resp == sps_extra_errno_output_stalled) &&
if ((resp == sps_extra_code_output_stalled) &&
(conn->unfixable_error_reported == 0)) {
conn->unfixable_error_reported = 1;
if (config.cmd_unfixable) {
command_execute(config.cmd_unfixable, "output_device_stalled");
command_execute(config.cmd_unfixable, "output_device_stalled", 1);
} else {
warn(
"an unrecoverable error, \"output_device_stalled\", has been detected.",
@@ -1197,8 +1209,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
if (wait) {
uint64_t time_to_wait_for_wakeup_fp =
((uint64_t)1 << 32) / conn->input_rate; // this is time period of one frame
time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets
time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time
time_to_wait_for_wakeup_fp *= 2 * 352; // two full 352-frame packets
time_to_wait_for_wakeup_fp /= 3; // two thirds of a packet time
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp;
@@ -1417,6 +1429,9 @@ void player_thread_cleanup_handler(void *arg) {
debug(3, "Connection %d: player thread main loop exit via player_thread_cleanup_handler.",
conn->connection_number);
if (config.output->stop)
config.output->stop();
if (config.statistics_requested) {
int rawSeconds = (int)difftime(time(NULL), conn->playstart);
int elapsedHours = rawSeconds / 3600;
@@ -1469,8 +1484,6 @@ void player_thread_cleanup_handler(void *arg) {
}
free_audio_buffers(conn);
terminate_decoders(conn);
if (config.output->stop)
config.output->stop();
clear_reference_timestamp(conn);
conn->rtp_running = 0;
@@ -1550,6 +1563,11 @@ void *player_thread_func(void *arg) {
debug(3, "Output frame bytes is %d.", conn->output_bytes_per_frame);
conn->dac_buffer_queue_minimum_length = (int64_t)(
config.audio_backend_buffer_interpolation_threshold_in_seconds * config.output_rate);
debug(3, "dac_buffer_queue_minimum_length is %" PRId64 " frames.",
conn->dac_buffer_queue_minimum_length);
conn->session_corrections = 0;
// conn->play_segment_reference_frame = 0; // zero signals that we are not in a play segment
@@ -1642,6 +1660,7 @@ void *player_thread_func(void *arg) {
(config.playback_mode == ST_mono))
conn->enable_dither = 1;
// remember, the output device may never have been initialised prior to this call
config.output->start(config.output_rate, config.output_format); // will need a corresponding stop
// we need an intermediate "transition" buffer
@@ -1811,8 +1830,9 @@ void *player_thread_func(void *arg) {
}
} else {
if ((config.output->parameters == NULL) || (conn->input_bit_depth > output_bit_depth) ||
(config.playback_mode == ST_mono))
if (((config.output->parameters == NULL) && (config.ignore_volume_control == 0) &&
(config.airplay_volume != 0.0)) ||
(conn->input_bit_depth > output_bit_depth) || (config.playback_mode == ST_mono))
conn->enable_dither = 1;
else
conn->enable_dither = 0;
@@ -1973,8 +1993,8 @@ void *player_thread_func(void *arg) {
if (config.output->delay) {
long l_delay;
resp = config.output->delay(&l_delay);
current_delay = l_delay;
if (resp == 0) { // no error
current_delay = l_delay;
if (current_delay < 0) {
debug(2, "Underrun of %lld frames reported, but ignored.", current_delay);
current_delay =
@@ -1984,7 +2004,8 @@ void *player_thread_func(void *arg) {
minimum_dac_queue_size = current_delay; // update for display later
}
} else {
if ((resp == sps_extra_errno_output_stalled) &&
current_delay = 0;
if ((resp == sps_extra_code_output_stalled) &&
(conn->unfixable_error_reported == 0)) {
conn->unfixable_error_reported = 1;
if (config.cmd_unfixable) {
@@ -1992,7 +2013,7 @@ void *player_thread_func(void *arg) {
"stalled. Executing the "
"\"run_this_if_an_unfixable_error_is_detected\" command.",
conn->connection_number);
command_execute(config.cmd_unfixable, "output_device_stalled");
command_execute(config.cmd_unfixable, "output_device_stalled", 1);
} else {
warn("Connection %d: An unfixable error has been detected -- output device is "
"stalled. \"No "
@@ -2001,11 +2022,11 @@ void *player_thread_func(void *arg) {
conn->connection_number);
}
} else
debug(2, "Delay error %d when checking running latency.", resp);
debug(3, "Delay error %d when checking running latency.", resp);
}
}
if (resp >= 0) {
if (resp == 0) {
uint32_t should_be_frame_32;
local_time_to_frame(local_time_now, &should_be_frame_32, conn);
@@ -2075,8 +2096,14 @@ void *player_thread_func(void *arg) {
debug(2, "Large positive sync error: %" PRId64 ".", sync_error);
int64_t local_frames_to_drop = sync_error / conn->output_sample_ratio;
uint32_t frames_to_drop_sized = local_frames_to_drop;
do_flush(inframe->given_timestamp + frames_to_drop_sized,
conn); // this will reset_input_flow_metrics anyway
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
conn->flush_rtp_timestamp =
inframe->given_timestamp +
frames_to_drop_sized; // flush all packets up to (and including?) this
reset_input_flow_metrics(conn);
debug_mutex_unlock(&conn->flush_mutex, 3);
} else if ((sync_error < 0) && ((-sync_error) > filler_length)) {
debug(2,
"Large negative sync error: %" PRId64 " with should_be_frame_32 of %" PRIu32
@@ -2212,7 +2239,7 @@ void *player_thread_func(void *arg) {
}
}
if ((current_delay < DAC_BUFFER_QUEUE_MINIMUM_LENGTH) ||
if ((current_delay < conn->dac_buffer_queue_minimum_length) ||
(config.packet_stuffing == ST_basic)) {
play_samples =
stuff_buffer_basic_32((int32_t *)conn->tbuf, inbuflength, config.output_format,
@@ -2247,8 +2274,13 @@ void *player_thread_func(void *arg) {
else {
if (play_samples == 0)
debug(1, "play_samples==0 skipping it (1).");
else
else {
if (conn->software_mute_enabled) {
generate_zero_frames(conn->outbuf, play_samples, config.output_format,
conn->enable_dither, conn->previous_random_number);
}
config.output->play(conn->outbuf, play_samples);
}
}
// check for loss of sync
@@ -2282,8 +2314,13 @@ void *player_thread_func(void *arg) {
conn->outbuf, 0, conn->enable_dither, conn);
if (conn->outbuf == NULL)
debug(1, "NULL outbuf to play -- skipping it.");
else
else {
if (conn->software_mute_enabled) {
generate_zero_frames(conn->outbuf, play_samples, config.output_format,
conn->enable_dither, conn->previous_random_number);
}
config.output->play(conn->outbuf, play_samples); // remove the (short*)!
}
}
// mark the frame as finished
@@ -2504,236 +2541,216 @@ void *player_thread_func(void *arg) {
pthread_exit(NULL);
}
// takes the volume as specified by the airplay protocol
void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) {
debug_mutex_lock(&conn->volume_control_mutex, 5000, 1);
debug(2, "player_volume_without_notification %f", airplay_volume);
// first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw
// attenuation on top
// no cancellation points here if we assume that the mute call to the back end has no cancellation
// points
enum volume_mode_type { vol_sw_only, vol_hw_only, vol_both } volume_mode;
// The volume ranges -144.0 (mute) or -30 -- 0. See
// http://git.zx2c4.com/Airtunes2/about/#setting-volume
// By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30
// equal increments. Since the human ear's response is roughly logarithmic, we imagine these to
// be power dB, i.e. from -30dB to 0dB.
// take account of whether there is a hardware mixer, if a max volume has been specified and if a
// range has been specified
// the range might imply that both hw and software mixers are needed, so calculate this
// We may have a hardware mixer, and if so, we will give it priority.
// If a desired volume range is given, then we will try to accommodate it from
// the top of the hardware mixer's range downwards.
// If no desired volume range is given, we will use the native resolution of the hardware mixer,
// if any,
// or failing that, the software mixer. The software mixer has a range of from -96.3 dB up to 0
// dB,
// corresponding to a multiplier of 1 to 65535.
// Otherwise, we will accommodate the desired volume range in the combination of the software and
// hardware mixer
// Intuitively (!), it seems best to give the hardware mixer as big a role as possible, so
// we will use its full range and then accommodate the rest of the attenuation in software.
// A problem is that we don't know whether the lowest hardware volume actually mutes the output
// so we must assume that it does, and for this reason, the volume control goes at the "bottom" of
// the adjustment range
// The dB range of a value from 1 to 65536 is about 96.3 dB (log10 of 65536 is 4.8164).
// Since the levels correspond with amplitude, they correspond to voltage, hence voltage dB,
// or 20 times the log of the ratio. Then multiplied by 100 for convenience.
// Thus, we ask our vol2attn function for an appropriate dB between -96.3 and 0 dB and translate
// it back to a number.
int32_t hw_min_db, hw_max_db, hw_range_db, min_db,
max_db; // hw_range_db is a flag; if 0 means no mixer
if (config.output->parameters) { // no cancellation points in here
int32_t hw_max_db = 0, hw_min_db = 0; // zeroed to quieten an incorrect uninitialised warning
int32_t sw_max_db = 0, sw_min_db = -9630;
if (config.output->parameters) {
volume_mode = vol_hw_only;
audio_parameters audio_information;
// have a hardware mixer
config.output->parameters(&audio_information);
hw_max_db = audio_information.maximum_volume_dB;
hw_min_db = audio_information.minimum_volume_dB;
hw_range_db = hw_max_db - hw_min_db;
} else {
// don't have a hardware mixer
hw_max_db = hw_min_db = hw_range_db = 0;
}
int32_t sw_min_db = -9630;
int32_t sw_max_db = 0;
int32_t sw_range_db = sw_max_db - sw_min_db;
int32_t desired_range_db = 0; // this is also used as a flag; if 0 means no desired range
if (config.volume_range_db)
desired_range_db = (int32_t)trunc(config.volume_range_db * 100);
// This is wrong, I think -- it doesn't work properly if the volume range is composite and you
// want to set a maximum value which should affect the hardware mixer.
if (config.volume_max_db_set) {
if (hw_range_db) {
if (((config.volume_max_db * 100) < hw_max_db) &&
((config.volume_max_db * 100) > hw_min_db)) {
hw_max_db = (int)config.volume_max_db * 100;
hw_range_db = hw_max_db - hw_min_db;
} else {
inform("The volume_max_db setting is out of range of the hardware mixers's limits of %d dB "
"to %d dB. It will be ignored.",
(int)(hw_max_db / 100), (int)(hw_min_db / 100));
}
} else {
if (((config.volume_max_db * 100) < sw_max_db) &&
((config.volume_max_db * 100) > sw_min_db)) {
sw_max_db = (int)config.volume_max_db * 100;
sw_range_db = sw_max_db - sw_min_db;
} else {
inform("The volume_max_db setting is out of range of the software attenuation's limits of "
"0 dB to -96.3 dB. It will be ignored.");
}
}
}
if (desired_range_db) {
// debug(1,"An attenuation range of %d is requested.",desired_range_db);
// we have a desired volume range.
if (hw_range_db) {
// we have a hardware mixer
if (hw_range_db >= desired_range_db) {
// the hardware mixer can accommodate the desired range
max_db = hw_max_db;
min_db = max_db - desired_range_db;
} else {
// we have a hardware mixer and a desired range greater than the mixer's range.
if ((hw_range_db + sw_range_db) < desired_range_db) {
inform("The volume attenuation range %f is greater than can be accommodated by the "
"hardware and software -- set to %f.",
config.volume_range_db, hw_range_db + sw_range_db);
desired_range_db = hw_range_db + sw_range_db;
}
min_db = hw_min_db;
max_db = min_db + desired_range_db;
}
} else {
// we have a desired volume range and no hardware mixer
if (sw_range_db < desired_range_db) {
inform("The volume attenuation range %f is greater than can be accommodated by the "
"software -- set to %f.",
config.volume_range_db, sw_range_db);
desired_range_db = sw_range_db;
}
max_db = sw_max_db;
min_db = max_db - desired_range_db;
}
} else {
// we do not have a desired volume range, so use the mixer's volume range, if there is one.
// debug(1,"No attenuation range requested.");
if (hw_range_db) {
min_db = hw_min_db;
max_db = hw_max_db;
} else {
min_db = sw_min_db;
max_db = sw_max_db;
}
}
/*
if (config.volume_max_db_set) {
if ((config.volume_max_db*100<=max_db) && (config.volume_max_db*100>=min_db)) {
debug(1,"Reducing the maximum volume from %d to %d.",max_db/100,config.volume_max_db);
max_db = (int)(config.volume_max_db*100);
if (((config.volume_max_db * 100) <= hw_max_db) &&
((config.volume_max_db * 100) >= hw_min_db))
hw_max_db = (int32_t)config.volume_max_db * 100;
else if (config.volume_range_db) {
hw_max_db = hw_min_db;
sw_max_db = (config.volume_max_db * 100) - hw_min_db;
} else {
inform("The value of volume_max_db is invalid. It must be in the range %d to
%d.",max_db,min_db);
warn("The maximum output level is outside the range of the hardware mixer -- ignored");
}
}
*/
double hardware_attenuation = 0.0, software_attenuation = 0.0;
double scaled_attenuation = hw_min_db + sw_min_db;
// now, we can map the input to the desired output volume
if ((airplay_volume == -144.0) && (config.ignore_volume_control == 0)) {
// do a mute
// needed even with hardware mute, as when sound is unmuted it might otherwise be very loud.
hardware_attenuation = hw_min_db;
if (config.output->mute) {
// allow the audio material to reach the mixer, but mute the mixer
// it the mute is removed externally, the material with be there
software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and
// the max is +40, this will be -36
// (all by 100, of course)
// debug(1,"Mute, with hardware mute and software_attenuation set to
// %d.",software_attenuation);
config.output->mute(1); // use real mute if it's there
} else {
if (config.output->volume == NULL) { // if there is also no hardware volume control
software_attenuation = sw_min_db; // set any software output to zero too
// debug(1,"Mute, with no hardware mute and software_attenuation set to
// %d.",software_attenuation);
// here, we have set limits on the hw_max_db and the sw_max_db
// but we haven't actually decided whether we need both hw and software attenuation
// only if a range is specified could we need both
if (config.volume_range_db) {
// see if the range requested exceeds the hardware range available
int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100);
if ((desired_range_db) > (hw_max_db - hw_min_db)) {
volume_mode = vol_both;
int32_t desired_sw_range = desired_range_db - (hw_max_db - hw_min_db);
if ((sw_max_db - desired_sw_range) < sw_min_db)
warn("The range requested is too large to accommodate -- ignored.");
else
sw_min_db = (sw_max_db - desired_sw_range);
}
}
} else {
if (config.output->mute)
config.output->mute(0); // unmute mute if it's there
if (config.ignore_volume_control == 1)
scaled_attenuation = max_db;
else if (config.volume_control_profile == VCP_standard)
scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points
else if (config.volume_control_profile == VCP_flat)
scaled_attenuation = flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points
else
debug(1, "Unrecognised volume control profile");
if (hw_range_db) {
// if there is a hardware mixer
if (scaled_attenuation <= hw_max_db) {
// the attenuation is so low that's it's in the hardware mixer's range
// debug(1,"Attenuation all taken care of by the hardware mixer.");
hardware_attenuation = scaled_attenuation;
software_attenuation = sw_max_db - (max_db - hw_max_db); // e.g. if the hw_max_db is +4 and
// the max is +40, this will be -36
// (all by 100, of course)
} else {
// debug(1,"Attenuation taken care of by hardware and software mixer.");
hardware_attenuation = hw_max_db; // the hardware mixer is turned up full
software_attenuation = sw_max_db - (max_db - scaled_attenuation);
}
} else {
// if there is no hardware mixer, the scaled_volume is the software volume
// debug(1,"Attenuation all taken care of by the software mixer.");
software_attenuation = scaled_attenuation;
// debug(1,"has no hardware mixer");
volume_mode = vol_sw_only;
if (config.volume_max_db_set) {
if (((config.volume_max_db * 100) <= sw_max_db) &&
((config.volume_max_db * 100) >= sw_min_db))
sw_max_db = (int32_t)config.volume_max_db * 100;
}
if (config.volume_range_db) {
// see if the range requested exceeds the software range available
int32_t desired_range_db = (int32_t)trunc(config.volume_range_db * 100);
if ((desired_range_db) > (sw_max_db - sw_min_db))
warn("The range requested is too large to accommodate -- ignored.");
else
sw_min_db = (sw_max_db - desired_range_db);
}
}
if ((config.output->volume) && (hw_range_db)) {
config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value
// debug(1,"Hardware attenuation set to %f for airplay volume of
// %f.",hardware_attenuation,airplay_volume);
}
double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000);
// debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of
// %f",software_attenuation,temp_fix_volume,airplay_volume);
// here, we know whether it's hw volume control only, sw only or both, and we have the hw and sw
// limits.
// if it's both, we haven't decided whether hw or sw should be on top
// we have to consider the settings ignore_volume_control and mute.
conn->fix_volume = temp_fix_volume;
memory_barrier(); // no cancellation points
if (config.ignore_volume_control == 0) {
if (airplay_volume == -144.0) {
if (config.loudness)
loudness_set_volume(software_attenuation / 100);
if ((config.output->mute) && (config.output->mute(1) == 0))
debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
"hardware mute is enabled.",
volume_mode, airplay_volume);
else {
conn->software_mute_enabled = 1;
debug(2, "player_volume_without_notification: volume mode is %d, airplay_volume is %f, "
"software mute is enabled.",
volume_mode, airplay_volume);
}
if (config.logOutputLevel) {
inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
}
} else {
int32_t max_db = 0, min_db = 0;
switch (volume_mode) {
case vol_hw_only:
max_db = hw_max_db;
min_db = hw_min_db;
break;
case vol_sw_only:
max_db = sw_max_db;
min_db = sw_min_db;
break;
case vol_both:
// debug(1, "dB range passed is hw: %d, sw: %d, total: %d", hw_max_db - hw_min_db,
// sw_max_db - sw_min_db, (hw_max_db - hw_min_db) + (sw_max_db - sw_min_db));
max_db =
(hw_max_db - hw_min_db) + (sw_max_db - sw_min_db); // this should be the range requested
min_db = 0;
break;
default:
debug(1, "player_volume_without_notification: error: not in a volume mode");
break;
}
double scaled_attenuation = 0.0;
if (config.volume_control_profile == VCP_standard)
scaled_attenuation = vol2attn(airplay_volume, max_db, min_db); // no cancellation points
else if (config.volume_control_profile == VCP_flat)
scaled_attenuation =
flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points
else
debug(1, "player_volume_without_notification: unrecognised volume control profile");
// so here we have the scaled attenuation. If it's for hw or sw only, it's straightforward.
double hardware_attenuation = 0.0;
double software_attenuation = 0.0;
switch (volume_mode) {
case vol_hw_only:
hardware_attenuation = scaled_attenuation;
break;
case vol_sw_only:
software_attenuation = scaled_attenuation;
break;
case vol_both:
// here, we now the attenuation required, so we have to apportion it to the sw and hw mixers
// if we give the hw priority, that means when lowering the volume, set the hw volume to its
// lowest
// before using the sw attenuation.
// similarly, if we give the sw priority, that means when lowering the volume, set the sw
// volume to its lowest
// before using the hw attenuation.
// one imagines that hw priority is likely to be much better
// if (config.volume_range_hw_priority) {
if (config.volume_range_hw_priority != 0) {
// hw priority
if ((sw_max_db - sw_min_db) > scaled_attenuation) {
software_attenuation = sw_min_db + scaled_attenuation;
hardware_attenuation = hw_min_db;
} else {
software_attenuation = sw_max_db;
hardware_attenuation = hw_min_db + scaled_attenuation - (sw_max_db - sw_min_db);
}
} else {
// sw priority
if ((hw_max_db - hw_min_db) > scaled_attenuation) {
hardware_attenuation = hw_min_db + scaled_attenuation;
software_attenuation = sw_min_db;
} else {
hardware_attenuation = hw_max_db;
software_attenuation = sw_min_db + scaled_attenuation - (hw_max_db - hw_min_db);
}
}
break;
default:
debug(1, "player_volume_without_notification: error: not in a volume mode");
break;
}
if (((volume_mode == vol_hw_only) || (volume_mode == vol_both)) && (config.output->volume)) {
config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value
// debug(1,"Hardware attenuation set to %f for airplay volume of
// %f.",hardware_attenuation,airplay_volume);
if (volume_mode == vol_hw_only)
conn->fix_volume = 0x10000;
}
if ((volume_mode == vol_sw_only) || (volume_mode == vol_both)) {
double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000);
// debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of
// %f",software_attenuation,temp_fix_volume,airplay_volume);
conn->fix_volume = temp_fix_volume;
if (config.loudness)
loudness_set_volume(software_attenuation / 100);
}
if (config.logOutputLevel) {
inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
}
#ifdef CONFIG_METADATA
char *dv = malloc(128); // will be freed in the metadata thread
if (dv) {
memset(dv, 0, 128);
if (config.ignore_volume_control == 1)
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
else
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
min_db / 100.0, max_db / 100.0);
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
}
char *dv = malloc(128); // will be freed in the metadata thread
if (dv) {
memset(dv, 0, 128);
if (config.ignore_volume_control == 1)
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
else
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
min_db / 100.0, max_db / 100.0);
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
}
#endif
// here, store the volume for possible use in the future
// here, store the volume for possible use in the future
if (config.output->mute)
config.output->mute(0);
conn->software_mute_enabled = 0;
debug(2, "player_volume_without_notification: volume mode is %d, airplay volume is %f, "
"software_attenuation: %f, hardware_attenuation: %f, muting "
"is disabled.",
volume_mode, airplay_volume, software_attenuation, hardware_attenuation);
}
}
config.airplay_volume = airplay_volume;
debug_mutex_unlock(&conn->volume_control_mutex, 3);
}
void player_volume(double airplay_volume, rtsp_conn_info *conn) {
@@ -2774,6 +2791,8 @@ int player_play(rtsp_conn_info *conn) {
if (config.buffer_start_fill > BUFFER_FRAMES)
die("specified buffer starting fill %d > buffer size %d", config.buffer_start_fill,
BUFFER_FRAMES);
activity_monitor_signify_activity(
1); // active, and should be before play's command hook, command_start()
command_start();
pthread_t *pt = malloc(sizeof(pthread_t));
if (pt == NULL)
@@ -2822,10 +2841,11 @@ int player_stop(rtsp_conn_info *conn) {
#endif
// debuglev = dl;
command_stop();
activity_monitor_signify_activity(0); // inactive, and should be after command_stop()
return 0;
} else {
debug(3, "Connection %d: player thread already deleted.", conn->connection_number);
// debuglev = dl;
return -1;
}
}
}
+11 -9
View File
@@ -68,14 +68,15 @@ typedef struct {
} 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
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 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
@@ -132,7 +133,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
@@ -248,6 +249,7 @@ typedef struct {
void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled.
int enable_dither; // needed for filling silences before play actually starts
int64_t dac_buffer_queue_minimum_length;
} rtsp_conn_info;
uint32_t modulo_32_offset(uint32_t from, uint32_t to);
+11 -8
View File
@@ -137,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;
@@ -368,9 +369,10 @@ void *rtp_control_receiver(void *arg) {
if (la != conn->latency) {
conn->latency = la;
debug(3, "New latency detected: %" PRIu32 ", sync latency: %" PRIu32
", minimum latency: %" PRIu32 ", maximum "
"latency: %" PRIu32 ", fixed offset: %" PRIu32 ".",
debug(3,
"New latency detected: %" PRIu32 ", sync latency: %" PRIu32
", minimum latency: %" PRIu32 ", maximum "
"latency: %" PRIu32 ", fixed offset: %" PRIu32 ".",
la, sync_rtp_timestamp - rtp_timestamp_less_latency, conn->minimum_latency,
conn->maximum_latency, config.fixedLatencyOffset);
}
@@ -1098,7 +1100,7 @@ int sanitised_source_rate_information(uint32_t *frames, uint64_t *time, rtsp_con
double calculated_frame_rate = ((1.0 * local_frames) / local_time) * one_fp;
if (((calculated_frame_rate / conn->input_rate) > 1.002) ||
((calculated_frame_rate / conn->input_rate) < 0.998)) {
debug(2, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate);
debug(3, "input frame rate out of bounds at %.2f fps.", calculated_frame_rate);
result = 1;
} else {
*frames = local_frames;
@@ -1226,8 +1228,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 {
+138 -63
View File
@@ -3,7 +3,7 @@
* Copyright (c) James Laird 2013
* Modifications associated with audio synchronization, mutithreading and
* metadata handling copyright (c) Mike Brady 2014-2018
* metadata handling copyright (c) Mike Brady 2014-2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -51,6 +51,7 @@
#endif
#ifdef CONFIG_MBEDTLS
#include <mbedtls/version.h>
#include <mbedtls/md5.h>
#endif
@@ -90,6 +91,8 @@ enum rtsp_read_request_response {
// Mike Brady's part...
int metadata_running = 0;
// always lock use this when accessing the playing conn value
static pthread_mutex_t playing_conn_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -119,7 +122,10 @@ typedef struct {
} pc_queue; // producer-consumer queue
#endif
static int msg_indexes = 1;
typedef struct {
int index_number;
uint32_t referenceCount; // we might start using this...
unsigned int nheaders;
char *name[16];
@@ -285,15 +291,16 @@ void *player_watchdog_thread_code(void *arg) {
if (time_since_last_bark >= ct) {
conn->watchdog_barks++;
if (conn->watchdog_barks == 1) {
debug(1, "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone "
"of the heart\".",
debug(1,
"Connection %d: As Yeats almost said, \"Too long a silence / can make a stone "
"of the heart\".",
conn->connection_number);
conn->stop = 1;
pthread_cancel(conn->thread);
} else if (conn->watchdog_barks == 3) {
if ((config.cmd_unfixable) && (conn->unfixable_error_reported == 0)) {
conn->unfixable_error_reported = 1;
command_execute(config.cmd_unfixable, "unable_to_cancel_play_session");
command_execute(config.cmd_unfixable, "unable_to_cancel_play_session", 1);
} else {
warn("an unrecoverable error, \"unable_to_cancel_play_session\", has been detected.",
conn->connection_number);
@@ -401,16 +408,17 @@ static char *nextline(char *in, int inbuf) {
}
void msg_retain(rtsp_message *msg) {
if (msg) {
int rc = pthread_mutex_lock(&reference_counter_lock);
if (rc)
debug(1, "Error %d locking reference counter lock");
int rc = pthread_mutex_lock(&reference_counter_lock);
if (rc)
debug(1, "Error %d locking reference counter lock");
if (msg > (rtsp_message *)0x00010000) {
msg->referenceCount++;
// debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
rc = pthread_mutex_unlock(&reference_counter_lock);
if (rc)
debug(1, "Error %d unlocking reference counter lock");
} else {
debug(1, "null rtsp_message pointer passed to retain");
debug(1, "invalid rtsp_message pointer 0x%x passed to retain", (uintptr_t)msg);
}
}
@@ -419,9 +427,11 @@ rtsp_message *msg_init(void) {
if (msg) {
memset(msg, 0, sizeof(rtsp_message));
msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
msg->index_number = msg_indexes++;
} else {
die("can not allocate memory for an rtsp_message.");
die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
}
// debug(1,"msg_init -- create item %d.", msg->index_number);
return msg;
}
@@ -476,12 +486,11 @@ static void debug_print_msg_content(int level, rtsp_message *msg) {
}
*/
void msg_free(rtsp_message *msg) {
if (msg) {
debug_mutex_lock(&reference_counter_lock, 1000, 0);
void msg_free(rtsp_message **msgh) {
debug_mutex_lock(&reference_counter_lock, 1000, 0);
if (*msgh > (rtsp_message *)0x00010000) {
rtsp_message *msg = *msgh;
msg->referenceCount--;
debug_mutex_unlock(&reference_counter_lock, 0);
if (msg->referenceCount == 0) {
unsigned int i;
for (i = 0; i < msg->nheaders; i++) {
@@ -490,14 +499,24 @@ void msg_free(rtsp_message *msg) {
}
if (msg->content)
free(msg->content);
// debug(1,"msg_free item %d -- free.",msg->index_number);
uintptr_t index = (msg->index_number) & 0xFFFF;
if (index == 0)
index = 0x10000; // ensure it doesn't fold to zero.
*msgh =
(rtsp_message *)(index); // put a version of the index number of the freed message in here
free(msg);
} // else {
// debug(1,"rtsp_message reference count non-zero:
// %d!",msg->referenceCount);
//}
} else {
debug(1, "null rtsp_message pointer passed to msg_free()");
} else {
// debug(1,"msg_free item %d -- decrement reference to
// %d.",msg->index_number,msg->referenceCount);
}
} else if (*msgh != NULL) {
debug(1,
"msg_free: error attempting to free an allocated but already-freed rtsp_message, number "
"%d.",
(uintptr_t)*msgh);
}
debug_mutex_unlock(&reference_counter_lock, 0);
}
int msg_handle_line(rtsp_message **pmsg, char *line) {
@@ -550,18 +569,23 @@ int msg_handle_line(rtsp_message **pmsg, char *line) {
}
fail:
*pmsg = NULL;
msg_free(msg);
msg_free(pmsg);
return 0;
}
enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) {
*the_packet = NULL; // need this for erro handling
enum rtsp_read_request_response reply = rtsp_read_request_response_ok;
ssize_t buflen = 4096;
int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay
char *buf = malloc(buflen + 1); // add a NUL at the end
rtsp_message *msg = NULL;
if (!buf) {
warn("rtsp_read_request: can't get a buffer.");
return(rtsp_read_request_response_error);
}
pthread_cleanup_push(malloc_cleanup, buf);
ssize_t nread;
ssize_t inbuf = 0;
int msg_size = -1;
@@ -607,9 +631,9 @@ do {
char *next;
while (msg_size < 0 && (next = nextline(buf, inbuf))) {
msg_size = msg_handle_line(&msg, buf);
msg_size = msg_handle_line(the_packet, buf);
if (!msg) {
if (!(*the_packet)) {
warn("no RTSP header received");
reply = rtsp_read_request_response_bad_packet;
goto shutdown;
@@ -674,7 +698,7 @@ do {
size_t read_chunk = msg_size - inbuf;
if (read_chunk > max_read_chunk)
read_chunk = max_read_chunk;
usleep(40000); // wait about 40 milliseconds between reads of up to about 64 kB
usleep(80000); // wait about 80 milliseconds between reads of up to about 64 kB
nread = read(conn->fd, buf + inbuf, read_chunk);
if (!nread) {
reply = rtsp_read_request_response_error;
@@ -690,21 +714,18 @@ do {
inbuf += nread;
}
rtsp_message *msg = *the_packet;
msg->contentlength = inbuf;
msg->content = buf;
char *jp = inbuf + buf;
*jp = '\0';
*the_packet = msg;
return reply;
shutdown:
if (msg) {
msg_free(msg); // which will free the content and everything else
if (reply != rtsp_read_request_response_ok) {
msg_free(the_packet);
release_buffer = 1; // allow the buffer to be released
}
// in case the message wasn't formed or wasn't fully initialised
if ((msg && (msg->content == NULL)) || (!msg))
free(buf);
*the_packet = NULL;
pthread_cleanup_pop(release_buffer);
return reply;
}
@@ -820,9 +841,10 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *
rtsp_message *resp) {
debug(3, "Connection %d: OPTIONS", conn->connection_number);
resp->respcode = 200;
msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, "
"PAUSE, FLUSH, TEARDOWN, "
"OPTIONS, GET_PARAMETER, SET_PARAMETER");
msg_add_header(resp, "Public",
"ANNOUNCE, SETUP, RECORD, "
"PAUSE, FLUSH, TEARDOWN, "
"OPTIONS, GET_PARAMETER, SET_PARAMETER");
}
void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
@@ -1034,8 +1056,8 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
} else
#ifdef CONFIG_METADATA
if (!strncmp(cp, "progress: ", strlen("progress: "))) {
char *progress = cp + strlen("volume: ");
// debug(2, "progress: \"%s\"\n",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per
char *progress = cp + strlen("progress: ");
// debug(2, "progress: \"%s\"",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per
// second
send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1);
@@ -1076,6 +1098,8 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the
// first few bytes to see
// which.
// 'abeg' -- active mode entered. No arguments
// 'aend' -- active mode exited. No arguments
// 'pbeg' -- play stream begin. No arguments
// 'pend' -- play stream end. No arguments
// 'pfls' -- play stream flush. No arguments
@@ -1396,7 +1420,7 @@ void metadata_pack_cleanup_function(void *arg) {
// debug(1, "metadata_pack_cleanup_function called");
metadata_package *pack = (metadata_package *)arg;
if (pack->carrier)
msg_free(pack->carrier); // release the message
msg_free(&pack->carrier); // release the message
else if (pack->data)
free(pack->data);
}
@@ -1432,12 +1456,15 @@ void metadata_init(void) {
int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
if (ret)
debug(1, "Failed to create metadata thread!");
metadata_running = 1;
}
void metadata_stop(void) {
debug(1, "metadata_stop called.");
pthread_cancel(metadata_thread);
pthread_join(metadata_thread, NULL);
if (metadata_running) {
debug(1, "metadata_stop called.");
pthread_cancel(metadata_thread);
pthread_join(metadata_thread, NULL);
}
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
@@ -1474,14 +1501,17 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rts
pack.code = code;
pack.data = data;
pack.length = length;
if (carrier)
msg_retain(carrier);
pack.carrier = carrier;
if (pack.carrier)
msg_retain(pack.carrier);
int rc = pc_queue_add_item(&metadata_queue, &pack, block);
if ((rc == EBUSY) && (carrier))
msg_free(carrier);
if (rc == EBUSY)
warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code);
if (rc == EBUSY) {
if (pack.carrier)
msg_free(&pack.carrier);
else if (data)
free(data);
warn("Metadata queue is busy, discarding message of type 0x%08X, code 0x%08X.", type, code);
}
return rc;
}
@@ -1548,7 +1578,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m
char *ct = msg_get_header(req, "Content-Type");
if (ct) {
// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct);
// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct);
#ifdef CONFIG_METADATA
// It seems that the rtptime of the message is used as a kind of an ID that
@@ -1996,6 +2026,8 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
MD5_Init(&ctx);
MD5_Update(&ctx, username, strlen(username));
MD5_Update(&ctx, ":", 1);
@@ -2008,9 +2040,25 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, uri, strlen(uri));
MD5_Final(digest_mu, &ctx);
pthread_setcancelstate(oldState, NULL);
#endif
#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 *)username, strlen(username));
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)realm, strlen(realm));
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)config.password, strlen(config.password));
mbedtls_md5_finish_ret(&tctx, digest_urp);
mbedtls_md5_starts_ret(&tctx);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)req->method, strlen(req->method));
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri));
mbedtls_md5_finish_ret(&tctx, digest_mu);
#else
mbedtls_md5_context tctx;
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username));
@@ -2024,6 +2072,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri));
mbedtls_md5_finish(&tctx, digest_mu);
#endif
#endif
#ifdef CONFIG_POLARSSL
@@ -2048,6 +2097,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
snprintf((char *)buf + 2 * i, 3, "%02x", digest_urp[i]);
#ifdef CONFIG_OPENSSL
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
MD5_Init(&ctx);
MD5_Update(&ctx, buf, 32);
MD5_Update(&ctx, ":", 1);
@@ -2057,9 +2107,21 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]);
MD5_Update(&ctx, buf, 32);
MD5_Final(digest_total, &ctx);
pthread_setcancelstate(oldState, NULL);
#endif
#ifdef CONFIG_MBEDTLS
#if MBEDTLS_VERSION_MINOR >= 7
mbedtls_md5_starts_ret(&tctx);
mbedtls_md5_update_ret(&tctx, buf, 32);
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update_ret(&tctx, (const unsigned char *)*nonce, strlen(*nonce));
mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1);
for (i = 0; i < 16; i++)
snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]);
mbedtls_md5_update_ret(&tctx, buf, 32);
mbedtls_md5_finish_ret(&tctx, digest_total);
#else
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, buf, 32);
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
@@ -2069,6 +2131,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]);
mbedtls_md5_update(&tctx, buf, 32);
mbedtls_md5_finish(&tctx, digest_total);
#endif
#endif
#ifdef CONFIG_POLARSSL
@@ -2135,7 +2198,10 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
}
// remove flow control and mutexes
int rc = pthread_cond_destroy(&conn->flowcontrol);
int rc = pthread_mutex_destroy(&conn->volume_control_mutex);
if (rc)
debug(1, "Connection %d: error %d destroying volume_control_mutex.", conn->connection_number, rc);
rc = pthread_cond_destroy(&conn->flowcontrol);
if (rc)
debug(1, "Connection %d: error %d destroying flow control condition variable.",
conn->connection_number, rc);
@@ -2168,7 +2234,7 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
void msg_cleanup_function(void *arg) {
// debug(3, "msg_cleanup_function called.");
msg_free((rtsp_message *)arg);
msg_free((rtsp_message **)arg);
}
static void *rtsp_conversation_thread_func(void *pconn) {
@@ -2198,6 +2264,9 @@ static void *rtsp_conversation_thread_func(void *pconn) {
if (rc)
die("Connection %d: error %d initialising flow control condition variable.",
conn->connection_number, rc);
rc = pthread_mutex_init(&conn->volume_control_mutex, NULL);
if (rc)
die("Connection %d: error %d initialising volume_control_mutex.", conn->connection_number, rc);
// nothing before this is cancellable
pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn);
@@ -2214,16 +2283,17 @@ static void *rtsp_conversation_thread_func(void *pconn) {
int debug_level = 3; // for printing the request and response
reply = rtsp_read_request(conn, &req);
if (reply == rtsp_read_request_response_ok) {
pthread_cleanup_push(msg_cleanup_function, (void *)req);
pthread_cleanup_push(msg_cleanup_function, (void *)&req);
resp = msg_init();
pthread_cleanup_push(msg_cleanup_function, (void *)resp);
pthread_cleanup_push(msg_cleanup_function, (void *)&resp);
resp->respcode = 400;
if (strcmp(req->method, "OPTIONS") !=
0) // the options message is very common, so don't log it until level 3
debug_level = 2;
debug(debug_level, "Connection %d: Received an RTSP Packet of type \"%s\":",
conn->connection_number, req->method),
debug(debug_level,
"Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number,
req->method),
debug_print_msg_headers(debug_level, req);
apple_challenge(conn->fd, req, resp);
@@ -2282,8 +2352,9 @@ static void *rtsp_conversation_thread_func(void *pconn) {
if (conn->stop == 0) {
int err = msg_write_response(conn->fd, resp);
if (err) {
debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the "
"connection.",
debug(1,
"Connection %d: Unable to write an RTSP message response. Terminating the "
"connection.",
conn->connection_number);
struct linger so_linger;
so_linger.l_onoff = 1; // "true"
@@ -2576,8 +2647,12 @@ void rtsp_listen_loop(void) {
ret = pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func,
conn); // also acts as a memory barrier
if (ret)
die("Failed to create RTSP receiver thread %d!", conn->connection_number);
if (ret) {
char errorstring[1024];
strerror_r(ret, (char *)errorstring, sizeof(errorstring));
die("Connection %d: cannot create an RTSP conversation thread. Error %d: \"%s\".",
conn->connection_number, ret, (char *)errorstring);
}
debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number);
conn->running = 1; // this must happen before the thread is tracked
track_thread(conn);
+10 -1
View File
@@ -29,6 +29,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_hw_priority = "yes"; // 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 attenuator first, then the 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".
@@ -41,6 +42,7 @@ general =
// 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_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,15 +50,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" mode starts when play begins and ends when the active_mode_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed.
// run_this_before_entering_active_mode = "/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_mode = "/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_mode_timeout = 10; // wait for this number of seconds after play ends before leaving the active mode, 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.
};
+141 -95
View File
@@ -2,7 +2,7 @@
* Shairport, an Apple Airplay receiver
* Copyright (c) James Laird 2013
* All rights reserved.
* Modifications (c) Mike Brady 2014--2018
* Modifications and additions (c) Mike Brady 2014--2019
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -45,6 +45,7 @@
#ifdef CONFIG_MBEDTLS
#include <mbedtls/md5.h>
#include <mbedtls/version.h>
#endif
#ifdef CONFIG_POLARSSL
@@ -59,8 +60,8 @@
#include <glib.h>
#endif
#include "activity_monitor.h"
#include "common.h"
#include "mdns.h"
#include "rtp.h"
#include "rtsp.h"
@@ -84,17 +85,21 @@
#include "mpris-service.h"
#endif
#ifdef CONFIG_LIBDAEMON
#include <libdaemon/dexec.h>
#include <libdaemon/dfork.h>
#include <libdaemon/dlog.h>
#include <libdaemon/dpid.h>
#include <libdaemon/dsignal.h>
#else
#include <syslog.h>
#endif
#ifdef CONFIG_CONVOLUTION
#include <FFTConvolver/convolver.h>
#endif
static int shutting_down = 0;
// static int shutting_down = 0;
char configuration_file_path[4096 + 1];
char actual_configuration_file_path[4096 + 1];
@@ -103,19 +108,18 @@ static void sig_ignore(__attribute__((unused)) int foo, __attribute__((unused))
static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar,
__attribute__((unused)) void *baz) {
debug(1, "shutdown requested...");
// daemon_log(LOG_NOTICE, "exit...");
#ifdef CONFIG_LIBDAEMON
daemon_retval_send(255);
daemon_pid_file_remove();
#endif
exit(0);
}
static void sig_child(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar,
__attribute__((unused)) void *baz) {
// wait for child processes to exit
pid_t pid;
while ((pid = waitpid((pid_t)-1, 0, WNOHANG)) > 0) {
if (pid == mdns_pid && !shutting_down) {
die("MDNS child process died unexpectedly!");
}
}
}
@@ -149,12 +153,12 @@ void usage(char *progname) {
printf("\n");
printf("Options:\n");
printf(" -h, --help show this help.\n");
#ifdef CONFIG_LIBDAEMON
printf(" -d, --daemon daemonise.\n");
printf(" -j, --justDaemoniseNoPIDFile daemonise without a PID file.\n");
#endif
printf(" -V, --version show version information.\n");
printf(" -k, --kill kill the existing shairport daemon.\n");
printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n");
printf(" -R, --reconnectToOutput reconnect to the output device.\n");
printf(" -c, --configfile=FILE read configuration settings from FILE. Default is "
"/etc/shairport-sync.conf.\n");
@@ -314,6 +318,7 @@ int parse_options(int argc, char **argv) {
poptFreeContext(optCon);
#ifdef CONFIG_LIBDAEMON
if ((daemonisewith) && (daemonisewithout))
die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected "
"both!");
@@ -322,6 +327,7 @@ int parse_options(int argc, char **argv) {
if (daemonisewith)
config.daemonise_store_pid = 1;
};
#endif
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
@@ -330,6 +336,10 @@ int parse_options(int argc, char **argv) {
// nothing else comes in first.
config.fixedLatencyOffset = 11025; // this sounds like it works properly.
config.diagnostic_drop_packet_fraction = 0.0;
config.active_mode_timeout = 10.0;
config.volume_range_hw_priority =
1; // if combining software and hardware volume control, give the hardware priority
// i.e. when reducing volume, reduce the hw first before reducing the software.
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
@@ -363,12 +373,15 @@ int parse_options(int argc, char **argv) {
/* Read the file. If there is an error, report it and exit. */
if (config_read_file(&config_file_stuff, config_file_real_path)) {
free(config_file_real_path);
config_set_auto_convert(&config_file_stuff,
1); // allow autoconversion from int/float to int/float
// make config.cfg point to it
config.cfg = &config_file_stuff;
/* Get the Service Name. */
if (config_lookup_string(config.cfg, "general.name", &str)) {
raw_service_name = (char *)str;
}
#ifdef CONFIG_LIBDAEMON
int daemonisewithout = 0;
int daemonisewith = 0;
/* Get the Daemonize setting. */
@@ -389,7 +402,7 @@ int parse_options(int argc, char **argv) {
/* Get the directory path for the pid file created when the program is daemonised. */
if (config_lookup_string(config.cfg, "sessioncontrol.daemon_pid_dir", &str))
config.piddir = (char *)str;
#endif
/* Get the mdns_backend setting. */
if (config_lookup_string(config.cfg, "general.mdns_backend", &str))
config.mdns_name = (char *)str;
@@ -605,6 +618,9 @@ int parse_options(int argc, char **argv) {
"or \"flat\"");
}
config_set_lookup_bool(config.cfg, "general.volume_control_combined_hardware_priority",
&config.volume_range_hw_priority);
/* Get the interface to listen on, if specified Default is all interfaces */
/* we keep the interface name and the index */
@@ -721,6 +737,25 @@ int parse_options(int argc, char **argv) {
config.cmd_stop = (char *)str;
}
if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_entering_active_mode",
&str)) {
config.cmd_active_start = (char *)str;
}
if (config_lookup_string(config.cfg, "sessioncontrol.run_this_after_exiting_active_mode",
&str)) {
config.cmd_active_stop = (char *)str;
}
if (config_lookup_float(config.cfg, "sessioncontrol.active_mode_timeout", &dvalue)) {
if (dvalue < 0.0)
warn("Invalid value \"%f\" for sessioncontrol.active_mode_timeout. It must be positive. "
"The default of %f will be used instead.",
dvalue, config.active_mode_timeout);
else
config.active_mode_timeout = dvalue;
}
if (config_lookup_string(config.cfg,
"sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) {
config.cmd_unfixable = (char *)str;
@@ -1022,6 +1057,8 @@ int parse_options(int argc, char **argv) {
}
#endif
#ifdef CONFIG_LIBDAEMON
// now, check and calculate the pid directory
#ifdef DEFINED_CUSTOM_PID_DIR
char *use_this_pid_dir = PIDDIR;
@@ -1033,12 +1070,12 @@ int parse_options(int argc, char **argv) {
use_this_pid_dir = config.piddir;
if (use_this_pid_dir)
config.computed_piddir = strdup(use_this_pid_dir);
#endif
return optind + 1;
}
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
GMainLoop *g_main_loop;
static GMainLoop *g_main_loop = NULL;
pthread_t dbus_thread;
void *dbus_thread_func(__attribute__((unused)) void *arg) {
@@ -1089,14 +1126,7 @@ void signal_setup(void) {
sigaction(SIGCHLD, &sa, NULL);
}
// forked daemon lets the spawner know it's up and running OK
// should be called only once!
void shairport_startup_complete(void) {
if (config.daemonise) {
// daemon_ready();
}
}
#ifdef CONFIG_LIBDAEMON
char pid_file_path_string[4096] = "\0";
const char *pid_file_proc(void) {
@@ -1105,29 +1135,10 @@ const char *pid_file_proc(void) {
// debug(1,"pid_file_path_string \"%s\".",pid_file_path_string);
return pid_file_path_string;
}
void exit_function() {
debug(1, "exit function called...");
// cancel_all_RTSP_threads();
if (conns)
free(conns); // make sure the connections have been deleted first
if (config.service_name)
free(config.service_name);
if (config.regtype)
free(config.regtype);
if (config.computed_piddir)
free(config.computed_piddir);
if (ranarray)
free((void *)ranarray);
if (config.cfg)
config_destroy(config.cfg);
if (config.appName)
free(config.appName);
// probably should be freeing malloc'ed memory here, including strdup-created strings...
}
#endif
void main_cleanup_handler(__attribute__((unused)) void *arg) {
// it doesn't look like this is called when the main function is cancelled eith a pthread cancel.
debug(1, "main cleanup handler called.");
#ifdef CONFIG_MQTT
if (config.mqtt_enabled) {
@@ -1142,9 +1153,11 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) {
#ifdef CONFIG_DBUS_INTERFACE
stop_dbus_service();
#endif
debug(1, "Stopping DBUS Loop Thread");
g_main_loop_quit(g_main_loop);
pthread_join(dbus_thread, NULL);
if (g_main_loop) {
debug(1, "Stopping DBUS Loop Thread");
g_main_loop_quit(g_main_loop);
pthread_join(dbus_thread, NULL);
}
#endif
#ifdef CONFIG_DACP_CLIENT
@@ -1160,28 +1173,51 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) {
#ifdef CONFIG_METADATA
metadata_stop(); // close down the metadata pipe
#endif
if (config.output->deinit) {
activity_monitor_stop(0);
if ((config.output) && (config.output->deinit)) {
debug(1, "Deinitialise the audio backend.");
config.output->deinit();
}
daemon_log(LOG_NOTICE, "Unexpected exit...");
#ifdef CONFIG_LIBDAEMON
daemon_retval_send(0);
daemon_pid_file_remove();
daemon_signal_done();
#endif
debug(1, "Exit...");
exit(0);
}
void exit_function() {
debug(1, "exit function called...");
main_cleanup_handler(NULL);
if (conns)
free(conns); // make sure the connections have been deleted first
if (config.service_name)
free(config.service_name);
if (config.regtype)
free(config.regtype);
#ifdef CONFIG_LIBDAEMON
if (config.computed_piddir)
free(config.computed_piddir);
#endif
if (ranarray)
free((void *)ranarray);
if (config.cfg)
config_destroy(config.cfg);
if (config.appName)
free(config.appName);
// probably should be freeing malloc'ed memory here, including strdup-created strings...
}
int main(int argc, char **argv) {
conns = NULL; // no connections active
memset((void *)&main_thread_id, 0, sizeof(main_thread_id));
memset(&config, 0, sizeof(config)); // also clears all strings, BTW
fp_time_at_startup = get_absolute_time_in_fp();
fp_time_at_last_debug_message = fp_time_at_startup;
// debug(1,"startup");
daemon_set_verbosity(LOG_DEBUG);
memset(&config, 0, sizeof(config)); // also clears all strings, BTW
atexit(exit_function);
// this is a bit weird, but apparently necessary
// this is a bit weird, but necessary -- basename() may modify the argument passed in
char *basec = strdup(argv[0]);
char *bname = basename(basec);
config.appName = strdup(bname);
@@ -1189,9 +1225,18 @@ int main(int argc, char **argv) {
die("can not allocate memory for the app name!");
free(basec);
// debug(1,"startup");
#ifdef CONFIG_LIBDAEMON
daemon_set_verbosity(LOG_DEBUG);
#else
setlogmask (LOG_UPTO (LOG_DEBUG));
openlog(NULL,0,LOG_DAEMON);
#endif
atexit(exit_function);
// set defaults
// get thje endianness
// get the endianness
union {
uint32_t u32;
uint8_t arr[4];
@@ -1268,6 +1313,8 @@ int main(int argc, char **argv) {
exit(1);
}
#ifdef CONFIG_LIBDAEMON
pid_t pid;
/* Reset signal handlers */
@@ -1287,44 +1334,22 @@ int main(int argc, char **argv) {
daemon_pid_file_proc = pid_file_proc;
/* Check if we are called with -D or --disconnectFromOutput parameter */
if (argc >= 2 &&
((strcmp(argv[1], "-D") == 0) || (strcmp(argv[1], "--disconnectFromOutput") == 0))) {
if ((pid = daemon_pid_file_is_running()) >= 0) {
if (kill(pid, SIGUSR2) != 0) { // try to send the signal
daemon_log(LOG_WARNING,
"Failed trying to send disconnectFromOutput command to daemon pid: %d: %s", pid,
strerror(errno));
}
} else {
daemon_log(LOG_WARNING,
"Can't send a disconnectFromOutput request -- Failed to find daemon: %s",
strerror(errno));
}
exit(1);
}
#endif
/* Check if we are called with -R or --reconnectToOutput parameter */
if (argc >= 2 &&
((strcmp(argv[1], "-R") == 0) || (strcmp(argv[1], "--reconnectToOutput") == 0))) {
if ((pid = daemon_pid_file_is_running()) >= 0) {
if (kill(pid, SIGHUP) != 0) { // try to send the signal
daemon_log(LOG_WARNING,
"Failed trying to send reconnectToOutput command to daemon pid: %d: %s", pid,
strerror(errno));
}
} else {
daemon_log(LOG_WARNING, "Can't send a reconnectToOutput request -- Failed to find daemon: %s",
strerror(errno));
}
exit(1);
}
// parse arguments into config -- needed to locate pid_dir
int audio_arg = parse_options(argc, argv);
// mDNS supports maximum of 63-character names (we append 13).
if (strlen(config.service_name) > 50) {
warn("Supplied name too long (max 50 characters)");
config.service_name[50] = '\0'; // truncate it and carry on...
}
/* Check if we are called with -k or --kill parameter */
if (argc >= 2 && ((strcmp(argv[1], "-k") == 0) || (strcmp(argv[1], "--kill") == 0))) {
#ifdef CONFIG_LIBDAEMON
int ret;
/* Kill daemon with SIGTERM */
@@ -1334,20 +1359,20 @@ int main(int argc, char **argv) {
else
daemon_pid_file_remove();
return ret < 0 ? 1 : 0;
#else
syslog(LOG_ERR, "shairport-sync was built without support for the -k or --kill option");
return 1;
#endif
}
#ifdef CONFIG_LIBDAEMON
/* If we are going to daemonise, check that the daemon is not running already.*/
if ((config.daemonise) && ((pid = daemon_pid_file_is_running()) >= 0)) {
daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid);
return 1;
}
// mDNS supports maximum of 63-character names (we append 13).
if (strlen(config.service_name) > 50) {
warn("Supplied name too long (max 50 characters)");
config.service_name[50] = '\0'; // truncate it and carry on...
}
/* here, daemonise with libdaemon */
if (config.daemonise) {
@@ -1430,6 +1455,8 @@ int main(int argc, char **argv) {
/* end libdaemon stuff */
}
#endif
main_thread_id = pthread_self();
if (!main_thread_id)
debug(1, "Main thread is set up to be NULL!");
@@ -1501,17 +1528,23 @@ int main(int argc, char **argv) {
"deliberately.",
config.diagnostic_drop_packet_fraction);
debug(1, "statistics_requester status is %d.", config.statistics_requested);
#if CONFIG_LIBDAEMON
debug(1, "daemon status is %d.", config.daemonise);
debug(1, "deamon pid file path is \"%s\".", pid_file_proc());
#endif
debug(1, "rtsp listening port is %d.", config.port);
debug(1, "udp base port is %d.", config.udp_port_base);
debug(1, "udp port range is %d.", config.udp_port_range);
debug(1, "player name is \"%s\".", config.service_name);
debug(1, "backend is \"%s\".", config.output_name);
debug(1, "on-start action is \"%s\".", config.cmd_start);
debug(1, "on-stop action is \"%s\".", config.cmd_stop);
debug(1, "run_this_before_play_begins action is \"%s\".", config.cmd_start);
debug(1, "run_this_after_play_ends action is \"%s\".", config.cmd_stop);
debug(1, "wait-cmd status is %d.", config.cmd_blocking);
debug(1, "on-start returns output is %d.", config.cmd_start_returns_output);
debug(1, "run_this_before_play_begins may return output is %d.", config.cmd_start_returns_output);
debug(1, "run_this_if_an_unfixable_error_is_detected action is \"%s\".", config.cmd_unfixable);
debug(1, "run_this_before_entering_active_mode action is \"%s\".", config.cmd_active_start);
debug(1, "run_this_after_exiting_active_mode action is \"%s\".", config.cmd_active_stop);
debug(1, "active_mode_timeout is %f seconds.", config.active_mode_timeout);
debug(1, "mdns backend \"%s\".", config.mdns_name);
debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency);
debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing);
@@ -1525,6 +1558,12 @@ int main(int argc, char **argv) {
debug(1, "volume_max_db is %d.", config.volume_max_db);
else
debug(1, "volume_max_db is not set");
debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.",
config.volume_range_db);
debug(
1,
"combined attenuators (0 -- software is / 1 -- hardware is top of attenuation range) is %d.",
config.volume_range_hw_priority);
debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).",
config.playback_mode);
debug(1, "disable_synchronization is %d.", config.no_sync);
@@ -1538,8 +1577,6 @@ int main(int argc, char **argv) {
debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset);
debug(1, "audio backend silence lead-in time is %f seconds. A value -1.0 means use the default.",
config.audio_backend_silent_lead_in_time);
debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.",
config.volume_range_db);
debug(1, "zeroconf regtype is \"%s\".", config.regtype);
debug(1, "decoders_supported field is %d.", config.decoders_supported);
debug(1, "use_apple_decoder is %d.", config.use_apple_decoder);
@@ -1584,11 +1621,18 @@ int main(int argc, char **argv) {
#endif
#ifdef CONFIG_MBEDTLS
#if MBEDTLS_VERSION_MINOR >= 7
mbedtls_md5_context tctx;
mbedtls_md5_starts_ret(&tctx);
mbedtls_md5_update_ret(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
mbedtls_md5_finish_ret(&tctx, ap_md5);
#else
mbedtls_md5_context tctx;
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
mbedtls_md5_finish(&tctx, ap_md5);
#endif
#endif
#ifdef CONFIG_POLARSSL
md5_context tctx;
@@ -1629,6 +1673,8 @@ int main(int argc, char **argv) {
}
#endif
activity_monitor_start();
// daemon_log(LOG_INFO, "Successful Startup");
rtsp_listen_loop();
+2 -1
View File
@@ -1713,7 +1713,8 @@ void mdnsd_stop(struct mdnsd *s) {
assert(s != NULL);
struct timeval tv = {
.tv_sec = 0, .tv_usec = 500 * 1000,
.tv_sec = 0,
.tv_usec = 500 * 1000,
};
s->stop_flag = 1;