Move to version 3.0

This commit is contained in:
Mike Brady
2017-02-24 21:39:02 +00:00
39 changed files with 4679 additions and 3074 deletions
+1 -17
View File
@@ -7,26 +7,10 @@ env:
- CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemv"
- CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-avahi --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-avahi --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-stdio --with-avahi --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-stdio --with-avahi --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-pipe --with-avahi --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-pipe --with-avahi --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-ao --with-avahi --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-ao --with-avahi --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemv"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemv"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=polarssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-alsa --with-tinysvcmdns --with-ssl=openssl --with-soxr --with-metadata --with-systemd"
- CFG="--with-stdio --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-stdio --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-pipe --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-pipe --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd"
- CFG="--with-ao --with-tinysvcmdns --with-ssl=openssl --with-metadata --with-systemd"
- CFG="--with-ao --with-tinysvcmdns --with-ssl=polarssl --with-metadata --with-systemd"
script:
- autoreconf -fi
@@ -35,7 +19,7 @@ script:
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y libdaemon-dev avahi-daemon libavahi-client-dev
- sudo apt-get install -y libdaemon-dev avahi-daemon libavahi-client-dev
addons:
apt:
+3
View File
@@ -11,6 +11,9 @@ tinysvcmdns.c, tinysvcmdns.h:
Shairport:
Copyright (c) 2011-2013 James Laird <jhl@mafipulation.org>
Shairport Sync:
Copyright (c) 2014–2017 Mike Brady <mikebrady@eircom.net>
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
+10 -1
View File
@@ -1,9 +1,14 @@
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
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 -DSYSCONFDIR=\"$(sysconfdir)\"
AM_CPPFLAGS = -Wno-multichar -DSYSCONFDIR=\"$(sysconfdir)\"
if USE_APPLE_ALAC
shairport_sync_SOURCES += apple_alac.cpp
endif
if USE_CUSTOMPIDDIR
AM_CFLAGS+= \
@@ -42,6 +47,10 @@ if USE_AO
shairport_sync_SOURCES += audio_ao.c
endif
if USE_SOUNDIO
shairport_sync_SOURCES += audio_soundio.c
endif
if USE_PULSE
shairport_sync_SOURCES += audio_pulse.c
endif
+49 -29
View File
@@ -1,20 +1,21 @@
Shairport Sync
=============
Shairport Sync is an AirPlay audio player — it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player and ForkedDaapd, among others.
**Welcome to Version 3 of Shairport Sync. This is a big update from version 2. Please see the Release Notes for more information.**
Shairport Sync is an AirPlay audio player – it plays audio streamed from iTunes, iOS devices and other AirPlay sources such as Quicktime Player and ForkedDaapd, among others.
Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible without difficulty. (Hence the name Shairport Sync, BTW.)
Shairport Sync does not support AirPlay video or photo streaming.
This is the stable "master" branch. Changes and updates are incorporated into this branch relatively slowly. To access the development version, where all the latest changes are made first, please switch to the "development" branch.
**Please note that, between 2.8.4 and 2.8.5, there is a change in the standard ./configure arguments which you should not ignore!**
**Please note that, between 2.8.4 and 2.8.5, there is a change in the standard ./configure arguments which you should not ignore!** If you are updating Shairport Sync from a version that is 2.8.4 or older, please read the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page very carefully.
More Information
----------
Shairport Sync works by using timing information and timestamps present in data coming from the audio source (e.g. an iPhone) to play audio at exactly the right time. It does this by monitoring and controlling the *latency* — the time-gap between when a sound frame is supposed to be played, as specified by its `timestamp`, and the time when it is actually played by the audio output device, usually a Digital to Audio Converter (DAC).
The latency to be used is specified by the source when it negotiates with Shairport Sync. Most sources set a latency of 88,200 frames — exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of 99,577 frames.
The latency to be used is specified by the source when it negotiates with Shairport Sync. Most sources set a latency of exactly two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds.
Timestamps are measured relative to the source computer's clock – the `source clock`, but timing must be done relative to the clock of the computer running Shairport Sync – the `local clock`. This is done by synchronising the two clocks, usually to within a fraction of a millisecond, using a variant of NTP synchronisation protocols.
@@ -22,7 +23,7 @@ To maintain the exact latency required, if an output device is running slow rela
Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/master/README.md#contributors-to-version-1x for a list of the contributors to Shairport 1.x and Shairport 0.x. From a "heritage" point of view, Shairport Sync is a fork of Shairport 1.0.
Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100 samples per second interleaved PCM stereo (you'll get a message in the logfile if there's a problem).
Shairport Sync is mainly designed for `alsa` and thus for Linux, although since `alsa` has been ported to FreeBSD, Shairport Sync runs in FreeBSD too. It must have direct access to the output device, which must be a real sound card capable of working with 44,100, 88,200 or 176,400 samples per second, interleaved PCM stereo of 8, 16, 24 or 32 bits. The default is 44,100 samples per second / 16 bits (you'll get a message in the logfile if there's a problem).
For more about the motivation behind Shairport Sync, please see the wiki at https://github.com/mikebrady/shairport-sync/wiki.
@@ -30,6 +31,8 @@ What else?
--------------
* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range.
* Hardware Mute — Shairport Sync will mute properly if the hardware supports it.
* Support for the Apple ALAC decoder.
* Bit depths of 8, 16, 24 and 32 bits, rather than the standard 16 bits.
* Fast Response — With hardware volume control, response is instantaneous; otherwise the response time is 0.15 seconds.
* Non-Interruptible — Shairport Sync sends back a "busy" signal if it's already playing audio from another source, so other sources can't disrupt an existing Shairport Sync session. (If a source disappears without warning, the session automatically terminates after two minutes and the device becomes available again.)
* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
@@ -40,7 +43,7 @@ Status
------
Shairport Sync works on a wide variety of Linux devices. It works on standard Ubuntu laptops, on the Raspberry Pi with Raspbian Wheezy and Jessie, Arch Linux and OpenWrt, and it runs on a Linksys NSLU2 and a TP-Link 710N using OpenWrt. It works with built-in audio and with a variety of USB-connected audio amplifiers and DACs, including a cheapo USB "3D Sound" dongle, a first generation iMic and a Topping TP30 amplifier with a USB DAC input. It will not work properly — if at all — with a PulseAudio (pseudo-)output device. Using a port of the `alsa` system, Shairport Sync runs rather well on FreeBSD.
Shairport Sync runs well on the Raspberry Pi. It can drive the built-in sound card, though the audio out of the card is of poor quality (see the note below on configuring the Raspberry Pi to make best use of it). USB-connected sound cards work well, though [very] old versions of Raspbian appear to suffer from a problem — see http://www.raspberrypi.org/forums/viewtopic.php?t=23544, so it is wise to update. Shairport Sync works well with the IQAudIO Pi-DAC — see http://www.iqaudio.com.
Shairport Sync runs well on the Raspberry Pi on USB and I2S cards. It can drive the built-in sound card, though the audio out of the card is of poor quality – see the note below on configuring the Raspberry Pi to make best use of it.
At the time of writing, OpenWrt trunk does not support USB audio well on the Raspberry Pi.
@@ -88,11 +91,12 @@ Please see the guide at [CYGWIN.md](https://github.com/mikebrady/shairport-sync/
Sincere thanks to all package contributors!
**General Build Instructions**
The following procedures will install the shairport-sync application into your system. Before continuing, you should check to see if shairport-sync is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions.
The following libraries are required:
* OpenSSL or PolarSSL
* OpenSSL or mbed TLS (PolarSSL is supported but deprecated)
* Avahi
* ALSA
* libdaemon
@@ -104,17 +108,20 @@ The following libraries are required:
Optional:
* libsoxr
* libalac (This is a library containing the Apple ALAC decoder.)
Many Linux distributions have Avahi and OpenSSL already in place, so normally it probably makes sense to choose those options rather than tinysvcmdns or 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 mbed TLS. The `libsoxr` library is available in recent Linux distributions, but it requires lots of processor power — chances are an embedded processor won't be able to keep up.
Debian, Ubuntu and Raspbian users can get the basics with:
- `apt-get install build-essential git` – these may already be installed.
- `apt-get install autoconf automake libtool libdaemon-dev libasound2-dev libpopt-dev libconfig-dev`
- `apt-get install avahi-daemon libavahi-client-dev` if you want to use Avahi (recommended).
- `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use PolarSSL otherwise.
- `apt-get install libpolarssl-dev` if you want to use PolarSSL, or use OpenSSL/libcrypto otherwise.
- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspioan Jessie; if not, instructions for how to build it from source for Raspian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/master/LIBSOXR.md).
- `apt-get install libssl-dev` if you want to use OpenSSL and libcrypto, or use mbed TLS otherwise.
- `apt-get install libmbedtls-dev` if you want to use mbed TLS, or use OpenSSL/libcrypto otherwise. (You can still use PolarSSL with `apt-get install libpolarssl-dev` if you want to use PolarSSL, but it is deprecated as it's not longer being supported. It is suggested you use mbed TLS instead.)
- `apt-get install libsoxr-dev` if you want support for libsoxr-based resampling. This library is in many recent distributions, including Jessie and Raspbian Jessie; if not, instructions for how to build it from source for Rasbpian/Debian Wheezy are available at [LIBSOXR.md](https://github.com/mikebrady/shairport-sync/blob/development/LIBSOXR.md).
If you wish to include the Apple ALAC decoder, you need install it first – please refer to the [ALAC](https://github.com/mikebrady/alac) repository for more information.
**Download Shairport Sync:**
@@ -133,7 +140,7 @@ $ autoreconf -i -f
- `--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-ssl=openssl`, `--with-ssl=mbedtls` or `--with-ssl=polarssl` (deprecated) for encryption and related utilities using either OpenSSL, mbed TLS or PolarSSL.
- `--with-soxr` for libsoxr-based resampling.
- `--with-piddir` for specifying where the PID file should be stored. This directory is normally chosen automatically. The directory must be writable. If you use this option, you may have to edit the init script to search for the PID file in your new location.
- `--with-metadata` to add support for Shairport Sync to pipe metadata to a compatible application of your choice. See https://github.com/mikebrady/shairport-sync-metadata-reader for a sample metadata reader.
@@ -141,6 +148,7 @@ $ autoreconf -i -f
- `--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.
- `--with-apple-alac` to include the Apple ALAC Decoder.
**Determine if it's a `systemd` or a "System V" installation:**
@@ -207,7 +215,7 @@ To enable Shairport Sync to start automatically at system startup, enter:
`$ sudo systemctl enable shairport-sync`
**Install into a System V system**
If you are installing onto a System V system:
If you are installing onto a System V system, enter:
```
$ sudo make install
@@ -272,11 +280,11 @@ Please note that the full path to the programs must be specified, and script fil
Note: Shairport Sync can take configuration settings from command line options. This is mainly for backward compatability, but sometimes still useful. For normal use, it is strongly recommended that you use the configuration file method.
**Raspberry Pi**
The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. This provides a low-quality output that is nevertheless useful for testing purposes and may be adequate for [very] casual listening. It is not HiFi -- it is quite noisy and can't play anything above about 15kHz. A further problem is that it declares itself to have a very large mixer volume control range -- all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 30dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` stanza to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 30, the top 30 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -26dB, below which muting will occur.
The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. This provides a low-quality output that is nevertheless useful for testing purposes and may be adequate for [very] casual listening. It is not HiFi – it is quite noisy and can't play anything above about 15kHz. A further problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 30dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 30, the top 30 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -26dB, below which muting will occur.
From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume.
Another setting to consider is the `general` `drift` setting: you should set it to a larger number, such as 352, to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC.
Another setting to consider is the `general` `drift_tolerance_in_seconds` setting: you should set it to a larger tolerance, say 10 milliseconds – `drift_tolerance_in_seconds=0.010;` – to reduce the amount of overcorrection that seems to occur when using the Raspberry Pi's built-in DAC.
*Command Line Arguments*
@@ -388,38 +396,50 @@ Latency
-------
Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer.
Shairport Sync uses latencies supplied by the source, typically either 88,200 or 99,577 frames. You shouldn't need to change them. (The `latencies` stanza in the configuration file and the various latency command-line options are now obselete and are deprecated.)
Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them.
Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -4410.
Problems can arise when you are trying to synchronise with speaker systems — typically surround-sound home theatre systems — that have their own inherent delays. You can compensate for an inherent delay using the appropriate backend (typically `alsa`) `audio_backend_latency_offset_in_seconds`. Set this offset (in frames) to compensate for a fixed delay in the audio back end; for example, if the output device delays by 100 ms, set this to -0.1.
Resynchronisation
-------------
Shairport Sync actively maintains synchronisation with the source.
If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 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.
If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above.
* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting.
Tolerance
---------
Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 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.
Playback synchronisation is allowed to wander — to "drift" — a small amount before attempting to correct it. The default is 0.002 seconds, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the `statistics` setting to monitor correction levels. Corrections should not greatly exceed net corrections.
* You can vary the tolerance with the `general` `drift_tolerance_in_seconds` setting.
Some Statistics
---------------
If you turn on the `general` `statistics` setting, statistics like this will be printed at intervals on the console (or in the logfile if running in daemon mode):
If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file:
```
sync error in milliseconds, net correction in ppm, corrections in ppm, total packets, missing packets, late packets, too late packets, resend requests, min DAC queue size, min buffer occupancy, max buffer occupancy
```
This will be followed by the statistics themselves at regular intervals, for example:
```
-1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270
-2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267
-2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268
-2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267
-2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268
-2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267
-2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267
-2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266
```
`Sync error: -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 in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made.
"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 drift by the `general` `drift` setting — 88 frames (± 2 milliseconds) by default — before a correction will be made.
"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm.
"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 in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections.
"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.
For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+.
It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway...
"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 samples, and will go low if the processor is very busy. If it goes below about 2,000 then it's a sign that the processor can't really keep up.
"Min DAC queue size" is the minimum size the queue of samples in the output device's hardware buffer was measured at. It is meant to stand at 0.15 seconds = 6,615 frames at 44,100 frames per second, and will go low if the processor is very busy. If it goes below about 2,000 then it's an indication that the processor can't really keep up.
Troubleshooting
---------------
+101 -1
View File
@@ -1,3 +1,104 @@
Version 3.0
====
Big Update
----
Version 3 brings in support for 24-bit and 32-bit (and 8 bit!) DACs and for DACs running at multiples of 44,100 samples per second.
The most obvious audible change is if you are using software volume control and can take advantage of 32- or 24-bit DACs. Dithering can now occur on a 32-bit or 24-bit sample rather than on a 16-bit sample, making the noise floor very much lower. This is the case, for example, with a Pimoroni PHAT DAC.
Here is the list of new features:
**New Features**
* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices.
* 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling. It's only worth doing if 44,100 samples per second output is not available.
* Internal processing including software volume control and interpolation is done after sample size and rate conversion.
* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac), to be installed. Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file.
* Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further.
* Choose Network Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically.
* Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3.
* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details.
Pesky Changes You Cannot Ignore
----
* Processor load is up by about 11%.
* Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details.
* Sox-based interpolation at higher sample rates may overload your CPU -- you might have to choose between higher sample rates and sox-based interpolation.
Version 3.0rc0 – Release Candidate 0
----
Note: all Version 3 changes are summarized above.
**New Feature**
* An experimental new back end for `libsoundio`, a C library for cross-platform real-time audio input and output. Many thanks to [Serg Podtynnyi](https://github.com/shtirlic). Please see https://github.com/mikebrady/shairport-sync/pull/433 for more details.
**Other changes**
* Updates to `man` page and [README](https://github.com/mikebrady/shairport-sync/blob/development/README.md). Reports of typos or suggestions for improvement are welcome!
Version 3.0d24 – Development Version
----
Note: all Version 3 changes are summarized above.
**New Feature**
* Set Max Volume. Add a new setting, for advanced users only, in the `general` section. Use the `volume_max_db` setting to allow you to specify the maximum level to set on the hardware mixer (if chosen) or the built-in software mixer otherwise. The software mixer's range is 0.0 dB to -96.1 dB. The setting must be a number with a decimal point, e.g. 21.3.
Version 3.0d23 – Development Version
----
Note: all Version 3 changes are summarized above.
**New Feature**
* Choose Interface. Add a new setting, for advanced users only, in the `general` section. Use the `interface` setting to allow you to specify the interface on which to provide the AirPlay service. Omit the setting to get the default, which is to choose the interfaces automatically.
Version 3.0d22 – Development Version
----
Note: all Version 3 changes are summarized above.
**Bug Fix**
* Fixed a bug which prevented successful building in the OpenWrt build system. The problem was caused by an `#include apple_alac.h` in `player.c` which was actioned even if the apple alac decoder was not selected. This caused the OpenWrt build system to expect the standard C++ library – required by the apple alac code – to be referenced, but it was not specified on the build manifest and therefore stopped the build. The solution was to make the `#include` conditional on selecting the apple alac decoder.
Version 3.0d21 – Development Version
----
Note: all Version 3 changes are summarized above.
**Bug Fix**
* Fixed a bug which turned off resync by default. Duh.
Version 3.0d20 – Development Version
----
Note: all Version 3 changes are summarized above.
**Bug Fix**
* Fix a small and generally silent error in configure.ac so that it only looks for the systemd direcotry if systemd has been chosen. It caused a warning when cross-compiling.
Version 3.0d19 – Development Version
----
Note: all Version 3 changes are summarized above.
**New Feature**
* Reduces processor load back to V2.X levels by using a precalculated array of pseudorandom numbers to do dithering. Doesn't seem to make any audible difference.
Version 3.0d18 – Development Version
----
Note: all Version 3 changes are summarized above.
**New Features**
* 8-bit, 16-bit, 24-bit, 24-bit three-byte (S24_3LE and S24_3BE) and 32-bit output to ALSA devices. (Other back ends are not updated yet.)
* 44,100, 88,200, 176,400 and 352,800 sample per second output. This is done using simple upsampling.
* Internal processing including software volume control and interpolation is done after sample size and rate conversion.
* Apple ALAC decoder support. This needs the `libalac` library, available at [ALAC](https://github.com/mikebrady/alac). Add the flag `--with-apple-alac` to the `./configure` arguments. Then you can choose the Apple ALAC decoder in the configuration file.
* Support for `mbed TLS` has been added and the use of `PolarSSL` is deprecated, as `mbed TLS` is a development of `PolarSSL` and `PolarSSL` itself is not being developed further.
* Settings that were denominated in frames are now deprecated but still honoured. Deprecation warnings are issued.
Pesky Changes You Cannot Ignore
====
* Settings have changed -- basically, any timings that were denominated in frames are now in seconds. Please refer to the shairport-sync.conf.sample file for details.
* Sox-based interpolation at higher sample rates may overload your CPU -- yopu might have to choose between higher sample rates and sox-based interpolation.
**Bugs**
* Documentation is not updated.
Version 2.8.6 – Stable Candidate
----
@@ -19,7 +120,6 @@ The enhancements and bug fixes in 2.8.5 were made in versions 2.8.4.1 to 2.8.4.8
For advice on updating an installation you built yourself,
please visit the [UPDATING](https://github.com/mikebrady/shairport-sync/blob/master/UPDATING.md) page.
=======
Version 2.8.4.8 – Development Version
----
**Enhancements**
+756 -902
View File
File diff suppressed because it is too large Load Diff
+26 -35
View File
@@ -6,52 +6,43 @@
typedef struct alac_file alac_file;
alac_file *alac_create(int samplesize, int numchannels);
void alac_decode_frame(alac_file *alac,
unsigned char *inbuffer,
void *outbuffer, int *outputsize);
void alac_decode_frame(alac_file *alac, unsigned char *inbuffer, void *outbuffer, int *outputsize);
void alac_set_info(alac_file *alac, char *inputbuffer);
void alac_allocate_buffers(alac_file *alac);
void alac_free(alac_file *alac);
struct alac_file
{
unsigned char *input_buffer;
int input_buffer_bitaccumulator; /* used so we can do arbitary
bit reads */
struct alac_file {
unsigned char *input_buffer;
int input_buffer_bitaccumulator; /* used so we can do arbitary
bit reads */
int samplesize;
int numchannels;
int bytespersample;
int samplesize;
int numchannels;
int bytespersample;
/* buffers */
int32_t *predicterror_buffer_a;
int32_t *predicterror_buffer_b;
/* buffers */
int32_t *predicterror_buffer_a;
int32_t *predicterror_buffer_b;
int32_t *outputsamples_buffer_a;
int32_t *outputsamples_buffer_b;
int32_t *uncompressed_bytes_buffer_a;
int32_t *uncompressed_bytes_buffer_b;
int32_t *outputsamples_buffer_a;
int32_t *outputsamples_buffer_b;
int32_t *uncompressed_bytes_buffer_a;
int32_t *uncompressed_bytes_buffer_b;
/* stuff from setinfo */
uint32_t setinfo_max_samples_per_frame; /* 0x1000 = 4096 */ /* max samples per frame? */
uint8_t setinfo_7a; /* 0x00 */
uint8_t setinfo_sample_size; /* 0x10 */
uint8_t setinfo_rice_historymult; /* 0x28 */
uint8_t setinfo_rice_initialhistory; /* 0x0a */
uint8_t setinfo_rice_kmodifier; /* 0x0e */
uint8_t setinfo_7f; /* 0x02 */
uint16_t setinfo_80; /* 0x00ff */
uint32_t setinfo_82; /* 0x000020e7 */ /* max sample size?? */
uint32_t setinfo_86; /* 0x00069fe4 */ /* bit rate (avarge)?? */
uint32_t setinfo_8a_rate; /* 0x0000ac44 */
uint32_t setinfo_max_samples_per_frame; /* 0x1000 = 4096 */ /* max samples per frame? */
uint8_t setinfo_7a; /* 0x00 */
uint8_t setinfo_sample_size; /* 0x10 */
uint8_t setinfo_rice_historymult; /* 0x28 */
uint8_t setinfo_rice_initialhistory; /* 0x0a */
uint8_t setinfo_rice_kmodifier; /* 0x0e */
uint8_t setinfo_7f; /* 0x02 */
uint16_t setinfo_80; /* 0x00ff */
uint32_t setinfo_82; /* 0x000020e7 */ /* max sample size?? */
uint32_t setinfo_86; /* 0x00069fe4 */ /* bit rate (avarge)?? */
uint32_t setinfo_8a_rate; /* 0x0000ac44 */
/* end setinfo stuff */
};
#endif /* __ALAC__DECOMP_H */
+55
View File
@@ -0,0 +1,55 @@
#include <string.h>
// these are headers for the ALAC decoder, utilities and endian utilities
#include <alac/ALACBitUtilities.h>
#include <alac/ALACDecoder.h>
#include <alac/EndianPortable.h>
#include "apple_alac.h"
#include "config.h"
typedef struct magicCookie {
ALACSpecificConfig config;
ALACAudioChannelLayout channelLayoutInfo; // seems to be unused
} magicCookie;
magicCookie cookie;
ALACDecoder *theDecoder;
extern "C" int apple_alac_init(int32_t fmtp[12]) {
memset(&cookie, 0, sizeof(magicCookie));
// create a magic cookie for the decoder from the fmtp information. It seems to be in the same
// format as a simple magic cookie
cookie.config.frameLength = Swap32NtoB(352);
cookie.config.compatibleVersion = fmtp[2]; // should be zero, uint8_t
cookie.config.bitDepth = fmtp[3]; // uint8_t expected to be 16
cookie.config.pb = fmtp[4]; // uint8_t should be 40;
cookie.config.mb = fmtp[5]; // uint8_t should be 10;
cookie.config.kb = fmtp[6]; // uint8_t should be 14;
cookie.config.numChannels = fmtp[7]; // uint8_t expected to be 2
cookie.config.maxRun = Swap16NtoB(fmtp[8]); // uint16_t expected to be 255
cookie.config.maxFrameBytes = Swap32NtoB(fmtp[9]); // uint32_t should be 0;
cookie.config.avgBitRate = Swap32NtoB(fmtp[10]); // uint32_t should be 0;;
cookie.config.sampleRate = Swap32NtoB(fmtp[11]); // uint32_t expected to be 44100;
theDecoder = new ALACDecoder;
theDecoder->Init(&cookie, sizeof(magicCookie));
return 0;
}
extern "C" int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength,
unsigned char *dest, int *outsize) {
uint32_t numFrames = 0;
BitBuffer theInputBuffer;
BitBufferInit(&theInputBuffer, sampleBuffer, bufferLength);
theDecoder->Decode(&theInputBuffer, dest, Swap32BtoN(cookie.config.frameLength),
cookie.config.numChannels, &numFrames);
*outsize = numFrames;
return 0;
}
extern "C" int apple_alac_terminate() { delete (theDecoder); }
+20
View File
@@ -0,0 +1,20 @@
#ifndef __APPLE_ALAC_H
#define __APPLE_ALAC_H
#include "config.h"
#include <stdint.h>
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
EXTERNC int apple_alac_init(int32_t fmtp[12]);
EXTERNC int apple_alac_terminate();
EXTERNC int apple_alac_decode_frame(unsigned char *sampleBuffer, uint32_t bufferLength,
unsigned char *dest, int *outsize);
#undef EXTERNC
#endif /* __APPLE_ALAC_H */
+9 -3
View File
@@ -24,10 +24,10 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include "audio.h"
#include "config.h"
#include <stdio.h>
#include <string.h>
#ifdef CONFIG_SNDIO
extern audio_output audio_sndio;
@@ -35,6 +35,9 @@ extern audio_output audio_sndio;
#ifdef CONFIG_AO
extern audio_output audio_ao;
#endif
#ifdef CONFIG_SOUNDIO
extern audio_output audio_soundio;
#endif
#ifdef CONFIG_PULSE
extern audio_output audio_pulse;
#endif
@@ -64,10 +67,13 @@ static audio_output *outputs[] = {
#ifdef CONFIG_AO
&audio_ao,
#endif
#ifdef CONFIG_SOUNDIO
&audio_soundio,
#endif
#ifdef CONFIG_DUMMY
&audio_dummy,
#endif
#ifdef CONFIG_PIPE
#ifdef CONFIG_PIPE
&audio_pipe,
#endif
#ifdef CONFIG_STDOUT
+5 -5
View File
@@ -1,8 +1,8 @@
#ifndef _AUDIO_H
#define _AUDIO_H
#include <stdint.h>
#include <libconfig.h>
#include <stdint.h>
typedef struct {
double current_volume_dB;
@@ -19,7 +19,7 @@ typedef struct {
// at end of program
void (*deinit)(void);
void (*start)(int sample_rate);
void (*start)(int sample_rate, int sample_format);
// block of samples
void (*play)(short buf[], int samples);
@@ -32,17 +32,17 @@ typedef struct {
// 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.
// returns a negative error code if there's a problem
int (*delay)(long* the_delay); // snd_pcm_sframes_t is a signed long
int (*delay)(long *the_delay); // snd_pcm_sframes_t is a signed long
// 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);
// may be NULL, in which case software muting is used.
void (*mute)(int do_mute);
} audio_output;
audio_output *audio_get_output(char *name);
+379 -278
View File
@@ -26,23 +26,23 @@
#define ALSA_PCM_NEW_HW_PARAMS_API
#include "audio.h"
#include "common.h"
#include <alsa/asoundlib.h>
#include <math.h>
#include <memory.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <memory.h>
#include <math.h>
#include <pthread.h>
#include <alsa/asoundlib.h>
#include "common.h"
#include "audio.h"
static void help(void);
static int init(int argc, char **argv);
static void deinit(void);
static void start(int sample_rate);
static void start(int i_sample_rate, int i_sample_format);
static void play(short buf[], int samples);
static void stop(void);
static void flush(void);
int delay(long* the_delay);
int delay(long *the_delay);
static void volume(double vol);
static void linear_volume(double vol);
static void parameters(audio_parameters *info);
@@ -50,24 +50,23 @@ static void mute(int do_mute);
static double set_volume;
static int output_method_signalled = 0;
audio_output audio_alsa = {
.name = "alsa",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.flush = &flush,
.delay = &delay,
.play = &play,
.mute = &mute,
.volume = &volume,
.parameters = &parameters
};
audio_output audio_alsa = {.name = "alsa",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.flush = &flush,
.delay = &delay,
.play = &play,
.mute = &mute,
.volume = &volume,
.parameters = &parameters};
static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned int desired_sample_rate;
static snd_pcm_format_t sample_format;
static snd_pcm_t *alsa_handle = NULL;
static snd_pcm_hw_params_t *alsa_params = NULL;
@@ -86,15 +85,15 @@ static int alsa_mix_index = 0;
static int hardware_mixer = 0;
static int has_softvol = 0;
static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, snd_pcm_uframes_t) = snd_pcm_writei;
static snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *,
snd_pcm_uframes_t) = snd_pcm_writei;
static int play_number;
static int64_t accumulated_delay, accumulated_da_delay;
int alsa_characteristics_already_listed = 0;
static snd_pcm_uframes_t period_size_requested,buffer_size_requested;
static int set_period_size_request,set_buffer_size_request;
static snd_pcm_uframes_t period_size_requested, buffer_size_requested;
static int set_period_size_request, set_buffer_size_request;
static void help(void) {
printf(" -d output-device set the output device [default*|...]\n"
@@ -135,40 +134,72 @@ static int init(int argc, char **argv) {
// debug(2,"audio_alsa init called.");
const char *str;
int value;
double dvalue;
set_period_size_request = 0;
set_buffer_size_request = 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
set_period_size_request = 0;
set_buffer_size_request = 0;
config.audio_backend_latency_offset = 0;
config.audio_backend_buffer_desired_length = 0.15;
// get settings from settings file first, allow them to be overridden by
// command line options
if (config.cfg != NULL) {
/* Get the desired buffer size setting. */
if (config_lookup_int(config.cfg,
"alsa.audio_backend_buffer_desired_length", &value)) {
if ((value < 0) || (value > 66150))
if (config_lookup_int(config.cfg, "alsa.audio_backend_buffer_desired_length", &value)) {
if ((value < 0) || (value > 66150)) {
inform("The setting alsa.audio_backend_buffer_desired_length is deprecated. "
"Use alsa.audio_backend_buffer_desired_length_in_seconds instead.");
die("Invalid alsa audio backend buffer desired length \"%d\". It "
"should be between 0 and "
"66150, default is 6615",
value);
else {
config.audio_backend_buffer_desired_length = value;
} else {
inform("The setting alsa.audio_backend_buffer_desired_length is deprecated. "
"Use alsa.audio_backend_buffer_desired_length_in_seconds instead.");
config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
}
}
/* Get the desired buffer size setting. */
if (config_lookup_float(config.cfg, "alsa.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
if ((dvalue < 0) || (dvalue > 1.5)) {
die("Invalid alsa audio backend buffer desired time \"%f\". It "
"should be between 0 and "
"1.5, default is 0.15 seconds",
dvalue);
} else {
config.audio_backend_buffer_desired_length = dvalue;
}
}
/* Get the latency offset. */
if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset",
&value)) {
if ((value < -66150) || (value > 66150))
if (config_lookup_int(config.cfg, "alsa.audio_backend_latency_offset", &value)) {
if ((value < -66150) || (value > 66150)) {
inform("The setting alsa.audio_backend_latency_offset is deprecated. "
"Use alsa.audio_backend_latency_offset_in_seconds instead.");
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;
} else {
inform("The setting alsa.audio_backend_latency_offset is deprecated. "
"Use alsa.audio_backend_latency_offset_in_seconds instead.");
config.audio_backend_latency_offset = 1.0 * value / 44100;
}
}
/* Get the latency offset. */
if (config_lookup_float(config.cfg, "alsa.audio_backend_latency_offset_in_seconds", &dvalue)) {
if ((dvalue < -1.0) || (dvalue > 1.5)) {
die("Invalid alsa audio backend buffer latency offset time \"%f\". It "
"should be between -1.0 and +1.5, default is 0 seconds",
dvalue);
} else {
config.audio_backend_latency_offset = dvalue;
}
}
/* Get the Output Device Name. */
@@ -194,7 +225,7 @@ static int init(int argc, char **argv) {
alsa_mix_ctrl = (char *)str;
hardware_mixer = 1;
}
/* Get the disable_synchronization setting. */
if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) {
if (strcasecmp(str, "no") == 0)
@@ -204,7 +235,45 @@ static int init(int argc, char **argv) {
else
die("Invalid disable_synchronization option choice \"%s\". It should be \"yes\" or \"no\"");
}
/* Get the output format, using the same names as aplay does*/
if (config_lookup_string(config.cfg, "alsa.output_format", &str)) {
if (strcasecmp(str, "S16") == 0)
config.output_format = SPS_FORMAT_S16;
else if (strcasecmp(str, "S24") == 0)
config.output_format = SPS_FORMAT_S24;
else if (strcasecmp(str, "S24_3LE") == 0)
config.output_format = SPS_FORMAT_S24_3LE;
else if (strcasecmp(str, "S24_3BE") == 0)
config.output_format = SPS_FORMAT_S24_3BE;
else if (strcasecmp(str, "S32") == 0)
config.output_format = SPS_FORMAT_S32;
else if (strcasecmp(str, "U8") == 0)
config.output_format = SPS_FORMAT_U8;
else if (strcasecmp(str, "S8") == 0)
config.output_format = SPS_FORMAT_S8;
else
die("Invalid output format \"%s\". It should be \"U8\", \"S8\", \"S16\", \"S24\", "
"\"S24_3LE\", \"S24_3BE\" or "
"\"S32\"",
str);
}
/* Get the output rate, which must be a multiple of 44,100*/
if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) {
debug(1, "Value read for output rate is %d.", value);
switch (value) {
case 44100:
case 88200:
case 176400:
case 352800:
config.output_rate = value;
break;
default:
die("Invalid output rate \"%d\". It should be a multiple of 44,100 up to 352,800", value);
}
}
/* Get the use_mmap_if_available setting. */
if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) {
if (strcasecmp(str, "no") == 0)
@@ -215,10 +284,9 @@ static int init(int argc, char **argv) {
die("Invalid use_mmap_if_available option choice \"%s\". It should be \"yes\" or \"no\"");
}
/* Get the optional period size value */
if (config_lookup_int(config.cfg, "alsa.period_size",
&value)) {
if (config_lookup_int(config.cfg, "alsa.period_size", &value)) {
set_period_size_request = 1;
debug(1,"Value read for period size is %d.",value);
debug(1, "Value read for period size is %d.", value);
if (value < 0)
die("Invalid alsa period size setting \"%d\". It "
"must be greater than 0.",
@@ -228,10 +296,9 @@ static int init(int argc, char **argv) {
}
/* Get the optional buffer size value */
if (config_lookup_int(config.cfg, "alsa.buffer_size",
&value)) {
if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) {
set_buffer_size_request = 1;
debug(1,"Value read for buffer size is %d.",value);
debug(1, "Value read for buffer size is %d.", value);
if (value < 0)
die("Invalid alsa buffer size setting \"%d\". It "
"must be greater than 0.",
@@ -240,8 +307,6 @@ static int init(int argc, char **argv) {
buffer_size_requested = value;
}
}
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
@@ -282,7 +347,7 @@ static int init(int argc, char **argv) {
debug(1, "Output device name is \"%s\".", alsa_out_dev);
if (hardware_mixer) {
if (alsa_mix_dev == NULL)
alsa_mix_dev = alsa_out_dev;
@@ -290,25 +355,24 @@ static int init(int argc, char **argv) {
open_mixer();
if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv,
&alsa_mix_maxv) < 0)
if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
0)
debug(1, "Can't read mixer's [linear] min and max volumes.");
else {
if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
&alsa_mix_maxdb) == 0) {
if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb, &alsa_mix_maxdb) ==
0) {
audio_alsa.volume =
&volume; // insert the volume function now we know it can do dB stuff
audio_alsa.volume = &volume; // insert the volume function now we know it can do dB stuff
audio_alsa.parameters = &parameters; // likewise the parameters stuff
if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
// Raspberry Pi does this
debug(1, "Lowest dB value is a mute -- try minimum volume +1");
if (snd_mixer_selem_ask_playback_vol_dB(
alsa_mix_elem, alsa_mix_minv + 1, &alsa_mix_mindb) != 0)
if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
&alsa_mix_mindb) != 0)
debug(1, "Can't get dB value corresponding to a minimum volume + 1.");
}
debug(1, "Hardware mixer has dB volume from %f to %f.",
(1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
debug(1, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
(1.0 * alsa_mix_maxdb) / 100.0);
} else {
// use the linear scale and do the db conversion ourselves
debug(1, "note: the hardware mixer specified -- \"%s\" -- does not have "
@@ -323,16 +387,12 @@ static int init(int argc, char **argv) {
snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);
if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb,
&alsa_mix_maxdb) == 0) {
debug(1, "Volume control \"%s\" has dB volume from %f to %f.",
alsa_mix_ctrl,
(1.0 * alsa_mix_mindb) / 100.0,
(1.0 * alsa_mix_maxdb) / 100.0);
if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
debug(1, "Volume control \"%s\" has dB volume from %f to %f.", alsa_mix_ctrl,
(1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
has_softvol = 1;
} else {
debug(1, "Cannot get the dB range from the volume control \"%s\"",
alsa_mix_ctrl);
debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl);
}
/*
@@ -352,14 +412,13 @@ static int init(int argc, char **argv) {
}
}
if (snd_mixer_selem_has_playback_switch(alsa_mix_elem)) {
audio_alsa.mute =
&mute; // insert the mute function now we know it can do muting stuff
audio_alsa.mute = &mute; // insert the mute function now we know it can do muting stuff
debug(1, "Has mute ability.");
}
snd_mixer_close(alsa_mix_handle);
}
alsa_mix_handle = NULL;
pthread_mutex_unlock(&alsa_mutex);
return 0;
@@ -374,7 +433,7 @@ static void deinit(void) {
}
int open_alsa_device(void) {
const snd_pcm_uframes_t minimal_buffer_headroom =
352 * 2; // we accept this much headroom in the hardware buffer, but we'll
// accept less
@@ -386,10 +445,16 @@ int open_alsa_device(void) {
// snd_pcm_uframes_t frames = 441 * 10;
snd_pcm_uframes_t buffer_size, actual_buffer_length;
snd_pcm_access_t access;
// ensure no calls are made to the alsa device enquiring about the buffer length if synchronisation is disabled.
if (config.no_sync!=0)
audio_alsa.delay = NULL;
// ensure no calls are made to the alsa device enquiring about the buffer length if
// synchronisation is disabled.
if (config.no_sync != 0)
audio_alsa.delay = NULL;
// ensure no calls are made to the alsa device enquiring about the buffer length if
// synchronisation is disabled.
if (config.no_sync != 0)
audio_alsa.delay = NULL;
ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0);
if (ret < 0)
@@ -406,78 +471,106 @@ int open_alsa_device(void) {
alsa_out_dev);
}
if ((config.no_mmap == 0) && (snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) >= 0)) {
if (output_method_signalled==0) {
debug(1,"Output written using MMAP");
output_method_signalled=1;
}
if ((config.no_mmap == 0) &&
(snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) >=
0)) {
if (output_method_signalled == 0) {
debug(1, "Output written using MMAP");
output_method_signalled = 1;
}
access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
alsa_pcm_write = snd_pcm_mmap_writei;
} else {
if (output_method_signalled==0) {
debug(1,"Output written with RW");
output_method_signalled=1;
}
if (output_method_signalled == 0) {
debug(1, "Output written with RW");
output_method_signalled = 1;
}
access = SND_PCM_ACCESS_RW_INTERLEAVED;
alsa_pcm_write = snd_pcm_writei;
}
ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, access);
if (ret < 0) {
die("audio_alsa: Access type not available for device \"%s\": %s",
alsa_out_dev, snd_strerror(ret));
die("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev,
snd_strerror(ret));
}
ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params,
SND_PCM_FORMAT_S16);
snd_pcm_format_t sf;
switch (sample_format) {
case SPS_FORMAT_S8:
sf = SND_PCM_FORMAT_S8;
break;
case SPS_FORMAT_U8:
sf = SND_PCM_FORMAT_U8;
break;
case SPS_FORMAT_S16:
sf = SND_PCM_FORMAT_S16;
break;
case SPS_FORMAT_S24:
sf = SND_PCM_FORMAT_S24;
break;
case SPS_FORMAT_S24_3LE:
sf = SND_PCM_FORMAT_S24_3LE;
break;
case SPS_FORMAT_S24_3BE:
sf = SND_PCM_FORMAT_S24_3BE;
break;
case SPS_FORMAT_S32:
sf = SND_PCM_FORMAT_S32;
break;
}
ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf);
if (ret < 0) {
die("audio_alsa: Sample format not available for device \"%s\": %s",
alsa_out_dev, snd_strerror(ret));
die("audio_alsa: Sample format %d not available for device \"%s\": %s", sf, alsa_out_dev,
snd_strerror(ret));
}
ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2);
if (ret < 0) {
die("audio_alsa: Channels count (2) not available for device \"%s\": %s",
alsa_out_dev, snd_strerror(ret));
die("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev,
snd_strerror(ret));
}
ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params,
&my_sample_rate, &dir);
ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &my_sample_rate, &dir);
if (ret < 0) {
die("audio_alsa: Rate %iHz not available for playback: %s",
desired_sample_rate, snd_strerror(ret));
die("audio_alsa: Rate %iHz not available for playback: %s", desired_sample_rate,
snd_strerror(ret));
}
if (set_period_size_request!=0) {
debug(1,"Attempting to set the period size");
ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested, &dir);
if (ret < 0) {
die("audio_alsa: cannot set period size of %lu: %s",
period_size_requested, snd_strerror(ret));
snd_pcm_uframes_t actual_period_size;
snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir);
if (actual_period_size!=period_size_requested)
inform("Actual period size set to a different value than requested. Requested: %lu, actual setting: %lu",period_size_requested,actual_period_size);
}
if (set_period_size_request != 0) {
debug(1, "Attempting to set the period size");
ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested,
&dir);
if (ret < 0) {
die("audio_alsa: cannot set period size of %lu: %s", period_size_requested,
snd_strerror(ret));
snd_pcm_uframes_t actual_period_size;
snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir);
if (actual_period_size != period_size_requested)
inform("Actual period size set to a different value than requested. Requested: %lu, actual "
"setting: %lu",
period_size_requested, actual_period_size);
}
}
if (set_buffer_size_request!=0) {
debug(1,"Attempting to set the buffer size to %lu",buffer_size_requested);
ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested);
if (ret < 0) {
die("audio_alsa: cannot set buffer size of %lu: %s",
buffer_size_requested, snd_strerror(ret));
snd_pcm_uframes_t actual_buffer_size;
snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
if (actual_buffer_size!=buffer_size_requested)
inform("Actual period size set to a different value than requested. Requested: %lu, actual setting: %lu",buffer_size,actual_buffer_size);
}
if (set_buffer_size_request != 0) {
debug(1, "Attempting to set the buffer size to %lu", buffer_size_requested);
ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested);
if (ret < 0) {
die("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested,
snd_strerror(ret));
snd_pcm_uframes_t actual_buffer_size;
snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
if (actual_buffer_size != buffer_size_requested)
inform("Actual period size set to a different value than requested. Requested: %lu, actual "
"setting: %lu",
buffer_size, actual_buffer_size);
}
}
ret = snd_pcm_hw_params(alsa_handle, alsa_params);
if (ret < 0) {
die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.",
alsa_out_dev, snd_strerror(ret));
die("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev,
snd_strerror(ret));
}
if (my_sample_rate != desired_sample_rate) {
@@ -486,12 +579,11 @@ int open_alsa_device(void) {
ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length);
if (ret < 0) {
die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.",
alsa_out_dev, snd_strerror(ret));
die("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev,
snd_strerror(ret));
}
if (actual_buffer_length <
config.audio_backend_buffer_desired_length + minimal_buffer_headroom) {
if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) {
/*
// the dac buffer is too small, so let's try to set it
buffer_size =
@@ -514,146 +606,158 @@ int open_alsa_device(void) {
buffer_size);
}
*/
debug(1,"The alsa buffer is to small (%lu bytes) to accommodate the desired backend buffer length (%ld) you have chosen.",actual_buffer_length,config.audio_backend_buffer_desired_length);
debug(1, "The alsa buffer is to small (%lu bytes) to accommodate the desired backend buffer "
"length (%ld) you have chosen.",
actual_buffer_length, config.audio_backend_buffer_desired_length);
}
if (alsa_characteristics_already_listed==0) {
alsa_characteristics_already_listed=1;
int log_level = 2; // the level at which debug information should be output
int rc;
snd_pcm_access_t access_type;
snd_pcm_format_t format_type;
snd_pcm_subformat_t subformat_type;
unsigned int val, val2;
unsigned int uval, uval2;
int sval;
int dir;
snd_pcm_uframes_t frames;
debug(log_level,"PCM handle name = '%s'",
snd_pcm_name(alsa_handle));
// ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
// if (ret < 0) {
// die("audio_alsa: Cannpot get configuration for device \"%s\": no configurations "
// "available",
// alsa_out_dev);
// }
if (alsa_characteristics_already_listed == 0) {
alsa_characteristics_already_listed = 1;
int log_level = 2; // the level at which debug information should be output
int rc;
snd_pcm_access_t access_type;
snd_pcm_format_t format_type;
snd_pcm_subformat_t subformat_type;
unsigned int val, val2;
unsigned int uval, uval2;
int sval;
int dir;
snd_pcm_uframes_t frames;
debug(log_level,"alsa device parameters:");
debug(log_level, "PCM handle name = '%s'", snd_pcm_name(alsa_handle));
snd_pcm_hw_params_get_access(alsa_params,&access_type);
debug(log_level," access type = %s", snd_pcm_access_name(access_type));
// ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
// if (ret < 0) {
// die("audio_alsa: Cannpot get configuration for device \"%s\": no
// configurations
//"
// "available",
// alsa_out_dev);
// }
snd_pcm_hw_params_get_format(alsa_params,&format_type);
debug(log_level," format = '%s' (%s)",snd_pcm_format_name(format_type),snd_pcm_format_description(format_type));
debug(log_level, "alsa device parameters:");
snd_pcm_hw_params_get_subformat(alsa_params,&subformat_type);
debug(log_level," subformat = '%s' (%s)",snd_pcm_subformat_name(subformat_type),snd_pcm_subformat_description(subformat_type));
snd_pcm_hw_params_get_access(alsa_params, &access_type);
debug(log_level, " access type = %s", snd_pcm_access_name(access_type));
snd_pcm_hw_params_get_channels(alsa_params, &uval);
debug(log_level," number of channels = %u", uval);
snd_pcm_hw_params_get_format(alsa_params, &format_type);
debug(log_level, " format = '%s' (%s)", snd_pcm_format_name(format_type),
snd_pcm_format_description(format_type));
sval = snd_pcm_hw_params_get_sbits(alsa_params);
debug(log_level," number of significant bits = %d", sval);
snd_pcm_hw_params_get_rate(alsa_params, &uval, &dir);
switch (dir) {
case -1:
debug(log_level," rate = %u frames per second (<).", uval);
break;
case 0:
debug(log_level," rate = %u frames per second (precisely).", uval);
break;
case 1:
debug(log_level," rate = %u frames per second (>).", uval);
break;
}
snd_pcm_hw_params_get_subformat(alsa_params, &subformat_type);
debug(log_level, " subformat = '%s' (%s)", snd_pcm_subformat_name(subformat_type),
snd_pcm_subformat_description(subformat_type));
if (snd_pcm_hw_params_get_rate_numden(alsa_params,&uval, &uval2)==0)
debug(log_level," precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval, uval2, ((double)uval)/uval2);
else
debug(log_level," precise (rational) rate information unavailable.");
snd_pcm_hw_params_get_channels(alsa_params, &uval);
debug(log_level, " number of channels = %u", uval);
snd_pcm_hw_params_get_period_time(alsa_params,&uval, &dir);
switch (dir) {
case -1:
debug(log_level," period_time = %u us (<).", uval);
break;
case 0:
debug(log_level," period_time = %u us (precisely).", uval);
break;
case 1:
debug(log_level," period_time = %u us (>).", uval);
break;
}
sval = snd_pcm_hw_params_get_sbits(alsa_params);
debug(log_level, " number of significant bits = %d", sval);
snd_pcm_hw_params_get_period_size(alsa_params,&frames, &dir);
switch (dir) {
case -1:
debug(log_level," period_size = %lu frames (<).", frames);
break;
case 0:
debug(log_level," period_size = %lu frames (precisely).", frames);
break;
case 1:
debug(log_level," period_size = %lu frames (>).", frames);
break;
}
snd_pcm_hw_params_get_rate(alsa_params, &uval, &dir);
switch (dir) {
case -1:
debug(log_level, " rate = %u frames per second (<).", uval);
break;
case 0:
debug(log_level, " rate = %u frames per second (precisely).", uval);
break;
case 1:
debug(log_level, " rate = %u frames per second (>).", uval);
break;
}
snd_pcm_hw_params_get_buffer_time(alsa_params,&uval, &dir);
switch (dir) {
case -1:
debug(log_level," buffer_time = %u us (<).", uval);
break;
case 0:
debug(log_level," buffer_time = %u us (precisely).", uval);
break;
case 1:
debug(log_level," buffer_time = %u us (>).", uval);
break;
}
if (snd_pcm_hw_params_get_rate_numden(alsa_params, &uval, &uval2) == 0)
debug(log_level, " precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval,
uval2, ((double)uval) / uval2);
else
debug(log_level, " precise (rational) rate information unavailable.");
snd_pcm_hw_params_get_period_time(alsa_params, &uval, &dir);
switch (dir) {
case -1:
debug(log_level, " period_time = %u us (<).", uval);
break;
case 0:
debug(log_level, " period_time = %u us (precisely).", uval);
break;
case 1:
debug(log_level, " period_time = %u us (>).", uval);
break;
}
snd_pcm_hw_params_get_period_size(alsa_params, &frames, &dir);
switch (dir) {
case -1:
debug(log_level, " period_size = %lu frames (<).", frames);
break;
case 0:
debug(log_level, " period_size = %lu frames (precisely).", frames);
break;
case 1:
debug(log_level, " period_size = %lu frames (>).", frames);
break;
}
snd_pcm_hw_params_get_buffer_time(alsa_params, &uval, &dir);
switch (dir) {
case -1:
debug(log_level, " buffer_time = %u us (<).", uval);
break;
case 0:
debug(log_level, " buffer_time = %u us (precisely).", uval);
break;
case 1:
debug(log_level, " buffer_time = %u us (>).", uval);
break;
}
snd_pcm_hw_params_get_buffer_size(alsa_params, &frames);
switch (dir) {
case -1:
debug(log_level, " buffer_size = %lu frames (<).", frames);
break;
case 0:
debug(log_level, " buffer_size = %lu frames (precisely).", frames);
break;
case 1:
debug(log_level, " buffer_size = %lu frames (>).", frames);
break;
}
snd_pcm_hw_params_get_periods(alsa_params, &uval, &dir);
switch (dir) {
case -1:
debug(log_level, " periods_per_buffer = %u (<).", uval);
break;
case 0:
debug(log_level, " periods_per_buffer = %u (precisely).", uval);
break;
case 1:
debug(log_level, " periods_per_buffer = %u (>).", uval);
break;
}
}
snd_pcm_hw_params_get_buffer_size(alsa_params,&frames);
switch (dir) {
case -1:
debug(log_level," buffer_size = %lu frames (<).", frames);
break;
case 0:
debug(log_level," buffer_size = %lu frames (precisely).", frames);
break;
case 1:
debug(log_level," buffer_size = %lu frames (>).", frames);
break;
}
snd_pcm_hw_params_get_periods(alsa_params, &uval, &dir);
switch (dir) {
case -1:
debug(log_level," periods_per_buffer = %u (<).", uval);
break;
case 0:
debug(log_level," periods_per_buffer = %u (precisely).", uval);
break;
case 1:
debug(log_level," periods_per_buffer = %u (>).", uval);
break;
}
}
return (0);
}
static void start(int sample_rate) {
static void start(int i_sample_rate, int i_sample_format) {
// debug(2,"audio_alsa start called.");
if (sample_rate != 44100)
die("Unexpected sample rate %d -- only 44,100 supported!", sample_rate);
desired_sample_rate = sample_rate; // must be a variable
if (i_sample_rate == 0)
desired_sample_rate = 44100; // default
else
desired_sample_rate = i_sample_rate; // must be a variable
if (i_sample_format == 0)
sample_format = SPS_FORMAT_S16; // default
else
sample_format = i_sample_format;
}
int delay(long* the_delay) {
//snd_pcm_sframes_t is a signed long -- hence the return of a "long"
int reply;
int delay(long *the_delay) {
// snd_pcm_sframes_t is a signed long -- hence the return of a "long"
int reply;
// debug(3,"audio_alsa delay called.");
if (alsa_handle == NULL) {
return -ENODEV;
@@ -673,12 +777,11 @@ int delay(long* the_delay) {
reply = 0; // no error
} else {
if (snd_pcm_state(alsa_handle) == SND_PCM_STATE_XRUN) {
*the_delay = 0;
reply = 0; // no error
*the_delay = 0;
reply = 0; // no error
} else {
reply = -EIO;
debug(1, "Error -- ALSA delay(): bad state: %d.",
snd_pcm_state(alsa_handle));
debug(1, "Error -- ALSA delay(): bad state: %d.", snd_pcm_state(alsa_handle));
}
if ((derr = snd_pcm_prepare(alsa_handle))) {
ignore = snd_pcm_recover(alsa_handle, derr, 1);
@@ -687,8 +790,8 @@ int delay(long* the_delay) {
}
pthread_mutex_unlock(&alsa_mutex);
// here, occasionally pretend there's a problem with pcm_get_delay()
//if ((random() % 100000) < 3) // keep it pretty rare
// reply = -EPERM; // pretend something bad has happened
// if ((random() % 100000) < 3) // keep it pretty rare
// reply = -EPERM; // pretend something bad has happened
return reply;
}
}
@@ -700,7 +803,7 @@ static void play(short buf[], int samples) {
pthread_mutex_lock(&alsa_mutex);
ret = open_alsa_device();
if (hardware_mixer)
open_mixer();
open_mixer();
pthread_mutex_unlock(&alsa_mutex);
if ((hardware_mixer) && (ret == 0) && (audio_alsa.volume))
audio_alsa.volume(set_volume);
@@ -711,17 +814,17 @@ static void play(short buf[], int samples) {
int err, ignore;
if ((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
(snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)) {
if (buf==NULL)
debug(1,"NULL buffer passed to pcm_writei -- skipping it");
if (samples==0)
debug(1,"empty buffer being passed to pcm_writei -- skipping it");
if ((samples!=0) && (buf!=NULL)) {
err = alsa_pcm_write(alsa_handle, (char *)buf, samples);
if (err < 0) {
debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples,
snd_strerror(err));
ignore = snd_pcm_recover(alsa_handle, err, 1);
}
if (buf == NULL)
debug(1, "NULL buffer passed to pcm_writei -- skipping it");
if (samples == 0)
debug(1, "empty buffer being passed to pcm_writei -- skipping it");
if ((samples != 0) && (buf != NULL)) {
err = alsa_pcm_write(alsa_handle, (char *)buf, samples);
if (err < 0) {
debug(1, "Error %d writing %d samples in play(): \"%s\".", err, samples,
snd_strerror(err));
ignore = snd_pcm_recover(alsa_handle, err, 1);
}
}
} else {
debug(1, "Error -- ALSA device in incorrect state (%d) for play.",
@@ -759,8 +862,7 @@ static void flush(void) {
*/
if (!((snd_pcm_state(alsa_handle) == SND_PCM_STATE_PREPARED) ||
(snd_pcm_state(alsa_handle) == SND_PCM_STATE_RUNNING)))
debug(1, "Flush returning unexpected state -- %d.",
snd_pcm_state(alsa_handle));
debug(1, "Flush returning unexpected state -- %d.", snd_pcm_state(alsa_handle));
// flush also closes the device
snd_pcm_close(alsa_handle);
@@ -786,7 +888,7 @@ static void parameters(audio_parameters *info) {
static void volume(double vol) {
pthread_mutex_lock(&alsa_mutex);
debug(2, "Setting volume db to %f.", vol);
debug(2, "Setting volume db to %f.", vol);
set_volume = vol;
if (hardware_mixer && alsa_mix_handle) {
if (has_softvol) {
@@ -794,17 +896,17 @@ static void volume(double vol) {
snd_ctl_elem_value_t *value;
long raw;
if (snd_ctl_convert_from_dB(ctl, elem_id, (long) vol, &raw, 0) < 0)
debug(1, "Failed converting dB gain to raw volume value for the "
"software volume control.");
if (snd_ctl_convert_from_dB(ctl, elem_id, (long)vol, &raw, 0) < 0)
debug(1, "Failed converting dB gain to raw volume value for the "
"software volume control.");
snd_ctl_elem_value_alloca(&value);
snd_ctl_elem_value_set_id(value, elem_id);
snd_ctl_elem_value_set_integer(value, 0, raw);
snd_ctl_elem_value_set_integer(value, 1, raw);
if (snd_ctl_elem_write(ctl, value) < 0)
debug(1, "Failed to set playback dB volume for the software volume "
"control.");
debug(1, "Failed to set playback dB volume for the software volume "
"control.");
}
} else {
if (snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol, 0) != 0) {
@@ -824,8 +926,7 @@ static void linear_volume(double vol) {
if (hardware_mixer && alsa_mix_handle) {
double linear_volume = pow(10, vol);
// debug(1,"Linear volume is %f.",linear_volume);
long int_vol =
alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume;
long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) * linear_volume;
// debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol);
if (alsa_mix_handle) {
if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0)
+56 -19
View File
@@ -24,12 +24,12 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "audio.h"
#include "common.h"
#include <ao/ao.h>
#include <memory.h>
#include <stdio.h>
#include <unistd.h>
#include <memory.h>
#include <ao/ao.h>
#include "common.h"
#include "audio.h"
ao_device *dev = NULL;
@@ -43,38 +43,73 @@ static void help(void) {
static int init(int argc, char **argv) {
const char *str;
int value;
double dvalue;
ao_initialize();
int driver = ao_default_driver_id();
ao_option *ao_opts = NULL;
config.audio_backend_buffer_desired_length = 44100; // one second.
config.audio_backend_buffer_desired_length = 1.0;
config.audio_backend_latency_offset = 0;
// get settings from settings file first, allow them to be overridden by command line options
if (config.cfg != NULL) {
/* Get the desired buffer size setting. */
if (config_lookup_int(config.cfg, "ao.audio_backend_buffer_desired_length", &value)) {
if ((value < 0) || (value > 66150))
die("Invalid a0 audio backend buffer desired length \"%d\". It should be between 0 and "
"66150, default is 44100",
if ((value < 0) || (value > 66150)) {
inform("The setting ao.audio_backend_buffer_desired_length is deprecated. "
"Use ao.audio_backend_buffer_desired_length_in_seconds instead.");
die("Invalid ao audio backend buffer desired length \"%d\". It "
"should be between 0 and "
"66150, default is 6615",
value);
else {
config.audio_backend_buffer_desired_length = value;
} else {
inform("The setting ao.audio_backend_buffer_desired_length is deprecated. "
"Use ao.audio_backend_buffer_desired_length_in_seconds instead.");
config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
}
}
/* Get the desired buffer size setting. */
if (config_lookup_float(config.cfg, "ao.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
if ((dvalue < 0) || (dvalue > 1.5)) {
die("Invalid ao audio backend buffer desired time \"%f\". It "
"should be between 0 and "
"1.5, default is 0.15 seconds",
dvalue);
} else {
config.audio_backend_buffer_desired_length = dvalue;
}
}
/* Get the latency offset. */
if (config_lookup_int(config.cfg, "ao.audio_backend_latency_offset", &value)) {
if ((value < -66150) || (value > 66150))
die("Invalid ao audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0",
if ((value < -66150) || (value > 66150)) {
inform("The setting ao.audio_backend_latency_offset is deprecated. "
"Use ao.audio_backend_latency_offset_in_seconds instead.");
die("Invalid ao audio backend buffer latency offset \"%d\". It "
"should be between -66150 and +66150, default is 0",
value);
else
config.audio_backend_latency_offset = value;
} else {
inform("The setting ao.audio_backend_latency_offset is deprecated. "
"Use ao.audio_backend_latency_offset_in_seconds instead.");
config.audio_backend_latency_offset = 1.0 * value / 44100;
}
}
/* Get the latency offset. */
if (config_lookup_float(config.cfg, "ao.audio_backend_latency_offset_in_seconds", &dvalue)) {
if ((dvalue < -1.0) || (dvalue > 1.5)) {
die("Invalid ao audio backend buffer latency offset time \"%f\". It "
"should be between -1.0 and +1.5, default is 0 seconds",
dvalue);
} else {
config.audio_backend_latency_offset = dvalue;
}
}
}
optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
argc++;
@@ -134,9 +169,11 @@ static void deinit(void) {
ao_shutdown();
}
static void start(int sample_rate) {
if (sample_rate != 44100)
static void start(int sample_rate, int sample_format) {
if (sample_rate != 0)
die("unexpected sample rate!");
if (sample_format != 0)
die("unexpected sample format!");
}
static void play(short buf[], int samples) { ao_play(dev, (char *)buf, samples * 4); }
+5 -5
View File
@@ -28,11 +28,11 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include "common.h"
#include "audio.h"
#include "common.h"
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
int Fs;
long long starttime, samples_played;
@@ -41,7 +41,7 @@ static int init(int argc, char **argv) { return 0; }
static void deinit(void) {}
static void start(int sample_rate) {
static void start(int sample_rate, int sample_format) {
Fs = sample_rate;
starttime = 0;
samples_played = 0;
+63 -29
View File
@@ -28,22 +28,22 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <unistd.h>
#include "audio.h"
#include "common.h"
#include <errno.h>
#include <fcntl.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "common.h"
#include "audio.h"
#include <sys/types.h>
#include <unistd.h>
static int fd = -1;
char *pipename = NULL;
static void start(int sample_rate) {
static void start(int sample_rate, int sample_format) {
// this will leave fd as -1 if a reader hasn't been attached
fd = open(pipename, O_WRONLY | O_NONBLOCK);
}
@@ -51,7 +51,7 @@ static void start(int sample_rate) {
static void play(short buf[], int samples) {
// if the file is not open, try to open it.
if (fd == -1) {
fd = open(pipename, O_WRONLY | O_NONBLOCK);
fd = open(pipename, O_WRONLY | O_NONBLOCK);
}
// if it's got a reader, write to it.
if (fd != -1) {
@@ -60,17 +60,18 @@ static void play(short buf[], int samples) {
}
static void stop(void) {
// Don't close the pipe just because a play session has stopped.
// if (fd > 0)
// 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) {
debug(1, "pipe init");
const char *str;
int value;
config.audio_backend_buffer_desired_length = 44100; // one second.
double dvalue;
config.audio_backend_buffer_desired_length = 1.0;
config.audio_backend_latency_offset = 0;
if (config.cfg != NULL) {
@@ -79,36 +80,71 @@ static int init(int argc, char **argv) {
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",
if ((value < 0) || (value > 66150)) {
inform("The setting pipe.audio_backend_buffer_desired_length is deprecated. "
"Use pipe.audio_backend_buffer_desired_length_in_seconds instead.");
die("Invalid pipe audio backend buffer desired length \"%d\". It "
"should be between 0 and "
"66150, default is 6615",
value);
else
config.audio_backend_buffer_desired_length = value;
} else {
inform("The setting pipe.audio_backend_buffer_desired_length is deprecated. "
"Use pipe.audio_backend_buffer_desired_length_in_seconds instead.");
config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
}
}
/* Get the desired buffer size setting. */
if (config_lookup_float(config.cfg, "pipe.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
if ((dvalue < 0) || (dvalue > 1.5)) {
die("Invalid pipe audio backend buffer desired time \"%f\". It "
"should be between 0 and "
"1.5, default is 0.15 seconds",
dvalue);
} else {
config.audio_backend_buffer_desired_length = dvalue;
}
}
/* 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",
if ((value < -66150) || (value > 66150)) {
inform("The setting pipe.audio_backend_latency_offset is deprecated. "
"Use pipe.audio_backend_latency_offset_in_seconds instead.");
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;
} else {
inform("The setting pipe.audio_backend_latency_offset is deprecated. "
"Use pipe.audio_backend_latency_offset_in_seconds instead.");
config.audio_backend_latency_offset = 1.0 * value / 44100;
}
}
/* Get the latency offset. */
if (config_lookup_float(config.cfg, "pipe.audio_backend_latency_offset_in_seconds", &dvalue)) {
if ((dvalue < -1.0) || (dvalue > 1.5)) {
die("Invalid pipe audio backend buffer latency offset time \"%f\". It "
"should be between -1.0 and +1.5, default is 0 seconds",
dvalue);
} else {
config.audio_backend_latency_offset = dvalue;
}
}
}
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);
@@ -119,13 +155,11 @@ static int init(int argc, char **argv) {
}
static void deinit(void) {
if (fd > 0)
if (fd > 0)
close(fd);
}
static void help(void) {
printf(" pipe takes 1 argument: the name of the FIFO to write to.\n");
}
static void help(void) { printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); }
audio_output audio_pipe = {.name = "pipe",
.help = &help,
+10 -8
View File
@@ -28,14 +28,14 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "audio.h"
#include "common.h"
#include <memory.h>
#include <pulse/def.h>
#include <pulse/error.h>
#include <pulse/simple.h>
#include <stdio.h>
#include <unistd.h>
#include <memory.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include <pulse/def.h>
#include "common.h"
#include "audio.h"
static pa_simple *pa_dev = NULL;
@@ -110,9 +110,11 @@ static void deinit(void) {
pa_dev = NULL;
}
static void start(int sample_rate) {
if (sample_rate != 44100)
static void start(int sample_rate, int sample_format) {
if (sample_rate != 0)
die("unexpected sample rate!");
if (sample_format != 0)
die("unexpected sample format!");
}
static void play(short buf[], int samples) {
+5 -3
View File
@@ -19,10 +19,10 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "audio.h"
#include <sndio.h>
#include <stdio.h>
#include <unistd.h>
#include <sndio.h>
#include "audio.h"
static struct sio_hdl *sio;
static struct sio_par par;
@@ -56,6 +56,8 @@ static void deinit(void) { sio_close(sio); }
static void start(int sample_rate) {
if (sample_rate != par.rate)
die("unexpected sample rate!");
if (sample_format != 0)
die("unexpected sample format!");
sio_start(sio);
}
@@ -86,4 +88,4 @@ audio_output audio_sndio = {.name = "sndio",
.play = &play,
.volume = &volume,
.parameters = NULL,
.mute= NULL};
.mute = NULL};
+215
View File
@@ -0,0 +1,215 @@
#include "audio.h"
#include "common.h"
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <soundio/soundio.h>
int Fs;
long long starttime, samples_played;
struct SoundIoOutStream *outstream;
struct SoundIo *soundio;
struct SoundIoDevice *device;
struct SoundIoRingBuffer *ring_buffer = NULL;
static int min_int(int a, int b) { return (a < b) ? a : b; }
static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min,
int frame_count_max) {
struct SoundIoChannelArea *areas;
int frame_count;
int err;
char *read_ptr = soundio_ring_buffer_read_ptr(ring_buffer);
int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer);
int fill_count = fill_bytes / outstream->bytes_per_frame;
debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
"outstream->bytes_per_frame: %d",
frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame);
if (frame_count_min > fill_count) {
int frame_count = frame_count_min;
if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) {
debug(0, "[--->>] begin write error: %s", soundio_strerror(err));
}
for (int frame = 0; frame < frame_count; frame += 1) {
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
memset(areas[ch].ptr, 0, outstream->bytes_per_sample);
areas[ch].ptr += areas[ch].step;
}
}
if ((err = soundio_outstream_end_write(outstream)))
debug(0, "[--->>] end write error: %s", soundio_strerror(err));
return;
}
int read_count = min_int(frame_count_max, fill_count);
int frames_left = read_count;
while (frames_left > 0) {
int frame_count = frames_left;
if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count)))
debug(0, "[--->>] begin write error: %s", soundio_strerror(err));
if (frame_count <= 0)
break;
for (int frame = 0; frame < frame_count; frame += 1) {
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
memcpy(areas[ch].ptr, read_ptr, outstream->bytes_per_sample);
areas[ch].ptr += areas[ch].step;
read_ptr += outstream->bytes_per_sample;
}
}
if ((err = soundio_outstream_end_write(outstream)))
debug(0, "[--->>] end write error: %s", soundio_strerror(err));
frames_left -= frame_count;
}
debug(3, "[--->>] Wrote: %d", read_count * outstream->bytes_per_frame);
soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream->bytes_per_frame);
}
static void underflow_callback(struct SoundIoOutStream *outstream) {
static int count = 0;
debug(0, "underflow %d\n", ++count);
}
static int init(int argc, char **argv) {
int err;
config.audio_backend_buffer_desired_length = 2.0;
soundio = soundio_create();
if (!soundio) {
debug(0, "out of memory\n");
return 1;
}
if ((err = soundio_connect_backend(soundio, SoundIoBackendCoreAudio))) {
debug(0, "error connecting: %s", soundio_strerror(err));
return 1;
}
soundio_flush_events(soundio);
int default_out_device_index = soundio_default_output_device_index(soundio);
if (default_out_device_index < 0) {
debug(0, "no output device found");
return 1;
}
device = soundio_get_output_device(soundio, default_out_device_index);
if (!device) {
debug(0, "out of memory");
return 1;
}
debug(0, "Output device: %s\n", device->name);
return 0;
}
static void deinit(void) {
soundio_ring_buffer_destroy(ring_buffer);
soundio_device_unref(device);
soundio_destroy(soundio);
debug(0, "soundio audio deinit\n");
}
static void start(int sample_rate, int sample_format) {
Fs = sample_rate;
starttime = 0;
samples_played = 0;
int err;
debug(1, "soundion rate: %d, format: %d", sample_rate, sample_format);
// soundio_device_sort_channel_layouts(device);
outstream = soundio_outstream_create(device);
outstream->format = SoundIoFormatS16NE;
outstream->sample_rate = sample_rate;
outstream->layout.channel_count = 2;
outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback;
// outstream->software_latency = 0;
if ((err = soundio_outstream_open(outstream))) {
debug(0, "unable to open device: %s", soundio_strerror(err));
}
if (outstream->layout_error)
debug(0, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error));
int capacity = outstream->sample_rate * outstream->bytes_per_frame;
ring_buffer = soundio_ring_buffer_create(soundio, capacity);
if (!ring_buffer)
debug(0, "unable to create ring buffer: out of memory");
char *buf = soundio_ring_buffer_write_ptr(ring_buffer);
memset(buf, 0, capacity);
soundio_ring_buffer_advance_write_ptr(ring_buffer, capacity);
if ((err = soundio_outstream_start(outstream))) {
debug(0, "unable to start outstream: %s", soundio_strerror(err));
}
debug(1, "libsoundio output started\n");
}
static void play(short buf[], int samples) {
int err;
int free_bytes = soundio_ring_buffer_free_count(ring_buffer);
int written_bytes = 0;
int write_bytes = 0;
int left_bytes = samples * outstream->bytes_per_frame;
char *write_ptr = soundio_ring_buffer_write_ptr(ring_buffer);
debug(3, "[<<---] samples: %d , size: %d", samples, left_bytes);
write_bytes = min_int(left_bytes, free_bytes);
debug(3, "[<<---] left_bytes: %d, write_bytes: %d, free_bytes: %d\n", left_bytes, write_bytes,
free_bytes);
if (write_bytes) {
memcpy(write_ptr, (char *)buf, write_bytes);
written_bytes += write_bytes;
soundio_ring_buffer_advance_write_ptr(ring_buffer, write_bytes);
debug(3, "[<<---] Written to buffer : %d\n", written_bytes);
}
}
static void parameters(audio_parameters *info) {
info->minimum_volume_dB = -30.0;
info->maximum_volume_dB = 0.0;
debug(2, "Parameters\n");
debug(2, "Current Volume dB: %f\n", info->current_volume_dB);
debug(2, "Minimum Volume dB: %d\n", info->minimum_volume_dB);
debug(2, "Maximum Volume dB: %d\n", info->maximum_volume_dB);
}
static void stop(void) {
soundio_outstream_destroy(outstream);
soundio_ring_buffer_clear(ring_buffer);
debug(1, "libsoundio output stopped\n");
}
static void flush(void) {
soundio_ring_buffer_clear(ring_buffer);
debug(1, "libsoundio output flushed\n");
}
static void help(void) { printf(" There are no options for libsoundio.\n"); }
audio_output audio_soundio = {.name = "soundio",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.flush = &flush,
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = &parameters,
.mute = NULL};
+67 -34
View File
@@ -27,24 +27,20 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <unistd.h>
#include "audio.h"
#include "common.h"
#include <errno.h>
#include <fcntl.h>
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "common.h"
#include "audio.h"
#include <unistd.h>
static int fd = -1;
static void start(int sample_rate) {
fd = STDOUT_FILENO;
}
static void start(int sample_rate, int sample_format) { fd = STDOUT_FILENO; }
static void play(short buf[], int samples) {
int ignore = write(fd, buf, samples * 4);
}
static void play(short buf[], int samples) { int ignore = write(fd, buf, samples * 4); }
static void stop(void) {
// don't close stdout
@@ -53,27 +49,66 @@ static void stop(void) {
static int init(int argc, char **argv) {
const char *str;
int value;
double dvalue;
config.audio_backend_buffer_desired_length = 44100; // one second.
config.audio_backend_buffer_desired_length = 1.0;
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",
if ((value < 0) || (value > 66150)) {
inform("The setting audio_backend_buffer_desired_length is deprecated. "
"Use audio_backend_buffer_desired_length_in_seconds instead.");
die("Invalid stdout audio backend buffer desired length \"%d\". It "
"should be between 0 and "
"66150, default is 6615",
value);
else
config.audio_backend_buffer_desired_length = value;
} else {
inform("The stdout.setting audio_backend_buffer_desired_length is deprecated. "
"Use stdout.audio_backend_buffer_desired_length_in_seconds instead.");
config.audio_backend_buffer_desired_length = 1.0 * value / 44100;
}
}
/* Get the desired buffer size setting. */
if (config_lookup_float(config.cfg, "stdout.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
if ((dvalue < 0) || (dvalue > 1.5)) {
die("Invalid stdout audio backend buffer desired time \"%f\". It "
"should be between 0 and "
"1.5, default is 0.15 seconds",
dvalue);
} else {
config.audio_backend_buffer_desired_length = dvalue;
}
}
/* 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",
if ((value < -66150) || (value > 66150)) {
inform("The setting stdout.audio_backend_latency_offset is deprecated. "
"Use stdout.audio_backend_latency_offset_in_seconds instead.");
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;
} else {
inform("The setting stdout.audio_backend_latency_offset is deprecated. "
"Use stdout.audio_backend_latency_offset_in_seconds instead.");
config.audio_backend_latency_offset = 1.0 * value / 44100;
}
}
/* Get the latency offset. */
if (config_lookup_float(config.cfg, "stdout.audio_backend_latency_offset_in_seconds",
&dvalue)) {
if ((dvalue < -1.0) || (dvalue > 1.5)) {
die("Invalid stdout audio backend buffer latency offset time \"%f\". It "
"should be between -1.0 and +1.5, default is 0 seconds",
dvalue);
} else {
config.audio_backend_latency_offset = dvalue;
}
}
}
return 0;
@@ -83,19 +118,17 @@ static void deinit(void) {
// don't close stdout
}
static void help(void) {
printf(" stdout takes no arguments\n");
}
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,
.mute = NULL};
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.flush = NULL,
.delay = NULL,
.play = &play,
.volume = NULL,
.parameters = NULL,
.mute = NULL};
+240 -54
View File
@@ -25,20 +25,20 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <popt.h>
#include <memory.h>
#include <poll.h>
#include <popt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <assert.h>
#include "common.h"
#include <assert.h>
#ifdef COMPILE_FOR_OSX
#include <CoreServices/CoreServices.h>
@@ -47,27 +47,36 @@
#endif
#ifdef HAVE_LIBSSL
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#endif
#ifdef HAVE_LIBPOLARSSL
#include <polarssl/version.h>
#include <polarssl/base64.h>
#include <polarssl/x509.h>
#include <polarssl/md.h>
#include "polarssl/entropy.h"
#include "polarssl/ctr_drbg.h"
#include "polarssl/entropy.h"
#include <polarssl/base64.h>
#include <polarssl/md.h>
#include <polarssl/version.h>
#include <polarssl/x509.h>
#if POLARSSL_VERSION_NUMBER >= 0x01030000
#include "polarssl/compat-1.2.h"
#endif
#endif
#include "common.h"
#ifdef HAVE_LIBMBEDTLS
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include <mbedtls/base64.h>
#include <mbedtls/md.h>
#include <mbedtls/version.h>
#include <mbedtls/x509.h>
#endif
#include <libdaemon/dlog.h>
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
@@ -126,6 +135,59 @@ void inform(char *format, ...) {
daemon_log(LOG_INFO, "%s", s);
}
#ifdef HAVE_LIBMBEDTLS
char *base64_enc(uint8_t *input, int length) {
char *buf = NULL;
size_t dlen = 0;
int rc = mbedtls_base64_encode(NULL, 0, &dlen, input, length);
if (rc && (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL))
debug(1, "Error %d getting length of base64 encode.", rc);
else {
buf = (char *)malloc(dlen);
rc = mbedtls_base64_encode((unsigned char *)buf, dlen, &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
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.");
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 = mbedtls_base64_decode(NULL, 0, &dlen, (unsigned char *)inbuf, inbufsize);
if (rc && (rc != MBEDTLS_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.");
else {
rc = mbedtls_base64_decode(buf, dlen, &dlen, (unsigned char *)inbuf, inbufsize);
if (rc != 0)
debug(1, "Error %d in base64_dec.", rc);
}
}
free(inbuf);
}
*outlen = dlen;
return buf;
}
#endif
#ifdef HAVE_LIBPOLARSSL
char *base64_enc(uint8_t *input, int length) {
char *buf = NULL;
@@ -280,6 +342,63 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
}
#endif
#ifdef HAVE_LIBMBEDTLS
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
mbedtls_pk_context pkctx;
mbedtls_rsa_context *trsa;
const char *pers = "rsa_encrypt";
size_t olen = *outlen;
int rc;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers,
strlen(pers));
mbedtls_pk_init(&pkctx);
rc = mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key, sizeof(super_secret_key),
NULL, 0);
if (rc != 0)
debug(1, "Error %d reading the private key.", rc);
uint8_t *outbuf = NULL;
trsa = mbedtls_pk_rsa(pkctx);
switch (mode) {
case RSA_MODE_AUTH:
mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
outbuf = malloc(trsa->len);
rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE,
inlen, input, outbuf);
if (rc != 0)
debug(1, "mbedtls_pk_encrypt error %d.", rc);
*outlen = trsa->len;
break;
case RSA_MODE_KEY:
mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
outbuf = malloc(trsa->len);
rc = mbedtls_rsa_pkcs1_decrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE,
&olen, input, outbuf, trsa->len);
if (rc != 0)
debug(1, "mbedtls_pk_decrypt error %d.", rc);
*outlen = olen;
break;
default:
die("bad rsa mode");
}
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
mbedtls_pk_free(&pkctx);
return outbuf;
}
#endif
#ifdef HAVE_LIBPOLARSSL
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
rsa_context trsa;
@@ -440,7 +559,7 @@ double vol2attn(double vol, long max_db, long min_db) {
double vol_setting = 0;
if ((vol <= 0.0) && (vol >= -30.0)) {
long range_db = max_db - min_db; // this will be a positive nunmber
long range_db = max_db - min_db; // this will be a positive number
// 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.
@@ -479,7 +598,8 @@ uint64_t get_absolute_time_in_fp() {
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; // types okay
time_now_fp =
((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000; // types okay
#endif
#ifdef COMPILE_FOR_OSX
uint64_t time_now_mach;
@@ -514,13 +634,13 @@ uint64_t get_absolute_time_in_fp() {
}
ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
void *ibuf = (void *)buf;
size_t bytes_remaining = count;
int rc = 0;
void *ibuf = (void *)buf;
size_t bytes_remaining = count;
int rc = 0;
struct pollfd ufds[1];
while ((bytes_remaining>0) && (rc==0)) {
// check that we can do some writing
ufds[0].fd = fd;
while ((bytes_remaining > 0) && (rc == 0)) {
// check that we can do some writing
ufds[0].fd = fd;
ufds[0].events = POLLOUT;
rc = poll(ufds, 1, 5000);
if (rc < 0) {
@@ -528,51 +648,117 @@ ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
} else if (rc == 0) {
// warn("non-blocking write timeout waiting for pipe to become ready for writing");
rc = -2;
} else { //rc > 0, implying it might be ready
size_t bytes_written = write(fd,ibuf,bytes_remaining);
if (bytes_written==-1) {
// debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno));
rc = -1;
} else {
ibuf += bytes_written;
bytes_remaining -= bytes_written;
}
} else { // rc > 0, implying it might be ready
size_t bytes_written = write(fd, ibuf, bytes_remaining);
if (bytes_written == -1) {
// debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno));
rc = -1;
} else {
ibuf += bytes_written;
bytes_remaining -= bytes_written;
}
}
}
if (rc==0)
return count-bytes_remaining; // this is just to mimic a normal write/3.
else
return rc;
}
if (rc == 0)
return count - bytes_remaining; // this is just to mimic a normal write/3.
else
return rc;
// return write(fd,buf,count);
}
/* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */
/* from
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
*/
char *str_replace ( const char *string, const char *substr, const char *replacement ){
char *str_replace(const char *string, const char *substr, const char *replacement) {
char *tok = NULL;
char *newstr = NULL;
char *oldstr = NULL;
char *head = NULL;
/* if either substr or replacement is NULL, duplicate string a let caller handle it */
if ( substr == NULL || replacement == NULL ) return strdup (string);
newstr = strdup (string);
if (substr == NULL || replacement == NULL)
return strdup(string);
newstr = strdup(string);
head = newstr;
while ( (tok = strstr ( head, substr ))){
while ((tok = strstr(head, substr))) {
oldstr = newstr;
newstr = malloc ( strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1 );
newstr = malloc(strlen(oldstr) - strlen(substr) + strlen(replacement) + 1);
/*failed to alloc mem, free old string and return NULL */
if ( newstr == NULL ){
free (oldstr);
if (newstr == NULL) {
free(oldstr);
return NULL;
}
memcpy ( newstr, oldstr, tok - oldstr );
memcpy ( newstr + (tok - oldstr), replacement, strlen ( replacement ) );
memcpy ( newstr + (tok - oldstr) + strlen( replacement ), tok + strlen ( substr ), strlen ( oldstr ) - strlen ( substr ) - ( tok - oldstr ) );
memset ( newstr + strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) , 0, 1 );
memcpy(newstr, oldstr, tok - oldstr);
memcpy(newstr + (tok - oldstr), replacement, strlen(replacement));
memcpy(newstr + (tok - oldstr) + strlen(replacement), tok + strlen(substr),
strlen(oldstr) - strlen(substr) - (tok - oldstr));
memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement), 0, 1);
/* move back head right after the last replacement */
head = newstr + (tok - oldstr) + strlen( replacement );
free (oldstr);
head = newstr + (tok - oldstr) + strlen(replacement);
free(oldstr);
}
return newstr;
}
/* from http://burtleburtle.net/bob/rand/smallprng.html */
// typedef uint64_t u8;
typedef struct ranctx {
uint64_t a;
uint64_t b;
uint64_t c;
uint64_t d;
} ranctx;
static struct ranctx rx;
#define rot(x, k) (((x) << (k)) | ((x) >> (64 - (k))))
uint64_t ranval(ranctx *x) {
uint64_t e = x->a - rot(x->b, 7);
x->a = x->b ^ rot(x->c, 13);
x->b = x->c + rot(x->d, 37);
x->c = x->d + e;
x->d = e + x->a;
return x->d;
}
void raninit(ranctx *x, uint64_t seed) {
uint64_t i;
x->a = 0xf1ea5eed, x->b = x->c = x->d = seed;
for (i = 0; i < 20; ++i) {
(void)ranval(x);
}
}
void r64init(uint64_t seed) { raninit(&rx, seed); }
uint64_t r64u() { return (ranval(&rx)); }
int64_t r64i() { return (ranval(&rx) >> 1); }
/* generate an array of 64-bit random numbers */
const int ranarraylength = 1009; // these will be 8-byte numbers.
uint64_t *ranarray;
int ranarraynext;
void ranarrayinit() {
ranarray = (uint64_t *)malloc(ranarraylength * sizeof(uint64_t));
int i;
for (i = 0; i < ranarraylength; i++)
ranarray[i] = r64u();
ranarraynext = 0;
}
uint64_t ranarrayval() {
uint64_t v = ranarray[ranarraynext];
ranarraynext = (ranarraynext++) % ranarraylength;
}
void r64arrayinit() { ranarrayinit(); }
uint64_t ranarray64u() { return (ranarrayval()); }
int64_t ranarray64i() { return (ranarrayval(&rx) >> 1); }
+69 -22
View File
@@ -1,12 +1,12 @@
#ifndef _COMMON_H
#define _COMMON_H
#include <libconfig.h>
#include <stdint.h>
#include <sys/socket.h>
#include <libconfig.h>
#include "config.h"
#include "audio.h"
#include "config.h"
#include "mdns.h"
#if defined(__APPLE__) && defined(__MACH__)
@@ -46,12 +46,34 @@ enum stuffing_type {
enum playback_mode_type {
ST_stereo = 0,
ST_mono,
ST_reverse_stereo,
ST_left_only,
ST_right_only,
} playback_mode_type;
enum decoders_supported_type {
decoder_hammerton = 0,
decoder_apple_alac,
} decoders_supported_type;
// the following enum must _exactly match the snd_pcm_format_t definition in the alsa pcm.h file.
enum sps_format_t {
SPS_FORMAT_UNKNOWN = 0,
SPS_FORMAT_S8,
SPS_FORMAT_U8,
SPS_FORMAT_S16,
SPS_FORMAT_S24,
SPS_FORMAT_S24_3LE,
SPS_FORMAT_S24_3BE,
SPS_FORMAT_S32,
} sps_format_t;
typedef struct {
config_t *cfg;
char *password;
char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running on host %h"
char *service_name; // the name for the shairport service, e.g. "Shairport Sync Version %v running
// on host %h"
#ifdef CONFIG_METADATA
int metadata_enabled;
char *metadata_pipename;
@@ -65,10 +87,13 @@ typedef struct {
int udp_port_base;
int udp_port_range;
int ignore_volume_control;
int no_sync; // disable synchronisation, even if it's available
int no_mmap; // disable use of mmap-based output, even if it's available
int resyncthreshold; // if it get's out of whack my more than this, resync. Zero means never
// resync.
int volume_max_db_set; // set to 1 if a maximum volume db has been set
int volume_max_db;
int no_sync; // disable synchronisation, even if it's available
int no_mmap; // disable use of mmap-based output, even if it's available
double resyncthreshold; // if it get's out of whack my more than this number of seconds, 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.
@@ -79,27 +104,37 @@ typedef struct {
char *mdns_name;
mdns_backend *mdns;
int buffer_start_fill;
int32_t latency;
int32_t userSuppliedLatency; // overrides all other latencies -- use with caution
int32_t iTunesLatency; // supplied with --iTunesLatency option
int32_t AirPlayLatency; // supplied with --AirPlayLatency option
int32_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option
int64_t latency;
int64_t userSuppliedLatency; // overrides all other latencies -- use with caution
int64_t iTunesLatency; // supplied with --iTunesLatency option
int64_t AirPlayLatency; // supplied with --AirPlayLatency option
int64_t ForkedDaapdLatency; // supplied with --ForkedDaapdLatency option
int daemonise;
int statistics_requested,use_negotiated_latencies;
int statistics_requested, use_negotiated_latencies;
enum playback_mode_type playback_mode;
char *cmd_start, *cmd_stop;
int cmd_blocking;
int tolerance; // allow this much drift before attempting to correct it
double tolerance; // allow this much drift before attempting to correct it
enum stuffing_type packet_stuffing;
int decoders_supported;
int use_apple_decoder; // set to 1 if you want to use the apple decoder instead of the original by
// David Hammerton
char *pidfile;
// char *logfile;
// char *errfile;
char *configfile;
char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by default “_raop._tcp.”.
long audio_backend_buffer_desired_length; // this will be the desired number of frames in the
// audio backend buffer -- the DAC buffer for ALSA
long audio_backend_latency_offset; // this will be the offset to compensate for any fixed latency there might be in the audio path
uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's native range.
char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by
// default “_raop._tcp.”.
char *interface; // a string containg the interface name, or NULL if nothing specified
int interface_index; // only valid if the interface string is non-NULL
double audio_backend_buffer_desired_length; // this will be the length in seconds of the
// audio backend buffer -- the DAC buffer for ALSA
double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any
// fixed latency there might be in the audio path
uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
// native range.
enum sps_format_t output_format;
int output_rate;
} shairport_cfg;
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
@@ -110,8 +145,20 @@ 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
/* from http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 */
char *str_replace ( const char *string, const char *substr, const char *replacement );
/* from
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
*/
char *str_replace(const char *string, const char *substr, const char *replacement);
// based on http://burtleburtle.net/bob/rand/smallprng.html
void r64init(uint64_t seed);
uint64_t r64u();
int64_t r64i();
void r64arrayinit();
uint64_t ranarray64u();
int64_t ranarray64i();
int debuglev;
void die(char *format, ...);
@@ -144,7 +191,7 @@ config_t config_file_stuff;
int32_t buffer_occupancy; // allow it to be negative because seq_diff may be negative
int64_t session_corrections;
uint32_t play_segment_reference_frame;
int64_t play_segment_reference_frame;
uint64_t play_segment_reference_frame_remote_time;
void command_start(void);
+45 -68
View File
@@ -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 [-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
\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 [--get-cover-art]\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
@@ -63,7 +63,7 @@ These are the settings available within the \fBgeneral\f1 group:
\fBname=\f1\fI"service_name"\f1\fB;\f1
Use this \fIservice_name\f1 to identify this player in iTunes, etc.
The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "2.8.4" and \fB%V\f1 for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata".
The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
The default is "%H", which is replaced by the hostname with the first letter capitalised.
.TP
@@ -91,11 +91,11 @@ When shairport-sync starts to play audio, it establises three UDP connections to
\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.
\fBdrift_tolerance_in_seconds=\f1\fIseconds\f1\fB;\f1
Allow playback to drift up to \fIseconds\f1 out of exact synchronization before attempting to correct it. The default is 0.002 seconds, i.e. 2 milliseconds. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections. This setting replaces the deprecated \fBdrift\f1 setting.
.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 \fI0\f1 to disable resynchronisation.
\fBresync_threshold_in_seconds=\f1\fIthreshold\f1\fB;\f1
Resynchronise if timings differ by more than \fIthreshold\f1 seconds. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 0.050 seconds, i.e. 50 milliseconds. Specify 0.0 to disable resynchronisation. This setting replaces the deprecated \fBresync_threshold\f1 setting.
.TP
\fBlog_verbosity=\f1\fI0\f1\fB;\f1
Use this to specify how much debugging information should be output or logged. The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
@@ -103,6 +103,9 @@ Use this to specify how much debugging information should be output or logged. T
\fBignore_volume_control=\f1\fI"choice"\f1\fB;\f1
Set this \fIchoice\f1 to \fI"yes"\f1 if you want the volume to be at 100% no matter what the source's volume control is set to. This might be useful if you want to set the volume on the output device, independently of the setting at the source. The default is \fI"no"\f1.
.TP
\fBvolume_max_db=\f1\fIdBvalue\f1\fB;\f1
Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB.
.TP
\fBvolume_range_db=\f1\fIdBvalue\f1\fB;\f1
Use this \fIdBvalue\f1 to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume.
@@ -118,7 +121,13 @@ If you omit this setting, the native range of the mixer is used.
Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is \fI"_raop._tcp"\f1.
.TP
\fBplayback_mode=\f1\fI"mode"\f1\fB;\f1
The \fImode\f1 can be \fI"stereo"\f1 or \fI"mono"\f1. Default is \fI"stereo"\f1.
The \fImode\f1 can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
.TP
\fBinterface=\f1\fI"name"\f1\fB;\f1
Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
.TP
\fBalac_decoder=\f1\fI"decodername"\f1\fB;\f1
This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work.
.TP
\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:
@@ -134,11 +143,17 @@ This setting is deprecated and is ignored. For your information, its functionali
\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 \fI-4410\f1. Default is \fI0\f1.
\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset\f1\fB;\f1
Set this \fIoffset\f1, in seconds, to compensate for a fixed delay in the audio back end. For example, if the output device delays by 100 ms, set this to -0.1.
.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 \fI6,615\f1 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.
\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIlength\f1\fB;\f1
Use this to set the desired length, in seconds, of the queue of audio frames in the output device's hardware output buffer. The default is 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
\fBoutput_rate=\f1\fIframe rate\f1\fB;\f1
Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available.
.TP
\fBoutput_format=\f1\fI"format"\f1\fB;\f1
Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify. "S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16". If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
.TP
\fBdisable_synchronization=\f1\fI"no"\f1\fB;\f1
This is an advanced setting and is for debugging only. Set to \fI"yes"\f1 to disable synchronization. Default is \fI"no"\f1. If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to audio buffer overflow or underflow.
@@ -157,44 +172,44 @@ These settings are for the PIPE backend, used to route audio to a named unix pip
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.
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_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\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 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.
\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1
Packets of audio frames are written to the pipe 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 the pipe 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.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.
\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1
Use this setting, in seconds, 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_in_seconds\f1 setting of 1.0, 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 1 second. 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 1.0 seconds.
.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, interleaved stereo.
These settings are for the STDOUT backend, used to route audio to standard output ("stdout"). The audio is in raw CD audio format, usually PCM 16 bit little endian, 44,100 samples per second, interleaved 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.
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_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\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.
\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\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 -0.1. Default setting is 0.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.
\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\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_in_seconds\f1 setting of 1.0, 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 1 second. 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 1.0 seconds.
.TP
\fB"AO" SETTINGS\f1
These settings are for the AO backend, used for the libao audio library.
There are two settings affecting timing. The \fIaudio_backend_latency_offset\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length\f1 setting affects the nominal output buffer size.
There are two settings affecting timing. The \fIaudio_backend_latency_offset_in_seconds\f1 affects precisely when the first audio packet is sent and the \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting affects the nominal output buffer size.
These are the settings available within the \fBao\f1 group:
.TP
\fBaudio_backend_latency_offset=\f1\fIoffset_in_frames\f1\fB;\f1
Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.
\fBaudio_backend_latency_offset_in_seconds=\f1\fIoffset_in_seconds\f1\fB;\f1
Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played. You can offset the time of initial audio output relative to its nominal time using this setting. For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.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 libao system. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length\f1 setting of 44100, send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames. Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 44,100 frames.
\fBaudio_backend_buffer_desired_length_in_seconds=\f1\fIbuffer_length_in_seconds\f1\fB;\f1
Use this setting, in seconds, 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 libao system. For example, if you send the first packet of audio exactly when it is due and, using a \fIaudio_backend_buffer_desired_length_in_seconds\f1 setting of 1.0, 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 1 second. Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow -- shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 1.0 seconds.
.TP
\fB"METADATA" SETTINGS\f1
shairport-sync can process metadata provided by the source, such as Track Number, Album Name, cover art, etc. and can provide additional metadata such as volume level, pause/resume, etc. It sends the metadata to a pipe, by default \fI/tmp/shairport-sync-metadata\f1. To process metadata, shairport-sync must have been compiled with metadata support included. You can check that this is so by running the command \fB$ shairport-sync -V\f1; the identification string will contain the word \fBmetadata\f1.
@@ -238,29 +253,6 @@ If \fBchoice\f1 is set to "yes", then another source will be able to interrupt a
.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"LATENCIES" SETTINGS\f1
The latencies settings are now deprecated. Do not use them for new installations. They will be removed from a future version of shairport-sync.
Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer.
shairport-sync now sets latencies automatically using information supplied by the source, typically either 88,200 or 99,577 frames.
The following relates to the old scheme of using fixed latencies, which ignored the latency information supplied by the source. There are four default latency settings. 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.
Note: If you are thinking of changing latencies to compensate for a delay in the audio output device, then instead of changing these individual latencies, use 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.
.SH OPTIONS
This section is about the command-line options available in shairport-sync.
@@ -273,15 +265,10 @@ There are two kinds of command-line options for shairport-sync: regular \fBprogr
These command-line options are used by shairport-sync itself.
.TP
\fB-a \f1\fIservice name\f1\fB | --name=\f1\fIservice name\f1
Use this \fIservice name\f1 to identify this player in iTunes, etc. The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "2.8.4" and \fB%V\f1 for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata".
Use this \fIservice name\f1 to identify this player in iTunes, etc. The following substitutions are allowed: \fB%h\f1 for the computer's hostname, \fB%H\f1 for the computer's hostname with the first letter capitalised (ASCII only), \fB%v\f1 for the shairport-sync version number, e.g. "3.0.1" and \fB%V\f1 for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
The default is "%H", which is replaced by the hostname with the first letter capitalised.
.TP
\fB-A | --AirPlayLatency=\f1\fIlatency\f1
Use this \fIlatency\f1, in frames, for audio streamed from an AirPlay device. The default is 88,200 frames, where there are 44,100 frames to the second.
Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
.TP
\fB-B \f1\fIprogram\f1\fB | --on-start=\f1\fIprogram\f1
Execute \fIprogram\f1 when playback is about to begin. Specify the full path to the program, e.g. \fI/usr/bin/logger\f1. Executable scripts can be used, but they must have \fI#!/bin/sh\f1 (or whatever is appropriate) in the headline.
@@ -303,11 +290,6 @@ Execute \fIprogram\f1 when playback has ended. Specify the full path to the prog
If you want shairport-sync to wait until the command has completed before continuing, select the \fB-w\f1 option as well.
.TP
\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.
Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
.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.
@@ -316,11 +298,6 @@ Please note that cover art data may be very large, and may place too great a bur
\fB-h | --help\f1
Print brief help message and exit.
.TP
\fB-i | --iTunesLatency=\f1\fIlatency\f1
Use this \fIlatency\f1, in frames, for audio streamed from an iTunes source, where iTunes is Version 10 or later. The default is 99,400 frames, where there are 44,100 frames to the second. If the source is iTunes but is earler than Version 10, the \fIdefault latency\f1 is used (see the \fB-L\f1 option). Some third party programs masquerade as older versions of iTunes.
Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
.TP
\fB-k | --kill\f1
Kill the shairport-sync daemon and exit. (Requires that the daemon has written its PID to an agreed file -- see the \fB-d\f1 option).
.TP
@@ -350,7 +327,7 @@ Reconnect the shairport-sync daemon to the output device and exit. It may take a
Please note that this feature is deprecated and will be removed in a future version of shairport-sync.
.TP
\fB-r \f1\fIthreshold\f1\fB | --resync=\f1\fIthreshold\f1
Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation.
Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the output timing differs from the source timing by more than the threshold, output will be muted and a full resynchronisation will occur. The default threshold is 2,205 frames, i.e. 50 milliseconds. Specify \fB0\f1 to disable resynchronisation. This setting is deprecated and will be removed in a future version of shairport-sync.
.TP
\fB--statistics\f1
Print some statistics in the standard output, or in the logfile if in daemon mode.
@@ -364,7 +341,7 @@ Exit play mode if the stream disappears for more than \fItimeout\f1 seconds.
When shairport-sync plays an audio stream, it starts a play session and will return a busy signal to any other sources that attempt to use it. If the audio stream disappears for longer than \fItimeout\f1 seconds, the play session will be terminated. If you specify a timeout time of \fB0\f1, shairport-sync will never signal that it is busy and will not prevent other sources from "barging in" on an existing play session. The default value is 120 seconds.
.TP
\fB--tolerance=\f1\fIframes\f1
Allow playback to be up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections.
Allow playback to be up to \fIframes\f1 out of exact synchronization before attempting to correct it. The default is 88 frames, i.e. 2 ms. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fB--statistics\f1 option to monitor correction levels. Corrections should not greatly exceed net corrections. This setting is deprecated and will be removed in a future version of shairport-sync.
.TP
\fB-V | --version\f1
Print version information and exit.
+78 -118
View File
@@ -4,7 +4,7 @@
<!--
This man page source file is part of shairport-sync.
Copyright (c) Mike Brady 2014-2016
Copyright (c) Mike Brady 2014-2017
All rights reserved.
@@ -41,9 +41,7 @@
<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>
@@ -126,8 +124,8 @@
<p>The following substitutions are allowed:
<opt>%h</opt> for the computer's hostname,
<opt>%H</opt> for the computer's hostname with the first letter capitalised (ASCII only),
<opt>%v</opt> for the shairport-sync version number, e.g. "2.8.4" and
<opt>%V</opt> for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata".</p>
<opt>%v</opt> for the shairport-sync version number, e.g. "3.0.1" and
<opt>%V</opt> for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".</p>
<p>The default is "%H", which is replaced by the hostname with the first letter capitalised.</p>
</optdesc>
</option>
@@ -177,20 +175,22 @@
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.
<p><opt>drift_tolerance_in_seconds=</opt><arg>seconds</arg><opt>;</opt></p>
<optdesc>Allow playback to drift up to <arg>seconds</arg> out of exact synchronization before attempting to correct it.
The default is 0.002 seconds, i.e. 2 milliseconds. The smaller the tolerance, the more likely it is that overcorrection will occur.
Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the <opt>statistics</opt> setting to
monitor correction levels. Corrections should not greatly exceed net corrections.
monitor correction levels. Corrections should not greatly exceed net corrections. This setting replaces the deprecated <opt>drift</opt> setting.
</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.
<p><opt>resync_threshold_in_seconds=</opt><arg>threshold</arg><opt>;</opt></p>
<optdesc>Resynchronise if timings differ by more than <arg>threshold</arg> seconds.
If the output timing differs from the source timing by more than
the threshold, output will be muted and a full resynchronisation
will occur. The default threshold is 2,205 frames, i.e. 50
milliseconds. Specify <arg>0</arg> to disable resynchronisation.</optdesc>
will occur. The default threshold is 0.050 seconds, i.e. 50
milliseconds. Specify 0.0 to disable resynchronisation.
This setting replaces the deprecated <opt>resync_threshold</opt> setting.
</optdesc>
</option>
<option>
<p><opt>log_verbosity=</opt><arg>0</arg><opt>;</opt></p>
@@ -202,6 +202,12 @@
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 <arg>"no"</arg>.</optdesc>
</option>
<option>
<p><opt>volume_max_db=</opt><arg>dBvalue</arg><opt>;</opt></p>
<optdesc><p>Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB.</p>
</optdesc>
</option>
<option>
<p><opt>volume_range_db=</opt><arg>dBvalue</arg><opt>;</opt></p>
<optdesc><p>Use this <arg>dBvalue</arg> to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume.</p>
@@ -220,7 +226,6 @@
will be combined to give the specified range.</p>
<p>If you omit this setting, the native range of the mixer is used.</p></optdesc>
</option>
<option>
<p><opt>regtype=</opt><arg>"regTypeString"</arg><opt>;</opt></p>
<optdesc>Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is <arg>"_raop._tcp"</arg>.</optdesc>
@@ -228,10 +233,19 @@
<option>
<p><opt>playback_mode=</opt><arg>"mode"</arg><opt>;</opt></p>
<optdesc>The <arg>mode</arg> can be <arg>"stereo"</arg> or <arg>"mono"</arg>. Default is <arg>"stereo"</arg>.</optdesc>
<optdesc>The <arg>mode</arg> can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".</optdesc>
</option>
<option>
<p><opt>interface=</opt><arg>"name"</arg><opt>;</opt></p>
<optdesc>Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.</optdesc>
</option>
<option>
<p><opt>alac_decoder=</opt><arg>"decodername"</arg><opt>;</opt></p>
<optdesc>This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work.</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.
@@ -259,19 +273,31 @@
<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 <arg>-4410</arg>. Default is <arg>0</arg>.</optdesc>
<p><opt>audio_backend_latency_offset_in_seconds=</opt><arg>offset</arg><opt>;</opt></p>
<optdesc>Set this <arg>offset</arg>, in seconds, to compensate for a fixed delay in the audio back end.
For example, if the output device delays by 100 ms, set this to -0.1.</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 <arg>6,615</arg> frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines.
<p><opt>audio_backend_buffer_desired_length_in_seconds=</opt><arg>length</arg><opt>;</opt></p>
<optdesc>Use this to set the desired length, in seconds, of the queue of audio frames in the output device's hardware output buffer.
The default is 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>output_rate=</opt><arg>frame rate</arg><opt>;</opt></p>
<optdesc>Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available.
</optdesc>
</option>
<option>
<p><opt>output_format=</opt><arg>"format"</arg><opt>;</opt></p>
<optdesc>Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32". The device must have the capability to accept the format you specify. "S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16". If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
</optdesc>
</option>
<option>
<p><opt>disable_synchronization=</opt><arg>"no"</arg><opt>;</opt></p>
<optdesc>This is an advanced setting and is for debugging only. Set to <arg>"yes"</arg> to disable synchronization. Default is <arg>"no"</arg>.
@@ -297,9 +323,9 @@
interleaved 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
a program to play an audio stream such as <opt>aplay</opt>. The <arg>audio_backend_latency_offset_in_seconds</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>
and the <arg>audio_backend_buffer_desired_length_in_seconds</arg> setting affects the nominal output buffer size.</p>
<p>These are the settings available within the <opt>pipe</opt> group:</p>
<option>
@@ -311,75 +337,75 @@
</option>
<option>
<p><opt>audio_backend_latency_offset=</opt><arg>offset_in_frames</arg><opt>;</opt></p>
<p><opt>audio_backend_latency_offset_in_seconds=</opt><arg>offset_in_seconds</arg><opt>;</opt></p>
<optdesc>
Packets of audio frames are written to the pipe 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 the pipe 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.</optdesc>
For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.</optdesc>
</option>
<option>
<p><opt>audio_backend_buffer_desired_length=</opt><arg>buffer_length_in_frames</arg><opt>;</opt></p>
<p><opt>audio_backend_buffer_desired_length_in_seconds=</opt><arg>buffer_length_in_seconds</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.
Use this setting, in seconds, 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_in_seconds</arg> setting of 1.0,
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 1 second.
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.
shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 1.0 seconds.
</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, interleaved stereo.</p>
The audio is in raw CD audio format, usually PCM 16 bit little endian, 44,100 samples per second, interleaved 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>
a program to play an audio stream such as <opt>aplay</opt>. The <arg>audio_backend_latency_offset_in_seconds</arg> affects precisely when the first audio packet is sent
and the <arg>audio_backend_buffer_desired_length_in_seconds</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>
<p><opt>audio_backend_latency_offset_in_seconds=</opt><arg>offset_in_seconds</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>
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.</optdesc>
</option>
<option>
<p><opt>audio_backend_buffer_desired_length=</opt><arg>buffer_length_in_frames</arg><opt>;</opt></p>
<p><opt>audio_backend_buffer_desired_length_in_seconds=</opt><arg>buffer_length_in_seconds</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.
For example, if you send the first packet of audio exactly when it is due and, using a <arg>audio_backend_buffer_desired_length_in_seconds</arg> setting of 1.0,
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 1 second.
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.
shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 1.0 seconds.
</optdesc>
</option>
<option><p><opt>"AO" SETTINGS</opt></p></option>
<p>These settings are for the AO backend, used for the libao audio library.</p>
<p>There are two settings affecting timing. The <arg>audio_backend_latency_offset</arg> affects precisely when the first audio packet is sent
and the <arg>audio_backend_buffer_desired_length</arg> setting affects the nominal output buffer size.</p>
<p>There are two settings affecting timing. The <arg>audio_backend_latency_offset_in_seconds</arg> affects precisely when the first audio packet is sent
and the <arg>audio_backend_buffer_desired_length_in_seconds</arg> setting affects the nominal output buffer size.</p>
<p>These are the settings available within the <opt>ao</opt> group:</p>
<option>
<p><opt>audio_backend_latency_offset=</opt><arg>offset_in_frames</arg><opt>;</opt></p>
<p><opt>audio_backend_latency_offset_in_seconds=</opt><arg>offset_in_seconds</arg><opt>;</opt></p>
<optdesc>
Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played.
You can offset the time of initial audio output relative to its nominal time using this setting.
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.</optdesc>
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.</optdesc>
</option>
<option>
<p><opt>audio_backend_buffer_desired_length=</opt><arg>buffer_length_in_frames</arg><opt>;</opt></p>
<p><opt>audio_backend_buffer_desired_length_in_seconds=</opt><arg>buffer_length_in_seconds</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
Use this setting, in seconds, 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 libao system.
For example, if you send the first packet of audio exactly when it is due and, using a <arg>audio_backend_buffer_desired_length</arg> setting of 44100,
send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames.
For example, if you send the first packet of audio exactly when it is due and, using a <arg>audio_backend_buffer_desired_length_in_seconds</arg> setting of 1.0,
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 1 second.
Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow --
shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 44,100 frames.
shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 1.0 seconds.
</optdesc>
</option>
@@ -453,39 +479,6 @@
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>"LATENCIES" SETTINGS</opt></p></option>
<p>The latencies settings are now deprecated. Do not use them for new installations. They will be removed from a future version of shairport-sync.</p>
<p>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.</p>
<p>shairport-sync now sets latencies automatically using information supplied by the source, typically either 88,200 or 99,577 frames.</p>
<p>The following relates to the old scheme of using fixed latencies, which ignored the latency information supplied by the source.
There are four default latency settings. 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>Note: If you are thinking of changing latencies to compensate for a delay in the audio output device, then
instead of changing these individual latencies, use 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>
</section>
@@ -517,23 +510,12 @@
<p>The following substitutions are allowed:
<opt>%h</opt> for the computer's hostname,
<opt>%H</opt> for the computer's hostname with the first letter capitalised (ASCII only),
<opt>%v</opt> for the shairport-sync version number, e.g. "2.8.4" and
<opt>%V</opt> for the shairport-sync version string, e.g. "2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata".</p>
<opt>%v</opt> for the shairport-sync version number, e.g. "3.0.1" and
<opt>%V</opt> for the shairport-sync version string, e.g. "3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".</p>
<p>The default is "%H", which is replaced by the hostname with the first letter capitalised.</p>
</p></optdesc>
</option>
<option>
<p><opt>-A | --AirPlayLatency=</opt><arg>latency</arg></p>
<optdesc><p>
Use this <arg>latency</arg>, in frames, for audio streamed from an AirPlay
device. The default is 88,200 frames, where there are 44,100
frames to the second.
</p>
<p>Please note that this feature is deprecated and will be removed in a future version of shairport-sync.</p>
</optdesc>
</option>
<option>
<p><opt>-B </opt><arg>program</arg><opt> | --on-start=</opt><arg>program</arg></p>
<optdesc><p>
@@ -589,17 +571,6 @@
</p></optdesc>
</option>
<option>
<p><opt>--forkedDaapdLatency=</opt><arg>latency</arg></p>
<optdesc><p>
Use this <arg>latency</arg>, 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.
</p>
<p>Please note that this feature is deprecated and will be removed in a future version of shairport-sync.</p>
</optdesc>
</option>
<option>
<p><opt>--get-coverart</opt></p>
<optdesc><p>
@@ -618,17 +589,6 @@
</p></optdesc>
</option>
<option>
<p><opt>-i | --iTunesLatency=</opt><arg>latency</arg></p>
<optdesc><p>
Use this <arg>latency</arg>, in frames, for audio streamed from an iTunes
source, where iTunes is Version 10 or later. The default is 99,400 frames, where there are 44,100
frames to the second. If the source is iTunes but is earler than Version 10, the <arg>default latency</arg> is used (see the <opt>-L</opt> option). Some third party programs masquerade as older versions of iTunes.
</p>
<p>Please note that this feature is deprecated and will be removed in a future version of shairport-sync.</p>
</optdesc>
</option>
<option>
<p><opt>-k | --kill</opt></p>
<optdesc><p>
@@ -710,7 +670,7 @@
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 <opt>0</opt> to disable resynchronisation.
milliseconds. Specify <opt>0</opt> to disable resynchronisation. This setting is deprecated and will be removed in a future version of shairport-sync.
</p></optdesc>
</option>
@@ -758,7 +718,7 @@
Allow playback to be 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> option to
monitor correction levels. Corrections should not greatly exceed net corrections.
monitor correction levels. Corrections should not greatly exceed net corrections. This setting is deprecated and will be removed in a future version of shairport-sync.
</p></optdesc>
</option>
+77 -117
View File
@@ -12,9 +12,7 @@
<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>
@@ -107,8 +105,8 @@
<p>The following substitutions are allowed:
<b>%h</b> for the computer's hostname,
<b>%H</b> for the computer's hostname with the first letter capitalised (ASCII only),
<b>%v</b> for the shairport-sync version number, e.g. &quot;2.8.4&quot; and
<b>%V</b> for the shairport-sync version string, e.g. &quot;2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata&quot;.</p>
<b>%v</b> for the shairport-sync version number, e.g. &quot;3.0.1&quot; and
<b>%V</b> for the shairport-sync version string, e.g. &quot;3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc&quot;.</p>
<p>The default is &quot;%H&quot;, which is replaced by the hostname with the first letter capitalised.</p>
@@ -158,20 +156,22 @@
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.
<p><b>drift_tolerance_in_seconds=</b><em>seconds</em><b>;</b></p>
Allow playback to drift up to <em>seconds</em> out of exact synchronization before attempting to correct it.
The default is 0.002 seconds, i.e. 2 milliseconds. The smaller the tolerance, the more likely it is that overcorrection will occur.
Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the <b>statistics</b> setting to
monitor correction levels. Corrections should not greatly exceed net corrections.
monitor correction levels. Corrections should not greatly exceed net corrections. This setting replaces the deprecated <b>drift</b> setting.
<p><b>resync_threshold=</b><em>threshold</em><b>;</b></p>
Resynchronise if timings differ by more than <em>threshold</em> frames.
<p><b>resync_threshold_in_seconds=</b><em>threshold</em><b>;</b></p>
Resynchronise if timings differ by more than <em>threshold</em> seconds.
If the output timing differs from the source timing by more than
the threshold, output will be muted and a full resynchronisation
will occur. The default threshold is 2,205 frames, i.e. 50
milliseconds. Specify <em>0</em> to disable resynchronisation.
will occur. The default threshold is 0.050 seconds, i.e. 50
milliseconds. Specify 0.0 to disable resynchronisation.
This setting replaces the deprecated <b>resync_threshold</b> setting.
<p><b>log_verbosity=</b><em>0</em><b>;</b></p>
@@ -184,6 +184,12 @@
<p><b>volume_max_db=</b><em>dBvalue</em><b>;</b></p>
<p>Specify the maximum output level to be used with the hardware mixer, if used. If no hardware mixed is used, this setting speciies the maximum setting permissible in the software mixer, which has an attenuation of from 0.0 dB down to -96.3 dB.</p>
<p><b>volume_range_db=</b><em>dBvalue</em><b>;</b></p>
<p>Use this <em>dBvalue</em> to reduce or increase the attenuation range, in decibels, between the minimum and maximum volume.</p>
@@ -202,17 +208,25 @@
<p>If you omit this setting, the native range of the mixer is used.</p>
<p><b>regtype=</b><em>&quot;regTypeString&quot;</em><b>;</b></p>
Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is <em>&quot;_raop._tcp&quot;</em>.
<p><b>playback_mode=</b><em>&quot;mode&quot;</em><b>;</b></p>
The <em>mode</em> can be <em>&quot;stereo&quot;</em> or <em>&quot;mono&quot;</em>. Default is <em>&quot;stereo&quot;</em>.
The <em>mode</em> can be &quot;stereo&quot;, &quot;mono&quot;, &quot;reverse stereo&quot;, &quot;both left&quot; or &quot;both right&quot;. Default is &quot;stereo&quot;.
<p><b>interface=</b><em>&quot;name&quot;</em><b>;</b></p>
Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
<p><b>alac_decoder=</b><em>&quot;decodername&quot;</em><b>;</b></p>
This can be &quot;hammerton&quot; or &quot;apple&quot;. This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting &quot;--with-apple-alac&quot; and the Apple ALAC decoder library must be present for this to work.
<p><b>&quot;ALSA&quot; SETTINGS</b></p>
<p>These settings are for the ALSA back end, used to communicate with audio output devices in the ALSA system.
@@ -240,20 +254,32 @@
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 <em>-4410</em>. Default is <em>0</em>.
<p><b>audio_backend_latency_offset_in_seconds=</b><em>offset</em><b>;</b></p>
Set this <em>offset</em>, in seconds, to compensate for a fixed delay in the audio back end.
For example, if the output device delays by 100 ms, set this to -0.1.
<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 <em>6,615</em> frames, or 0.15 seconds. If set too small, buffer underflow may occur on low-powered machines.
<p><b>audio_backend_buffer_desired_length_in_seconds=</b><em>length</em><b>;</b></p>
Use this to set the desired length, in seconds, of the queue of audio frames in the output device's hardware output buffer.
The default is 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>output_rate=</b><em>frame rate</em><b>;</b></p>
Use this setting to specify the frame rate to output to the ALSA device. Allowable values are 44100 (default), 88200, 176400 and 352800. The device must have the capability to accept the format you specify. There is no particular reason to use anything other than 44100 if it is available.
<p><b>output_format=</b><em>&quot;format&quot;</em><b>;</b></p>
Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are &quot;U8&quot;, &quot;S8&quot;, &quot;S16&quot;, &quot;S24&quot;, &quot;S24_3LE&quot;, &quot;S24_3BE&quot; or &quot;S32&quot;. The device must have the capability to accept the format you specify. &quot;S&quot; means signed; &quot;U&quot; means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is &quot;S16&quot;. If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
<p><b>disable_synchronization=</b><em>&quot;no&quot;</em><b>;</b></p>
This is an advanced setting and is for debugging only. Set to <em>&quot;yes&quot;</em> to disable synchronization. Default is <em>&quot;no&quot;</em>.
If you use it to disable synchronisation, then sooner or later you'll experience audio glitches due to
@@ -278,9 +304,9 @@
interleaved 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
a program to play an audio stream such as <b>aplay</b>. The <em>audio_backend_latency_offset_in_seconds</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>
and the <em>audio_backend_buffer_desired_length_in_seconds</em> setting affects the nominal output buffer size.</p>
<p>These are the settings available within the <b>pipe</b> group:</p>
@@ -292,75 +318,75 @@
<p><b>audio_backend_latency_offset=</b><em>offset_in_frames</em><b>;</b></p>
<p><b>audio_backend_latency_offset_in_seconds=</b><em>offset_in_seconds</em><b>;</b></p>
Packets of audio frames are written to the pipe 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 the pipe 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.
For example to send an audio stream to the pipe 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
<p><b>audio_backend_buffer_desired_length=</b><em>buffer_length_in_frames</em><b>;</b></p>
<p><b>audio_backend_buffer_desired_length_in_seconds=</b><em>buffer_length_in_seconds</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.
Use this setting, in seconds, 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_in_seconds</em> setting of 1.0,
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 1 second.
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.
shairport-sync performs no stuffing or interpolation when writing to a pipe. Default setting is 1.0 seconds.
<p><b>&quot;STDOUT&quot; SETTINGS</b></p>
<p>These settings are for the STDOUT backend, used to route audio to standard output (&quot;stdout&quot;).
The audio is in raw CD audio format: PCM 16 bit little endian, 44,100 samples per second, interleaved stereo.</p>
The audio is in raw CD audio format, usually PCM 16 bit little endian, 44,100 samples per second, interleaved 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>
a program to play an audio stream such as <b>aplay</b>. The <em>audio_backend_latency_offset_in_seconds</em> affects precisely when the first audio packet is sent
and the <em>audio_backend_buffer_desired_length_in_seconds</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>
<p><b>audio_backend_latency_offset_in_seconds=</b><em>offset_in_seconds</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.
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
<p><b>audio_backend_buffer_desired_length=</b><em>buffer_length_in_frames</em><b>;</b></p>
<p><b>audio_backend_buffer_desired_length_in_seconds=</b><em>buffer_length_in_seconds</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.
For example, if you send the first packet of audio exactly when it is due and, using a <em>audio_backend_buffer_desired_length_in_seconds</em> setting of 1.0,
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 1 second.
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.
shairport-sync performs no stuffing or interpolation when writing to stdout. Default setting is 1.0 seconds.
<p><b>&quot;AO&quot; SETTINGS</b></p>
<p>These settings are for the AO backend, used for the libao audio library.</p>
<p>There are two settings affecting timing. The <em>audio_backend_latency_offset</em> affects precisely when the first audio packet is sent
and the <em>audio_backend_buffer_desired_length</em> setting affects the nominal output buffer size.</p>
<p>There are two settings affecting timing. The <em>audio_backend_latency_offset_in_seconds</em> affects precisely when the first audio packet is sent
and the <em>audio_backend_buffer_desired_length_in_seconds</em> setting affects the nominal output buffer size.</p>
<p>These are the settings available within the <b>ao</b> group:</p>
<p><b>audio_backend_latency_offset=</b><em>offset_in_frames</em><b>;</b></p>
<p><b>audio_backend_latency_offset_in_seconds=</b><em>offset_in_seconds</em><b>;</b></p>
Packets of audio frames are written to the libao system synchronously -- that is, they are written at exactly the time they should be played.
You can offset the time of initial audio output relative to its nominal time using this setting.
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -4410. Default setting is 0.
For example to send an audio stream to stdout 100 milliseconds before it is due to be played, set this to -0.1. Default setting is 0.0.
<p><b>audio_backend_buffer_desired_length=</b><em>buffer_length_in_frames</em><b>;</b></p>
<p><b>audio_backend_buffer_desired_length_in_seconds=</b><em>buffer_length_in_seconds</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
Use this setting, in seconds, 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 libao system.
For example, if you send the first packet of audio exactly when it is due and, using a <em>audio_backend_buffer_desired_length</em> setting of 44100,
send subsequent packets of audio a second before they are due to be played, they will be buffered in the stdout reader's buffer, giving it a nominal buffer size of 44,100 frames.
For example, if you send the first packet of audio exactly when it is due and, using a <em>audio_backend_buffer_desired_length_in_seconds</em> setting of 1.0,
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 1 second.
Note that if the libao system consumes audio packets faster or slower than they are supplied, the buffer will eventually empty or overflow --
shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 44,100 frames.
shairport-sync performs no stuffing or interpolation when writing to libao. Default setting is 1.0 seconds.
@@ -434,39 +460,6 @@
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>&quot;LATENCIES&quot; SETTINGS</b></p>
<p>The latencies settings are now deprecated. Do not use them for new installations. They will be removed from a future version of shairport-sync.</p>
<p>Latency is the exact time from a sound signal's original timestamp until that signal actually &quot;appears&quot; 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.</p>
<p>shairport-sync now sets latencies automatically using information supplied by the source, typically either 88,200 or 99,577 frames.</p>
<p>The following relates to the old scheme of using fixed latencies, which ignored the latency information supplied by the source.
There are four default latency settings. One latency matches the latency used by recent versions of
iTunes when playing audio and another matches the latency
used by so-called &quot;AirPlay&quot; 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>Note: If you are thinking of changing latencies to compensate for a delay in the audio output device, then
instead of changing these individual latencies, use 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.
@@ -502,24 +495,13 @@
<p>The following substitutions are allowed:
<b>%h</b> for the computer's hostname,
<b>%H</b> for the computer's hostname with the first letter capitalised (ASCII only),
<b>%v</b> for the shairport-sync version number, e.g. &quot;2.8.4&quot; and
<b>%V</b> for the shairport-sync version string, e.g. &quot;2.8.4-OpenSSL-Avahi-ALSA-soxr-metadata&quot;.</p>
<b>%v</b> for the shairport-sync version number, e.g. &quot;3.0.1&quot; and
<b>%V</b> for the shairport-sync version string, e.g. &quot;3.0.1-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc&quot;.</p>
<p>The default is &quot;%H&quot;, which is replaced by the hostname with the first letter capitalised.</p>
</p>
<p><b>-A | --AirPlayLatency=</b><em>latency</em></p>
<p>
Use this <em>latency</em>, in frames, for audio streamed from an AirPlay
device. The default is 88,200 frames, where there are 44,100
frames to the second.
</p>
<p>Please note that this feature is deprecated and will be removed in a future version of shairport-sync.</p>
<p><b>-B </b><em>program</em><b> | --on-start=</b><em>program</em></p>
<p>
Execute <em>program</em> when playback is about to begin. Specify the
@@ -575,17 +557,6 @@
<p><b>--forkedDaapdLatency=</b><em>latency</em></p>
<p>
Use this <em>latency</em>, 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.
</p>
<p>Please note that this feature is deprecated and will be removed in a future version of shairport-sync.</p>
<p><b>--get-coverart</b></p>
<p>
This option requires the <b>--meta-dir</b> option to be set, and enables
@@ -604,17 +575,6 @@
<p><b>-i | --iTunesLatency=</b><em>latency</em></p>
<p>
Use this <em>latency</em>, in frames, for audio streamed from an iTunes
source, where iTunes is Version 10 or later. The default is 99,400 frames, where there are 44,100
frames to the second. If the source is iTunes but is earler than Version 10, the <em>default latency</em> is used (see the <b>-L</b> option). Some third party programs masquerade as older versions of iTunes.
</p>
<p>Please note that this feature is deprecated and will be removed in a future version of shairport-sync.</p>
<p><b>-k | --kill</b></p>
<p>
Kill the shairport-sync daemon and exit. (Requires that the daemon has
@@ -695,7 +655,7 @@
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 <b>0</b> to disable resynchronisation.
milliseconds. Specify <b>0</b> to disable resynchronisation. This setting is deprecated and will be removed in a future version of shairport-sync.
</p>
@@ -743,7 +703,7 @@
Allow playback to be 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> option to
monitor correction levels. Corrections should not greatly exceed net corrections.
monitor correction levels. Corrections should not greatly exceed net corrections. This setting is deprecated and will be removed in a future version of shairport-sync.
</p>
+8 -6
View File
@@ -24,13 +24,13 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "mdns.h"
#include "common.h"
#include "config.h"
#include <memory.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "config.h"
#include "common.h"
#include "mdns.h"
#include <string.h>
#ifdef CONFIG_AVAHI
extern mdns_backend mdns_avahi;
@@ -46,10 +46,12 @@ 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,
+8 -9
View File
@@ -23,9 +23,10 @@ typedef struct {
// "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"
#define MDNS_RECORD_WITH_METADATA \
"sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", \
METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false"
#define MDNS_RECORD_WITH_METADATA \
"sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", \
METADATA_EXPRESSION, "ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", \
"ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false"
#endif
@@ -33,11 +34,9 @@ typedef struct {
// "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"
#define MDNS_RECORD_WITHOUT_METADATA \
"sf=0x4", "fv=76400.10", "am=ShairportSync", "vs=105.1", "tp=TCP,UDP", "vn=65537", \
"ss=16", "sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", config.password ? "pw=true" : "pw=false"
#define MDNS_RECORD_WITHOUT_METADATA \
"sf=0x4", "fv=76400.10", "am=AirPort4,107", "vs=105.1", "tp=TCP,UDP", "vn=65537", "ss=16", \
"sr=44100", "da=true", "sv=false", "et=0,1", "ek=1", "cn=0,1", "ch=2", "txtvers=1", \
config.password ? "pw=true" : "pw=false"
#endif // _MDNS_H
+177 -162
View File
@@ -27,15 +27,15 @@
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "mdns.h"
#include <string.h>
#include <avahi-client/client.h>
#include <avahi-client/publish.h>
#include <avahi-common/thread-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>
#include <avahi-common/malloc.h>
#include <avahi-common/thread-watch.h>
#include <avahi-client/lookup.h>
#include <avahi-common/alternative.h>
@@ -48,157 +48,148 @@ static AvahiThreadedPoll *tpoll = NULL;
static char *name = NULL;
static int port = 0;
static void resolve_callback(
AvahiServiceResolver *r,
AVAHI_GCC_UNUSED AvahiIfIndex interface,
AVAHI_GCC_UNUSED AvahiProtocol protocol,
AvahiResolverEvent event,
const char *name,
const char *type,
const char *domain,
const char *host_name,
const AvahiAddress *address,
uint16_t port,
AvahiStringList *txt,
AvahiLookupResultFlags flags,
AVAHI_GCC_UNUSED void* userdata) {
static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface,
AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event,
const char *name, const char *type, const char *domain,
const char *host_name, const AvahiAddress *address, uint16_t port,
AvahiStringList *txt, AvahiLookupResultFlags flags,
AVAHI_GCC_UNUSED void *userdata) {
assert(r);
assert(r);
/* Called whenever a service has been resolved successfully or timed out */
/* Called whenever a service has been resolved successfully or timed out */
switch (event) {
case AVAHI_RESOLVER_FAILURE:
debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
break;
switch (event) {
case AVAHI_RESOLVER_FAILURE:
debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name,
type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
break;
case AVAHI_RESOLVER_FOUND: {
if (flags & AVAHI_LOOKUP_RESULT_OUR_OWN) {
char a[AVAHI_ADDRESS_STR_MAX], *t;
case AVAHI_RESOLVER_FOUND: {
if (flags & AVAHI_LOOKUP_RESULT_OUR_OWN) {
char a[AVAHI_ADDRESS_STR_MAX], *t;
// debug(1, "avahi: service '%s' of type '%s' in domain '%s' added.", name, type, domain);
avahi_address_snprint(a, sizeof(a), address);
debug(1,"avahi: address advertised is: \"%s\".",a);
/*
t = avahi_string_list_to_string(txt);
debug(1,
"\t%s:%u (%s)\n"
"\tTXT=%s\n"
"\tcookie is %u\n"
"\tis_local: %i\n"
"\tour_own: %i\n"
"\twide_area: %i\n"
"\tmulticast: %i\n"
"\tcached: %i\n",
host_name, port, a,
t,
avahi_string_list_get_service_cookie(txt),
!!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
!!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
!!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
!!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
!!(flags & AVAHI_LOOKUP_RESULT_CACHED));
// debug(1, "avahi: service '%s' of type '%s' in domain '%s' added.", name, type,
// domain);
avahi_address_snprint(a, sizeof(a), address);
debug(1, "avahi: address advertised is: \"%s\".", a);
/*
t = avahi_string_list_to_string(txt);
debug(1,
"\t%s:%u (%s)\n"
"\tTXT=%s\n"
"\tcookie is %u\n"
"\tis_local: %i\n"
"\tour_own: %i\n"
"\twide_area: %i\n"
"\tmulticast: %i\n"
"\tcached: %i\n",
host_name, port, a,
t,
avahi_string_list_get_service_cookie(txt),
!!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
!!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
!!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
!!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
!!(flags & AVAHI_LOOKUP_RESULT_CACHED));
avahi_free(t);
*/
}
}
avahi_free(t);
*/
}
}
}
avahi_service_resolver_free(r);
avahi_service_resolver_free(r);
}
static void browse_callback(
AvahiServiceBrowser *b,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char *name,
const char *type,
const char *domain,
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
void* userdata) {
static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
AvahiBrowserEvent event, const char *name, const char *type,
const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
void *userdata) {
AvahiClient *c = userdata;
assert(b);
AvahiClient *c = userdata;
assert(b);
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
switch (event) {
case AVAHI_BROWSER_FAILURE:
switch (event) {
case AVAHI_BROWSER_FAILURE:
// debug(1, "(Browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
return;
// debug(1, "(Browser) %s\n",
// avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
return;
case AVAHI_BROWSER_NEW:
// debug(1, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
case AVAHI_BROWSER_NEW:
// debug(1, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type,
// domain);
/* We ignore the returned resolver object. In the callback
function we free it. If the server is terminated before
the callback function is called the server will free
the resolver for us. */
/* We ignore the returned resolver object. In the callback
function we free it. If the server is terminated before
the callback function is called the server will free
the resolver for us. */
if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, c)))
debug(1, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC,
0, resolve_callback, c)))
debug(1, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
break;
break;
case AVAHI_BROWSER_REMOVE:
// debug(1, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
break;
case AVAHI_BROWSER_REMOVE:
// debug(1, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name,
// type, domain);
break;
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED:
// debug(1, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
break;
}
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED:
// debug(1, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ?
// "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
break;
}
}
static void register_service( AvahiClient *c );
static void register_service(AvahiClient *c);
static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
AVAHI_GCC_UNUSED void *userdata) {
switch ( state )
{
case AVAHI_ENTRY_GROUP_ESTABLISHED:
/* The entry group has been established successfully */
debug(1,"avahi: service '%s' successfully added.", name );
break;
switch (state) {
case AVAHI_ENTRY_GROUP_ESTABLISHED:
/* The entry group has been established successfully */
debug(1, "avahi: service '%s' successfully added.", name);
break;
case AVAHI_ENTRY_GROUP_COLLISION:
{
char *n;
case AVAHI_ENTRY_GROUP_COLLISION: {
char *n;
/* A service name collision with a remote service
* happened. Let's pick a new name */
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
/* A service name collision with a remote service
* happened. Let's pick a new name */
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
debug(2,"avahi: service name collision, renaming service to '%s'", name );
debug(2, "avahi: service name collision, renaming service to '%s'", name);
/* And recreate the services */
register_service( avahi_entry_group_get_client( g ) );
break;
}
/* And recreate the services */
register_service(avahi_entry_group_get_client(g));
break;
}
case AVAHI_ENTRY_GROUP_FAILURE:
debug(1,"avahi: entry group failure: %s", avahi_strerror( avahi_client_errno( avahi_entry_group_get_client( g ) ) ) );
break;
case AVAHI_ENTRY_GROUP_FAILURE:
debug(1, "avahi: entry group failure: %s",
avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
debug(2,"avahi: service '%s' group is not yet commited.", name );
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
debug(2, "avahi: service '%s' group is not yet commited.", name);
break;
case AVAHI_ENTRY_GROUP_REGISTERING:
debug(2,"avahi: service '%s' group is registering.", name );
break;
case AVAHI_ENTRY_GROUP_REGISTERING:
debug(2, "avahi: service '%s' group is registering.", name);
break;
default:
debug(1,"avahi: unhandled egroup state: %d", state );
break;
}
default:
debug(1, "avahi: unhandled egroup state: %d", state);
break;
}
}
static void register_service(AvahiClient *c) {
@@ -213,61 +204,85 @@ static void register_service(AvahiClient *c) {
return;
int ret;
#ifdef CONFIG_METADATA
AvahiIfIndex selected_interface;
if (config.interface != NULL)
selected_interface = config.interface_index;
else
selected_interface = AVAHI_IF_UNSPEC;
#ifdef CONFIG_METADATA
if (config.metadata_enabled) {
ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name,
config.regtype, NULL, NULL, port, MDNS_RECORD_WITH_METADATA,
NULL);
if (ret==0)
debug(1, "avahi: request to add \"%s\" service with metadata",config.regtype);
ret = avahi_entry_group_add_service(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, name,
config.regtype, NULL, NULL, port,
MDNS_RECORD_WITH_METADATA, NULL);
if (ret == 0)
debug(1, "avahi: request to add \"%s\" service with metadata", config.regtype);
} else {
#endif
ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name,
#endif
ret = avahi_entry_group_add_service(group, selected_interface, AVAHI_PROTO_UNSPEC, 0, name,
config.regtype, NULL, NULL, port,
MDNS_RECORD_WITHOUT_METADATA, NULL);
if (ret==0)
debug(1, "avahi: request to add \"%s\" service without metadata",config.regtype);
#ifdef CONFIG_METADATA
if (ret == 0)
debug(1, "avahi: request to add \"%s\" service without metadata", config.regtype);
#ifdef CONFIG_METADATA
}
#endif
#endif
if (ret < 0)
debug(1,"avahi: avahi_entry_group_add_service failed");
debug(1, "avahi: avahi_entry_group_add_service failed");
else {
ret = avahi_entry_group_commit(group);
if (ret < 0)
debug(1,"avahi: avahi_entry_group_commit failed");
debug(1, "avahi: avahi_entry_group_commit failed");
}
}
}
static void client_callback(AvahiClient *c, AvahiClientState state,
AVAHI_GCC_UNUSED void *userdata) {
int err;
switch (state) {
case AVAHI_CLIENT_S_REGISTERING:
if (group)
avahi_entry_group_reset(group);
break;
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:
debug(1,"avahi: client failure");
break;
case AVAHI_CLIENT_FAILURE:
err = avahi_client_errno(c);
debug(1, "avahi: client failure: %s", avahi_strerror(err));
case AVAHI_CLIENT_S_COLLISION:
debug(2, "avahi: state is AVAHI_CLIENT_S_COLLISION...needs a rename: %s", name );
break;
if (err == AVAHI_ERR_DISCONNECTED) {
/* We have been disconnected, so lets reconnect */
avahi_client_free(c);
c = NULL;
group = NULL;
case AVAHI_CLIENT_CONNECTING:
debug(2, "avahi: received AVAHI_CLIENT_CONNECTING" );
break;
if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), AVAHI_CLIENT_NO_FAIL,
client_callback, userdata, &err))) {
warn("avahi: failed to create client object: %s", avahi_strerror(err));
avahi_threaded_poll_quit(tpoll);
}
} else {
warn("avahi: client failure: %s", avahi_strerror(err));
avahi_threaded_poll_quit(tpoll);
}
break;
default:
debug(1,"avahi: unexpected and unhandled avahi client state: %d", state );
break;
case AVAHI_CLIENT_S_COLLISION:
debug(2, "avahi: state is AVAHI_CLIENT_S_COLLISION...needs a rename: %s", name);
break;
case AVAHI_CLIENT_CONNECTING:
debug(2, "avahi: received AVAHI_CLIENT_CONNECTING");
break;
default:
debug(1, "avahi: unexpected and unhandled avahi client state: %d", state);
break;
}
}
@@ -281,18 +296,18 @@ static int avahi_register(char *srvname, int srvport) {
warn("couldn't create avahi threaded tpoll!");
return -1;
}
if (!(client =
avahi_client_new(avahi_threaded_poll_get(tpoll), 0, client_callback, NULL, &err))) {
if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll), AVAHI_CLIENT_NO_FAIL,
client_callback, NULL, &err))) {
warn("couldn't create avahi client: %s!", avahi_strerror(err));
return -1;
}
// we need this to detect the IPv6 number we're advertising...
// if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, config.regtype, NULL, 0, browse_callback, client))) {
// warn("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client)));
// return -1;
// }
// we need this to detect the IPv6 number we're advertising...
// if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
// config.regtype, NULL, 0, browse_callback, client))) {
// warn("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client)));
// return -1;
// }
if (avahi_threaded_poll_start(tpoll) < 0) {
warn("couldn't start avahi tpoll thread");
+4 -4
View File
@@ -24,12 +24,12 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <dns_sd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include "mdns.h"
#include "common.h"
#include <arpa/inet.h>
#include <dns_sd.h>
#include <stdlib.h>
#include <string.h>
static DNSServiceRef service;
+11 -11
View File
@@ -24,14 +24,14 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "common.h"
#include "mdns.h"
#include "common.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int mdns_pid = 0;
@@ -87,8 +87,8 @@ static int mdns_external_avahi_register(char *apname, int port) {
char mdns_port[6];
sprintf(mdns_port, "%d", config.port);
char *argvwithoutmetadata[] = {NULL, apname, config.regtype, mdns_port,
MDNS_RECORD_WITHOUT_METADATA, NULL};
char *argvwithoutmetadata[] = {
NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL};
#ifdef CONFIG_METADATA
char *argvwithmetadata[] = {NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITH_METADATA,
NULL};
@@ -126,8 +126,8 @@ static int mdns_external_dns_sd_register(char *apname, int port) {
char mdns_port[6];
sprintf(mdns_port, "%d", config.port);
char *argvwithoutmetadata[] = {NULL, apname, config.regtype, mdns_port,
MDNS_RECORD_WITHOUT_METADATA, NULL};
char *argvwithoutmetadata[] = {
NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITHOUT_METADATA, NULL};
#ifdef CONFIG_METADATA
char *argvwithmetadata[] = {NULL, apname, config.regtype, mdns_port, MDNS_RECORD_WITH_METADATA,
+47 -38
View File
@@ -24,15 +24,15 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <ifaddrs.h>
#include <unistd.h>
#include <netinet/in.h>
#include "common.h"
#include "mdns.h"
#include "common.h"
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "tinysvcmdns.h"
@@ -70,17 +70,22 @@ static int mdns_tinysvcmdns_register(char *apname, int port) {
// 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;
// only check for the named interface, if specified
if ((config.interface == NULL) || (strcmp(config.interface, ifa->ifa_name) == 0)) {
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;
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_v6(svr, hostname, addr); // TTL should be 120 seconds
break;
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;
}
}
}
@@ -93,19 +98,22 @@ static int mdns_tinysvcmdns_register(char *apname, int port) {
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;
// only check for the named interface, if specified
if ((config.interface == NULL) || (strcmp(config.interface, ifa->ifa_name) == 0)) {
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;
}
}
}
@@ -124,22 +132,23 @@ static int mdns_tinysvcmdns_register(char *apname, int port) {
#endif
txt = txtwithoutmetadata;
if (config.regtype == NULL)
die("tinysvcmdns: regtype is null");
char* extendedregtype = malloc(strlen(config.regtype)+strlen(".local")+1);
char *extendedregtype = malloc(strlen(config.regtype) + strlen(".local") + 1);
if (extendedregtype==NULL)
if (extendedregtype == NULL)
die("tinysvcmdns: could not allocated memory to request a Zeroconf service");
strcpy(extendedregtype,config.regtype);
strcat(extendedregtype,".local");
struct mdns_service *svc = mdnsd_register_svc(svr, apname, extendedregtype, port, NULL,
strcpy(extendedregtype, config.regtype);
strcat(extendedregtype, ".local");
struct mdns_service *svc =
mdnsd_register_svc(svr, apname, extendedregtype, port, NULL,
(const char **)txt); // TTL should be 75 minutes, i.e. 4500 seconds
mdns_service_destroy(svc);
free(extendedregtype);
return 0;
+1278 -519
View File
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -18,8 +18,18 @@ int player_play(stream_cfg *cfg, pthread_t *thread);
void player_stop(pthread_t *thread);
void player_volume(double f);
void player_flush(uint32_t timestamp);
void player_flush(int64_t timestamp);
void player_put_packet(seq_t seqno, uint32_t timestamp, uint8_t *data, int len);
void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len);
int64_t monotonic_timestamp(uint32_t timestamp); // add an epoch to the timestamp. The monotonic
// timestamp guaranteed to start between 2^32 2^33
// frames and continue up to 2^64 frames
// which is about 2*10^8 * 1,000 seconds at 384,000 frames per second -- about 2 trillion seconds.
// assumes, without checking, that successive timestamps in a series always span an interval of less
// than one minute.
uint64_t monotonic_seqno(uint16_t seq_no); // add an epoch to the seq_no. Uses the accompanying
// timstamp to detemine the correct epoch
#endif //_PLAYER_H
+167 -150
View File
@@ -24,19 +24,19 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <time.h>
#include <arpa/inet.h>
#include <errno.h>
#include <math.h>
#include <memory.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <memory.h>
#include <math.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "common.h"
#include "player.h"
@@ -59,19 +59,20 @@ typedef struct time_ping_record {
static int running = 0;
static char client_ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client
static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it could be one of many, so we need to know it
static uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope
static short connection_ip_family; // AF_INET / AF_INET6
static uint32_t client_active_remote; // used when you want to control the client...
static char self_ip_string[INET6_ADDRSTRLEN]; // the ip string being used by this program -- it
// could be one of many, so we need to know it
static uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope
static short connection_ip_family; // AF_INET / AF_INET6
static uint32_t client_active_remote; // used when you want to control the client...
static SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client
static SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client
static int audio_socket; // our local [server] audio socket
static int control_socket; // our local [server] control socket
static int timing_socket; // local timing socket
//static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
// static pthread_t rtp_audio_thread, rtp_control_thread, rtp_timing_thread;
static uint32_t reference_timestamp;
static int64_t reference_timestamp;
static uint64_t reference_timestamp_time;
static uint64_t remote_reference_timestamp_time;
@@ -104,29 +105,33 @@ void *rtp_audio_receiver(void *arg) {
uint64_t time_of_previous_packet_fp = 0;
float longest_packet_time_interval_us = 0.0;
// mean and variance calculations from "online_variance" algorithm at https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
// mean and variance calculations from "online_variance" algorithm at
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
int32_t stat_n = 0;
float stat_mean = 0.0;
float stat_M2 = 0.0;
ssize_t nread;
while (itr->please_stop==0) {
while (itr->please_stop == 0) {
nread = recv(audio_socket, packet, sizeof(packet), 0);
uint64_t local_time_now_fp = get_absolute_time_in_fp();
if (time_of_previous_packet_fp) {
float time_interval_us = (((local_time_now_fp - time_of_previous_packet_fp)*1000000)>>32)*1.0;
float time_interval_us =
(((local_time_now_fp - time_of_previous_packet_fp) * 1000000) >> 32) * 1.0;
time_of_previous_packet_fp = local_time_now_fp;
if (time_interval_us>longest_packet_time_interval_us)
longest_packet_time_interval_us=time_interval_us;
stat_n+=1;
if (time_interval_us > longest_packet_time_interval_us)
longest_packet_time_interval_us = time_interval_us;
stat_n += 1;
float stat_delta = time_interval_us - stat_mean;
stat_mean += stat_delta/stat_n;
stat_M2 += stat_delta*(time_interval_us - stat_mean);
stat_mean += stat_delta / stat_n;
stat_M2 += stat_delta * (time_interval_us - stat_mean);
if (stat_n % 2500 == 0) {
debug(2,"Packet reception interval stats: mean, standard deviation and max for the last 2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",stat_mean, sqrtf(stat_M2 / (stat_n-1)),longest_packet_time_interval_us);
debug(2, "Packet reception interval stats: mean, standard deviation and max for the last "
"2,500 packets in microseconds: %10.1f, %10.1f, %10.1f.",
stat_mean, sqrtf(stat_M2 / (stat_n - 1)), longest_packet_time_interval_us);
stat_n = 0;
stat_mean = 0.0;
stat_M2 = 0.0;
@@ -155,11 +160,11 @@ void *rtp_audio_receiver(void *arg) {
last_seqno = seqno;
else {
last_seqno = (last_seqno + 1) & 0xffff;
//if (seqno != last_seqno)
// if (seqno != last_seqno)
// debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno);
last_seqno = seqno; // reset warning...
}
uint32_t timestamp = ntohl(*(unsigned long *)(pktp + 4));
int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)));
// if (packet[1]&0x10)
// debug(1,"Audio packet Extension bit set.");
@@ -198,9 +203,9 @@ void *rtp_control_receiver(void *arg) {
uint8_t packet[2048], *pktp;
struct timespec tn;
uint64_t remote_time_of_sync, local_time_now, remote_time_now;
uint32_t sync_rtp_timestamp, rtp_timestamp_less_latency;
int64_t sync_rtp_timestamp, rtp_timestamp_less_latency;
ssize_t nread;
while (itr->please_stop==0) {
while (itr->please_stop == 0) {
nread = recv(control_socket, packet, sizeof(packet), 0);
local_time_now = get_absolute_time_in_fp();
// clock_gettime(CLOCK_MONOTONIC,&tn);
@@ -229,17 +234,17 @@ void *rtp_control_receiver(void *arg) {
// debug(1,"Remote Sync Time: %0llx.",remote_time_of_sync);
rtp_timestamp_less_latency = ntohl(*((uint32_t *)&packet[4]));
sync_rtp_timestamp = ntohl(*((uint32_t *)&packet[16]));
rtp_timestamp_less_latency = monotonic_timestamp(ntohl(*((uint32_t *)&packet[4])));
sync_rtp_timestamp = monotonic_timestamp(ntohl(*((uint32_t *)&packet[16])));
if (config.use_negotiated_latencies) {
uint32_t la = sync_rtp_timestamp-rtp_timestamp_less_latency+11025;
if (la!=config.latency) {
int64_t la = sync_rtp_timestamp - rtp_timestamp_less_latency + 11025;
if (la != config.latency) {
config.latency = la;
// debug(1,"Using negotiated latency of %u frames.",config.latency);
}
}
if (packet[0] & 0x10) {
// if it's a packet right after a flush or resume
sync_rtp_timestamp += 352; // add frame_size -- can't see a reference to this anywhere,
@@ -265,11 +270,11 @@ void *rtp_control_receiver(void *arg) {
}
} else if (packet[1] == 0xd6) { // resent audio data in the control path -- whaale only?
// debug(1, "Control Port -- Retransmitted Audio Data Packet received.");
pktp = packet+4;
pktp = packet + 4;
plen -= 4;
seq_t seqno = ntohs(*(unsigned short *)(pktp + 2));
uint32_t timestamp = ntohl(*(unsigned long *)(pktp + 4));
int64_t timestamp = monotonic_timestamp(ntohl(*(unsigned long *)(pktp + 4)));
pktp += 12;
plen -= 12;
@@ -293,7 +298,7 @@ void *rtp_control_receiver(void *arg) {
void *rtp_timing_sender(void *arg) {
debug(2, "Timing sender thread starting.");
int *stop = arg; // the parameter points to this request to stop thing
int *stop = arg; // the parameter points to this request to stop thing
struct timing_request {
char leader;
char type;
@@ -314,8 +319,8 @@ void *rtp_timing_sender(void *arg) {
time_ping_count = 0;
// we inherit the signal mask (SIGUSR1)
while (*stop==0) {
// debug(1,"Send a timing request");
while (*stop == 0) {
// debug(1,"Send a timing request");
if (!running)
die("rtp_timing_sender called without active stream!");
@@ -351,7 +356,7 @@ void *rtp_timing_receiver(void *arg) {
debug(2, "Timing receiver -- Server RTP thread starting.");
// we inherit the signal mask (SIGUSR1)
struct inter_threads_record *itr = arg;
struct inter_threads_record *itr = arg;
uint8_t packet[2048], *pktp;
ssize_t nread;
@@ -365,11 +370,11 @@ void *rtp_timing_receiver(void *arg) {
local_to_remote_time_jitters_count = 0;
uint64_t first_remote_time = 0;
uint64_t first_local_time = 0;
uint64_t first_local_to_remote_time_difference = 0;
uint64_t first_local_to_remote_time_difference_time;
uint64_t l2rtd = 0;
while (itr->please_stop==0) {
while (itr->please_stop == 0) {
nread = recv(timing_socket, packet, sizeof(packet), 0);
arrival_time = get_absolute_time_in_fp();
// clock_gettime(CLOCK_MONOTONIC,&att);
@@ -428,13 +433,14 @@ void *rtp_timing_receiver(void *arg) {
// these are for diagnostics only -- not used
time_pings[0].local_time = arrival_time;
time_pings[0].remote_time = distant_transmit_time;
time_pings[0].local_to_remote_difference = local_time_by_remote_clock - arrival_time;
time_pings[0].dispersion = return_time;
if (time_ping_count < time_ping_history)
time_ping_count++;
uint64_t local_time_chosen = arrival_time;;
uint64_t local_time_chosen = arrival_time;
;
uint64_t remote_time_chosen = distant_transmit_time;
// now pick the timestamp with the lowest dispersion
uint64_t l2rtd = time_pings[0].local_to_remote_difference;
@@ -465,69 +471,81 @@ void *rtp_timing_receiver(void *arg) {
// with dispersion of %lld us with delta of %lld us",rtus,ji);
local_to_remote_time_difference = l2rtd;
if (first_local_to_remote_time_difference==0) {
if (first_local_to_remote_time_difference == 0) {
first_local_to_remote_time_difference = local_to_remote_time_difference;
first_local_to_remote_time_difference_time = get_absolute_time_in_fp();
}
int64_t clock_drift, clock_drift_in_usec;
if (first_local_time==0) {
if (first_local_time == 0) {
first_local_time = local_time_chosen;
first_remote_time = remote_time_chosen;
clock_drift = 0;
} else {
uint64_t local_time_change = local_time_chosen - first_local_time;
uint64_t remote_time_change = remote_time_chosen - first_remote_time;
if (remote_time_change >= local_time_change)
clock_drift = remote_time_change - local_time_change;
else
clock_drift = -(local_time_change - remote_time_change);
}
if (clock_drift>=0)
clock_drift_in_usec = (clock_drift * 1000000)>>32;
if (clock_drift >= 0)
clock_drift_in_usec = (clock_drift * 1000000) >> 32;
else
clock_drift_in_usec = -(((-clock_drift) * 1000000)>>32);
int64_t source_drift_usec;
if (play_segment_reference_frame!=0) {
uint32_t reference_timestamp;
uint64_t reference_timestamp_time,remote_reference_timestamp_time;
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, &remote_reference_timestamp_time);
uint64_t frame_difference = 0;
if (reference_timestamp>=play_segment_reference_frame)
frame_difference = (uint64_t)reference_timestamp-(uint64_t)play_segment_reference_frame;
else // rollover
frame_difference = (uint64_t)reference_timestamp+UINT64_C(0x100000000)-(uint64_t)play_segment_reference_frame;
uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference<<32)/44100);
uint64_t frame_time_difference_actual = remote_reference_timestamp_time-play_segment_reference_frame_remote_time; // this is all done by reference to the sources' system clock
// debug(1,"%llu frames since play started, %llu usec calculated, %llu usec actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, (frame_time_difference_actual*1000000)>>32);
if (frame_time_difference_calculated>=frame_time_difference_actual) // i.e. if the time it should have taken to send the packets is greater than the actual time difference measured on the source clock
// then the source DAC's clock is running fast relative to the source system clock
source_drift_usec = frame_time_difference_calculated-frame_time_difference_actual;
else
// otherwise the source DAC's clock is running slow relative to the source system clock
source_drift_usec = -(frame_time_difference_actual-frame_time_difference_calculated);
} else
source_drift_usec = 0;
source_drift_usec = (source_drift_usec*1000000)>>32; // turn it to microseconds
//long current_delay = 0;
//if (config.output->delay) {
// config.output->delay(&current_delay);
//}
// Useful for troubleshooting:
// clock_drift between source and local clock -- +ve means source is faster
// session_corrections -- the amount of correction done, in microseconds. +ve means frames added
// current_delay = delay in DAC buffer in frames
// source_drift_usec = how much faster (+ve) or slower the source DAC is running relative to the source clock
// buffer_occupancy = the number of buffers occupied. Crude, but should show no long term trend if source and device are in sync.
// return_time = the time from soliciting a timing packet to getting it back. It should be short ( < 5 ms) and pretty consistent.
// debug(1, "%lld\t%lld\t%ld\t%lld\t%u\t%llu", clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32);
clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32);
int64_t source_drift_usec;
if (play_segment_reference_frame != 0) {
int64_t reference_timestamp;
uint64_t reference_timestamp_time, remote_reference_timestamp_time;
get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time,
&remote_reference_timestamp_time);
uint64_t frame_difference = 0;
if (reference_timestamp >= play_segment_reference_frame)
frame_difference = (uint64_t)reference_timestamp - (uint64_t)play_segment_reference_frame;
else // rollover
frame_difference =
(uint64_t)reference_timestamp + 0x100000000 - (uint64_t)play_segment_reference_frame;
uint64_t frame_time_difference_calculated = (((uint64_t)frame_difference << 32) / 44100);
uint64_t frame_time_difference_actual =
remote_reference_timestamp_time -
play_segment_reference_frame_remote_time; // this is all done by reference to the
// sources' system clock
// debug(1,"%llu frames since play started, %llu usec calculated, %llu usec
// actual",frame_difference, (frame_time_difference_calculated*1000000)>>32,
// (frame_time_difference_actual*1000000)>>32);
if (frame_time_difference_calculated >=
frame_time_difference_actual) // i.e. if the time it should have taken to send the
// packets is greater than the actual time difference
// measured on the source clock
// then the source DAC's clock is running fast relative to the source system clock
source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual;
else
// otherwise the source DAC's clock is running slow relative to the source system clock
source_drift_usec = -(frame_time_difference_actual - frame_time_difference_calculated);
} else
source_drift_usec = 0;
source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds
// long current_delay = 0;
// if (config.output->delay) {
// config.output->delay(&current_delay);
//}
// Useful for troubleshooting:
// clock_drift between source and local clock -- +ve means source is faster
// session_corrections -- the amount of correction done, in microseconds. +ve means frames
// added
// current_delay = delay in DAC buffer in frames
// source_drift_usec = how much faster (+ve) or slower the source DAC is running relative
// to the source clock
// buffer_occupancy = the number of buffers occupied. Crude, but should show no long term
// trend if source and device are in sync.
// return_time = the time from soliciting a timing packet to getting it back. It should be
// short ( < 5 ms) and pretty consistent.
// debug(1, "%lld\t%lld\t%ld\t%lld\t%u\t%llu",
// clock_drift_in_usec,(session_corrections*1000000)/44100,current_delay,source_drift_usec,buffer_occupancy,(return_time*1000000)>>32);
} else {
debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread);
}
@@ -545,44 +563,45 @@ void *rtp_timing_receiver(void *arg) {
return NULL;
}
static int bind_port(int ip_family,const char *self_ip_address,uint32_t scope_id,int *sock) {
static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id, int *sock) {
// look for a port in the range, if any was specified.
int desired_port = config.udp_port_base;
int ret;
int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP);
if (local_socket== -1)
if (local_socket == -1)
die("Could not allocate a socket.");
SOCKADDR myaddr;
do {
memset(&myaddr,0,sizeof(myaddr));
if (ip_family==AF_INET) {
memset(&myaddr, 0, sizeof(myaddr));
if (ip_family == AF_INET) {
struct sockaddr_in *sa = (struct sockaddr_in *)&myaddr;
sa->sin_family = AF_INET;
sa->sin_port = ntohs(desired_port);
inet_pton(AF_INET,self_ip_address,&(sa->sin_addr));
ret = bind(local_socket,(struct sockaddr*)sa, sizeof(struct sockaddr_in));
inet_pton(AF_INET, self_ip_address, &(sa->sin_addr));
ret = bind(local_socket, (struct sockaddr *)sa, sizeof(struct sockaddr_in));
}
#ifdef AF_INET6
if (ip_family==AF_INET6) {
if (ip_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&myaddr;
sa6->sin6_family = AF_INET6;
sa6->sin6_port = ntohs(desired_port);
inet_pton(AF_INET6,self_ip_address,&(sa6->sin6_addr));
sa6->sin6_scope_id=scope_id;
ret = bind(local_socket,(struct sockaddr*)sa6, sizeof(struct sockaddr_in6));
inet_pton(AF_INET6, self_ip_address, &(sa6->sin6_addr));
sa6->sin6_scope_id = scope_id;
ret = bind(local_socket, (struct sockaddr *)sa6, sizeof(struct sockaddr_in6));
}
#endif
} while ((ret<0) && (errno==EADDRINUSE) && (desired_port!=0) && (desired_port++ < config.udp_port_base+config.udp_port_range));
#endif
} while ((ret < 0) && (errno == EADDRINUSE) && (desired_port != 0) &&
(desired_port++ < config.udp_port_base + config.udp_port_range));
// debug(1,"UDP port chosen: %d.",desired_port);
if (ret < 0) {
close(local_socket);
die("error: could not bind a UDP port!");
close(local_socket);
die("error: could not bind a UDP port!");
}
int sport;
SOCKADDR local;
socklen_t local_len = sizeof(local);
@@ -597,18 +616,18 @@ static int bind_port(int ip_family,const char *self_ip_address,uint32_t scope_id
struct sockaddr_in *sa = (struct sockaddr_in *)&local;
sport = ntohs(sa->sin_port);
}
*sock = local_socket;
return sport;
}
void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote, int *lsport,
int *lcport, int *ltport) {
void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t active_remote,
int *lsport, int *lcport, int *ltport) {
// this gets the local and remote ip numbers (and ports used for the TCD stuff)
// we use the local stuff to specify the address we are coming from and
// we use the remote stuff to specify where we're goint to
if (running)
die("rtp_setup called with active stream!");
@@ -617,13 +636,13 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t
client_active_remote = active_remote;
// print out what we know about the client
void *client_addr,*self_addr;
int client_port,self_port;
void *client_addr, *self_addr;
int client_port, self_port;
char client_port_str[64];
char self_addr_str[64];
connection_ip_family = remote->SAFAMILY; // keep information about the kind of ip of the client
#ifdef AF_INET6
if (connection_ip_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote;
@@ -644,13 +663,10 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t
self_port = ntohs(sa4->sin_port);
}
inet_ntop(connection_ip_family, client_addr, client_ip_string,
sizeof(client_ip_string));
inet_ntop(connection_ip_family, self_addr, self_ip_string,
sizeof(self_ip_string));
debug(1, "Set up play connection from %s to self at %s.", client_ip_string,self_ip_string);
inet_ntop(connection_ip_family, client_addr, client_ip_string, sizeof(client_ip_string));
inet_ntop(connection_ip_family, self_addr, self_ip_string, sizeof(self_ip_string));
debug(1, "Set up play connection from %s to self at %s.", client_ip_string, self_ip_string);
// set up a the record of the remote's control socket
struct addrinfo hints;
@@ -698,23 +714,24 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, uint32_t
// now, we open three sockets -- one for the audio stream, one for the timing and one for the
// control
*lsport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&audio_socket);
*lcport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&control_socket);
*ltport = bind_port(connection_ip_family,self_ip_string,self_scope_id,&timing_socket);
*lsport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &audio_socket);
*lcport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &control_socket);
*ltport = bind_port(connection_ip_family, self_ip_string, self_scope_id, &timing_socket);
debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport,
*ltport);
reference_timestamp = 0;
//pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
//pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
//pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
// pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
// pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
// pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
running = 1;
request_sent = 0;
}
void get_reference_timestamp_stuff(uint32_t *timestamp, uint64_t *timestamp_time, uint64_t *remote_timestamp_time) {
void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
uint64_t *remote_timestamp_time) {
// types okay
pthread_mutex_lock(&reference_time_mutex);
*timestamp = reference_timestamp;
@@ -732,25 +749,25 @@ void clear_reference_timestamp(void) {
void rtp_shutdown(void) {
if (!running)
debug(1,"rtp_shutdown called without active stream!");
debug(1, "rtp_shutdown called without active stream!");
debug(2, "shutting down RTP thread");
clear_reference_timestamp();
// debug(1,"Shut down audio, control and timing threads");
// usleep(3000000); // hack
// pthread_kill(rtp_audio_thread, SIGUSR1);
// pthread_kill(rtp_control_thread, SIGUSR1);
// pthread_kill(rtp_timing_thread, SIGUSR1);
// pthread_join(rtp_audio_thread, &retval);
// pthread_join(rtp_control_thread, &retval);
// pthread_join(rtp_timing_thread, &retval);
// debug(1,"Shut down audio, control and timing threads");
// usleep(3000000); // hack
// pthread_kill(rtp_audio_thread, SIGUSR1);
// pthread_kill(rtp_control_thread, SIGUSR1);
// pthread_kill(rtp_timing_thread, SIGUSR1);
// pthread_join(rtp_audio_thread, &retval);
// pthread_join(rtp_control_thread, &retval);
// pthread_join(rtp_timing_thread, &retval);
running = 0;
}
void rtp_request_resend(seq_t first, uint32_t count) {
if (running) {
//if (!request_sent) {
debug(3, "requesting resend of %d packets starting at %u.", count, first);
// if (!request_sent) {
debug(3, "requesting resend of %d packets starting at %u.", count, first);
// request_sent = 1;
//}
@@ -771,8 +788,8 @@ void rtp_request_resend(seq_t first, uint32_t count) {
perror("Error sendto-ing to audio socket");
}
} else {
//if (!request_sent) {
debug(2, "rtp_request_resend called without active stream!");
// if (!request_sent) {
debug(2, "rtp_request_resend called without active stream!");
// request_sent = 1;
//}
}
+6 -6
View File
@@ -5,21 +5,21 @@
#include "player.h"
typedef struct inter_threads_record {
uint32_t please_stop;
} inter_threads_record;
typedef struct inter_threads_record { uint32_t please_stop; } inter_threads_record;
void *rtp_audio_receiver(void *arg);
void *rtp_control_receiver(void *arg);
void *rtp_timing_receiver(void *arg);
void rtp_setup(SOCKADDR *local, 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 *local, 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, uint64_t *remote_timestamp_time);
void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
uint64_t *remote_timestamp_time);
void clear_reference_timestamp(void);
uint64_t static local_to_remote_time_jitters;
+182 -185
View File
@@ -26,23 +26,23 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <memory.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/select.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <memory.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "config.h"
@@ -50,14 +50,18 @@
#include <openssl/md5.h>
#endif
#ifdef HAVE_LIBMBEDTLS
#include <mbedtls/md5.h>
#endif
#ifdef HAVE_LIBPOLARSSL
#include <polarssl/md5.h>
#endif
#include "common.h"
#include "mdns.h"
#include "player.h"
#include "rtp.h"
#include "mdns.h"
#ifdef AF_INET6
#define INETx_ADDRSTRLEN INET6_ADDRSTRLEN
@@ -82,7 +86,7 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
// only one thread is allowed to use the player at once.
// it monitors the request variable (at least when interrupted)
//static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER;
// static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER;
// static int please_shutdown = 0;
// static pthread_t playing_thread = 0;
@@ -90,14 +94,15 @@ typedef struct {
int fd;
int authorized; // set is a password is required and has been supplied
stream_cfg stream;
SOCKADDR remote,local;
SOCKADDR remote, local;
int stop;
int running;
pthread_t thread;
pthread_t player_thread;
} rtsp_conn_info;
static rtsp_conn_info *playing_conn = NULL; // the data structure representing the connection that has the player.
static rtsp_conn_info *playing_conn =
NULL; // the data structure representing the connection that has the player.
static rtsp_conn_info **conns = NULL;
void memory_barrier() {
@@ -144,8 +149,7 @@ typedef struct {
rtsp_message *carrier;
} metadata_package;
void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size,
uint32_t number_of_items) {
void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items) {
pthread_mutex_init(&the_queue->pc_queue_lock, NULL);
pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL);
pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL);
@@ -157,8 +161,8 @@ void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size,
the_queue->eoq = 0;
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length,
rtsp_message *carrier, int block);
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block);
int send_ssnc_metadata(uint32_t code, char *data, uint32_t length, int block) {
return send_metadata('ssnc', code, data, length, NULL, block);
@@ -176,8 +180,7 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
if (rc)
debug(1, "Error locking for pc_queue_add_item");
while (the_queue->count == the_queue->capacity) {
rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal,
&the_queue->pc_queue_lock);
rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock);
if (rc)
debug(1, "Error waiting for item to be removed");
}
@@ -214,8 +217,7 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
if (rc)
debug(1, "Error locking for pc_queue_get_item");
while (the_queue->count == 0) {
rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal,
&the_queue->pc_queue_lock);
rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock);
if (rc)
debug(1, "Error waiting for item to be added");
}
@@ -263,11 +265,10 @@ static inline int rtsp_playing(void) {
void rtsp_request_shutdown_stream(void) {
debug(1, "Request to shut down all rtsp conversation threads");
ask_other_rtsp_conversation_threads_to_stop(
0); // i.e. ask all playing threads to stop
ask_other_rtsp_conversation_threads_to_stop(0); // i.e. ask all playing threads to stop
}
//static void rtsp_take_player(void) {
// static void rtsp_take_player(void) {
// if (rtsp_playing())
// return;
@@ -314,8 +315,7 @@ void ask_other_rtsp_conversation_threads_to_stop(pthread_t except_this_thread) {
int i;
debug(2, "asking playing threads to stop");
for (i = 0; i < nconns; i++) {
if (((except_this_thread == 0) ||
(pthread_equal(conns[i]->thread, except_this_thread) == 0)) &&
if (((except_this_thread == 0) || (pthread_equal(conns[i]->thread, except_this_thread) == 0)) &&
(conns[i]->running != 0)) {
conns[i]->stop = 1;
pthread_kill(conns[i]->thread, SIGUSR1);
@@ -363,8 +363,7 @@ static void msg_retain(rtsp_message *msg) {
static rtsp_message *msg_init(void) {
rtsp_message *msg = malloc(sizeof(rtsp_message));
memset(msg, 0, sizeof(rtsp_message));
msg->referenceCount =
1; // from now on, any access to this must be protected with the lock
msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
return msg;
}
@@ -405,8 +404,7 @@ static void msg_free(rtsp_message *msg) {
msg->referenceCount--;
rc = pthread_mutex_unlock(&reference_counter_lock);
if (rc)
debug(1, "Error %d unlocking reference counter lock during msg_free()",
rc);
debug(1, "Error %d unlocking reference counter lock during msg_free()", rc);
if (msg->referenceCount == 0) {
int i;
for (i = 0; i < msg->nheaders; i++) {
@@ -479,8 +477,8 @@ fail:
return 0;
}
static enum rtsp_read_request_response
rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) {
static enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn,
rtsp_message **the_packet) {
enum rtsp_read_request_response reply = rtsp_read_request_response_ok;
ssize_t buflen = 512;
char *buf = malloc(buflen + 1);
@@ -499,8 +497,8 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) {
goto shutdown;
}
nread = read(conn->fd, buf + inbuf, buflen - inbuf);
if (nread==0) {
if (nread == 0) {
// a blocking read that returns zero means eof -- implies connection closed
debug(3, "RTSP connection closed.");
reply = rtsp_read_request_response_shutdown_requested;
@@ -542,8 +540,8 @@ rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) {
buflen = msg_size;
}
uint64_t threshold_time = get_absolute_time_in_fp() +
((uint64_t)5 << 32); // i.e. five seconds from now
uint64_t threshold_time =
get_absolute_time_in_fp() + ((uint64_t)5 << 32); // i.e. five seconds from now
int warning_message_sent = 0;
const size_t max_read_chunk = 50000;
@@ -628,8 +626,7 @@ static void msg_write_response(int fd, rtsp_message *resp) {
int ignore = write(fd, pkt, p - pkt + 2);
}
static void handle_record(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
// debug(1,"Handle Record");
resp->respcode = 200;
// I think this is for telling the client what the absolute minimum latency
@@ -663,16 +660,14 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req,
}
}
static void handle_options(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_options(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, "
"PAUSE, FLUSH, TEARDOWN, "
"OPTIONS, GET_PARAMETER, SET_PARAMETER");
}
static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
if (!rtsp_playing())
debug(1, "This RTSP conversation thread doesn't think it's playing, but "
"it's sending a response to teardown anyway");
@@ -681,8 +676,7 @@ static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req,
conn->stop = 1;
}
static void handle_flush(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
if (!rtsp_playing())
debug(1, "This RTSP conversation thread doesn't think it's playing, but "
"it's sending a response to flush anyway");
@@ -705,8 +699,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req,
resp->respcode = 200;
}
static void handle_setup(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
// debug(1,"Handle Setup");
int cport, tport;
int lsport, lcport, ltport;
@@ -768,10 +761,8 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req,
config.latency = config.iTunesLatency;
}
} else if (strstr(ua, "AirPlay") == ua) {
debug(
2,
"User-Agent is AirPlay; selecting the AirPlay latency of %d frames.",
config.AirPlayLatency);
debug(2, "User-Agent is AirPlay; selecting the AirPlay latency of %d frames.",
config.AirPlayLatency);
config.latency = config.AirPlayLatency;
} else if (strstr(ua, "forked-daapd") == ua) {
debug(2, "User-Agent is forked-daapd; selecting the forked-daapd latency "
@@ -779,8 +770,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req,
config.ForkedDaapdLatency);
config.latency = config.ForkedDaapdLatency;
} else {
debug(2, "Unrecognised User-Agent. Using latency of %d frames.",
config.latency);
debug(2, "Unrecognised User-Agent. Using latency of %d frames.", config.latency);
}
}
@@ -809,9 +799,8 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req,
p = strchr(p, '=') + 1;
tport = atoi(p);
// rtsp_take_player();
rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport,
&ltport);
// rtsp_take_player();
rtp_setup(&conn->local, &conn->remote, cport, tport, active_remote, &lsport, &lcport, &ltport);
if (!lsport)
goto error;
char *q;
@@ -853,13 +842,11 @@ error:
resp->respcode = 451; // invalid arguments
}
static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
}
static void handle_set_parameter_parameter(rtsp_conn_info *conn,
rtsp_message *req,
static void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
char *cp = req->content;
int cp_left = req->contentlength;
@@ -888,7 +875,7 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn,
#ifdef CONFIG_METADATA
if (!strncmp(cp, "progress: ", 10)) {
char *progress = cp + 10;
//debug(2, "progress: \"%s\"\n",
// debug(2, "progress: \"%s\"\n",
// progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 per second
send_ssnc_metadata('prgr', strdup(progress), strlen(progress), 1);
} else
@@ -970,7 +957,6 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn,
// `clip` -- the payload is the IP number of the client, i.e. the sender of audio.
// Can be an IPv4 or an IPv6 number.
//
// including a simple base64 encoder to minimise malloc/free activity
// From Stack Overflow, with thanks:
@@ -981,12 +967,11 @@ static void handle_set_parameter_parameter(rtsp_conn_info *conn,
// add _so to end of name to avoid confusion with polarssl's implementation
static char encoding_table[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
static int mod_table[] = {0, 2, 1};
@@ -995,8 +980,8 @@ static int mod_table[] = {0, 2, 1};
// containing its maximum length
// the actual length will be returned.
char *base64_encode_so(const unsigned char *data, size_t input_length,
char *encoded_data, size_t *output_length) {
char *base64_encode_so(const unsigned char *data, size_t input_length, char *encoded_data,
size_t *output_length) {
size_t calculated_output_length = 4 * ((input_length + 2) / 3);
if (calculated_output_length > *output_length)
@@ -1093,8 +1078,7 @@ static void metadata_close(void) {
fd = -1;
}
void metadata_process(uint32_t type, uint32_t code, char *data,
uint32_t length) {
void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
// debug(2, "Process metadata with type %x, code %x and length %u.", type, code, length);
int ret;
@@ -1108,7 +1092,8 @@ void metadata_process(uint32_t type, uint32_t code, char *data,
memcpy(ptr, &v, 4);
ptr += 4;
memcpy(ptr, data, length);
sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr));
sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr,
sizeof(metadata_sockaddr));
}
// readers may go away and come back
@@ -1117,9 +1102,8 @@ void metadata_process(uint32_t type, uint32_t code, char *data,
if (fd < 0)
return;
char thestring[1024];
snprintf(thestring, 1024,
"<item><type>%x</type><code>%x</code><length>%u</length>", type,
code, length);
snprintf(thestring, 1024, "<item><type>%x</type><code>%x</code><length>%u</length>", type, code,
length);
ret = non_blocking_write(fd, thestring, strlen(thestring));
if (ret < 0) {
// debug(1,"metadata_process error %d exit 1",ret);
@@ -1144,10 +1128,9 @@ void metadata_process(uint32_t type, uint32_t code, char *data,
size_t towrite_count = remaining_count;
if (towrite_count > 57)
towrite_count = 57;
size_t outbuf_size =
76; // size of output buffer on entry, length of result on exit
if (base64_encode_so((unsigned char *)remaining_data, towrite_count,
outbuf, &outbuf_size) == NULL)
size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit
if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) ==
NULL)
debug(1, "Error encoding base64 data.");
// debug(1,"Remaining count: %d ret: %d, outbuf_size:
// %d.",remaining_count,ret,outbuf_size);
@@ -1191,16 +1174,15 @@ void *metadata_thread_function(void *ignore) {
void metadata_init(void) {
// create a pc_queue for passing information to a threaded metadata handler
pc_queue_init(&metadata_queue, (char *)&metadata_queue_items,
sizeof(metadata_package), metadata_queue_size);
int ret =
pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package),
metadata_queue_size);
int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL);
if (ret)
debug(1, "Failed to create metadata thread!");
}
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length,
rtsp_message *carrier, int block) {
int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier,
int block) {
// parameters: type, code, pointer to data or NULL, length of data or NULL,
// the rtsp_message or
@@ -1240,14 +1222,11 @@ int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length,
if ((rc == EBUSY) && (carrier))
msg_free(carrier);
if (rc == EBUSY)
warn(
"Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.",
type, code);
warn("Metadata queue is busy, dropping message of type 0x%08X, code 0x%08X.", type, code);
return rc;
}
static void handle_set_parameter_metadata(rtsp_conn_info *conn,
rtsp_message *req,
static void handle_set_parameter_metadata(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
char *cp = req->content;
int cl = req->contentlength;
@@ -1257,14 +1236,12 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn,
uint32_t itag, vl;
while (off < cl) {
// pick up the metadata tag as an unsigned longint
memcpy(&itag, (uint32_t *)(cp + off),
sizeof(uint32_t)); /* can be misaligned, thus memcpy */
memcpy(&itag, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */
itag = ntohl(itag);
off += sizeof(uint32_t);
// pick up the length of the data
memcpy(&vl, (uint32_t *)(cp + off),
sizeof(uint32_t)); /* can be misaligned, thus memcpy */
memcpy(&vl, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */
vl = ntohl(vl);
off += sizeof(uint32_t);
@@ -1281,14 +1258,12 @@ static void handle_set_parameter_metadata(rtsp_conn_info *conn,
#endif
static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_get_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
debug(1, "received GET_PARAMETER request.");
resp->respcode = 200;
}
static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
// if (!req->contentlength)
// debug(1, "received empty SET_PARAMETER request.");
@@ -1297,7 +1272,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req,
char *ct = msg_get_header(req, "Content-Type");
if (ct) {
// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct);
// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct);
#ifdef CONFIG_METADATA
// It seems that the rtptime of the message is used as a kind of an ID that
@@ -1325,8 +1300,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req,
if (p == NULL)
debug(1, "Missing RTP-Time info for metadata");
if (p)
send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req,
1); // metadata starting
send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, 1); // metadata starting
else
send_metadata('ssnc', 'mdst', NULL, 0, NULL,
0); // metadata starting, if rtptime is not available
@@ -1334,8 +1308,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req,
handle_set_parameter_metadata(conn, req, resp);
if (p)
send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req,
1); // metadata ending
send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, 1); // metadata ending
else
send_metadata('ssnc', 'mden', NULL, 0, NULL,
0); // metadata starting, if rtptime is not available
@@ -1351,8 +1324,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req,
if (p == NULL)
debug(1, "Missing RTP-Time info for picture item");
if (p)
send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req,
1); // picture starting
send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, 1); // picture starting
else
send_metadata('ssnc', 'pcst', NULL, 0, NULL,
0); // picture starting, if rtptime is not available
@@ -1360,8 +1332,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req,
send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1);
if (p)
send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req,
1); // picture ending
send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, 1); // picture ending
else
send_metadata('ssnc', 'pcen', NULL, 0, NULL,
0); // picture ending, if rtptime is not available
@@ -1372,11 +1343,9 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req,
#endif
if (!strncmp(ct, "text/parameters", 15)) {
// debug(2, "received parameters in SET_PARAMETER request.");
handle_set_parameter_parameter(conn, req,
resp); // this could be volume or progress
handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress
} else {
debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.",
ct);
debug(1, "received unknown Content-Type \"%s\" in SET_PARAMETER request.", ct);
}
} else {
debug(1, "missing Content-Type header in SET_PARAMETER request.");
@@ -1385,8 +1354,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req,
resp->respcode = 200;
}
static void handle_announce(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int have_the_player = 0;
// interrupt session if permitted
@@ -1403,15 +1371,15 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req,
die("Non existent the_playing_conn with play_lock enabled.");
}
usleep(1000000); // here, it is possible for other connections to come in and nab the player.
debug(1,"Try to get the player now");
//pthread_mutex_lock(&play_lock);
debug(1, "Try to get the player now");
// pthread_mutex_lock(&play_lock);
if (pthread_mutex_trylock(&play_lock) == 0)
have_the_player = 1;
}
}
if (have_the_player) {
playing_conn = conn; // the present connection is now playing
if (have_the_player) {
playing_conn = conn; // the present connection is now playing
resp->respcode = 456; // 456 - Header Field Not Valid for Resource
char *paesiv = NULL;
char *prsaaeskey = NULL;
@@ -1482,8 +1450,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req,
free(aeskey);
}
int i;
for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]);
i++)
for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t"));
char *hdr = msg_get_header(req, "X-Apple-Client-Name");
@@ -1651,6 +1618,22 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
MD5_Final(digest_mu, &ctx);
#endif
#ifdef HAVE_LIBMBEDTLS
mbedtls_md5_context tctx;
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username));
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm));
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password));
mbedtls_md5_finish(&tctx, digest_urp);
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method));
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri));
mbedtls_md5_finish(&tctx, digest_mu);
#endif
#ifdef HAVE_LIBPOLARSSL
md5_context tctx;
md5_starts(&tctx);
@@ -1658,8 +1641,7 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
md5_update(&tctx, (unsigned char *)":", 1);
md5_update(&tctx, (const unsigned char *)realm, strlen(realm));
md5_update(&tctx, (unsigned char *)":", 1);
md5_update(&tctx, (const unsigned char *)config.password,
strlen(config.password));
md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password));
md5_finish(&tctx, digest_urp);
md5_starts(&tctx);
md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method));
@@ -1685,6 +1667,18 @@ static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
MD5_Final(digest_total, &ctx);
#endif
#ifdef HAVE_LIBMBEDTLS
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, buf, 32);
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce));
mbedtls_md5_update(&tctx, (unsigned char *)":", 1);
for (i = 0; i < 16; i++)
sprintf((char *)buf + 2 * i, "%02x", digest_mu[i]);
mbedtls_md5_update(&tctx, buf, 32);
mbedtls_md5_finish(&tctx, digest_total);
#endif
#ifdef HAVE_LIBPOLARSSL
md5_starts(&tctx);
md5_update(&tctx, buf, 32);
@@ -1731,8 +1725,8 @@ static void *rtsp_conversation_thread_func(void *pconn) {
do {
reply = rtsp_read_request(conn, &req);
if (reply == rtsp_read_request_response_ok) {
debug(3,"RTSP Packet received of type \"%s\":",req->method),
debug_print_msg_headers(3,req);
debug(3, "RTSP Packet received of type \"%s\":", req->method),
debug_print_msg_headers(3, req);
resp = msg_init();
resp->respcode = 400;
@@ -1740,26 +1734,25 @@ static void *rtsp_conversation_thread_func(void *pconn) {
hdr = msg_get_header(req, "CSeq");
if (hdr)
msg_add_header(resp, "CSeq", hdr);
// msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog");
// msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog");
msg_add_header(resp, "Server", "AirTunes/105.1");
if ((conn->authorized==1) || (rtsp_auth(&auth_nonce, req, resp))==0) {
conn->authorized=1; // it must have been authorized or didn't need a password
struct method_handler *mh;
int method_selected = 0;
for (mh = method_handlers; mh->method; mh++) {
if (!strcmp(mh->method, req->method)) {
method_selected = 1;
mh->handler(conn, req, resp);
break;
}
}
if (method_selected == 0)
debug(1, "Unrecognised and unhandled rtsp request \"%s\".",
req->method);
}
debug(3,"RTSP Response:");
debug_print_msg_headers(3,resp);
if ((conn->authorized == 1) || (rtsp_auth(&auth_nonce, req, resp)) == 0) {
conn->authorized = 1; // it must have been authorized or didn't need a password
struct method_handler *mh;
int method_selected = 0;
for (mh = method_handlers; mh->method; mh++) {
if (!strcmp(mh->method, req->method)) {
method_selected = 1;
mh->handler(conn, req, resp);
break;
}
}
if (method_selected == 0)
debug(1, "Unrecognised and unhandled rtsp request \"%s\".", req->method);
}
debug(3, "RTSP Response:");
debug_print_msg_headers(3, resp);
msg_write_response(conn->fd, resp);
msg_free(req);
msg_free(resp);
@@ -1781,11 +1774,11 @@ static void *rtsp_conversation_thread_func(void *pconn) {
close(conn->fd);
if (auth_nonce)
free(auth_nonce);
// pthread_mutex_unlock(&playing_mutex);
// usleep(1000000);
// } // else {
//debug(1, "This RTSP conversation thread doesn't think it's playing for a "
// "close RTSP connection.");
// pthread_mutex_unlock(&playing_mutex);
// usleep(1000000);
// } // else {
// debug(1, "This RTSP conversation thread doesn't think it's playing for a "
// "close RTSP connection.");
// }
debug(2, "RTSP conversation thread terminated.");
// please_shutdown = 0;
@@ -1831,7 +1824,7 @@ void rtsp_listen_loop(void) {
}
for (p = info; p; p = p->ai_next) {
ret = 0;
ret = 0;
int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP);
int yes = 1;
@@ -1865,14 +1858,15 @@ void rtsp_listen_loop(void) {
// report its availability. do not complain.
if (ret) {
char *family;
char *family;
#ifdef AF_INET6
if (p->ai_family == AF_INET6) {
family = "IPv6";
} else
if (p->ai_family == AF_INET6) {
family = "IPv6";
} else
#endif
family = "IPv4";
debug(1, "Unable to listen on %s port %d. The error is: \"%s\".", family, config.port,strerror(errno));
family = "IPv4";
debug(1, "Unable to listen on %s port %d. The error is: \"%s\".", family, config.port,
strerror(errno));
continue;
}
@@ -1885,7 +1879,9 @@ void rtsp_listen_loop(void) {
freeaddrinfo(info);
if (!nsock)
die("Could not establish a service on port %d -- program terminating. Is another Shairport Sync running on this device?",config.port);
die("Could not establish a service on port %d -- program terminating. Is another instance of "
"Shairport Sync running on this device?",
config.port);
int maxfd = -1;
fd_set fds;
@@ -1938,57 +1934,58 @@ void rtsp_listen_loop(void) {
perror("failed to accept connection");
free(conn);
} else {
SOCKADDR *local_info = (SOCKADDR*)&conn->local;
SOCKADDR *local_info = (SOCKADDR *)&conn->local;
socklen_t size_of_reply = sizeof(*local_info);
memset(local_info,0,sizeof(SOCKADDR));
if (getsockname(conn->fd, (struct sockaddr*)local_info, &size_of_reply)==0) {
memset(local_info, 0, sizeof(SOCKADDR));
if (getsockname(conn->fd, (struct sockaddr *)local_info, &size_of_reply) == 0) {
// IPv4:
if (local_info->SAFAMILY==AF_INET) {
char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
char remote_ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
struct sockaddr_in *sa = (struct sockaddr_in*)local_info;
if (local_info->SAFAMILY == AF_INET) {
char ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
char remote_ip4[INET_ADDRSTRLEN]; // space to hold the IPv4 string
struct sockaddr_in *sa = (struct sockaddr_in *)local_info;
inet_ntop(AF_INET, &(sa->sin_addr), ip4, INET_ADDRSTRLEN);
unsigned short int tport = ntohs(sa->sin_port);
sa = (struct sockaddr_in*)&conn->remote;
sa = (struct sockaddr_in *)&conn->remote;
inet_ntop(AF_INET, &(sa->sin_addr), remote_ip4, INET_ADDRSTRLEN);
unsigned short int rport = ntohs(sa->sin_port);
#ifdef CONFIG_METADATA
send_ssnc_metadata('clip', strdup(remote_ip4), strlen(remote_ip4) , 1);
send_ssnc_metadata('svip', strdup(ip4), strlen(ip4) , 1);
send_ssnc_metadata('clip', strdup(remote_ip4), strlen(remote_ip4), 1);
send_ssnc_metadata('svip', strdup(ip4), strlen(ip4), 1);
#endif
debug(1,"New RTSP connection from %s:%u to self at %s:%u.",remote_ip4,rport,ip4,tport);
debug(1, "New RTSP connection from %s:%u to self at %s:%u.", remote_ip4, rport, ip4,
tport);
}
#ifdef AF_INET6
if (local_info->SAFAMILY==AF_INET6) {
if (local_info->SAFAMILY == AF_INET6) {
// IPv6:
char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
char remote_ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)local_info; // pretend this is loaded with something
struct sockaddr_in6 *sa6 =
(struct sockaddr_in6 *)local_info; // pretend this is loaded with something
inet_ntop(AF_INET6, &(sa6->sin6_addr), ip6, INET6_ADDRSTRLEN);
u_int16_t tport = ntohs(sa6->sin6_port);
sa6 = (struct sockaddr_in6*)&conn->remote; // pretend this is loaded with something
sa6 = (struct sockaddr_in6 *)&conn->remote; // pretend this is loaded with something
inet_ntop(AF_INET6, &(sa6->sin6_addr), remote_ip6, INET6_ADDRSTRLEN);
u_int16_t rport = ntohs(sa6->sin6_port);
#ifdef CONFIG_METADATA
send_ssnc_metadata('clip', strdup(remote_ip6), strlen(remote_ip6), 1);
send_ssnc_metadata('svip', strdup(ip6), strlen(ip6), 1);
send_ssnc_metadata('clip', strdup(remote_ip6), strlen(remote_ip6), 1);
send_ssnc_metadata('svip', strdup(ip6), strlen(ip6), 1);
#endif
debug(1,"New RTSP connection from [%s]:%u to self at [%s]:%u.",remote_ip6,rport,ip6,tport);
debug(1, "New RTSP connection from [%s]:%u to self at [%s]:%u.", remote_ip6, rport, ip6,
tport);
}
#endif
#endif
} else {
debug(1,"Error figuring out Shairport Sync's own IP number.");
debug(1, "Error figuring out Shairport Sync's own IP number.");
}
usleep(500000);
pthread_t rtsp_conversation_thread;
ret = pthread_create(&rtsp_conversation_thread, NULL,
rtsp_conversation_thread_func, conn);
ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn);
if (ret)
die("Failed to create RTSP receiver thread!");
-1
View File
@@ -5,7 +5,6 @@ void rtsp_listen_loop(void);
// void rtsp_shutdown_stream(void);
void rtsp_request_shutdown_stream(void);
// initialise the metadata stuff
void metadata_init(void);
+27 -18
View File
@@ -9,8 +9,8 @@ general =
// You can use the following substitutions:
// %h for the hostname,
// %H for the Hostname (i.e. with first letter capitalised (ASCII only)),
// %v for the version number, e.g. 2.8.5 and
// %V for the full version string, e.g. 2.8.5-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
// %v for the version number, e.g. 3.0 and
// %V for the full version string, e.g. 3.0-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
// Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H".
// password = "secret"; // leave this commented out if you don't want to require a password
// interpolation = "basic"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor.
@@ -20,25 +20,31 @@ general =
// 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
// drift_tolerance_in_seconds = 0.002; // allow a timing error of this number of seconds of drift away from exact synchronisation before attempting to correct it
// resync_threshold_in_seconds = 0.050; // a synchronisation error greater than this number of seconds will cause resynchronisation; 0 disables it
// 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.
// volume_range_db = 60 ; // use this to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range.
// volume_range_db = 60 ; // use this advanced setting to set the range, in dB, you want between the maximum volume and the minimum volume. Range is 30 to 150 dB. Leave it commented out to use mixer's native range.
// volume_max_db = 0.0 ; // use this advanced setting, which must have a decimal point in it, to set the maximum volume, in dB, you wish to use.
// The setting is for the hardware mixer, if chosen, or the software mixer otherwise. The value must be in the mixer's range (0.0 to -96.2 for the software mixer).
// Leave it commented out to use mixer's maximum volume.
// regtype = "_raop._tcp"; // Use this advanced setting to set the service type and transport to be advertised by Zeroconf/Bonjour. Default is "_raop._tcp".
// playback_mode = "stereo"; // This can be "stereo" or "mono". Default is "stereo".
// playback_mode = "stereo"; // This can be "stereo", "mono", "reverse stereo", "both left" or "both right". Default is "stereo".
// alac_decoder = "hammerton"; // This can be "hammerton" or "apple". This advanced setting allows you to choose
// the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple.
// interface = "name"; // Use this advanced setting to specify the interface on which Shairport Sync should provide its service. Leave it commented out to get the default, which is to select the interface(s) automatically.
};
// 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
// enabled = "no"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
// include_cover_art = "no"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
// pipe_name = "/tmp/shairport-sync-metadata";
// pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up
// socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes"
// socket_port = 5555; // if socket_address is set, the port to send UDP packets to
// socket_msglength = 65000; // the maximum packet size for any UDP metadata. This will be clipped to be between 500 or 65000. The default is 500.
// socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes"
// socket_port = 5555; // if socket_address is set, the port to send UDP packets to
// socket_msglength = 65000; // the maximum packet size for any UDP metadata. This will be clipped to be between 500 or 65000. The default is 500.
};
// Advanced parameters for controlling how a Shairport Sync runs
@@ -59,10 +65,13 @@ 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.
// output_rate = 44100; // can be 44100, 88200, 176400 or 352800, but the device must have the capability.
// output_format = "S16"; // can be "U8", "S8", "S16", "S24", "S24_3LE", "S24_3BE" or "S32", but the device must have the capability. Except where stated using (*LE or *BE), endianness matches that of the processor.
// audio_backend_latency_offset_in_seconds = 0.0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
// audio_backend_buffer_desired_length_in_seconds = 0.15; // If set too small, buffer underflow occurs on low-powered machines. Too long and the response times with software mixer become annoying.
// disable_synchronization = "no"; // Set to "yes" to disable synchronization. Default is "no".
// period_size = <number>; // Use this optional advanced setting to set the alsa period size near to this value
// buffer_size = <number>; // Use this optional advanced setting to set the alsa buffer size near to this value
// use_mmap_if_available = "yes"; // Use this optional advanced setting to control whether MMAP-based output is used to communicate with the DAC. Default is "yes"
};
@@ -70,22 +79,22 @@ alsa =
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.
// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length.
};
// 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.
// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length.
};
// These are parameters for the "ao" audio back end. No interpolation is done.
ao =
{
// audio_backend_latency_offset = 0; // Set this offset to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -4410.
// audio_backend_buffer_desired_length = 44100; // Having started to send audio at the right time, send all subsequent audio this many frames ahead of time, creating a buffer this size.
// audio_backend_latency_offset = 0.0; // Set this offset in seconds to compensate for a fixed delay in the audio back end. E.g. if the output device delays by 100 ms, set this to -0.1.
// audio_backend_buffer_desired_length = 1.0; // Having started to send audio at the right time, send all subsequent audio this much ahead of time, creating a buffer this length.
};
// Static latency settings are deprecated and the settings have been removed.
+393 -200
View File
@@ -2,7 +2,7 @@
* Shairport, an Apple Airplay receiver
* Copyright (c) James Laird 2013
* All rights reserved.
* Modifications (c) Mike Brady 2014--2016
* Modifications (c) Mike Brady 2014--2017
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,23 +25,28 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <memory.h>
#include <sys/wait.h>
#include <getopt.h>
#include <libconfig.h>
#include <libgen.h>
#include <memory.h>
#include <net/if.h>
#include <popt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <popt.h>
#include <libgen.h>
#include <libconfig.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "config.h"
#ifdef HAVE_LIBMBEDTLS
#include <mbedtls/md5.h>
#endif
#ifdef HAVE_LIBPOLARSSL
#include <polarssl/md5.h>
#endif
@@ -51,20 +56,20 @@
#endif
#include "common.h"
#include "rtsp.h"
#include "rtp.h"
#include "mdns.h"
#include "rtp.h"
#include "rtsp.h"
#include <libdaemon/dexec.h>
#include <libdaemon/dfork.h>
#include <libdaemon/dsignal.h>
#include <libdaemon/dlog.h>
#include <libdaemon/dpid.h>
#include <libdaemon/dexec.h>
#include <libdaemon/dsignal.h>
static int shutting_down = 0;
static char *appName = NULL;
char configuration_file_path[4096+1];
char actual_configuration_file_path[4096+1];
char configuration_file_path[4096 + 1];
char actual_configuration_file_path[4096 + 1];
void shairport_shutdown() {
if (shutting_down)
@@ -80,7 +85,7 @@ static void sig_ignore(int foo, siginfo_t *bar, void *baz) {}
static void sig_shutdown(int foo, siginfo_t *bar, void *baz) {
debug(1, "shutdown requested...");
shairport_shutdown();
// daemon_log(LOG_NOTICE, "exit...");
// daemon_log(LOG_NOTICE, "exit...");
daemon_retval_send(255);
daemon_pid_file_remove();
exit(0);
@@ -105,52 +110,58 @@ static void sig_connect_audio_output(int foo, siginfo_t *bar, void *baz) {
set_requested_connection_state_to_output(1);
}
char* get_version_string() {
char* version_string = malloc(200);
char *get_version_string() {
char *version_string = malloc(200);
if (version_string) {
strcpy(version_string, PACKAGE_VERSION);
#ifdef HAVE_LIBPOLARSSL
#ifdef HAVE_LIBMBEDTLS
strcat(version_string, "-mbedTLS");
#endif
#ifdef HAVE_LIBPOLARSSL
strcat(version_string, "-PolarSSL");
#endif
#ifdef HAVE_LIBSSL
#endif
#ifdef HAVE_LIBSSL
strcat(version_string, "-OpenSSL");
#endif
#ifdef CONFIG_TINYSVCMDNS
#endif
#ifdef CONFIG_TINYSVCMDNS
strcat(version_string, "-tinysvcmdns");
#endif
#ifdef CONFIG_AVAHI
#endif
#ifdef CONFIG_AVAHI
strcat(version_string, "-Avahi");
#endif
#ifdef CONFIG_DNS_SD
#endif
#ifdef CONFIG_DNS_SD
strcat(version_string, "-dns_sd");
#endif
#ifdef CONFIG_ALSA
#endif
#ifdef CONFIG_ALSA
strcat(version_string, "-ALSA");
#endif
#ifdef CONFIG_SNDIO
#endif
#ifdef CONFIG_SNDIO
strcat(version_string, "-sndio");
#endif
#ifdef CONFIG_AO
#endif
#ifdef CONFIG_AO
strcat(version_string, "-ao");
#endif
#ifdef CONFIG_PULSE
#endif
#ifdef CONFIG_PULSE
strcat(version_string, "-pulse");
#endif
#ifdef CONFIG_DUMMY
#endif
#ifdef CONFIG_SOUNDIO
strcat(version_string, "-soundio");
#endif
#ifdef CONFIG_DUMMY
strcat(version_string, "-dummy");
#endif
#ifdef CONFIG_STDOUT
#endif
#ifdef CONFIG_STDOUT
strcat(version_string, "-stdout");
#endif
#ifdef CONFIG_PIPE
#endif
#ifdef CONFIG_PIPE
strcat(version_string, "-pipe");
#endif
#ifdef HAVE_LIBSOXR
#endif
#ifdef HAVE_LIBSOXR
strcat(version_string, "-soxr");
#endif
#ifdef CONFIG_METADATA
#endif
#ifdef CONFIG_METADATA
strcat(version_string, "-metadata");
#endif
#endif
strcat(version_string, "-sysconfdir:");
strcat(version_string, SYSCONFDIR);
}
@@ -158,12 +169,12 @@ char* get_version_string() {
}
void print_version(void) {
char* version_string = get_version_string();
char *version_string = get_version_string();
if (version_string) {
printf("%s\n", version_string);
free(version_string);
} else {
debug(1,"Can't print version string!");
debug(1, "Can't print version string!");
}
}
@@ -182,21 +193,26 @@ void usage(char *progname) {
"/etc/shairport-sync.conf.\n");
printf("\n");
printf("The following general options are for backward compatability. These and all new options have settings in the configuration file, by default /etc/shairport-sync.conf:\n");
printf("The following general options are for backward compatability. These and all new options "
"have settings in the configuration file, by default /etc/shairport-sync.conf:\n");
printf(" -v, --verbose -v print debug information; -vv more; -vvv lots.\n");
printf(" -p, --port=PORT set RTSP listening port.\n");
printf(" -a, --name=NAME set advertised name.\n");
printf(
" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an AirPlay device.\n");
// printf(" -A, --AirPlayLatency=FRAMES [Deprecated] Set the latency for audio sent from an "
// "AirPlay device.\n");
// printf(" The default is to set it automatically.\n");
// printf(" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes
// "
// "10 or later.\n");
// printf(" The default is to set it automatically.\n");
printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown "
"device.\n");
printf(" The default is to set it automatically.\n");
printf(
" -i, --iTunesLatency=FRAMES [Deprecated] Set the latency for audio sent from iTunes 10 or later.\n");
printf(" --forkedDaapdLatency=FRAMES [Deprecated] Set the latency for audio sent from "
"forked-daapd.\n");
printf(" The default is to set it automatically.\n");
printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown device.\n");
printf(" The default is to set it automatically.\n");
printf(" --forkedDaapdLatency=FRAMES [Deprecated] Set the latency for audio sent from forked-daapd.\n");
printf(" The default is to set it automatically.\n");
printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, where \n");
printf(" -S, --stuffing=MODE set how to adjust current latency to match desired latency, "
"where \n");
printf(" \"basic\" (default) inserts or deletes audio frames from "
"packet frames with low processor overhead, and \n");
printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- "
@@ -209,18 +225,21 @@ void usage(char *progname) {
"e.g. /usr/bin/logger.\n");
printf(" Executable scripts work, but must have #!/bin/sh (or "
"whatever) in the headline.\n");
printf(" -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n");
printf(
" -w, --wait-cmd wait until the -B or -E programs finish before continuing.\n");
printf(" -o, --output=BACKEND select audio output method.\n");
printf(" -m, --mdns=BACKEND force the use of BACKEND to advertize the service.\n");
printf(" if no mdns provider is specified,\n");
printf(" shairport tries them all until one works.\n");
printf(" -r, --resync=THRESHOLD resync if error exceeds this number of frames. Set to 0 to "
printf(" -r, --resync=THRESHOLD [Deprecated] resync if error exceeds this number of frames. "
"Set to 0 to "
"stop resyncing.\n");
printf(" -t, --timeout=SECONDS go back to idle mode from play mode after a break in "
"communications of this many seconds (default 120). Set to 0 never to exit play mode.\n");
printf(" --statistics print some interesting statistics -- output to the logfile "
"if running as a daemon.\n");
printf(" --tolerance=TOLERANCE allow a synchronization error of TOLERANCE frames (default "
printf(" --tolerance=TOLERANCE [Deprecated] allow a synchronization error of TOLERANCE "
"frames (default "
"88) before trying to correct it.\n");
printf(" --password=PASSWORD require PASSWORD to connect. Default is not to require a "
"password.\n");
@@ -237,11 +256,14 @@ void usage(char *progname) {
}
int parse_options(int argc, char **argv) {
// there are potential memory leaks here -- it's called a second time, previously allocated strings will dangle.
// there are potential memory leaks here -- it's called a second time, previously allocated
// strings will dangle.
char *raw_service_name = NULL; /* Used to pick up the service name before possibly expanding it */
char *stuffing = NULL; /* used for picking up the stuffing option */
signed char c; /* used for argument parsing */
int i = 0; /* used for tracking options */
char *stuffing = NULL; /* used for picking up the stuffing option */
signed char c; /* used for argument parsing */
int i = 0; /* used for tracking options */
int fResyncthreshold = (int)(config.resyncthreshold * 44100);
int fTolerance = (int)(config.tolerance * 44100);
poptContext optCon; /* context for parsing command-line options */
struct poptOption optionsTable[] = {
{"verbose", 'v', POPT_ARG_NONE, NULL, 'v', NULL},
@@ -262,20 +284,19 @@ int parse_options(int argc, char **argv) {
{"latency", 'L', POPT_ARG_INT, &config.userSuppliedLatency, 0, NULL},
{"AirPlayLatency", 'A', POPT_ARG_INT, &config.AirPlayLatency, 0, NULL},
{"iTunesLatency", 'i', POPT_ARG_INT, &config.iTunesLatency, 0, NULL},
{"forkedDaapdLatency", 0, POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL},
{"forkedDaapdLatency", 'f', POPT_ARG_INT, &config.ForkedDaapdLatency, 0, NULL},
{"stuffing", 'S', POPT_ARG_STRING, &stuffing, 'S', NULL},
{"resync", 'r', POPT_ARG_INT, &config.resyncthreshold, 0, NULL},
{"resync", 'r', POPT_ARG_INT, &fResyncthreshold, 0, NULL},
{"timeout", 't', POPT_ARG_INT, &config.timeout, 't', NULL},
{"password", 0, POPT_ARG_STRING, &config.password, 0, NULL},
{"tolerance", 0, POPT_ARG_INT, &config.tolerance, 0, NULL},
{"tolerance", 'z', POPT_ARG_INT, &fTolerance, 0, NULL},
#ifdef CONFIG_METADATA
{"metadata-pipename", 'M', POPT_ARG_STRING, &config.metadata_pipename, 'M', NULL},
{"get-coverart", 'g', POPT_ARG_NONE, &config.get_coverart, 'g', NULL},
#endif
POPT_AUTOHELP
{NULL, 0, 0, NULL, 0}};
POPT_AUTOHELP{NULL, 0, 0, NULL, 0}};
// we have to parse the command line arguments to look for a config file
// we have to parse the command line arguments to look for a config file
int optind;
optind = argc;
int j;
@@ -285,31 +306,61 @@ int parse_options(int argc, char **argv) {
optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0);
poptSetOtherOptionHelp(optCon, "[OPTIONS]* ");
/* Now do options processing just to get a debug level */
/* Now do options processing just to get a debug level */
debuglev = 0;
while ((c = poptGetNextOpt(optCon)) >= 0) {
switch (c) {
case 'v':
debuglev++;
break;
case 'D':
inform("Warning: the option -D or --disconnectFromOutput is deprecated.");
break;
case 'R':
inform("Warning: the option -R or --reconnectToOutput is deprecated.");
break;
case 'A':
inform("Warning: the option -A or --AirPlayLatency is deprecated. This setting is now "
"automatically received from the AirPlay device.");
break;
case 'i':
inform("Warning: the option -i or --iTunesLatency is deprecated. This setting is now "
"automatically received from iTunes");
break;
case 'f':
inform("Warning: the option --forkedDaapdLatency is deprecated. This setting is now "
"automatically received from forkedDaapd");
break;
case 'r':
inform("Warning: the option -r or --resync is deprecated. Please use the "
"\"resync_threshold_in_seconds\" setting in the config file instead.");
break;
case 'z':
inform("Warning: the option --tolerance is deprecated. Please use the "
"\"drift_tolerance_in_seconds\" setting in the config file instead.");
break;
}
}
if (c < -1) {
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
}
config.resyncthreshold = 1.0 * fResyncthreshold / 44100;
config.tolerance = 1.0 * fTolerance / 44100;
config_setting_t *setting;
const char *str=0;
int value=0;
debug(1,"Looking for the configuration file \"%s\".",config.configfile);
const char *str = 0;
int value = 0;
double dvalue = 0.0;
debug(1, "Looking for the configuration file \"%s\".", config.configfile);
config_init(&config_file_stuff);
char *config_file_real_path = realpath(config.configfile, NULL);
if (config_file_real_path==NULL) {
debug(2,"Can't resolve the configuration file \"%s\".",config.configfile);
if (config_file_real_path == NULL) {
debug(2, "Can't resolve the configuration file \"%s\".", config.configfile);
} else {
debug(2, "Looking for configuration file at full path \"%s\"", config_file_real_path);
/* Read the file. If there is an error, report it and exit. */
@@ -357,7 +408,8 @@ int parse_options(int argc, char **argv) {
config.udp_port_base = value;
}
/* Get the udp port range setting. This is number of ports that will be tried for free ports , starting at the port base. Only three ports are needed. */
/* Get the udp port range setting. This is number of ports that will be tried for free ports ,
* starting at the port base. Only three ports are needed. */
if (config_lookup_int(config.cfg, "general.udp_port_range", &value)) {
if ((value < 0) || (value > 65535))
die("Invalid port range \"%sd\". It should be between 0 and 65535, default is 100",
@@ -389,13 +441,27 @@ int parse_options(int argc, char **argv) {
die("Invalid statistics option choice \"%s\". It should be \"yes\" or \"no\"");
}
/* The old drift tolerance setting. */
if (config_lookup_int(config.cfg, "general.drift", &value)) {
inform("The drift setting is deprecated. Use "
"drift_tolerance_in_seconds instead");
config.tolerance = 1.0 * value / 44100;
}
/* The old resync setting. */
if (config_lookup_int(config.cfg, "general.resync_threshold", &value)) {
inform("The resync_threshold setting is deprecated. Use "
"resync_threshold_in_seconds instead");
config.resyncthreshold = 1.0 * value / 44100;
}
/* Get the drift tolerance setting. */
if (config_lookup_int(config.cfg, "general.drift", &value))
config.tolerance = value;
if (config_lookup_float(config.cfg, "general.drift_tolerance_in_seconds", &dvalue))
config.tolerance = dvalue;
/* Get the resync setting. */
if (config_lookup_int(config.cfg, "general.resync_threshold", &value))
config.resyncthreshold = value;
if (config_lookup_float(config.cfg, "general.resync_threshold_in_seconds", &dvalue))
config.resyncthreshold = dvalue;
/* Get the verbosity setting. */
if (config_lookup_int(config.cfg, "general.log_verbosity", &value)) {
@@ -417,31 +483,97 @@ int parse_options(int argc, char **argv) {
die("Invalid ignore_volume_control option choice \"%s\". It should be \"yes\" or \"no\"");
}
/* Get the optional volume_max_db setting. */
if (config_lookup_float(config.cfg, "general.volume_max_db", &dvalue)) {
debug(1, "Max volume setting of %f dB", dvalue);
config.volume_max_db = dvalue;
config.volume_max_db_set = 1;
}
/* Get the playback_mode setting */
if (config_lookup_string(config.cfg, "general.playback_mode", &str)) {
if (config_lookup_string(config.cfg, "general.playback_mode", &str)) {
if (strcasecmp(str, "stereo") == 0)
config.playback_mode = ST_stereo;
else if (strcasecmp(str, "mono") == 0)
config.playback_mode = ST_mono;
else if (strcasecmp(str, "reverse stereo") == 0)
config.playback_mode = ST_reverse_stereo;
else if (strcasecmp(str, "both left") == 0)
config.playback_mode = ST_left_only;
else if (strcasecmp(str, "both right") == 0)
config.playback_mode = ST_right_only;
else
die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default) or \"mono\"");
die("Invalid playback_mode choice \"%s\". It should be \"stereo\" (default), \"mono\", "
"\"reverse stereo\", \"both left\", \"both right\"");
}
/* Get the regtype -- the service type and protocol, separated by a dot. Default is "_raop._tcp" */
/* Get the interface to listen on, if specified Default is all interfaces */
/* we keep the interface name and the index */
if (config_lookup_string(config.cfg, "general.interface", &str))
config.interface = strdup(str);
if (config_lookup_string(config.cfg, "general.interface", &str)) {
int specified_interface_found = 0;
struct if_nameindex *if_ni, *i;
if_ni = if_nameindex();
if (if_ni == NULL) {
debug(1, "Can't get a list of interface names.");
} else {
for (i = if_ni; !(i->if_index == 0 && i->if_name == NULL); i++) {
// printf("%u: %s\n", i->if_index, i->if_name);
if (strcmp(i->if_name, str) == 0) {
config.interface_index = i->if_index;
specified_interface_found = 1;
}
}
}
if_freenameindex(if_ni);
if (specified_interface_found == 0) {
inform(
"The mdns service interface \"%s\" was not found, so the setting has been ignored.",
config.interface);
free(config.interface);
config.interface = NULL;
config.interface_index = 0;
}
}
/* Get the regtype -- the service type and protocol, separated by a dot. Default is
* "_raop._tcp" */
if (config_lookup_string(config.cfg, "general.regtype", &str))
config.regtype = strdup(str);
/* Get the volume range, in dB, that should be used If not set, it means you just use the range set by the mixer. */
/* Get the volume range, in dB, that should be used If not set, it means you just use the
* range set by the mixer. */
if (config_lookup_int(config.cfg, "general.volume_range_db", &value)) {
if ((value < 30) || (value > 150))
die("Invalid volume range \"%sd\". It should be between 30 and 150 dB. Zero means use the mixer's native range",
die("Invalid volume range \"%sd\". It should be between 30 and 150 dB. Zero means use "
"the mixer's native range",
value);
else
config.volume_range_db = value;
}
/* Get the default latency. Deprecated! */
/* Get the alac_decoder setting. */
if (config_lookup_string(config.cfg, "general.alac_decoder", &str)) {
if (strcasecmp(str, "hammerton") == 0)
config.use_apple_decoder = 0;
else if (strcasecmp(str, "apple") == 0) {
if ((config.decoders_supported & 1 << decoder_apple_alac) != 0)
config.use_apple_decoder = 1;
else
inform("Support for the Apple ALAC decoder has not been compiled into this version of "
"Shairport Sync. The default decoder will be used.");
} else
die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"");
}
/* Get the default latency. Deprecated! */
if (config_lookup_int(config.cfg, "latencies.default", &value))
config.userSuppliedLatency = value;
@@ -457,7 +589,7 @@ int parse_options(int argc, char **argv) {
if (config_lookup_int(config.cfg, "latencies.forkedDaapd", &value))
config.ForkedDaapdLatency = value;
#ifdef CONFIG_METADATA
#ifdef CONFIG_METADATA
/* Get the metadata setting. */
if (config_lookup_string(config.cfg, "metadata.enabled", &str)) {
if (strcasecmp(str, "no") == 0)
@@ -493,7 +625,7 @@ int parse_options(int argc, char **argv) {
config.metadata_sockmsglength = value < 500 ? 500 : value > 65000 ? 65000 : value;
}
#endif
#endif
if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) {
config.cmd_start = (char *)str;
@@ -520,7 +652,8 @@ int parse_options(int argc, char **argv) {
else if (strcasecmp(str, "yes") == 0)
config.allow_session_interruption = 1;
else
die("Invalid session control allow_interruption option choice \"%s\". It should be \"yes\" "
die("Invalid session control allow_interruption option choice \"%s\". It should be "
"\"yes\" "
"or \"no\"");
}
@@ -541,8 +674,9 @@ int parse_options(int argc, char **argv) {
free(config_file_real_path);
}
// now, do the command line options again, but this time do them fully -- it's a unix convention that command line
// arguments have precedence over configuration file settings.
// now, do the command line options again, but this time do them fully -- it's a unix convention
// that command line
// arguments have precedence over configuration file settings.
optind = argc;
for (j = 0; j < argc; j++)
@@ -595,34 +729,34 @@ int parse_options(int argc, char **argv) {
if (c < -1) {
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
}
#ifdef CONFIG_METADATA
if ((config.metadata_enabled == 1) && (config.metadata_pipename == NULL))
config.metadata_pipename=strdup("/tmp/shairport-sync-metadata");
config.metadata_pipename = strdup("/tmp/shairport-sync-metadata");
#endif
/* if the regtype hasn't been set, do it now */
if (config.regtype==NULL)
/* if the regtype hasn't been set, do it now */
if (config.regtype == NULL)
config.regtype = strdup("_raop._tcp");
if (tdebuglev!=0)
if (tdebuglev != 0)
debuglev = tdebuglev;
/* if the Service Name wasn't specified, do it now */
/* if the Service Name wasn't specified, do it now */
if (raw_service_name==NULL)
if (raw_service_name == NULL)
raw_service_name = strdup("%H");
// now, do the substitutions in the service name
// now, do the substitutions in the service name
char hostname[100];
gethostname(hostname, 100);
char *i1 = str_replace(raw_service_name,"%h",hostname);
if ((hostname[0]>='a') && (hostname[0]<='z'))
hostname[0] = hostname[0]-0x20; // convert a lowercase first letter into a capital letter
char *i2 = str_replace(i1,"%H",hostname);
char *i3 = str_replace(i2,"%v",PACKAGE_VERSION);
char *i1 = str_replace(raw_service_name, "%h", hostname);
if ((hostname[0] >= 'a') && (hostname[0] <= 'z'))
hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter
char *i2 = str_replace(i1, "%H", hostname);
char *i3 = str_replace(i2, "%v", PACKAGE_VERSION);
char *vs = get_version_string();
config.service_name = str_replace(i3,"%V",vs);
config.service_name = str_replace(i3, "%V", vs);
free(i1);
free(i2);
free(i3);
@@ -680,7 +814,6 @@ void shairport_startup_complete(void) {
const char *pid_file_proc(void) {
#ifdef HAVE_ASPRINTF
static char *fn = NULL;
free(fn);
asprintf(&fn, "%s/%s.pid", PIDDIR, daemon_pid_file_ident ? daemon_pid_file_ident : "unknown");
#else
static char fn[8192];
@@ -695,6 +828,7 @@ const char *pid_file_proc(void) {
void exit_function() {
if (config.cfg)
config_destroy(config.cfg);
// probably should be freeing malloc'ed memory here, including strdup-created strings...
}
int main(int argc, char **argv) {
@@ -710,27 +844,27 @@ int main(int argc, char **argv) {
free(basec);
// set defaults
// get thje endianness
union {
uint32_t u32;
uint8_t arr[4];
uint32_t u32;
uint8_t arr[4];
} xn;
xn.arr[0] = 0x44; /* Lowest-address byte */
xn.arr[0] = 0x44; /* Lowest-address byte */
xn.arr[1] = 0x33;
xn.arr[2] = 0x22;
xn.arr[3] = 0x11; /* Highest-address byte */
if (xn.u32==0x11223344)
xn.arr[3] = 0x11; /* Highest-address byte */
if (xn.u32 == 0x11223344)
endianness = SS_LITTLE_ENDIAN;
else if (xn.u32==0x33441122)
else if (xn.u32 == 0x33441122)
endianness = SS_PDP_ENDIAN;
else if (xn.u32==0x44332211)
else if (xn.u32 == 0x44332211)
endianness = SS_BIG_ENDIAN;
else die("Can not recognise the endianness of the processor.");
else
die("Can not recognise the endianness of the processor.");
strcpy(configuration_file_path, SYSCONFDIR);
strcat(configuration_file_path, "/");
strcat(configuration_file_path, appName);
@@ -738,28 +872,48 @@ int main(int argc, char **argv) {
config.configfile = configuration_file_path;
config.statistics_requested = 0; // don't print stats in the log
config.latency = -1; // -1 means not set. 88200 works well. This is also reset in rtsp.c when play is about to start
config.userSuppliedLatency = 0; // zero means none supplied
config.iTunesLatency = -1; // -1 means not supplied. 99400 seems to work pretty well for iTunes from Version 10 (?)
// upwards-- two left-ear headphones, one from the iMac jack, one
// from an NSLU2 running a cheap "3D Sound" USB Soundcard
config.AirPlayLatency = -1; // -1 means not set. 88200 seems to work well for AirPlay -- Syncs sound and
// vision on AppleTV, but also used for iPhone/iPod/iPad sources
config.ForkedDaapdLatency = -1; // -1 means not set. 99400 seems to be right
config.resyncthreshold = 441 * 5; // this number of frames is 50 ms
config.latency = -1; // -1 means not set. 88200 works well. This is also reset in rtsp.c when play
// is about to start
config.userSuppliedLatency = 0; // zero means none supplied
config.iTunesLatency =
-1; // -1 means not supplied. 99400 seems to work pretty well for iTunes from Version 10 (?)
// upwards-- two left-ear headphones, one from the iMac jack, one
// from an NSLU2 running a cheap "3D Sound" USB Soundcard
config.AirPlayLatency =
-1; // -1 means not set. 88200 seems to work well for AirPlay -- Syncs sound and
// vision on AppleTV, but also used for iPhone/iPod/iPad sources
config.ForkedDaapdLatency = -1; // -1 means not set. 99400 seems to be right
config.resyncthreshold = 0.05; // 50 ms
config.timeout = 120; // this number of seconds to wait for [more] audio before switching to idle.
config.tolerance = 88; // this number of frames of error before attempting to correct it.
config.tolerance =
0.002; // this number of seconds of timing error before attempting to correct it.
config.buffer_start_fill = 220;
config.port = 5000;
config.packet_stuffing = ST_basic; // simple interpolation or deletion
//char hostname[100];
//gethostname(hostname, 100);
//config.service_name = malloc(20 + 100);
//snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname);
set_requested_connection_state_to_output(1); // we expect to be able to connect to the output device
// char hostname[100];
// gethostname(hostname, 100);
// config.service_name = malloc(20 + 100);
// snprintf(config.service_name, 20 + 100, "Shairport Sync on %s", hostname);
set_requested_connection_state_to_output(
1); // we expect to be able to connect to the output device
config.audio_backend_buffer_desired_length = 6615; // 0.15 seconds.
config.udp_port_base = 6001;
config.udp_port_range = 100;
config.output_format = SPS_FORMAT_S16; // default
config.output_rate = 44100; // default
config.decoders_supported =
1 << decoder_hammerton; // David Hammerton's decoder supported by default
#ifdef HAVE_APPLE_ALAC
config.decoders_supported += 1 << decoder_apple_alac;
#endif
// initialise random number generator
r64init(0);
// initialise the randomw number array
r64arrayinit();
/* Check if we are called with -V or --version parameter */
if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
@@ -854,7 +1008,7 @@ int main(int argc, char **argv) {
// mDNS supports maximum of 63-character names (we append 13).
if (strlen(config.service_name) > 50) {
warn("Supplied name too long (max 50 characters)");
config.service_name[50] = '\0'; //truncate it and carry on...
config.service_name[50] = '\0'; // truncate it and carry on...
}
/* here, daemonise with libdaemon */
@@ -924,69 +1078,84 @@ int main(int argc, char **argv) {
config.output->init(argc - audio_arg, argv + audio_arg);
// daemon_log(LOG_NOTICE, "startup");
switch (endianness) {
case SS_LITTLE_ENDIAN:
debug(2,"The processor is running little-endian.");
break;
case SS_BIG_ENDIAN:
debug(2,"The processor is running big-endian.");
break;
case SS_PDP_ENDIAN:
debug(2,"The processor is running pdp-endian.");
break;
case SS_LITTLE_ENDIAN:
debug(2, "The processor is running little-endian.");
break;
case SS_BIG_ENDIAN:
debug(2, "The processor is running big-endian.");
break;
case SS_PDP_ENDIAN:
debug(2, "The processor is running pdp-endian.");
break;
}
/* Mess around with the latency options */
// Basically, we used to rely on static latencies -- 99400 for iTunes 10 or later and forkedDaapd, 88200 for everything else
// Nowadays we allow the source to set the latency, which works out at 99651 for iTunes 10 and forkedDaapd and 88220 for everything else
// What we want to do here is allow the source to set the latency unless the user has specified an non-standard latency.
// Basically, we used to rely on static latencies -- 99400 for iTunes 10 or later and forkedDaapd,
// 88200 for everything else
// Nowadays we allow the source to set the latency, which works out at 99651 for iTunes 10 and
// forkedDaapd and 88220 for everything else
// What we want to do here is allow the source to set the latency unless the user has specified an
// non-standard latency.
// If the user has specified a standard latency, we suggest to them to stop doing it.
// If they specify a non-standard latency, we suggest the user to use the audio_backend_latency_offset instead.
if (config.AirPlayLatency!=-1) {
if (config.AirPlayLatency==88200) {
inform("It is not necessary to set the AirPlay latency to 88200 -- you should remove this setting or configuration option, as it is deprecated.");
config.AirPlayLatency= -1;
// If they specify a non-standard latency, we suggest the user to use the
// audio_backend_latency_offset instead.
if (config.AirPlayLatency != -1) {
if (config.AirPlayLatency == 88200) {
inform("It is not necessary to set the AirPlay latency to 88200 -- you should remove this "
"setting or configuration option, as it is deprecated.");
config.AirPlayLatency = -1;
} else {
inform("The AirPlay latency setting is deprecated, as Shairport Sync can now get the correct latency from the source.");
inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere.");
inform("The AirPlay latency setting is deprecated, as Shairport Sync can now get the correct "
"latency from the source.");
inform("Please remove this setting and use the relevant audio_backend_latency_offset "
"setting, if necessary, to compensate for delays elsewhere.");
}
}
if (config.iTunesLatency!=-1) {
if (config.iTunesLatency==99400) {
inform("It is not necessary to set the iTunes latency to 99400 -- you should remove this setting or configuration option, as it is deprecated and ignored.");
config.iTunesLatency= -1;
if (config.iTunesLatency != -1) {
if (config.iTunesLatency == 99400) {
inform("It is not necessary to set the iTunes latency to 99400 -- you should remove this "
"setting or configuration option, as it is deprecated and ignored.");
config.iTunesLatency = -1;
} else {
inform("The iTunes latency setting is deprecated, as Shairport Sync can now get the correct latency from the source.");
inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere.");
inform("The iTunes latency setting is deprecated, as Shairport Sync can now get the correct "
"latency from the source.");
inform("Please remove this setting and use the relevant audio_backend_latency_offset "
"setting, if necessary, to compensate for delays elsewhere.");
}
}
if (config.ForkedDaapdLatency!=-1) {
if (config.ForkedDaapdLatency==99400) {
inform("It is not necessary to set the forkedDaapd latency to 99400 -- you should remove this setting or configuration option, as it is deprecated and ignored.");
config.ForkedDaapdLatency= -1;
if (config.ForkedDaapdLatency != -1) {
if (config.ForkedDaapdLatency == 99400) {
inform("It is not necessary to set the forkedDaapd latency to 99400 -- you should remove "
"this setting or configuration option, as it is deprecated and ignored.");
config.ForkedDaapdLatency = -1;
} else {
inform("The forkedDaapd latency setting is deprecated, as Shairport Sync can now get the correct latency from the source.");
inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere.");
inform("The forkedDaapd latency setting is deprecated, as Shairport Sync can now get the "
"correct latency from the source.");
inform("Please remove this setting and use the relevant audio_backend_latency_offset "
"setting, if necessary, to compensate for delays elsewhere.");
}
}
if (config.userSuppliedLatency) {
inform("The default latency setting is deprecated, as Shairport Sync can now get the correct latency from the source.");
inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, if necessary, to compensate for delays elsewhere.");
inform("The default latency setting is deprecated, as Shairport Sync can now get the correct "
"latency from the source.");
inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, "
"if necessary, to compensate for delays elsewhere.");
}
/* print out version */
char* version_dbs = get_version_string();
char *version_dbs = get_version_string();
if (version_dbs) {
debug(1,"Version: \"%s\"",version_dbs);
debug(1, "Version: \"%s\"", version_dbs);
free(version_dbs);
} else {
debug(1,"Can't print the version information!");
debug(1, "Can't print the version information!");
}
/* Print out options */
@@ -1007,32 +1176,49 @@ int main(int argc, char **argv) {
debug(2, "iTunesLatency is %d.", config.iTunesLatency);
debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency);
debug(1, "stuffing option is \"%d\" (0-basic, 1-soxr).", config.packet_stuffing);
debug(1, "resync time is %d.", config.resyncthreshold);
debug(1, "resync time is %f seconds.", config.resyncthreshold);
debug(1, "allow a session to be interrupted: %d.", config.allow_session_interruption);
debug(1, "busy timeout time is %d.", config.timeout);
debug(1, "drift tolerance is %d frames.", config.tolerance);
debug(1, "drift tolerance is %f seconds.", config.tolerance);
debug(1, "password is \"%s\".", config.password);
debug(1, "ignore_volume_control is %d.", config.ignore_volume_control);
debug(1, "playback_mode is %d (0-stereo, 1-mono).", config.playback_mode);
if (config.volume_max_db_set)
debug(1, "volume_max_db is %d.", config.volume_max_db);
else
debug(1, "volume_max_db is not set");
debug(1, "playback_mode is %d (0-stereo, 1-mono, 1-reverse_stereo, 2-both_left, 3-both_right).",
config.playback_mode);
debug(1, "disable_synchronization is %d.", config.no_sync);
debug(1, "use_mmap_if_available is %d.", config.no_mmap ? 0 : 1);
debug(1, "audio backend desired buffer length is %d.",
debug(1, "output_rate is %d.", config.output_rate);
debug(1,
"output_format is %d (0-unknown, 1-S8, 2-U8, 3-S16, 4-S24, 5-S24_3LE, 6-S24_3BE, 7-S32).",
config.output_format);
debug(1, "audio backend desired buffer length is %f seconds.",
config.audio_backend_buffer_desired_length);
debug(1, "audio backend latency offset is %d.", config.audio_backend_latency_offset);
debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.", config.volume_range_db);
debug(1, "audio backend latency offset is %f seconds.", config.audio_backend_latency_offset);
debug(1, "volume range in dB (zero means use the range specified by the mixer): %u.",
config.volume_range_db);
debug(1, "zeroconf regtype is \"%s\".", config.regtype);
char *realConfigPath = realpath(config.configfile,NULL);
debug(1, "decoders_supported field is %d.", config.decoders_supported);
debug(1, "use_apple_decoder is %d.", config.use_apple_decoder);
if (config.interface)
debug(1, "mdns service interface \"%s\" requested.", config.interface);
else
debug(1, "no special mdns service interface was requested.");
char *realConfigPath = realpath(config.configfile, NULL);
if (realConfigPath) {
debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile,realConfigPath);
debug(1, "configuration file name \"%s\" resolves to \"%s\".", config.configfile,
realConfigPath);
free(realConfigPath);
} else {
debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile);
}
debug(1, "configuration file name \"%s\" can not be resolved.", config.configfile);
}
#ifdef CONFIG_METADATA
debug(1, "metdata enabled is %d.", config.metadata_enabled);
debug(1, "metadata pipename is \"%s\".", config.metadata_pipename);
debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr, config.metadata_sockport);
debug(1, "metadata socket address is \"%s\" port %d.", config.metadata_sockaddr,
config.metadata_sockport);
debug(1, "metadata socket packet size is \"%d\".", config.metadata_sockmsglength);
debug(1, "get-coverart is %d.", config.get_coverart);
#endif
@@ -1046,6 +1232,13 @@ int main(int argc, char **argv) {
MD5_Final(ap_md5, &ctx);
#endif
#ifdef HAVE_LIBMBEDTLS
mbedtls_md5_context tctx;
mbedtls_md5_starts(&tctx);
mbedtls_md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
mbedtls_md5_finish(&tctx, ap_md5);
#endif
#ifdef HAVE_LIBPOLARSSL
md5_context tctx;
md5_starts(&tctx);
@@ -1056,7 +1249,7 @@ int main(int argc, char **argv) {
#ifdef CONFIG_METADATA
metadata_init(); // create the metadata pipe if necessary
#endif
daemon_log(LOG_INFO,"Successful Startup");
daemon_log(LOG_INFO, "Successful Startup");
rtsp_listen_loop();
// should not reach this...
+15 -15
View File
@@ -48,15 +48,15 @@
//******************************************************//
// mdns.c //
//******************************************************//
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <winsock.h>
#include <in6addr.h>
#include <winsock.h>
#else
#include <netinet/in.h>
#endif
@@ -1029,26 +1029,26 @@ size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len
#include <ws2tcpip.h>
#define LOG_ERR 3
#else
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <syslog.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
/*
* Define a proper IP socket level if not already done.