Merge pull request #213 from mikebrady/development

Merge recent developments to the master branch. This will become 2.8.
This commit is contained in:
Mike Brady
2016-01-30 16:02:30 +00:00
32 changed files with 1348 additions and 536 deletions
+5
View File
@@ -1,4 +1,5 @@
/shairport-sync
/shairport-sync.exe
/*.o
/*~
*.xml~
@@ -16,3 +17,7 @@ install-sh
missing
stamp-h1
.deps
man/Makefile
man/Makefile.in
scripts/shairport-sync.service
scripts/shairport-sync
+54
View File
@@ -0,0 +1,54 @@
language: c
sudo: required
dist: trusty
env:
matrix:
- CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemv"
- CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemv"
- CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-stdio --with-avahi --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-stdio --with-avahi --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-pipe --with-avahi --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-pipe --with-avahi --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-ao --with-avahi --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-ao --with-avahi --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemv"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemv"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-stdio --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-stdio --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-pipe --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-pipe --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-ao --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-ao --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd"
script:
- autoreconf -fi
- ./configure $CFG
- make
branches:
only:
- development
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y libdaemon-dev avahi-daemon libavahi-client-dev
addons:
apt:
packages:
- libasound2-dev
- libsoxr-dev
- libpopt-dev
- libssl-dev
- libpolarssl-dev
- libconfig-dev
- xmltoman
- libao-dev
+1 -1
View File
@@ -25,4 +25,4 @@ Finally, for Shairport Sync to be able to locate `libsoxr-dev` during compilatio
```
sudo ldconfig -v
```
That's it. Now you can select the `--with-libsoxr` option when you're building Shairport Sync.
That's it. Now you can select the `--with-soxr` option when you're building Shairport Sync.
+2 -2
View File
@@ -61,6 +61,6 @@ if INSTALL_SYSTEMV
[ -f $(DESTDIR)/etc/init.d/shairport-sync ] || cp scripts/shairport-sync $(DESTDIR)/etc/init.d/
endif
if INSTALL_SYSTEMD
[ -e $(DESTDIR)/usr/lib/systemd/system ] || mkdir -p $(DESTDIR)/usr/lib/systemd/system
cp scripts/shairport-sync.service $(DESTDIR)/usr/lib/systemd/system
[ -e $(DESTDIR)$(systemdsystemunitdir) ] || mkdir -p $(DESTDIR)$(systemdsystemunitdir)
cp scripts/shairport-sync.service $(DESTDIR)$(systemdsystemunitdir)
endif
+71 -50
View File
@@ -1,20 +1,22 @@
Shairport Sync
=============
Shairport Sync is an AirPlay audio player -- it plays audio streamed from iTunes, iOS devices and third-party AirPlay sources such as ForkedDaapd and others.
Shairport Sync is an AirPlay audio player — it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player, ForkedDaapd among others.
Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.)
Shairport Sync does not support AirPlay video or photo streaming.
This branch -- "master" -- is the stable branch of Shairport Sync. To access the development version, please switch to the "development" branch.
This branch — "development" — is unstable and may change quickly. To access the stable version, please switch to the "master" branch.
More Information
----------
Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to "play" audio at exactly the right time. It does this by monitoring and controlling the *latency* — the time between a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC). Timestamps are measured relative to the source computer's clocks, the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync, the `local clock`. The source and local clocks are synchronised, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols.
Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to play audio at exactly the right time. It does this by monitoring and controlling the *latency* — the time-gap between when a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC).
The latency to be used is specified by the source when it negotiates with Shairport Sync. Most sources set a latency of 88,200 frames — exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of 99,577 frames.
Timestamps are measured 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`. The source and local clocks are synchronised by Shairport Sync, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols.
To maintain the exact latency required, if an 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”.
The exact latency to be used is specified by the source when it negotiates with Shaiport Sync. Most sources set a latency of 88,200 frames -- exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of 99,577 frames (pretty close to the 99,400 frames estimated from listening tests!).
Shairport Sync is a pretty substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0.
Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100 samples per second interleaved PCM stereo (you'll get a message in the logfile if there's a problem).
@@ -23,19 +25,19 @@ For more about the motivation behind Shairport Sync, please see the wiki at http
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.
* 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 will mute properly if the hardware supports it.
* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds.
* 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 be configured to deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes among others but the Music app in iOS 9 only supplies volume control metadata.
* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
* Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync.
* Autotools and Libtool Support — One important difference between Shairport Sync and other versions of Shairport is that the Shairport Sync build process uses GNU autotools and libtool to examine and configure the build environment — very important 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.
* 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.
Status
------
Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Wheezy and Jessie, 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. It will not work properly – if at all – with a PulseAudio (pseudo-)output device. Using a port of the `alsa` system, Shairport Sync runs rather well on FreeBSD.
Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Wheezy and Jessie, 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. It will not work properly — if at all — with a PulseAudio (pseudo-)output device. Using a port of the `alsa` system, Shairport Sync runs rather well on FreeBSD.
Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound card, though the audio out of the card is of poor quality (see the note below on configuring the Raspberry Pi to make best use of it). USB-connected sound cards work well on recent versions of Raspbian; however older versions of Raspbian appear to suffer from a problem — see http://www.raspberrypi.org/forums/viewtopic.php?t=23544, so it is wise to update. Shairport Sync works well with the IQAudIO Pi-DAC — see http://www.iqaudio.com.
Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound card, though the audio out of the card is of poor quality (see the note below on configuring the Raspberry Pi to make best use of it). USB-connected sound cards work well, though [very] old versions of Raspbian appear to suffer from a problem — see http://www.raspberrypi.org/forums/viewtopic.php?t=23544, so it is wise to update. Shairport Sync works well with the IQAudIO Pi-DAC — see http://www.iqaudio.com.
At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi.
@@ -49,16 +51,26 @@ Note: Historically, Shairport Sync has taken its settings from command line argu
Building And Installing
---------------------
If you wish to install Shairport Sync on OpenWrt, Arch or Fedora platforms, please follow the appropriate instructions below. Otherwise follow the General Build Instructions. Then, when the program has been installed, refer to the section on Configuring Shairport Sync that follows.
If you wish to install Shairport Sync on OpenWrt, Arch or Fedora platforms, please follow the appropriate instructions below. Limited support is also available for MAc OS X. Otherwise follow the General Build Instructions. Then, when the program has been installed, refer to the section on Configuring Shairport Sync that follows.
**Note**
The following procedures will install shairport-sync at `/usr/local/bin/shairport-sync`. Before continuing, you should check to see if shairport-sync is already installed on your system -- use `which shairport-sync` to find where it is located, if installed. If it is installed anywhere other than at `/usr/local/bin/shairport-sync`, you should delete it -- you may need to have superuser privileges.
The following procedures will install the shairport-sync application into your system. Before continuing, you should check to see if shairport-sync is already installed – you can use `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.
(If the existing installation of shairport-sync is where the new copy will be installed into, it will be overwritten; sometimes, however, the installation is to another location, so it is safer, initially, to delete previous versions manually.)
**Ubuntu:**
Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson. A `shairport-sync` installer package is available in Ubuntu 16.04, currently in its alpha phase.
**OpenWrt:**
There is a Shairport Sync package in OpenWrt `trunk`. Also, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt, including one that builds back to `Attitude Adjustment`.
There is a Shairport Sync package in OpenWrt `trunk`. Also, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt, including one that builds back to `Barrier Breaker`.
**Arch Linux:**
An Arch Linux installation package is available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD).
Shairport Sync is available for `x86_64` and `i686` platforms in the Arch Linux Community Repository -- search for `shairport-sync`. See also https://www.archlinux.org/packages/.
An Arch Linux installation package, suitable for compilation on any platform, is available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD).
**Mac OS X:**
A [HomeBrew](http://brew.sh) package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command `$brew install shairport-sync`. Note that the installation uses the `libao` library and so synchronisation is not available — playback glitches will occur occasionally, when the `ao` system's buffers overflow or underflow.
**Fedora:**
Install the toolchain and pre-requisites, if necessary:
@@ -94,6 +106,7 @@ The following libraries are required:
* ALSA
* libdaemon
* autoconf
* automake
* libtool
* libpopt
* libconfig
@@ -103,13 +116,14 @@ Optional:
Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or PolarSSL. Libsoxr 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.
Assuming the usual build essentials and git, Debian, Ubuntu and Raspbian users can get the basics with:
Debian, Ubuntu and Raspbian users can get the basics with:
- `apt-get install autoconf libtool libdaemon-dev libasound2-dev libpopt-dev libconfig-dev`
- `apt-get install build-essential git` – these may already be installed.
- `apt-get install autoconf automake libtool libdaemon-dev libasound2-dev libpopt-dev libconfig-dev`
- `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 PolarSSL otherwise.
- `apt-get install libpolarssl-dev` if you want to use PolarSSL, or use OpenSSL/libcrypto otherwise.
- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is not yet part of Raspbian; instructions for how to build it from source are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/development/LIBSOXR.md).
- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspioan Jessie; if not, instructions for how to build it from source for Raspian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/development/LIBSOXR.md).
Download Shairport Sync:
@@ -120,6 +134,7 @@ Next, `cd` into the shairport-sync directory and execute the following command:
```
$ autoreconf -i -f
```
(Note that the `autoreconf...` step may take some time on less powerful machines.)
Choose the appropriate `--with-*` options:
@@ -136,13 +151,13 @@ Choose the appropriate `--with-*` options:
- `--with-configfile` to install a configuration file and a separate sample file at the `make install` stage. Default is to install. An existing `/etc/shairport-sync.conf` will not be overwritten.
- `--with-pkg-config` to use pkg-config to find libraries. Default is to use pkg-config — this option is for special purpose use.
**System V and `systemd`**
**`systemd` and "System V"**
At the time of writing, there are two general systems for automatically starting programs automatically at startup: "System V" or `systemd`. To see if the `systemd` process is running on your system, enter the following command:
At the time of writing, there are two widely-used systems for automatically starting programs automatically at startup: `systemd` and "System V" . (There are others, but they are not considered here.) To see if the `systemd` process is running on your system, enter the following command:
`ps aux | grep systemd | grep -v grep`
On a system using `systemd` (this is a Raspberry Pi running Raspbian Jessie) you'll get many lines containing `systemd`, for example:
On a system using `systemd` (this is from a Raspberry Pi running Raspbian Jessie) you'll get many lines containing `systemd`, for example:
```
pi@raspberrypi ~ $ ps aux | grep systemd | grep -v grep
root 90 0.1 0.6 8088 2764 ? Ss 08:00 0:01 /lib/systemd/systemd-journald
@@ -152,19 +167,19 @@ message+ 528 0.0 0.7 5568 3172 ? Ss 08:00 0:01 /usr/bin/dbus-d
pi 983 0.0 0.7 4912 3256 ? Ss 08:00 0:00 /lib/systemd/systemd --user
pi@raspberrypi ~ $
```
whereas on a system without `systemd` -- presumably using System V -- (this is a Raspberry Pi running Raspbian Wheezy) , you'll get nothing:
whereas on a system without `systemd` – presumably using System V – (this is a from a Raspberry Pi running Raspbian Wheezy) , you'll get nothing:
```
pi@raspberrypi ~ $ ps aux | grep systemd | grep -v grep
pi@raspberrypi ~ $
```
Choose `--with-systemd` or `--with-systemv` on the basis of the outcome.
Here is an example, suitable for installations such as Ubuntu and Raspbian:
Here is an example, suitable for installations that use `systemd`, such as Ubuntu 15.10 and Raspbian Jessie:
`$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemv`
`$ ./configure --with-alsa --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 `systemd` system, replace the `--with-systemv` with `--with-systemd`.
* For installation into a System V system, replace the `--with-systemd` with `--with-systemv`.
Enter:
@@ -172,22 +187,8 @@ Enter:
to build the application.
**Installation to a System V system**
If you are installing onto a System V system:
```
$sudo make install
```
to install `shairport-sync` along with a `man` page, a default configuration file and a System V startup script to launch it automatically at system startup.
To complete the installation, enter:
```
$sudo update-rc.d shairport-sync defaults 90 10
```
**Installation to a `systemd` system**
To complete the installation, you need to define a `shairport-sync` group and user. This is a security measure -- the user and group are relatively unprivileged, and the user does not have login priviliges. The user must be a member of the `audio` group to be able to access the audio hardware. The following commands define the group and user correctly if they do not already exist (note the use of `sudo` -- omit this if you already have superuser privileges:
To complete the installation, you need to define a `shairport-sync` group and user. This is a security measure – the user and group are relatively unprivileged, and the user does not have login priviliges. The user must be a member of the `audio` group to be able to access the audio hardware. The following commands define the group and user correctly if they do not already exist (note the use of `sudo` – omit this if you already have superuser privileges:
```
$getent group shairport-sync &>/dev/null || sudo groupadd -r shairport-sync >/dev/null
@@ -206,21 +207,35 @@ To enable Shairport Sync to start automatically at system startup, enter:
`$sudo systemctl enable shairport-sync`
**Installation to a System V system**
If you are installing onto a System V system:
```
$sudo make install
```
to install `shairport-sync` along with a `man` page, a default configuration file and a System V startup script to launch it automatically at system startup.
To complete the installation, enter:
```
$sudo update-rc.d shairport-sync defaults 90 10
```
**Man Page**
You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/master/man/shairport-sync.html
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.
Starting and stopping automatically is taken care of differently in different versions of Linux -- see the previous section for an example of installing into a System V or a `systemd` based system.
**(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.
To get the best from Shairport Sync, you’ll need to (1) give Shairport Sync a service name by which it will be seen in iTunes etc., (2) specify the output device to use and (3) specify the name of the mixer volume control to use to control the output level. To get values for (2) and (3) you might need to explore the ALSA output devices with a program like `alsamixer` or similar.
**(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., (b) specify the output device to use and (c) specify the name of the mixer volume control to use to control the output level. To get values for (b) and (b) 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`. While it can also take configuration settings from command line options, it is recommended that you use the configuration file method. When you run `$sudo make install`, a default configuration is installed at `/etc/shairport-sync.conf` (it won't replace an existing one) which should work in almost any system with a sound card.
A sample configuration file is installed (or updated) at `/etc/shairport-sync.conf.sample`. 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.
Shairport Sync reads settings from a configuration file at `/etc/shairport-sync.conf`. When you run `$sudo make install`, a sample configuration file is installed (or updated) at `/etc/shairport-sync.conf.sample`. 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 at `/etc/shairport-sync.conf`, 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 used `libsoxr` interpolation, then you should do the following:
@@ -233,6 +248,8 @@ general =
// ... other general settings
};
```
(Remember, anything preceded by `//` is a comment and will have no effect on the setting of Shairport Sync.)
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.
@@ -253,14 +270,18 @@ Shairport Sync can run programs just before it starts to play an audio stream an
Please note that the full path to the programs must be specified, and script files will not be executed unless they are marked as executable and have the standard `#!/bin/...` first line. (This behaviour may be different from other Shairports.)
Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatability, 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. This provides a low-quality output that is nevertheless useful for testing purposes and may be adequate for [very] casual listening. It is not HiFi -- it is quite noisy and can't play anything above about 15kHz. A further 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 35dB 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` stanza 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 35, the top 35 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -31dB, below which muting will occur.
The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. This provides a low-quality output that is nevertheless useful for testing purposes and may be adequate for [very] casual listening. It is not HiFi -- it is quite noisy and can't play anything above about 15kHz. A further 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 30dB 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` stanza 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 30, the top 30 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -26dB, 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` setting: you should set it to a larger number, such as 352, to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC.
*Command Line Arguments*
You can use command line arguments to provide settings to Shairport Sync as before. For full information, please read the Shairport Sync `man` page, also available at http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/master/man/shairport-sync.html.
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.
@@ -368,9 +389,9 @@ 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 88,200 or 99,577 frames. You shouldn't need to change them. (The `latencies` stanza in the configuration file and the various latency command-line options are deprecated.)
Shairport Sync uses latencies supplied by the source, typically either 88,200 or 99,577 frames. You shouldn't need to change them. (The `latencies` stanza in the configuration file and the various latency command-line options are now obselete and are deprecated.)
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`. 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 -4410.
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`. 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 -4410.
Resynchronisation
-------------
@@ -380,7 +401,7 @@ If synchronisation is lost — say due to a busy source or a congested network
Tolerance
---------
Playback synchronisation is allowed to wander, or to "drift") a small amount before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the statistics setting to monitor correction levels. Corrections should not greatly exceed net corrections.
Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections.
* You can vary the tolerance with the `general` `drift` setting.
Some Statistics
+93 -2
View File
@@ -1,4 +1,95 @@
Version 2.6
Version 2.7.10 -- Development Version
----
**New Feature**
* If the `ignore_volume_control` setting was `yes`, Shairport Sync really did ignore volume control settings and did not send any volume metadata (i.e. `pvol` coded metadata). Now, while continuing to ignore volume control settings, it sends a `pvol` token where the first number is the AirPlay volume, as before, but the remaining three parameters are set to zero.
Version 2.7.9 -- Development Version
----
**Bug Fix**
* Oops – brown-bag update. Fixed a crashing bug introduced in the last release, caused by not checking for a hardware mixer before trying to access it, duh.
Version 2.7.8 -- Development Version
----
**Bug Fix**
* Fixed an issue whereby Shairport Sync did not reset the hardware mixer volume level before resuming playing. The issue was caused by not releasing and later reaquiring the mixer when pausing and resuming. Thanks to [Tim Curtis](https://github.com/moodeaudio) for reporting the issue.
Version 2.7.7 -- Development Version
----
**Enhancements**
* Add note about the Arch Linux Community repository package `shairport-sync`. Thanks to [Anatol Pomozov](https://github.com/anatol).
* Shairport Sync doesn't ask for packets to be resent quite so quickly -- it waits about half a second now before asking for missing packets to be resent.
**Bug Fixes**
* Improved Shairport Sync's behaviour when it's asked to stop a play session and immediately start another. The signalling system used to stop threads was sometimes stopping threads belonging to the new session. This affected iOS 9.2 users going to the next track -- sometimes the player would become unavailable for an instant and disconnect the session. Th problem still happens occasionally.
* Removed code favouring the use of "public" IPv6 addresses as source addresses when connecting to a distant IPv6 port – Neither OpenWrt nor FreeBSD can use it at present. Also, it's not clear if any problems are being caused by not favouring public IPv6 addresses.
Version 2.7.6 -- Development Version
----
**Bug Fixes**
* Look for the correct tag name for desired `ao` buffer length: `audio_backend_buffer_desired_length` rather than `audio_backend_buffer_desired_length_software`.
* Fix a few FreeBSD compilation bugs.
* Fix a few documentation issues and typos. Thanks to [Chris Boot](https://github.com/bootc).
**Enhancements**
* Add note about installing to Mac OS X. Thanks to [Serg Podtynnyi](https://github.com/shtirlic).
* Add automatic rebuild of manpage and html documentation when `xmltoman` and friends are available. Thanks to [Chris Boot](https://github.com/bootc).
* Favour the use of "public" IPv6 addresses as source addresses when connecting to a distant IPv6 port.
Version 2.7.5 -- Development Version
----
**New Features**
* Ubuntu PPA files now available at https://launchpad.net/~dantheperson.
**Enhancements**
* Broaden the use of the value `$PREFIX` instead of the path `/usr/local/bin` during configuration. Thanks to [dantheperson](https://github.com/dantheperson).
Version 2.7.4 -- Development Version
----
**Enhancements**
* Use the correct method for finding the `systemd` unit path, as recomended by debain maintainers and
http://www.freedesktop.org/software/systemd/man/daemon.html#Installing%20Systemd%20Service%20Files. Thanks to [dantheperson](https://github.com/dantheperson).
* Rather than hardwire the path `/usr/local/bin` as the path to the shairport-sync executable, the value of `$PREFIX` is now used during configuration. Thanks to [Nick Steel](https://github.com/kingosticks).
* Add some extra diagnostic messages if the hardware buffer in the DAC is smaller than desired.
* If metadata has been enabled, but if picture sending has not been requested and the source sends pictures anyway, omit them from the metadata feed. Thanks to [Jörg Krause](https://github.com/joerg-krause).
**Bug Fixes**
* Fixed a data alignment issue in the handling of metadata on some processors. Thanks to [Jörg Krause](https://github.com/joerg-krause).
* Removed an `assert` which would terminate the program if a malformed packet of data was received.
* Look for the correct tag name for desired alsa buffer length: `audio_backend_buffer_desired_length` rather than `audio_backend_buffer_desired_length_software`.
Version 2.7.3 -- Development Version
----
**Bug Fix**
* The dither code was broken in Shairport Sync and also less than ideal anyway. Fixed and improved. Dither is added whenever you use the software volume control at less than full volume. See http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf for a very influential paper by Lipshitz, Wannamaker and Vanderkooy, 1992. The dither code in Shairport Sync was inherited from Shairport and does not conform to the recommendations in the paper -- specifically the implementation would give one bit of dither where the paper recommends two bits peak-to-peak. The other thing is that the inherited dither code was actually broken in Shairport Sync. So, the new dither code gives a two bit peak-to-peak dither based on a Triangular Propability Distributing Function (TPDF). It sounds like a very low-level white noise, unmodulated by the audio material. It would be nice if it was even lower, but it's better than listening to the artifacts present when dithering is disabled.
Version 2.7.2 -- Development Version
----
**Bug Fix**
* Fix a bug that suppressed output of the `rtptime` associated with metadata and with picture information coming from the audio source and passed on via the metadata pipe.
**Other Changes**
* Added some more information to the log whenever problems are detected with the proposed alsa device.
Version 2.7.1 -- Development Version
----
**Bug Fix**
* The new volume-extension code was not correctly setting the volume after a pause / resume. Fixed.
Version 2.7 -- Development Version
----
**New Features**
* Extend the volume range for some DACs. Background: some of the cheaper DACS have a very small volume range (that is, the ratio of the highest to the lowest volume, expressed in decibels). In some really cheap DACs it's only around 30 dB. That means that the difference betweeen the lowest and highest volume settings isn't large enough. With the new feature, if you set the `general` `volume_range_db` to more than the hardware mixer's range, Shairport Sync will combine the hardware mixer's range with a software attenuator to give the desired range. For example, suppose you want a volume range of 70 dB and the hardware mixer offers only 30 dB, then Shairport Sync will make up the other 40 dB with a software attenuator. One drawback is that, when the volume is being changed, there may be a slight delay (0.15 seconds by default) as the audio, whose volume may have been adjusted in software, propagates through the system. Another slight possible drawback is a slightly heavier load on the processor.
* Check for underflow a little better when buffer aliasing occurs on very bad connections...
* Add extra debug messages to the alsa back end to diagnose strange DACs.
* Add configuration file for the `libao` back end -- to change the buffer size and the latency offset, same as for stdout.
* Add `shairport-sync.exe` to `.gitignore`.
* Add a check to support compilation on a CYGWIN platform.
* Add `rtptime` tags to metadata and picture information and add two new metadata items to precede and follow the transmission of a picture. Background: it seems that metadata and picture information for the same item, e.g. a track, are normally tagged with a timestamp called the `rtptime`; if they refer to the same item, they will have the same `rtptime` tags. The update here is to add the `rtptime` value, if available, as data to the `mdst` and `mden` metadata items, which are sent before ("MetaData STart") and after ("MetaData ENd") a metadata sequence.
In addition, similar tags -- `pcst` ("PiCture STart") and `pcen` ("PiCture ENd") are now sent before and after a picture with the `rtptime` value, if available, sent as data.
By the way, the progress metadata (`prgr` for "PRoGRess"), which is sent just when a track starts, contains the same `rtptime` as its middle element.
Version 2.6 -- Stable Version
----
This is basically version 2.4.2 with two small fixes. It's been bumped to 2.6 because (1) the new features added between 2.4.1 and 2.4.2 deserve more than just a bug-fix increment and (2) the development versions (2.5.x) should have lower numbers than the release versions, so that releases are always seen as upgrades. For example: 2.5.0.9 --> 2.6 looks like an upgrade, whereas 2.5.0.9 --> 2.4.2 looks like a downgrade.
@@ -88,7 +179,7 @@ Version 2.3.12
**Enhancements**
* Larger range of interpolation. Shairport Sync has previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 real frames. This contraint has been relaxed, and it is now able to make corrections of up to 1 in 352 real frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows:
* Larger range of interpolation. Shairport Sync was previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 frames. This contraint has been relaxed, and it is now able to make corrections of up to 1 in 352 frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows:
* No corrections happen for the first five seconds.
* Corrections of up to about 1 in 1000 for the next 25 seconds.
* Corrections of up to 1 in 352 thereafter.
+3 -3
View File
@@ -810,7 +810,7 @@ void alac_decode_frame(alac_file *alac,
}
else
{
fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type);
fprintf(stderr, "FIXME: unhandled prediction type for compressed case: %i\n", prediction_type);
/* i think the only other prediction type (or perhaps this is just a
* boolean?) runs adaptive fir twice.. like:
* predictor_decompress_fir_adapt(predictor_error, tempout, ...)
@@ -1010,7 +1010,7 @@ void alac_decode_frame(alac_file *alac,
}
else
{ /* see mono case */
fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type_a);
fprintf(stderr, "FIXME: unhandled prediction type on channel 1: %i\n", prediction_type_a);
}
/* channel 2 */
@@ -1035,7 +1035,7 @@ void alac_decode_frame(alac_file *alac,
}
else
{
fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type_b);
fprintf(stderr, "FIXME: unhandled prediction type on channel 2: %i\n", prediction_type_b);
}
}
else
+6 -6
View File
@@ -5,13 +5,9 @@
#include <libconfig.h>
typedef struct {
double airplay_volume;
double current_volume_dB;
int minimum_volume_dB;
int maximum_volume_dB;
int has_true_mute;
int is_muted;
int valid;
int32_t minimum_volume_dB;
int32_t maximum_volume_dB;
} audio_parameters;
typedef struct {
@@ -42,6 +38,10 @@ typedef struct {
// may be NULL, in which case soft volume parameters are used
void (*parameters)(audio_parameters *info);
// may be NULL, in which case software muting is used.
void (*mute)(int do_mute);
} audio_output;
audio_output *audio_get_output(char *name);
+214 -103
View File
@@ -46,7 +46,7 @@ static uint32_t delay(void);
static void volume(double vol);
static void linear_volume(double vol);
static void parameters(audio_parameters *info);
static int has_mute = 0;
static void mute(int do_mute);
static double set_volume;
audio_output audio_alsa = {
@@ -59,6 +59,7 @@ audio_output audio_alsa = {
.flush = &flush,
.delay = &delay,
.play = &play,
.mute = NULL, // to be set later on...
.volume = NULL, // to be set later on...
.parameters = NULL // to be set later on...
};
@@ -80,6 +81,8 @@ static char *alsa_out_dev = "default";
static char *alsa_mix_dev = NULL;
static char *alsa_mix_ctrl = "Master";
static int alsa_mix_index = 0;
static int hardware_mixer = 0;
static int play_number;
static int64_t accumulated_delay, accumulated_da_delay;
@@ -92,35 +95,64 @@ static void help(void) {
" *) default option\n");
}
int open_mixer() {
if (hardware_mixer) {
debug(3, "Open Mixer");
int ret = 0;
snd_mixer_selem_id_alloca(&alsa_mix_sid);
snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl);
if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0)
die("Failed to open mixer");
debug(3, "Mixer device name is \"%s\".", alsa_mix_dev);
if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0)
die("Failed to attach mixer");
if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0)
die("Failed to register mixer element");
ret = snd_mixer_load(alsa_mix_handle);
if (ret < 0)
die("Failed to load mixer element");
debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl);
alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
if (!alsa_mix_elem)
die("Failed to find mixer element");
}
}
static int init(int argc, char **argv) {
// debug(1,"audio_alsa init called.");
const char *str;
int value;
int hardware_mixer = 0;
config.audio_backend_latency_offset = 0; // this is the default for ALSA
config.audio_backend_buffer_desired_length =
6615; // default for alsa with a software mixer
config.audio_backend_latency_offset = 0; // this is the default for ALSA
config.audio_backend_buffer_desired_length = 6615; // default for alsa with a software mixer
// get settings from settings file first, allow them to be overridden by command line options
// get settings from settings file first, allow them to be overridden by
// command line options
if (config.cfg != NULL) {
/* Get the desired buffer size setting. */
if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length_software", &value)) {
if (config_lookup_int(config.cfg,
"alsa.audio_backend_buffer_desired_length", &value)) {
if ((value < 0) || (value > 66150))
die("Invalid alsa audio backend buffer desired length (software) \"%d\". It should be between 0 and "
die("Invalid alsa audio backend buffer desired length \"%d\". It "
"should be between 0 and "
"66150, default is 6615",
value);
else {
config.audio_backend_buffer_desired_length = value;
}
}
/* Get the latency offset. */
if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) {
if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset",
&value)) {
if ((value < -66150) || (value > 66150))
die("Invalid alsa audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0",
die("Invalid alsa audio backend buffer latency offset \"%d\". It "
"should be between -66150 and +66150, default is 0",
value);
else
config.audio_backend_latency_offset = value;
@@ -131,13 +163,13 @@ static int init(int argc, char **argv) {
alsa_out_dev = (char *)str;
}
/* Get the Mixer Type setting. */
if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) {
inform("The alsa mixer_type setting is deprecated and has been ignored. FYI, using the \"mixer_control_name\" setting automatically chooses a hardware mixer.");
inform("The alsa mixer_type setting is deprecated and has been ignored. "
"FYI, using the \"mixer_control_name\" setting automatically "
"chooses a hardware mixer.");
}
/* Get the Mixer Device Name. */
if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) {
@@ -163,7 +195,9 @@ static int init(int argc, char **argv) {
break;
case 't':
inform("The alsa backend -t option is deprecated and has been ignored. FYI, using the -c option automatically chooses a hardware mixer.");
inform("The alsa backend -t option is deprecated and has been ignored. "
"FYI, using the -c option automatically chooses a hardware "
"mixer.");
break;
case 'm':
@@ -184,8 +218,8 @@ static int init(int argc, char **argv) {
if (optind < argc)
die("Invalid audio argument: %s", argv[optind]);
debug(1,"Output device name is \"%s\".",alsa_out_dev);
debug(1, "Output device name is \"%s\".", alsa_out_dev);
if (!hardware_mixer)
return 0;
@@ -193,74 +227,58 @@ static int init(int argc, char **argv) {
if (alsa_mix_dev == NULL)
alsa_mix_dev = alsa_out_dev;
int ret = 0;
snd_mixer_selem_id_alloca(&alsa_mix_sid);
snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl);
// Open mixer
if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0)
die("Failed to open mixer");
debug(1,"Mixer device name is \"%s\".",alsa_mix_dev);
if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0)
die("Failed to attach mixer");
if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0)
die("Failed to register mixer element");
open_mixer();
ret = snd_mixer_load(alsa_mix_handle);
if (ret < 0)
die("Failed to load mixer element");
debug(1,"Mixer Control name is \"%s\".",alsa_mix_ctrl);
alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
if (!alsa_mix_elem)
die("Failed to find mixer element");
if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) < 0)
if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv,
&alsa_mix_maxv) < 0)
debug(1, "Can't read mixer's [linear] min and max volumes.");
else {
if (snd_mixer_selem_get_playback_dB_range (alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
&alsa_mix_maxdb) == 0) {
audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
audio_alsa.volume =
&volume; // insert the volume function now we know it can do dB stuff
audio_alsa.parameters = &parameters; // likewise the parameters stuff
if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
// Raspberry Pi does this
debug(1, "Lowest dB value is a mute.");
if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
&alsa_mix_mindb) == 0)
if (snd_mixer_selem_ask_playback_vol_dB(
alsa_mix_elem, alsa_mix_minv + 1, &alsa_mix_mindb) == 0)
debug(1, "Can't get dB value corresponding to a \"volume\" of 1.");
}
debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
(1.0 * alsa_mix_maxdb) / 100.0);
if (config.volume_range_db) {
long suggested_alsa_min_db = alsa_mix_maxdb - (long)trunc(config.volume_range_db*100);
if (suggested_alsa_min_db > alsa_mix_mindb)
alsa_mix_mindb = suggested_alsa_min_db;
else
inform("The volume_range_db setting, %f is greater than the native range of the mixer %f, so it is ignored.",config.volume_range_db,(alsa_mix_maxdb-alsa_mix_mindb)/100.0);
}
debug(1, "Hardware mixer has dB volume from %f to %f.",
(1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
} else {
// use the linear scale and do the db conversion ourselves
debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have a dB volume scale, so it can't be used.",alsa_mix_ctrl);
debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have "
"a dB volume scale, so it can't be used.",
alsa_mix_ctrl);
/*
debug(1, "Min and max volumes are %d and %d.",alsa_mix_minv,alsa_mix_maxv);
debug(1, "Min and max volumes are %d and
%d.",alsa_mix_minv,alsa_mix_maxv);
alsa_mix_maxdb = 0;
if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
alsa_mix_mindb = -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
alsa_mix_mindb =
-20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
else if (alsa_mix_maxv!=0)
alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
audio_alsa.volume = &linear_volume; // insert the linear volume function
audio_alsa.parameters = &parameters; // likewise the parameters stuff
debug(1,"Max and min dB calculated are %d and %d.",alsa_mix_maxdb,alsa_mix_mindb);
debug(1,"Max and min dB calculated are %d and
%d.",alsa_mix_maxdb,alsa_mix_mindb);
*/
}
}
}
if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) {
has_mute = 1;
audio_alsa.mute =
&mute; // insert the mute function now we know it can do muting stuff
debug(1, "Has mute ability.");
}
snd_mixer_close(alsa_mix_handle);
alsa_mix_handle = NULL;
return 0;
}
@@ -272,32 +290,104 @@ static void deinit(void) {
}
int open_alsa_device(void) {
const snd_pcm_uframes_t minimal_buffer_headroom =
352 * 2; // we accept this much headroom in the hardware buffer, but we'll
// accept less
const snd_pcm_uframes_t requested_buffer_headroom =
minimal_buffer_headroom + 2048; // we ask for this much headroom in the
// hardware buffer, but we'll accept less
int ret, dir = 0;
unsigned int my_sample_rate = desired_sample_rate;
snd_pcm_uframes_t frames = 441 * 10;
snd_pcm_uframes_t buffer_size = frames * 4;
// snd_pcm_uframes_t frames = 441 * 10;
snd_pcm_uframes_t buffer_size, actual_buffer_length;
ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0);
if (ret < 0)
return (ret);
// die("Alsa initialization failed: unable to open pcm device: %s.", snd_strerror(ret));
// die("Alsa initialization failed: unable to open pcm device: %s.",
// snd_strerror(ret));
snd_pcm_hw_params_alloca(&alsa_params);
snd_pcm_hw_params_any(alsa_handle, alsa_params);
snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(alsa_handle, alsa_params, SND_PCM_FORMAT_S16);
snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2);
snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir);
// snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &frames, &dir);
// snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size);
ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
if (ret < 0) {
die("audio_alsa: Broken configuration for device \"%s\": no configurations "
"available",
alsa_out_dev);
}
ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (ret < 0) {
die("audio_alsa: Access type not available for device \"%s\": %s",
alsa_out_dev, snd_strerror(ret));
}
ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params,
SND_PCM_FORMAT_S16);
if (ret < 0) {
die("audio_alsa: Sample format not available for device \"%s\": %s",
alsa_out_dev, snd_strerror(ret));
}
ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2);
if (ret < 0) {
die("audio_alsa: Channels count (2) not available for device \"%s\": %s",
alsa_out_dev, snd_strerror(ret));
}
ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params,
&my_sample_rate, &dir);
if (ret < 0) {
die("audio_alsa: Rate %iHz not available for playback: %s",
desired_sample_rate, snd_strerror(ret));
}
// snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &frames,
// &dir);
// snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params,
// &buffer_size);
ret = snd_pcm_hw_params(alsa_handle, alsa_params);
if (ret < 0) {
die("unable to set hw parameters: %s.", snd_strerror(ret));
die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.",
alsa_out_dev, snd_strerror(ret));
}
if (my_sample_rate != desired_sample_rate) {
die("Can't set the D/A converter to %d -- set to %d instead./n", desired_sample_rate,
my_sample_rate);
die("Can't set the D/A converter to %d.", desired_sample_rate);
}
ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length);
if (ret < 0) {
die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.",
alsa_out_dev, snd_strerror(ret));
}
if (actual_buffer_length <
config.audio_backend_buffer_desired_length + minimal_buffer_headroom) {
// the dac buffer is too small, so let's try to set it
buffer_size =
config.audio_backend_buffer_desired_length + requested_buffer_headroom;
ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params,
&buffer_size);
if (ret < 0)
die("audio_alsa: Unable to set hw buffer size to %lu for device \"%s\": "
"%s.",
config.audio_backend_buffer_desired_length +
requested_buffer_headroom,
alsa_out_dev, snd_strerror(ret));
if (config.audio_backend_buffer_desired_length + minimal_buffer_headroom >
buffer_size) {
die("audio_alsa: Can't set hw buffer size to %lu or more for device "
"\"%s\". Requested size: %lu, granted size: %lu.",
config.audio_backend_buffer_desired_length + minimal_buffer_headroom,
alsa_out_dev, config.audio_backend_buffer_desired_length +
requested_buffer_headroom,
buffer_size);
}
}
return (0);
}
@@ -319,8 +409,8 @@ static uint32_t delay() {
// current_avail not used
if (derr != 0) {
ignore = snd_pcm_recover(alsa_handle, derr, 0);
debug(1, "Error %d in delay(): %s. Delay reported is %d frames.", derr, snd_strerror(derr),
current_delay);
debug(1, "Error %d in delay(): %s. Delay reported is %d frames.", derr,
snd_strerror(derr), current_delay);
current_delay = -1;
}
} else if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) {
@@ -330,7 +420,8 @@ static uint32_t delay() {
current_delay = 0;
else {
current_delay = -1;
debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle));
debug(1, "Error -- ALSA delay(): bad state: %d.",
snd_pcm_state(alsa_handle));
}
if ((derr = snd_pcm_prepare(alsa_handle))) {
ignore = snd_pcm_recover(alsa_handle, derr, 0);
@@ -347,6 +438,7 @@ static void play(short buf[], int samples) {
int ret = 0;
if (alsa_handle == NULL) {
ret = open_alsa_device();
open_mixer();
if ((ret == 0) && (audio_alsa.volume))
audio_alsa.volume(set_volume);
}
@@ -359,7 +451,8 @@ static void play(short buf[], int samples) {
err = snd_pcm_writei(alsa_handle, (char *)buf, samples);
if (err < 0) {
ignore = snd_pcm_recover(alsa_handle, err, 0);
debug(1, "Error %d writing %d samples in play() %s.", err, samples, snd_strerror(err));
debug(1, "Error %d writing %d samples in play() %s.", err, samples,
snd_strerror(err));
}
} else {
debug(1, "Error -- ALSA device in incorrect state (%d) for play.",
@@ -375,6 +468,10 @@ static void play(short buf[], int samples) {
static void flush(void) {
int derr;
if (alsa_mix_handle) {
snd_mixer_close(alsa_mix_handle);
alsa_mix_handle = NULL;
}
if (alsa_handle) {
// debug(1,"Dropping frames for flush...");
if ((derr = snd_pcm_drop(alsa_handle)))
@@ -391,7 +488,8 @@ static void flush(void) {
*/
if (!((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
(snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)))
debug(1, "Flush returning unexpected state -- %d.", snd_pcm_state(alsa_handle));
debug(1, "Flush returning unexpected state -- %d.",
snd_pcm_state(alsa_handle));
// flush also closes the device
snd_pcm_close(alsa_handle);
@@ -402,42 +500,55 @@ static void flush(void) {
static void stop(void) {
if (alsa_handle != 0)
// when we want to stop, we want the alsa device
// to be closed immediately -- we may even be killing the thread, so we don't wish to wait
// to be closed immediately -- we may even be killing the thread, so we
// don't wish to wait
// so we should flush first
flush(); // flush will also close the device
// close_alsa_device();
}
static void parameters(audio_parameters *info) {
info->has_true_mute = has_mute;
info->is_muted = ((has_mute) && (set_volume == -144.0));
info->minimum_volume_dB = alsa_mix_mindb;
info->maximum_volume_dB = alsa_mix_maxdb;
info->airplay_volume = set_volume;
info->current_volume_dB = vol2attn(set_volume, alsa_mix_maxdb, alsa_mix_mindb);
}
static void volume(double vol) {
// debug(1,"Volume called %f.",vol);
debug(2, "Setting volume db to %f.", vol);
set_volume = vol;
double vol_setting = vol2attn(vol, alsa_mix_maxdb, alsa_mix_mindb);
// debug(1,"Setting volume db to %f, for volume input of %f.",vol_setting/100,vol);
if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol_setting, -1) != 0)
die("Failed to set playback dB volume");
if (has_mute)
snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0));
if (alsa_mix_handle) {
if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 0) != 0) {
debug(1, "Can't set playback volume accurately to %f dB.", vol);
if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, -1) != 0)
if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 1) != 0)
die("Failed to set playback dB volume");
}
}
}
static void linear_volume(double vol) {
debug(2, "Setting linear volume to %f.", vol);
set_volume = vol;
double vol_setting = vol2attn(vol, 0, alsa_mix_mindb)/2000;
// debug(1,"Adjusted volume is %f.",vol_setting);
double linear_volume = pow(10, vol_setting);
// debug(1,"Linear volume is %f.",linear_volume);
long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume;
// debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol);
if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0)
die("Failed to set playback volume");
if (has_mute)
snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0));
if (alsa_mix_handle) {
double linear_volume = pow(10, vol);
// debug(1,"Linear volume is %f.",linear_volume);
long int_vol =
alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume;
// debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol);
if (alsa_mix_handle) {
if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0)
die("Failed to set playback volume");
}
}
}
static void mute(int do_mute) {
if (alsa_mix_handle) {
if (do_mute) {
// debug(1,"Mute");
snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0);
} else {
// debug(1,"Unmute");
snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1);
}
}
}
+32 -4
View File
@@ -41,17 +41,44 @@ static void help(void) {
}
static int init(int argc, char **argv) {
const char *str;
int value;
ao_initialize();
int driver = ao_default_driver_id();
ao_option *ao_opts = NULL;
config.audio_backend_buffer_desired_length = 44100; // one second.
config.audio_backend_latency_offset = 0;
// get settings from settings file first, allow them to be overridden by command line options
if (config.cfg != NULL) {
/* Get the desired buffer size setting. */
if (config_lookup_int(config.cfg, "ao.audio_backend_buffer_desired_length", &value)) {
if ((value < 0) || (value > 66150))
die("Invalid a0 audio backend buffer desired length \"%d\". It should be between 0 and "
"66150, default is 44100",
value);
else {
config.audio_backend_buffer_desired_length = value;
}
}
/* Get the latency offset. */
if (config_lookup_int(config.cfg, "ao.audio_backend_latency_offset", &value)) {
if ((value < -66150) || (value > 66150))
die("Invalid ao audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0",
value);
else
config.audio_backend_latency_offset = value;
}
}
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
argc++;
config.audio_backend_buffer_desired_length = 44100; // one second.
config.audio_backend_latency_offset = 0;
// some platforms apparently require optreset = 1; - which?
int opt;
char *mid;
@@ -126,4 +153,5 @@ audio_output audio_ao = {.name = "ao",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+2 -1
View File
@@ -60,4 +60,5 @@ audio_output audio_dummy = {.name = "dummy",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+2 -1
View File
@@ -133,4 +133,5 @@ audio_output audio_pipe = {.name = "pipe",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+2 -1
View File
@@ -137,4 +137,5 @@ audio_output audio_pulse = {.name = "pulse",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+2 -1
View File
@@ -81,4 +81,5 @@ audio_output audio_sndio = {.name = "sndio",
.delay = NULL,
.play = &play,
.volume = &volume,
.parameters = NULL};
.parameters = NULL,
.mute= NULL};
+2 -1
View File
@@ -96,4 +96,5 @@ audio_output audio_stdout = {.name = "stdout",
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL};
.parameters = NULL,
.mute = NULL};
+1 -1
View File
@@ -474,7 +474,7 @@ double vol2attn(double vol, long max_db, long min_db) {
uint64_t get_absolute_time_in_fp() {
uint64_t time_now_fp;
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN
struct timespec tn;
// can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
clock_gettime(CLOCK_MONOTONIC, &tn);
+10 -3
View File
@@ -18,9 +18,9 @@
#endif
#endif
#if defined(__linux__) || defined(__FreeBSD__)
#if defined(__linux__) || defined(__FreeBSD__) || defined(__CYGWIN__)
/* Linux and FreeBSD */
#define COMPILE_FOR_LINUX_AND_FREEBSD 1
#define COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN 1
#endif
// struct sockaddr_in6 is bigger than struct sockaddr. derp
@@ -32,6 +32,12 @@
#define SAFAMILY sa_family
#endif
enum endian_type {
SS_LITTLE_ENDIAN = 0,
SS_PDP_ENDIAN,
SS_BIG_ENDIAN,
} endian_type;
enum stuffing_type {
ST_basic = 0,
ST_soxr,
@@ -116,12 +122,13 @@ uint64_t get_absolute_time_in_fp(void);
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
long endianness;
uint32_t uatoi(const char *nptr);
shairport_cfg config;
config_t config_file_stuff;
uint32_t buffer_occupancy;
int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative
int64_t session_corrections;
uint32_t play_segment_reference_frame;
uint64_t play_segment_reference_frame_remote_time;
+34 -10
View File
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
AC_INIT([shairport-sync], [2.6], [mikebrady@eircom.net])
AC_INIT([shairport-sync], [2.7.10], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
@@ -164,7 +164,7 @@ AC_ARG_WITH(tinysvcmdns, [ --with-tinysvcmdns = choose tinysvcmdns-based mDNS s
AM_CONDITIONAL([USE_TINYSVCMDNS], [test "x$HAS_TINYSVCMDNS" = "x1"])
# Look for ALSA flag
AC_ARG_WITH(alsa, [ --with-alsa = choose native ALSA API support (GNU/Linux only)],
AC_ARG_WITH(alsa, [ --with-alsa = choose ALSA API support (GNU/Linux only)],
[AC_MSG_RESULT(>>Including an ALSA back end)
HAS_ALSA=1
AM_CONDITIONAL([USE_ALSA], [test 0])
@@ -181,27 +181,27 @@ AC_ARG_WITH(alsa, [ --with-alsa = choose native ALSA API support (GNU/Linux onl
AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"])
# Look for SNDIO flag
AC_ARG_WITH(sndio, [ --with-sndio = choose native SNDIO API support (FreeBSD) -- probably broken], [
AC_MSG_RESULT(>>Including a SNDIO back end --- N.B. this is probably broken!)
AC_ARG_WITH(sndio, [ --with-sndio = choose SNDIO API support (FreeBSD) -- probably broken], [
AC_MSG_RESULT(>>Including a SNDIO back end -- N.B. this is probably broken!)
HAS_SNDIO=1
AC_DEFINE([CONFIG_SNDIO], 1, [Needed by the compiler.])
AC_CHECK_LIB([sndio], [sio_open], , AC_MSG_ERROR(SNDIO support requires the sndio library!))], )
AM_CONDITIONAL([USE_SNDIO], [test "x$HAS_SNDIO" = "x1"])
# Look for AO flag
AC_ARG_WITH(ao, [ --with-ao = choose native AO (Audio Output?) API support -- probably broken], [
AC_MSG_RESULT(>>Including an AO back end --- N.B. this is probably broken!)
AC_ARG_WITH(ao, [ --with-ao = choose AO (Audio Output?) API support. N.B. no synchronisation -- so underflow or overflow is inevitable!], [
AC_MSG_RESULT(>>Including an AO back end. N.B. no synchronisation -- so underflow or overflow is inevitable!)
HAS_AO=1
AC_DEFINE([CONFIG_AO], 1, [Needed by the compiler.])
AC_CHECK_LIB([ao], [ao_initialize], , AC_MSG_ERROR(AO support requires the ao library!))], )
AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"])
# Look for pulseaudio flag
AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose native PulseAudio API support -- N.B. no synchronisation -- so underflow or overflow is inevitable!], [
AC_MSG_RESULT(>>Including a PulseAudio back end -- N.B. no synchronisation -- so underflow or overflow is inevitable!)
AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose PulseAudio API support. N.B. no synchronisation -- so underflow or overflow is inevitable!], [
AC_MSG_RESULT(>>Including a PulseAudio back end. N.B. no synchronisation -- so underflow or overflow is inevitable!)
HAS_PULSE=1
AC_DEFINE([CONFIG_PULSE], 1, [Needed by the compiler.])
AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the pulse-simple library!))
AC_CHECK_LIB([pulse-simple], [pa_simple_new], , AC_MSG_ERROR(PulseAudio support requires the pulse-simple library, possibly in the package libpulse-dev!))
AC_CHECK_LIB([pulse], [pa_stream_peek], , AC_MSG_ERROR(PulseAudio support requires the libpulse-dev library!))], )
AM_CONDITIONAL([USE_PULSE], [test "x$HAS_PULSE" = "x1"])
@@ -213,6 +213,29 @@ AC_ARG_WITH(dns_sd, [ --with-dns_sd = choose dns_sd mDNS support], [
AC_SEARCH_LIBS([DNSServiceRefDeallocate], [dns_sd], , AC_MSG_ERROR(dns_sd support requires the dns_sd library!))], )
AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"])
# Find systemd unit dir
AC_ARG_WITH([systemdsystemunitdir],
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
[with_systemdsystemunitdir=auto])
AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [
def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
AS_IF([test "x$def_systemdsystemunitdir" = "x"],
[AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
[AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])])
with_systemdsystemunitdir=no],
[with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
[AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
# Look for xmltoman
AC_CHECK_PROGS([XMLTOMAN], [xmltoman])
if test -z "$XMLTOMAN"; then
AC_MSG_WARN([>>xmltoman not found - not rebuilding man pages])
fi
AM_CONDITIONAL([HAVE_XMLTOMAN], [test -n "$XMLTOMAN"])
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([getopt_long.h])
@@ -239,5 +262,6 @@ AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([atexit clock_gettime gethostname inet_ntoa memchr memmove memset mkfifo pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul])
AC_CONFIG_FILES([Makefile man/Makefile])
AC_CONFIG_FILES([Makefile man/Makefile scripts/shairport-sync.service])
AC_CONFIG_FILES([scripts/shairport-sync],[chmod +x scripts/shairport-sync])
AC_OUTPUT
+10
View File
@@ -1 +1,11 @@
man_MANS = shairport-sync.7
if HAVE_XMLTOMAN
all-local: shairport-sync.html
shairport-sync.7: shairport-sync.7.xml
xmltoman $< > $@
shairport-sync.html: shairport-sync.7.xml
xmlmantohtml $< > $@
endif
+15 -2
View File
@@ -53,7 +53,7 @@ Most settings have sensible default values, so -- as in the example above -- use
A sample configuration file with all possible settings, but with all of them commented out, is installed at \fI/etc/shairport-sync.conf.sample\f1.
To retain backwards compatability with previous versions of shairport-sync you can use still use command line options, but any new features, etc. will be available only via configuration file settings.
To retain backwards compatibility with previous versions of shairport-sync you can use still use command line options, but any new features, etc. will be available only via configuration file settings.
The configuration file is processed using the \fIlibconfig\f1 library -- see \fBhttp://www.hyperrealm.com/libconfig/libconfig_manual.html\f1.
.TP
@@ -153,6 +153,19 @@ Packets of audio frames are written to stdout synchronously -- that is, they are
\fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1
Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to stdout. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the stdout reader consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100 frames.
.TP
\fB"AO" SETTINGS\f1
These settings are for the AO backend, used for the libao audio library.
There are two settings affecting timing. The \fIaudio_backend_latency_offset\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length\f1 setting affects the nominal output buffer size.
These are the settings available within the \fBao\f1 group:
.TP
\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1
Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.
.TP
\fBaudio_backend_buffer_desired_length=\f1\fIbuffer_length_in_frames\f1\fB;\f1
Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of audio frames are sent to to the libao system. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 44,100 frames.
.TP
\fB"METADATA" SETTINGS\f1
shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It sends the metadata to a pipe, by default \fI/tmp/shairport-sync-metadata\f1. To process metadata, shairport-sync must have been compiled with metadata support included. You can check that this is so by running \fBshairport-sync -V\f1; the identification string will contain the word \fBmetadata\f1.
@@ -210,7 +223,7 @@ This is the \fIlatency\f1, in frames, used for forkedDaapd sources. Default is 9
\fBdefault=\f1\fIlatency\f1\fB;\f1
This is the \fIlatency\f1, in frames, used when the source is unrecognised. Default is 88,200.
.SH OPTIONS
Note: if you are setting up Shairport Sync for the first time or are updating an existing installation, you are encouraged to use the configuration file settings described above. Most of the options described below simply replicate the configuration settings and are retained to provide backward compatability with older installations of Shairport Sync.
Note: if you are setting up Shairport Sync for the first time or are updating an existing installation, you are encouraged to use the configuration file settings described above. Most of the options described below simply replicate the configuration settings and are retained to provide backward compatibility with older installations of Shairport Sync.
Many of the options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages.
+31 -4
View File
@@ -106,7 +106,7 @@
<p>A sample configuration file with all possible settings, but with all of them commented out, is installed at <file>/etc/shairport-sync.conf.sample</file>.</p>
<p>To retain backwards compatability with previous versions of shairport-sync
<p>To retain backwards compatibility with previous versions of shairport-sync
you can use still use command line options, but any new features, etc. will
be available only via configuration file settings.</p>
@@ -295,7 +295,7 @@
You can offset the time of initial audio output relative to its nominal time using this setting.
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.</optdesc>
</option>
<option>
<p><opt>audio_backend_buffer_desired_length=</opt><arg>buffer_length_in_frames</arg><opt>;</opt></p>
<optdesc>
@@ -306,7 +306,34 @@
shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100 frames.
</optdesc>
</option>
<option><p><opt>"AO" SETTINGS</opt></p></option>
<p>These settings are for the AO backend, used for the libao audio library.</p>
<p>There are two settings affecting timing. The <arg>audio_backend_latency_offset</arg> affects precisely when the first audio packet is sent
and the <arg>audio_backend_buffer_desired_length</arg> setting affects the nominal output buffer size.</p>
<p>These are the settings available within the <opt>ao</opt> group:</p>
<option>
<p><opt>audio_backend_latency_offset=</opt><arg>offset_in_frames</arg><opt>;</opt></p>
<optdesc>
Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played.
You can offset the time of initial audio output relative to its nominal time using this setting.
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.</optdesc>
</option>
<option>
<p><opt>audio_backend_buffer_desired_length=</opt><arg>buffer_length_in_frames</arg><opt>;</opt></p>
<optdesc>
Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of
audio frames are sent to to the libao system.
For example, if you send the first packet of audio exactly when it is due and, using a <arg>audio_backend_buffer_desired_length</arg> setting of 44100,
send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames.
Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow --
shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 44,100 frames.
</optdesc>
</option>
<option><p><opt>"METADATA" SETTINGS</opt></p></option>
<p>shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level,
pause/resume, etc. It sends the metadata to a pipe, by default <file>/tmp/shairport-sync-metadata</file>.
@@ -406,7 +433,7 @@
<p>Note: if you are setting up Shairport Sync for the first time or are updating an existing installation,
you are encouraged to use the configuration file settings described above. Most of the options described below
simply replicate the configuration settings and are retained to provide backward compatability with older installations of Shairport Sync.</p>
simply replicate the configuration settings and are retained to provide backward compatibility with older installations of Shairport Sync.</p>
<p>Many of the options take sensible default values, so you can normally
ignore most of them. See the EXAMPLES section for typical usages.</p>
+31 -4
View File
@@ -88,7 +88,7 @@
<p>A sample configuration file with all possible settings, but with all of them commented out, is installed at <em>/etc/shairport-sync.conf.sample</em>.</p>
<p>To retain backwards compatability with previous versions of shairport-sync
<p>To retain backwards compatibility with previous versions of shairport-sync
you can use still use command line options, but any new features, etc. will
be available only via configuration file settings.</p>
@@ -277,7 +277,7 @@
You can offset the time of initial audio output relative to its nominal time using this setting.
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.
<p><b>audio_backend_buffer_desired_length=</b><em>buffer_length_in_frames</em><b>;</b></p>
@@ -288,7 +288,34 @@
shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 44,100 frames.
<p><b>&quot;AO&quot; SETTINGS</b></p>
<p>These settings are for the AO backend, used for the libao audio library.</p>
<p>There are two settings affecting timing. The <em>audio_backend_latency_offset</em> affects precisely when the first audio packet is sent
and the <em>audio_backend_buffer_desired_length</em> setting affects the nominal output buffer size.</p>
<p>These are the settings available within the <b>ao</b> group:</p>
<p><b>audio_backend_latency_offset=</b><em>offset_in_frames</em><b>;</b></p>
Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played.
You can offset the time of initial audio output relative to its nominal time using this setting.
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.
<p><b>audio_backend_buffer_desired_length=</b><em>buffer_length_in_frames</em><b>;</b></p>
Use this setting, in frames, to set the size of the output buffer. It works by determining how soon the second and subsequent packets of
audio frames are sent to to the libao system.
For example, if you send the first packet of audio exactly when it is due and, using a <em>audio_backend_buffer_desired_length</em> setting of 44100,
send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames.
Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow --
shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 44,100 frames.
<p><b>&quot;METADATA&quot; SETTINGS</b></p>
<p>shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level,
pause/resume, etc. It sends the metadata to a pipe, by default <em>/tmp/shairport-sync-metadata</em>.
@@ -390,7 +417,7 @@
<p>Note: if you are setting up Shairport Sync for the first time or are updating an existing installation,
you are encouraged to use the configuration file settings described above. Most of the options described below
simply replicate the configuration settings and are retained to provide backward compatability with older installations of Shairport Sync.</p>
simply replicate the configuration settings and are retained to provide backward compatibility with older installations of Shairport Sync.</p>
<p>Many of the options take sensible default values, so you can normally
ignore most of them. See the EXAMPLES section for typical usages.</p>
+221 -86
View File
@@ -81,7 +81,7 @@ static int sampling_rate, frame_size;
static aes_context dctx;
#endif
static pthread_t player_thread;
//static pthread_t player_thread = NULL;
static int please_stop;
static int encrypted; // Normally the audio is encrypted, but it may not be
@@ -95,7 +95,6 @@ static uint64_t packet_count = 0;
static int32_t last_seqno_read;
// interthread variables
static double software_mixer_volume = 1.0;
static int fix_volume = 0x10000;
static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -243,8 +242,13 @@ static void alac_decode(short *dest, uint8_t *buf, int len) {
} else {
alac_decode_frame(decoder_info, buf, dest, &outsize);
}
assert(outsize == FRAME_BYTES(frame_size));
if (outsize!=FRAME_BYTES(frame_size)) {
if(outsize<FRAME_BYTES(frame_size)) {
debug(1,"Output from alac_decode is smaller than expected. Encrypted = %d.",encrypted);
} else {
debug(1,"OUtput from alac_decode larger than expected -- truncated, but buffer overflow possible! Encrypted = %d.",encrypted);
}
}
}
static int init_decoder(int32_t fmtp[12]) {
@@ -304,7 +308,7 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len)
if ((flush_rtp_timestamp != 0) &&
((timestamp == flush_rtp_timestamp) || seq32_order(timestamp, flush_rtp_timestamp))) {
debug(2, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %u, flushing to "
debug(3, "Dropping flushed packet in player_put_packet, seqno %u, timestamp %u, flushing to "
"timestamp: %u.",
seqno, timestamp, flush_rtp_timestamp);
} else {
@@ -328,7 +332,7 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len)
// if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8)
// debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and seqno:
// %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno);
int32_t gap = seq_diff(ab_write, PREDECESSOR(seqno)) + 1;
int32_t gap = seq_diff(ab_write, seqno);
if (gap <= 0)
debug(1, "Unexpected gap size: %d.", gap);
int i;
@@ -340,8 +344,8 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len)
}
// debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write);
abuf = audio_buffer + BUFIDX(seqno);
rtp_request_resend(ab_write, gap);
resend_requests++;
// rtp_request_resend(ab_write, gap);
// resend_requests++;
ab_write = SUCCESSOR(seqno);
} else if (seq_order(ab_read, seqno)) { // late but not yet played
late_packets++;
@@ -374,22 +378,44 @@ void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len)
pthread_mutex_unlock(&ab_mutex);
}
static inline short lcg_rand(void) {
static unsigned long lcg_prev = 12345;
lcg_prev = lcg_prev * 69069 + 3;
return lcg_prev & 0xffff;
int32_t rand_in_range(int32_t exclusive_range_limit) {
static uint32_t lcg_prev = 12345;
// returns a pseudo random integer in the range 0 to (exclusive_range_limit-1) inclusive
int64_t sp = lcg_prev;
int64_t rl = exclusive_range_limit;
lcg_prev = lcg_prev * 69069 + 3; // crappy psrg
sp = sp*rl; // 64 bit calculation. INtersting part if above the 32 rightmost bits;
return sp >> 32;
}
static inline short dithered_vol(short sample) {
short rand_a, rand_b;
long out;
out = (long)sample * fix_volume;
if (fix_volume < 0x10000) {
rand_b = rand_a;
rand_a = lcg_rand();
out += rand_a;
out -= rand_b;
// add a TPDF dither -- see http://www.users.qwest.net/%7Evolt42/cadenzarecording/DitherExplained.pdf
// and the discussion around https://www.hydrogenaud.io/forums/index.php?showtopic=16963&st=25
// I think, for a 32 --> 16 bits, the range of
// random numbers needs to be from -2^16 to 2^16, i.e. from -65536 to 65536 inclusive, not from -32768 to +32767
// See the original paper at http://www.ece.rochester.edu/courses/ECE472/resources/Papers/Lipshitz_1992.pdf
// by Lipshitz, Wannamaker and Vanderkooy, 1992.
long tpdf = rand_in_range(65536+1) - rand_in_range(65536+1);
// Check there's no clipping -- if there is,
if (tpdf>=0) {
if (LONG_MAX-tpdf>=out)
out += tpdf;
else
out = LONG_MAX;
} else {
if (LONG_MIN-tpdf<=out)
out += tpdf;
else
out = LONG_MIN;
}
}
return out >> 16;
}
@@ -452,7 +478,7 @@ static abuf_t *buffer_get_frame(void) {
if (ab_synced) {
do {
curframe = audio_buffer + BUFIDX(ab_read);
if (curframe->ready) {
if ((ab_read!=ab_write) && (curframe->ready)) { // it could be synced and empty, under exceptional circumstances, with the frame unused, thus apparently ready
if (curframe->sequence_number != ab_read) {
// some kind of sync problem has occurred.
@@ -553,9 +579,8 @@ static abuf_t *buffer_get_frame(void) {
<< 32) /
44100;
uint32_t filler_size = frame_size;
uint32_t max_dac_delay = 4410;
filler_size = 4410; // 0.1 second -- the maximum we'll add to the DAC
uint32_t filler_size = max_dac_delay; // 0.1 second -- the maximum we'll add to the DAC
if (local_time_now >= first_packet_time_to_play) {
// we've gone past the time...
@@ -675,7 +700,7 @@ static abuf_t *buffer_get_frame(void) {
time_to_wait_for_wakeup_fp *= 4 * 352; // four full 352-frame packets
time_to_wait_for_wakeup_fp /= 3; // four thirds of a packet time
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN
uint64_t time_of_wakeup_fp = local_time_now + time_to_wait_for_wakeup_fp;
uint64_t sec = time_of_wakeup_fp >> 32;
uint64_t nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
@@ -765,14 +790,14 @@ static int stuff_buffer_basic(short *inptr, short *outptr, int stuff) {
};
if (stuff) {
if (stuff == 1) {
debug(3, "+++++++++");
// debug(3, "+++++++++");
// interpolate one sample
//*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1);
//*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1);
*outptr++ = dithered_vol(shortmean(inptr[-2], inptr[0]));
*outptr++ = dithered_vol(shortmean(inptr[-1], inptr[1]));
} else if (stuff == -1) {
debug(3, "---------");
// debug(3, "---------");
inptr++;
inptr++;
}
@@ -838,7 +863,7 @@ static int stuff_buffer_soxr(short *inptr, short *outptr, int stuff) {
}
// finally, adjust the volume, if necessary
if (software_mixer_volume != 1.0) {
if (fix_volume != 65536.0) {
// pthread_mutex_lock(&vol_mutex);
op = outptr;
for (i = 0; i < frame_size + stuff; i++) {
@@ -868,6 +893,13 @@ typedef struct stats { // statistics for running averages
} stats_t;
static void *player_thread_func(void *arg) {
int threads_stop = 0;
// create and start the timing, control and audio receiver threads
pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, (void *)&threads_stop);
pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, (void *)&threads_stop);
pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, (void *)&threads_stop);
session_corrections = 0;
play_segment_reference_frame = 0; // zero signals that we are not in a play segment
// check that there are enough buffers to accommodate the desired latency and the latency offset
@@ -888,7 +920,6 @@ static void *player_thread_func(void *arg) {
int32_t minimum_buffer_occupancy = BUFFER_FRAMES;
int32_t maximum_buffer_occupancy = 0;
audio_information.valid = 0;
buffer_occupancy = 0;
int play_samples;
@@ -1171,7 +1202,7 @@ static void *player_thread_func(void *arg) {
inform("Sync error: %.1f (frames); net correction: %.1f (ppm); corrections: %.1f "
"(ppm); missing packets %llu; late packets %llu; too late packets %llu; "
"resend requests %llu; min DAC queue size %lli, min and max buffer occupancy "
"%u and %u.",
"%d and %d.",
moving_average_sync_error, moving_average_correction * 1000000 / 352,
moving_average_insertions_plus_deletions * 1000000 / 352, missing_packets,
late_packets, too_late_packets, resend_requests, minimum_dac_queue_size,
@@ -1179,7 +1210,7 @@ static void *player_thread_func(void *arg) {
else
inform("Synchronisation disabled. Missing packets %llu; late packets %llu; too late packets %llu; "
"resend requests %llu; min and max buffer occupancy "
"%u and %u.",
"%d and %d.",
missing_packets,
late_packets, too_late_packets, resend_requests,
minimum_buffer_occupancy, maximum_buffer_occupancy);
@@ -1195,80 +1226,179 @@ static void *player_thread_func(void *arg) {
}
}
}
if (config.output->stop)
config.output->stop();
free(outbuf);
free(silence);
debug(1,"Shut down audio, control and timing threads");
// usleep(1000000);
pthread_kill(rtp_audio_thread, SIGUSR1);
pthread_kill(rtp_control_thread, SIGUSR1);
pthread_kill(rtp_timing_thread, SIGUSR1);
pthread_join(rtp_timing_thread, NULL);
debug(1,"timing thread joined");
pthread_join(rtp_audio_thread, NULL);
debug(1,"audio thread joined");
pthread_join(rtp_control_thread, NULL);
debug(1,"control thread joined");
debug(1,"Player thread exit");
return 0;
}
// takes the volume as specified by the airplay protocol
void player_volume(double f) {
void player_volume(double airplay_volume) {
// The volume ranges -144.0 (mute) or -30 -- 0. See
// http://git.zx2c4.com/Airtunes2/about/#setting-volume
// By examination, the -30 -- 0 range is linear on the slider; i.e. the slider is calibrated in 30
// equal increments. Since the human ear's response is roughly logarithmic, we imagine these to
// be power dB, i.e. from -30dB to 0dB.
// We may have a hardware mixer, and if so, we will give it priority.
// If a desired volume range is given, then we will try to accommodate it from
// the top of the hardware mixer's range downwards.
// So, if we have a hardware mixer, we will pass this on to its dB volume settings.
// Without a hardware mixer, we have to do attenuation in software, so
// here, we ask for an attenuation we will apply to the signal amplitude in software.
// 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.
double linear_volume = 0.0;
if (config.output->volume) {
// debug(1,"Set volume to %f.",f);
config.output->volume(f); // volume will be sent as metadata by the config.output device
linear_volume = 1.0; // no attenuation needed -- this value is used as a flag to avoid calculations
}
if (config.output->parameters)
int32_t hw_min_db, hw_max_db, hw_range_db, range_to_use, min_db, max_db; // hw_range_db is a flag; if 0 means no mixer
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; // this is 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);
else
desired_range_db = 0;
if (config.output->parameters) {
// have a hardware mixer
config.output->parameters(&audio_information);
else {
long mindb = -9630;
if (config.volume_range_db) {
long suggested_alsa_min_db = -(long)trunc(config.volume_range_db*100);
if (suggested_alsa_min_db > mindb)
mindb = suggested_alsa_min_db;
else
inform("The volume_range_db setting, %f is greater than the native range of the mixer %f, so it is ignored.",config.volume_range_db,mindb/100.0);
}
double scaled_volume = vol2attn(f, 0, mindb);
linear_volume = pow(10, scaled_volume / 2000);
audio_information.airplay_volume = f;
audio_information.minimum_volume_dB = mindb;
audio_information.maximum_volume_dB = 0;
audio_information.current_volume_dB = scaled_volume;
audio_information.has_true_mute = 0;
audio_information.is_muted = 0;
// debug(1,"Minimum software volume set to %d centi-dB",f,mindb);
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;
}
audio_information.valid = 1;
// debug(1,"Software volume set to %f on scale with a %f dB",f,linear_volume);
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 {
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;
}
}
double hardware_attenuation, software_attenuation;
double scaled_attenuation = hw_min_db+sw_min_db;
// now, we can map the input to the desired output volume
if (airplay_volume==-144.0) {
// do a mute
if (config.output->mute) {
config.output->mute(1); // use real mute if it's there
software_attenuation = sw_min_db; // needed for when sound is unmuted; otherwise it might be very loud.
} else {
hardware_attenuation = hw_min_db;
software_attenuation = sw_min_db;
// debug(1,"Software mute.");
}
} else {
if (config.output->mute)
config.output->mute(0); // unmute mute if it's there
scaled_attenuation = vol2attn(airplay_volume, max_db, min_db);
if (hw_range_db) {
// if there is a hardware mixer
if (scaled_attenuation<=hw_max_db) {
// the attenuation is so low that's it's in the hardware mixer's range
// debug(1,"Attenuation all taken care of by the hardware mixer.");
hardware_attenuation = scaled_attenuation;
software_attenuation = sw_max_db - (max_db-hw_max_db); // e.g. if the hw_max_db is +4 and the max is +40, this will be -36 (all by 100, of course)
} else {
// debug(1,"Attenuation taken care of by hardware and software mixer.");
hardware_attenuation = hw_max_db; // the hardware mixer is turned up full
software_attenuation = sw_max_db - (max_db-scaled_attenuation);
}
} else {
// if there is no hardware mixer, the scaled_volume is the software volume
// debug(1,"Attenuation all taken care of by the software mixer.");
software_attenuation = scaled_attenuation;
}
}
if ((config.output->volume) && (hw_range_db)) {
config.output->volume(hardware_attenuation); // otherwise set the output to the lowest value
//debug(1,"Hardware attenuation set to %f for airplay volume of %f.",hardware_attenuation,airplay_volume);
}
double temp_fix_volume = 65536.0 * pow(10, software_attenuation / 2000);
// debug(1,"Software attenuation set to %f, i.e %f out of 65,536, for airplay volume of %f",software_attenuation,temp_fix_volume,airplay_volume);
pthread_mutex_lock(&vol_mutex);
software_mixer_volume = linear_volume;
fix_volume = 65536.0 * software_mixer_volume;
fix_volume = temp_fix_volume;
pthread_mutex_unlock(&vol_mutex);
#ifdef CONFIG_METADATA
char *dv = malloc(128); // will be freed in the metadata thread
if (dv) {
memset(dv, 0, 128);
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", audio_information.airplay_volume,
audio_information.current_volume_dB / 100.0,
audio_information.minimum_volume_dB / 100.0,
audio_information.maximum_volume_dB / 100.0);
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
}
void player_flush(uint32_t timestamp) {
// debug(1,"Flush requested up to %u. It seems as if 0 is special.",timestamp);
debug(3,"Flush requested up to %u. It seems as if 0 is special.",timestamp);
pthread_mutex_lock(&flush_mutex);
flush_requested = 1;
// if (timestamp!=0)
@@ -1280,7 +1410,9 @@ void player_flush(uint32_t timestamp) {
#endif
}
int player_play(stream_cfg *stream) {
int player_play(stream_cfg *stream, pthread_t *player_thread) {
//if (*player_thread!=NULL)
// die("Trying to create a second player thread for this RTSP session");
packet_count = 0;
encrypted = stream->encrypted;
if (config.buffer_start_fill > BUFFER_FRAMES)
@@ -1307,7 +1439,7 @@ int player_play(stream_cfg *stream) {
#endif
// set the flowcontrol condition variable to wait on a monotonic clock
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN
pthread_condattr_t attr;
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
@@ -1325,23 +1457,26 @@ int player_play(stream_cfg *stream) {
rc = pthread_attr_setstacksize(&tattr, size);
if (rc)
debug(1, "Error setting stack size for player_thread: %s", strerror(errno));
pthread_create(&player_thread, &tattr, player_thread_func, NULL);
pthread_create(player_thread, &tattr, player_thread_func, NULL);
pthread_attr_destroy(&tattr);
return 0;
}
void player_stop(void) {
please_stop = 1;
pthread_cond_signal(&flowcontrol); // tell it to give up
pthread_join(player_thread, NULL);
#ifdef CONFIG_METADATA
send_ssnc_metadata('pend', NULL, 0, 1);
#endif
config.output->stop();
command_stop();
free_buffer();
free_decoder();
int rc = pthread_cond_destroy(&flowcontrol);
if (rc)
debug(1, "Error destroying condition variable.");
void player_stop(pthread_t *player_thread) {
//if (*thread==NULL)
// debug(1,"Trying to stop a non-existent player thread");
// else {
please_stop = 1;
pthread_cond_signal(&flowcontrol); // tell it to give up
pthread_join(*player_thread, NULL);
#ifdef CONFIG_METADATA
send_ssnc_metadata('pend', NULL, 0, 1);
#endif
command_stop();
free_buffer();
free_decoder();
int rc = pthread_cond_destroy(&flowcontrol);
if (rc)
debug(1, "Error destroying condition variable.");
// }
}
+2 -2
View File
@@ -14,8 +14,8 @@ typedef uint16_t seq_t;
// wrapped number between two seq_t.
int32_t seq_diff(seq_t a, seq_t b);
int player_play(stream_cfg *cfg);
void player_stop(void);
int player_play(stream_cfg *cfg, pthread_t *thread);
void player_stop(pthread_t *thread);
void player_volume(double f);
void player_flush(uint32_t timestamp);
+65 -42
View File
@@ -41,6 +41,12 @@
#include "player.h"
#include "rtp.h"
/*
// this does not compile properly with OpenWrt Barrier Breaker...
#if defined(__linux__)
#include <linux/in6.h>
#endif
*/
typedef struct {
uint32_t seconds;
uint32_t fraction;
@@ -55,7 +61,6 @@ typedef struct time_ping_record {
// only one RTP session can be active at a time.
static int running = 0;
static int please_shutdown;
static char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
static short client_ip_family; // AF_INET / AF_INET6
@@ -66,7 +71,7 @@ static SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing po
static int audio_socket; // our local [server] audio socket
static int control_socket; // our local [server] control socket
static int timing_socket; // local timing socket
static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
//static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
static uint32_t reference_timestamp;
static uint64_t reference_timestamp_time;
@@ -90,16 +95,16 @@ static pthread_mutex_t reference_time_mutex = PTHREAD_MUTEX_INITIALIZER;
uint64_t static local_to_remote_time_difference; // used to switch between local and remote clocks
static void *rtp_audio_receiver(void *arg) {
void *rtp_audio_receiver(void *arg) {
// we inherit the signal mask (SIGUSR1)
int *stop = arg; // when set to 1, we should stop
int32_t last_seqno = -1;
uint8_t packet[2048], *pktp;
ssize_t nread;
while (1) {
if (please_shutdown)
break;
while (*stop==0) {
nread = recv(audio_socket, packet, sizeof(packet), 0);
if (nread < 0)
break;
@@ -152,17 +157,18 @@ static void *rtp_audio_receiver(void *arg) {
return NULL;
}
static void *rtp_control_receiver(void *arg) {
void *rtp_control_receiver(void *arg) {
// we inherit the signal mask (SIGUSR1)
int *stop = arg; // when set to 1, we should stop
reference_timestamp = 0; // nothing valid received yet
uint8_t packet[2048], *pktp;
struct timespec tn;
uint64_t remote_time_of_sync, local_time_now, remote_time_now;
uint32_t sync_rtp_timestamp, rtp_timestamp_less_latency;
ssize_t nread;
while (1) {
if (please_shutdown)
break;
while (*stop==0) {
nread = recv(control_socket, packet, sizeof(packet), 0);
local_time_now = get_absolute_time_in_fp();
// clock_gettime(CLOCK_MONOTONIC,&tn);
@@ -253,7 +259,8 @@ static void *rtp_control_receiver(void *arg) {
return NULL;
}
static void *rtp_timing_sender(void *arg) {
void *rtp_timing_sender(void *arg) {
int *stop = arg; // the parameter points to this request to stop thing
struct timing_request {
char leader;
char type;
@@ -274,9 +281,8 @@ static void *rtp_timing_sender(void *arg) {
time_ping_count = 0;
// we inherit the signal mask (SIGUSR1)
while (1) {
if (please_shutdown)
break;
while (*stop==0) {
// debug(1,"Send a timing request");
if (!running)
die("rtp_timing_sender called without active stream!");
@@ -308,12 +314,16 @@ static void *rtp_timing_sender(void *arg) {
return NULL;
}
static void *rtp_timing_receiver(void *arg) {
void *rtp_timing_receiver(void *arg) {
// we inherit the signal mask (SIGUSR1)
int *stop = arg; // when set to 1, we should stop
uint8_t packet[2048], *pktp;
ssize_t nread;
int request_stop = 0;
pthread_t timer_requester;
pthread_create(&timer_requester, NULL, &rtp_timing_sender, NULL);
pthread_create(&timer_requester, NULL, &rtp_timing_sender, (void *)&request_stop);
// struct timespec att;
uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time, transit_time,
processing_time;
@@ -325,9 +335,7 @@ static void *rtp_timing_receiver(void *arg) {
uint64_t first_local_to_remote_time_difference = 0;
uint64_t first_local_to_remote_time_difference_time;
uint64_t l2rtd = 0;
while (1) {
if (please_shutdown)
break;
while (*stop==0) {
nread = recv(timing_socket, packet, sizeof(packet), 0);
arrival_time = get_absolute_time_in_fp();
// clock_gettime(CLOCK_MONOTONIC,&att);
@@ -491,7 +499,8 @@ static void *rtp_timing_receiver(void *arg) {
}
}
debug(1, "Timing RTP thread interrupted. terminating.");
debug(1, "Timing thread interrupted. terminating.");
request_stop = 1;
void *retval;
pthread_kill(timer_requester, SIGUSR1);
pthread_join(timer_requester, &retval);
@@ -524,6 +533,21 @@ static int bind_port(SOCKADDR *remote, int *sock) {
die("failed to get usable addrinfo?! %s.", gai_strerror(ret));
*sock = socket(remote->SAFAMILY, SOCK_DGRAM, IPPROTO_UDP);
/*
// this doesn't compile properly with OpenWrt Barrier Breaker.
#if defined(__linux__)
#ifdef AF_INET6
// now, if we are on IPv6, prefer a public ipv6 address
if (remote->SAFAMILY==AF_INET6) {
int value = IPV6_PREFER_SRC_PUBLIC;
ret = setsockopt(*sock, IPPROTO_IPV6, IPV6_ADDR_PREFERENCES, &value, sizeof(value));
if (ret<0)
die("error: could not select a preference for public IPv6 address");
}
#endif
#endif
*/
ret = bind(*sock, info->ai_addr, info->ai_addrlen);
freeaddrinfo(info);
@@ -634,11 +658,10 @@ void rtp_setup(SOCKADDR *remote, int cport, int tport, uint32_t active_remote, i
debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport,
*ltport);
please_shutdown = 0;
reference_timestamp = 0;
pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
//pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
//pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
//pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
running = 1;
request_sent = 0;
@@ -661,27 +684,27 @@ void clear_reference_timestamp(void) {
void rtp_shutdown(void) {
if (!running)
die("rtp_shutdown called without active stream!");
debug(1,"rtp_shutdown called without active stream!");
debug(2, "shutting down RTP thread");
please_shutdown = 1;
void *retval;
reference_timestamp = 0;
pthread_kill(rtp_audio_thread, SIGUSR1);
pthread_join(rtp_audio_thread, &retval);
pthread_kill(rtp_control_thread, SIGUSR1);
pthread_join(rtp_control_thread, &retval);
pthread_kill(rtp_timing_thread, SIGUSR1);
pthread_join(rtp_timing_thread, &retval);
clear_reference_timestamp();
// debug(1,"Shut down audio, control and timing threads");
// usleep(3000000); // hack
// pthread_kill(rtp_audio_thread, SIGUSR1);
// pthread_kill(rtp_control_thread, SIGUSR1);
// pthread_kill(rtp_timing_thread, SIGUSR1);
// pthread_join(rtp_audio_thread, &retval);
// pthread_join(rtp_control_thread, &retval);
// pthread_join(rtp_timing_thread, &retval);
running = 0;
}
void rtp_request_resend(seq_t first, uint32_t count) {
if (running) {
if (!request_sent) {
debug(2, "requesting resend on %d packets starting at %u.", count, first);
request_sent = 1;
}
//if (!request_sent) {
debug(2, "requesting resend of %d packets starting at %u.", count, first);
// request_sent = 1;
//}
char req[8]; // *not* a standard RTCP NACK
req[0] = 0x80;
@@ -691,7 +714,7 @@ void rtp_request_resend(seq_t first, uint32_t count) {
*(unsigned short *)(req + 6) = htons(count); // count
socklen_t msgsize = sizeof(struct sockaddr_in);
#ifdef AF_INET6
if (rtp_client_timing_socket.SAFAMILY == AF_INET6) {
if (rtp_client_control_socket.SAFAMILY == AF_INET6) {
msgsize = sizeof(struct sockaddr_in6);
}
#endif
@@ -700,10 +723,10 @@ void rtp_request_resend(seq_t first, uint32_t count) {
perror("Error sendto-ing to audio socket");
}
} else {
if (!request_sent) {
//if (!request_sent) {
debug(2, "rtp_request_resend called without active stream!");
request_sent = 1;
}
// request_sent = 1;
//}
}
}
+4
View File
@@ -5,6 +5,10 @@
#include "player.h"
void *rtp_audio_receiver(void *arg);
void *rtp_control_receiver(void *arg);
void *rtp_timing_receiver(void *arg);
void rtp_setup(SOCKADDR *remote, int controlport, int timingport, uint32_t active_remote,
int *local_server_port, int *local_control_port, int *local_timing_port);
void rtp_shutdown(void);
+368 -183
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -65,6 +65,13 @@ stdout =
// audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size.
};
// These are parameters for the "ao" audio back end. No interpolation is done.
ao =
{
// audio_backend_latency_offset = 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 -4410.
// audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size.
};
// The following static latency settings are deprecated -- do not use them for new installations. Shairport Sync now sets latencies automatically.
// To compensate for a delay in the output device please use the alsa/stdio/pipe "audio_backend_latency_offset" setting.
@@ -17,7 +17,7 @@
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="AirPlay Synchronous Audio Service"
NAME=shairport-sync
DAEMON=/usr/local/bin/$NAME
DAEMON=@prefix@/bin/$NAME
# We don't use the DAEMON_ARGS variable here because some of the identifiers may have spaces in them, and so are
# impossible to pass as arguments.
@@ -5,7 +5,7 @@ Requires=avahi-daemon.service
After=avahi-daemon.service
[Service]
ExecStart=/usr/local/bin/shairport-sync
ExecStart=@prefix@/bin/shairport-sync
User=shairport-sync
Group=shairport-sync
+54 -19
View File
@@ -157,31 +157,33 @@ void usage(char *progname) {
printf(" or: %s [options...] -- [audio output-specific options]\n", progname);
printf("\n");
printf("Options:\n");
printf(" -h, --help show this help\n");
printf(" -h, --help show this help.\n");
printf(" -d, --daemon daemonise.\n");
printf(" -V, --version show version information\n");
printf(" -V, --version show version information.\n");
printf(" -k, --kill kill the existing shairport daemon.\n");
printf(" -D, --disconnectFromOutput disconnect immediately from the output device.\n");
printf(" -R, --reconnectToOutput reconnect to the output device.\n");
printf(" -c, --configfile=FILE read configuration settings from FILE. Default is "
"/etc/shairport-sync.conf.\n");
printf(" -v, --verbose -v print debug information; -vv more; -vvv lots\n");
printf(" -p, --port=PORT set RTSP listening port\n");
printf(" -a, --name=NAME set advertised name\n");
printf("\n");
printf("The following general options are for backward compatability. These and all new options have settings in the configuration file, by default /etc/shairport-sync.conf:\n");
printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n");
printf(" -p, --port=PORT set RTSP listening port.\n");
printf(" -a, --name=NAME set advertised name.\n");
printf(
" -A, --AirPlayLatency=FRAMES set the latency for audio sent from an AirPlay device.\n");
printf(" The default value is %d frames.\n", config.AirPlayLatency);
" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an AirPlay device.\n");
printf(" The default is to set it automatically.\n");
printf(
" -i, --iTunesLatency=FRAMES set the latency for audio sent from iTunes 10 or later.\n");
printf(" The default value is %d frames.\n", config.iTunesLatency);
printf(" -L, --latency=FRAMES set the latency for audio sent from an unknown device\n");
printf(" or from an old version of iTunes. Default is %d frames.\n",
config.latency);
printf(" --forkedDaapdLatency=FRAMES set the latency for audio sent from forked-daapd.\n");
printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency \n");
" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes 10 or later.\n");
printf(" The default is to set it automatically.\n");
printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown device.\n");
printf(" The default is to set it automatically.\n");
printf(" --forkedDaapdLatency=FRAMES [Deprecated] Set the latency for audio sent from forked-daapd.\n");
printf(" The default is to set it automatically.\n");
printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, where \n");
printf(" \"basic\" (default) inserts or deletes audio frames from "
"packet frames with low processor overhead.\n");
"packet frames with low processor overhead, and \n");
printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- "
"moderate processor overhead.\n");
printf(
@@ -192,9 +194,9 @@ void usage(char *progname) {
"e.g. /usr/bin/logger.\n");
printf(" Executable scripts work, but must have #!/bin/sh (or "
"whatever) in the headline.\n");
printf(" -w, --wait-cmd wait until the -B or -E programs finish before continuing\n");
printf(" -o, --output=BACKEND select audio output method\n");
printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service\n");
printf(" -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n");
printf(" -o, --output=BACKEND select audio output method.\n");
printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n");
printf(" if no mdns provider is specified,\n");
printf(" shairport tries them all until one works.\n");
printf(" -r, --resync=THRESHOLD resync if error exceeds this number of frames. Set to 0 to "
@@ -210,9 +212,9 @@ void usage(char *progname) {
#ifdef CONFIG_METADATA
printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. "
"--metadata-pipename=/tmp/shairport-sync-metadata.\n");
printf(" The default is /tmp/shairport-sync-metadata.\n");
printf(" --get-coverart send cover art through the metadata pipe.\n");
#endif
printf("\nGeneral options can be configured in /etc/%s.conf.\n", appName);
printf("\n");
mdns_ls_backends();
printf("\n");
@@ -639,6 +641,27 @@ int main(int argc, char **argv) {
free(basec);
// set defaults
// get thje endianness
union {
uint32_t u32;
uint8_t arr[4];
} xn;
xn.arr[0] = 0x44; /* Lowest-address byte */
xn.arr[1] = 0x33;
xn.arr[2] = 0x22;
xn.arr[3] = 0x11; /* Highest-address byte */
if (xn.u32==0x11223344)
endianness = SS_LITTLE_ENDIAN;
else if (xn.u32==0x33441122)
endianness = SS_PDP_ENDIAN;
else if (xn.u32==0x44332211)
endianness = SS_BIG_ENDIAN;
else die("Can not recognise the endianness of the processor.");
strcpy(configuration_file_path, "/etc/");
strcat(configuration_file_path, appName);
strcat(configuration_file_path, ".conf");
@@ -830,6 +853,18 @@ int main(int argc, char **argv) {
daemon_log(LOG_NOTICE, "startup");
switch (endianness) {
case SS_LITTLE_ENDIAN:
debug(2,"The processor is running little-endian.");
break;
case SS_BIG_ENDIAN:
debug(2,"The processor is running big-endian.");
break;
case SS_PDP_ENDIAN:
debug(2,"The processor is running pdp-endian.");
break;
}
/* Mess around with the latency options */
// Basically, we used to rely on static latencies -- 99400 for iTunes 10 or later and forkedDaapd, 88200 for everything else
// Nowadays we allow the source to set the latency, which works out at 99651 for iTunes 10 and forkedDaapd and 88220 for everything else
+2 -2
View File
@@ -33,7 +33,7 @@
#include "tinysvcmdns.h"
#include "common.h"
#define DEBUG_PRINTF(...) debug(1, __VA_ARGS__)
#define DEBUG_PRINTF(...) debug(3, __VA_ARGS__)
#define log_message(level, ...) \
do { \
switch (level) { \
@@ -41,7 +41,7 @@
warn(__VA_ARGS__); \
break; \
default: \
debug(1, __VA_ARGS__); \
debug(3, __VA_ARGS__); \
} \
} while (0)