Merge 2.3.X from development to master
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/shairport-sync
|
||||
/*.o
|
||||
/*~
|
||||
*.xml~
|
||||
/config.mk
|
||||
/config.h
|
||||
Makefile
|
||||
|
||||
+26
-3
@@ -1,7 +1,7 @@
|
||||
SUBDIRS = man
|
||||
|
||||
bin_PROGRAMS = shairport-sync
|
||||
shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c audio_dummy.c audio_pipe.c
|
||||
shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c
|
||||
|
||||
AM_CFLAGS = -Wno-multichar
|
||||
|
||||
@@ -26,6 +26,18 @@ if USE_SNDIO
|
||||
shairport_sync_SOURCES += audio_sndio.c
|
||||
endif
|
||||
|
||||
if USE_STDOUT
|
||||
shairport_sync_SOURCES += audio_stdout.c
|
||||
endif
|
||||
|
||||
if USE_PIPE
|
||||
shairport_sync_SOURCES += audio_pipe.c
|
||||
endif
|
||||
|
||||
if USE_DUMMY
|
||||
shairport_sync_SOURCES += audio_dummy.c
|
||||
endif
|
||||
|
||||
if USE_AO
|
||||
shairport_sync_SOURCES += audio_ao.c
|
||||
endif
|
||||
@@ -39,5 +51,16 @@ shairport_sync_SOURCES += mdns_dns_sd.c
|
||||
endif
|
||||
|
||||
install-exec-hook:
|
||||
[ -f /etc/init.d/shairport-sync ] || cp scripts/shairport-sync /etc/init.d/
|
||||
update-rc.d shairport-sync defaults 90 10
|
||||
if INSTALL_CONFIG_FILES
|
||||
[ -e $(DESTDIR)/etc ] || mkdir $(DESTDIR)/etc
|
||||
cp scripts/shairport-sync.conf $(DESTDIR)/etc/shairport-sync.conf.sample
|
||||
[ -f $(DESTDIR)/etc/shairport-sync.conf ] || cp scripts/shairport-sync.conf $(DESTDIR)/etc/shairport-sync.conf
|
||||
endif
|
||||
if INSTALL_SYSTEMV
|
||||
[ -e $(DESTDIR)/etc/init.d ] || mkdir -p $(DESTDIR)/etc/init.d
|
||||
[ -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
|
||||
endif
|
||||
|
||||
@@ -1,63 +1,89 @@
|
||||
Shairport Sync
|
||||
=============
|
||||
Shairport Sync emulates an AirPort Express for the purpose of streaming audio from iTunes, iPods, iPhones, iPads and AppleTVs.
|
||||
Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. Thus, for example, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.)
|
||||
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.
|
||||
|
||||
More Information
|
||||
-------
|
||||
Shairport Sync works by using timing information present in the audio data stream to keep in step with the source. It does this by monitoring and controlling the "latency" -- the time between when a sound is time-stamped at the source and when it is played by the audio output device. To measure latency precisely, it keeps its own clock synchronised with the clock used by the source, 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 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.
|
||||
|
||||
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 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. 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 even less obtrusive than insertion and deletion but requires a good deal of processing power -- most embedded devices probably can't support it.
|
||||
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”.
|
||||
|
||||
There are three default latency settings, chosen automatically. One latency (the "iTunes" latency) matches the latency used by recent versions of iTunes when playing audio. The "AirPlay" latency is used by older version of iTunes, by iOS devices and by iTunes and Quicktime Player when playing video. Finally, the "forkedDaapd" latency is used for forked-daapd sources.
|
||||
There are four default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and another matches the latency used by so-called "AirPlay" devices — iOS devices and iTunes and Quicktime Player when they are playing video. A third latency is used when the audio source is `forked-daapd`. The fourth latency is the default if no other latency is chosen and is used for older versions of iTunes.
|
||||
|
||||
Shairport Sync is a pretty substantial rewrite of 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 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 works only with Linux and ALSA. The sound card you use must be 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).
|
||||
Shairport Sync is designed for Linux and ALSA. 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).
|
||||
|
||||
For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki.
|
||||
|
||||
What else?
|
||||
--------------
|
||||
* 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.
|
||||
* Shairport Sync will mute properly if the hardware supports it.
|
||||
* If Shairport Sync has to use software volume and mute controls, the response time is shorter than before -- now it responds in 0.15 seconds.
|
||||
* 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.)
|
||||
* 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.
|
||||
* 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, 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.
|
||||
* 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.
|
||||
|
||||
Status
|
||||
------
|
||||
Shairport Sync works on standard Ubuntu laptops, on the Raspberry Pi with Raspian and with OpenWrt, and it runs on a Linksys NSLU2 using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input.
|
||||
Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspian, 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.
|
||||
|
||||
Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound card, and though you wouldn't mistake the output for HiFi, it's really not too shabby. USB-connected sound cards work well on the latest version of Raspian; however older versions of Raspian 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 very 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. USB-connected sound cards work well on recent versions of Raspian; however older versions of Raspian 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.
|
||||
|
||||
Shairport Sync runs on Ubuntu and Debian inside VMWare Fusion 7 on a Mac, but synchronisation does not work -- possibly because the soundcard is being emulated.
|
||||
Shairport Sync runs on Ubuntu, OpenWrt, Debian, Arch Linux and Fedora inside VMWare Fusion on a Mac, but synchronisation in inaccurate — possibly because the soundcard is being emulated.
|
||||
|
||||
Shairport Sync works only with the ALSA back end. You can try compiling the other back ends in as you wish, but it definitely will not work properly with them. Maybe someday...
|
||||
|
||||
One other difference from 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. All previous versions looked in the current system to determine which packages were available, instead of looking at what packages were available in the target system.
|
||||
Shairport Sync will output to alsa cards, to standard output and to pipes using appropriate backends. You can try compiling additional backends in as you wish, but it definitely will not work properly with them. Maybe someday...
|
||||
|
||||
For information about changes and updates, please refer to the RELEASENOTES.md file in the distribution.
|
||||
|
||||
Note: Historically, Shairport Sync has taken its settings from command line arguments. While this is still the case, it does not always work well across distributions. Accordingly, from version 2.4 onwards, Shairport Sync reads settings from the file `/etc/shairport-sync.conf`. Access to new features will only be provided via the settings file.
|
||||
|
||||
Building And Installing
|
||||
---------------------
|
||||
Shairport Sync packages exist for a number of popular Linux distributions:
|
||||
* OpenWrt: Shairport Sync is available as an optional package in trunk OpenWrt.
|
||||
Alternatively, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt.
|
||||
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 progam has been installed, refer to the section on Configuring Shairport Sync that follows.
|
||||
|
||||
* Buildroot: Shairport Sync is available as an optional package in Buildroot.
|
||||
**OpenWrt:**
|
||||
If you're interested in Shairport Sync for OpenWrt, there's an OpenWrt package at https://github.com/mikebrady/shairport-sync-for-openwrt. OpenWrt doesn't support the IQaudIO Pi-DAC.
|
||||
|
||||
* Arch Linux: An Arch Linux installation package suitable for compilation on any architecture is available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD).
|
||||
Arch Linux installation packages, precompiled for the Intel 64-bit and Intel 32 bit architectures, are available in the official repo: https://www.archlinux.org/packages/community/x86_64/shairport-sync/.
|
||||
**Arch Linux:**
|
||||
An Arch Linux installation package is available at [EliaCereda/shairport-sync-PKGBUILD](https://github.com/EliaCereda/shairport-sync-PKGBUILD).
|
||||
|
||||
(Thanks to all package contributors!)
|
||||
**Fedora:**
|
||||
Install the toolchain and pre-requisites, if necessary:
|
||||
```
|
||||
% sudo yum install make automake gcc gcc-c++ kernel-devel
|
||||
% sudo yum install alsa-lib-devel autoconf automake avahi-devel libconfig-devel libdaemon-devel openssl-devel popt-devel soxr-devel
|
||||
```
|
||||
Download the tarball from the "releases" tab on github or use `wget` and then use `rpmbuild`. This example is for version 2.4:
|
||||
```
|
||||
% wget -O shairport-sync-2.4.tar.gz https://github.com/mikebrady/shairport-sync/archive/2.4.tar.gz
|
||||
% rpmbuild -ta shairport-sync-2.4.tar.gz
|
||||
```
|
||||
The `-ta` means "build all from this tarball".
|
||||
|
||||
Otherwise, follow these instructions.
|
||||
The RPM will be built in a directory and will have a pathname like, for example, `~/rpmbuild/RPMS/i686/shairport-sync-2.4-1.fc22.i686.rpm` You should then install it with (for this example):
|
||||
```
|
||||
%sudo rpm -i ~/rpmbuild/RPMS/i686/shairport-sync-2.4-1.fc22.i686.rpm
|
||||
```
|
||||
You may have to manually create the directory `/var/shairport-sync` for the installation to succeed. Having edited the configuration file `/etc/shairport-sync.conf` as appropriate (see "Configuring Shairport Sync" below), enable and start the service with:
|
||||
```
|
||||
%sudo systemctl enable shairport-sync.service
|
||||
%sudo systemctl start shairport-sync.service
|
||||
```
|
||||
Sincere thanks to all package contributors!
|
||||
|
||||
**General Build Instructions**
|
||||
|
||||
To build Shairport Sync from sources on Debian, Ubuntu, Raspian, etc. follow these instructions.
|
||||
|
||||
The following libraries are required:
|
||||
* OpenSSL or PolarSSL
|
||||
@@ -67,19 +93,20 @@ The following libraries are required:
|
||||
* autoconf
|
||||
* libtool
|
||||
* libpopt
|
||||
* libconfig
|
||||
|
||||
Optional:
|
||||
* libsoxr
|
||||
|
||||
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.
|
||||
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 Raspian users can get the basics with:
|
||||
|
||||
- `apt-get install autoconf libtool libdaemon-dev libasound2-dev libpopt-dev`
|
||||
- `apt-get install autoconf 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 Raspian; instructions for how to build it from source are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/2.2/LIBSOXR.md).
|
||||
- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is not yet part of Raspian; instructions for how to build it from source are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/2.3/LIBSOXR.md).
|
||||
|
||||
Download Shairport Sync:
|
||||
|
||||
@@ -94,141 +121,226 @@ $ autoreconf -i -f
|
||||
Choose the appropriate `--with-*` options:
|
||||
|
||||
- `--with-alsa` for the ALSA audio back end. This is required.
|
||||
- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service -- it may already be in your system. (There seem to be problems with the `--with-tinysvcmdns` option right now, so read the following with caution.) If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system -- bad things may happen, according to RFC 6762, §15.
|
||||
- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout).
|
||||
- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe.
|
||||
- `--with-avahi` or `--with-tinysvcmdns` for mdns support. Avahi is a widely-used system-wide zero-configuration networking (zeroconf) service — it may already be in your system. If you don't have Avahi, or similar, then consider including tinysvcmdns, which is a tiny zeroconf service embedded inside the shairport-sync application itself. To enable multicast for `tinysvcmdns`, you may have to add a default route with the following command: `route add -net 224.0.0.0 netmask 224.0.0.0 eth0` (substitute the correct network port for `eth0`). You should not have more than one zeroconf service on the same system — bad things may happen, according to RFC 6762, §15.
|
||||
- `--with-ssl=openssl` or `--with-ssl=polarssl` for encryption and related utilities using either OpenSSL or PolarSSL.
|
||||
- `--with-soxr` for libsoxr-based resampling.
|
||||
- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location.
|
||||
- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader.
|
||||
- `--with-systemv` to install a System V init script at the `make install` stage. Default is not to to install.
|
||||
- `--with-systemd` to install a systemd service description at the `make install` stage. Default is not to to install.
|
||||
- `--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.
|
||||
|
||||
Here is an example, suitable for most installations:
|
||||
Here is an example, suitable for installations such as Ubuntu and Raspbian:
|
||||
|
||||
`$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-soxr`
|
||||
`$ ./configure --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemv`
|
||||
|
||||
Omit the `--with-soxr` if the libsoxr library is not available.
|
||||
Omit the `--with-soxr` if the libsoxr library is not available. For installation into a `systemd` system, replace the `--with-systemv` with `--with-systemd`.
|
||||
|
||||
Enter:
|
||||
|
||||
`$ make`
|
||||
|
||||
Run `$sudo make install` to install `shairport-sync` along with an init script which will automatically launch it at startup. The settings in the init script are the most basic defaults, so you will want to edit it -- the file is `/etc/init.d/shairport-sync` -- to give the service a name, use a different card, use the hardware mixer and volume control, etc. -- there are some examples in the script file.
|
||||
to build the application. Next, run:
|
||||
|
||||
Man Page
|
||||
--------
|
||||
You can see a web version of the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/master/man/shairport-sync.html
|
||||
```
|
||||
$sudo make install
|
||||
$sudo update-rc.d shairport-sync defaults 90 10
|
||||
```
|
||||
|
||||
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.
|
||||
The settings are the most basic defaults, so you will want to edit the configuration — the file is `/etc/shairport-sync.conf` — to give the service a name,
|
||||
use a specific sound card and mixer control, etc. — there are some examples in the sample configuration file.
|
||||
|
||||
*Man Page*
|
||||
|
||||
You can view the man page here: http://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/master/man/shairport-sync.html
|
||||
|
||||
Configuring Shairport Sync
|
||||
--------
|
||||
Shairport Sync installs a default configuration at `/etc/init.d/shairport-sync` (it won't replace an existing one) which should work in almost any system with a sound card. If there is a problem, it will be noted in the logfile, normally `/etc/log/syslog`. However, to get the most out of your software and hardware, you need to adjust some of the settings.
|
||||
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.
|
||||
|
||||
To understand what follows, note that settings and parameters are passed to Shairport Sync through command line arguments. The purpose of the init script at `/etc/init.d/shairport-sync` is to launch or terminate Shairport Sync while passing the correct arguments to it. You are perfectly free to remove the init script and launch and terminate Shairport Sync yourself directly; indeed it is useful when you are troubleshooting the program. If you do launch it directly, make sure it isn't running already!
|
||||
Starting and stopping automatically is taken care of differently in different versions of Linux. In the example above, when you run `$sudo make install`, a System V startup script is placed at `/etc/init.d/shairport-sync`. This will not be appropriate in Linuxes that use `systemd` such as Arch Linux, so please look at the separate installation scripts for those.
|
||||
|
||||
As well as the man page, don't forget you can launch Shairport Sync with the `-h` option to get some help on the options available.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
These are the important options:
|
||||
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.
|
||||
|
||||
The `-a` option allows you to specify the service name Shairport Sync will use on the network. If you don't specify a service name, the name `Shairport Sync on ...your computer's hostname...` will be used.
|
||||
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:
|
||||
|
||||
The `-S` option allows you to specify the kind of "stuffing" or interpolation to be used -- `basic` (default) for simple insertion/deletion or `soxr` for smoother resampling-based interpolation.
|
||||
```
|
||||
general =
|
||||
{
|
||||
name = "Front Room";
|
||||
password = "secret";
|
||||
interpolation = "soxr";
|
||||
// ... other general settings
|
||||
};
|
||||
```
|
||||
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 `--password` option allows you to password-protect access to the service provided by Shairport Sync.
|
||||
The following `alsa` group settings are very important for maximum performance. If your audio device has a mixer that can be use to control the volume, then Shairport Sync can use it to give instant response to volume and mute commands and it can offload some work from the processor.
|
||||
* The `mixer_control_name` tag allows you to specify the name of the mixer volume control.
|
||||
* The `mixer_device` tag allows you specify where the mixer is. By default, the mixer is on the `output_device`, so you only need to use the `mixer_device` tag if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `output_device` tag, because normally a mixer is associated with a *card* rather than a device. Suppose you wish to use the output device `5` of card `hw:0` and the mixer volume-control named `PCM`:
|
||||
|
||||
These may be also of interest:
|
||||
```
|
||||
alsa =
|
||||
{
|
||||
output_device = "hw:0,5";
|
||||
mixer_device = "hw:0";
|
||||
mixer_control_name = "PCM";
|
||||
// ... other alsa settings
|
||||
};
|
||||
```
|
||||
|
||||
The `-B`, `-E` and `-w` options allow you to specify a program to execute before (`-B`) and after (`-E`) Shairport Sync plays. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Use the `-w` option for Shairport Sync to wait until the respective commands have been completed before continuing. 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.)
|
||||
Shairport Sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the `sessioncontrol` group settings `run_this_before_play_begins` and `run_this_after_play_ends`. This is to facilitate situations where something has to be done before and after playing, e.g. switching on an amplifier beforehand and switching it off afterwards. Set the `wait_for_completion` value to `"yes"` for Shairport Sync to wait until the respective commands have been completed before continuing.
|
||||
|
||||
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.)
|
||||
|
||||
*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/update-documentation/man/shairport-sync.html.
|
||||
|
||||
Apart from the following options, all command line options can be replaced by settings in the configuration file. Here is a brief description of command line options that are not replicated by settings in the settings file.
|
||||
|
||||
* The `-c` option allows you to specify the location of the configuration file — default is `/etc/shairport-sync.conf`.
|
||||
* The `-V` option gives you version information about Shairport Sync and then quits.
|
||||
* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon and then quit. You need to have sudo privileges for this.
|
||||
* The `-v` option causes Shairport Sync to print some information and debug messages.
|
||||
* The `-d` option causes Shairport Sync to properly daemonise itself, that is, to run in the background. You may need sudo privileges for this.
|
||||
* The `-k` option causes Shairport Sync to kill an existing Shairport Sync daemon. You may need to have sudo privileges for this.
|
||||
|
||||
Apart from arguments of Shairport Sync, there are also arguments for controlling the ALSA audio system. ALSA arguments follow a `--` on the command line -- see the examples below for layout of command line.
|
||||
These are important because you use them to specify the actual audio device you wish to use and you give Shairport Sync important information about the capabilities of the device. The important ALSA arguments are:
|
||||
The System V init script at `/etc/init.d/shairport-sync` has a bare minimum :
|
||||
`-d`. Basically all it does is put the program in daemon mode. The program will read its settings from the configuration file, normally `/etc/shairport-sync.conf`.
|
||||
|
||||
* The `-d` option which allows you to specify the audio device to use. Typical values would be `default` (default), `hw:0`, `hw:1`, etc. Those examples are specifying which *soundcard* to use; the actual output device used is the card's default, typically output device 0. You could specify, for example, device 5 on card hw:0 with `-d hw:0,5`.
|
||||
Examples
|
||||
--------
|
||||
|
||||
The following settings are very important for maximum performance. If your audio device has a hardware mixer and volume control, then Shairport Sync can use it to give faster response to volume and mute commands and it can offload some work from the processor.
|
||||
* The `-m` option allows you specify where the mixer is. By default, the mixer is to be found where you specify with the `-d` option, so you only need to use the `-m` option if the mixer is elsewhere. This can happen if you specify a *device* rather than a *card* with the `-d` option, because normally a mixer is associated with a *card* rather than a device. For example, if you specified that the output device was device 5 of card hw:0 and if the mixer was associated with the card, you would write `-d hw:0,5 -m hw:0`.
|
||||
* The `-t` option allows you to specify the type of audio mixer -- `software` (default) or `hardware`.
|
||||
* The `-c` option allows you to specify the name of the volume control on the hardware mixer.
|
||||
Here are some examples of complete configuration files.
|
||||
|
||||
The init script at `/etc/init.d/shairport-sync` has a bare minimum set of options (see line 60):
|
||||
```
|
||||
general = {
|
||||
name = "Joe's Stereo";
|
||||
};
|
||||
|
||||
`-d`
|
||||
alsa = {
|
||||
output_device = "hw:0";
|
||||
};
|
||||
```
|
||||
|
||||
Basically all it does is put the program in daemon mode, selects the default output device and uses a software volume control.
|
||||
This gives the service a particular name — "Joe's Stereo" and specifies that audio device hw:0 be used.
|
||||
|
||||
*Examples*
|
||||
For best results — including getting true mute and instant response to volume control and pause commands — you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the volume controller to be used after the `-c` option.
|
||||
|
||||
Here are some examples of complete commands. If you are modifying the init script, you don't need the `shairport-sync` at the start, but you should include the `-d` option, as it puts the program into daemon mode. There are some commented-out examples in the init script -- see lines 61--63.
|
||||
Here is an example for for a Raspberry Pi using its internal soundcard — device hw:0 — that drives the headphone jack:
|
||||
```
|
||||
general = {
|
||||
name = "Mike's Boombox";
|
||||
};
|
||||
|
||||
- `shairport-sync -d -a "Joe's Stereo" -- -d hw:0`
|
||||
|
||||
This gives the service a particular name -- "Joe's Stereo" and specifies that audio device hw:0 be used.
|
||||
|
||||
|
||||
For best results -- including getting true mute and instant response to volume control and pause commands -- you should access the hardware volume controls. Use `amixer` or `alsamixer` or similar to discover the name of the volume controller to be used after the `-c` option.
|
||||
|
||||
Here is an example for for a Raspberry Pi using its internal soundcard -- device hw:0 -- that drives the headphone jack:
|
||||
|
||||
- `shairport-sync -d -a "Mike's Boombox" -- -d hw:0 -t hardware -c PCM`
|
||||
alsa = {
|
||||
output_device = "hw:0";
|
||||
mixer_control_name = "PCM";
|
||||
};
|
||||
```
|
||||
|
||||
Here is an example of using soxr-based resampling and driving a Topping TP30 Digital Amplifier, which has an integrated USB DAC and which is connected as audio device `hw:1`:
|
||||
```
|
||||
general = {
|
||||
name = "Kitchen";
|
||||
interpolation = "soxr";
|
||||
};
|
||||
|
||||
- `shairport-sync -d -a Kitchen -S soxr -- -d hw:1 -t hardware -c PCM`
|
||||
alsa = {
|
||||
output_device = "hw:1";
|
||||
mixer_control_name = "PCM";
|
||||
};
|
||||
```
|
||||
|
||||
For a cheapo "3D Sound" USB card (Stereo output and input only) on a Raspberry Pi:
|
||||
```
|
||||
general = {
|
||||
name = "Front Room";
|
||||
};
|
||||
|
||||
- `shairport-sync -d -a "Front Room" -- -d hw:1 -t hardware -c Speaker`
|
||||
alsa = {
|
||||
output_device = "hw:1";
|
||||
mixer_control_name = "Speaker";
|
||||
};
|
||||
```
|
||||
|
||||
For a first generation Griffin iMic on a Raspberry Pi:
|
||||
```
|
||||
general = {
|
||||
name = "Attic";
|
||||
};
|
||||
|
||||
- `shairport-sync -d -a "Attic" -- -d hw:1 -t hardware -c PCM`
|
||||
alsa = {
|
||||
output_device = "hw:1";
|
||||
mixer_control_name = "PCM";
|
||||
};
|
||||
```
|
||||
|
||||
For an NSLU2, which has no internal soundcard, there appears to be a bug in ALSA -- you can not specify a device other than "default". Thus:
|
||||
For an NSLU2, which has no internal soundcard, there appears to be a bug in ALSA — you can not specify a device other than "default". Thus:
|
||||
|
||||
On an NSLU2, to drive a first generation Griffin iMic:
|
||||
```
|
||||
general = {
|
||||
name = "Den";
|
||||
};
|
||||
|
||||
- `shairport-sync -d -a "Den" -- -t hardware -c PCM`
|
||||
alsa = {
|
||||
mixer_control_name = "PCM";
|
||||
};
|
||||
```
|
||||
|
||||
On an NSLU2, to drive the "3D Sound" USB card:
|
||||
```
|
||||
general = {
|
||||
name = "TV Room";
|
||||
};
|
||||
|
||||
- `shairport-sync -d -a "TV Room" -- -t hardware -c Speaker`
|
||||
alsa = {
|
||||
mixer_control_name = "Speaker";
|
||||
};
|
||||
```
|
||||
|
||||
Latency
|
||||
-------
|
||||
Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the DAC, irrespective of any internal delays, processing times, etc. in the computer. From listening tests, it seems that there are two latencies in current use:
|
||||
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. From listening tests, it seems that there are three latencies in current use:
|
||||
* If the source is iTunes 10 or later, a latency of 99,400 frames seems to bring Shairport Sync into exact synchronisation both with the speakers on the iTunes computer itself and with AirPort Express receivers.
|
||||
* If the source is an AirPlay device, the latency seems to be exactly 88,200 frames. AirPlay devices include AppleTV, iPod, iPad and iPhone and Quicktime Player on Mac.
|
||||
* If the source is a `forked-daapd`-powered device, the latency seems to be exactly 99,400 frames.
|
||||
* If the source cannot be identified as AirPlay or as iTunes 10 or later, then the default latency of 88,200 frames seems to work in general. Note that some third party programs masquerade as older versions of iTunes.
|
||||
|
||||
Shairport Sync uses the latencies described above as defaults. You shouldn't need to change them, but occasionally problems arise when you are trying to synchronise with speaker systems -- typically surround-sound home theatre systems -- that have their own inherent delays. You can set the default latency with the `-L` or `--latency` option (e.g. `-L 99400` or `--latency=99400`). You can set your own iTunes 10 (or later) latency with the `-i` or `--iTunesLatency` option. Similarly you can set an AirPlay latency with the `-A` or `--AirPlayLatency` option and the forked-daapd latency with the `--forkedDaapdLatency` option.
|
||||
Shairport Sync uses the latencies described above as defaults. You shouldn't need to change them.
|
||||
|
||||
Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the `alsa` group `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
|
||||
-------------
|
||||
Shairport Sync actively maintains synchronisation with the source.
|
||||
If synchronisation is lost -- say due to a busy source or a congested network -- Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 50 ms -- i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above.
|
||||
* You can vary the resync threshold, or turn resync off completely, with the `-r` opton.
|
||||
If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 50 ms — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above.
|
||||
* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold` setting.
|
||||
|
||||
Tolerance
|
||||
---------
|
||||
Playback synchronisation is allowed to wander 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 option to monitor correction levels. Corrections should not greatly exceed net corrections.
|
||||
* You can vary the tolerance with the `--tolerance` option.
|
||||
Playback synchronisation is allowed to wander 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
|
||||
---------------
|
||||
If you add the option `--statistics`, e.g. as follows for the Raspberry Pi with "3D Sound" card:
|
||||
|
||||
`shairport-sync -a "Shairport Sync" --statistics -- -d hw:1 -t hardware -c Speaker`
|
||||
|
||||
it will print statistics like this occasionally on the console (or in the logfile if running in daemon mode):
|
||||
If you turn on the `general` `statistics` setting, statistics like this will be printed at intervals on the console (or in the logfile if running in daemon mode):
|
||||
|
||||
`Sync error: -35.4 (frames); net correction: 24.2 (ppm); corrections: 24.2 (ppm); missing packets 0; late packets 5; too late packets 0; resend requests 6; min DAC queue size 4430.`
|
||||
|
||||
"Sync error" is the average deviation from exact synchronisation. The example above indicates that the output is on average 35.4 frames ahead of exact synchronisation. Sync is allowed to wander by the tolerance -- 88 frames (± 2 milliseconds) by default -- before a correction will be made.
|
||||
"Sync error" is the average deviation from exact synchronisation. The example above indicates that the output is on average 35.4 frames ahead of exact synchronisation. Sync is allowed to wander by the tolerance — 88 frames (± 2 milliseconds) by default — before a correction will be made.
|
||||
|
||||
"Net correction" is actually the net sum of corrections -- the number of frame insertions less the number of frame deletions -- given as a moving average in parts per million. After an initial settling period, it represents the divergence between the source clock and the sound device's clock. The example above indicates that the output DAC's clock is running 24.2 ppm faster than the source's clock.
|
||||
"Net correction" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The example above indicates that the output device is consuming frames 24.2 ppm faster than the source is generating them.
|
||||
|
||||
"Corrections" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the absolute value of the drift, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections.
|
||||
"Corrections" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections.
|
||||
|
||||
For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from a second-generation iPod driving the Raspberry Pi which is connected over Ethernet.
|
||||
|
||||
|
||||
+176
-3
@@ -1,3 +1,179 @@
|
||||
Version 2.4
|
||||
----
|
||||
**Stable release**
|
||||
|
||||
This stable release is the culmination of the 2.3.X sequence of development releases.
|
||||
|
||||
**Change Summary**
|
||||
|
||||
Changes from the previous stable version -- 2.2.5 -- are summarised here:
|
||||
* Settings are now read from a configuration file. Command-line settings are supported but discouraged.
|
||||
* Metadata is now supported -- it can be delivered to a unix pipe for processing by a helper application. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader.
|
||||
* Raw PCM audio can be delivered to standard output ("stdout") or to a unix pipe. The internal architecture has changed considerably to support this.
|
||||
* Support for compilation on OpenWrt back to Attitude Adjustment.
|
||||
* Version 2.4 uses the libconfig library.
|
||||
* Runs on a wider range of platforms, including Arch Linux and Fedora.
|
||||
* Bug fixes.
|
||||
|
||||
Please note that building instructions have changed slightly from the previous version.
|
||||
Also, the `-t hardware/software` option has been deprecated in the alsa back end.
|
||||
|
||||
Version 2.3.13
|
||||
----
|
||||
**Note**
|
||||
* We're getting ready to release the development branch as the new, stable, master branch at 2.4. If you're packaging Shairport Sync, you might prefer to wait a short while as we add a little polish before the release.
|
||||
|
||||
**Changes**
|
||||
* Harmonise version numbers on the release and on the `shairport.spec` file used in Fedora.
|
||||
|
||||
Version 2.3.12
|
||||
----
|
||||
**Note**
|
||||
* We're getting ready to release the development branch as the new, stable, master branch at 2.4. If you're packaging Shairport Sync, you might prefer to wait a short while as we add a little polish before the release.
|
||||
|
||||
**Changes**
|
||||
* `update-rc.d` has been removed from the installation script for System V because it causes problems for package makers. It's now noted in the user installation instructions.
|
||||
* The `alsa` group `mixer_type` setting is deprecated and you should stop using it. Its functionality has been subsumed into `mixer_name` – when you specify a `mixer_name` it automatically chooses the `hardware` mixer type.
|
||||
|
||||
|
||||
**Enhancements**
|
||||
* Larger range of interpolation. Shairport Sync 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:
|
||||
* No corrections happen for the first five seconds.
|
||||
* Corrections of up to about 1 in 1000 for the next 25 seconds.
|
||||
* Corrections of up to 1 in 352 thereafter.
|
||||
|
||||
**Documentation Update**
|
||||
* Nearly there with updates concerning the configuration file.
|
||||
|
||||
Version 2.3.11
|
||||
----
|
||||
Documentation Update
|
||||
* Beginning to update the `man` document to include information about the configuration file. It's pretty sparse, but it's a start.
|
||||
|
||||
Version 2.3.10
|
||||
----
|
||||
Bug fix
|
||||
* The "pipe" backend used output code that would block if the pipe didn't have a reader. This has been replaced by non-blocking code. Here are some implications:
|
||||
* When the pipe is created, Shairport Sync will not block if a reader isn't present.
|
||||
* If the pipe doesn't have a reader when Shairport Sync wants to output to it, the output will be discarded.
|
||||
* If a reader disappears while writing is occuring, the write will time out after five seconds.
|
||||
* Shairport Sync will only close the pipe on termination.
|
||||
|
||||
Version 2.3.9
|
||||
----
|
||||
* Bug fix
|
||||
* Specifying the configuration file using a *relative* file path now works properly.
|
||||
* The debug verbosity requested with `-v`, `-vv`, etc. is now honoured before the configuration file is read. It is read and honoured from when the command line arguments are scanned the first time to get a possible configuration file path.
|
||||
|
||||
Version 2.3.8
|
||||
----
|
||||
* Annoying changes you must make
|
||||
* You probably need to change your `./configure` arguments. The flag `with-initscript` has changed to `with-systemv`. It was previously enabled by default; now you must enable it explicitly.
|
||||
|
||||
* Changes
|
||||
* Added limited support for installing into `systemd` and Fedora systems. For `systemd` support, use the configuration flag `--with-systemd` in place of `--with-systemv`. The installation does not do everything needed, such as defining special users and groups.
|
||||
* Renamed `with-initscript` configuration flag to `with-systemv` to describe its role more accurately.
|
||||
* A System V startup script is no longer installed by default; if you want it, ask for it with the `--with-systemv` configuration flag.
|
||||
* Added limited support for FreeBSD. You must specify `LDFLAGS='-I/usr/local/lib'` and `CPPFLAGS='-L/usr/local/include'` before running `./configure --with-foo etc.`
|
||||
* Removed the `-configfile` annotation from the version string because it's no longer optional; it's always there.
|
||||
* Removed the `dummy`, `pipe` and `stdout` backends from the standard build – they are now optional and are no longer automatically included in the build.
|
||||
|
||||
* Bug fixes
|
||||
* Allow more stack space to prevent a segfault in certain configurations (thanks to https://github.com/joerg-krause).
|
||||
* Add missing header files(thanks to https://github.com/joerg-krause).
|
||||
* Removed some (hopefully) mostly silent bugs from the configure.ac file.
|
||||
|
||||
Version 2.3.7
|
||||
----
|
||||
* Changes
|
||||
* Removed the two different buffer lengths for the alsa back end that made a brief appearance in 2.3.5.
|
||||
* Enhancements
|
||||
* Command line arguments are now given precedence over config file settings. This conforms to standard unix practice.
|
||||
* A `–without-pkg-config` configuration argument now allows for build systems, e.g. for older OpenWrt builds, that haven't fully implemented it. There is still some unhappiness in arch linux builds.
|
||||
* More
|
||||
* Quite a bit of extra diagnostic code was written to investigate clock drift, DAC timings and so on. It was useful but has been commented out. If might be useful in the future.
|
||||
|
||||
Version 2.3.5
|
||||
----
|
||||
* Changes
|
||||
* The metadata item 'sndr' is no longer sent in metadata. It's been replaced by 'snam' and 'snua' -- see below.
|
||||
* Enhancements
|
||||
* When a play session is initiated by a source, it attempts to reserve the player by sending an "ANNOUNCE" packet. Typically, a source device name and/or a source "user agent" is sent as part of the packet. The "user agent" is usually the name of the sending application along with some more information. If metadata is enabled, the source name, if provided, is emitted as a metadata item with the type `ssnc` and code `snam` and similarly the user agent, if provided, is sent with the type `ssnc` and code `snua`.
|
||||
* Two default buffer lengths for ALSA -- default 6615 frames if a software volume control is used, to minimise the response time to pause and volume control changes; default 22050 frames if a hardware volume control is used, to give more resilience to timing problems, sudden processor loading, etc. This is especially useful if you are processing metadata and artwork on the same machine.
|
||||
* Extra metadata: when a play session starts, the "Active-Remote" and "DACP-ID" fields -- information that can be used to identify the source -- are provided as metadata, with the type `ssnc` and the codes `acre` and `daid` respectively. The IDs are provided as strings.
|
||||
* Unencrypted audio data. The iOS player "Whaale" attempts to send unencrypted audio, presumably to save processing effort; if unsuccessful, it will send encrypted audio as normal. Shairport Sync now recognises and handles unencrypted audio data. (Apparently it always advertised that it could process unencrypted audio!)
|
||||
* Handle retransmitted audio in the control channel. When a packet of audio is missed, Shairport Sync will ask for it to be retransmitted. Normally the retransmitted audio comes back the audio channel, but "Whaale" sends it back in the control channel. (I think this is a bug in "Whaale".) Shairport Sync will now correctly handle retransmitted audio packets coming back in the control channel.
|
||||
* Bugfixes
|
||||
* Generate properly-formed `<item>..</item>` items of information.
|
||||
|
||||
Version 2.3.4
|
||||
----
|
||||
* Enhancement
|
||||
* When a play session starts, Shairport Sync opens three UDP ports to communicate with the source. Until now, those ports could be any high numbered port. Now, they are located within a range of 100 port locations starting at port 6001. The starting port and the port range are settable by two new general settings in `/etc/shairport-sync.conf` -- `udp_port_base` (default 6001) and `udp_port_range` (default 100). To retain the previous behaviour, set the `udp_port_base` to `0`.
|
||||
* Bugfixes
|
||||
* Fix an out-of-stack-space error that can occur in certain cases (thanks to https://github.com/joerg-krause).
|
||||
* Fix a couple of compiler warnings (thanks to https://github.com/joerg-krause).
|
||||
* Tidy up a couple of debug messages that were emitting misleading information.
|
||||
|
||||
Version 2.3.3.2
|
||||
----
|
||||
* Bugfix -- fixed an error in the sample configuration file.
|
||||
|
||||
Version 2.3.3.1
|
||||
----
|
||||
* Enhancement
|
||||
* Metadata format has changed slightly -- the format of each item is now `<item><type>..</type><code>..</code><length>..</length><data..>..</data></item>`, where the `<data..>..</data>` part is present if the length is non-zero. The change is that everything is now enclosed in an `<item>..</item>` pair.
|
||||
|
||||
Version 2.3.2 and 2.3.3
|
||||
----
|
||||
These releases were faulty and have been deleted.
|
||||
|
||||
Version 2.3.1
|
||||
-----
|
||||
Some big changes "under the hood" have been made, leading to limited support for unsynchronised output to `stdout` or to a named pipe and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided.
|
||||
|
||||
In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`.
|
||||
This gives more flexibility in adding features gives better compatability across different versions of Linux.
|
||||
Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file.
|
||||
|
||||
Note that, for the present, settings in the configuration will have priority over command line options for Shairport Sync itself, in contravention of the normal unix convention. Audio back end command line options, i.e. those after the `--`, have priority over configuration file settings for the audio backends.
|
||||
|
||||
In moving to the the use of a configuration file, some "housekeeping" is being done -- some logical corrections and other small changes are being made to option names and modes of operations, so the settings in the configuration file do not exactly match command line options.
|
||||
|
||||
When `make install` is executed, a sample configuration is installed or updated at `/etc/shairport-sync.conf.sample`. The same file is also installed as `/etc/shairport-sync.conf` if that file doesn't already exist. To prevent the configuration files being installed, use the configuration option `--without-configfiles`.
|
||||
|
||||
* Pesky Change You Must Do Something About
|
||||
|
||||
If you are using metadata, please note that the option has changed somewhat. The option `-M` has a new long name equivalent: `--metadata-pipename` and the argument you provide must now be the full name of the metadata pipe, e.g. `-M /tmp/shairport-sync-metadata`.
|
||||
|
||||
* Enhancements
|
||||
* Shairport Sync now reads settings from the configuration file `/etc/shairport-sync.conf`. This has settings for most command-line options and it's where any new settings will go. A default configuration file will be installed if one doesn't exist, and a sample file configuration file is always installed or updated. Details of settings are provided in the sample file. Shairport Sync relies on the `libconfig` library to read configuration files. For the present, you can disable the new feature (and save the space taken up by `libconfig`) by using the configure option `--without-configfile-support`.
|
||||
* New command-line option `-c <file>` or `--configfile=<file>` allows you to specify a configuration file other than `/etc/shairport-sync.conf`.
|
||||
* Session Timeout and Allow Session Interruption can now be set independently. This is really some "housekeeping" as referred to above -- it's a kind of a bug fix, where the bug in question is an inappropriate connection of the setting of two parameters. To explain: (1) By default, when a source such as iTunes starts playing to the Shairport Sync device, any other source attempting to start a play session receives a "busy" signal. If a source disappears without warning, Shairport Sync will wait for 120 seconds before dropping the session and allowing another source to start a play session. (2) The command-line option `-t` or `--timeout` allows you to set the wait time before dropping the session. If you set this parameter to `0`, Shairport Sync will not send a "busy" signal, thus allowing another source to interrupt an existing one. (3) The problem is that if you set the parameter to `0`, a session will never be dropped if the source disappears without warning.
|
||||
The (obvious) fix for this is to separate the setting of the two parameters, and this is now done in the configuration file `/etc/shairport-sync.conf` -- please see the settings `allow_session_interruption` and `session_timeout`. The behaviour of the `-t` and `--timeout` command-line options is unchanged but deprecated.
|
||||
* New Option -- "Ignore Volume Control" ('ignore_volume_control'). If you set this to "yes", the output from Shairport Sync is always set at 100%. This is useful when you want to set the volume locally. Available via the settings file only.
|
||||
* Statistics option correctly reports when no frames are received in a sampling interval and when output is not being synchronised.
|
||||
* A new, supported audio back end called `stdout` provides raw 16-bit 44.1kHz stereo PCM output. To activate, set `output_backend = "stdout"` in the general section of the configuration file. Output is provided synchronously with the source feed. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-stdout`.
|
||||
* Support for the `pipe` back end has been enhanced to provide raw 16-bit 44.1kHz stereo PCM output to a named pipe. To activate, set `output_backend = "pipe"` in the general section of the configuration and give the fully-specified pathname to the pipe in the pipe section of the configuration file -- see `etc/shairport-sync.conf.sample` for an example. No stuffing or stripping is done. If you are feeding it to an output device that runs slower or faster, you'll eventually get buffer overflow or underflow in that device. To include support for this back end, use the configuration option `--with-pipe`.
|
||||
* Support for the `dummy` audio backend device continues. To activate, set `output_backend = "dummy"` in in the general section of the configuration. To include support for this back end, use the configuration option `--with-dummy`.
|
||||
* Limited support for the PulseAudio audio backend continues. To activate, set `output_backend = "pulse"` in in the general section of the configuration. You must still enter its settings via the command line, after the `--` as before. Note that no stuffing or stripping is done: if the PulseAudio sink runs slower or faster, you'll eventually get buffer overflow or underflow.
|
||||
* New backend-specific settings are provided for setting the size of the backend's buffer and for adding or removing a fixed offset to the overall latency. The `audio_backend_buffer_desired_length` default is 6615 frames, or 0.15 seconds. On some slower machines, particularly with metadata processing going on, the DAC buffer can underflow on this setting, so it might be worth making the buffer larger. A problem on software mixers only is that changes to volume control settings have to propagate through the buffer to be heard, so the larger the buffer, the longer the response time. If you're using an alsa back end and are using a hardware mixers, this isn't a problem. The `audio_backend_latency_offset` allows you emit frames to the audio back end some time before or after the synchronised time. This would be useful, for example, if you are outputting to a device that takes 20 ms to process audio; yoou would specify a `audio_backend_latency_offset = -882`, where 882 is the number of frames in 20 ms, to compensate for the device delay.
|
||||
|
||||
Version 2.3
|
||||
-----
|
||||
* Enhancements
|
||||
* Adding the System V startup script (the "initscript") is now a configuration option. The default is to include it, so if you want to omit the installation of the initscript, add the configuration option `--without-initscript`.
|
||||
* Metadata support is now a compile-time option: `--with-metadata`.
|
||||
* A metadata feed has been added. Use the option `-M <pipe-directory>`, e.g. `-M /tmp`. Shairport Sync will provide metadata in a pipe called `<pipe-directory>/shairport-sync-metadata`. (This is changed in 2.3.1.) There's a sample metadata reader at https://github.com/mikebrady/shairport-sync-metadata-reader. The format of the metadata is a mixture of XML-style tags, 4-character codes and base64 data. Please look at `rtsp.c` and `player.c` for examples. Please note that the format of the metadata may change.
|
||||
Beware: there appears to be a serious bug in iTunes before 12.1.2, such that it may stall for a long period when sending large (more than a few hundred kilobytes) coverart images.
|
||||
|
||||
* Bugfix
|
||||
* Fix a bug when compiling for Arch Linux on Raspberry Pi 2 (thanks to https://github.com/joaodriessen).
|
||||
* Fix a bug whereby if the ANNOUNCE and/or SETUP method fails, the play_lock mutex is never unlocked, thus blocking other clients from connecting. This can affect all types of users, but particularly Pulseaudio users. (Thanks to https://github.com/jclehner.)
|
||||
* Modify the init script to start after all services are ready. Add in a commented-out sleep command if users find it necessary (thanks to https://github.com/BNoiZe).
|
||||
* Two memory leaks fixed (thanks to https://github.com/pdgendt).
|
||||
* An error handling time specifications for flushes was causing an audible glitch when pausing and resuming some tracks. This has been fixed (thanks to https://github.com/Hamster128).
|
||||
|
||||
Version 2.2.5
|
||||
-----
|
||||
* Bugfixes
|
||||
@@ -16,9 +192,6 @@ Version 2.2.3
|
||||
* Bugfixes
|
||||
* Fix a bug when compiling for Arch Linux on Raspberry Pi 2 (thanks to https://github.com/joaodriessen).
|
||||
* Fix a compiler warning (thanks to https://github.com/sdigit).
|
||||
* Modify the init script to start after all services are ready. Add in a commented-out sleep command if users find it necessary (thanks to https://github.com/BNoiZe).
|
||||
* Two memory leaks fixed (thanks to https://github.com/pdgendt).
|
||||
* An error handling time specifications for flushes was causing an audible glitch when pausing and resuming some tracks. This has been fixed (thanks to https://github.com/Hamster128).
|
||||
|
||||
Version 2.2.2
|
||||
-----
|
||||
|
||||
@@ -41,7 +41,15 @@ extern audio_output audio_pulse;
|
||||
#ifdef CONFIG_ALSA
|
||||
extern audio_output audio_alsa;
|
||||
#endif
|
||||
extern audio_output audio_dummy, audio_pipe;
|
||||
#ifdef CONFIG_DUMMY
|
||||
extern audio_output audio_dummy;
|
||||
#endif
|
||||
#ifdef CONFIG_PIPE
|
||||
extern audio_output audio_pipe;
|
||||
#endif
|
||||
#ifdef CONFIG_STDOUT
|
||||
extern audio_output audio_stdout;
|
||||
#endif
|
||||
|
||||
static audio_output *outputs[] = {
|
||||
#ifdef CONFIG_SNDIO
|
||||
@@ -56,36 +64,41 @@ static audio_output *outputs[] = {
|
||||
#ifdef CONFIG_AO
|
||||
&audio_ao,
|
||||
#endif
|
||||
#ifdef CONFIG_DUMMY
|
||||
&audio_dummy,
|
||||
#endif
|
||||
#ifdef CONFIG_PIPE
|
||||
&audio_pipe,
|
||||
NULL
|
||||
};
|
||||
|
||||
#endif
|
||||
#ifdef CONFIG_STDOUT
|
||||
&audio_stdout,
|
||||
#endif
|
||||
NULL};
|
||||
|
||||
audio_output *audio_get_output(char *name) {
|
||||
audio_output **out;
|
||||
audio_output **out;
|
||||
|
||||
// default to the first
|
||||
if (!name)
|
||||
return outputs[0];
|
||||
// default to the first
|
||||
if (!name)
|
||||
return outputs[0];
|
||||
|
||||
for (out=outputs; *out; out++)
|
||||
if (!strcasecmp(name, (*out)->name))
|
||||
return *out;
|
||||
for (out = outputs; *out; out++)
|
||||
if (!strcasecmp(name, (*out)->name))
|
||||
return *out;
|
||||
|
||||
return NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void audio_ls_outputs(void) {
|
||||
audio_output **out;
|
||||
audio_output **out;
|
||||
|
||||
printf("Available audio outputs:\n");
|
||||
for (out=outputs; *out; out++)
|
||||
printf(" %s%s\n", (*out)->name, out==outputs ? " (default)" : "");
|
||||
printf("Available audio outputs:\n");
|
||||
for (out = outputs; *out; out++)
|
||||
printf(" %s%s\n", (*out)->name, out == outputs ? " (default)" : "");
|
||||
|
||||
for (out=outputs; *out; out++) {
|
||||
printf("\n");
|
||||
printf("Options for output %s:\n", (*out)->name);
|
||||
(*out)->help();
|
||||
}
|
||||
for (out = outputs; *out; out++) {
|
||||
printf("\n");
|
||||
printf("Options for output %s:\n", (*out)->name);
|
||||
(*out)->help();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define _AUDIO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <libconfig.h>
|
||||
|
||||
typedef struct {
|
||||
double airplay_volume;
|
||||
@@ -14,33 +15,33 @@ typedef struct {
|
||||
} audio_parameters;
|
||||
|
||||
typedef struct {
|
||||
void (*help)(void);
|
||||
char *name;
|
||||
void (*help)(void);
|
||||
char *name;
|
||||
|
||||
// start of program
|
||||
int (*init)(int argc, char **argv);
|
||||
// at end of program
|
||||
void (*deinit)(void);
|
||||
// start of program
|
||||
int (*init)(int argc, char **argv);
|
||||
// at end of program
|
||||
void (*deinit)(void);
|
||||
|
||||
void (*start)(int sample_rate);
|
||||
|
||||
// block of samples
|
||||
void (*play)(short buf[], int samples);
|
||||
void (*stop)(void);
|
||||
|
||||
// may be null if not implemented
|
||||
void (*flush)(void);
|
||||
|
||||
// returns the delay before the next frame to be sent to the device would actually be audible.
|
||||
// almost certainly wrong if the buffer is empty, so put silent buffers into it to make it busy.
|
||||
// will change dynamically, so keep watching it. Implemented in ALSA only.
|
||||
uint32_t (*delay)();
|
||||
void (*start)(int sample_rate);
|
||||
|
||||
// may be NULL, in which case soft volume is applied
|
||||
void (*volume)(double vol);
|
||||
|
||||
// may be NULL, in which case soft volume parameters are used
|
||||
void (*parameters)(audio_parameters* info);
|
||||
// block of samples
|
||||
void (*play)(short buf[], int samples);
|
||||
void (*stop)(void);
|
||||
|
||||
// may be null if not implemented
|
||||
void (*flush)(void);
|
||||
|
||||
// returns the delay before the next frame to be sent to the device would actually be audible.
|
||||
// almost certainly wrong if the buffer is empty, so put silent buffers into it to make it busy.
|
||||
// will change dynamically, so keep watching it. Implemented in ALSA only.
|
||||
uint32_t (*delay)();
|
||||
|
||||
// may be NULL, in which case soft volume is applied
|
||||
void (*volume)(double vol);
|
||||
|
||||
// may be NULL, in which case soft volume parameters are used
|
||||
void (*parameters)(audio_parameters *info);
|
||||
} audio_output;
|
||||
|
||||
audio_output *audio_get_output(char *name);
|
||||
|
||||
+193
-130
@@ -43,9 +43,9 @@ static void stop(void);
|
||||
static void flush(void);
|
||||
static uint32_t delay(void);
|
||||
static void volume(double vol);
|
||||
static void parameters(audio_parameters* info);
|
||||
static int has_mute=0;
|
||||
static int has_db_vol=0;
|
||||
static void parameters(audio_parameters *info);
|
||||
static int has_mute = 0;
|
||||
static int has_db_vol = 0;
|
||||
static double set_volume;
|
||||
|
||||
audio_output audio_alsa = {
|
||||
@@ -58,7 +58,7 @@ audio_output audio_alsa = {
|
||||
.flush = &flush,
|
||||
.delay = &delay,
|
||||
.play = &play,
|
||||
.volume = NULL, // to be set later on...
|
||||
.volume = NULL, // to be set later on...
|
||||
.parameters = NULL // to be set later on...
|
||||
};
|
||||
|
||||
@@ -81,58 +81,114 @@ static char *alsa_mix_ctrl = "Master";
|
||||
static int alsa_mix_index = 0;
|
||||
|
||||
static int play_number;
|
||||
static int64_t accumulated_delay,accumulated_da_delay;
|
||||
static int64_t accumulated_delay, accumulated_da_delay;
|
||||
|
||||
static void help(void) {
|
||||
printf(" -d output-device set the output device [default*|...]\n"
|
||||
" -t mixer-type set the mixer type [software*|hardware]\n"
|
||||
" -m mixer-device set the mixer device ['output-device'*|...]\n"
|
||||
" -c mixer-control set the mixer control [Master*|...]\n"
|
||||
" -i mixer-index set the mixer index [0*|...]\n"
|
||||
" *) default option\n"
|
||||
);
|
||||
printf(" -d output-device set the output device [default*|...]\n"
|
||||
" -m mixer-device set the mixer device ['output-device'*|...]\n"
|
||||
" -c mixer-control set the mixer control [Master*|...]\n"
|
||||
" -i mixer-index set the mixer index [0*|...]\n"
|
||||
" *) default option\n");
|
||||
}
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
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
|
||||
|
||||
|
||||
|
||||
// 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 ((value < 0) || (value > 66150))
|
||||
die("Invalid alsa audio backend buffer desired length (software) \"%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 ((value < -66150) || (value > 66150))
|
||||
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;
|
||||
}
|
||||
|
||||
/* Get the Output Device Name. */
|
||||
if (config_lookup_string(config.cfg, "alsa.output_device", &str)) {
|
||||
alsa_out_dev = (char *)str;
|
||||
}
|
||||
|
||||
|
||||
/* Get the Mixer Type setting. */
|
||||
|
||||
if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) {
|
||||
inform("The alsa mixer_type setting is deprecated and has been ignored. FYI, using the \"mixer_control_name\" setting automatically chooses a hardware mixer.");
|
||||
}
|
||||
|
||||
|
||||
/* Get the Mixer Device Name. */
|
||||
if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) {
|
||||
alsa_mix_dev = (char *)str;
|
||||
}
|
||||
|
||||
/* Get the Mixer Control Name. */
|
||||
if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) {
|
||||
alsa_mix_ctrl = (char *)str;
|
||||
hardware_mixer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
|
||||
argv--; // so we shift the arguments to satisfy getopt()
|
||||
argc++;
|
||||
// some platforms apparently require optreset = 1; - which?
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
alsa_out_dev = optarg;
|
||||
break;
|
||||
case 't':
|
||||
if (strcmp(optarg, "hardware") == 0)
|
||||
hardware_mixer = 1;
|
||||
break;
|
||||
case 'm':
|
||||
alsa_mix_dev = optarg;
|
||||
break;
|
||||
case 'c':
|
||||
alsa_mix_ctrl = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
alsa_mix_index = strtol(optarg, NULL, 10);
|
||||
break;
|
||||
default:
|
||||
help();
|
||||
die("Invalid audio option -%c specified", opt);
|
||||
}
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
alsa_out_dev = optarg;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
inform("The alsa backend -t option is deprecated and has been ignored. FYI, using the -c option automatically chooses a hardware mixer.");
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
alsa_mix_dev = optarg;
|
||||
break;
|
||||
case 'c':
|
||||
alsa_mix_ctrl = optarg;
|
||||
hardware_mixer = 1;
|
||||
break;
|
||||
case 'i':
|
||||
alsa_mix_index = strtol(optarg, NULL, 10);
|
||||
break;
|
||||
default:
|
||||
help();
|
||||
die("Invalid audio option -%c specified", opt);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
die("Invalid audio argument: %s", argv[optind]);
|
||||
die("Invalid audio argument: %s", argv[optind]);
|
||||
|
||||
if (!hardware_mixer)
|
||||
return 0;
|
||||
return 0;
|
||||
|
||||
if (alsa_mix_dev == NULL)
|
||||
alsa_mix_dev = alsa_out_dev;
|
||||
alsa_mix_dev = alsa_out_dev;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
@@ -141,39 +197,41 @@ static int init(int argc, char **argv) {
|
||||
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");
|
||||
die("Failed to open mixer");
|
||||
if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0)
|
||||
die ("Failed to attach mixer");
|
||||
die("Failed to attach mixer");
|
||||
if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0)
|
||||
die ("Failed to register mixer element");
|
||||
die("Failed to register mixer element");
|
||||
|
||||
ret = snd_mixer_load(alsa_mix_handle);
|
||||
if (ret < 0)
|
||||
die ("Failed to load mixer element");
|
||||
die("Failed to load mixer element");
|
||||
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)
|
||||
debug(1,"Can't read mixer's [linear] min and max volumes.");
|
||||
die("Failed to find mixer element");
|
||||
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_ask_playback_vol_dB(alsa_mix_elem,alsa_mix_minv,&alsa_mix_mindb)==0) &&
|
||||
(snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem,alsa_mix_maxv,&alsa_mix_maxdb)==0)) {
|
||||
has_db_vol=1;
|
||||
if ((snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv, &alsa_mix_mindb) == 0) &&
|
||||
(snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_maxv, &alsa_mix_maxdb) == 0)) {
|
||||
has_db_vol = 1;
|
||||
audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
|
||||
audio_alsa.parameters = ¶meters; // likewise the parameters stuff
|
||||
if (alsa_mix_mindb==-9999999) {
|
||||
if (alsa_mix_mindb == -9999999) {
|
||||
// trying to say that the lowest vol is mute, maybe? Raspberry Pi does this
|
||||
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.");
|
||||
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);
|
||||
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 {
|
||||
debug(1,"Hardware mixer does not have dB volume -- not used.");
|
||||
debug(1, "Hardware mixer does not have dB volume -- not used.");
|
||||
}
|
||||
}
|
||||
if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) {
|
||||
has_mute=1;
|
||||
debug(1,"Has mute ability.");
|
||||
has_mute = 1;
|
||||
debug(1, "Has mute ability.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -181,20 +239,20 @@ static int init(int argc, char **argv) {
|
||||
static void deinit(void) {
|
||||
stop();
|
||||
if (alsa_mix_handle) {
|
||||
snd_mixer_close(alsa_mix_handle);
|
||||
snd_mixer_close(alsa_mix_handle);
|
||||
}
|
||||
}
|
||||
|
||||
int open_alsa_device(void) {
|
||||
int open_alsa_device(void) {
|
||||
|
||||
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 = frames * 4;
|
||||
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));
|
||||
return (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);
|
||||
@@ -208,75 +266,79 @@ int open_alsa_device(void) {
|
||||
if (ret < 0) {
|
||||
die("unable to set hw parameters: %s.", 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);
|
||||
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);
|
||||
}
|
||||
return(0);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void start(int sample_rate) {
|
||||
if (sample_rate != 44100)
|
||||
die("Unexpected sample rate %d -- only 44,100 supported!",sample_rate);
|
||||
die("Unexpected sample rate %d -- only 44,100 supported!", sample_rate);
|
||||
desired_sample_rate = sample_rate; // must be a variable
|
||||
}
|
||||
|
||||
static uint32_t delay() {
|
||||
if (alsa_handle==NULL) {
|
||||
return 0;
|
||||
} else {
|
||||
pthread_mutex_lock(&alsa_mutex);
|
||||
snd_pcm_sframes_t current_avail,current_delay = 0;
|
||||
int derr,ignore;
|
||||
if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING) {
|
||||
derr = snd_pcm_avail_delay(alsa_handle,¤t_avail,¤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);
|
||||
current_delay=-1;
|
||||
}
|
||||
} else if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED) {
|
||||
current_delay=0;
|
||||
} else {
|
||||
if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_XRUN)
|
||||
current_delay=0;
|
||||
else {
|
||||
current_delay=-1;
|
||||
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);
|
||||
debug(1,"Error preparing after delay error: %s.", snd_strerror(derr));
|
||||
current_delay = -1;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&alsa_mutex);
|
||||
return current_delay;
|
||||
if (alsa_handle == NULL) {
|
||||
return 0;
|
||||
} else {
|
||||
pthread_mutex_lock(&alsa_mutex);
|
||||
snd_pcm_sframes_t current_avail, current_delay = 0;
|
||||
int derr, ignore;
|
||||
if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING) {
|
||||
derr = snd_pcm_avail_delay(alsa_handle, ¤t_avail, ¤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);
|
||||
current_delay = -1;
|
||||
}
|
||||
} else if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) {
|
||||
current_delay = 0;
|
||||
} else {
|
||||
if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN)
|
||||
current_delay = 0;
|
||||
else {
|
||||
current_delay = -1;
|
||||
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);
|
||||
debug(1, "Error preparing after delay error: %s.", snd_strerror(derr));
|
||||
current_delay = -1;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&alsa_mutex);
|
||||
return current_delay;
|
||||
}
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
int ret = 0;
|
||||
if (alsa_handle==NULL) {
|
||||
ret = open_alsa_device();
|
||||
if ((ret==0) && (audio_alsa.volume))
|
||||
volume(set_volume);
|
||||
}
|
||||
if (ret==0) {
|
||||
if (alsa_handle == NULL) {
|
||||
ret = open_alsa_device();
|
||||
if ((ret == 0) && (audio_alsa.volume))
|
||||
volume(set_volume);
|
||||
}
|
||||
if (ret == 0) {
|
||||
pthread_mutex_lock(&alsa_mutex);
|
||||
snd_pcm_sframes_t current_delay = 0;
|
||||
int err,ignore;
|
||||
if ((snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED) || (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING)) {
|
||||
err = snd_pcm_writei(alsa_handle, (char*)buf, samples);
|
||||
int err, ignore;
|
||||
if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
|
||||
(snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) {
|
||||
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.",snd_pcm_state(alsa_handle));
|
||||
debug(1, "Error -- ALSA device in incorrect state (%d) for play.",
|
||||
snd_pcm_state(alsa_handle));
|
||||
if (err = snd_pcm_prepare(alsa_handle)) {
|
||||
ignore = snd_pcm_recover(alsa_handle, err, 0);
|
||||
debug(1,"Error preparing after play error: %s.", snd_strerror(err));
|
||||
debug(1, "Error preparing after play error: %s.", snd_strerror(err));
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&alsa_mutex);
|
||||
@@ -288,20 +350,21 @@ static void flush(void) {
|
||||
if (alsa_handle) {
|
||||
// debug(1,"Dropping frames for flush...");
|
||||
if (derr = snd_pcm_drop(alsa_handle))
|
||||
debug(1,"Error dropping frames: %s.", snd_strerror(derr));
|
||||
debug(1, "Error dropping frames: %s.", snd_strerror(derr));
|
||||
// debug(1,"Dropped frames ok. State is %d.",snd_pcm_state(alsa_handle));
|
||||
if (derr = snd_pcm_prepare(alsa_handle))
|
||||
debug(1,"Error preparing after flush: %s.", snd_strerror(derr));
|
||||
debug(1, "Error preparing after flush: %s.", snd_strerror(derr));
|
||||
// debug(1,"Frames successfully dropped.");
|
||||
/*
|
||||
if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_PREPARED)
|
||||
debug(1,"Flush returns to SND_PCM_STATE_PREPARED state.");
|
||||
debug(1,"Flush returns to SND_PCM_STATE_PREPARED state.");
|
||||
if (snd_pcm_state(alsa_handle)==SND_PCM_STATE_RUNNING)
|
||||
debug(1,"Flush returns to SND_PCM_STATE_RUNNING state.");
|
||||
*/
|
||||
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));
|
||||
|
||||
*/
|
||||
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));
|
||||
|
||||
// flush also closes the device
|
||||
snd_pcm_close(alsa_handle);
|
||||
alsa_handle = NULL;
|
||||
@@ -309,29 +372,29 @@ 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
|
||||
// so we should flush first
|
||||
flush(); // flush will also close the device
|
||||
// close_alsa_device();
|
||||
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
|
||||
// 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;
|
||||
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);
|
||||
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) {
|
||||
set_volume=vol;
|
||||
double vol_setting = vol2attn(vol,alsa_mix_maxdb,alsa_mix_mindb);
|
||||
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));
|
||||
die("Failed to set playback dB volume");
|
||||
if (has_mute)
|
||||
snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, (vol != -144.0));
|
||||
}
|
||||
|
||||
+73
-76
@@ -24,7 +24,6 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <memory.h>
|
||||
@@ -35,98 +34,96 @@
|
||||
ao_device *dev = NULL;
|
||||
|
||||
static void help(void) {
|
||||
printf(" -d driver set the output driver\n"
|
||||
" -o name=value set an arbitrary ao option\n"
|
||||
" -i id shorthand for -o id=<id>\n"
|
||||
" -n name shorthand for -o dev=<name> -o dsp=<name>\n"
|
||||
);
|
||||
printf(" -d driver set the output driver\n"
|
||||
" -o name=value set an arbitrary ao option\n"
|
||||
" -i id shorthand for -o id=<id>\n"
|
||||
" -n name shorthand for -o dev=<name> -o dsp=<name>\n");
|
||||
}
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
ao_initialize();
|
||||
int driver = ao_default_driver_id();
|
||||
ao_option *ao_opts = NULL;
|
||||
ao_initialize();
|
||||
int driver = ao_default_driver_id();
|
||||
ao_option *ao_opts = NULL;
|
||||
|
||||
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
|
||||
argv--; // so we shift the arguments to satisfy getopt()
|
||||
argc++;
|
||||
// some platforms apparently require optreset = 1; - which?
|
||||
int opt;
|
||||
char *mid;
|
||||
while ((opt = getopt(argc, argv, "d:i:n:o:")) > 0) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
driver = ao_driver_id(optarg);
|
||||
if (driver < 0)
|
||||
die("could not find ao driver %s", optarg);
|
||||
break;
|
||||
case 'i':
|
||||
ao_append_option(&ao_opts, "id", optarg);
|
||||
break;
|
||||
case 'n':
|
||||
ao_append_option(&ao_opts, "dev", optarg);
|
||||
// Old libao versions (for example, 0.8.8) only support
|
||||
// "dsp" instead of "dev".
|
||||
ao_append_option(&ao_opts, "dsp", optarg);
|
||||
break;
|
||||
case 'o':
|
||||
mid = strchr(optarg, '=');
|
||||
if (!mid)
|
||||
die("Expected an = in audio option %s", optarg);
|
||||
*mid = 0;
|
||||
ao_append_option(&ao_opts, optarg, mid+1);
|
||||
break;
|
||||
default:
|
||||
help();
|
||||
die("Invalid audio option -%c specified", opt);
|
||||
}
|
||||
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;
|
||||
while ((opt = getopt(argc, argv, "d:i:n:o:")) > 0) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
driver = ao_driver_id(optarg);
|
||||
if (driver < 0)
|
||||
die("could not find ao driver %s", optarg);
|
||||
break;
|
||||
case 'i':
|
||||
ao_append_option(&ao_opts, "id", optarg);
|
||||
break;
|
||||
case 'n':
|
||||
ao_append_option(&ao_opts, "dev", optarg);
|
||||
// Old libao versions (for example, 0.8.8) only support
|
||||
// "dsp" instead of "dev".
|
||||
ao_append_option(&ao_opts, "dsp", optarg);
|
||||
break;
|
||||
case 'o':
|
||||
mid = strchr(optarg, '=');
|
||||
if (!mid)
|
||||
die("Expected an = in audio option %s", optarg);
|
||||
*mid = 0;
|
||||
ao_append_option(&ao_opts, optarg, mid + 1);
|
||||
break;
|
||||
default:
|
||||
help();
|
||||
die("Invalid audio option -%c specified", opt);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
die("Invalid audio argument: %s", argv[optind]);
|
||||
if (optind < argc)
|
||||
die("Invalid audio argument: %s", argv[optind]);
|
||||
|
||||
ao_sample_format fmt;
|
||||
memset(&fmt, 0, sizeof(fmt));
|
||||
ao_sample_format fmt;
|
||||
memset(&fmt, 0, sizeof(fmt));
|
||||
|
||||
fmt.bits = 16;
|
||||
fmt.rate = 44100;
|
||||
fmt.channels = 2;
|
||||
fmt.byte_format = AO_FMT_NATIVE;
|
||||
fmt.bits = 16;
|
||||
fmt.rate = 44100;
|
||||
fmt.channels = 2;
|
||||
fmt.byte_format = AO_FMT_NATIVE;
|
||||
|
||||
dev = ao_open_live(driver, &fmt, ao_opts);
|
||||
dev = ao_open_live(driver, &fmt, ao_opts);
|
||||
|
||||
return dev ? 0 : 1;
|
||||
return dev ? 0 : 1;
|
||||
}
|
||||
|
||||
static void deinit(void) {
|
||||
if (dev)
|
||||
ao_close(dev);
|
||||
dev = NULL;
|
||||
ao_shutdown();
|
||||
if (dev)
|
||||
ao_close(dev);
|
||||
dev = NULL;
|
||||
ao_shutdown();
|
||||
}
|
||||
|
||||
static void start(int sample_rate) {
|
||||
if (sample_rate != 44100)
|
||||
die("unexpected sample rate!");
|
||||
if (sample_rate != 44100)
|
||||
die("unexpected sample rate!");
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
ao_play(dev, (char*)buf, samples*4);
|
||||
}
|
||||
static void play(short buf[], int samples) { ao_play(dev, (char *)buf, samples * 4); }
|
||||
|
||||
static void stop(void) {
|
||||
}
|
||||
static void stop(void) {}
|
||||
|
||||
audio_output audio_ao = {
|
||||
.name = "ao",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL
|
||||
};
|
||||
audio_output audio_ao = {.name = "ao",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL};
|
||||
|
||||
+21
-44
@@ -27,60 +27,37 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include "common.h"
|
||||
#include "audio.h"
|
||||
|
||||
int Fs;
|
||||
long long starttime, samples_played;
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
static int init(int argc, char **argv) { return 0; }
|
||||
|
||||
static void deinit(void) {
|
||||
}
|
||||
static void deinit(void) {}
|
||||
|
||||
static void start(int sample_rate) {
|
||||
Fs = sample_rate;
|
||||
starttime = 0;
|
||||
samples_played = 0;
|
||||
printf("dummy audio output started at Fs=%d Hz\n", sample_rate);
|
||||
Fs = sample_rate;
|
||||
starttime = 0;
|
||||
samples_played = 0;
|
||||
debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
struct timeval tv;
|
||||
static void play(short buf[], int samples) {}
|
||||
|
||||
// this is all a bit expensive but it's long-term stable.
|
||||
gettimeofday(&tv, NULL);
|
||||
static void stop(void) { debug(1, "dummy audio stopped\n"); }
|
||||
|
||||
long long nowtime = tv.tv_usec + 1e6*tv.tv_sec;
|
||||
static void help(void) { printf(" There are no options for dummy audio.\n"); }
|
||||
|
||||
if (!starttime)
|
||||
starttime = nowtime;
|
||||
|
||||
samples_played += samples;
|
||||
|
||||
long long finishtime = starttime + samples_played * 1e6 / Fs;
|
||||
usleep(finishtime - nowtime);
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
printf("dummy audio stopped\n");
|
||||
}
|
||||
|
||||
static void help(void) {
|
||||
printf(" There are no options for dummy audio.\n");
|
||||
}
|
||||
|
||||
audio_output audio_dummy = {
|
||||
.name = "dummy",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL
|
||||
};
|
||||
audio_output audio_dummy = {.name = "dummy",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL};
|
||||
|
||||
+76
-31
@@ -29,6 +29,9 @@
|
||||
#include <fcntl.h>
|
||||
#include <memory.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include "common.h"
|
||||
#include "audio.h"
|
||||
|
||||
@@ -37,55 +40,97 @@ static int fd = -1;
|
||||
char *pipename = NULL;
|
||||
|
||||
static void start(int sample_rate) {
|
||||
fd = open(pipename, O_WRONLY);
|
||||
if (fd < 0) {
|
||||
perror("open");
|
||||
die("could not open specified pipe for writing");
|
||||
}
|
||||
// this will leave fd as -1 if a reader hasn't been attached
|
||||
fd = open(pipename, O_WRONLY | O_NONBLOCK);
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
int ignore = write(fd, buf, samples*4);
|
||||
// if the file is not open, try to open it.
|
||||
if (fd == -1) {
|
||||
fd = open(pipename, O_WRONLY | O_NONBLOCK);
|
||||
}
|
||||
// if it's got a reader, write to it.
|
||||
if (fd != -1) {
|
||||
int ignore = non_blocking_write(fd, buf, samples * 4);
|
||||
}
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
close(fd);
|
||||
// Don't close the pipe just because a play session has stopped.
|
||||
// if (fd > 0)
|
||||
// close(fd);
|
||||
}
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
if (argc != 1)
|
||||
die("bad argument(s) to pipe");
|
||||
debug(1, "pipe init");
|
||||
const char *str;
|
||||
int value;
|
||||
|
||||
config.audio_backend_buffer_desired_length = 44100; // one second.
|
||||
config.audio_backend_latency_offset = 0;
|
||||
|
||||
if (config.cfg != NULL) {
|
||||
/* Get the Output Pipename. */
|
||||
const char *str;
|
||||
if (config_lookup_string(config.cfg, "pipe.name", &str)) {
|
||||
pipename = (char *)str;
|
||||
}
|
||||
|
||||
if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0))
|
||||
die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead.");
|
||||
|
||||
/* Get the desired buffer size setting. */
|
||||
if (config_lookup_int(config.cfg, "pipe.audio_backend_buffer_desired_length", &value)) {
|
||||
if ((value < 0) || (value > 132300))
|
||||
die("Invalid pipe audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100",
|
||||
value);
|
||||
else
|
||||
config.audio_backend_buffer_desired_length = value;
|
||||
}
|
||||
|
||||
/* Get the latency offset. */
|
||||
if (config_lookup_int(config.cfg, "pipe.audio_backend_latency_offset", &value)) {
|
||||
if ((value < -66150) || (value > 66150))
|
||||
die("Invalid pipe audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0",
|
||||
value);
|
||||
else
|
||||
config.audio_backend_latency_offset = value;
|
||||
}
|
||||
}
|
||||
|
||||
if ((pipename == NULL) && (argc != 1))
|
||||
die("bad or missing argument(s) to pipe");
|
||||
|
||||
if (argc == 1)
|
||||
pipename = strdup(argv[0]);
|
||||
|
||||
|
||||
// here, create the pipe
|
||||
if (mkfifo(pipename, 0644) && errno != EEXIST)
|
||||
die("Could not create output pipe \"%s\"", pipename);
|
||||
|
||||
// test open pipe so we error on startup if it's going to fail
|
||||
start(44100);
|
||||
stop();
|
||||
debug(1, "Pipename is \"%s\"", pipename);
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void deinit(void) {
|
||||
if (fd > 0)
|
||||
close(fd);
|
||||
if (pipename)
|
||||
free(pipename);
|
||||
if (fd > 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void help(void) {
|
||||
printf(" pipe takes 1 argument: the name of the FIFO to write to.\n");
|
||||
printf(" pipe takes 1 argument: the name of the FIFO to write to.\n");
|
||||
}
|
||||
|
||||
audio_output audio_pipe = {
|
||||
.name = "pipe",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL
|
||||
};
|
||||
audio_output audio_pipe = {.name = "pipe",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL};
|
||||
|
||||
+67
-80
@@ -24,7 +24,6 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <memory.h>
|
||||
@@ -37,117 +36,105 @@
|
||||
static pa_simple *pa_dev = NULL;
|
||||
|
||||
static struct {
|
||||
char *server;
|
||||
char *sink;
|
||||
char *apname;
|
||||
} pulse_options = {
|
||||
.server = NULL,
|
||||
.sink = NULL,
|
||||
.apname = NULL
|
||||
};
|
||||
char *server;
|
||||
char *sink;
|
||||
char *apname;
|
||||
} pulse_options = {.server = NULL, .sink = NULL, .apname = NULL};
|
||||
|
||||
static int pa_error;
|
||||
|
||||
static void pulse_connect(void);
|
||||
|
||||
static void help(void) {
|
||||
printf(" -a server set the server name\n"
|
||||
" -s sink set the output sink\n"
|
||||
" -n name set the application name, as seen by PulseAudio\n"
|
||||
" defaults to the access point name\n"
|
||||
);
|
||||
printf(" -a server set the server name\n"
|
||||
" -s sink set the output sink\n"
|
||||
" -n name set the application name, as seen by PulseAudio\n"
|
||||
" defaults to the access point name\n");
|
||||
}
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
pulse_options.apname = config.apname;
|
||||
|
||||
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
|
||||
argv--; // so we shift the arguments to satisfy getopt()
|
||||
argc++;
|
||||
pulse_options.apname = config.apname;
|
||||
|
||||
// some platforms apparently require optreset = 1; - which?
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "a:s:n:")) > 0) {
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
pulse_options.server = optarg;
|
||||
break;
|
||||
case 's':
|
||||
pulse_options.sink = optarg;
|
||||
break;
|
||||
case 'n':
|
||||
pulse_options.apname = optarg;
|
||||
break;
|
||||
default:
|
||||
help();
|
||||
die("Invalid audio option -%c specified", opt);
|
||||
}
|
||||
config.audio_backend_buffer_desired_length = 44100; // one second.
|
||||
config.audio_backend_latency_offset = 0;
|
||||
|
||||
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
|
||||
argv--; // so we shift the arguments to satisfy getopt()
|
||||
argc++;
|
||||
|
||||
// some platforms apparently require optreset = 1; - which?
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "a:s:n:")) > 0) {
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
pulse_options.server = optarg;
|
||||
break;
|
||||
case 's':
|
||||
pulse_options.sink = optarg;
|
||||
break;
|
||||
case 'n':
|
||||
pulse_options.apname = optarg;
|
||||
break;
|
||||
default:
|
||||
help();
|
||||
die("Invalid audio option -%c specified", opt);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
die("Invalid audio argument: %s", argv[optind]);
|
||||
if (optind < argc)
|
||||
die("Invalid audio argument: %s", argv[optind]);
|
||||
|
||||
pulse_connect();
|
||||
pulse_connect();
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pulse_connect(void) {
|
||||
static const pa_sample_spec ss = {
|
||||
.format = PA_SAMPLE_S16LE,
|
||||
.rate = 44100,
|
||||
.channels = 2
|
||||
};
|
||||
static const pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2};
|
||||
|
||||
pa_dev = pa_simple_new(pulse_options.server,
|
||||
pulse_options.apname,
|
||||
PA_STREAM_PLAYBACK,
|
||||
pulse_options.sink,
|
||||
"Shairport Stream",
|
||||
&ss, NULL, NULL,
|
||||
&pa_error);
|
||||
pa_dev = pa_simple_new(pulse_options.server, pulse_options.apname, PA_STREAM_PLAYBACK,
|
||||
pulse_options.sink, "Shairport Stream", &ss, NULL, NULL, &pa_error);
|
||||
|
||||
if (!pa_dev)
|
||||
die("Could not connect to pulseaudio server: %s", pa_strerror(pa_error));
|
||||
if (!pa_dev)
|
||||
die("Could not connect to pulseaudio server: %s", pa_strerror(pa_error));
|
||||
}
|
||||
|
||||
static void deinit(void) {
|
||||
if (pa_dev)
|
||||
pa_simple_free(pa_dev);
|
||||
pa_dev = NULL;
|
||||
if (pa_dev)
|
||||
pa_simple_free(pa_dev);
|
||||
pa_dev = NULL;
|
||||
}
|
||||
|
||||
static void start(int sample_rate) {
|
||||
if (sample_rate != 44100)
|
||||
die("unexpected sample rate!");
|
||||
if (sample_rate != 44100)
|
||||
die("unexpected sample rate!");
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
if( pa_simple_write(pa_dev, (char *)buf, (size_t)samples * 4, &pa_error) < 0 ) {
|
||||
fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(pa_error));
|
||||
if (pa_error == PA_ERR_CONNECTIONTERMINATED) {
|
||||
fprintf(stderr, __FILE__": reconnecting.");
|
||||
deinit();
|
||||
pulse_connect();
|
||||
}
|
||||
if (pa_simple_write(pa_dev, (char *)buf, (size_t)samples * 4, &pa_error) < 0) {
|
||||
fprintf(stderr, __FILE__ ": pa_simple_write() failed: %s\n", pa_strerror(pa_error));
|
||||
if (pa_error == PA_ERR_CONNECTIONTERMINATED) {
|
||||
fprintf(stderr, __FILE__ ": reconnecting.");
|
||||
deinit();
|
||||
pulse_connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
if (pa_simple_drain(pa_dev, &pa_error) < 0)
|
||||
fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(pa_error));
|
||||
if (pa_simple_drain(pa_dev, &pa_error) < 0)
|
||||
fprintf(stderr, __FILE__ ": pa_simple_drain() failed: %s\n", pa_strerror(pa_error));
|
||||
}
|
||||
|
||||
audio_output audio_pulse = {
|
||||
.name = "pulse",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL
|
||||
};
|
||||
audio_output audio_pulse = {.name = "pulse",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL};
|
||||
|
||||
+38
-41
@@ -24,64 +24,61 @@ static struct sio_hdl *sio;
|
||||
static struct sio_par par;
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
sio = sio_open(SIO_DEVANY, SIO_PLAY, 0);
|
||||
if (!sio)
|
||||
die("sndio: cannot connect to sound server");
|
||||
sio = sio_open(SIO_DEVANY, SIO_PLAY, 0);
|
||||
if (!sio)
|
||||
die("sndio: cannot connect to sound server");
|
||||
|
||||
sio_initpar(&par);
|
||||
sio_initpar(&par);
|
||||
|
||||
par.bits = 16;
|
||||
par.rate = 44100;
|
||||
par.pchan = 2;
|
||||
par.le = SIO_LE_NATIVE;
|
||||
par.sig = 1;
|
||||
par.bits = 16;
|
||||
par.rate = 44100;
|
||||
par.pchan = 2;
|
||||
par.le = SIO_LE_NATIVE;
|
||||
par.sig = 1;
|
||||
|
||||
if (!sio_setpar(sio, &par))
|
||||
die("sndio: failed to set audio parameters");
|
||||
if (!sio_getpar(sio, &par))
|
||||
die("sndio: failed to get audio parameters");
|
||||
if (!sio_setpar(sio, &par))
|
||||
die("sndio: failed to set audio parameters");
|
||||
if (!sio_getpar(sio, &par))
|
||||
die("sndio: failed to get audio parameters");
|
||||
|
||||
return 0;
|
||||
config.audio_backend_buffer_desired_length = 44100; // one second.
|
||||
config.audio_backend_latency_offset = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void deinit(void) {
|
||||
sio_close(sio);
|
||||
}
|
||||
static void deinit(void) { sio_close(sio); }
|
||||
|
||||
static void start(int sample_rate) {
|
||||
if (sample_rate != par.rate)
|
||||
die("unexpected sample rate!");
|
||||
sio_start(sio);
|
||||
if (sample_rate != par.rate)
|
||||
die("unexpected sample rate!");
|
||||
sio_start(sio);
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
sio_write(sio, (char *)buf, samples * par.bps * par.pchan);
|
||||
sio_write(sio, (char *)buf, samples * par.bps * par.pchan);
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
sio_stop(sio);
|
||||
}
|
||||
static void stop(void) { sio_stop(sio); }
|
||||
|
||||
static void help(void) {
|
||||
printf(" There are no options for sndio audio.\n");
|
||||
printf(" Use AUDIODEVICE environment variable.\n");
|
||||
printf(" There are no options for sndio audio.\n");
|
||||
printf(" Use AUDIODEVICE environment variable.\n");
|
||||
}
|
||||
|
||||
static void volume(double vol) {
|
||||
unsigned int v = vol * SIO_MAXVOL;
|
||||
sio_setvol(sio, v);
|
||||
unsigned int v = vol * SIO_MAXVOL;
|
||||
sio_setvol(sio, v);
|
||||
}
|
||||
|
||||
audio_output audio_sndio = {
|
||||
.name = "sndio",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = &volume,
|
||||
.parameters = NULL
|
||||
};
|
||||
audio_output audio_sndio = {.name = "sndio",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = &volume,
|
||||
.parameters = NULL};
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* stdout output driver. This file is part of Shairport Sync.
|
||||
* Copyright (c) Mike Brady 2015
|
||||
* Based on pipe output driver
|
||||
* Copyright (c) James Laird 2013
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <memory.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include "common.h"
|
||||
#include "audio.h"
|
||||
|
||||
static int fd = -1;
|
||||
|
||||
static void start(int sample_rate) {
|
||||
fd = STDOUT_FILENO;
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
int ignore = write(fd, buf, samples * 4);
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
// don't close stdout
|
||||
}
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
const char *str;
|
||||
int value;
|
||||
|
||||
config.audio_backend_buffer_desired_length = 44100; // one second.
|
||||
config.audio_backend_latency_offset = 0;
|
||||
|
||||
if (config.cfg != NULL) {
|
||||
/* Get the desired buffer size setting. */
|
||||
if (config_lookup_int(config.cfg, "stdout.audio_backend_buffer_desired_length", &value)) {
|
||||
if ((value < 0) || (value > 132300))
|
||||
die("Invalid stdout audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100",
|
||||
value);
|
||||
else
|
||||
config.audio_backend_buffer_desired_length = value;
|
||||
}
|
||||
|
||||
/* Get the latency offset. */
|
||||
if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", &value)) {
|
||||
if ((value < -66150) || (value > 66150))
|
||||
die("Invalid stdout audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0",
|
||||
value);
|
||||
else
|
||||
config.audio_backend_latency_offset = value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void deinit(void) {
|
||||
// don't close stdout
|
||||
}
|
||||
|
||||
static void help(void) {
|
||||
printf(" stdout takes no arguments\n");
|
||||
}
|
||||
|
||||
audio_output audio_stdout = {.name = "stdout",
|
||||
.help = &help,
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.flush = NULL,
|
||||
.delay = NULL,
|
||||
.play = &play,
|
||||
.volume = NULL,
|
||||
.parameters = NULL};
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <popt.h>
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
@@ -66,12 +67,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#include "common.h"
|
||||
#include <libdaemon/dlog.h>
|
||||
|
||||
//true if Shairport Sync is supposed to be sending output to the output device, false otherwise
|
||||
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
|
||||
|
||||
static volatile int requested_connection_state_to_output = 1;
|
||||
|
||||
@@ -79,99 +78,98 @@ shairport_cfg config;
|
||||
|
||||
int debuglev = 0;
|
||||
|
||||
int get_requested_connection_state_to_output() {
|
||||
return requested_connection_state_to_output;
|
||||
}
|
||||
|
||||
void set_requested_connection_state_to_output(int v) {
|
||||
requested_connection_state_to_output = v;
|
||||
}
|
||||
int get_requested_connection_state_to_output() { return requested_connection_state_to_output; }
|
||||
|
||||
void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; }
|
||||
|
||||
void die(char *format, ...) {
|
||||
char s[1024];
|
||||
s[0]=0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(s,format,args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_EMERG,"%s", s);
|
||||
shairport_shutdown();
|
||||
exit(1);
|
||||
char s[1024];
|
||||
s[0] = 0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(s, format, args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_EMERG, "%s", s);
|
||||
shairport_shutdown();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void warn(char *format, ...) {
|
||||
char s[1024];
|
||||
s[0]=0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(s,format,args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_WARNING,"%s", s);
|
||||
char s[1024];
|
||||
s[0] = 0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(s, format, args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_WARNING, "%s", s);
|
||||
}
|
||||
|
||||
void debug(int level, char *format, ...) {
|
||||
if (level > debuglev)
|
||||
return;
|
||||
char s[1024];
|
||||
s[0]=0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(s,format,args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_DEBUG,"%s", s);
|
||||
if (level > debuglev)
|
||||
return;
|
||||
char s[1024];
|
||||
s[0] = 0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(s, format, args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_DEBUG, "%s", s);
|
||||
}
|
||||
|
||||
void inform(char *format, ...) {
|
||||
char s[1024];
|
||||
s[0]=0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(s,format,args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_INFO,"%s", s);
|
||||
char s[1024];
|
||||
s[0] = 0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(s, format, args);
|
||||
va_end(args);
|
||||
daemon_log(LOG_INFO, "%s", s);
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
char *base64_enc(uint8_t *input, int length) {
|
||||
char *buf = NULL;
|
||||
size_t dlen = 0;
|
||||
int rc = base64_encode(NULL,&dlen,input,length);
|
||||
if (rc && (rc!=POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL))
|
||||
debug(1,"Error %d getting length of base64 encode.",rc);
|
||||
int rc = base64_encode(NULL, &dlen, input, length);
|
||||
if (rc && (rc != POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL))
|
||||
debug(1, "Error %d getting length of base64 encode.", rc);
|
||||
else {
|
||||
buf = (char *)malloc(dlen);
|
||||
rc = base64_encode((unsigned char *)buf,&dlen,input,length);
|
||||
if (rc!=0)
|
||||
debug(1,"Error %d encoding base64.",rc);
|
||||
rc = base64_encode((unsigned char *)buf, &dlen, input, length);
|
||||
if (rc != 0)
|
||||
debug(1, "Error %d encoding base64.", rc);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
uint8_t *base64_dec(char *input, int *outlen) {
|
||||
// slight problem here is that Apple cut the padding off their challenges. We must restore it before passing it in to the decoder, it seems
|
||||
// slight problem here is that Apple cut the padding off their challenges. We must restore it
|
||||
// before passing it in to the decoder, it seems
|
||||
uint8_t *buf = NULL;
|
||||
size_t dlen = 0;
|
||||
int inbufsize = ((strlen(input)+3)/4)*4; // this is the size of the input buffer we will send to the decoder, but we need space for 3 extra "="s and a NULL
|
||||
char *inbuf = malloc(inbufsize+4);
|
||||
if (inbuf==0)
|
||||
debug(1,"Can't malloc memory for inbuf in base64_decode.");
|
||||
int inbufsize = ((strlen(input) + 3) / 4) * 4; // this is the size of the input buffer we will
|
||||
// send to the decoder, but we need space for 3
|
||||
// extra "="s and a NULL
|
||||
char *inbuf = malloc(inbufsize + 4);
|
||||
if (inbuf == 0)
|
||||
debug(1, "Can't malloc memory for inbuf in base64_decode.");
|
||||
else {
|
||||
strcpy(inbuf,input);
|
||||
strcat(inbuf,"===");
|
||||
// debug(1,"base64_dec called with string \"%s\", length %d, filled string: \"%s\", length %d.",input,strlen(input),inbuf,inbufsize);
|
||||
int rc = base64_decode(buf,&dlen,(unsigned char *)inbuf,inbufsize);
|
||||
if (rc && (rc!=POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL))
|
||||
debug(1,"Error %d getting decode length, result is %d.",rc,dlen);
|
||||
strcpy(inbuf, input);
|
||||
strcat(inbuf, "===");
|
||||
// debug(1,"base64_dec called with string \"%s\", length %d, filled string: \"%s\", length
|
||||
// %d.",input,strlen(input),inbuf,inbufsize);
|
||||
int rc = base64_decode(buf, &dlen, (unsigned char *)inbuf, inbufsize);
|
||||
if (rc && (rc != POLARSSL_ERR_BASE64_BUFFER_TOO_SMALL))
|
||||
debug(1, "Error %d getting decode length, result is %d.", rc, dlen);
|
||||
else {
|
||||
// debug(1,"Decode size is %d.",dlen);
|
||||
buf = malloc(dlen);
|
||||
if (buf==0)
|
||||
debug(1,"Can't allocate memory in base64_dec.");
|
||||
if (buf == 0)
|
||||
debug(1, "Can't allocate memory in base64_dec.");
|
||||
else {
|
||||
rc = base64_decode(buf,&dlen,(unsigned char *)inbuf,inbufsize);
|
||||
if (rc!=0)
|
||||
debug(1,"Error %d in base64_dec.",rc);
|
||||
rc = base64_decode(buf, &dlen, (unsigned char *)inbuf, inbufsize);
|
||||
if (rc != 0)
|
||||
debug(1, "Error %d in base64_dec.", rc);
|
||||
}
|
||||
}
|
||||
free(inbuf);
|
||||
@@ -183,221 +181,226 @@ uint8_t *base64_dec(char *input, int *outlen) {
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
char *base64_enc(uint8_t *input, int length) {
|
||||
BIO *bmem, *b64;
|
||||
BUF_MEM *bptr;
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bmem);
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
BIO_write(b64, input, length);
|
||||
BIO_flush(b64);
|
||||
BIO_get_mem_ptr(b64, &bptr);
|
||||
BIO *bmem, *b64;
|
||||
BUF_MEM *bptr;
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bmem);
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
BIO_write(b64, input, length);
|
||||
BIO_flush(b64);
|
||||
BIO_get_mem_ptr(b64, &bptr);
|
||||
|
||||
char *buf = (char *)malloc(bptr->length);
|
||||
if (bptr->length) {
|
||||
memcpy(buf, bptr->data, bptr->length-1);
|
||||
buf[bptr->length-1] = 0;
|
||||
}
|
||||
char *buf = (char *)malloc(bptr->length);
|
||||
if (bptr->length) {
|
||||
memcpy(buf, bptr->data, bptr->length - 1);
|
||||
buf[bptr->length - 1] = 0;
|
||||
}
|
||||
|
||||
BIO_free_all(bmem);
|
||||
BIO_free_all(bmem);
|
||||
|
||||
return buf;
|
||||
return buf;
|
||||
}
|
||||
|
||||
uint8_t *base64_dec(char *input, int *outlen) {
|
||||
BIO *bmem, *b64;
|
||||
int inlen = strlen(input);
|
||||
BIO *bmem, *b64;
|
||||
int inlen = strlen(input);
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bmem);
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bmem);
|
||||
|
||||
// Apple cut the padding off their challenges; restore it
|
||||
BIO_write(bmem, input, inlen);
|
||||
while (inlen++ & 3)
|
||||
BIO_write(bmem, "=", 1);
|
||||
BIO_flush(bmem);
|
||||
// Apple cut the padding off their challenges; restore it
|
||||
BIO_write(bmem, input, inlen);
|
||||
while (inlen++ & 3)
|
||||
BIO_write(bmem, "=", 1);
|
||||
BIO_flush(bmem);
|
||||
|
||||
int bufsize = strlen(input)*3/4 + 1;
|
||||
uint8_t *buf = malloc(bufsize);
|
||||
int nread;
|
||||
int bufsize = strlen(input) * 3 / 4 + 1;
|
||||
uint8_t *buf = malloc(bufsize);
|
||||
int nread;
|
||||
|
||||
nread = BIO_read(b64, buf, bufsize);
|
||||
nread = BIO_read(b64, buf, bufsize);
|
||||
|
||||
BIO_free_all(bmem);
|
||||
BIO_free_all(bmem);
|
||||
|
||||
*outlen = nread;
|
||||
return buf;
|
||||
*outlen = nread;
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
static char super_secret_key[] =
|
||||
"-----BEGIN RSA PRIVATE KEY-----\n"
|
||||
"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
|
||||
"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
|
||||
"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
|
||||
"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
|
||||
"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
|
||||
"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
|
||||
"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
|
||||
"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
|
||||
"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
|
||||
"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
|
||||
"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
|
||||
"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
|
||||
"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
|
||||
"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
|
||||
"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
|
||||
"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
|
||||
"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
|
||||
"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
|
||||
"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
|
||||
"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
|
||||
"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
|
||||
"-----END RSA PRIVATE KEY-----\0";
|
||||
"-----BEGIN RSA PRIVATE KEY-----\n"
|
||||
"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
|
||||
"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
|
||||
"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
|
||||
"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
|
||||
"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
|
||||
"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
|
||||
"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
|
||||
"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
|
||||
"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
|
||||
"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
|
||||
"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
|
||||
"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
|
||||
"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
|
||||
"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
|
||||
"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
|
||||
"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
|
||||
"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
|
||||
"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
|
||||
"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
|
||||
"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
|
||||
"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
|
||||
"-----END RSA PRIVATE KEY-----\0";
|
||||
|
||||
#ifdef HAVE_LIBSSL
|
||||
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
static RSA *rsa = NULL;
|
||||
static RSA *rsa = NULL;
|
||||
|
||||
if (!rsa) {
|
||||
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
|
||||
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
|
||||
BIO_free(bmem);
|
||||
}
|
||||
if (!rsa) {
|
||||
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
|
||||
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
|
||||
BIO_free(bmem);
|
||||
}
|
||||
|
||||
uint8_t *out = malloc(RSA_size(rsa));
|
||||
switch (mode) {
|
||||
case RSA_MODE_AUTH:
|
||||
*outlen = RSA_private_encrypt(inlen, input, out, rsa,
|
||||
RSA_PKCS1_PADDING);
|
||||
break;
|
||||
case RSA_MODE_KEY:
|
||||
*outlen = RSA_private_decrypt(inlen, input, out, rsa,
|
||||
RSA_PKCS1_OAEP_PADDING);
|
||||
break;
|
||||
default:
|
||||
die("bad rsa mode");
|
||||
}
|
||||
return out;
|
||||
uint8_t *out = malloc(RSA_size(rsa));
|
||||
switch (mode) {
|
||||
case RSA_MODE_AUTH:
|
||||
*outlen = RSA_private_encrypt(inlen, input, out, rsa, RSA_PKCS1_PADDING);
|
||||
break;
|
||||
case RSA_MODE_KEY:
|
||||
*outlen = RSA_private_decrypt(inlen, input, out, rsa, RSA_PKCS1_OAEP_PADDING);
|
||||
break;
|
||||
default:
|
||||
die("bad rsa mode");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBPOLARSSL
|
||||
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
rsa_context trsa;
|
||||
const char *pers = "rsa_encrypt";
|
||||
int rc;
|
||||
|
||||
entropy_context entropy;
|
||||
ctr_drbg_context ctr_drbg;
|
||||
entropy_init( &entropy );
|
||||
if( ( rc = ctr_drbg_init( &ctr_drbg, entropy_func, &entropy,(const unsigned char *) pers,strlen( pers ) ) ) != 0 )
|
||||
debug(1, "ctr_drbg_init returned %d\n", rc );
|
||||
rsa_context trsa;
|
||||
const char *pers = "rsa_encrypt";
|
||||
int rc;
|
||||
|
||||
rsa_init(&trsa,RSA_PKCS_V21,POLARSSL_MD_SHA1); // padding and hash id get overwritten
|
||||
// BTW, this seems to reset a lot of parameters in the rsa_context
|
||||
rc = x509parse_key(&trsa,(unsigned char *)super_secret_key,strlen(super_secret_key),NULL,0);
|
||||
if (rc!=0)
|
||||
debug(1,"Error %d reading the private key.");
|
||||
|
||||
uint8_t *out = NULL;
|
||||
entropy_context entropy;
|
||||
ctr_drbg_context ctr_drbg;
|
||||
entropy_init(&entropy);
|
||||
if ((rc = ctr_drbg_init(&ctr_drbg, entropy_func, &entropy, (const unsigned char *)pers,
|
||||
strlen(pers))) != 0)
|
||||
debug(1, "ctr_drbg_init returned %d\n", rc);
|
||||
|
||||
switch (mode) {
|
||||
case RSA_MODE_AUTH:
|
||||
trsa.padding = RSA_PKCS_V15;
|
||||
trsa.hash_id = POLARSSL_MD_NONE;
|
||||
debug(2,"rsa_apply encrypt");
|
||||
out = malloc(trsa.len);
|
||||
rc = rsa_pkcs1_encrypt( &trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, inlen, input, out );
|
||||
if (rc!=0)
|
||||
debug(1,"rsa_pkcs1_encrypt error %d.",rc);
|
||||
*outlen=trsa.len;
|
||||
break;
|
||||
case RSA_MODE_KEY:
|
||||
debug(2,"rsa_apply decrypt");
|
||||
trsa.padding=RSA_PKCS_V21;
|
||||
trsa.hash_id=POLARSSL_MD_SHA1;
|
||||
out = malloc(trsa.len);
|
||||
rsa_init(&trsa, RSA_PKCS_V21, POLARSSL_MD_SHA1); // padding and hash id get overwritten
|
||||
// BTW, this seems to reset a lot of parameters in the rsa_context
|
||||
rc = x509parse_key(&trsa, (unsigned char *)super_secret_key, strlen(super_secret_key), NULL, 0);
|
||||
if (rc != 0)
|
||||
debug(1, "Error %d reading the private key.");
|
||||
|
||||
uint8_t *out = NULL;
|
||||
|
||||
switch (mode) {
|
||||
case RSA_MODE_AUTH:
|
||||
trsa.padding = RSA_PKCS_V15;
|
||||
trsa.hash_id = POLARSSL_MD_NONE;
|
||||
debug(2, "rsa_apply encrypt");
|
||||
out = malloc(trsa.len);
|
||||
rc = rsa_pkcs1_encrypt(&trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, inlen, input, out);
|
||||
if (rc != 0)
|
||||
debug(1, "rsa_pkcs1_encrypt error %d.", rc);
|
||||
*outlen = trsa.len;
|
||||
break;
|
||||
case RSA_MODE_KEY:
|
||||
debug(2, "rsa_apply decrypt");
|
||||
trsa.padding = RSA_PKCS_V21;
|
||||
trsa.hash_id = POLARSSL_MD_SHA1;
|
||||
out = malloc(trsa.len);
|
||||
#if POLARSSL_VERSION_NUMBER >= 0x01020900
|
||||
rc = rsa_pkcs1_decrypt(&trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, (size_t *)outlen, input, out, trsa.len);
|
||||
rc = rsa_pkcs1_decrypt(&trsa, ctr_drbg_random, &ctr_drbg, RSA_PRIVATE, (size_t *)outlen, input,
|
||||
out, trsa.len);
|
||||
#else
|
||||
rc = rsa_pkcs1_decrypt(&trsa, RSA_PRIVATE, outlen, input, out, trsa.len);
|
||||
rc = rsa_pkcs1_decrypt(&trsa, RSA_PRIVATE, outlen, input, out, trsa.len);
|
||||
#endif
|
||||
if (rc!=0)
|
||||
debug(1,"decrypt error %d.",rc);
|
||||
break;
|
||||
default:
|
||||
die("bad rsa mode");
|
||||
}
|
||||
rsa_free(&trsa);
|
||||
debug(2,"rsa_apply exit");
|
||||
return out;
|
||||
if (rc != 0)
|
||||
debug(1, "decrypt error %d.", rc);
|
||||
break;
|
||||
default:
|
||||
die("bad rsa mode");
|
||||
}
|
||||
rsa_free(&trsa);
|
||||
debug(2, "rsa_apply exit");
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
void command_start(void) {
|
||||
if (config.cmd_start) {
|
||||
/*Spawn a child to run the program.*/
|
||||
pid_t pid=fork();
|
||||
if (pid==0) { /* child process */
|
||||
int argC;
|
||||
char **argV;
|
||||
// debug(1,"on-start command found.");
|
||||
if (poptParseArgvString(config.cmd_start,&argC,(const char ***)&argV)!=0) // note that argV should be free()'d after use, but we expect this fork to exit eventually.
|
||||
debug(1,"Can't decipher on-start command arguments");
|
||||
else {
|
||||
// debug(1,"Executing on-start command %s with %d arguments.",argV[0],argC);
|
||||
execv(argV[0],argV);
|
||||
warn("Execution of on-start command failed to start");
|
||||
debug(1,"Error executing on-start command %s",config.cmd_start);
|
||||
exit(127); /* only if execv fails */
|
||||
}
|
||||
} else {
|
||||
if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */
|
||||
pid_t rc = waitpid(pid,0,0); /* wait for child to exit */
|
||||
if (rc!=pid) {
|
||||
warn("Execution of on-start command returned an error.");
|
||||
debug(1,"on-start command %s finished with error %d",config.cmd_start,errno);
|
||||
}
|
||||
}
|
||||
// debug(1,"Continue after on-start command");
|
||||
}
|
||||
}
|
||||
if (config.cmd_start) {
|
||||
/*Spawn a child to run the program.*/
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) { /* child process */
|
||||
int argC;
|
||||
char **argV;
|
||||
// debug(1,"on-start command found.");
|
||||
if (poptParseArgvString(config.cmd_start, &argC, (const char ***)&argV) !=
|
||||
0) // note that argV should be free()'d after use, but we expect this fork to exit
|
||||
// eventually.
|
||||
debug(1, "Can't decipher on-start command arguments");
|
||||
else {
|
||||
// debug(1,"Executing on-start command %s with %d arguments.",argV[0],argC);
|
||||
execv(argV[0], argV);
|
||||
warn("Execution of on-start command failed to start");
|
||||
debug(1, "Error executing on-start command %s", config.cmd_start);
|
||||
exit(127); /* only if execv fails */
|
||||
}
|
||||
} else {
|
||||
if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
|
||||
process to finish */
|
||||
pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
|
||||
if (rc != pid) {
|
||||
warn("Execution of on-start command returned an error.");
|
||||
debug(1, "on-start command %s finished with error %d", config.cmd_start, errno);
|
||||
}
|
||||
}
|
||||
// debug(1,"Continue after on-start command");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void command_stop(void) {
|
||||
if (config.cmd_stop) {
|
||||
/*Spawn a child to run the program.*/
|
||||
pid_t pid=fork();
|
||||
if (pid==0) { /* child process */
|
||||
int argC;
|
||||
char **argV;
|
||||
// debug(1,"on-stop command found.");
|
||||
if (poptParseArgvString(config.cmd_stop,&argC,(const char ***)&argV)!=0) // note that argV should be free()'d after use, but we expect this fork to exit eventually.
|
||||
debug(1,"Can't decipher on-stop command arguments");
|
||||
if (config.cmd_stop) {
|
||||
/*Spawn a child to run the program.*/
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) { /* child process */
|
||||
int argC;
|
||||
char **argV;
|
||||
// debug(1,"on-stop command found.");
|
||||
if (poptParseArgvString(config.cmd_stop, &argC, (const char ***)&argV) !=
|
||||
0) // note that argV should be free()'d after use, but we expect this fork to exit
|
||||
// eventually.
|
||||
debug(1, "Can't decipher on-stop command arguments");
|
||||
else {
|
||||
// debug(1,"Executing on-stop command %s",config.cmd_stop);
|
||||
execv(argV[0],argV);
|
||||
warn("Execution of on-stop command failed to start");
|
||||
debug(1,"Error executing on-stop command %s",config.cmd_stop);
|
||||
exit(127); /* only if execv fails */
|
||||
}
|
||||
} else {
|
||||
if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */
|
||||
pid_t rc = waitpid(pid,0,0); /* wait for child to exit */
|
||||
if (rc!=pid) {
|
||||
warn("Execution of on-stop command returned an error.");
|
||||
debug(1,"Stop command %s finished with error %d",config.cmd_stop,errno);
|
||||
}
|
||||
}
|
||||
// debug(1,"Continue after on-stop command");
|
||||
}
|
||||
}
|
||||
// debug(1,"Executing on-stop command %s",config.cmd_stop);
|
||||
execv(argV[0], argV);
|
||||
warn("Execution of on-stop command failed to start");
|
||||
debug(1, "Error executing on-stop command %s", config.cmd_stop);
|
||||
exit(127); /* only if execv fails */
|
||||
}
|
||||
} else {
|
||||
if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for
|
||||
process to finish */
|
||||
pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */
|
||||
if (rc != pid) {
|
||||
warn("Execution of on-stop command returned an error.");
|
||||
debug(1, "Stop command %s finished with error %d", config.cmd_stop, errno);
|
||||
}
|
||||
}
|
||||
// debug(1,"Continue after on-stop command");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
|
||||
|
||||
uint32_t uatoi(const char *nptr) {
|
||||
@@ -406,19 +409,25 @@ uint32_t uatoi(const char *nptr) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Given a volume (0 to -30) and high and low attenuations available in the mixer in dB, return an attenuation depending on the volume and the function's transfer function
|
||||
// Given a volume (0 to -30) and high and low attenuations available in the mixer in dB, return an
|
||||
// attenuation depending on the volume and the function's transfer function
|
||||
// See http://tangentsoft.net/audio/atten.html for data on good attenuators.
|
||||
// We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer functions referred to at the link above.
|
||||
// We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer
|
||||
// functions referred to at the link above.
|
||||
|
||||
// Note that the max_db and min_db are given as dB*100
|
||||
|
||||
double vol2attn(double vol, long max_db, long min_db) {
|
||||
double vol2attn(double vol, long max_db, long min_db) {
|
||||
|
||||
// We use a little coordinate geometry to build a transfer function from the volume passed in to the device's dynamic range.
|
||||
// We use a little coordinate geometry to build a transfer function from the volume passed in to the
|
||||
// device's dynamic range.
|
||||
// (See the diagram in the documents folder.)
|
||||
// The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from the bottom of the range to the top.
|
||||
// We build the transfer function from one or more lines. We characterise each line with two numbers:
|
||||
// the first is where on x the line starts when y=0 (x can be from 0 to -30); the second is where on y the line stops when when x is -30.
|
||||
// The x axis is the "volume in" which will be from -30 to 0. The y axis will be the "volume out"
|
||||
// which will be from the bottom of the range to the top.
|
||||
// We build the transfer function from one or more lines. We characterise each line with two
|
||||
// numbers:
|
||||
// the first is where on x the line starts when y=0 (x can be from 0 to -30); the second is where on
|
||||
// y the line stops when when x is -30.
|
||||
// thus, if the line was characterised as {0,-30}, it would be an identity transfer.
|
||||
// Assuming, for example, a dynamic range of lv=-60 to hv=0
|
||||
// Typically we'll use three lines -- a three order transfer function
|
||||
@@ -429,27 +438,33 @@ double vol2attn(double vol, long max_db, long min_db) {
|
||||
#define order 3
|
||||
|
||||
double vol_setting = max_db;
|
||||
|
||||
if ((vol<=0.0) && (vol>=-30.0)) {
|
||||
long range_db = max_db-min_db; // this will be a positive nunmber
|
||||
|
||||
if ((vol <= 0.0) && (vol >= -30.0)) {
|
||||
long range_db = max_db - min_db; // this will be a positive nunmber
|
||||
// debug(1,"Volume min %ddB, max %ddB, range %ddB.",min_db,max_db,range_db);
|
||||
//double first_slope = -3000.0; // this is the slope of the attenuation at the high end -- 30dB for the full rotation.
|
||||
double first_slope = -range_db/2; // this is the slope of the attenuation at the high end -- 30dB for the full rotation.
|
||||
if (-range_db>first_slope)
|
||||
// double first_slope = -3000.0; // this is the slope of the attenuation at the high end -- 30dB
|
||||
// for the full rotation.
|
||||
double first_slope =
|
||||
-range_db /
|
||||
2; // this is the slope of the attenuation at the high end -- 30dB for the full rotation.
|
||||
if (-range_db > first_slope)
|
||||
first_slope = range_db;
|
||||
double lines[order][2] = {{0,first_slope},{-5,first_slope-(range_db+first_slope)/2},{-17,-range_db}};
|
||||
double lines[order][2] = {
|
||||
{0, first_slope}, {-5, first_slope - (range_db + first_slope) / 2}, {-17, -range_db}};
|
||||
int i;
|
||||
for (i=0;i<order;i++) {
|
||||
if (vol<=lines[i][0]) {
|
||||
double tvol = lines[i][1]*(vol-lines[i][0])/(-30-lines[i][0]);
|
||||
// debug(1,"On line %d, end point of %f, input vol %f yields output vol %f.",i,lines[i][1],vol,tvol);
|
||||
if (tvol<vol_setting)
|
||||
vol_setting=tvol;
|
||||
for (i = 0; i < order; i++) {
|
||||
if (vol <= lines[i][0]) {
|
||||
double tvol = lines[i][1] * (vol - lines[i][0]) / (-30 - lines[i][0]);
|
||||
// debug(1,"On line %d, end point of %f, input vol %f yields output vol
|
||||
// %f.",i,lines[i][1],vol,tvol);
|
||||
if (tvol < vol_setting)
|
||||
vol_setting = tvol;
|
||||
}
|
||||
}
|
||||
vol_setting+=max_db;
|
||||
} else if (vol!=-144.0) {
|
||||
debug(1,"Volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.",vol);
|
||||
vol_setting += max_db;
|
||||
} else if (vol != -144.0) {
|
||||
debug(1, "Volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.",
|
||||
vol);
|
||||
} else {
|
||||
vol_setting = min_db; // for safety, return the lowest setting...
|
||||
}
|
||||
@@ -458,39 +473,66 @@ 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
|
||||
struct timespec tn;
|
||||
clock_gettime(CLOCK_MONOTONIC,&tn);
|
||||
time_now_fp=((uint64_t)tn.tv_sec<<32)+((uint64_t)tn.tv_nsec<<32)/1000000000;
|
||||
uint64_t time_now_fp;
|
||||
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD
|
||||
struct timespec tn;
|
||||
// can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
|
||||
clock_gettime(CLOCK_MONOTONIC, &tn);
|
||||
time_now_fp = ((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000;
|
||||
#endif
|
||||
#ifdef COMPILE_FOR_OSX
|
||||
uint64_t time_now_mach;
|
||||
uint64_t elapsedNano;
|
||||
static mach_timebase_info_data_t sTimebaseInfo = {0,0};
|
||||
uint64_t time_now_mach;
|
||||
uint64_t elapsedNano;
|
||||
static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
|
||||
|
||||
time_now_mach = mach_absolute_time();
|
||||
time_now_mach = mach_absolute_time();
|
||||
|
||||
// If this is the first time we've run, get the timebase.
|
||||
// We can use denom == 0 to indicate that sTimebaseInfo is
|
||||
// uninitialised because it makes no sense to have a zero
|
||||
// denominator in a fraction.
|
||||
// If this is the first time we've run, get the timebase.
|
||||
// We can use denom == 0 to indicate that sTimebaseInfo is
|
||||
// uninitialised because it makes no sense to have a zero
|
||||
// denominator in a fraction.
|
||||
|
||||
if ( sTimebaseInfo.denom == 0 ) {
|
||||
debug(1,"Mac initialise timebase info.");
|
||||
(void) mach_timebase_info(&sTimebaseInfo);
|
||||
}
|
||||
if (sTimebaseInfo.denom == 0) {
|
||||
debug(1, "Mac initialise timebase info.");
|
||||
(void)mach_timebase_info(&sTimebaseInfo);
|
||||
}
|
||||
|
||||
// Do the maths. We hope that the multiplication doesn't
|
||||
// overflow; the price you pay for working in fixed point.
|
||||
// Do the maths. We hope that the multiplication doesn't
|
||||
// overflow; the price you pay for working in fixed point.
|
||||
|
||||
// this gives us nanoseconds
|
||||
uint64_t time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
|
||||
|
||||
// take the units and shift them to the upper half of the fp, and take the nanoseconds, shift them to the upper half and then divide the result to 1000000000
|
||||
time_now_fp = ((time_now_ns/1000000000)<<32) + (((time_now_ns%1000000000)<<32)/1000000000);
|
||||
// this gives us nanoseconds
|
||||
uint64_t time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
|
||||
|
||||
// take the units and shift them to the upper half of the fp, and take the nanoseconds, shift them
|
||||
// to the upper half and then divide the result to 1000000000
|
||||
time_now_fp =
|
||||
((time_now_ns / 1000000000) << 32) + (((time_now_ns % 1000000000) << 32) / 1000000000);
|
||||
|
||||
#endif
|
||||
return time_now_fp;
|
||||
}
|
||||
|
||||
ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
|
||||
// debug(1,"writing %u to pipe...",count);
|
||||
// we are assuming that the count is always smaller than the FIFO's buffer
|
||||
struct pollfd ufds[1];
|
||||
ssize_t reply;
|
||||
do {
|
||||
ufds[0].fd = fd;
|
||||
ufds[0].events = POLLOUT;
|
||||
int rv = poll(ufds, 1, 5000);
|
||||
if (rv == -1)
|
||||
debug(1, "error waiting for pipe to unblock...");
|
||||
if (rv == 0)
|
||||
debug(1, "timeout waiting for pipe to unblock");
|
||||
reply = write(fd, buf, count);
|
||||
if ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
|
||||
debug(1, "writing to pipe will block...");
|
||||
// else
|
||||
// debug(1,"writing %u to pipe done...",reply);
|
||||
} while ((reply == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)));
|
||||
return reply;
|
||||
|
||||
// return write(fd,buf,count);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,75 +3,95 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/socket.h>
|
||||
#include <libconfig.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "audio.h"
|
||||
#include "mdns.h"
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
/* Apple OSX and iOS (Darwin). ------------------------------ */
|
||||
/* Apple OSX and iOS (Darwin). ------------------------------ */
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_MAC == 1
|
||||
/* OSX */
|
||||
#define COMPILE_FOR_OSX 1
|
||||
/* OSX */
|
||||
#define COMPILE_FOR_OSX 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
/* Linux. --------------------------------------------------- */
|
||||
#define COMPILE_FOR_LINUX 1
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
/* Linux and FreeBSD */
|
||||
#define COMPILE_FOR_LINUX_AND_FREEBSD 1
|
||||
#endif
|
||||
|
||||
// struct sockaddr_in6 is bigger than struct sockaddr. derp
|
||||
#ifdef AF_INET6
|
||||
#define SOCKADDR struct sockaddr_storage
|
||||
#define SAFAMILY ss_family
|
||||
#define SOCKADDR struct sockaddr_storage
|
||||
#define SAFAMILY ss_family
|
||||
#else
|
||||
#define SOCKADDR struct sockaddr
|
||||
#define SAFAMILY sa_family
|
||||
#define SOCKADDR struct sockaddr
|
||||
#define SAFAMILY sa_family
|
||||
#endif
|
||||
|
||||
|
||||
enum stuffing_type {
|
||||
ST_basic = 0,
|
||||
ST_basic = 0,
|
||||
ST_soxr,
|
||||
} type;
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *password;
|
||||
char *apname;
|
||||
uint8_t hw_addr[6];
|
||||
int port;
|
||||
int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never resync.
|
||||
int timeout; // while in play mode, exit if no packets of audio come in for more than this number of seconds . Zero means never exit.
|
||||
char *output_name;
|
||||
audio_output *output;
|
||||
char *mdns_name;
|
||||
mdns_backend *mdns;
|
||||
int buffer_start_fill;
|
||||
uint32_t latency;
|
||||
uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution
|
||||
uint32_t iTunesLatency; // supplied with --iTunesLatency option
|
||||
uint32_t AirPlayLatency; //supplied with --AirPlayLatency option
|
||||
uint32_t ForkedDaapdLatency; //supplied with --ForkedDaapdLatency option
|
||||
int daemonise;
|
||||
int statistics_requested;
|
||||
char *cmd_start, *cmd_stop;
|
||||
int tolerance; // allow this much drift before attempting to correct it
|
||||
int cmd_blocking;
|
||||
enum stuffing_type packet_stuffing;
|
||||
char *pidfile;
|
||||
char *logfile;
|
||||
char *errfile;
|
||||
config_t *cfg;
|
||||
char *password;
|
||||
char *apname;
|
||||
#ifdef CONFIG_METADATA
|
||||
int metadata_enabled;
|
||||
char *metadata_pipename;
|
||||
int get_coverart;
|
||||
#endif
|
||||
uint8_t hw_addr[6];
|
||||
int port;
|
||||
int udp_port_base;
|
||||
int udp_port_range;
|
||||
int ignore_volume_control;
|
||||
int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never
|
||||
// resync.
|
||||
int allow_session_interruption;
|
||||
int timeout; // while in play mode, exit if no packets of audio come in for more than this number
|
||||
// of seconds . Zero means never exit.
|
||||
int dont_check_timeout; // this is used to maintain backward compatability with the old -t option
|
||||
// behaviour; only set by -t 0, cleared by everything else
|
||||
char *output_name;
|
||||
audio_output *output;
|
||||
char *mdns_name;
|
||||
mdns_backend *mdns;
|
||||
int buffer_start_fill;
|
||||
uint32_t latency;
|
||||
uint32_t userSuppliedLatency; // overrides all other latencies -- use with caution
|
||||
uint32_t iTunesLatency; // supplied with --iTunesLatency option
|
||||
uint32_t AirPlayLatency; // supplied with --AirPlayLatency option
|
||||
uint32_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option
|
||||
int daemonise;
|
||||
int statistics_requested;
|
||||
char *cmd_start, *cmd_stop;
|
||||
int cmd_blocking;
|
||||
int tolerance; // allow this much drift before attempting to correct it
|
||||
enum stuffing_type packet_stuffing;
|
||||
char *pidfile;
|
||||
char *logfile;
|
||||
char *errfile;
|
||||
char *configfile;
|
||||
uint32_t audio_backend_buffer_desired_length; // this will be the desired number of frames in the
|
||||
// audio backend buffer -- the DAC buffer for ALSA
|
||||
uint32_t audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency
|
||||
// there might be in the audio
|
||||
} shairport_cfg;
|
||||
|
||||
//true if Shairport Sync is supposed to be sending output to the output device, false otherwise
|
||||
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
|
||||
|
||||
int get_requested_connection_state_to_output();
|
||||
|
||||
void set_requested_connection_state_to_output(int v);
|
||||
|
||||
ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places
|
||||
|
||||
int debuglev;
|
||||
void die(char *format, ...);
|
||||
void warn(char *format, ...);
|
||||
@@ -82,10 +102,11 @@ uint8_t *base64_dec(char *input, int *outlen);
|
||||
char *base64_enc(uint8_t *input, int length);
|
||||
|
||||
#define RSA_MODE_AUTH (0)
|
||||
#define RSA_MODE_KEY (1)
|
||||
#define RSA_MODE_KEY (1)
|
||||
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode);
|
||||
|
||||
// given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60 dB), return an attenuation depending on the transfer function
|
||||
// given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60
|
||||
// dB), return an attenuation depending on the transfer function
|
||||
double vol2attn(double vol, long max_db, long min_db);
|
||||
|
||||
// return a monolithic (always increasing) time in nanoseconds
|
||||
@@ -97,9 +118,12 @@ uint64_t get_absolute_time_in_fp(void);
|
||||
uint32_t uatoi(const char *nptr);
|
||||
|
||||
shairport_cfg config;
|
||||
char sender_name[1024];
|
||||
char sender_ip[1024];
|
||||
char album_name[1024]; // we might need this for picture diagnostics
|
||||
config_t config_file_stuff;
|
||||
|
||||
uint32_t buffer_occupancy;
|
||||
int64_t session_corrections;
|
||||
uint32_t play_segment_reference_frame;
|
||||
uint64_t play_segment_reference_frame_remote_time;
|
||||
|
||||
void command_start(void);
|
||||
void command_stop(void);
|
||||
|
||||
+88
-20
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.50])
|
||||
AC_INIT([shairport-sync], [2.2.5], [mikebrady@eircom.net])
|
||||
AC_INIT([shairport-sync], [2.4], [mikebrady@eircom.net])
|
||||
AM_INIT_AUTOMAKE
|
||||
AC_CONFIG_SRCDIR([shairport.c])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
@@ -11,7 +11,7 @@ AC_CONFIG_HEADERS([config.h])
|
||||
#
|
||||
# Specifying the OS type, defaulting to linux.
|
||||
#
|
||||
AC_ARG_WITH(os_type, AS_HELP_STRING([--with-os-type=OSType],[Specify the distribution to target: One of linux darwin]))
|
||||
AC_ARG_WITH(os_type, AS_HELP_STRING([--with-os-type=OSType],[Specify the distribution to target: One of linux freebsd or darwin]))
|
||||
if test "z$with_os_type" = "z"; then
|
||||
with_os_type="linux"
|
||||
fi
|
||||
@@ -24,18 +24,67 @@ AC_PROG_INSTALL
|
||||
PKG_PROG_PKG_CONFIG([0.9.0])
|
||||
|
||||
# Checks for libraries.
|
||||
if test "x${with_os_type}" = xlinux; then
|
||||
if test "x${with_os_type}" = xlinux -o "x${with_os_type}" = xfreebsd ; then
|
||||
AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed))
|
||||
fi
|
||||
|
||||
##### Some build systems are not fully using pkg-config, so we can use the flag ${with_pkg_config} on a case-by-case basis
|
||||
##### to control how to deal with them
|
||||
|
||||
AC_ARG_WITH([pkg_config],
|
||||
[ --with-pkg-config = use pkg-config to find libraries], ,[with_pkg_config=yes])
|
||||
|
||||
AC_CHECK_LIB([daemon],[daemon_log], , AC_MSG_ERROR(libdaemon needed))
|
||||
AC_CHECK_LIB([pthread],[pthread_create], , AC_MSG_ERROR(pthread library needed))
|
||||
AC_CHECK_LIB([m],[exp], , AC_MSG_ERROR(maths library needed))
|
||||
PKG_CHECK_MODULES(
|
||||
[POPT], [popt],
|
||||
[LIBS="${POPT_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])])
|
||||
|
||||
AC_MSG_RESULT(>>Including libpopt)
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[POPT], [popt],
|
||||
[LIBS="${POPT_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])])
|
||||
else
|
||||
AC_CHECK_LIB([popt],[poptGetContext], , AC_MSG_ERROR(libpopt needed))
|
||||
AC_DEFINE([HAVE_LIBPOPT],[1],[Define to 1 if you have popt])
|
||||
fi
|
||||
|
||||
AC_ARG_WITH([dummy],[ --with-dummy = include the dummy audio back end ],[AC_MSG_RESULT(>>Including the dummy audio back end) AC_DEFINE([CONFIG_DUMMY], 1, [Needed by the compiler.]) ], )
|
||||
AM_CONDITIONAL([USE_DUMMY], [test "x$with_dummy" = "xyes" ])
|
||||
|
||||
AC_ARG_WITH([stdout],[ --with-stdout = include the stdout audio back end ],[ AC_MSG_RESULT(>>Including the stdout audio back end) AC_DEFINE([CONFIG_STDOUT], 1, [Needed by the compiler.]) ], )
|
||||
|
||||
AM_CONDITIONAL([USE_STDOUT], [test "x$with_stdout" = "xyes" ])
|
||||
|
||||
AC_ARG_WITH([pipe],[ --with-pipe = include the pipe audio back end ],[ AC_MSG_RESULT(>>Including the pipe audio back end) AC_DEFINE([CONFIG_PIPE], 1, [Needed by the compiler.]) ], )
|
||||
AM_CONDITIONAL([USE_PIPE], [test "x$with_pipe" = "xyes" ])
|
||||
|
||||
# Check to see if we should include the System V initscript
|
||||
|
||||
AC_ARG_WITH([systemv],
|
||||
[ --with-systemv = install a System V startup script during a make install], , )
|
||||
AM_CONDITIONAL([INSTALL_SYSTEMV], [test "x$with_systemv" = "xyes"])
|
||||
|
||||
# Check to see if we should include the systemd stuff to define it as a service
|
||||
|
||||
AC_ARG_WITH([systemd],
|
||||
[ --with-systemd = install a systemd service description file during a make install], , )
|
||||
AM_CONDITIONAL([INSTALL_SYSTEMD], [test "x$with_systemd" = "xyes"])
|
||||
|
||||
# Add the libconfig package
|
||||
if test "x${with_pkg_config}" = xyes ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[LIBCONFIG], [libconfig],
|
||||
[LIBS="${LIBCONFIG_LIBS} ${LIBS}"])
|
||||
else
|
||||
AC_CHECK_LIB([config],[config_init], , AC_MSG_ERROR([libconfig library needed]))
|
||||
fi
|
||||
|
||||
AC_ARG_WITH([configfiles],
|
||||
[ --with-configfiles = install configuration files during a make install ], ,[with_configfiles=yes])
|
||||
AM_CONDITIONAL([INSTALL_CONFIG_FILES], [test "x$with_configfiles" = "xyes"])
|
||||
|
||||
|
||||
# Look for piddir flag
|
||||
AC_ARG_WITH(piddir, [ --with-piddir=<pathname> Specify a pathname to a directory in which to write the PID file.], [
|
||||
AC_MSG_CHECKING(--with-piddir argument)
|
||||
@@ -57,11 +106,18 @@ AC_ARG_WITH(ssl, [ choose --with-ssl=openssl or --with-ssl=polarssl for encrypti
|
||||
AC_MSG_ERROR(choose either "openssl" or "polarssl" encryption)
|
||||
fi
|
||||
if test "x${with_ssl}" = xopenssl ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[SSL], [libssl,libcrypto],
|
||||
[LIBS="${SSL_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto])
|
||||
AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])])
|
||||
if test "x${with_pkg_config}" = x1 ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[SSL], [libssl,libcrypto],
|
||||
[LIBS="${SSL_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto])
|
||||
AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])])
|
||||
else
|
||||
AC_CHECK_LIB([crypto], [main], , AC_MSG_ERROR(libcrypto selected but the library cannot be found!))
|
||||
AC_CHECK_LIB([ssl], [main], , AC_MSG_ERROR(libssl selected but the library cannot be found!))
|
||||
AC_DEFINE([HAVE_LIBCRYPTO],[1],[Define to 1 if you have libcrypto])
|
||||
AC_DEFINE([HAVE_LIBSSL],[1],[Define to 1 if you have libssl])
|
||||
fi
|
||||
elif test "x${with_ssl}" = xpolarssl ; then
|
||||
AC_CHECK_LIB([polarssl],[ssl_init], , AC_MSG_ERROR(PolarSSL selected but the library cannot be found!))
|
||||
else
|
||||
@@ -74,6 +130,12 @@ AC_ARG_WITH(soxr, [ --with-soxr = choose libsoxr for high-quality interpolation
|
||||
AC_MSG_RESULT(>>Including support for soxr-based interpolation)
|
||||
AC_CHECK_LIB([soxr],[soxr_create], , AC_MSG_ERROR(soxr support requested but libsoxr not found!))], )
|
||||
|
||||
# Look for metadata flag -- set flag for conditional compilation
|
||||
AC_ARG_WITH(metadata, [ --with-metadata = include support for a metadata feed], [
|
||||
AC_MSG_RESULT(>>Including metadata support)
|
||||
AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])], )
|
||||
AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"])
|
||||
|
||||
# What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation
|
||||
# and the relevant link files are added.
|
||||
|
||||
@@ -99,10 +161,15 @@ AC_ARG_WITH(alsa, [ --with-alsa = choose native ALSA API support (GNU/Linux onl
|
||||
HAS_ALSA=1
|
||||
AM_CONDITIONAL([USE_ALSA], [test 0])
|
||||
AC_DEFINE([CONFIG_ALSA], 1, [Needed by the compiler.])
|
||||
PKG_CHECK_MODULES(
|
||||
[ALSA], [alsa],
|
||||
[LIBS="${ALSA_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])])])
|
||||
if test "x${with_pkg_config}" = x1 ; then
|
||||
PKG_CHECK_MODULES(
|
||||
[ALSA], [alsa],
|
||||
[LIBS="${ALSA_LIBS} ${LIBS}"
|
||||
AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])])
|
||||
else
|
||||
AC_CHECK_LIB([asound], [snd_pcm_open], , AC_MSG_ERROR(ALSA support requires the asound library!))
|
||||
AC_DEFINE([HAVE_LIBASOUND],[1],[Define to 1 if you have ALSA])
|
||||
fi ])
|
||||
AM_CONDITIONAL([USE_ALSA], [test "x$HAS_ALSA" = "x1"])
|
||||
|
||||
# Look for SNDIO flag
|
||||
@@ -122,8 +189,8 @@ AC_ARG_WITH(ao, [ --with-ao = choose native AO (Audio Output?) API support -- p
|
||||
AM_CONDITIONAL([USE_AO], [test "x$HAS_AO" = "x1"])
|
||||
|
||||
# Look for pulseaudio flag
|
||||
AC_ARG_WITH(pulseaudio, [ --with-pulseaudio = choose native PulseAudio API support -- probably broken], [
|
||||
AC_MSG_RESULT(>>Including a PulseAudio back end --- N.B. this is probably broken!)
|
||||
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!)
|
||||
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!))
|
||||
@@ -141,7 +208,7 @@ AM_CONDITIONAL([USE_DNS_SD], [test "x$HAS_DNS_SD" = "x1"])
|
||||
# Checks for header files.
|
||||
AC_HEADER_STDC
|
||||
AC_CHECK_HEADERS([getopt_long.h])
|
||||
AC_CHECK_HEADERS([arpa/inet.h fcntl.h mach/mach.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h syslog.h unistd.h])
|
||||
AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h mach/mach.h memory.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h syslog.h unistd.h])
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_C_INLINE
|
||||
@@ -157,11 +224,12 @@ AC_TYPE_UINT64_T
|
||||
AC_TYPE_UINT8_T
|
||||
|
||||
# Checks for library functions.
|
||||
AC_FUNC_ALLOCA
|
||||
AC_FUNC_ERROR_AT_LINE
|
||||
AC_FUNC_FORK
|
||||
AC_FUNC_MALLOC
|
||||
AC_FUNC_REALLOC
|
||||
AC_CHECK_FUNCS([clock_gettime gethostname gettimeofday inet_ntoa memchr memmove memset pow select socket stpcpy strcasecmp strchr strdup strerror strstr strtol strtoul])
|
||||
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_OUTPUT
|
||||
|
||||
+197
-6
@@ -2,7 +2,7 @@
|
||||
.SH NAME
|
||||
shairport-sync \- Synchronised Audio Player for iTunes / AirPlay
|
||||
.SH SYNOPSIS
|
||||
\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-E \fB\fIcommand\fB]\fB [--forkedDaapdLatency=\fB\fIlatency\fB]\fB [-i \fB\fIlatency\fB]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB
|
||||
\fBshairport-sync [-dvw]\fB [-a \fB\fIname\fB]\fB [-A \fB\fIlatency\fB]\fB [-B \fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB]\fB [-E \fB\fIcommand\fB]\fB [--forkedDaapdLatency=\fB\fIlatency\fB]\fB [--get-cover-art]\fB [-i \fB\fIlatency\fB]\fB [-L \fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB]\fB [--meta-dir=\fB\fIdirectory\fB]\fB [-o \fB\fIbackend\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB]\fB [--statistics]\fB [-S \fB\fImode\fB]\fB [-t \fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-- \fB\fIaudio_backend_options\fB]\fB
|
||||
|
||||
shairport-sync -D\fB
|
||||
|
||||
@@ -17,10 +17,190 @@ shairport-sync -R\fB
|
||||
shairport-sync -V\fB
|
||||
\f1
|
||||
.SH DESCRIPTION
|
||||
shairport-sync plays audio streamed from iTunes or from an AirPlay device to an audio device connected via an audio back end. At present, the only fully-implemented back end is for ALSA.
|
||||
shairport-sync plays audio streamed from iTunes or from an AirPlay device to an ALSA-compatible audio output device.
|
||||
|
||||
A feature of shairport-sync is that the audio is played synchronously. This means that if many devices are playing the same stream at the same time, all the outputs will stay in step with one another. This allows multiple devices play the same source without getting out of phase with one another, enabling, for example, simultaneous multi-room operation.
|
||||
|
||||
shairport-sync can additionally be compiled and configured to stream raw audio to a pipe or to stdout.
|
||||
|
||||
Settings can be made using the configuration file (recommended for all new installations) or by using command-line options.
|
||||
.SH CONFIGURATION FILE SETTINGS
|
||||
You should use the configuration file for setting up shairport-sync. This file is normally \fI/etc/shairport-sync.conf\f1. You may need to have root privileges to modify it.
|
||||
|
||||
Settings are organised into groups, for example, there is a "general" group of standard settings, and there is an "alsa" group with settings that pertain to the ALSA back end. Here is an example of a typical configuration file:
|
||||
|
||||
\fBgeneral = {\f1
|
||||
|
||||
\fBname = "Mike's Boombox";\f1
|
||||
|
||||
\fBinterpolation = "soxr";\f1
|
||||
|
||||
\fBpassword = "secret";\f1
|
||||
|
||||
\fB};\f1
|
||||
|
||||
\fB\f1
|
||||
|
||||
\fBalsa = {\f1
|
||||
|
||||
\fBoutput_device = "hw:0";\f1
|
||||
|
||||
\fBmixer_control_name = "PCM";\f1
|
||||
|
||||
\fB};\f1
|
||||
|
||||
Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specificed. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.
|
||||
|
||||
A sample configuration file with all possible settings, but with all of them commented out, is installed at \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.
|
||||
|
||||
The configuration file is processed using the \fIlibconfig\f1 library -- see \fBhttp://www.hyperrealm.com/libconfig/libconfig_manual.html\f1.
|
||||
.TP
|
||||
\fB"GENERAL" SETTINGS\f1
|
||||
These are the settings available within the \fBgeneral\f1 group:
|
||||
.TP
|
||||
\fBname=\f1\fI"service_name"\f1\fB;\f1
|
||||
Use this \fIservice_name\f1 to identify this player in iTunes, etc. The default name is "Shairport Sync on <hostname>".
|
||||
.TP
|
||||
\fBpassword=\f1\fI"password"\f1\fB;\f1
|
||||
Require the password \fIpassword\f1 to connect to the service. If you leave this setting commented out, no password is needed.
|
||||
.TP
|
||||
\fBinterpolation=\f1\fI"mode"\f1\fB;\f1
|
||||
Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly in synchrony with the player. The default mode, "basic", is normally almost completely inaudible. The alternative mode, "soxr", is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled.
|
||||
.TP
|
||||
\fBstatistics=\f1\fI"setting"\f1\fB;\f1
|
||||
Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
|
||||
.TP
|
||||
\fBmdns_backend=\f1\fI"backend"\f1\fB;\f1
|
||||
shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS \fIbackend\f1. The default is "avahi". Perform the command \fBshairport-sync -h\f1 to get a list of available mDNS modules.
|
||||
.TP
|
||||
\fBoutput_backend=\f1\fI"backend"\f1\fB;\f1
|
||||
shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. The default is "alsa". Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends. Only the alsa backend supports synchronisation.
|
||||
.TP
|
||||
\fBport=\f1\fIportnumber\f1\fB;\f1
|
||||
Use this to specify the \fIportnumber\f1 shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.
|
||||
.TP
|
||||
\fBudp_port_base=\f1\fIportnumber\f1\fB;\f1
|
||||
When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting \fIportnumber\f1 for these three ports. It will pick the first three unused ports starting from \fIportnumber\f1. The default is port 6001.
|
||||
.TP
|
||||
\fBudp_port_range=\f1\fIrange\f1\fB;\f1
|
||||
Use this in conjunction with the prevous setting to specify the \fIrange\f1 of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.
|
||||
.TP
|
||||
\fBdrift=\f1\fIframes\f1\fB;\f1
|
||||
Allow playback to drift up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections.
|
||||
.TP
|
||||
\fBresync_threshold=\f1\fIthreshold\f1\fB;\f1
|
||||
Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify 0 to disable resynchronisation.
|
||||
.TP
|
||||
\fBlog_verbosity=\f1\fI0\f1\fB;\f1
|
||||
Use this to specify how much debugging information should be output or logged. "0" means no debug information, "3" means most debug information. The default is "0".
|
||||
.TP
|
||||
\fBignore_volume_control=\f1\fI"choice"\f1\fB;\f1
|
||||
Set this \fIchoice\f1 to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is "no".
|
||||
.TP
|
||||
\fB"LATENCIES" SETTINGS\f1
|
||||
There are four default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and another matches the latency used by so-called "AirPlay" devices, including iOS devices and iTunes and Quicktime Player when they are playing video. A third latency is used when the audio source is forked-daapd. The fourth latency is the default if no other latency is chosen and is used for older versions of iTunes.
|
||||
|
||||
If you want to change latencies to compensate for a delay in the audio output device (which will have the same effect on all sources), instead of changing these individual latencies, consider using the \fBaudio_backend_latency_offset\f1 setting in the \fBalsa\f1 group (or the appropriate other group if you're not outputing through the alsa backend).
|
||||
.TP
|
||||
\fBitunes=\f1\fIlatency\f1\fB;\f1
|
||||
This is the \fIlatency\f1, in frames, used for iTunes 10 or later. Default is 99,400.
|
||||
.TP
|
||||
\fBairplay=\f1\fIlatency\f1\fB;\f1
|
||||
This is the \fIlatency\f1, in frames, used for AirPlay devices, including iOS devices and iTunes and Quicktime Player when they are playing video. Default is 88,200.
|
||||
.TP
|
||||
\fBforkedDaapd=\f1\fIlatency\f1\fB;\f1
|
||||
This is the \fIlatency\f1, in frames, used for forkedDaapd sources. Default is 99,400.
|
||||
.TP
|
||||
\fBdefault=\f1\fIlatency\f1\fB;\f1
|
||||
This is the \fIlatency\f1, in frames, used when the source is unrecognised. Default is 88,200.
|
||||
.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 provides 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.
|
||||
|
||||
The \fBmetadata\f1 group of settings allow you to enable metadata handling and to control certain aspects of it:
|
||||
.TP
|
||||
\fBenabled=\f1\fI"choice"\f1\fB;\f1
|
||||
Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it, along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".
|
||||
.TP
|
||||
\fBinclude_cover_art=\f1\fI"choice"\f1\fB;\f1
|
||||
Set the \fIchoice\f1 to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe. You must also enable metadata (see above). One reason for not including cover art is that the images can sometimes be very large and may delay transmission of subsequent metadata through the pipe. The default is "no".
|
||||
.TP
|
||||
\fBpipe_name=\f1\fI"filepathname"\f1\fB;\f1
|
||||
Specify the absolute path name of the pipe through which metadata should be sent The default is \fI/tmp/shairport-sync-metadata\f1".
|
||||
.TP
|
||||
\fB"SESSIONCONTROL" SETTINGS\f1
|
||||
shairport-sync can run programs just before it starts to play an audio stream and just after it finishes. You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends.
|
||||
.TP
|
||||
\fBrun_this_before_play_begins=\f1\fI"/path/to/application and args"\f1\fB;\f1
|
||||
Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate.
|
||||
.TP
|
||||
\fBrun_this_after_play_ends=\f1\fI"/path/to/application and args"\f1\fB;\f1
|
||||
Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application. The application must be marked as executable and, if it is a script, its first line must begin with the standard \fI#!/bin/...\f1 as appropriate.
|
||||
.TP
|
||||
\fBwait_for_completion=\f1\fI"choice"\f1\fB;\f1
|
||||
Set \fIchoice\f1 to "yes" to make shairport-sync wait until the programs specified in the \fBrun_this_before_play_begins\f1 and \fBrun_this_after_play_ends\f1 have completed execution before continuing. The default is "no".
|
||||
.TP
|
||||
\fBallow_session_interruption=\f1\fI"choice"\f1\fB;\f1
|
||||
If \fBchoice\f1 is set to "yes", then another source will be able to interrupt an existing play session and start a new one. When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal.
|
||||
.TP
|
||||
\fBsession_timeout=\f1\fIseconds\f1\fB;\f1
|
||||
If a play session has been established and the source disappears without warning (such as a device going out of range of a network) then wait for \fIseconds\f1 seconds before ending the session. Once the session has terminated, other devices can use it. The default is 120 seconds.
|
||||
.TP
|
||||
\fB"ALSA" SETTINGS\f1
|
||||
These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system. (By the way, you can use tools such as \fBalsamixer\f1 or \fBaplay\f1 to discover what devices are available.) Use these settings to select the output device and the mixer control to be used to control the output volume. You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the \fBalsa\f1 group settings:
|
||||
.TP
|
||||
\fBoutput_device=\f1\fI"output_device"\f1\fB;\f1
|
||||
Use the output device called \fIoutput_device\f1. The default is the device called "default".
|
||||
.TP
|
||||
.TP
|
||||
\fBmixer_control_name=\f1\fI"name"\f1\fB;\f1
|
||||
Specify the \fIname\f1 of the mixer control to be used by shairport-sync to control the volume. The mixer control must be on the mixer device, which by default is the output device. If you do not specify a mixer control name, shairport-sync will adjust the volume in software. \fBmixer_type=\f1\fI"mixer_type"\f1\fB;\f1
|
||||
This setting is deprecated and will be removed soon. If you wish to use a mixer control to control the volume, then set \fImixer_type\f1 to "hardware". The default is "software".
|
||||
.TP
|
||||
\fBmixer_device=\f1\fI"mixer_device"\f1\fB;\f1
|
||||
By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
|
||||
.TP
|
||||
\fBaudio_backend_latency_offset=\f1\fIoffset\f1\fB;\f1
|
||||
Set this \fIoffset\f1, 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.
|
||||
.TP
|
||||
\fBaudio_backend_buffer_desired_length=\f1\fIlength\f1\fB;\f1
|
||||
Use this to set the desired number frames to be in the output device's hardware output buffer. The default is 6,615 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines. If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying, or it may exceed the hardware buffer size. It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata.
|
||||
.TP
|
||||
\fB"PIPE" SETTINGS\f1
|
||||
These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo.
|
||||
|
||||
Use the \fIname\f1 setting to set the name and location of the pipe.
|
||||
|
||||
There are two further settings affecting timing that might be useful if the pipe reader is, for example, a program to play an audio stream such as \fBaplay\f1. 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 \fBpipe\f1 group:
|
||||
.TP
|
||||
\fBname=\f1\fI"/path/to/pipe"\f1\fB;\f1
|
||||
Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown. Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached. The sender will wait for up to five seconds for a packet to be written before discarding it.
|
||||
.TP
|
||||
\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1
|
||||
Packets of audio frames are written to the pipe synchronously -- that is, they are written to 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 the pipe 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 the pipe. 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 pipe reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the pipe 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 a pipe. Default setting is 44,100 frames.
|
||||
.TP
|
||||
\fB"STDOUT" SETTINGS\f1
|
||||
These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo.
|
||||
|
||||
There are two settings affecting timing that might be useful if the stdout reader is, for example, a program to play an audio stream such as \fBaplay\f1. 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 \fBstdout\f1 group:
|
||||
.TP
|
||||
\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1
|
||||
Packets of audio frames are written to stdout 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 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.
|
||||
.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.
|
||||
|
||||
Many of the options take sensible default values, so you can normally ignore most of them. See the EXAMPLES section for typical usages.
|
||||
|
||||
The command line for shairport-sync can take two kinds of options: regular \fBprogram options\f1 and \fBaudio backend options\f1. Program options are always listed first, followed by any audio backend options, preceded by a \fB--\f1 symbol.
|
||||
@@ -38,6 +218,9 @@ Execute \fIprogram\f1 when playback is about to begin. Specify the full path to
|
||||
|
||||
If you want shairport-sync to wait until the command has completed before starting to play, select the \fB-w\f1 option as well.
|
||||
.TP
|
||||
\fB-c \f1\fIfilename\f1\fB | --configfile=\f1\fIfilename\f1
|
||||
Read configuration settings from \fIfilename\f1. The default is to read them from \fI/etc/shairport-sync.conf\f1. For information about configuration settings, see the "Configuration File Settings" section above.
|
||||
.TP
|
||||
\fB-D | --disconnectFromOutput\f1
|
||||
Disconnect the shairport-sync daemon from the output device and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option).
|
||||
.TP
|
||||
@@ -52,6 +235,11 @@ If you want shairport-sync to wait until the command has completed before contin
|
||||
\fB--forkedDaapdLatency=\f1\fIlatency\f1
|
||||
Use this \fIlatency\f1, in frames, for audio streamed from a forked-daapd based source. The default is 99,400 frames, where there are 44,100 frames to the second.
|
||||
.TP
|
||||
\fB--get-coverart\f1
|
||||
This option requires the \fB--meta-dir\f1 option to be set, and enables shairport-sync to request cover art from the source and to transmit it through the metadata pipe.
|
||||
|
||||
Please note that cover art data may be very large, and may place too great a burden on your network. Also, at the time of writing, there appears to be a serious bug in iTunes which stalls transmission of large cover art items, stalling and potentially crashing shairport-sync.
|
||||
.TP
|
||||
\fB-h | --help\f1
|
||||
Print brief help message and exit.
|
||||
.TP
|
||||
@@ -64,6 +252,9 @@ Kill the shairport-sync daemon and exit. (Requires that the daemon has written i
|
||||
\fB-L | --latency=\f1\fIlatency\f1
|
||||
Use this to set the \fIdefault latency\f1, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the \fIdefault latency\f1 is 88,200 frames, where there are 44,100 frames to the second.
|
||||
.TP
|
||||
\fB--meta-dir=\f1\fIdirectory\f1
|
||||
Listen for metadata coming from the source and send it, along with metadata from shairport-sync itself, to a pipe called \fIshairport-sync-metadata\f1 in the \fIdirectory\f1 you specify. If you add the \fB--get-cover-art\f1 then cover art will be sent through the pipe too. See \fBhttps://github.com/mikebrady/shairport-sync-metadata-reader\f1 for a sample metadata reader.
|
||||
.TP
|
||||
\fB-m \f1\fImdnsbackend\f1\fB | --mdns=\f1\fImdnsbackend\f1
|
||||
Force the use of the specified mDNS backend to advertise the player on the network. The default is to try all mDNS backends until one works.
|
||||
.TP
|
||||
@@ -119,17 +310,17 @@ Use the specified output \fIdevice\f1. You may specify a card, e.g. \fBhw:0\f1,
|
||||
Use the specified hardware \fImixer\f1 for volume control. Use this to specify where the mixer is to be found. For example, if the mixer is associated with a card, as is often the case, specify the card, e.g. \fBhw:0\f1. If (unusually) the mixer is associated with a specific device on a card, specify the device, e.g. \fBhw:0,1\f1. The default is the device named in the \fB-d\f1 option, if given, or the device named \fBdefault\f1.
|
||||
.TP
|
||||
\fB-t \f1\fIdevicetype\f1
|
||||
The type of the output device is \fIdevicetype\f1 which must be \fBhardware\f1 or \fBsoftware\f1. The default is \fBsoftware\f1. If you specify \fBhardware\f1 you must also specify the output device using the \fB-d\f1 option and the name of the volume control using the \fB-c\f1 option. You may also need to specify the mixer using the \fB-m\f1 option.
|
||||
This option is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the -c option -- if you specify a mixer name with the -c option, it is assumed that the mixer is implemented in hardware.
|
||||
.SH EXAMPLES
|
||||
Here is a slightly contrived but typical example:
|
||||
|
||||
shairport-sync \fB-d\f1 \fB-a "Joe's Stereo"\f1 \fB-S soxr\f1 \fB--\f1 \fB-d hw:1,0\f1 \fB-m hw:1\f1 \fB-t hardware\f1 \fB-c PCM\f1
|
||||
shairport-sync \fB-d\f1 \fB-a "Joe's Stereo"\f1 \fB-S soxr\f1 \fB--\f1 \fB-d hw:1,0\f1 \fB-m hw:1\f1 \fB-c PCM\f1
|
||||
|
||||
The program will run in daemon mode ( \fB-d\f1 ), will be visible as "Joe's Stereo" ( \fB-a "Joe's Stereo"\f1 ) and will use the SoX Resampler Library-based stuffing ( \fB-S soxr\f1 ). The audio backend options following the \fB--\f1 separator specify that the audio will be output on output 0 of soundcard 1 ( \fB-d hw:1,0\f1 ) and will take advantage of the same sound card's hardware (\fB-t hardware\f1) mixer ( \fB-m hw:1\f1 ) using the level control named "PCM" ( \fB-c "PCM"\f1 ).
|
||||
The program will run in daemon mode ( \fB-d\f1 ), will be visible as "Joe's Stereo" ( \fB-a "Joe's Stereo"\f1 ) and will use the SoX Resampler Library-based stuffing ( \fB-S soxr\f1 ). The audio backend options following the \fB--\f1 separator specify that the audio will be output on output 0 of soundcard 1 ( \fB-d hw:1,0\f1 ) and will take advantage of the same sound card's mixer ( \fB-m hw:1\f1 ) using the level control named "PCM" ( \fB-c "PCM"\f1 ).
|
||||
|
||||
The example above is slightly contrived in order to show the use of the \fB-m\f1 option. Typically, output 0 is the default output of a card, so the output device could be written \fB-d hw:1\f1 and then the mixer option would be unnecessary, giving the following, simpler, command:
|
||||
|
||||
shairport-sync \fB-d\f1 \fB-a "Joe's Stereo"\f1 \fB-S soxr\f1 \fB--\f1 \fB-d hw:1\f1 \fB-t hardware\f1 \fB-c PCM\f1
|
||||
shairport-sync \fB-d\f1 \fB-a "Joe's Stereo"\f1 \fB-S soxr\f1 \fB--\f1 \fB-d hw:1\f1 \fB-c PCM\f1
|
||||
.SH CREDITS
|
||||
Mike Brady developed shairport-sync from the original shairport by James Laird.
|
||||
|
||||
|
||||
+342
-14
@@ -2,7 +2,7 @@
|
||||
<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
|
||||
<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
This man pager source file is part of shairport-sync.
|
||||
Copyright (c) Mike Brady 2014-2015
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
-->
|
||||
-->
|
||||
|
||||
<manpage name="shairport-sync" section="7" desc="Synchronised Audio Player for iTunes / AirPlay">
|
||||
|
||||
@@ -39,11 +39,14 @@
|
||||
<opt>[-a </opt><arg>name</arg><opt>]</opt>
|
||||
<opt>[-A </opt><arg>latency</arg><opt>]</opt>
|
||||
<opt>[-B </opt><arg>command</arg><opt>]</opt>
|
||||
<opt>[-c </opt><arg>configurationfile</arg><opt>]</opt>
|
||||
<opt>[-E </opt><arg>command</arg><opt>]</opt>
|
||||
<opt>[--forkedDaapdLatency=</opt><arg>latency</arg><opt>]</opt>
|
||||
<opt>[--get-cover-art]</opt>
|
||||
<opt>[-i </opt><arg>latency</arg><opt>]</opt>
|
||||
<opt>[-L </opt><arg>latency</arg><opt>]</opt>
|
||||
<opt>[-m </opt><arg>backend</arg><opt>]</opt>
|
||||
<opt>[--meta-dir=</opt><arg>directory</arg><opt>]</opt>
|
||||
<opt>[-o </opt><arg>backend</arg><opt>]</opt>
|
||||
<opt>[--password=</opt><arg>secret</arg><opt>]</opt>
|
||||
<opt>[-r </opt><arg>threshold</arg><opt>]</opt>
|
||||
@@ -63,8 +66,7 @@
|
||||
|
||||
<description>
|
||||
<p>shairport-sync plays audio streamed from iTunes or from an AirPlay
|
||||
device to an audio device connected via an audio back end. At present,
|
||||
the only fully-implemented back end is for ALSA.</p>
|
||||
device to an ALSA-compatible audio output device.</p>
|
||||
|
||||
<p> A feature of shairport-sync is that the audio is played synchronously.
|
||||
This means that if many devices are playing the same stream at the same
|
||||
@@ -72,11 +74,312 @@
|
||||
This allows multiple devices play the same source without getting out of phase with one another,
|
||||
enabling, for example, simultaneous multi-room operation.
|
||||
</p>
|
||||
|
||||
|
||||
<p>shairport-sync can additionally be compiled and configured to stream raw audio to a pipe or to stdout.</p>
|
||||
|
||||
<p>Settings can be made using the configuration file (recommended for all new installations) or by using command-line options.</p>
|
||||
|
||||
</description>
|
||||
|
||||
<section name="Configuration File Settings">
|
||||
<p>You should use the configuration file for setting up shairport-sync.
|
||||
This file is normally <file>/etc/shairport-sync.conf</file>.
|
||||
You may need to have root privileges to modify it.</p>
|
||||
|
||||
<p>Settings are organised into <i>groups</i>, for example, there is a "general" group of
|
||||
standard settings, and there is an "alsa" group with settings that pertain to the ALSA
|
||||
back end. Here is an example of a typical configuration file:</p>
|
||||
|
||||
<p><opt>general = {</opt></p>
|
||||
<p><p><opt>name = "Mike's Boombox";</opt></p></p>
|
||||
<p><p><opt>interpolation = "soxr";</opt></p></p>
|
||||
<p><p><opt>password = "secret";</opt></p></p>
|
||||
<p><opt>};</opt></p>
|
||||
<p><opt></opt></p>
|
||||
<p><opt>alsa = {</opt></p>
|
||||
<p><p><opt>output_device = "hw:0";</opt></p></p>
|
||||
<p><p><opt>mixer_control_name = "PCM";</opt></p></p>
|
||||
<p><opt>};</opt></p>
|
||||
|
||||
<p>Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and
|
||||
(3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specificed. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.</p>
|
||||
|
||||
<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
|
||||
you can use still use command line options, but any new features, etc. will
|
||||
be available only via configuration file settings.</p>
|
||||
|
||||
<p>The configuration file is processed using the <file>libconfig</file> library
|
||||
-- see <url href="http://www.hyperrealm.com/libconfig/libconfig_manual.html"/>.</p>
|
||||
|
||||
<option><p><opt>"GENERAL" SETTINGS</opt></p></option>
|
||||
<p>These are the settings available within the <opt>general</opt> group:</p>
|
||||
|
||||
<option>
|
||||
<p><opt>name=</opt><arg>"service_name"</arg><opt>;</opt></p>
|
||||
<optdesc>
|
||||
Use this <arg>service_name</arg> to identify this player in iTunes, etc.
|
||||
The default name is "Shairport Sync on <hostname>".
|
||||
</optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>password=</opt><arg>"password"</arg><opt>;</opt></p>
|
||||
<optdesc>Require the password <arg>password</arg> to connect to the service. If you leave this setting commented out, no password is needed.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>interpolation=</opt><arg>"mode"</arg><opt>;</opt></p>
|
||||
<optdesc>Interpolate, or "stuff", the audio stream using the <arg>mode</arg>. Interpolation here refers to the
|
||||
process of adding or removing frames of audio to or from the
|
||||
stream sent to the output device to keep it exactly in synchrony
|
||||
with the player.
|
||||
The default mode, "basic", is normally almost completely inaudible.
|
||||
The alternative mode, "soxr", is even less obtrusive but
|
||||
requires much more processing power. For this mode, support for
|
||||
libsoxr, the SoX Resampler Library, must be selected when
|
||||
shairport-sync is compiled.
|
||||
</optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>statistics=</opt><arg>"setting"</arg><opt>;</opt></p>
|
||||
<optdesc>Use this <arg>setting</arg> to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.</optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>mdns_backend=</opt><arg>"backend"</arg><opt>;</opt></p>
|
||||
<optdesc>shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS <arg>backend</arg>. The default is "avahi". Perform the command <opt>shairport-sync -h</opt> to get a list of available mDNS modules.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>output_backend=</opt><arg>"backend"</arg><opt>;</opt></p>
|
||||
<optdesc>shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio <arg>backend</arg>. The default is "alsa". Perform the command <opt>shairport-sync -h</opt> to get a list of available audio backends. Only the alsa backend supports synchronisation.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>port=</opt><arg>portnumber</arg><opt>;</opt></p>
|
||||
<optdesc>Use this to specify the <arg>portnumber</arg> shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>udp_port_base=</opt><arg>portnumber</arg><opt>;</opt></p>
|
||||
<optdesc>When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting <arg>portnumber</arg> for these three ports. It will pick the first three unused ports starting from <arg>portnumber</arg>. The default is port 6001.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>udp_port_range=</opt><arg>range</arg><opt>;</opt></p>
|
||||
<optdesc>Use this in conjunction with the prevous setting to specify the <arg>range</arg> of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>drift=</opt><arg>frames</arg><opt>;</opt></p>
|
||||
<optdesc>Allow playback to drift up to <arg>frames</arg> out of exact synchronization before attempting to correct it.
|
||||
The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur.
|
||||
Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the <opt>statistics</opt> setting to
|
||||
monitor correction levels. Corrections should not greatly exceed net corrections.
|
||||
</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>resync_threshold=</opt><arg>threshold</arg><opt>;</opt></p>
|
||||
<optdesc>Resynchronise if timings differ by more than <arg>threshold</arg> frames.
|
||||
If the output timing differs from the source timing by more than
|
||||
the threshold, output will be muted and a full resynchronisation
|
||||
will occur. The default threshold is 2,205 frames, i.e. 50
|
||||
milliseconds. Specify 0 to disable resynchronisation.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>log_verbosity=</opt><arg>0</arg><opt>;</opt></p>
|
||||
<optdesc>Use this to specify how much debugging information should be output or logged. "0" means no debug information, "3" means most debug information. The default is "0".</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>ignore_volume_control=</opt><arg>"choice"</arg><opt>;</opt></p>
|
||||
<optdesc>Set this <arg>choice</arg> to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is "no".</optdesc>
|
||||
</option>
|
||||
|
||||
<option><p><opt>"LATENCIES" SETTINGS</opt></p></option>
|
||||
<p>There are four default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and another matches the latency used by so-called "AirPlay" devices, including iOS devices and iTunes and Quicktime Player when they are playing video. A third latency is used when the audio source is forked-daapd. The fourth latency is the default if no other latency is chosen and is used for older versions of iTunes.</p>
|
||||
<p>If you want to change latencies to compensate for a delay in the audio output device (which will have the same effect on all sources), instead of changing these individual latencies, consider using the <opt>audio_backend_latency_offset</opt> setting in the <opt>alsa</opt> group (or the appropriate other group if you're not outputing through the alsa backend).</p>
|
||||
|
||||
|
||||
<option>
|
||||
<p><opt>itunes=</opt><arg>latency</arg><opt>;</opt></p>
|
||||
<optdesc>This is the <arg>latency</arg>, in frames, used for iTunes 10 or later. Default is 99,400.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>airplay=</opt><arg>latency</arg><opt>;</opt></p>
|
||||
<optdesc>This is the <arg>latency</arg>, in frames, used for AirPlay devices, including iOS devices and iTunes and Quicktime Player when they are playing video. Default is 88,200.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>forkedDaapd=</opt><arg>latency</arg><opt>;</opt></p>
|
||||
<optdesc>This is the <arg>latency</arg>, in frames, used for forkedDaapd sources. Default is 99,400.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>default=</opt><arg>latency</arg><opt>;</opt></p>
|
||||
<optdesc>This is the <arg>latency</arg>, in frames, used when the source is unrecognised. Default is 88,200.</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 provides the metadata to a pipe, by default <file>/tmp/shairport-sync-metadata</file>.
|
||||
To process metadata, shairport-sync must have been compiled with metadata support included.
|
||||
You can check that this is so by running <opt>shairport-sync -V</opt>; the identification string will contain the word <opt>metadata</opt>.</p>
|
||||
<p>The <opt>metadata</opt> group of settings allow you to enable metadata handling and to control certain aspects of it:</p>
|
||||
|
||||
|
||||
<option>
|
||||
<p><opt>enabled=</opt><arg>"choice"</arg><opt>;</opt></p>
|
||||
<optdesc>Set the <arg>choice</arg> to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it,
|
||||
along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>include_cover_art=</opt><arg>"choice"</arg><opt>;</opt></p>
|
||||
<optdesc>Set the <arg>choice</arg> to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe.
|
||||
You must also enable metadata (see above).
|
||||
One reason for not including cover art is that the images can sometimes be very large and may delay transmission of subsequent metadata through the pipe.
|
||||
The default is "no".</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>pipe_name=</opt><arg>"filepathname"</arg><opt>;</opt></p>
|
||||
<optdesc>Specify the absolute path name of the pipe through which metadata should be sent The default is <file>/tmp/shairport-sync-metadata</file>".</optdesc>
|
||||
</option>
|
||||
|
||||
<option><p><opt>"SESSIONCONTROL" SETTINGS</opt></p></option>
|
||||
<p>shairport-sync can run programs just before it starts to play an audio stream and just after it finishes.
|
||||
You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends. </p>
|
||||
|
||||
|
||||
<option>
|
||||
<p><opt>run_this_before_play_begins=</opt><arg>"/path/to/application and args"</arg><opt>;</opt></p>
|
||||
<optdesc>Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application.
|
||||
The application must be marked as executable and, if it is a script, its first line must begin with the standard <file>#!/bin/...</file> as appropriate.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>run_this_after_play_ends=</opt><arg>"/path/to/application and args"</arg><opt>;</opt></p>
|
||||
<optdesc>Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application.
|
||||
The application must be marked as executable and, if it is a script, its first line must begin with the standard <file>#!/bin/...</file> as appropriate.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>wait_for_completion=</opt><arg>"choice"</arg><opt>;</opt></p>
|
||||
<optdesc>Set <arg>choice</arg> to "yes" to make shairport-sync wait until the programs specified in the <opt>run_this_before_play_begins</opt>
|
||||
and <opt>run_this_after_play_ends</opt> have completed execution before continuing. The default is "no".</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>allow_session_interruption=</opt><arg>"choice"</arg><opt>;</opt></p>
|
||||
<optdesc>If <opt>choice</opt> is set to "yes", then another source will be able to interrupt an existing play session and start a new one.
|
||||
When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>session_timeout=</opt><arg>seconds</arg><opt>;</opt></p>
|
||||
<optdesc>If a play session has been established and the source disappears without warning (such as a device going out of range of a network)
|
||||
then wait for <arg>seconds</arg> seconds before ending the session. Once the session has terminated, other devices can use it.
|
||||
The default is 120 seconds.</optdesc>
|
||||
</option>
|
||||
|
||||
<option><p><opt>"ALSA" SETTINGS</opt></p></option>
|
||||
<p>These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system.
|
||||
(By the way, you can use tools such as <opt>alsamixer</opt> or <opt>aplay</opt> to discover what devices are available.)
|
||||
Use these settings to select the output device and the mixer control to be used to control the output volume.
|
||||
You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the <opt>alsa</opt> group settings:</p>
|
||||
|
||||
<option>
|
||||
<p><opt>output_device=</opt><arg>"output_device"</arg><opt>;</opt></p>
|
||||
<optdesc>Use the output device called <arg>output_device</arg>. The default is the device called "default".</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<option>
|
||||
<p><opt>mixer_control_name=</opt><arg>"name"</arg><opt>;</opt></p>
|
||||
<optdesc>Specify the <arg>name</arg> of the mixer control to be used by shairport-sync to control the volume.
|
||||
The mixer control must be on the mixer device, which by default is the output device.
|
||||
If you do not specify a mixer control name, shairport-sync will adjust the volume in software.</optdesc>
|
||||
</option>
|
||||
<p><opt>mixer_type=</opt><arg>"mixer_type"</arg><opt>;</opt></p>
|
||||
<optdesc>This setting is deprecated and will be removed soon. If you wish to use a mixer control to control the volume, then set <arg>mixer_type</arg> to "hardware".
|
||||
The default is "software".</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>mixer_device=</opt><arg>"mixer_device"</arg><opt>;</opt></p>
|
||||
<optdesc>By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>audio_backend_latency_offset=</opt><arg>offset</arg><opt>;</opt></p>
|
||||
<optdesc>Set this <arg>offset</arg>, 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.</optdesc>
|
||||
</option>
|
||||
<option>
|
||||
<p><opt>audio_backend_buffer_desired_length=</opt><arg>length</arg><opt>;</opt></p>
|
||||
<optdesc>Use this to set the desired number frames to be in the output device's hardware output buffer.
|
||||
The default is 6,615 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines.
|
||||
If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying,
|
||||
or it may exceed the hardware buffer size.
|
||||
It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata.</optdesc>
|
||||
</option>
|
||||
|
||||
<option><p><opt>"PIPE" SETTINGS</opt></p></option>
|
||||
<p>These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second,
|
||||
stereo.</p>
|
||||
<p>Use the <arg>name</arg> setting to set the name and location of the pipe.</p>
|
||||
<p>There are two further settings affecting timing that might be useful if the pipe reader is, for example,
|
||||
a program to play an audio stream such as <opt>aplay</opt>. 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>pipe</opt> group:</p>
|
||||
|
||||
<option>
|
||||
<p><opt>name=</opt><arg>"/path/to/pipe"</arg><opt>;</opt></p>
|
||||
<optdesc>Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown.
|
||||
Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached.
|
||||
The sender will wait for up to five seconds for a packet to be written before discarding it.</optdesc>
|
||||
</option>
|
||||
|
||||
<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 pipe synchronously -- that is, they are written to 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 the pipe 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 the pipe.
|
||||
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 pipe reader's buffer, giving it a nominal buffer size of 44,100 frames.
|
||||
Note that if the pipe 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 a pipe. Default setting is 44,100 frames.
|
||||
</optdesc>
|
||||
</option>
|
||||
|
||||
<option><p><opt>"STDOUT" SETTINGS</opt></p></option>
|
||||
<p>These settings are for the STDOUT backend, used to route audio to standard output ("stdout").
|
||||
The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo.</p>
|
||||
<p>There are two settings affecting timing that might be useful if the stdout reader is, for example,
|
||||
a program to play an audio stream such as <opt>aplay</opt>. 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>stdout</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 stdout 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 stdout.
|
||||
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 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.
|
||||
</optdesc>
|
||||
</option>
|
||||
|
||||
</section>
|
||||
|
||||
<options>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -120,6 +423,13 @@
|
||||
</p></optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>-c </opt><arg>filename</arg><opt> | --configfile=</opt><arg>filename</arg></p>
|
||||
<optdesc><p>
|
||||
Read configuration settings from <arg>filename</arg>. The default is to read them from <file>/etc/shairport-sync.conf</file>. For information about configuration settings, see the "Configuration File Settings" section above.
|
||||
</p></optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>-D | --disconnectFromOutput</opt></p>
|
||||
<optdesc><p>
|
||||
@@ -160,6 +470,19 @@
|
||||
frames to the second.
|
||||
</p></optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>--get-coverart</opt></p>
|
||||
<optdesc><p>
|
||||
This option requires the <opt>--meta-dir</opt> option to be set, and enables
|
||||
shairport-sync to request cover art from the source and to transmit it through
|
||||
the metadata pipe.</p>
|
||||
<p>Please note that cover art data may be very large, and may place too great a
|
||||
burden on your network. Also, at the time of writing, there appears to be a
|
||||
serious bug in iTunes which stalls transmission of large cover art items,
|
||||
stalling and potentially crashing shairport-sync.
|
||||
</p></optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>-h | --help</opt></p>
|
||||
@@ -193,6 +516,17 @@
|
||||
</p></optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>--meta-dir=</opt><arg>directory</arg></p>
|
||||
<optdesc><p>
|
||||
Listen for metadata coming from the source and send it, along with metadata from
|
||||
shairport-sync itself, to a pipe called <arg>shairport-sync-metadata</arg>
|
||||
in the <arg>directory</arg> you specify. If you add the <opt>--get-cover-art</opt> then
|
||||
cover art will be sent through the pipe too. See <url href="https://github.com/mikebrady/shairport-sync-metadata-reader"/>
|
||||
for a sample metadata reader.
|
||||
</p></optdesc>
|
||||
</option>
|
||||
|
||||
<option>
|
||||
<p><opt>-m </opt><arg>mdnsbackend</arg><opt> | --mdns=</opt><arg>mdnsbackend</arg></p>
|
||||
<optdesc><p>
|
||||
@@ -372,11 +706,8 @@
|
||||
<p><opt>-t </opt><arg>devicetype</arg></p>
|
||||
<optdesc>
|
||||
<p>
|
||||
The type of the output device is <arg>devicetype</arg> which must be
|
||||
<opt>hardware</opt> or <opt>software</opt>. The default is <opt>software</opt>.
|
||||
If you specify <opt>hardware</opt> you must also specify the output device using the
|
||||
<opt>-d</opt> option and the name of the volume control using the <opt>-c</opt> option.
|
||||
You may also need to specify the mixer using the <opt>-m</opt> option.
|
||||
This option is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the -c option
|
||||
-- if you specify a mixer name with the -c option, it is assumed that the mixer is implemented in hardware.
|
||||
</p></optdesc>
|
||||
</option>
|
||||
</options>
|
||||
@@ -389,7 +720,6 @@
|
||||
<opt>--</opt>
|
||||
<opt>-d hw:1,0</opt>
|
||||
<opt>-m hw:1</opt>
|
||||
<opt>-t hardware</opt>
|
||||
<opt>-c PCM</opt>
|
||||
</cmd>
|
||||
<p>The program will run in daemon mode ( <opt>-d</opt> ), will be visible as
|
||||
@@ -397,8 +727,7 @@
|
||||
Library-based stuffing ( <opt>-S soxr</opt> ).
|
||||
The audio backend options following the <opt>--</opt> separator specify
|
||||
that the audio will be output on output 0 of soundcard 1 (
|
||||
<opt>-d hw:1,0</opt> ) and will take advantage of the same sound card's hardware
|
||||
(<opt>-t hardware</opt>) mixer ( <opt>-m hw:1</opt> )
|
||||
<opt>-d hw:1,0</opt> ) and will take advantage of the same sound card's mixer ( <opt>-m hw:1</opt> )
|
||||
using the level control named "PCM" ( <opt>-c "PCM"</opt> ).
|
||||
</p>
|
||||
<p>The example above is slightly contrived in order to show the use of the <opt>-m</opt> option.
|
||||
@@ -410,7 +739,6 @@
|
||||
<opt>-S soxr</opt>
|
||||
<opt>--</opt>
|
||||
<opt>-d hw:1</opt>
|
||||
<opt>-t hardware</opt>
|
||||
<opt>-c PCM</opt>
|
||||
</cmd>
|
||||
|
||||
|
||||
+341
-11
@@ -10,11 +10,14 @@
|
||||
<b>[-a </b><em>name</em><b>]</b>
|
||||
<b>[-A </b><em>latency</em><b>]</b>
|
||||
<b>[-B </b><em>command</em><b>]</b>
|
||||
<b>[-c </b><em>configurationfile</em><b>]</b>
|
||||
<b>[-E </b><em>command</em><b>]</b>
|
||||
<b>[--forkedDaapdLatency=</b><em>latency</em><b>]</b>
|
||||
<b>[--get-cover-art]</b>
|
||||
<b>[-i </b><em>latency</em><b>]</b>
|
||||
<b>[-L </b><em>latency</em><b>]</b>
|
||||
<b>[-m </b><em>backend</em><b>]</b>
|
||||
<b>[--meta-dir=</b><em>directory</em><b>]</b>
|
||||
<b>[-o </b><em>backend</em><b>]</b>
|
||||
<b>[--password=</b><em>secret</em><b>]</b>
|
||||
<b>[-r </b><em>threshold</em><b>]</b>
|
||||
@@ -43,8 +46,7 @@
|
||||
<h2>Description</h2>
|
||||
|
||||
<p>shairport-sync plays audio streamed from iTunes or from an AirPlay
|
||||
device to an audio device connected via an audio back end. At present,
|
||||
the only fully-implemented back end is for ALSA.</p>
|
||||
device to an ALSA-compatible audio output device.</p>
|
||||
|
||||
<p> A feature of shairport-sync is that the audio is played synchronously.
|
||||
This means that if many devices are playing the same stream at the same
|
||||
@@ -52,13 +54,316 @@
|
||||
This allows multiple devices play the same source without getting out of phase with one another,
|
||||
enabling, for example, simultaneous multi-room operation.
|
||||
</p>
|
||||
|
||||
<p>shairport-sync can additionally be compiled and configured to stream raw audio to a pipe or to stdout.</p>
|
||||
|
||||
<p>Settings can be made using the configuration file (recommended for all new installations) or by using command-line options.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
<h2>Configuration File Settings</h2>
|
||||
|
||||
<p>You should use the configuration file for setting up shairport-sync.
|
||||
This file is normally <em>/etc/shairport-sync.conf</em>.
|
||||
You may need to have root privileges to modify it.</p>
|
||||
|
||||
<p>Settings are organised into groups, for example, there is a "general" group of
|
||||
standard settings, and there is an "alsa" group with settings that pertain to the ALSA
|
||||
back end. Here is an example of a typical configuration file:</p>
|
||||
|
||||
<p><b>general = {</b></p>
|
||||
<p><p><b>name = "Mike's Boombox";</b></p></p>
|
||||
<p><p><b>interpolation = "soxr";</b></p></p>
|
||||
<p><p><b>password = "secret";</b></p></p>
|
||||
<p><b>};</b></p>
|
||||
<p><b></b></p>
|
||||
<p><b>alsa = {</b></p>
|
||||
<p><p><b>output_device = "hw:0";</b></p></p>
|
||||
<p><p><b>mixer_control_name = "PCM";</b></p></p>
|
||||
<p><b>};</b></p>
|
||||
|
||||
<p>Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and
|
||||
(3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specificed. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.</p>
|
||||
|
||||
<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
|
||||
you can use still use command line options, but any new features, etc. will
|
||||
be available only via configuration file settings.</p>
|
||||
|
||||
<p>The configuration file is processed using the <em>libconfig</em> library
|
||||
-- see <a href = "http://www.hyperrealm.com/libconfig/libconfig_manual.html">http://www.hyperrealm.com/libconfig/libconfig_manual.html</a>.</p>
|
||||
|
||||
<p><b>"GENERAL" SETTINGS</b></p>
|
||||
<p>These are the settings available within the <b>general</b> group:</p>
|
||||
|
||||
|
||||
<p><b>name=</b><em>"service_name"</em><b>;</b></p>
|
||||
|
||||
Use this <em>service_name</em> to identify this player in iTunes, etc.
|
||||
The default name is "Shairport Sync on <hostname>".
|
||||
|
||||
|
||||
|
||||
|
||||
<p><b>password=</b><em>"password"</em><b>;</b></p>
|
||||
Require the password <em>password</em> to connect to the service. If you leave this setting commented out, no password is needed.
|
||||
|
||||
|
||||
<p><b>interpolation=</b><em>"mode"</em><b>;</b></p>
|
||||
Interpolate, or "stuff", the audio stream using the <em>mode</em>. Interpolation here refers to the
|
||||
process of adding or removing frames of audio to or from the
|
||||
stream sent to the output device to keep it exactly in synchrony
|
||||
with the player.
|
||||
The default mode, "basic", is normally almost completely inaudible.
|
||||
The alternative mode, "soxr", is even less obtrusive but
|
||||
requires much more processing power. For this mode, support for
|
||||
libsoxr, the SoX Resampler Library, must be selected when
|
||||
shairport-sync is compiled.
|
||||
|
||||
|
||||
|
||||
|
||||
<p><b>statistics=</b><em>"setting"</em><b>;</b></p>
|
||||
Use this <em>setting</em> to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
|
||||
|
||||
|
||||
|
||||
<p><b>mdns_backend=</b><em>"backend"</em><b>;</b></p>
|
||||
shairport-sync has a number of modules of code ("backends") for interacting with the mDNS service to be used to advertise itself. Normally, the first mDNS backend that works is selected. This setting forces the selection of the specific mDNS <em>backend</em>. The default is "avahi". Perform the command <b>shairport-sync -h</b> to get a list of available mDNS modules.
|
||||
|
||||
|
||||
<p><b>output_backend=</b><em>"backend"</em><b>;</b></p>
|
||||
shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio <em>backend</em>. The default is "alsa". Perform the command <b>shairport-sync -h</b> to get a list of available audio backends. Only the alsa backend supports synchronisation.
|
||||
|
||||
|
||||
<p><b>port=</b><em>portnumber</em><b>;</b></p>
|
||||
Use this to specify the <em>portnumber</em> shairport-sync uses to listen for service requests from iTunes, etc. The default is port 5000.
|
||||
|
||||
|
||||
<p><b>udp_port_base=</b><em>portnumber</em><b>;</b></p>
|
||||
When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting <em>portnumber</em> for these three ports. It will pick the first three unused ports starting from <em>portnumber</em>. The default is port 6001.
|
||||
|
||||
|
||||
<p><b>udp_port_range=</b><em>range</em><b>;</b></p>
|
||||
Use this in conjunction with the prevous setting to specify the <em>range</em> of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.
|
||||
|
||||
|
||||
<p><b>drift=</b><em>frames</em><b>;</b></p>
|
||||
Allow playback to drift up to <em>frames</em> out of exact synchronization before attempting to correct it.
|
||||
The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur.
|
||||
Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the <b>statistics</b> setting to
|
||||
monitor correction levels. Corrections should not greatly exceed net corrections.
|
||||
|
||||
|
||||
|
||||
<p><b>resync_threshold=</b><em>threshold</em><b>;</b></p>
|
||||
Resynchronise if timings differ by more than <em>threshold</em> frames.
|
||||
If the output timing differs from the source timing by more than
|
||||
the threshold, output will be muted and a full resynchronisation
|
||||
will occur. The default threshold is 2,205 frames, i.e. 50
|
||||
milliseconds. Specify 0 to disable resynchronisation.
|
||||
|
||||
|
||||
<p><b>log_verbosity=</b><em>0</em><b>;</b></p>
|
||||
Use this to specify how much debugging information should be output or logged. "0" means no debug information, "3" means most debug information. The default is "0".
|
||||
|
||||
|
||||
<p><b>ignore_volume_control=</b><em>"choice"</em><b>;</b></p>
|
||||
Set this <em>choice</em> to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is "no".
|
||||
|
||||
|
||||
<p><b>"LATENCIES" SETTINGS</b></p>
|
||||
<p>There are four default latency settings, chosen automatically. One latency matches the latency used by recent versions of iTunes when playing audio and another matches the latency used by so-called "AirPlay" devices, including iOS devices and iTunes and Quicktime Player when they are playing video. A third latency is used when the audio source is forked-daapd. The fourth latency is the default if no other latency is chosen and is used for older versions of iTunes.</p>
|
||||
<p>If you want to change latencies to compensate for a delay in the audio output device (which will have the same effect on all sources), instead of changing these individual latencies, consider using the <b>audio_backend_latency_offset</b> setting in the <b>alsa</b> group (or the appropriate other group if you're not outputing through the alsa backend).</p>
|
||||
|
||||
|
||||
|
||||
<p><b>itunes=</b><em>latency</em><b>;</b></p>
|
||||
This is the <em>latency</em>, in frames, used for iTunes 10 or later. Default is 99,400.
|
||||
|
||||
|
||||
<p><b>airplay=</b><em>latency</em><b>;</b></p>
|
||||
This is the <em>latency</em>, in frames, used for AirPlay devices, including iOS devices and iTunes and Quicktime Player when they are playing video. Default is 88,200.
|
||||
|
||||
|
||||
<p><b>forkedDaapd=</b><em>latency</em><b>;</b></p>
|
||||
This is the <em>latency</em>, in frames, used for forkedDaapd sources. Default is 99,400.
|
||||
|
||||
|
||||
<p><b>default=</b><em>latency</em><b>;</b></p>
|
||||
This is the <em>latency</em>, in frames, used when the source is unrecognised. Default is 88,200.
|
||||
|
||||
|
||||
<p><b>"METADATA" 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 provides the metadata to a pipe, by default <em>/tmp/shairport-sync-metadata</em>.
|
||||
To process metadata, shairport-sync must have been compiled with metadata support included.
|
||||
You can check that this is so by running <b>shairport-sync -V</b>; the identification string will contain the word <b>metadata</b>.</p>
|
||||
<p>The <b>metadata</b> group of settings allow you to enable metadata handling and to control certain aspects of it:</p>
|
||||
|
||||
|
||||
|
||||
<p><b>enabled=</b><em>"choice"</em><b>;</b></p>
|
||||
Set the <em>choice</em> to "yes" to enable shairport-sync to look for metadata from the audio source and to forward it,
|
||||
along with metadata generated by shairport-sync itself, to the metadata pipe. The default is "no".
|
||||
|
||||
|
||||
<p><b>include_cover_art=</b><em>"choice"</em><b>;</b></p>
|
||||
Set the <em>choice</em> to "yes" to enable shairport-sync to look for cover art from the audio source and to include it in the feed to the metadata pipe.
|
||||
You must also enable metadata (see above).
|
||||
One reason for not including cover art is that the images can sometimes be very large and may delay transmission of subsequent metadata through the pipe.
|
||||
The default is "no".
|
||||
|
||||
|
||||
<p><b>pipe_name=</b><em>"filepathname"</em><b>;</b></p>
|
||||
Specify the absolute path name of the pipe through which metadata should be sent The default is <em>/tmp/shairport-sync-metadata</em>".
|
||||
|
||||
|
||||
<p><b>"SESSIONCONTROL" SETTINGS</b></p>
|
||||
<p>shairport-sync can run programs just before it starts to play an audio stream and just after it finishes.
|
||||
You specify them using the sessioncontrol group settings run_this_before_play_begins and run_this_after_play_ends. </p>
|
||||
|
||||
|
||||
|
||||
<p><b>run_this_before_play_begins=</b><em>"/path/to/application and args"</em><b>;</b></p>
|
||||
Here you can specify a program and its arguments that will be run just before a play session begins. Be careful to include the full path to the application.
|
||||
The application must be marked as executable and, if it is a script, its first line must begin with the standard <em>#!/bin/...</em> as appropriate.
|
||||
|
||||
|
||||
<p><b>run_this_after_play_ends=</b><em>"/path/to/application and args"</em><b>;</b></p>
|
||||
Here you can specify a program and its arguments that will be run just after a play session ends. Be careful to include the full path to the application.
|
||||
The application must be marked as executable and, if it is a script, its first line must begin with the standard <em>#!/bin/...</em> as appropriate.
|
||||
|
||||
|
||||
<p><b>wait_for_completion=</b><em>"choice"</em><b>;</b></p>
|
||||
Set <em>choice</em> to "yes" to make shairport-sync wait until the programs specified in the <b>run_this_before_play_begins</b>
|
||||
and <b>run_this_after_play_ends</b> have completed execution before continuing. The default is "no".
|
||||
|
||||
|
||||
<p><b>allow_session_interruption=</b><em>"choice"</em><b>;</b></p>
|
||||
If <b>choice</b> is set to "yes", then another source will be able to interrupt an existing play session and start a new one.
|
||||
When set to "no" (the default), other devices attempting to interrupt a session will fail, receiving a busy signal.
|
||||
|
||||
|
||||
<p><b>session_timeout=</b><em>seconds</em><b>;</b></p>
|
||||
If a play session has been established and the source disappears without warning (such as a device going out of range of a network)
|
||||
then wait for <em>seconds</em> seconds before ending the session. Once the session has terminated, other devices can use it.
|
||||
The default is 120 seconds.
|
||||
|
||||
|
||||
<p><b>"ALSA" SETTINGS</b></p>
|
||||
<p>These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system.
|
||||
(By the way, you can use tools such as <b>alsamixer</b> or <b>aplay</b> to discover what devices are available.)
|
||||
Use these settings to select the output device and the mixer control to be used to control the output volume.
|
||||
You can additionally set the desired size of the output buffer and you can adjust overall latency. Here are the <b>alsa</b> group settings:</p>
|
||||
|
||||
|
||||
<p><b>output_device=</b><em>"output_device"</em><b>;</b></p>
|
||||
Use the output device called <em>output_device</em>. The default is the device called "default".
|
||||
|
||||
|
||||
|
||||
<p><b>mixer_control_name=</b><em>"name"</em><b>;</b></p>
|
||||
Specify the <em>name</em> of the mixer control to be used by shairport-sync to control the volume.
|
||||
The mixer control must be on the mixer device, which by default is the output device.
|
||||
If you do not specify a mixer control name, shairport-sync will adjust the volume in software.
|
||||
|
||||
<p><b>mixer_type=</b><em>"mixer_type"</em><b>;</b></p>
|
||||
This setting is deprecated and will be removed soon. If you wish to use a mixer control to control the volume, then set <em>mixer_type</em> to "hardware".
|
||||
The default is "software".
|
||||
|
||||
|
||||
<p><b>mixer_device=</b><em>"mixer_device"</em><b>;</b></p>
|
||||
By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
|
||||
|
||||
|
||||
<p><b>audio_backend_latency_offset=</b><em>offset</em><b>;</b></p>
|
||||
Set this <em>offset</em>, 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.
|
||||
|
||||
|
||||
<p><b>audio_backend_buffer_desired_length=</b><em>length</em><b>;</b></p>
|
||||
Use this to set the desired number frames to be in the output device's hardware output buffer.
|
||||
The default is 6,615 frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines.
|
||||
If too large, the response times when using software volume control (i.e. when not using a mixer control to control volume) become annoying,
|
||||
or it may exceed the hardware buffer size.
|
||||
It may need to be larger on low-powered machines that are also performing other tasks, such as processing metadata.
|
||||
|
||||
|
||||
<p><b>"PIPE" SETTINGS</b></p>
|
||||
<p>These settings are for the PIPE backend, used to route audio to a named unix pipe. The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second,
|
||||
stereo.</p>
|
||||
<p>Use the <em>name</em> setting to set the name and location of the pipe.</p>
|
||||
<p>There are two further settings affecting timing that might be useful if the pipe reader is, for example,
|
||||
a program to play an audio stream such as <b>aplay</b>. 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>pipe</b> group:</p>
|
||||
|
||||
|
||||
<p><b>name=</b><em>"/path/to/pipe"</em><b>;</b></p>
|
||||
Use this to specify the name and location of the pipe. The pipe will be created and opened when shairport-sync starts up and will be closed upon shutdown.
|
||||
Frames of audio will be sent to the pipe in packets of 352 frames and will be discarded if the pipe has not have a reader attached.
|
||||
The sender will wait for up to five seconds for a packet to be written before discarding it.
|
||||
|
||||
|
||||
|
||||
<p><b>audio_backend_latency_offset=</b><em>offset_in_frames</em><b>;</b></p>
|
||||
|
||||
Packets of audio frames are written to the pipe synchronously -- that is, they are written to 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 the pipe 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 the pipe.
|
||||
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 pipe reader's buffer, giving it a nominal buffer size of 44,100 frames.
|
||||
Note that if the pipe 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 a pipe. Default setting is 44,100 frames.
|
||||
|
||||
|
||||
|
||||
<p><b>"STDOUT" SETTINGS</b></p>
|
||||
<p>These settings are for the STDOUT backend, used to route audio to standard output ("stdout").
|
||||
The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, stereo.</p>
|
||||
<p>There are two settings affecting timing that might be useful if the stdout reader is, for example,
|
||||
a program to play an audio stream such as <b>aplay</b>. 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>stdout</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 stdout 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 stdout.
|
||||
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 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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h2>Options</h2>
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -105,6 +410,13 @@
|
||||
|
||||
|
||||
|
||||
<p><b>-c </b><em>filename</em><b> | --configfile=</b><em>filename</em></p>
|
||||
<p>
|
||||
Read configuration settings from <em>filename</em>. The default is to read them from <em>/etc/shairport-sync.conf</em>. For information about configuration settings, see the "Configuration File Settings" section above.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p><b>-D | --disconnectFromOutput</b></p>
|
||||
<p>
|
||||
Disconnect the shairport-sync daemon from the output device and
|
||||
@@ -144,6 +456,19 @@
|
||||
frames to the second.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p><b>--get-coverart</b></p>
|
||||
<p>
|
||||
This option requires the <b>--meta-dir</b> option to be set, and enables
|
||||
shairport-sync to request cover art from the source and to transmit it through
|
||||
the metadata pipe.</p>
|
||||
<p>Please note that cover art data may be very large, and may place too great a
|
||||
burden on your network. Also, at the time of writing, there appears to be a
|
||||
serious bug in iTunes which stalls transmission of large cover art items,
|
||||
stalling and potentially crashing shairport-sync.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p><b>-h | --help</b></p>
|
||||
@@ -178,6 +503,17 @@
|
||||
|
||||
|
||||
|
||||
<p><b>--meta-dir=</b><em>directory</em></p>
|
||||
<p>
|
||||
Listen for metadata coming from the source and send it, along with metadata from
|
||||
shairport-sync itself, to a pipe called <em>shairport-sync-metadata</em>
|
||||
in the <em>directory</em> you specify. If you add the <b>--get-cover-art</b> then
|
||||
cover art will be sent through the pipe too. See <a href = "https://github.com/mikebrady/shairport-sync-metadata-reader">https://github.com/mikebrady/shairport-sync-metadata-reader</a>
|
||||
for a sample metadata reader.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p><b>-m </b><em>mdnsbackend</em><b> | --mdns=</b><em>mdnsbackend</em></p>
|
||||
<p>
|
||||
Force the use of the specified mDNS backend to advertise the
|
||||
@@ -358,11 +694,8 @@
|
||||
<p><b>-t </b><em>devicetype</em></p>
|
||||
|
||||
<p>
|
||||
The type of the output device is <em>devicetype</em> which must be
|
||||
<b>hardware</b> or <b>software</b>. The default is <b>software</b>.
|
||||
If you specify <b>hardware</b> you must also specify the output device using the
|
||||
<b>-d</b> option and the name of the volume control using the <b>-c</b> option.
|
||||
You may also need to specify the mixer using the <b>-m</b> option.
|
||||
This option is deprecated and is ignored. For your information, its functionality has been automatically incorporated in the -c option
|
||||
-- if you specify a mixer name with the -c option, it is assumed that the mixer is implemented in hardware.
|
||||
</p>
|
||||
|
||||
|
||||
@@ -376,7 +709,6 @@
|
||||
<b>--</b>
|
||||
<b>-d hw:1,0</b>
|
||||
<b>-m hw:1</b>
|
||||
<b>-t hardware</b>
|
||||
<b>-c PCM</b>
|
||||
<br>
|
||||
|
||||
@@ -385,8 +717,7 @@
|
||||
Library-based stuffing ( <b>-S soxr</b> ).
|
||||
The audio backend options following the <b>--</b> separator specify
|
||||
that the audio will be output on output 0 of soundcard 1 (
|
||||
<b>-d hw:1,0</b> ) and will take advantage of the same sound card's hardware
|
||||
(<b>-t hardware</b>) mixer ( <b>-m hw:1</b> )
|
||||
<b>-d hw:1,0</b> ) and will take advantage of the same sound card's mixer ( <b>-m hw:1</b> )
|
||||
using the level control named "PCM" ( <b>-c "PCM"</b> ).
|
||||
</p>
|
||||
<p>The example above is slightly contrived in order to show the use of the <b>-m</b> option.
|
||||
@@ -398,7 +729,6 @@
|
||||
<b>-S soxr</b>
|
||||
<b>--</b>
|
||||
<b>-d hw:1</b>
|
||||
<b>-t hardware</b>
|
||||
<b>-c PCM</b>
|
||||
<br>
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#include <memory.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
@@ -47,78 +46,66 @@ extern mdns_backend mdns_tinysvcmdns;
|
||||
|
||||
static mdns_backend *mdns_backends[] = {
|
||||
#ifdef CONFIG_AVAHI
|
||||
&mdns_avahi,
|
||||
&mdns_external_avahi,
|
||||
&mdns_avahi, &mdns_external_avahi,
|
||||
#endif
|
||||
#ifdef CONFIG_HAVE_DNS_SD_H
|
||||
&mdns_dns_sd,
|
||||
&mdns_external_dns_sd,
|
||||
&mdns_dns_sd, &mdns_external_dns_sd,
|
||||
#endif
|
||||
#ifdef CONFIG_TINYSVCMDNS
|
||||
&mdns_tinysvcmdns,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
NULL};
|
||||
|
||||
void mdns_register(void) {
|
||||
char *mdns_apname = alloca(strlen(config.apname) + 14);
|
||||
char *p = mdns_apname;
|
||||
int i;
|
||||
for (i=0; i<6; i++) {
|
||||
sprintf(p, "%02X", config.hw_addr[i]);
|
||||
p += 2;
|
||||
}
|
||||
*p++ = '@';
|
||||
strcpy(p, config.apname);
|
||||
char *mdns_apname = alloca(strlen(config.apname) + 14);
|
||||
char *p = mdns_apname;
|
||||
int i;
|
||||
for (i = 0; i < 6; i++) {
|
||||
sprintf(p, "%02X", config.hw_addr[i]);
|
||||
p += 2;
|
||||
}
|
||||
*p++ = '@';
|
||||
strcpy(p, config.apname);
|
||||
|
||||
mdns_backend **b = NULL;
|
||||
|
||||
if (config.mdns_name != NULL)
|
||||
{
|
||||
for (b = mdns_backends; *b; b++)
|
||||
{
|
||||
if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for
|
||||
continue;
|
||||
int error = (*b)->mdns_register(mdns_apname, config.port);
|
||||
if (error >= 0)
|
||||
{
|
||||
config.mdns = *b;
|
||||
}
|
||||
break;
|
||||
}
|
||||
mdns_backend **b = NULL;
|
||||
|
||||
if (*b == NULL)
|
||||
warn("%s mDNS backend not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (b = mdns_backends; *b; b++)
|
||||
{
|
||||
int error = (*b)->mdns_register(mdns_apname, config.port);
|
||||
if (error >= 0)
|
||||
{
|
||||
config.mdns = *b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (config.mdns_name != NULL) {
|
||||
for (b = mdns_backends; *b; b++) {
|
||||
if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for
|
||||
continue;
|
||||
int error = (*b)->mdns_register(mdns_apname, config.port);
|
||||
if (error >= 0) {
|
||||
config.mdns = *b;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.mdns == NULL)
|
||||
die("Could not establish mDNS advertisement!");
|
||||
if (*b == NULL)
|
||||
warn("%s mDNS backend not found");
|
||||
} else {
|
||||
for (b = mdns_backends; *b; b++) {
|
||||
int error = (*b)->mdns_register(mdns_apname, config.port);
|
||||
if (error >= 0) {
|
||||
config.mdns = *b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.mdns == NULL)
|
||||
die("Could not establish mDNS advertisement!");
|
||||
}
|
||||
|
||||
void mdns_unregister(void) {
|
||||
if (config.mdns) {
|
||||
config.mdns->mdns_unregister();
|
||||
}
|
||||
if (config.mdns) {
|
||||
config.mdns->mdns_unregister();
|
||||
}
|
||||
}
|
||||
|
||||
void mdns_ls_backends(void) {
|
||||
mdns_backend **b = NULL;
|
||||
printf("Available mDNS backends: \n");
|
||||
for (b = mdns_backends; *b; b++)
|
||||
{
|
||||
printf(" %s\n", (*b)->name);
|
||||
}
|
||||
mdns_backend **b = NULL;
|
||||
printf("Available mDNS backends: \n");
|
||||
for (b = mdns_backends; *b; b++) {
|
||||
printf(" %s\n", (*b)->name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef _MDNS_H
|
||||
#define _MDNS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
extern int mdns_pid;
|
||||
|
||||
void mdns_unregister(void);
|
||||
@@ -8,13 +10,24 @@ void mdns_register(void);
|
||||
void mdns_ls_backends(void);
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
int (*mdns_register)(char *apname, int port);
|
||||
void (*mdns_unregister)(void);
|
||||
char *name;
|
||||
int (*mdns_register)(char *apname, int port);
|
||||
void (*mdns_unregister)(void);
|
||||
} mdns_backend;
|
||||
|
||||
#define MDNS_RECORD_WITHOUT_METADATA "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", \
|
||||
"ss=16", "sr=44100", "vn=3", "txtvers=1", \
|
||||
config.password ? "pw=true" : "pw=false"
|
||||
// text and progress only -- picture feed really buggy from iTunes
|
||||
|
||||
#ifdef CONFIG_METADATA
|
||||
|
||||
#define METADATA_EXPRESSION config.get_coverart ? "md=0,1,2" : "md=0,2"
|
||||
|
||||
#define MDNS_RECORD_WITH_METADATA \
|
||||
"tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", METADATA_EXPRESSION, "ss=16", \
|
||||
"sr=44100", "vn=3", "txtvers=1", config.password ? "pw=true" : "pw=false"
|
||||
#endif
|
||||
|
||||
#define MDNS_RECORD_WITHOUT_METADATA \
|
||||
"tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", "ss=16", "sr=44100", "vn=3", \
|
||||
"txtvers=1", config.password ? "pw=true" : "pw=false"
|
||||
|
||||
#endif // _MDNS_H
|
||||
|
||||
+76
-83
@@ -42,111 +42,104 @@ static AvahiThreadedPoll *tpoll = NULL;
|
||||
static char *name = NULL;
|
||||
static int port = 0;
|
||||
|
||||
static void egroup_callback(AvahiEntryGroup *g,
|
||||
AvahiEntryGroupState state,
|
||||
static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
|
||||
AVAHI_GCC_UNUSED void *userdata) {
|
||||
if (state==AVAHI_ENTRY_GROUP_COLLISION)
|
||||
die("service name already exists on network!");
|
||||
if (state==AVAHI_ENTRY_GROUP_FAILURE)
|
||||
die("avahi entry group failure!");
|
||||
if (state == AVAHI_ENTRY_GROUP_COLLISION)
|
||||
die("service name already exists on network!");
|
||||
if (state == AVAHI_ENTRY_GROUP_FAILURE)
|
||||
die("avahi entry group failure!");
|
||||
}
|
||||
|
||||
static void register_service(AvahiClient *c) {
|
||||
debug(1, "avahi: register_service.");
|
||||
if (!group)
|
||||
group = avahi_entry_group_new(c, egroup_callback, NULL);
|
||||
if (!group)
|
||||
die("avahi_entry_group_new failed");
|
||||
debug(1, "avahi: register_service.");
|
||||
if (!group)
|
||||
group = avahi_entry_group_new(c, egroup_callback, NULL);
|
||||
if (!group)
|
||||
die("avahi_entry_group_new failed");
|
||||
|
||||
if (!avahi_entry_group_is_empty(group))
|
||||
return;
|
||||
if (!avahi_entry_group_is_empty(group))
|
||||
return;
|
||||
|
||||
int ret;
|
||||
debug(1,"Avahi without metadata");
|
||||
ret = avahi_entry_group_add_service(group,
|
||||
AVAHI_IF_UNSPEC,
|
||||
AVAHI_PROTO_UNSPEC,
|
||||
0,
|
||||
name,
|
||||
"_raop._tcp",
|
||||
NULL,
|
||||
NULL,
|
||||
port,
|
||||
MDNS_RECORD_WITHOUT_METADATA,
|
||||
int ret;
|
||||
#ifdef CONFIG_METADATA
|
||||
if (config.metadata_enabled) {
|
||||
debug(1, "Avahi with metadata");
|
||||
ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name,
|
||||
"_raop._tcp", NULL, NULL, port, MDNS_RECORD_WITH_METADATA,
|
||||
NULL);
|
||||
|
||||
if (ret < 0)
|
||||
die("avahi_entry_group_add_service failed");
|
||||
} else {
|
||||
#endif
|
||||
debug(1, "Avahi without metadata");
|
||||
ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name,
|
||||
"_raop._tcp", NULL, NULL, port,
|
||||
MDNS_RECORD_WITHOUT_METADATA, NULL);
|
||||
#ifdef CONFIG_METADATA
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = avahi_entry_group_commit(group);
|
||||
if (ret < 0)
|
||||
die("avahi_entry_group_commit failed");
|
||||
if (ret < 0)
|
||||
die("avahi_entry_group_add_service failed");
|
||||
|
||||
ret = avahi_entry_group_commit(group);
|
||||
if (ret < 0)
|
||||
die("avahi_entry_group_commit failed");
|
||||
}
|
||||
|
||||
static void client_callback(AvahiClient *c,
|
||||
AvahiClientState state,
|
||||
AVAHI_GCC_UNUSED void * userdata) {
|
||||
switch (state) {
|
||||
case AVAHI_CLIENT_S_REGISTERING:
|
||||
if (group)
|
||||
avahi_entry_group_reset(group);
|
||||
break;
|
||||
static void client_callback(AvahiClient *c, AvahiClientState state,
|
||||
AVAHI_GCC_UNUSED void *userdata) {
|
||||
switch (state) {
|
||||
case AVAHI_CLIENT_S_REGISTERING:
|
||||
if (group)
|
||||
avahi_entry_group_reset(group);
|
||||
break;
|
||||
|
||||
case AVAHI_CLIENT_S_RUNNING:
|
||||
register_service(c);
|
||||
break;
|
||||
case AVAHI_CLIENT_S_RUNNING:
|
||||
register_service(c);
|
||||
break;
|
||||
|
||||
case AVAHI_CLIENT_FAILURE:
|
||||
case AVAHI_CLIENT_S_COLLISION:
|
||||
die("avahi client failure");
|
||||
case AVAHI_CLIENT_FAILURE:
|
||||
case AVAHI_CLIENT_S_COLLISION:
|
||||
die("avahi client failure");
|
||||
|
||||
case AVAHI_CLIENT_CONNECTING:
|
||||
break;
|
||||
}
|
||||
case AVAHI_CLIENT_CONNECTING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int avahi_register(char *srvname, int srvport) {
|
||||
debug(1, "avahi: avahi_register.");
|
||||
name = strdup(srvname);
|
||||
port = srvport;
|
||||
debug(1, "avahi: avahi_register.");
|
||||
name = strdup(srvname);
|
||||
port = srvport;
|
||||
|
||||
int err;
|
||||
if (!(tpoll = avahi_threaded_poll_new())) {
|
||||
warn("couldn't create avahi threaded tpoll!");
|
||||
return -1;
|
||||
}
|
||||
if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll),
|
||||
0,
|
||||
client_callback,
|
||||
NULL,
|
||||
&err))) {
|
||||
warn("couldn't create avahi client: %s!", avahi_strerror(err));
|
||||
return -1;
|
||||
}
|
||||
int err;
|
||||
if (!(tpoll = avahi_threaded_poll_new())) {
|
||||
warn("couldn't create avahi threaded tpoll!");
|
||||
return -1;
|
||||
}
|
||||
if (!(client =
|
||||
avahi_client_new(avahi_threaded_poll_get(tpoll), 0, client_callback, NULL, &err))) {
|
||||
warn("couldn't create avahi client: %s!", avahi_strerror(err));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (avahi_threaded_poll_start(tpoll) < 0) {
|
||||
warn("couldn't start avahi tpoll thread");
|
||||
return -1;
|
||||
}
|
||||
if (avahi_threaded_poll_start(tpoll) < 0) {
|
||||
warn("couldn't start avahi tpoll thread");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void avahi_unregister(void) {
|
||||
debug(1, "avahi: avahi_unregister.");
|
||||
if (tpoll)
|
||||
avahi_threaded_poll_stop(tpoll);
|
||||
tpoll = NULL;
|
||||
debug(1, "avahi: avahi_unregister.");
|
||||
if (tpoll)
|
||||
avahi_threaded_poll_stop(tpoll);
|
||||
tpoll = NULL;
|
||||
|
||||
if (name)
|
||||
free(name);
|
||||
name = NULL;
|
||||
if (name)
|
||||
free(name);
|
||||
name = NULL;
|
||||
}
|
||||
|
||||
mdns_backend mdns_avahi =
|
||||
{
|
||||
.name = "avahi",
|
||||
.mdns_register = avahi_register,
|
||||
.mdns_unregister = avahi_unregister
|
||||
};
|
||||
|
||||
mdns_backend mdns_avahi = {
|
||||
.name = "avahi", .mdns_register = avahi_register, .mdns_unregister = avahi_unregister};
|
||||
|
||||
+46
-54
@@ -33,70 +33,62 @@
|
||||
static DNSServiceRef service;
|
||||
|
||||
static int mdns_dns_sd_register(char *apname, int port) {
|
||||
const char *recordwithoutmetadata[] = { MDNS_RECORD_WITHOUT_METADATA, NULL };
|
||||
const char **record = recordwithoutmetadata;
|
||||
|
||||
uint16_t length = 0;
|
||||
const char **field;
|
||||
const char *recordwithoutmetadata[] = {MDNS_RECORD_WITHOUT_METADATA, NULL};
|
||||
#ifdef CONFIG_METADATA
|
||||
const char *recordwithmetadata[] = {MDNS_RECORD_WITH_METADATA, NULL};
|
||||
#endif
|
||||
char **record;
|
||||
#ifdef CONFIG_METADATA
|
||||
if (config.meta_dir)
|
||||
record = recordwithmetadata;
|
||||
else
|
||||
#endif
|
||||
record = recordwithoutmetadata;
|
||||
|
||||
// Concatenate string contained i record into buf.
|
||||
uint16_t length = 0;
|
||||
const char **field;
|
||||
|
||||
for (field = record; *field; field ++)
|
||||
{
|
||||
length += strlen(*field) + 1; // One byte for length each time
|
||||
}
|
||||
// Concatenate string contained i record into buf.
|
||||
|
||||
char *buf = malloc(length * sizeof(char));
|
||||
if (buf == NULL)
|
||||
{
|
||||
warn("dns_sd: buffer record allocation failed");
|
||||
return -1;
|
||||
}
|
||||
for (field = record; *field; field++) {
|
||||
length += strlen(*field) + 1; // One byte for length each time
|
||||
}
|
||||
|
||||
char *p = buf;
|
||||
char *buf = malloc(length * sizeof(char));
|
||||
if (buf == NULL) {
|
||||
warn("dns_sd: buffer record allocation failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (field = record; *field; field ++)
|
||||
{
|
||||
char * newp = stpcpy(p + 1, *field);
|
||||
*p = newp - p - 1;
|
||||
p = newp;
|
||||
}
|
||||
char *p = buf;
|
||||
|
||||
DNSServiceErrorType error;
|
||||
error = DNSServiceRegister(&service,
|
||||
0,
|
||||
kDNSServiceInterfaceIndexAny,
|
||||
apname,
|
||||
"_raop._tcp",
|
||||
"",
|
||||
NULL,
|
||||
htons((uint16_t)port),
|
||||
length,
|
||||
buf,
|
||||
NULL,
|
||||
NULL);
|
||||
for (field = record; *field; field++) {
|
||||
char *newp = stpcpy(p + 1, *field);
|
||||
*p = newp - p - 1;
|
||||
p = newp;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
DNSServiceErrorType error;
|
||||
error = DNSServiceRegister(&service, 0, kDNSServiceInterfaceIndexAny, apname, "_raop._tcp", "",
|
||||
NULL, htons((uint16_t)port), length, buf, NULL, NULL);
|
||||
|
||||
if (error == kDNSServiceErr_NoError)
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
warn("dns-sd: DNSServiceRegister error %d", error);
|
||||
return -1;
|
||||
}
|
||||
free(buf);
|
||||
|
||||
if (error == kDNSServiceErr_NoError)
|
||||
return 0;
|
||||
else {
|
||||
warn("dns-sd: DNSServiceRegister error %d", error);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void mdns_dns_sd_unregister(void) {
|
||||
if (service)
|
||||
{
|
||||
DNSServiceRefDeallocate(service);
|
||||
service = NULL;
|
||||
}
|
||||
if (service) {
|
||||
DNSServiceRefDeallocate(service);
|
||||
service = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
mdns_backend mdns_dns_sd = {
|
||||
.name = "dns-sd",
|
||||
.mdns_register = mdns_dns_sd_register,
|
||||
.mdns_unregister = mdns_dns_sd_unregister
|
||||
};
|
||||
mdns_backend mdns_dns_sd = {.name = "dns-sd",
|
||||
.mdns_register = mdns_dns_sd_register,
|
||||
.mdns_unregister = mdns_dns_sd_unregister};
|
||||
|
||||
+101
-93
@@ -41,120 +41,128 @@ int mdns_pid = 0;
|
||||
* Check errno for error details.
|
||||
*/
|
||||
static int fork_execvp(const char *file, char *const argv[]) {
|
||||
int execpipe[2];
|
||||
int pid = 0;
|
||||
if (pipe(execpipe) < 0) {
|
||||
return -1;
|
||||
}
|
||||
int execpipe[2];
|
||||
int pid = 0;
|
||||
if (pipe(execpipe) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC) < 0) {
|
||||
close(execpipe[0]);
|
||||
close(execpipe[1]);
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC) < 0) {
|
||||
close(execpipe[0]);
|
||||
close(execpipe[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
close(execpipe[0]);
|
||||
close(execpipe[1]);
|
||||
return -1;
|
||||
}
|
||||
else if(pid == 0) { // Child
|
||||
close(execpipe[0]); // Close the read end
|
||||
execvp(file, argv);
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
close(execpipe[0]);
|
||||
close(execpipe[1]);
|
||||
return -1;
|
||||
} else if (pid == 0) { // Child
|
||||
close(execpipe[0]); // Close the read end
|
||||
execvp(file, argv);
|
||||
|
||||
// If we reach this point then execve has failed.
|
||||
// Write erno's value into the pipe and exit.
|
||||
int ignore = write(execpipe[1], &errno, sizeof(errno));
|
||||
// If we reach this point then execve has failed.
|
||||
// Write erno's value into the pipe and exit.
|
||||
int ignore = write(execpipe[1], &errno, sizeof(errno));
|
||||
|
||||
_exit(-1);
|
||||
return 0; // Just to make the compiler happy.
|
||||
}
|
||||
else { // Parent
|
||||
close(execpipe[1]); // Close the write end
|
||||
_exit(-1);
|
||||
return 0; // Just to make the compiler happy.
|
||||
} else { // Parent
|
||||
close(execpipe[1]); // Close the write end
|
||||
|
||||
int childErrno;
|
||||
// Block until child closes the pipe or sends errno.
|
||||
if(read(execpipe[0], &childErrno, sizeof(childErrno)) == sizeof(childErrno)) { // We received errno
|
||||
errno = childErrno;
|
||||
return -1;
|
||||
}
|
||||
else { // Child closed the pipe. execvp was successful.
|
||||
return pid;
|
||||
}
|
||||
int childErrno;
|
||||
// Block until child closes the pipe or sends errno.
|
||||
if (read(execpipe[0], &childErrno, sizeof(childErrno)) ==
|
||||
sizeof(childErrno)) { // We received errno
|
||||
errno = childErrno;
|
||||
return -1;
|
||||
} else { // Child closed the pipe. execvp was successful.
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int mdns_external_avahi_register(char *apname, int port) {
|
||||
char mdns_port[6];
|
||||
sprintf(mdns_port, "%d", config.port);
|
||||
char mdns_port[6];
|
||||
sprintf(mdns_port, "%d", config.port);
|
||||
|
||||
char *argvwithoutmetadata[] = {
|
||||
NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL
|
||||
};
|
||||
char **argv = argvwithoutmetadata;
|
||||
|
||||
argv[0] = "avahi-publish-service";
|
||||
int pid = fork_execvp(argv[0], argv);
|
||||
if (pid >= 0)
|
||||
{
|
||||
mdns_pid = pid;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
warn("Calling %s failed !", argv[0]);
|
||||
char *argvwithoutmetadata[] = {NULL, apname, "_raop._tcp", mdns_port,
|
||||
MDNS_RECORD_WITHOUT_METADATA, NULL};
|
||||
#ifdef CONFIG_METADATA
|
||||
char *argvwithmetadata[] = {NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITH_METADATA,
|
||||
NULL};
|
||||
#endif
|
||||
char **argv;
|
||||
|
||||
argv[0] = "mDNSPublish";
|
||||
pid = fork_execvp(argv[0], argv);
|
||||
if (pid >= 0)
|
||||
{
|
||||
mdns_pid = pid;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
warn("Calling %s failed !", argv[0]);
|
||||
|
||||
// If we reach here, both execvp calls failed.
|
||||
return -1;
|
||||
#ifdef CONFIG_METADATA
|
||||
if (config.metadata_enabled)
|
||||
argv = argvwithmetadata;
|
||||
else
|
||||
#endif
|
||||
argv = argvwithoutmetadata;
|
||||
|
||||
argv[0] = "avahi-publish-service";
|
||||
int pid = fork_execvp(argv[0], argv);
|
||||
if (pid >= 0) {
|
||||
mdns_pid = pid;
|
||||
return 0;
|
||||
} else
|
||||
warn("Calling %s failed !", argv[0]);
|
||||
|
||||
argv[0] = "mDNSPublish";
|
||||
pid = fork_execvp(argv[0], argv);
|
||||
if (pid >= 0) {
|
||||
mdns_pid = pid;
|
||||
return 0;
|
||||
} else
|
||||
warn("Calling %s failed !", argv[0]);
|
||||
|
||||
// If we reach here, both execvp calls failed.
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int mdns_external_dns_sd_register(char *apname, int port) {
|
||||
char mdns_port[6];
|
||||
sprintf(mdns_port, "%d", config.port);
|
||||
char mdns_port[6];
|
||||
sprintf(mdns_port, "%d", config.port);
|
||||
|
||||
char *argvwithoutmetadata[] = {
|
||||
NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL
|
||||
};
|
||||
char *argvwithoutmetadata[] = {NULL, apname, "_raop._tcp", mdns_port,
|
||||
MDNS_RECORD_WITHOUT_METADATA, NULL};
|
||||
|
||||
char **argv=argvwithoutmetadata;
|
||||
#ifdef CONFIG_METADATA
|
||||
char *argvwithmetadata[] = {NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD_WITH_METADATA,
|
||||
NULL};
|
||||
#endif
|
||||
|
||||
int pid = fork_execvp(argv[0], argv);
|
||||
if (pid >= 0)
|
||||
{
|
||||
mdns_pid = pid;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
warn("Calling %s failed !", argv[0]);
|
||||
char **argv;
|
||||
#ifdef CONFIG_METADATA
|
||||
if (config.metadata_enabled)
|
||||
argv = argvwithmetadata;
|
||||
else
|
||||
#endif
|
||||
|
||||
return -1;
|
||||
argv = argvwithoutmetadata;
|
||||
|
||||
int pid = fork_execvp(argv[0], argv);
|
||||
if (pid >= 0) {
|
||||
mdns_pid = pid;
|
||||
return 0;
|
||||
} else
|
||||
warn("Calling %s failed !", argv[0]);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void kill_mdns_child(void) {
|
||||
if (mdns_pid)
|
||||
kill(mdns_pid, SIGTERM);
|
||||
mdns_pid = 0;
|
||||
if (mdns_pid)
|
||||
kill(mdns_pid, SIGTERM);
|
||||
mdns_pid = 0;
|
||||
}
|
||||
|
||||
mdns_backend mdns_external_avahi = {
|
||||
.name = "external-avahi",
|
||||
.mdns_register = mdns_external_avahi_register,
|
||||
.mdns_unregister = kill_mdns_child
|
||||
};
|
||||
|
||||
mdns_backend mdns_external_dns_sd = {
|
||||
.name = "external-dns-sd",
|
||||
.mdns_register = mdns_external_dns_sd_register,
|
||||
.mdns_unregister = kill_mdns_child
|
||||
};
|
||||
mdns_backend mdns_external_avahi = {.name = "external-avahi",
|
||||
.mdns_register = mdns_external_avahi_register,
|
||||
.mdns_unregister = kill_mdns_child};
|
||||
|
||||
mdns_backend mdns_external_dns_sd = {.name = "external-dns-sd",
|
||||
.mdns_register = mdns_external_dns_sd_register,
|
||||
.mdns_unregister = kill_mdns_child};
|
||||
|
||||
+89
-98
@@ -27,7 +27,7 @@
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
@@ -39,117 +39,108 @@
|
||||
static struct mdnsd *svr = NULL;
|
||||
|
||||
static int mdns_tinysvcmdns_register(char *apname, int port) {
|
||||
struct ifaddrs *ifalist;
|
||||
struct ifaddrs *ifa;
|
||||
struct ifaddrs *ifalist;
|
||||
struct ifaddrs *ifa;
|
||||
|
||||
svr = mdnsd_start();
|
||||
if (svr == NULL) {
|
||||
warn("tinysvcmdns: mdnsd_start() failed");
|
||||
return -1;
|
||||
svr = mdnsd_start();
|
||||
if (svr == NULL) {
|
||||
warn("tinysvcmdns: mdnsd_start() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Thanks to Paul Lietar for this
|
||||
// room for name + .local + NULL
|
||||
char hostname[100 + 6];
|
||||
gethostname(hostname, 99);
|
||||
// according to POSIX, this may be truncated without a final NULL !
|
||||
hostname[99] = 0;
|
||||
|
||||
// will not work if the hostname doesn't end in .local
|
||||
char *hostend = hostname + strlen(hostname);
|
||||
if ((strlen(hostname) < strlen(".local")) || (strcmp(hostend - 6, ".local") != 0)) {
|
||||
strcat(hostname, ".local");
|
||||
}
|
||||
|
||||
if (getifaddrs(&ifalist) < 0) {
|
||||
warn("tinysvcmdns: getifaddrs() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ifa = ifalist;
|
||||
|
||||
// Look for an ipv4/ipv6 non-loopback interface to use as the main one.
|
||||
for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) {
|
||||
uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
|
||||
|
||||
mdnsd_set_hostname(svr, hostname, main_ip); // TTL should be 120 seconds
|
||||
break;
|
||||
} else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr &&
|
||||
ifa->ifa_addr->sa_family == AF_INET6) {
|
||||
struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
|
||||
|
||||
mdnsd_set_hostname_v6(svr, hostname, addr); // TTL should be 120 seconds
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Thanks to Paul Lietar for this
|
||||
// room for name + .local + NULL
|
||||
char hostname[100 + 6];
|
||||
gethostname(hostname, 99);
|
||||
// according to POSIX, this may be truncated without a final NULL !
|
||||
hostname[99] = 0;
|
||||
if (ifa == NULL) {
|
||||
warn("tinysvcmdns: no non-loopback ipv4 or ipv6 interface found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// will not work if the hostname doesn't end in .local
|
||||
char *hostend = hostname + strlen(hostname);
|
||||
if ((strlen(hostname) < strlen(".local")) || (strcmp(hostend - 6, ".local")!=0)) {
|
||||
strcat(hostname, ".local");
|
||||
}
|
||||
|
||||
if (getifaddrs(&ifalist) < 0)
|
||||
{
|
||||
warn("tinysvcmdns: getifaddrs() failed");
|
||||
return -1;
|
||||
// Skip the first one, it was already added by set_hostname
|
||||
for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces
|
||||
continue;
|
||||
|
||||
switch (ifa->ifa_addr->sa_family) {
|
||||
case AF_INET: { // ipv4
|
||||
uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
|
||||
struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds
|
||||
mdnsd_add_rr(svr, a_e);
|
||||
} break;
|
||||
case AF_INET6: { // ipv6
|
||||
struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
|
||||
struct rr_entry *aaaa_e =
|
||||
rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds
|
||||
mdnsd_add_rr(svr, aaaa_e);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
ifa = ifalist;
|
||||
freeifaddrs(ifa);
|
||||
|
||||
// Look for an ipv4/ipv6 non-loopback interface to use as the main one.
|
||||
for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next)
|
||||
{
|
||||
if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr &&
|
||||
ifa->ifa_addr->sa_family == AF_INET)
|
||||
{
|
||||
uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
|
||||
char *txtwithoutmetadata[] = {MDNS_RECORD_WITHOUT_METADATA, NULL};
|
||||
#ifdef CONFIG_METADATA
|
||||
char *txtwithmetadata[] = {MDNS_RECORD_WITH_METADATA, NULL};
|
||||
#endif
|
||||
char **txt;
|
||||
|
||||
mdnsd_set_hostname(svr, hostname, main_ip); // TTL should be 120 seconds
|
||||
break;
|
||||
}
|
||||
else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr &&
|
||||
ifa->ifa_addr->sa_family == AF_INET6)
|
||||
{
|
||||
struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
|
||||
#ifdef CONFIG_METADATA
|
||||
if (config.meta_dir)
|
||||
txt = txtwithmetadata;
|
||||
else
|
||||
#endif
|
||||
|
||||
mdnsd_set_hostname_v6(svr, hostname, addr); // TTL should be 120 seconds
|
||||
break;
|
||||
}
|
||||
}
|
||||
txt = txtwithoutmetadata;
|
||||
|
||||
if (ifa == NULL)
|
||||
{
|
||||
warn("tinysvcmdns: no non-loopback ipv4 or ipv6 interface found");
|
||||
return -1;
|
||||
}
|
||||
struct mdns_service *svc =
|
||||
mdnsd_register_svc(svr, apname, "_raop._tcp.local", port, NULL,
|
||||
(const char **)txt); // TTL should be 75 minutes, i.e. 4500 seconds
|
||||
|
||||
mdns_service_destroy(svc);
|
||||
|
||||
// Skip the first one, it was already added by set_hostname
|
||||
for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next)
|
||||
{
|
||||
if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces
|
||||
continue;
|
||||
|
||||
switch (ifa->ifa_addr->sa_family)
|
||||
{
|
||||
case AF_INET: { // ipv4
|
||||
uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
|
||||
struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip); // TTL should be 120 seconds
|
||||
mdnsd_add_rr(svr, a_e);
|
||||
}
|
||||
break;
|
||||
case AF_INET6: { // ipv6
|
||||
struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
|
||||
struct rr_entry *aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); // TTL should be 120 seconds
|
||||
mdnsd_add_rr(svr, aaaa_e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifa);
|
||||
|
||||
char *txtwithoutmetadata[] = { MDNS_RECORD_WITHOUT_METADATA, NULL };
|
||||
char **txt = txtwithoutmetadata;
|
||||
|
||||
|
||||
|
||||
struct mdns_service *svc = mdnsd_register_svc(svr,
|
||||
apname,
|
||||
"_raop._tcp.local",
|
||||
port,
|
||||
NULL,
|
||||
(const char **)txt); // TTL should be 75 minutes, i.e. 4500 seconds
|
||||
|
||||
mdns_service_destroy(svc);
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mdns_tinysvcmdns_unregister(void) {
|
||||
if (svr)
|
||||
{
|
||||
mdnsd_stop(svr);
|
||||
svr = NULL;
|
||||
}
|
||||
if (svr) {
|
||||
mdnsd_stop(svr);
|
||||
svr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
mdns_backend mdns_tinysvcmdns = {
|
||||
.name = "tinysvcmdns",
|
||||
.mdns_register = mdns_tinysvcmdns_register,
|
||||
.mdns_unregister = mdns_tinysvcmdns_unregister
|
||||
};
|
||||
|
||||
mdns_backend mdns_tinysvcmdns = {.name = "tinysvcmdns",
|
||||
.mdns_register = mdns_tinysvcmdns_register,
|
||||
.mdns_unregister = mdns_tinysvcmdns_unregister};
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
#include "audio.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t aesiv[16], aeskey[16];
|
||||
int32_t fmtp[12];
|
||||
int encrypted;
|
||||
uint8_t aesiv[16], aeskey[16];
|
||||
int32_t fmtp[12];
|
||||
} stream_cfg;
|
||||
|
||||
typedef uint16_t seq_t;
|
||||
@@ -19,6 +20,6 @@ void player_stop(void);
|
||||
void player_volume(double f);
|
||||
void player_flush(uint32_t timestamp);
|
||||
|
||||
void player_put_packet(seq_t seqno,uint32_t timestamp, uint8_t *data, int len);
|
||||
void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len);
|
||||
|
||||
#endif //_PLAYER_H
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
#include "player.h"
|
||||
|
||||
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_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);
|
||||
void rtp_request_resend(seq_t first, uint32_t count);
|
||||
void rtp_request_client_pause(void); // ask the client to pause
|
||||
|
||||
void get_reference_timestamp_stuff(uint32_t *timestamp,uint64_t *timestamp_time);
|
||||
void clear_reference_timestamp(void);
|
||||
void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time);
|
||||
void clear_reference_timestamp(void);
|
||||
|
||||
uint64_t static local_to_remote_time_jitters;
|
||||
uint64_t static local_to_remote_time_jitters_count;
|
||||
|
||||
@@ -5,12 +5,17 @@ void rtsp_listen_loop(void);
|
||||
void rtsp_shutdown_stream(void);
|
||||
void rtsp_request_shutdown_stream(void);
|
||||
|
||||
|
||||
// initialise the metadata stuff
|
||||
|
||||
void metadata_init(void);
|
||||
|
||||
// sends metadata out to the metadata pipe, if enabled.
|
||||
// It is sent with the type 'ssnc' the given code, data and length
|
||||
// The handler at the other end must kknow what to do with the data
|
||||
// e.g. it it's malloced, to free it, etc.
|
||||
// nothing is done automatically
|
||||
|
||||
int send_ssnc_metadata(uint32_t code,char *data,uint32_t length, int block);
|
||||
int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block);
|
||||
|
||||
#endif // _RTSP_H
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// Sample Configuration File for Shairport Sync
|
||||
// Commented out settings are generally the defaults, except where noted.
|
||||
|
||||
// General Settings
|
||||
general =
|
||||
{
|
||||
// name = "Shairport Sync Player"; // This is the name the service will advertise to iTunes. The default is "Shairport Sync on <hostname>"
|
||||
// password = "secret"; // comment out this line if you want to have no password
|
||||
// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor.
|
||||
// output_backend = "alsa"; // Run "shairport-sync -h" to get a list of all output_backends, e.g. "alsa", "pipe", "stdout". The default is the first one.
|
||||
// mdns_backend = "avahi"; // Run "shairport-sync -h" to get a list of all mdns_backends. The default is the first one.
|
||||
// port = 5000; // Listen for service requests on this port
|
||||
// udp_port_base = 6001; // start allocating UDP ports from this port number when needed
|
||||
// udp_port_range = 100; // look for free ports in this number of places, starting at the UDP port base (only three are needed).
|
||||
// statistics = "no"; // set to "yes" to print statistics in the log
|
||||
// drift = 88; // allow this number of frames of drift away from exact synchronisation before attempting to correct it
|
||||
// resync_threshold = 2205; // a synchronisation error greater than this will cause resynchronisation; 0 disables it
|
||||
// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose.
|
||||
// ignore_volume_control = "no"; // set this to "yes" if you want the volume to be at 100% no matter what the source's volume control is set to.
|
||||
};
|
||||
|
||||
// Latencies for different sources. These have been estimated from listening tests.
|
||||
// It's probably better to compensate for a delay in the output device using the alsa "audio_backend_latency_offset" setting -- see below.
|
||||
latencies =
|
||||
{
|
||||
// default = 88200; // used for unrecognised sources and for iTunes up to and including iTunes 9.X.
|
||||
// itunes = 99400; // used for iTunes 10 or later
|
||||
// airplay = 88200;
|
||||
// forkedDaapd = 99400;
|
||||
};
|
||||
|
||||
// How to deal with metadata, including artwork
|
||||
metadata =
|
||||
{
|
||||
// enabled = "no"; // et to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
|
||||
// include_cover_art = "no"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
|
||||
// pipe_name = "/tmp/shairport-sync-metadata";
|
||||
};
|
||||
|
||||
// Advanced parameters for controlling how a Shairport Sync runs
|
||||
sessioncontrol =
|
||||
{
|
||||
// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. It it's a script, include the #!... stuff on the first line
|
||||
// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. It it's a script, include the #!... stuff on the first line
|
||||
// wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing
|
||||
// allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source
|
||||
// session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again.
|
||||
};
|
||||
|
||||
//
|
||||
// Back End Settings
|
||||
//
|
||||
|
||||
// These are parameters for the "alsa" audio back end, the only back end that supports synchronised audio.
|
||||
alsa =
|
||||
{
|
||||
// output_device = "default"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc.
|
||||
// mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software.
|
||||
// mixer_device = "default"; // the mixer_device default is whatever the output_device is. Normally you wouldn't have to use this.
|
||||
// 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 = 6615; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying.
|
||||
};
|
||||
|
||||
// These are parameters for the "pipe" audio back end, a back end that directs raw CD-style audio output to a pipe. No interpolation is done.
|
||||
pipe =
|
||||
{
|
||||
// name = "/path/to/pipe"; // there is no default pipe name for the output
|
||||
// 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.
|
||||
};
|
||||
|
||||
// These are parameters for the "stdout" audio back end, a back end that directs raw CD-style audio output to stdout. No interpolation is done.
|
||||
stdout =
|
||||
{
|
||||
// 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.
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=ShairportSync AirTunes receiver
|
||||
After=sound.target
|
||||
Requires=avahi-daemon.service
|
||||
After=avahi-daemon.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/shairport-sync
|
||||
User=shairport-sync
|
||||
Group=shairport-sync
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,65 @@
|
||||
Name: shairport-sync
|
||||
Version: 2.4
|
||||
Release: 1%{?dist}
|
||||
Summary: AirTunes emulator. Shairport Sync adds multi-room capability with Audio Synchronisation
|
||||
|
||||
Group: Applications/Multimedia
|
||||
License: GPL
|
||||
URL: https://github.com/mikebrady/shairport-sync
|
||||
Source0: https://github.com/mikebrady/%{name}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz
|
||||
|
||||
BuildRequires: autoconf
|
||||
BuildRequires: automake
|
||||
BuildRequires: libconfig-devel
|
||||
BuildRequires: popt-devel
|
||||
BuildRequires: openssl-devel
|
||||
BuildRequires: libdaemon-devel
|
||||
BuildRequires: avahi-devel
|
||||
BuildRequires: alsa-lib-devel
|
||||
BuildRequires: systemd-units
|
||||
BuildRequires: soxr-devel
|
||||
Requires: popt
|
||||
Requires: openssl
|
||||
Requires: avahi
|
||||
Requires: libdaemon
|
||||
Requires: alsa-lib
|
||||
Requires: soxr
|
||||
|
||||
%description
|
||||
Shairport Sync emulates an AirPort Express for the purpose of streaming audio from iTunes, iPods, iPhones, iPads and AppleTVs. Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. Thus, for example, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.)
|
||||
|
||||
Shairport Sync does not support AirPlay video or photo streaming.
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
|
||||
%build
|
||||
autoreconf -i -f
|
||||
%configure --with-avahi --with-alsa --with-ssl=openssl --with-soxr --with-systemd
|
||||
make %{?_smp_mflags}
|
||||
|
||||
|
||||
%install
|
||||
make install DESTDIR=%{buildroot}
|
||||
rm %{buildroot}/etc/shairport-sync.conf.sample
|
||||
|
||||
%pre
|
||||
getent group %{name} &>/dev/null || groupadd --system %{name} >/dev/null
|
||||
getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
|
||||
-d %{_localstatedir}/%{name} -m -g %{name} -s /sbin/nologin \
|
||||
-G audio %{name} >/dev/null
|
||||
|
||||
%files
|
||||
%config /etc/shairport-sync.conf
|
||||
/usr/bin/shairport-sync
|
||||
/usr/share/man/man7/shairport-sync.7.gz
|
||||
%{_unitdir}/%{name}.service
|
||||
%doc AUTHORS LICENSES README.md
|
||||
|
||||
%changelog
|
||||
* Thu Aug 27 2015 Mike Brady <mikebrady@eircom.net> 2.4
|
||||
- Prepare for stable release 2.4
|
||||
* Wed Aug 26 2015 Mike Brady <mikebrady@eircom.net> 2.3.13.1-1
|
||||
- Harmonise release numbers
|
||||
* Fri Jul 24 2015 Bill Peck <bill@pecknet.com> 2.3.7-1
|
||||
- Initial spec file
|
||||
+709
-365
File diff suppressed because it is too large
Load Diff
+1094
-1117
File diff suppressed because it is too large
Load Diff
+70
-72
@@ -45,120 +45,118 @@
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#define MALLOC_ZERO_STRUCT(x, type) \
|
||||
x = malloc(sizeof(struct type)); \
|
||||
memset(x, 0, sizeof(struct type));
|
||||
|
||||
#define DECL_MALLOC_ZERO_STRUCT(x, type) \
|
||||
struct type * MALLOC_ZERO_STRUCT(x, type)
|
||||
|
||||
#define MALLOC_ZERO_STRUCT(x, type) \
|
||||
x = malloc(sizeof(struct type)); \
|
||||
memset(x, 0, sizeof(struct type));
|
||||
|
||||
#define DECL_MALLOC_ZERO_STRUCT(x, type) struct type *MALLOC_ZERO_STRUCT(x, type)
|
||||
|
||||
struct rr_data_srv {
|
||||
uint16_t priority;
|
||||
uint16_t weight;
|
||||
uint16_t port;
|
||||
uint8_t *target; // host
|
||||
uint16_t priority;
|
||||
uint16_t weight;
|
||||
uint16_t port;
|
||||
uint8_t *target; // host
|
||||
};
|
||||
|
||||
struct rr_data_txt {
|
||||
struct rr_data_txt *next;
|
||||
uint8_t *txt;
|
||||
struct rr_data_txt *next;
|
||||
uint8_t *txt;
|
||||
};
|
||||
|
||||
struct rr_data_nsec {
|
||||
//uint8_t *name; // same as record
|
||||
// uint8_t *name; // same as record
|
||||
|
||||
// NSEC occupies the 47th bit, 5 bytes
|
||||
//uint8_t bitmap_len; // = 5
|
||||
uint8_t bitmap[5]; // network order: first byte contains LSB
|
||||
// NSEC occupies the 47th bit, 5 bytes
|
||||
// uint8_t bitmap_len; // = 5
|
||||
uint8_t bitmap[5]; // network order: first byte contains LSB
|
||||
};
|
||||
|
||||
struct rr_data_ptr {
|
||||
uint8_t *name; // NULL if entry is to be used
|
||||
struct rr_entry *entry;
|
||||
uint8_t *name; // NULL if entry is to be used
|
||||
struct rr_entry *entry;
|
||||
};
|
||||
|
||||
struct rr_data_a {
|
||||
uint32_t addr;
|
||||
uint32_t addr;
|
||||
};
|
||||
|
||||
struct rr_data_aaaa {
|
||||
struct in6_addr *addr;
|
||||
struct in6_addr *addr;
|
||||
};
|
||||
|
||||
struct rr_entry {
|
||||
uint8_t *name;
|
||||
uint8_t *name;
|
||||
|
||||
enum rr_type {
|
||||
RR_A = 0x01,
|
||||
RR_PTR = 0x0C,
|
||||
RR_TXT = 0x10,
|
||||
RR_AAAA = 0x1C,
|
||||
RR_SRV = 0x21,
|
||||
RR_NSEC = 0x2F,
|
||||
RR_ANY = 0xFF,
|
||||
} type;
|
||||
enum rr_type {
|
||||
RR_A = 0x01,
|
||||
RR_PTR = 0x0C,
|
||||
RR_TXT = 0x10,
|
||||
RR_AAAA = 0x1C,
|
||||
RR_SRV = 0x21,
|
||||
RR_NSEC = 0x2F,
|
||||
RR_ANY = 0xFF,
|
||||
} type;
|
||||
|
||||
uint32_t ttl;
|
||||
uint32_t ttl;
|
||||
|
||||
// for use in Questions only
|
||||
char unicast_query;
|
||||
// for use in Questions only
|
||||
char unicast_query;
|
||||
|
||||
// for use in Answers only
|
||||
char cache_flush;
|
||||
// for use in Answers only
|
||||
char cache_flush;
|
||||
|
||||
uint16_t rr_class;
|
||||
uint16_t rr_class;
|
||||
|
||||
// RR data
|
||||
union {
|
||||
struct rr_data_nsec NSEC;
|
||||
struct rr_data_srv SRV;
|
||||
struct rr_data_txt TXT;
|
||||
struct rr_data_ptr PTR;
|
||||
struct rr_data_a A;
|
||||
struct rr_data_aaaa AAAA;
|
||||
} data;
|
||||
// RR data
|
||||
union {
|
||||
struct rr_data_nsec NSEC;
|
||||
struct rr_data_srv SRV;
|
||||
struct rr_data_txt TXT;
|
||||
struct rr_data_ptr PTR;
|
||||
struct rr_data_a A;
|
||||
struct rr_data_aaaa AAAA;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct rr_list {
|
||||
struct rr_entry *e;
|
||||
struct rr_list *next;
|
||||
struct rr_entry *e;
|
||||
struct rr_list *next;
|
||||
};
|
||||
|
||||
struct rr_group {
|
||||
uint8_t *name;
|
||||
uint8_t *name;
|
||||
|
||||
struct rr_list *rr;
|
||||
struct rr_list *rr;
|
||||
|
||||
struct rr_group *next;
|
||||
struct rr_group *next;
|
||||
};
|
||||
|
||||
#define MDNS_FLAG_RESP (1 << 15) // Query=0 / Response=1
|
||||
#define MDNS_FLAG_AA (1 << 10) // Authoritative
|
||||
#define MDNS_FLAG_TC (1 << 9) // TrunCation
|
||||
#define MDNS_FLAG_RD (1 << 8) // Recursion Desired
|
||||
#define MDNS_FLAG_RA (1 << 7) // Recursion Available
|
||||
#define MDNS_FLAG_Z (1 << 6) // Reserved (zero)
|
||||
#define MDNS_FLAG_RESP (1 << 15) // Query=0 / Response=1
|
||||
#define MDNS_FLAG_AA (1 << 10) // Authoritative
|
||||
#define MDNS_FLAG_TC (1 << 9) // TrunCation
|
||||
#define MDNS_FLAG_RD (1 << 8) // Recursion Desired
|
||||
#define MDNS_FLAG_RA (1 << 7) // Recursion Available
|
||||
#define MDNS_FLAG_Z (1 << 6) // Reserved (zero)
|
||||
|
||||
#define MDNS_FLAG_GET_RCODE(x) (x & 0x0F)
|
||||
#define MDNS_FLAG_GET_RCODE(x) (x & 0x0F)
|
||||
#define MDNS_FLAG_GET_OPCODE(x) ((x >> 11) & 0x0F)
|
||||
|
||||
// gets the PTR target name, either from "name" member or "entry" member
|
||||
#define MDNS_RR_GET_PTR_NAME(rr) (rr->data.PTR.name != NULL ? rr->data.PTR.name : rr->data.PTR.entry->name)
|
||||
#define MDNS_RR_GET_PTR_NAME(rr) \
|
||||
(rr->data.PTR.name != NULL ? rr->data.PTR.name : rr->data.PTR.entry->name)
|
||||
|
||||
struct mdns_pkt {
|
||||
uint16_t id; // transaction ID
|
||||
uint16_t flags;
|
||||
uint16_t num_qn;
|
||||
uint16_t num_ans_rr;
|
||||
uint16_t num_auth_rr;
|
||||
uint16_t num_add_rr;
|
||||
uint16_t id; // transaction ID
|
||||
uint16_t flags;
|
||||
uint16_t num_qn;
|
||||
uint16_t num_ans_rr;
|
||||
uint16_t num_auth_rr;
|
||||
uint16_t num_add_rr;
|
||||
|
||||
struct rr_list *rr_qn; // questions
|
||||
struct rr_list *rr_ans; // answer RRs
|
||||
struct rr_list *rr_auth; // authority RRs
|
||||
struct rr_list *rr_add; // additional RRs
|
||||
struct rr_list *rr_qn; // questions
|
||||
struct rr_list *rr_ans; // answer RRs
|
||||
struct rr_list *rr_auth; // authority RRs
|
||||
struct rr_list *rr_add; // additional RRs
|
||||
};
|
||||
|
||||
struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len);
|
||||
@@ -197,7 +195,7 @@ uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2);
|
||||
|
||||
// compares 2 names
|
||||
static inline int cmp_nlabel(const uint8_t *L1, const uint8_t *L2) {
|
||||
return strcmp((char *) L1, (char *) L2);
|
||||
return strcmp((char *)L1, (char *)L2);
|
||||
}
|
||||
|
||||
//******************************************************//
|
||||
@@ -225,10 +223,10 @@ void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr);
|
||||
|
||||
// registers a service with the MDNS responder instance
|
||||
struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name,
|
||||
const char *type, uint16_t port, const char *hostname, const char *txt[]);
|
||||
const char *type, uint16_t port, const char *hostname,
|
||||
const char *txt[]);
|
||||
|
||||
// destroys the mdns_service struct returned by mdnsd_register_svc()
|
||||
void mdns_service_destroy(struct mdns_service *srv);
|
||||
|
||||
|
||||
#endif // _TINYSVCMDNS_H
|
||||
|
||||
Reference in New Issue
Block a user