From bdc2dca3ec485f8afec8a3b8d2d06550dd49a0ac Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 6 Jul 2020 17:09:15 +0100
Subject: [PATCH] 3.3.7rc0
---
FREEBSD.md | 2 +-
README.md | 30 +-
activity_monitor.c | 20 +-
alac.h | 1 -
audio.c | 42 +-
audio_alsa.c | 366 +++--
audio_dummy.c | 2 +-
audio_jack.c | 61 +-
audio_pa.c | 8 +-
audio_pipe.c | 50 +-
audio_sndio.c | 14 +-
audio_soundio.c | 5 +-
audio_stdout.c | 2 +-
common.c | 303 ++--
common.h | 44 +-
dacp.c | 723 +++++-----
dbus-service.c | 69 +-
man/shairport-sync.7 | 20 +-
man/shairport-sync.7.xml | 634 ++++-----
man/shairport-sync.html | 1219 +---------------
mdns_avahi.c | 12 +-
mdns_dns_sd.c | 2 +-
mdns_external.c | 2 +-
metadata_hub.c | 199 +--
metadata_hub.h | 16 +-
mpris-service.c | 19 +-
mqtt.c | 14 +-
player.c | 1618 +++++++++++-----------
player.h | 27 +-
rtp.c | 402 +++---
rtsp.c | 446 ++++--
scripts/shairport-sync-dbus-policy.conf | 6 +-
scripts/shairport-sync-mpris-policy.conf | 5 +-
scripts/shairport-sync.conf | 49 +-
shairport-sync-dbus-test-client.c | 26 +-
shairport.c | 344 +++--
tinysvcmdns.c | 7 +-
37 files changed, 3138 insertions(+), 3671 deletions(-)
diff --git a/FREEBSD.md b/FREEBSD.md
index 057a96f2c..fe3a2a5e8 100644
--- a/FREEBSD.md
+++ b/FREEBSD.md
@@ -60,7 +60,7 @@ Next, configure the build and compile it:
```
$ autoreconf -i -f
-./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
+$ ./configure --with-libdaemon --with-avahi --with-ssl=openssl --with-sndio --with-libdaemon --with-os=freebsd --with-freebsd-service
$ make
```
Add `--with-alsa` if you wish to include the ALSA back end. Omit the `--with-sndio` if you don't want the `sndio` back end. Omit the `--with-freebsd-service` if you don't want to install a FreeBSD startup script, runtime folder and user and group -- see below for more details.
diff --git a/README.md b/README.md
index a6821f312..fde314f9f 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ For more about the motivation behind Shairport Sync, please see the wiki at http
Synchronisation, Latency, "Stuffing"
---------
-The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on.
+The AirPlay protocol uses an agreed *latency* – the time difference, or delay, between the time represented by a sound sample's `timestamp` and the time it is actually played by the audio output device, typically a Digital to Audio Converter (DAC). The latency to be used is specified by the audio source when it negotiates with Shairport Sync. Most sources set a latency of two seconds. Recent versions of iTunes and forkedDaapd use a latency of just over 2.25 seconds. A latency of this length allows AirPlay players to correct for network delays, processing time variations and so on.
As mentioned previously, Shairport Sync implements full audio synchronisation when used with `alsa`, `sndio` or PulseAudio systems. This is done by monitoring the timestamps present in data coming from the audio source and the timing information from the audio system, e.g. `alsa`. To maintain the latency required for exact synchronisation, if the output device is running slow relative to the source, Shairport Sync will delete frames of audio to allow the device to keep up. If the output device is running fast, Shairport Sync will insert frames to keep time. The number of frames inserted or deleted is so small as to be almost inaudible on normal audio material. Frames are inserted or deleted as necessary at pseudorandom intervals. Alternatively, with `libsoxr` support, Shairport Sync can resample the audio feed to ensure the output device can keep up. This is less obtrusive than insertion and deletion but requires a good deal of processing power — most embedded devices probably can't support it. The process of insertion/deletion or resampling is rather inelegantly called “stuffing”.
@@ -42,7 +42,7 @@ What else?
* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/) interface, partially complete and very functional, including access to metadata and artwork, and some limited remote control.
* An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), an often-used protocol in home automation projects.
* A native D-Bus interface, including access to metadata and artwork, some limited remote control and some system settings.
-
+
Heritage
-------
Shairport Sync is a substantial rewrite of the fantastic work done in Shairport 1.0 by James Laird and others — please see https://github.com/abrasive/shairport/blob/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.
@@ -67,7 +67,7 @@ See [here](https://github.com/mikebrady/shairport-sync/blob/master/INSTALL.md) f
Shairport Sync may already be available as a package in your Linux distribution (search for `shairport-sync` – the package named `shairport` is a different program). Packages are available on recent versions of Debian, Ubuntu, Arch, OpenWrt and possibly more:
-**Ubuntu:** A `shairport-sync` installer package is available for Ubuntu. Additionally, a Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson.
+**Ubuntu:** A `shairport-sync` installer package is available for Ubuntu. Additionally, a Personal Package Archives for Shairport Sync master and development branches are available at https://launchpad.net/~dantheperson.
**Debian:** shairport-sync is in the Debian archive.
@@ -77,7 +77,7 @@ Shairport Sync may already be available as a package in your Linux distribution
To build and install the latest version of Shairport Sync, an [Arch Linux build and installation guide](https://github.com/mikebrady/shairport-sync-for-arch-linux) is available, based on original work by [Elia Cereda](https://github.com/EliaCereda).
-**Mac OS X:** A HomeBrew package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command:
+**Mac OS X:** A HomeBrew package exists for Shairport Sync. With HomeBrew installed, Shairport Sync can be installed using the command:
```
$brew install shairport-sync
```
@@ -93,7 +93,7 @@ If you wish to build and install the latest version of Shairport Sync on Debian,
**Remove Old Versions of Shairport Sync and its Startup Scripts**
-You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
+You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
You should also remove the startup script files `/etc/systemd/system/shairport-sync.service`, `/lib/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
@@ -125,13 +125,13 @@ If PulseAudio in not installed, you'll get something like this:
```
$ pactl info
-bash: pactl: command not found
-$
+$
```
If your system does not use PulseAudio, then it is likely that it uses the Advanced Linux Sound Architecture (ALSA), so you should build Shairport Sync with the ALSA backend. By the way, many systems with PulseAudio also have ALSA (in fact, PulseAudio is effectively a client of ALSA); in those cases you should choose the PulseAudio backend.
If PulseAudio is not installed, there is no necessity to install it for Shairport Sync. In fact, Shairport Sync works better without it.
-**Building**
+**Building**
To build Shairport Sync from sources on Debian, Ubuntu, Raspbian, etc. follow these instructions.
@@ -181,7 +181,7 @@ $ autoreconf -i -f
(Don't worry -- there's a recommended set of configuration options further down.)
-- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality.
+- `--with-alsa` include the ALSA backend module to audio to be output through the Advanced Linux Sound Architecture (ALSA) system directly. This is recommended for highest quality.
- `--with-pa` include the PulseAudio audio back end. This is recommended if your Linux installation already has PulseAudio installed. Although ALSA would be better, it requires direct and exclusive access to to a real (hardware) soundcard, and this is often impractical if PulseAudio is installed.
- `--with-stdout` include an optional backend module to enable raw audio to be output through standard output (stdout).
- `--with-pipe` include an optional backend module to enable raw audio to be output through a unix pipe.
@@ -201,9 +201,9 @@ $ autoreconf -i -f
**Determine if it's a `systemd` or a "System V" installation:**
-If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.)
+If you wish to have Shairport Sync start automatically when your system boots, you need to figure out what so-called "init system" your system is using. (If you are using Shairport Sync with PulseAudio, as installed in many desktop systems, this section doesn't apply.)
-There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system.
+There are a number of init systems in use: `systemd`, `upstart` and "System V" among others, and it's actually difficult to be certain which one your system is using. Fortunately, for Shairport Sync, all you have to do is figure out if it's a `systemd` init system or not. If it is not a `systemd` init system, you can assume that it is either a System V init system or else it is compatible with a System V init system. Recent systems tend to use `systemd`, whereas older systems use `upstart` or the earlier System V init system.
The easiest way is to look at the first few lines of the `init` manual. Enter the command:
@@ -365,7 +365,7 @@ Note: Shairport Sync can take configuration settings from command line options.
**Raspberry Pi**
-The Raspberry Pi Models A and B have a built-in audio DAC that is connected to the device's headphone jack. Apart from a loud click when used for the first time after power-up, it is now quite adequate for casual listening.
+The Raspberry Pi Models A and B have a built-in audio DAC that is connected to the device's headphone jack. Apart from a loud click when used for the first time after power-up, it is now quite adequate for casual listening.
To get the benefits of improvements in the Pi's software and firmware, you should update to the Raspian release of October 2018 or later, as a number of improvements have been made to the built-in DAC.
@@ -405,7 +405,7 @@ The System V init script at `/etc/init.d/shairport-sync` has a bare minimum :
Examples
--------
-Here are some examples of complete configuration files.
+Here are some examples of complete configuration files.
```
general = {
@@ -518,7 +518,7 @@ The UDP metadata format is very simple - the first four bytes are the metadata *
Latency
-------
-Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer.
+Latency is the exact time from a sound signal's original timestamp until that signal actually "appears" on the output of the audio output device, usually a Digital to Audio Converter (DAC), irrespective of any internal delays, processing times, etc. in the computer.
Shairport Sync uses latencies supplied by the source, typically either 2 seconds or just over 2.25 seconds. You shouldn't need to change them.
@@ -526,7 +526,7 @@ Problems can arise when you are trying to synchronise with speaker systems — t
Resynchronisation
-------------
-Shairport Sync actively maintains synchronisation with the source.
+Shairport Sync actively maintains synchronisation with the source.
If synchronisation is lost — say due to a busy source or a congested network — Shairport Sync will mute its output and resynchronise. The loss-of-sync threshold is a very conservative 0.050 seconds — i.e. the actual time and the expected time must differ by more than 50 ms to trigger a resynchronisation. Smaller disparities are corrected by insertions or deletions, as described above.
* You can vary the resync threshold, or turn resync off completely, with the `general` `resync_threshold_in_seconds` setting.
@@ -537,7 +537,7 @@ Playback synchronisation is allowed to wander — to "drift" — a small amount
Some Statistics
---------------
-If you turn on the `general` `statistics` setting, a heading like this will be output to the console or log file:
+If you turn on the `general` `statistics` setting, a heading like this will be output to the log file (or to `STDERR` if the `-u` command line option is chosen):
```
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, source nominal frames per second, source actual frames per second, output frames per second, source clock drift in ppm, source clock drift sample count, rough calculated correction in ppm
```
diff --git a/activity_monitor.c b/activity_monitor.c
index bcc25d245..735fd03ad 100644
--- a/activity_monitor.c
+++ b/activity_monitor.c
@@ -62,7 +62,7 @@ void going_active(int block) {
command_execute(config.cmd_active_start, "", block);
#ifdef CONFIG_METADATA
debug(2, "abeg"); // active mode begin
- send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
+ send_ssnc_metadata('abeg', NULL, 0, 1); // contains cancellation points
#endif
#ifdef CONFIG_DBUS_INTERFACE
@@ -89,7 +89,7 @@ void going_inactive(int block) {
command_execute(config.cmd_active_stop, "", block);
#ifdef CONFIG_METADATA
debug(2, "aend"); // active mode end
- send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
+ send_ssnc_metadata('aend', NULL, 0, 1); // contains cancellation points
#endif
#ifdef CONFIG_DBUS_INTERFACE
@@ -196,21 +196,19 @@ void *activity_monitor_thread_code(void *arg) {
} else {
state = am_timing_out;
- uint64_t time_to_wait_for_wakeup_fp =
- (uint64_t)(config.active_state_timeout * 1000000); // resolution of microseconds
- time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32;
- time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000;
+ uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(config.active_state_timeout * 1000000000);
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
- uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp;
- sec = time_of_wakeup_fp >> 32;
- nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
+ sec = time_of_wakeup_ns / 1000000000;
+ nsec = time_of_wakeup_ns % 1000000000;
time_for_wait.tv_sec = sec;
time_for_wait.tv_nsec = nsec;
#endif
+
#ifdef COMPILE_FOR_OSX
- sec = time_to_wait_for_wakeup_fp >> 32;
- nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+ sec = time_to_wait_for_wakeup_ns / 1000000000;
+ nsec = time_to_wait_for_wakeup_ns % 1000000000;
time_for_wait.tv_sec = sec;
time_for_wait.tv_nsec = nsec;
#endif
diff --git a/alac.h b/alac.h
index 7cdd507df..51db2c60c 100644
--- a/alac.h
+++ b/alac.h
@@ -27,7 +27,6 @@
*
*/
-
#ifndef __ALAC__DECOMP_H
#define __ALAC__DECOMP_H
diff --git a/audio.c b/audio.c
index 8b2762f45..23b1760dc 100644
--- a/audio.c
+++ b/audio.c
@@ -126,6 +126,7 @@ void parse_general_audio_options(void) {
* are set before any options are chosen */
int value;
double dvalue;
+ const char *str = 0;
if (config.cfg != NULL) {
/* Get the desired buffer size setting (deprecated). */
@@ -147,10 +148,10 @@ void parse_general_audio_options(void) {
/* Get the desired buffer size setting in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
- if ((dvalue < 0) || (dvalue > 1.5)) {
+ if (dvalue < 0) {
die("Invalid audio_backend_buffer_desired_length_in_seconds value: \"%f\". It "
- "should be between 0 and "
- "1.5, default is %.3f seconds",
+ "should be 0.0 or greater."
+ " The default is %.3f seconds",
dvalue, config.audio_backend_buffer_desired_length);
} else {
config.audio_backend_buffer_desired_length = dvalue;
@@ -190,23 +191,42 @@ void parse_general_audio_options(void) {
/* Get the latency offset in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds",
&dvalue)) {
- if ((dvalue < -1.75) || (dvalue > 1.75)) {
- die("Invalid audio_backend_latency_offset_in_seconds \"%f\". It "
- "should be between -1.75 and +1.75, default is 0 seconds",
- dvalue);
+ config.audio_backend_latency_offset = dvalue;
+ }
+
+ /* Check if the length of the silent lead-in ia set to \"auto\". */
+ if (config_lookup_string(config.cfg, "general.audio_backend_silent_lead_in_time", &str)) {
+ if (strcasecmp(str, "auto") == 0) {
+ config.audio_backend_silent_lead_in_time_auto = 1;
} else {
- config.audio_backend_latency_offset = dvalue;
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ warn("Invalid audio_backend_silent_lead_in_time \"%s\". It should be \"auto\" or the "
+ "lead-in time in seconds. "
+ "It remains set to \"auto\". Note: numbers should not be placed in quotes.",
+ str);
+ else
+ warn("Invalid output rate \"%s\". It should be \"auto\" or the lead-in time in seconds. "
+ "It remains set to %f. Note: numbers should not be placed in quotes.",
+ str, config.audio_backend_silent_lead_in_time);
}
}
/* Get the desired length of the silent lead-in. */
if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", &dvalue)) {
if ((dvalue < 0.0) || (dvalue > 4)) {
- die("Invalid audio_backend_silent_lead_in_time \"%f\". It "
- "must be between 0.0 and 4.0 seconds. Omit setting to use the default value",
- dvalue);
+ if (config.audio_backend_silent_lead_in_time_auto == 1)
+ warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
+ "The setting remains at \"auto\".",
+ dvalue);
+ else
+ warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
+ "must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
+ "It remains set to %f.",
+ dvalue, config.audio_backend_silent_lead_in_time);
} else {
config.audio_backend_silent_lead_in_time = dvalue;
+ config.audio_backend_silent_lead_in_time_auto = 0;
}
}
}
diff --git a/audio_alsa.c b/audio_alsa.c
index 39e4d0514..69722d12f 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -210,9 +210,6 @@ int precision_delay_available() {
generate_zero_frames(silence, frames_of_silence, config.output_format,
use_dither, // i.e. with dither
dither_random_number_store);
- // debug(1,"Play %d frames of silence with most_recent_write_time of
- // %" PRIx64 ".",
- // frames_of_silence,most_recent_write_time);
do_play(silence, frames_of_silence);
pthread_cleanup_pop(1);
// now we can get the delay, and we'll note if it uses update timestamps
@@ -255,8 +252,6 @@ static uint64_t frames_played_at_measurement_start_time;
static uint64_t measurement_time;
static uint64_t frames_played_at_measurement_time;
-volatile uint64_t most_recent_write_time;
-
static uint64_t frames_sent_for_playing;
static uint64_t frame_index;
static int measurement_data_is_valid;
@@ -342,8 +337,9 @@ void actual_close_alsa_device() {
if (alsa_handle) {
int derr;
if ((derr = snd_pcm_hw_free(alsa_handle)))
- debug(1, "Error %d (\"%s\") freeing the output device hardware while "
- "closing it.",
+ debug(1,
+ "Error %d (\"%s\") freeing the output device hardware while "
+ "closing it.",
derr, snd_strerror(derr));
if ((derr = snd_pcm_close(alsa_handle)))
@@ -358,7 +354,10 @@ void actual_close_alsa_device() {
// The lowest rate that the DAC is capable of is chosen.
unsigned int auto_speed_output_rates[] = {
- 44100, 88200, 176400, 352800,
+ 44100,
+ 88200,
+ 176400,
+ 352800,
};
// This array is of all the formats known to Shairport Sync, in order of the SPS_FORMAT definitions,
@@ -707,9 +706,10 @@ int actual_open_alsa_device(int do_auto_setup) {
buffer_size);
}
*/
- debug(1, "The alsa buffer is smaller (%lu bytes) than the desired backend "
- "buffer "
- "length (%ld) you have chosen.",
+ debug(1,
+ "The alsa buffer is smaller (%lu bytes) than the desired backend "
+ "buffer "
+ "length (%ld) you have chosen.",
actual_buffer_length, config.audio_backend_buffer_desired_length);
}
@@ -869,120 +869,117 @@ int prepare_mixer() {
// do any alsa device initialisation (general case)
// at present, this is only needed if a hardware mixer is being used
// if there's a hardware mixer, it needs to be initialised before use
- if (alsa_mix_ctrl == NULL) {
- audio_alsa.volume = NULL;
- audio_alsa.parameters = NULL;
- audio_alsa.mute = NULL;
- } else {
- debug(2, "alsa: hardware mixer prepare");
- int oldState;
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
-
- if (alsa_mix_dev == NULL)
- alsa_mix_dev = alsa_out_dev;
-
- // Now, start trying to initialise the alsa device with the settings
- // obtained
- pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
- if (open_mixer() == 1) {
- 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) {
-
- audio_alsa.volume = &volume; // insert the volume function now we
- // know it can do dB stuff
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
- // For instance, the Raspberry Pi does this
- debug(1, "Lowest dB value is a mute");
- mixer_volume_setting_gives_mute = 1;
- alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be
- // necessary -- it's
- // always
- // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
- // debug(1, "Try minimum volume + 1 as lowest true attenuation
- // value");
- 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(3, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
- (1.0 * alsa_mix_maxdb) / 100.0);
+ if (alsa_mix_ctrl == NULL) {
+ audio_alsa.volume = NULL;
+ audio_alsa.parameters = NULL;
+ audio_alsa.mute = NULL;
+ } else {
+ debug(2, "alsa: hardware mixer prepare");
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+
+ if (alsa_mix_dev == NULL)
+ alsa_mix_dev = alsa_out_dev;
+
+ // Now, start trying to initialise the alsa device with the settings
+ // obtained
+ pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
+ if (open_mixer() == 1) {
+ 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) {
+
+ audio_alsa.volume = &volume; // insert the volume function now we
+ // know it can do dB stuff
+ audio_alsa.parameters = ¶meters; // likewise the parameters stuff
+ if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
+ // For instance, the Raspberry Pi does this
+ debug(1, "Lowest dB value is a mute");
+ mixer_volume_setting_gives_mute = 1;
+ alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be
+ // necessary -- it's
+ // always
+ // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
+ // debug(1, "Try minimum volume + 1 as lowest true attenuation
+ // value");
+ 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(3, "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
+ warn("The hardware mixer specified -- \"%s\" -- does not have "
+ "a dB volume scale.",
+ alsa_mix_ctrl);
+
+ if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
+ warn("Cannot open control \"%s\"", alsa_mix_dev);
+ response = -1;
+ }
+ if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
+ debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
+ elem_id = NULL;
+ response = -2;
} else {
- // use the linear scale and do the db conversion ourselves
- warn("The hardware mixer specified -- \"%s\" -- does not have "
- "a dB volume scale.",
- alsa_mix_ctrl);
-
- if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
- warn("Cannot open control \"%s\"", alsa_mix_dev);
- response = -1;
- }
- if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
- debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
- elem_id = NULL;
- response = -2;
+ 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,
+ "alsa: hardware mixer \"%s\" selected, with 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;
+ audio_alsa.volume = &volume; // insert the volume function now
+ // we know it can do dB stuff
+ audio_alsa.parameters = ¶meters; // likewise the parameters stuff
} else {
- 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, "alsa: hardware mixer \"%s\" selected, with 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;
- audio_alsa.volume = &volume; // insert the volume function now
- // we know it can do dB stuff
- audio_alsa.parameters = ¶meters; // likewise the parameters stuff
- } 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);
}
- /*
- debug(1, "Min and max volumes are %d and
- %d.",alsa_mix_minv,alsa_mix_maxv);
- alsa_mix_maxdb = 0;
- if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
- alsa_mix_mindb =
- -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
- else if (alsa_mix_maxv!=0)
- alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
- audio_alsa.volume = &linear_volume; // insert the linear volume
- function
- audio_alsa.parameters = ¶meters; // likewise the parameters
- stuff
- debug(1,"Max and min dB calculated are %d and
- %d.",alsa_mix_maxdb,alsa_mix_mindb);
- */
}
+ /*
+ debug(1, "Min and max volumes are %d and
+ %d.",alsa_mix_minv,alsa_mix_maxv);
+ alsa_mix_maxdb = 0;
+ if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
+ alsa_mix_mindb =
+ -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
+ else if (alsa_mix_maxv!=0)
+ alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
+ audio_alsa.volume = &linear_volume; // insert the linear volume
+ function
+ audio_alsa.parameters = ¶meters; // likewise the parameters
+ stuff
+ debug(1,"Max and min dB calculated are %d and
+ %d.",alsa_mix_maxdb,alsa_mix_mindb);
+ */
}
- if (((config.alsa_use_hardware_mute == 1) &&
- (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
- mixer_volume_setting_gives_mute) {
- audio_alsa.mute = &mute; // insert the mute function now we know it
- // can do muting stuff
- // debug(1, "Has mixer and mute ability we will use.");
- } else {
- // debug(1, "Has mixer but not using hardware mute.");
- }
- close_mixer();
}
- debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
- pthread_cleanup_pop(0);
- pthread_setcancelstate(oldState, NULL);
+ if (((config.alsa_use_hardware_mute == 1) &&
+ (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
+ mixer_volume_setting_gives_mute) {
+ audio_alsa.mute = &mute; // insert the mute function now we know it
+ // can do muting stuff
+ // debug(1, "Has mixer and mute ability we will use.");
+ } else {
+ // debug(1, "Has mixer but not using hardware mute.");
+ }
+ close_mixer();
}
+ debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
+ pthread_cleanup_pop(0);
+ pthread_setcancelstate(oldState, NULL);
+ }
return response;
}
-int alsa_device_init() {
- return prepare_mixer();
-}
-
+int alsa_device_init() { return prepare_mixer(); }
static int init(int argc, char **argv) {
// for debugging
@@ -1140,8 +1137,10 @@ static int init(int argc, char **argv) {
"\"S16\", \"S24\", \"S24_LE\", \"S24_BE\", "
"\"S24_3LE\", \"S24_3BE\" or "
"\"S32\", \"S32_LE\", \"S32_BE\". It remains set to \"%s\".",
- str, config.output_format_auto_requested == 1 ? "auto" : sps_format_description_string(
- config.output_format));
+ str,
+ config.output_format_auto_requested == 1
+ ? "auto"
+ : sps_format_description_string(config.output_format));
}
}
@@ -1291,9 +1290,9 @@ static int init(int argc, char **argv) {
warn("Invalid use_precision_timing option choice \"%s\". It should be "
"\"yes\", \"auto\" or \"no\". "
"It remains set to \"%s\".",
- config.use_precision_timing == YNA_NO ? "no" : config.use_precision_timing == YNA_AUTO
- ? "auto"
- : "yes");
+ config.use_precision_timing == YNA_NO
+ ? "no"
+ : config.use_precision_timing == YNA_AUTO ? "auto" : "yes");
}
}
@@ -1350,7 +1349,6 @@ static int init(int argc, char **argv) {
// length of the queue
// if the queue gets too short, stuff it with silence
- most_recent_write_time = 0; // could be used by the alsa_buffer_monitor_thread_code
pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL);
return response;
@@ -1525,14 +1523,16 @@ int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) ||
((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) {
- debug(2, "DAC seems to have stalled with time_now_ns: %" PRIX64
- ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64
- ", stall_monitor_error_threshold %" PRIX64 ".",
+ debug(2,
+ "DAC seems to have stalled with time_now_ns: %" PRIX64
+ ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64
+ ", stall_monitor_error_threshold %" PRIX64 ".",
time_now_ns, update_timestamp_ns, stall_monitor_start_time,
stall_monitor_error_threshold);
- debug(2, "DAC seems to have stalled with time_now: %lx,%lx"
- ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
- ", stall_monitor_error_threshold %" PRIX64 ".",
+ debug(2,
+ "DAC seems to have stalled with time_now: %lx,%lx"
+ ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
+ ", stall_monitor_error_threshold %" PRIX64 ".",
tn.tv_sec, tn.tv_nsec, update_timestamp.tv_sec, update_timestamp.tv_nsec,
stall_monitor_start_time, stall_monitor_error_threshold);
ret = sps_extra_code_output_stalled;
@@ -1605,6 +1605,7 @@ int delay(long *the_delay) {
}
int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) {
+ // elapsed_time is in nanoseconds
int response = 0; // zero means okay
if (measurement_data_is_valid) {
*elapsed_time = measurement_time - measurement_start_time;
@@ -1657,7 +1658,7 @@ int do_play(void *buf, int samples) {
if ((frame_index == start_measurement_from_this_frame) ||
((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) {
- measurement_time = get_absolute_time_in_fp();
+ measurement_time = get_absolute_time_in_ns();
frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples;
if (frame_index == start_measurement_from_this_frame) {
@@ -1697,8 +1698,9 @@ int do_play(void *buf, int samples) {
}
}
} else {
- debug(1, "alsa: device status returns fault status %d and SND_PCM_STATE_* "
- "%d for play.",
+ debug(1,
+ "alsa: device status returns fault status %d and SND_PCM_STATE_* "
+ "%d for play.",
ret, state);
frame_index = 0;
measurement_data_is_valid = 0;
@@ -1744,6 +1746,7 @@ int do_close() {
// debug(1,"alsa: do_close() -- closing the output device");
if ((derr = snd_pcm_drop(alsa_handle)))
debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr));
+ usleep(5000);
if ((derr = snd_pcm_hw_free(alsa_handle)))
debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr));
@@ -1946,7 +1949,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
alsa_device_init();
alsa_device_initialised = 1;
}
- int sleep_time_ms = (int)(config.disable_standby_mode_silence_scan_interval * 1000);
+ int sleep_time_us = (int)(config.disable_standby_mode_silence_scan_interval * 1000000);
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0);
// check possible state transitions here
if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) {
@@ -1974,65 +1977,60 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
int reply;
long buffer_size = 0;
snd_pcm_state_t state;
- uint64_t present_time = get_absolute_time_in_fp();
- if ((most_recent_write_time == 0) || (present_time > most_recent_write_time)) {
- reply = delay_and_status(&state, &buffer_size, NULL);
- if (reply != 0) {
- buffer_size = 0;
- char errorstring[1024];
- strerror_r(-reply, (char *)errorstring, sizeof(errorstring));
- debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply,
- (char *)errorstring);
- }
- long buffer_size_threshold =
- (long)(config.disable_standby_mode_silence_threshold * config.output_rate);
- size_t size_of_silence_buffer;
- if (buffer_size < buffer_size_threshold) {
- uint64_t sleep_time_in_fp = sleep_time_ms;
- sleep_time_in_fp = sleep_time_in_fp << 32;
- sleep_time_in_fp = sleep_time_in_fp / 1000;
- int frames_of_silence = 1024;
- size_of_silence_buffer = frames_of_silence * frame_size;
- void *silence = malloc(size_of_silence_buffer);
- if (silence == NULL) {
- warn("disable_standby_mode has been turned off because a memory allocation error "
- "occurred.");
- error_detected = 1;
- } else {
- int ret;
- pthread_cleanup_push(malloc_cleanup, silence);
- int use_dither = 0;
- if ((alsa_mix_ctrl == NULL) && (config.ignore_volume_control == 0) &&
- (config.airplay_volume != 0.0))
- use_dither = 1;
- dither_random_number_store =
- generate_zero_frames(silence, frames_of_silence, config.output_format,
- use_dither, // i.e. with dither
- dither_random_number_store);
- ret = do_play(silence, frames_of_silence);
- frame_count++;
- pthread_cleanup_pop(1); // free malloced buffer
- if (ret < 0) {
- error_count++;
- char errorstring[1024];
- strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
- debug(2, "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
- "to alsa device -- %d errors in %d trials.",
- ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
- if ((error_count > 40) && (frame_count < 100)) {
- warn("disable_standby_mode has been turned off because too many underruns "
- "occurred. Is Shairport Sync outputting to a virtual device or running in a "
- "virtual machine?");
- error_detected = 1;
- }
+ reply = delay_and_status(&state, &buffer_size, NULL);
+ if (reply != 0) {
+ buffer_size = 0;
+ char errorstring[1024];
+ strerror_r(-reply, (char *)errorstring, sizeof(errorstring));
+ debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply,
+ (char *)errorstring);
+ }
+ long buffer_size_threshold =
+ (long)(config.disable_standby_mode_silence_threshold * config.output_rate);
+ size_t size_of_silence_buffer;
+ if (buffer_size < buffer_size_threshold) {
+ int frames_of_silence = 1024;
+ size_of_silence_buffer = frames_of_silence * frame_size;
+ void *silence = malloc(size_of_silence_buffer);
+ if (silence == NULL) {
+ warn("disable_standby_mode has been turned off because a memory allocation error "
+ "occurred.");
+ error_detected = 1;
+ } else {
+ int ret;
+ pthread_cleanup_push(malloc_cleanup, silence);
+ int use_dither = 0;
+ if ((alsa_mix_ctrl == NULL) && (config.ignore_volume_control == 0) &&
+ (config.airplay_volume != 0.0))
+ use_dither = 1;
+ dither_random_number_store =
+ generate_zero_frames(silence, frames_of_silence, config.output_format,
+ use_dither, // i.e. with dither
+ dither_random_number_store);
+ ret = do_play(silence, frames_of_silence);
+ frame_count++;
+ pthread_cleanup_pop(1); // free malloced buffer
+ if (ret < 0) {
+ error_count++;
+ char errorstring[1024];
+ strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
+ debug(2,
+ "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
+ "to alsa device -- %d errors in %d trials.",
+ ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
+ if ((error_count > 40) && (frame_count < 100)) {
+ warn("disable_standby_mode has been turned off because too many underruns "
+ "occurred. Is Shairport Sync outputting to a virtual device or running in a "
+ "virtual machine?");
+ error_detected = 1;
}
}
}
}
}
debug_mutex_unlock(&alsa_mutex, 0);
- pthread_cleanup_pop(0); // release the mutex
- usleep(sleep_time_ms * 1000); // has a cancellation point in it
+ pthread_cleanup_pop(0); // release the mutex
+ usleep(sleep_time_us); // has a cancellation point in it
}
pthread_exit(NULL);
}
diff --git a/audio_dummy.c b/audio_dummy.c
index 5d64ecf23..5d16bf95e 100644
--- a/audio_dummy.c
+++ b/audio_dummy.c
@@ -39,7 +39,7 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
static void deinit(void) {}
static void start(int sample_rate, __attribute__((unused)) int sample_format) {
- debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
+ debug(1, "dummy audio output started at %d frames per second.", sample_rate);
}
static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {
diff --git a/audio_jack.c b/audio_jack.c
index db6e18994..21c3e5431 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -89,14 +89,9 @@ typedef struct soxr_quality {
const char *name;
} soxr_quality_t;
-static soxr_quality_t soxr_quality_table[] = {
- { SOXR_VHQ, "very high" },
- { SOXR_HQ, "high" },
- { SOXR_MQ, "medium" },
- { SOXR_LQ, "low" },
- { SOXR_QQ, "quick" },
- { -1, NULL }
-};
+static soxr_quality_t soxr_quality_table[] = {{SOXR_VHQ, "very high"}, {SOXR_HQ, "high"},
+ {SOXR_MQ, "medium"}, {SOXR_LQ, "low"},
+ {SOXR_QQ, "quick"}, {-1, NULL}};
static int parse_soxr_quality_name(const char *name) {
for (soxr_quality_t *s = soxr_quality_table; s->name != NULL; ++s) {
@@ -107,9 +102,9 @@ static int parse_soxr_quality_name(const char *name) {
return -1;
}
-static soxr_t soxr = NULL;
+static soxr_t soxr = NULL;
static soxr_quality_spec_t quality_spec;
-static soxr_io_spec_t io_spec;
+static soxr_io_spec_t io_spec;
#endif
static inline sample_t sample_conv(short sample) {
@@ -122,8 +117,7 @@ static inline sample_t sample_conv(short sample) {
return ((sample < 0) ? (-1.0 * sample / SHRT_MIN) : (1.0 * sample / SHRT_MAX));
}
-static void deinterleave(const char *interleaved_input_buffer,
- sample_t *jack_output_buffer[],
+static void deinterleave(const char *interleaved_input_buffer, sample_t *jack_output_buffer[],
jack_nframes_t offset, jack_nframes_t nframes) {
jack_nframes_t f;
// We're dealing with 16bit audio here:
@@ -271,7 +265,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
io_spec = soxr_io_spec(SOXR_INT16_I, SOXR_FLOAT32_I);
} else
#endif
- if (sample_rate != 44100) {
+ if (sample_rate != 44100) {
die("The JACK server is running at the wrong sample rate (%d) for Shairport Sync."
" Must be 44100 Hz.",
sample_rate);
@@ -353,8 +347,7 @@ void jack_deinit() {
#endif
}
-void jack_start(int i_sample_rate,
- __attribute__((unused)) int i_sample_format) {
+void jack_start(int i_sample_rate, __attribute__((unused)) int i_sample_format) {
// Nothing to do, JACK client has already been set up at jack_init().
// Also, we have no say over the sample rate or sample format of JACK,
// We convert the 16bit samples to float, and die if the sample rate is != 44k1 without soxr.
@@ -365,13 +358,7 @@ void jack_start(int i_sample_rate,
soxr_delete(soxr);
}
soxr_error_t e = NULL;
- soxr = soxr_create(i_sample_rate,
- sample_rate,
- NPORTS,
- &e,
- &io_spec,
- &quality_spec,
- NULL);
+ soxr = soxr_create(i_sample_rate, sample_rate, NPORTS, &e, &io_spec, &quality_spec, NULL);
if (!soxr) {
die("Unable to create soxr resampler for JACK: %s", e);
}
@@ -395,13 +382,13 @@ int jack_delay(long *the_delay) {
// occupancy after another transfer had occurred, so we could "lose" a full transfer
// (e.g. 1024 frames @ 44,100 fps ~ 23.2 milliseconds)
pthread_mutex_lock(&buffer_mutex);
- int64_t time_now = get_absolute_time_in_fp();
- int64_t delta = time_now - time_of_latest_transfer;
+ int64_t time_now = get_absolute_time_in_ns();
+ int64_t delta = time_now - time_of_latest_transfer; // nanoseconds
size_t audio_occupancy_now = jack_ringbuffer_read_space(jackbuf) / bytes_per_frame;
debug(2, "audio_occupancy_now is %d.", audio_occupancy_now);
pthread_mutex_unlock(&buffer_mutex);
- int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) >> 32;
+ int64_t frames_processed_since_latest_latency_check = (delta * sample_rate) / 1000000000;
// debug(1,"delta: %" PRId64 " frames.",frames_processed_since_latest_latency_check);
// jack_latency is set by the graph() callback, it's the average of the maximum
// latencies of all our output ports. Adjust this constant baseline delay according
@@ -428,13 +415,7 @@ int play(void *buf, int samples) {
size_t i_done, o_done;
soxr_error_t e;
while (samples > 0 && thisbuf > 0) {
- e = soxr_process(soxr,
- (soxr_in_t)in,
- samples,
- &i_done,
- (soxr_out_t)out,
- thisbuf,
- &o_done);
+ e = soxr_process(soxr, (soxr_in_t)in, samples, &i_done, (soxr_out_t)out, thisbuf, &o_done);
if (e)
die("Error during soxr process: %s", e);
@@ -445,18 +426,18 @@ int play(void *buf, int samples) {
}
} else {
#endif
- j = 0;
- for (j = 0; j < thisbuf && samples > 0; ++j) {
- for (c = 0; c < NPORTS; ++c)
- out[j * NPORTS + c] = sample_conv(*in++);
- --samples;
- }
- jack_ringbuffer_write_advance(jackbuf, j * jack_sample_size * NPORTS);
+ j = 0;
+ for (j = 0; j < thisbuf && samples > 0; ++j) {
+ for (c = 0; c < NPORTS; ++c)
+ out[j * NPORTS + c] = sample_conv(*in++);
+ --samples;
+ }
+ jack_ringbuffer_write_advance(jackbuf, j * jack_sample_size * NPORTS);
#ifdef CONFIG_SOXR
}
#endif
}
- time_of_latest_transfer = get_absolute_time_in_fp();
+ time_of_latest_transfer = get_absolute_time_in_ns();
pthread_mutex_unlock(&buffer_mutex);
if (samples) {
warn("JACK ringbuffer overrun. Dropped %d samples.", samples);
diff --git a/audio_pa.c b/audio_pa.c
index e6a5b5767..951860beb 100644
--- a/audio_pa.c
+++ b/audio_pa.c
@@ -85,6 +85,11 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
if (config.cfg != NULL) {
const char *str;
+ /* Get the PulseAudio server name. */
+ if (config_lookup_string(config.cfg, "pa.server", &str)) {
+ config.pa_server = (char *)str;
+ }
+
/* Get the Application Name. */
if (config_lookup_string(config.cfg, "pa.application_name", &str)) {
config.pa_application_name = (char *)str;
@@ -126,7 +131,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
// Start the mainloop
if (pa_threaded_mainloop_start(mainloop) != 0)
die("could not start the pulseaudio threaded mainloop");
- if (pa_context_connect(context, NULL, 0, NULL) != 0)
+
+ if (pa_context_connect(context, config.pa_server, 0, NULL) != 0)
die("failed to connect to the pulseaudio context -- the error message is \"%s\".",
pa_strerror(pa_context_errno(context)));
diff --git a/audio_pipe.c b/audio_pipe.c
index 4ec9b2402..6dedc464b 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -42,7 +42,6 @@
static int fd = -1;
char *pipename = NULL;
-int warned = 0;
static void start(__attribute__((unused)) int sample_rate,
__attribute__((unused)) int sample_format) {
@@ -51,47 +50,46 @@ static void start(__attribute__((unused)) int sample_rate,
// we check that it's not a "real" error though. From the "man 2 open" page:
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
// open for reading."
- fd = open(pipename, O_WRONLY | O_NONBLOCK);
- if ((fd == -1) && (errno != ENXIO) && (warned == 0)) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "pipe: start -- error %d (\"%s\") opening the pipe named \"%s\".", errno,
- (char *)errorstring, pipename);
- warn("Error %d opening the pipe named \"%s\".", errno, pipename);
- warned = 1;
- }
+
+ fd = try_to_open_pipe_for_writing(pipename);
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ if ((fd == -1) && (errno != ENXIO)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "audio_pipe start -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, pipename);
+ warn("can not open audio pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, pipename);
+ }
}
static int play(void *buf, int samples) {
// if the file is not open, try to open it.
char errorstring[1024];
if (fd == -1) {
- fd = open(pipename, O_WRONLY | O_NONBLOCK);
+ fd = try_to_open_pipe_for_writing(pipename);
}
// if it's got a reader, write to it.
if (fd > 0) {
- int rc = non_blocking_write(fd, buf, samples * 4);
- if ((rc < 0) && (warned == 0)) {
+ //int rc = non_blocking_write(fd, buf, samples * 4);
+ int rc = write(fd, buf, samples * 4);
+ if ((rc < 0) && (errno != EPIPE)) {
strerror_r(errno, (char *)errorstring, 1024);
- warn("Error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
- warned = 1;
+ debug(1, "audio_pip play: error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
}
- } else if ((fd == -1) && (errno != ENXIO) && (warned == 0)) {
- strerror_r(errno, (char *)errorstring, 1024);
- warn("Error %d opening the pipe named \"%s\": \"%s\".", errno, pipename, errorstring);
- warned = 1;
}
- return warned;
+ return 0;
}
static void stop(void) {
// 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");
+ // debug(1, "pipe init");
// const char *str;
// int value;
// double dvalue;
@@ -122,10 +120,12 @@ static int init(int argc, char **argv) {
pipename = strdup(argv[0]);
// here, create the pipe
+ mode_t oldumask = umask(000);
if (mkfifo(pipename, 0644) && errno != EEXIST)
- die("Could not create output pipe \"%s\"", pipename);
+ die("Could not create audio pipe \"%s\"", pipename);
+ umask(oldumask);
- debug(1, "Pipename is \"%s\"", pipename);
+ debug(1, "audio pipe name is \"%s\"", pipename);
return 0;
}
diff --git a/audio_sndio.c b/audio_sndio.c
index ac5cfa380..f8582c8ab 100644
--- a/audio_sndio.c
+++ b/audio_sndio.c
@@ -247,7 +247,7 @@ static void stop() {
}
static void onmove_cb(__attribute__((unused)) void *arg, int delta) {
- time_of_last_onmove_cb = get_absolute_time_in_fp();
+ time_of_last_onmove_cb = get_absolute_time_in_ns();
at_least_one_onmove_cb_seen = 1;
played += delta;
}
@@ -258,10 +258,14 @@ static int delay(long *_delay) {
if (at_least_one_onmove_cb_seen) { // when output starts, the onmove_cb callback will be made
// calculate the difference in time between now and when the last callback occurred,
// and use it to estimate the frames that would have been output
- uint64_t time_difference = get_absolute_time_in_fp() - time_of_last_onmove_cb;
- uint64_t frame_difference = time_difference * par.rate;
- uint64_t frame_difference_big_integer = frame_difference >> 32;
- estimated_extra_frames_output = frame_difference_big_integer;
+ uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb;
+ uint64_t frame_difference = (time_difference * par.rate) / 1000000000;
+ estimated_extra_frames_output = frame_difference;
+ // sanity check -- total estimate can not exceed frames written.
+ if ((estimated_extra_frames_output + played) > written / framesize) {
+ // debug(1,"play estimate fails sanity check, possibly due to running on a VM");
+ estimated_extra_frames_output = 0; // can't make any sensible guess
+ }
// debug(1,"Frames played to last cb: %d, estimated to current time:
// %d.",played,estimated_extra_frames_output);
}
diff --git a/audio_soundio.c b/audio_soundio.c
index 119f7670e..501c6db2d 100644
--- a/audio_soundio.c
+++ b/audio_soundio.c
@@ -24,8 +24,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m
int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer);
int fill_count = fill_bytes / outstream->bytes_per_frame;
- debug(3, "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
- "outstream->bytes_per_frame: %d",
+ debug(3,
+ "[--->>] frame_count_min: %d , frame_count_max: %d , fill_bytes: %d , fill_count: %d , "
+ "outstream->bytes_per_frame: %d",
frame_count_min, frame_count_max, fill_bytes, fill_count, outstream->bytes_per_frame);
if (frame_count_min > fill_count) {
diff --git a/audio_stdout.c b/audio_stdout.c
index 00ded166e..8b37883e0 100644
--- a/audio_stdout.c
+++ b/audio_stdout.c
@@ -56,7 +56,7 @@ static int play(void *buf, int samples) {
}
static void stop(void) {
- // don't close stdout
+ // Do nothing when play stops
}
static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) {
diff --git a/common.c b/common.c
index 228dfec97..39c4d62ee 100644
--- a/common.c
+++ b/common.c
@@ -40,6 +40,8 @@
#include
#include
#include
+#include
+#include
#ifdef COMPILE_FOR_OSX
#include
@@ -88,10 +90,11 @@ void set_alsa_out_dev(char *);
#endif
config_t config_file_stuff;
+int emergency_exit;
pthread_t main_thread_id;
-uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
+uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
-// always lock use this when accessing the fp_time_at_last_debug_message
+// always lock use this when accessing the ns_time_at_last_debug_message
static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t the_conn_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -101,7 +104,7 @@ const char *sps_format_description_string_array[] = {
"S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid"};
const char *sps_format_description_string(sps_format_t format) {
- if ((format >= SPS_FORMAT_UNKNOWN) && (format <= SPS_FORMAT_AUTO))
+ if (format <= SPS_FORMAT_AUTO)
return sps_format_description_string_array[format];
else
return sps_format_description_string_array[SPS_FORMAT_INVALID];
@@ -120,7 +123,7 @@ static void (*sps_log)(int prio, const char *t, ...) = daemon_log;
static void (*sps_log)(int prio, const char *t, ...) = syslog;
#endif
-void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
+void do_sps_log_to_stderr(__attribute__((unused)) int prio, const char *t, ...) {
char s[1024];
va_list args;
va_start(args, t);
@@ -129,7 +132,86 @@ void do_sps_log(__attribute__((unused)) int prio, const char *t, ...) {
fprintf(stderr, "%s\n", s);
}
-void log_to_stderr() { sps_log = do_sps_log; }
+void do_sps_log_to_stdout(__attribute__((unused)) int prio, const char *t, ...) {
+ char s[1024];
+ va_list args;
+ va_start(args, t);
+ vsnprintf(s, sizeof(s), t, args);
+ va_end(args);
+ fprintf(stdout, "%s\n", s);
+}
+
+int create_log_file(const char* path) {
+ int fd = -1;
+ if (path != NULL) {
+ char *dirc = strdup(path);
+ if (dirc) {
+ char *dname = dirname(dirc);
+ // create the directory, if necessary
+ int result = 0;
+ if (dname) {
+ char *pdir = realpath(dname, NULL); // will return a NULL if the directory doesn't exist
+ if (pdir == NULL) {
+ mode_t oldumask = umask(000);
+ result = mkpath(dname, 0777);
+ umask(oldumask);
+ } else {
+ free(pdir);
+ }
+ if ((result == 0) || (result == -EEXIST)) {
+ // now open the file
+ fd = open(path, O_WRONLY | O_NONBLOCK | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if ((fd == -1) && (errno == EEXIST))
+ fd = open(path, O_WRONLY | O_APPEND | O_NONBLOCK);
+
+ if (fd >= 0) {
+ // now we switch to blocking mode
+ int flags = fcntl(fd, F_GETFL);
+ if (flags == -1) {
+// strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+// debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
+// (char *)errorstring, pathname);
+ } else {
+ flags = fcntl(fd, F_SETFL,flags & ~O_NONBLOCK);
+// if (flags == -1) {
+// strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+// debug(1, "create_log_file -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
+// (char *)errorstring, pathname);
+ }
+ }
+ }
+ }
+ free(dirc);
+ }
+ }
+ return fd;
+}
+
+void do_sps_log_to_fd(__attribute__((unused)) int prio, const char *t, ...) {
+ char s[1024];
+ va_list args;
+ va_start(args, t);
+ vsnprintf(s, sizeof(s), t, args);
+ va_end(args);
+ if (config.log_fd == -1)
+ config.log_fd = create_log_file(config.log_file_path);
+ if (config.log_fd >= 0) {
+ dprintf(config.log_fd, "%s\n", s);
+ } else if (errno != ENXIO) { // maybe there is a pipe there but not hooked up
+ fprintf(stderr, "%s\n", s);
+ }
+}
+
+void log_to_stderr() { sps_log = do_sps_log_to_stderr; }
+void log_to_stdout() { sps_log = do_sps_log_to_stdout; }
+void log_to_file() { sps_log = do_sps_log_to_fd; }
+void log_to_syslog() {
+#ifdef CONFIG_LIBDAEMON
+ sps_log = daemon_log;
+#else
+ sps_log = syslog;
+#endif
+}
shairport_cfg config;
@@ -202,10 +284,10 @@ char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss
insertion_point = insertion_point + strlen(insertion_point);
space_remaining = space_remaining - strlen(insertion_point);
}
-
if (prefix) {
snprintf(insertion_point, space_remaining, "%s", prefix);
insertion_point = insertion_point + strlen(insertion_point);
+ space_remaining = space_remaining - strlen(insertion_point);
}
return insertion_point;
}
@@ -218,17 +300,17 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *fatal error: ");
} else {
- s = b;
+ strncpy(b, "fatal error: ", sizeof(b));
+ s = b+strlen(b);
}
va_list args;
va_start(args, format);
@@ -236,7 +318,8 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
va_end(args);
sps_log(LOG_ERR, "%s", b);
pthread_setcancelstate(oldState, NULL);
- abort(); // exit() doesn't always work, by heaven.
+ emergency_exit = 1;
+ exit(EXIT_FAILURE);
}
void _warn(const char *filename, const int linenumber, const char *format, ...) {
@@ -247,17 +330,17 @@ void _warn(const char *filename, const int linenumber, const char *format, ...)
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " *warning: ");
} else {
- s = b;
+ strncpy(b, "warning: ", sizeof(b));
+ s = b+strlen(b);
}
va_list args;
va_start(args, format);
@@ -275,14 +358,13 @@ void _debug(const char *filename, const int linenumber, int level, const char *f
char b[1024];
b[0] = 0;
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " ");
va_list args;
va_start(args, format);
@@ -300,14 +382,13 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
char *s;
if (debuglev) {
pthread_mutex_lock(&debug_timing_lock);
- uint64_t time_now = get_absolute_time_in_fp();
- uint64_t time_since_start = time_now - fp_time_at_startup;
- uint64_t time_since_last_debug_message = time_now - fp_time_at_last_debug_message;
- fp_time_at_last_debug_message = time_now;
+ uint64_t time_now = get_absolute_time_in_ns();
+ uint64_t time_since_start = time_now - ns_time_at_startup;
+ uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message;
+ ns_time_at_last_debug_message = time_now;
pthread_mutex_unlock(&debug_timing_lock);
- uint64_t divisor = (uint64_t)1 << 32;
- s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
- 1.0 * time_since_last_debug_message / divisor, filename,
+ s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000,
+ 1.0 * time_since_last_debug_message / 1000000000, filename,
linenumber, " ");
} else {
s = b;
@@ -1013,43 +1094,77 @@ uint64_t get_absolute_time_in_fp() {
return time_now_fp;
}
-ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count, int timeout) {
- // timeout is in milliseconds
- void *ibuf = (void *)buf;
- size_t bytes_remaining = count;
- int rc = 1;
- struct pollfd ufds[1];
- 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, timeout);
- if (rc < 0) {
- // debug(1, "non-blocking write error waiting for pipe to become ready for writing...");
- } else if (rc == 0) {
- // warn("non-blocking write timeout waiting for pipe to become ready for writing");
- rc = -1;
- errno = -ETIMEDOUT;
- } else { // rc > 0, implying it might be ready
- ssize_t bytes_written = write(fd, ibuf, bytes_remaining);
- if (bytes_written == -1) {
- // debug(1,"Error %d in non_blocking_write: \"%s\".",errno,strerror(errno));
- rc = bytes_written; // to imitate the return from write()
- } else {
- ibuf += bytes_written;
- bytes_remaining -= bytes_written;
- }
- }
+uint64_t get_absolute_time_in_ns() {
+ uint64_t time_now_ns;
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ struct timespec tn;
+ // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
+ clock_gettime(CLOCK_MONOTONIC, &tn);
+ uint64_t tnnsec = tn.tv_sec;
+ tnnsec = tnnsec * 1000000000;
+ uint64_t tnjnsec = tn.tv_nsec;
+ time_now_ns = tnnsec + tnjnsec;
+#endif
+
+#ifdef COMPILE_FOR_OSX
+ uint64_t time_now_mach;
+ uint64_t elapsedNano;
+ static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
+
+ time_now_mach = mach_absolute_time();
+
+ // If this is the first time we've run, get the timebase.
+ // We can use denom == 0 to indicate that sTimebaseInfo is
+ // uninitialised because it makes no sense to have a zero
+ // denominator in a fraction.
+
+ if (sTimebaseInfo.denom == 0) {
+ debug(1, "Mac initialise timebase info.");
+ (void)mach_timebase_info(&sTimebaseInfo);
}
- if (rc > 0)
- return count - bytes_remaining; // this is just to mimic a normal write/3.
- else
- return rc;
- // return write(fd,buf,count);
+
+ // Do the maths. We hope that the multiplication doesn't
+ // overflow; the price you pay for working in fixed point.
+
+ // this gives us nanoseconds
+ time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
+#endif
+
+ return time_now_ns;
}
-ssize_t non_blocking_write(int fd, const void *buf, size_t count) {
- return non_blocking_write_with_timeout(fd, buf, count, 5000); // default is 5 seconds.
+int try_to_open_pipe_for_writing(const char* pathname) {
+ // tries to open the pipe in non-blocking mode first.
+ // if it succeeds, it sets it to blocking.
+ // if not, it returns -1.
+
+ int fdis = open(pathname, O_WRONLY | O_NONBLOCK); // open it in non blocking mode first
+
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ // This is checked by the caller.
+
+ if (fdis >= 0) {
+ // now we switch to blocking mode
+ int flags = fcntl(fdis, F_GETFL);
+ if (flags == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "try_to_open_pipe -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno,
+ (char *)errorstring, pathname);
+ } else {
+ flags = fcntl(fdis, F_SETFL,flags & ~O_NONBLOCK);
+ if (flags == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "try_to_open_pipe -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno,
+ (char *)errorstring, pathname);
+ }
+ }
+ }
+ return fdis;
}
/* from
@@ -1093,7 +1208,9 @@ char *str_replace(const char *string, const char *substr, const char *replacemen
/* from http://burtleburtle.net/bob/rand/smallprng.html */
-// this is not thread-safe, so we need a mutex on it to use it properly// always lock use this when accessing the fp_time_at_last_debug_message
+// this is not thread-safe, so we need a mutex on it to use it properly.
+// always lock use this when accessing the fp_time_at_last_debug_message
+
pthread_mutex_t r64_mutex = PTHREAD_MUTEX_INITIALIZER;
// typedef uint64_t u8;
@@ -1201,18 +1318,17 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
timeoutTime.tv_sec = time_then;
timeoutTime.tv_nsec = time_then_nsec;
- int64_t start_time = get_absolute_time_in_fp();
+ uint64_t start_time = get_absolute_time_in_ns();
int r = pthread_mutex_timedlock(mutex, &timeoutTime);
- int64_t et = get_absolute_time_in_fp() - start_time;
+ uint64_t et = get_absolute_time_in_ns() - start_time;
if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
- et = (et * 1000000) >> 32; // microseconds
char errstr[1000];
if (r == ETIMEDOUT)
debug(debuglevel,
- "timed out waiting for a mutex, having waiting %f seconds, with a maximum "
+ "timed out waiting for a mutex, having waited %f microseconds, with a maximum "
"waiting time of %d microseconds. \"%s\".",
- (1.0 * et) / 1000000, dally_time, debugmessage);
+ (1.0E6 * et) / 1000000000, dally_time, debugmessage);
else
debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
strerror_r(r, errstr, sizeof(errstr)), debugmessage);
@@ -1261,7 +1377,7 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
return pthread_mutex_lock(mutex);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- uint64_t time_at_start = get_absolute_time_in_fp();
+ uint64_t time_at_start = get_absolute_time_in_ns();
char dstring[1000];
memset(dstring, 0, sizeof(dstring));
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
@@ -1270,12 +1386,10 @@ int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char
int result = sps_pthread_mutex_timedlock(mutex, dally_time, dstring, debuglevel);
if (result == ETIMEDOUT) {
result = pthread_mutex_lock(mutex);
- uint64_t time_delay = get_absolute_time_in_fp() - time_at_start;
- uint64_t divisor = (uint64_t)1 << 32;
- double delay = 1.0 * time_delay / divisor;
+ uint64_t time_delay = get_absolute_time_in_ns() - time_at_start;
debug(debuglevel,
- "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f sec.",
- mutexname, dstring, (1.0 * dally_time) / 1000000, delay);
+ "mutex_lock \"%s\" at \"%s\" expected max wait: %0.9f, actual wait: %0.9f microseconds.",
+ mutexname, dstring, (1.0 * dally_time), 0.001 * time_delay);
}
pthread_setcancelstate(oldState, NULL);
return result;
@@ -1313,6 +1427,9 @@ char *get_version_string() {
if (version_string) {
strcpy(version_string, PACKAGE_VERSION);
+#ifdef CONFIG_APPLE_ALAC
+ strcat(version_string, "-alac");
+#endif
#ifdef CONFIG_LIBDAEMON
strcat(version_string, "-libdaemon");
#endif
@@ -1543,9 +1660,11 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t f
return previous_random_number;
}
-// This will check the incoming string "s" of length "len" with the existing NUL-terminated string "str" and update "flag" accordingly.
+// This will check the incoming string "s" of length "len" with the existing NUL-terminated string
+// "str" and update "flag" accordingly.
-// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings are stored.
+// Note: if the incoming string length is zero, then the a NULL is used; i.e. no zero-length strings
+// are stored.
// If the strings are different, the str is free'd and replaced by a pointer
// to a newly strdup'd string and the flag is set
@@ -1558,7 +1677,7 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
free(*str);
//*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
char *p = malloc(len + 1);
- memcpy(p,s,len);
+ memcpy(p, s, len);
p[len] = '\0';
*str = p;
*flag = 1;
@@ -1575,7 +1694,7 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
if ((s) && (len)) {
//*str = strndup(s, len); // it seems that OpenWrt 12 doesn't have this
char *p = malloc(len + 1);
- memcpy(p,s,len);
+ memcpy(p, s, len);
p[len] = '\0';
*str = p;
*flag = 1;
@@ -1586,3 +1705,13 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) {
}
return *flag;
}
+
+// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
+void* memdup(const void* mem, size_t size) {
+ void* out = malloc(size);
+
+ if(out != NULL)
+ memcpy(out, mem, size);
+
+ return out;
+}
diff --git a/common.h b/common.h
index 2c9c05bdb..1f5d1c87b 100644
--- a/common.h
+++ b/common.h
@@ -100,9 +100,13 @@ typedef enum {
const char *sps_format_description_string(sps_format_t format);
typedef struct {
- double resend_control_first_check_time; // wait this long before asking for a missing packet to be resent
+ double missing_port_dacp_scan_interval_seconds; // if no DACP port number can be found, check at
+ // these intervals
+ double resend_control_first_check_time; // wait this long before asking for a missing packet to be
+ // resent
double resend_control_check_interval_time; // wait this long between making requests
- double resend_control_last_check_time; // if the packet is missing this close to the time of use, give up
+ double resend_control_last_check_time; // if the packet is missing this close to the time of use,
+ // give up
pthread_mutex_t lock;
config_t *cfg;
int endianness;
@@ -113,6 +117,7 @@ typedef struct {
// on host %h"
#ifdef CONFIG_PA
+ char *pa_server; // the pulseaudio server address that Shairport Sync will play on.
char *pa_application_name; // the name under which Shairport Sync shows up as an "Application" in
// the Sound Preferences in most desktop Linuxes.
// Defaults to "Shairport Sync". Shairport Sync must be playing to see it.
@@ -177,6 +182,8 @@ typedef struct {
char *pidfile;
#endif
+ int log_fd; // file descriptor of the file or pipe to log stuff to.
+ char *log_file_path; // path to file or pipe to log to, if any
int logOutputLevel; // log output level
int debugger_show_elapsed_time; // in the debug message, display the time since startup
int debugger_show_relative_time; // in the debug message, display the time since the last one
@@ -212,7 +219,11 @@ typedef struct {
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
+ int audio_backend_silent_lead_in_time_auto; // true if the lead-in time should be from as soon as
+ // packets are received
double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
+ uint32_t minimum_free_buffer_headroom; // when effective latency is calculated, ensure this number
+ // of buffers are unallocated
double active_state_timeout; // the amount of time from when play ends to when the system leaves
// into the "active" mode.
uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
@@ -274,7 +285,10 @@ typedef struct {
int jack_soxr_resample_quality;
#endif
#endif
-
+ void *gradients; // a linked list of the clock gradients discovered for all DACP IDs
+ // can't use IP numbers as they might be given to different devices
+ // can't get hold of MAC addresses.
+ // can't define the nvll linked list struct here
} shairport_cfg;
// accessors to config for multi-thread access
@@ -286,7 +300,12 @@ uint16_t nctohs(const uint8_t *p); // read 2 characters from *p and do ntohs on
void memory_barrier();
-void log_to_stderr(); // call this to director logging to stderr;
+void log_to_stderr(); // call this to direct logging to stderr;
+void log_to_stdout(); // call this to direct logging to stdout;
+void log_to_syslog(); // call this to direct logging to the system log;
+void log_to_file(); // call this to direct logging to a file or (pre-existing) pipe;
+
+
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
@@ -294,10 +313,7 @@ int get_requested_connection_state_to_output();
void set_requested_connection_state_to_output(int v);
-ssize_t non_blocking_write_with_timeout(int fd, const void *buf, size_t count,
- int timeout); // timeout in milliseconds
-
-ssize_t non_blocking_write(int fd, const void *buf, size_t count); // used in a few places
+int try_to_open_pipe_for_writing(const char* pathname); // open it without blocking if it's not hooked up
/* from
* http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
@@ -349,10 +365,11 @@ double flat_vol2attn(double vol, long max_db, long min_db);
double vol2attn(double vol, long max_db, long min_db);
// return a monolithic (always increasing) time in nanoseconds
-uint64_t get_absolute_time_in_fp(void);
+// uint64_t get_absolute_time_in_fp(void); // obselete
+uint64_t get_absolute_time_in_ns(void);
// time at startup for debugging timing
-extern uint64_t fp_time_at_startup, fp_time_at_last_debug_message;
+extern uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
// this is for reading an unsigned 32 bit number, such as an RTP timestamp
@@ -363,6 +380,7 @@ extern pthread_t main_thread_id;
extern shairport_cfg config;
extern config_t config_file_stuff;
+extern int emergency_exit;
int config_set_lookup_bool(config_t *cfg, char *where, int *dst);
@@ -420,9 +438,6 @@ extern pthread_mutex_t r64_mutex;
char *get_version_string(); // mallocs a string space -- remember to free it afterwards
-void sps_nanosleep(const time_t sec,
- const long nanosec); // waits for this time, even through interruptions
-
int64_t generate_zero_frames(char *outp, size_t number_of_frames, sps_format_t format,
int with_dither, int64_t random_number_in);
@@ -430,4 +445,7 @@ void malloc_cleanup(void *arg);
int string_update_with_size(char **str, int *flag, char *s, size_t len);
+// from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks
+void* memdup(const void* mem, size_t size);
+
#endif // _COMMON_H
diff --git a/dacp.c b/dacp.c
index f3111a838..c6b9968fd 100644
--- a/dacp.c
+++ b/dacp.c
@@ -1,6 +1,6 @@
/*
* DACP protocol handler. This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2017 -- 2019
+ * Copyright (c) Mike Brady 2017 -- 2020
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -49,7 +49,7 @@
typedef struct {
int players_connection_thread_index; // the connection thread index when a player thread is
// associated with this, zero otherwise
- int scan_enable; // set to 1 if if sacanning should be considered
+ int scan_enable; // set to 1 if scanning should be considered
char dacp_id[256]; // the DACP ID string
uint16_t port; // zero if no port discovered
short connection_family; // AF_INET6 or AF_INET
@@ -123,7 +123,7 @@ static const struct http_funcs responseFuncs = {
// static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t dacp_conversation_lock;
static pthread_mutex_t dacp_server_information_lock;
-static pthread_cond_t dacp_server_information_cv = PTHREAD_COND_INITIALIZER;
+static pthread_cond_t dacp_server_information_cv;
void addrinfo_cleanup(void *arg) {
// debug(1, "addrinfo cleanup called.");
@@ -154,7 +154,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"dacp_send_command: command is: \"%s\".",command);
if (dacp_server.port == 0) {
- debug(1, "No DACP port specified yet");
+ debug(3, "No DACP port specified yet");
result = 490; // no port specified
} else {
@@ -169,6 +169,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// 493 Client failed to send a message
// 492 Argument out of range
// 491 Client refused connection
+ // 490 No port specified
struct addrinfo hints, *res;
int sockfd;
@@ -201,7 +202,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
response.code = 498; // Bad Address information for the DACP server
} else {
- uint64_t start_time = get_absolute_time_in_fp();
+ uint64_t start_time = get_absolute_time_in_ns();
pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
// only do this one at a time -- not sure it is necessary, but better safe than sorry
@@ -221,18 +222,16 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
// debug(2, "dacp_send_command: open socket %d.",sockfd);
-
// This is for limiting the time to be spent waiting for a response.
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500000;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
+ debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
debug(1, "dacp_send_command: error %d setting send timeout.", errno);
-
// connect!
// debug(1, "DACP socket created.");
if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
@@ -352,10 +351,9 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
}
pthread_cleanup_pop(1); // this should free the addrinfo
// freeaddrinfo(res);
- uint64_t et = get_absolute_time_in_fp() - start_time;
- et = (et * 1000000) >> 32; // microseconds
+ uint64_t et = get_absolute_time_in_ns() - start_time; // this will be in nanoseconds
debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
- (1.0 * et) / 1000000, response.code, command);
+ (1.0 * et) / 1000000000, response.code, command);
}
*body = response.body;
*bodysize = response.size;
@@ -418,13 +416,11 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
// which return immediately with a 403 code if there are no changes.
dacp_server.always_use_revision_number_1 = 0;
char *p = strstr(conn->UserAgent, "forked-daapd");
- if ((p != 0) && (p == conn->UserAgent)) {// must exist and be at the start of the UserAgent string
+ if ((p != 0) &&
+ (p == conn->UserAgent)) { // must exist and be at the start of the UserAgent string
dacp_server.always_use_revision_number_1 = 1;
}
-
- mdns_dacp_monitor_set_id(dacp_server.dacp_id);
-
metadata_hub_modify_prolog();
int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
metadata_store.dacp_server_active = dacp_server.scan_enable;
@@ -508,381 +504,418 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
(metadata_store.advanced_dacp_server_active != 0);
metadata_store.dacp_server_active = 0;
metadata_store.advanced_dacp_server_active = 0;
- debug(2,
+ debug(3,
"setting metadata_store.dacp_server_active and "
"metadata_store.advanced_dacp_server_active to 0 with an update "
"flag value of %d",
ch);
metadata_hub_modify_epilog(ch);
- while (dacp_server.scan_enable == 0) {
- // debug(1, "dacp_monitor_thread_code wait for an event to possible enable scan");
- pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
- // debug(1,"dacp_monitor_thread_code wake up.");
+
+ uint64_t time_to_wait_for_wakeup_ns =
+ (uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
+ uint64_t sec = time_of_wakeup_ns / 1000000000;
+ uint64_t nsec = time_of_wakeup_ns % 1000000000;
+#endif
+#ifdef COMPILE_FOR_OSX
+ uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
+ uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
+#endif
+
+ struct timespec time_to_wait;
+ time_to_wait.tv_sec = sec;
+ time_to_wait.tv_nsec = nsec;
+
+ while ((dacp_server.scan_enable == 0) && (result != ETIMEDOUT)) {
+ // debug(1, "dacp_monitor_thread_code wait for an event to possibly enable scan");
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ result = pthread_cond_timedwait(&dacp_server_information_cv, &dacp_server_information_lock,
+ &time_to_wait); // this is a pthread cancellation point
+ // debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %"
+ // PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);
+
+#endif
+#ifdef COMPILE_FOR_OSX
+ result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv,
+ &dacp_server_information_lock, &time_to_wait);
+#endif
+ }
+ if (dacp_server.scan_enable == 1) {
+ bad_result_count = 0;
+ idle_scan_count = 0;
}
- // so dacp_server.scan_enable will be true at this point
- bad_result_count = 0;
- idle_scan_count = 0;
}
- always_use_revision_number_1 = dacp_server.always_use_revision_number_1; // set this while access is locked
+ always_use_revision_number_1 =
+ dacp_server.always_use_revision_number_1; // set this while access is locked
result = dacp_get_volume(&the_volume); // just want the http code
pthread_cleanup_pop(1);
- scan_index++;
- // debug(1,"DACP Scan Result: %d.", result);
-
- if ((result == 200) || (result == 400)) {
- bad_result_count = 0;
+ if (result == 490) { // 490 means no port was specified
+ if (((char *)dacp_server.dacp_id != NULL) && (strlen(dacp_server.dacp_id) != 0)) {
+ // debug(1,"mdns_dacp_monitor_set_id");
+ mdns_dacp_monitor_set_id(dacp_server.dacp_id);
+ }
} else {
- if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
- bad_result_count++;
- }
-
- // here, do the debouncing calculations to see
- // if the dacp server and the
- // advanced dacp server are available
+ scan_index++;
+ // debug(1,"DACP Scan Result: %d.", result);
- // -1 means we don't know because some bad statuses have been reported
- // 0 means definitely no
- // +1 means definitely yes
-
- int dacp_server_status_now = -1;
- int advanced_dacp_server_status_now = -1;
+ if ((result == 200) || (result == 400)) {
+ bad_result_count = 0;
+ } else {
+ if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
+ bad_result_count++;
+ }
- if (bad_result_count == 0) {
- dacp_server_status_now = 1;
- if (result == 200)
- advanced_dacp_server_status_now = 1;
- else if (result == 400)
+ // here, do the debouncing calculations to see
+ // if the dacp server and the
+ // advanced dacp server are available
+
+ // -1 means we don't know because some bad statuses have been reported
+ // 0 means definitely no
+ // +1 means definitely yes
+
+ int dacp_server_status_now = -1;
+ int advanced_dacp_server_status_now = -1;
+
+ if (bad_result_count == 0) {
+ dacp_server_status_now = 1;
+ if (result == 200)
+ advanced_dacp_server_status_now = 1;
+ else if (result == 400)
+ advanced_dacp_server_status_now = 0;
+ } else if (bad_result_count ==
+ config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
+ // then it's gone
+ dacp_server_status_now = 0;
advanced_dacp_server_status_now = 0;
- } else if (bad_result_count ==
- config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
- // then it's gone
- dacp_server_status_now = 0;
- advanced_dacp_server_status_now = 0;
- }
+ }
- if (metadata_store.player_thread_active == 0)
- idle_scan_count++;
- else
- idle_scan_count = 0;
+ if (metadata_store.player_thread_active == 0)
+ idle_scan_count++;
+ else
+ idle_scan_count = 0;
- debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
- idle_scan_count);
+ debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result,
+ bad_result_count, idle_scan_count);
- /* not used
- // decide if idle for too long
- if (idle_scan_count == config.scan_max_inactive_count) {
- debug(2, "DACP server status scanning stopped.");
- dacp_server.scan_enable = 0;
- }
- */
+ /* not used
+ // decide if idle for too long
+ if (idle_scan_count == config.scan_max_inactive_count) {
+ debug(2, "DACP server status scanning stopped.");
+ dacp_server.scan_enable = 0;
+ }
+ */
- int update_needed = 0;
- metadata_hub_modify_prolog();
- if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
- if (metadata_store.dacp_server_active != dacp_server_status_now) {
- debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
- metadata_store.dacp_server_active = dacp_server_status_now;
- update_needed = 1;
+ int update_needed = 0;
+ metadata_hub_modify_prolog();
+ if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
+ if (metadata_store.dacp_server_active != dacp_server_status_now) {
+ debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
+ metadata_store.dacp_server_active = dacp_server_status_now;
+ update_needed = 1;
+ }
}
- }
- if (advanced_dacp_server_status_now !=
- -1) { // if advanced_dacp_server_status_now is actually known...
- if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
- debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
- metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
- update_needed = 1;
+ if (advanced_dacp_server_status_now !=
+ -1) { // if advanced_dacp_server_status_now is actually known...
+ if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
+ debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
+ metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
+ update_needed = 1;
+ }
}
- }
- metadata_hub_modify_epilog(update_needed);
+ metadata_hub_modify_epilog(update_needed);
- // pthread_mutex_unlock(&dacp_server_information_lock);
- // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
- // dacp_server.ip_string, dacp_server.port, scan_index);
+ // pthread_mutex_unlock(&dacp_server_information_lock);
+ // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
+ // dacp_server.ip_string, dacp_server.port, scan_index);
- if (result == 200) {
- metadata_hub_modify_prolog();
- int diff = metadata_store.speaker_volume != the_volume;
- if (diff)
- metadata_store.speaker_volume = the_volume;
- metadata_hub_modify_epilog(diff);
-
- ssize_t le;
- char *response = NULL;
- int32_t item_size;
- char command[1024] = "";
- if (always_use_revision_number_1 != 0) // for forked-daapd
- revision_number = 1;
- snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
- revision_number);
- // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
- result = dacp_send_command(command, &response, &le);
- // debug(1,"Response to \"%s\" is %d.",command,result);
- // remember: unless the revision_number you pass in is 1,
- // response will be 200 only if there's something new to report.
if (result == 200) {
- // if (0) {
- char *sp = response;
- if (le >= 8) {
- // here start looking for the contents of the status update
- if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
- // here, we know that we are receiving playerstatusupdates, so set a flag
- metadata_hub_modify_prolog();
- // debug(1, "playstatusupdate release track metadata");
- // metadata_hub_reset_track_metadata();
- // metadata_store.playerstatusupdates_are_received = 1;
- sp -= item_size; // drop down into the array -- don't skip over it
- le -= 8;
- // char typestring[5];
- // we need to acquire the metadata data structure and possibly update it
- while (le >= 8) {
- uint32_t type = dacp_tlv_crawl(&sp, &item_size);
- le -= item_size + 8;
- char *t;
- // char u;
- // char *st;
- int32_t r;
- uint32_t ui;
- // uint64_t v;
- // int i;
-
- switch (type) {
- case 'cmsr': // revision number
- t = sp - item_size;
- revision_number = ntohl(*(uint32_t *)(t));
- // debug(1,"New revision number received: %d", revision_number);
- break;
- case 'caps': // play status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 2:
- if (metadata_store.play_status != PS_STOPPED) {
- metadata_store.play_status = PS_STOPPED;
- debug(2, "Play status is \"stopped\".");
- }
+ metadata_hub_modify_prolog();
+ int diff = metadata_store.speaker_volume != the_volume;
+ if (diff)
+ metadata_store.speaker_volume = the_volume;
+ metadata_hub_modify_epilog(diff);
+
+ ssize_t le;
+ char *response = NULL;
+ int32_t item_size;
+ char command[1024] = "";
+ if (always_use_revision_number_1 != 0) // for forked-daapd
+ revision_number = 1;
+ snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
+ revision_number);
+ // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
+ result = dacp_send_command(command, &response, &le);
+ // debug(1,"Response to \"%s\" is %d.",command,result);
+ // remember: unless the revision_number you pass in is 1,
+ // response will be 200 only if there's something new to report.
+ if (result == 200) {
+ // if (0) {
+ char *sp = response;
+ if (le >= 8) {
+ // here start looking for the contents of the status update
+ if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
+ // here, we know that we are receiving playerstatusupdates, so set a flag
+ metadata_hub_modify_prolog();
+ // debug(1, "playstatusupdate release track metadata");
+ // metadata_hub_reset_track_metadata();
+ // metadata_store.playerstatusupdates_are_received = 1;
+ sp -= item_size; // drop down into the array -- don't skip over it
+ le -= 8;
+ // char typestring[5];
+ // we need to acquire the metadata data structure and possibly update it
+ while (le >= 8) {
+ uint32_t type = dacp_tlv_crawl(&sp, &item_size);
+ le -= item_size + 8;
+ char *t;
+ // char u;
+ // char *st;
+ int32_t r;
+ uint32_t ui;
+ // uint64_t v;
+ // int i;
+
+ switch (type) {
+ case 'cmsr': // revision number
+ t = sp - item_size;
+ revision_number = ntohl(*(uint32_t *)(t));
+ // debug(1,"New revision number received: %d", revision_number);
break;
- case 3:
- if (metadata_store.play_status != PS_PAUSED) {
- metadata_store.play_status = PS_PAUSED;
- debug(2, "Play status is \"paused\".");
+ case 'caps': // play status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 2:
+ if (metadata_store.play_status != PS_STOPPED) {
+ metadata_store.play_status = PS_STOPPED;
+ debug(2, "Play status is \"stopped\".");
+ }
+ break;
+ case 3:
+ if (metadata_store.play_status != PS_PAUSED) {
+ metadata_store.play_status = PS_PAUSED;
+ debug(2, "Play status is \"paused\".");
+ }
+ break;
+ case 4:
+ if (metadata_store.play_status != PS_PLAYING) {
+ metadata_store.play_status = PS_PLAYING;
+ debug(2, "Play status changed to \"playing\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised play status %d received.", r);
+ break;
}
break;
- case 4:
- if (metadata_store.play_status != PS_PLAYING) {
- metadata_store.play_status = PS_PLAYING;
- debug(2, "Play status changed to \"playing\".");
+ case 'cash': // shuffle status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 0:
+ if (metadata_store.shuffle_status != SS_OFF) {
+ metadata_store.shuffle_status = SS_OFF;
+ debug(2, "Shuffle status is \"off\".");
+ }
+ break;
+ case 1:
+ if (metadata_store.shuffle_status != SS_ON) {
+ metadata_store.shuffle_status = SS_ON;
+ debug(2, "Shuffle status is \"on\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised shuffle status %d received.", r);
+ break;
}
break;
- default:
- debug(1, "Unrecognised play status %d received.", r);
+ case 'carp': // repeat status
+ t = sp - item_size;
+ r = *(unsigned char *)(t);
+ switch (r) {
+ case 0:
+ if (metadata_store.repeat_status != RS_OFF) {
+ metadata_store.repeat_status = RS_OFF;
+ debug(2, "Repeat status is \"none\".");
+ }
+ break;
+ case 1:
+ if (metadata_store.repeat_status != RS_ONE) {
+ metadata_store.repeat_status = RS_ONE;
+ debug(2, "Repeat status is \"one\".");
+ }
+ break;
+ case 2:
+ if (metadata_store.repeat_status != RS_ALL) {
+ metadata_store.repeat_status = RS_ALL;
+ debug(2, "Repeat status is \"all\".");
+ }
+ break;
+ default:
+ debug(1, "Unrecognised repeat status %d received.", r);
+ break;
+ }
break;
- }
- break;
- case 'cash': // shuffle status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 0:
- if (metadata_store.shuffle_status != SS_OFF) {
- metadata_store.shuffle_status = SS_OFF;
- debug(2, "Shuffle status is \"off\".");
+ case 'cann': // track name
+ debug(2, "DACP Track Name seen");
+ if (string_update_with_size(&metadata_store.track_name,
+ &metadata_store.track_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
}
break;
- case 1:
- if (metadata_store.shuffle_status != SS_ON) {
- metadata_store.shuffle_status = SS_ON;
- debug(2, "Shuffle status is \"on\".");
+ case 'cana': // artist name
+ debug(2, "DACP Artist Name seen");
+ if (string_update_with_size(&metadata_store.artist_name,
+ &metadata_store.artist_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
}
break;
- default:
- debug(1, "Unrecognised shuffle status %d received.", r);
+ case 'canl': // album name
+ debug(2, "DACP Album Name seen");
+ if (string_update_with_size(&metadata_store.album_name,
+ &metadata_store.album_name_changed, sp - item_size,
+ item_size)) {
+ debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
+ }
break;
- }
- break;
- case 'carp': // repeat status
- t = sp - item_size;
- r = *(unsigned char *)(t);
- switch (r) {
- case 0:
- if (metadata_store.repeat_status != RS_OFF) {
- metadata_store.repeat_status = RS_OFF;
- debug(2, "Repeat status is \"none\".");
+ case 'cang': // genre
+ debug(2, "DACP Genre seen");
+ if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
+ sp - item_size, item_size)) {
+ debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
}
break;
- case 1:
- if (metadata_store.repeat_status != RS_ONE) {
- metadata_store.repeat_status = RS_ONE;
- debug(2, "Repeat status is \"one\".");
+ case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
+ // see reference above)
+ debug(2, "DACP Composite ID seen");
+ if (memcmp(metadata_store.item_composite_id, sp - item_size,
+ sizeof(metadata_store.item_composite_id)) != 0) {
+ memcpy(metadata_store.item_composite_id, sp - item_size,
+ sizeof(metadata_store.item_composite_id));
+ char st[33];
+ char *pt = st;
+ int it;
+ for (it = 0; it < 16; it++) {
+ snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
+ pt += 2;
+ }
+ *pt = 0;
+ debug(2, "Item composite ID changed to 0x%s.", st);
+ metadata_store.item_composite_id_changed = 1;
}
break;
- case 2:
- if (metadata_store.repeat_status != RS_ALL) {
- metadata_store.repeat_status = RS_ALL;
- debug(2, "Repeat status is \"all\".");
+ case 'astm':
+ t = sp - item_size;
+ ui = ntohl(*(uint32_t *)(t));
+ debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
+ if (ui != metadata_store.songtime_in_milliseconds) {
+ metadata_store.songtime_in_milliseconds = ui;
+ metadata_store.songtime_in_milliseconds_changed = 1;
+ debug(2, "DACP Song Time set to: \"%u\"",
+ metadata_store.songtime_in_milliseconds);
}
break;
+
+ /*
+ case 'mstt':
+ case 'cant':
+ case 'cast':
+ case 'cmmk':
+ case 'caas':
+ case 'caar':
+ t = sp - item_size;
+ r = ntohl(*(uint32_t *)(t));
+ printf(" %d", r);
+ printf(" (0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ printf(")");
+ break;
+ case 'asai':
+ t = sp - item_size;
+ s = ntohl(*(uint32_t *)(t));
+ s = s << 32;
+ t += 4;
+ v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
+ s += v;
+ printf(" %lu", s);
+ printf(" (0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ printf(")");
+ break;
+ */
default:
- debug(1, "Unrecognised repeat status %d received.", r);
+ /*
+ printf(" 0x");
+ t = sp - item_size;
+ for (i = 0; i < item_size; i++) {
+ printf("%02x", *t & 0xff);
+ t++;
+ }
+ */
break;
}
- break;
- case 'cann': // track name
- debug(2, "DACP Track Name seen");
- if (string_update_with_size(&metadata_store.track_name,
- &metadata_store.track_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
- }
- break;
- case 'cana': // artist name
- debug(2, "DACP Artist Name seen");
- if (string_update_with_size(&metadata_store.artist_name,
- &metadata_store.artist_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
- }
- break;
- case 'canl': // album name
- debug(2, "DACP Album Name seen");
- if (string_update_with_size(&metadata_store.album_name,
- &metadata_store.album_name_changed, sp - item_size,
- item_size)) {
- debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
- }
- break;
- case 'cang': // genre
- debug(2, "DACP Genre seen");
- if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
- sp - item_size, item_size)) {
- debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
- }
- break;
- case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
- // see reference above)
- debug(2, "DACP Composite ID seen");
- if (memcmp(metadata_store.item_composite_id, sp - item_size,
- sizeof(metadata_store.item_composite_id)) != 0) {
- memcpy(metadata_store.item_composite_id, sp - item_size,
- sizeof(metadata_store.item_composite_id));
- char st[33];
- char *pt = st;
- int it;
- for (it = 0; it < 16; it++) {
- snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
- pt += 2;
- }
- *pt = 0;
- debug(2, "Item composite ID changed to 0x%s.", st);
- metadata_store.item_composite_id_changed = 1;
- }
- break;
- case 'astm':
- t = sp - item_size;
- ui = ntohl(*(uint32_t *)(t));
- debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
- if (ui != metadata_store.songtime_in_milliseconds) {
- metadata_store.songtime_in_milliseconds = ui;
- metadata_store.songtime_in_milliseconds_changed = 1;
- debug(2, "DACP Song Time set to: \"%u\"",
- metadata_store.songtime_in_milliseconds);
- }
- break;
-
- /*
- case 'mstt':
- case 'cant':
- case 'cast':
- case 'cmmk':
- case 'caas':
- case 'caar':
- t = sp - item_size;
- r = ntohl(*(uint32_t *)(t));
- printf(" %d", r);
- printf(" (0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- printf(")");
- break;
- case 'asai':
- t = sp - item_size;
- s = ntohl(*(uint32_t *)(t));
- s = s << 32;
- t += 4;
- v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
- s += v;
- printf(" %lu", s);
- printf(" (0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- printf(")");
- break;
- */
- default:
- /*
- printf(" 0x");
- t = sp - item_size;
- for (i = 0; i < item_size; i++) {
- printf("%02x", *t & 0xff);
- t++;
- }
- */
- break;
+ // printf("\n");
}
- // printf("\n");
- }
- // finished possibly writing to the metadata hub
- metadata_hub_modify_epilog(
- 1); // should really see if this can be made responsive to changes
+ // finished possibly writing to the metadata hub
+ metadata_hub_modify_epilog(
+ 1); // should really see if this can be made responsive to changes
+ } else {
+ debug(1, "Status Update not found.\n");
+ }
} else {
- debug(1, "Status Update not found.\n");
+ debug(1, "Can't find any content in playerstatusupdate request");
}
- } else {
- debug(1, "Can't find any content in playerstatusupdate request");
- }
- } /* else {
- if (result != 403)
- debug(1, "Unexpected response %d to playerstatusupdate request", result);
- } */
+ } /* else {
+ if (result != 403)
+ debug(1, "Unexpected response %d to playerstatusupdate request", result);
+ } */
+ if (response) {
+ free(response);
+ response = NULL;
+ };
+ };
+ /*
+ strcpy(command,"nowplayingartwork?mw=320&mh=320");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
if (response) {
free(response);
response = NULL;
- };
- };
- /*
- strcpy(command,"nowplayingartwork?mw=320&mh=320");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- strcpy(command,"getproperty?properties=dmcp.volume");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
- }
- strcpy(command,"setproperty?dmcp.volume=100.000000");
- debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
- if (response) {
- free(response);
- response = NULL;
+ }
+ strcpy(command,"getproperty?properties=dmcp.volume");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ strcpy(command,"setproperty?dmcp.volume=100.000000");
+ debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
+ if (response) {
+ free(response);
+ response = NULL;
+ }
+ */
+ if (metadata_store.player_thread_active)
+ sleep(config.scan_interval_when_active);
+ else
+ sleep(config.scan_interval_when_inactive);
}
- */
- if (metadata_store.player_thread_active)
- sleep(config.scan_interval_when_active);
- else
- sleep(config.scan_interval_when_inactive);
}
debug(1, "DACP monitor thread exiting -- should never happen.");
pthread_exit(NULL);
@@ -890,6 +923,20 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
void dacp_monitor_start() {
int rc;
+
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+ pthread_condattr_t attr;
+ pthread_condattr_init(&attr);
+ pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
+ rc = pthread_cond_init(&dacp_server_information_cv, &attr);
+ if (rc)
+ debug(1, "Error initialising the DACP Server Information Condition Variable");
+ pthread_condattr_destroy(&attr);
+#endif
+#ifdef COMPILE_FOR_OSX
+ rc = pthread_cond_init(&dacp_server_information_cv, NULL);
+#endif
+
pthread_mutexattr_t mta;
rc = pthread_mutexattr_init(&mta);
@@ -948,6 +995,8 @@ void dacp_monitor_stop() {
pthread_mutex_destroy(&dacp_server_information_lock);
debug(3, "DACP Conversation Lock Mutex Destroyed");
pthread_mutex_destroy(&dacp_conversation_lock);
+ pthread_cond_destroy(&dacp_server_information_cv);
+ debug(3, "DACP Server Information Condition Variable destroyed.");
}
}
@@ -1200,7 +1249,7 @@ int dacp_get_volume(int32_t *the_actual_volume) {
} else {
debug(1, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
}
- } else {
+ } else if (http_response != 400) {
debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response);
}
if (the_actual_volume) {
diff --git a/dbus-service.c b/dbus-service.c
index 457aefed9..bbd5dcd45 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -24,10 +24,10 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
+#include
#include
#include
#include
-#include
#include "config.h"
@@ -69,8 +69,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairport_sync_remote_control_set_client(shairportSyncRemoteControlSkeleton, argc->client_ip);
-
- // although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their term).
+ // although it's a DACP server, the server is in fact, part of the the AirPlay "client" (their
+ // term).
if (argc->dacp_server_active) {
shairport_sync_remote_control_set_available(shairportSyncRemoteControlSkeleton, TRUE);
} else {
@@ -166,7 +166,6 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairportSyncAdvancedRemoteControlSkeleton, response);
}
-
switch (argc->shuffle_status) {
case SS_NOT_AVAILABLE:
new_status = FALSE;
@@ -188,8 +187,8 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
// only set this if it's different
if (current_status != new_status) {
debug(3, "Shuffle State should be changed");
- shairport_sync_advanced_remote_control_set_shuffle(
- shairportSyncAdvancedRemoteControlSkeleton, new_status);
+ shairport_sync_advanced_remote_control_set_shuffle(shairportSyncAdvancedRemoteControlSkeleton,
+ new_status);
}
// Build the metadata array
@@ -369,8 +368,9 @@ static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
}
static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleton,
- GDBusMethodInvocation *invocation, const gdouble volume,
- __attribute__((unused)) gpointer user_data) {
+ GDBusMethodInvocation *invocation,
+ const gdouble volume,
+ __attribute__((unused)) gpointer user_data) {
debug(2, "Set airplay volume to %.6f.", volume);
char command[256] = "";
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume);
@@ -379,8 +379,6 @@ static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleto
return TRUE;
}
-
-
gboolean notify_elapsed_time_callback(ShairportSyncDiagnostics *skeleton,
__attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_elapsed_time_callback\" called.");
@@ -408,7 +406,7 @@ gboolean notify_delta_time_callback(ShairportSyncDiagnostics *skeleton,
}
gboolean notify_file_and_line_callback(ShairportSyncDiagnostics *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_file_and_line_callback\" called.");
if (shairport_sync_diagnostics_get_file_and_line(skeleton)) {
config.debugger_show_file_and_line = 1;
@@ -463,12 +461,13 @@ gboolean notify_disable_standby_callback(ShairportSync *skeleton,
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_convolution_callback\" called.");
if (shairport_sync_get_convolution(skeleton)) {
debug(1, ">> activating convolution");
config.convolution = 1;
- config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ config.convolver_valid =
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
} else {
debug(1, ">> deactivating convolution");
config.convolution = 0;
@@ -477,7 +476,7 @@ gboolean notify_convolution_callback(ShairportSync *skeleton,
}
#else
gboolean notify_convolution_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
warn(">> Convolution support is not built in to this build of Shairport Sync.");
return TRUE;
}
@@ -485,7 +484,7 @@ gboolean notify_convolution_callback(__attribute__((unused)) ShairportSync *skel
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_gain_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
gdouble th = shairport_sync_get_convolution_gain(skeleton);
if ((th <= 0.0) && (th >= -100.0)) {
@@ -499,34 +498,38 @@ gboolean notify_convolution_gain_callback(ShairportSync *skeleton,
}
#else
gboolean notify_convolution_gain_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
warn(">> Convolution support is not built in to this build of Shairport Sync.");
return TRUE;
}
#endif
#ifdef CONFIG_CONVOLUTION
gboolean notify_convolution_impulse_response_file_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused))
+ gpointer user_data) {
char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
if (config.convolution_ir_file)
free(config.convolution_ir_file);
config.convolution_ir_file = strdup(th);
- debug(1, ">> setting configuration impulse response filter file to \"%s\".", config.convolution_ir_file);
- config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ debug(1, ">> setting configuration impulse response filter file to \"%s\".",
+ config.convolution_ir_file);
+ config.convolver_valid =
+ convolver_init(config.convolution_ir_file, config.convolution_max_length);
return TRUE;
}
#else
-gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused)) ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
- __attribute__((unused)) char *th = (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
+gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused))
+ ShairportSync *skeleton,
+ __attribute__((unused))
+ gpointer user_data) {
+ __attribute__((unused)) char *th =
+ (char *)shairport_sync_get_convolution_impulse_response_file(skeleton);
return TRUE;
}
#endif
-
-
gboolean notify_loudness_callback(ShairportSync *skeleton,
- __attribute__((unused)) gpointer user_data) {
+ __attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_loudness_callback\" called.");
if (shairport_sync_get_loudness(skeleton)) {
debug(1, ">> activating loudness");
@@ -781,7 +784,6 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
return TRUE;
}
-
static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name,
__attribute__((unused)) gpointer user_data) {
@@ -823,8 +825,8 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
G_CALLBACK(notify_convolution_gain_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::convolution-impulse-response-file",
G_CALLBACK(notify_convolution_impulse_response_file_callback), NULL);
- g_signal_connect(shairportSyncSkeleton, "notify::loudness",
- G_CALLBACK(notify_loudness_callback), NULL);
+ g_signal_connect(shairportSyncSkeleton, "notify::loudness", G_CALLBACK(notify_loudness_callback),
+ NULL);
g_signal_connect(shairportSyncSkeleton, "notify::loudness-threshold",
G_CALLBACK(notify_loudness_threshold_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::drift-tolerance",
@@ -879,7 +881,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-set-airplay-volume",
G_CALLBACK(on_handle_set_airplay_volume), NULL);
-
g_signal_connect(shairportSyncAdvancedRemoteControlSkeleton, "handle-set-volume",
G_CALLBACK(on_handle_set_volume), NULL);
@@ -975,9 +976,11 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
shairport_sync_set_convolution(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
}
if (config.convolution_ir_file)
- shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), config.convolution_ir_file);
+ shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton),
+ config.convolution_ir_file);
// else
-// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), NULL);
+// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton),
+// NULL);
#endif
shairport_sync_set_version(SHAIRPORT_SYNC(shairportSyncSkeleton), PACKAGE_VERSION);
@@ -1047,14 +1050,14 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
static void on_dbus_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
__attribute__((unused)) const gchar *name,
__attribute__((unused)) gpointer user_data) {
- warn("Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", name,
+ warn("could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus.", name,
(config.dbus_service_bus_type == DBT_session) ? "session" : "system");
}
static void on_dbus_name_lost(__attribute__((unused)) GDBusConnection *connection,
__attribute__((unused)) const gchar *name,
__attribute__((unused)) gpointer user_data) {
- // debug(1, "Could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus --
+ // debug(1, "could not acquire a Shairport Sync native D-Bus interface \"%s\" on the %s bus --
// will try adding the process "
// "number to the end of it.",
// name, (config.dbus_service_bus_type == DBT_session) ? "session" : "system");
diff --git a/man/shairport-sync.7 b/man/shairport-sync.7
index 9048b5510..7446662cc 100644
--- a/man/shairport-sync.7
+++ b/man/shairport-sync.7
@@ -35,8 +35,6 @@ Within the configuration file, settings are organised into groups, for example,
\fBname = "Mike's Boombox";\f1
-\fBinterpolation = "soxr";\f1
-
\fBpassword = "secret";\f1
\fBoutput_backend = "alsa";\f1
@@ -53,7 +51,7 @@ Within the configuration file, settings are organised into groups, for example,
\fB};\f1
-Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the volume control's name should be specified. It is highly desirable to use the output device's mixer for volume control, if available -- response time is reduced to zero and the processor load is reduced. In the example above, "soxr" interpolation was also enabled.
+Most settings have sensible default values, so -- as in the example above -- users generally only need to set (1) the service name, (2) a password (if desired) and (3) the output device. If the output device has a mixer that can be used for volume control, then (4) the mixer name should be specified. It is important to do this if the mixer exists. Otherwise, the maximum output from the output device will be whatever setting the mixer happens to have, which will be a matter of chance and which could be very low or even silent.
A sample configuration file with all possible settings, but with all of them commented out, is installed at \fIshairport-sync.conf.sample\f1, within the System Configuration Directory -- \fI/etc\f1 in Linux, \fI/usr/local/etc\f1 in BSD unixes.
@@ -67,7 +65,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. "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 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.3.6" and \fB%V\f1 for the shairport-sync version string, e.g. "3.3.6-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
The default is "%H", which is replaced by the hostname with the first letter capitalised.
.TP
@@ -75,7 +73,7 @@ The default is "%H", which is replaced by the hostname with the first letter cap
Require the password \fIpassword\f1 to connect to the service. If you leave this setting commented out, no password is needed.
.TP
\fBinterpolation=\f1\fI"mode"\f1\fB;\f1
-Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly in synchrony with the player. The default mode, "basic", is normally almost completely inaudible. The alternative mode, "soxr", is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled.
+Interpolate, or "stuff", the audio stream using the \fImode\f1. Interpolation here refers to the process of adding or removing frames of audio to or from the stream sent to the output device to keep it exactly synchronised with the player. The "basic" mode is normally almost completely inaudible. The alternative mode, "soxr", is even less obtrusive but requires much more processing power. For this mode, support for libsoxr, the SoX Resampler Library, must be selected when shairport-sync is compiled. The default setting is "auto", which will choose "soxr" if support for it has been compiled into the build of Shairport Synce and if the CPU is fast enough. Otherwise, "basic" stuffing will be chosen.
.TP
\fBoutput_backend=\f1\fI"backend"\f1\fB;\f1
shairport-sync has a number of modules of code ("backends") through which audio is output. Normally, the first audio backend that works is selected. This setting forces the selection of the specific audio \fIbackend\f1. Perform the command \fBshairport-sync -h\f1 to get a list of available audio backends -- the default is the first on this list. Only the "alsa", "sndio" and "pa" backends support synchronisation.
@@ -218,14 +216,14 @@ Specify the \fIname\f1 of the mixer control to be used by shairport-sync to cont
By default, the mixer is assumed to be output_device. Use this setting to specify a device other than the output device.
.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.
+Use this setting to specify the frame rate to output to the ALSA device. Allowable values are "auto" (default), 44100, 88200, 176400 and 352800. The device must have the capability to accept the rate you specify. There is no particular reason to use anything other than 44100 if it is available, and if "auto" is selected, the lowest of these rates available, starting at 44100, will be selected.
.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.
+Use this setting to specify the format that should be used to send data to the ALSA device. Allowable values are "auto" (default), "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 unmodified 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.
+If you are using a hardware mixer, S16 is fine, as audio will pass through Shairport Sync unmodified except for interpolation, but any of the higher-resolution formats are okay too. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, in order to get the lowest possible levels of dither. The "auto" setting will cause Shairport Sync to choose the highest resolution available.
.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.
@@ -326,10 +324,10 @@ The maximum packet size for any UDP metadata. This must be between 500 or 65000.
\fB"DIAGNOSTICS" SETTINGS\f1
.TP
\fBstatistics=\f1\fI"setting"\f1\fB;\f1
-Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information on the console or in the log. The default is to disable statistics.
+Use this \fIsetting\f1 to enable ("yes") or disable ("no") the output of some statistical information to the system log (or to \fISTDERR\f1 if the \fB-u\f1 command line option is chosen). The default is to disable statistics.
.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.
+Use this to specify how much debugging information should sent to the system log (or to \fISTDERR\f1 if the \fB-u\f1 command line option is chosen). The value \fI0\f1 means no debug information, \fI3\f1 means most debug information. The default is \fI0\f1.
.SH OPTIONS
This section is about the command-line options available in shairport-sync.
@@ -425,7 +423,7 @@ If you are running shairport-sync from the command line and want logs to appear
Print version information and exit.
.TP
\fB-v | --verbose\f1
-Print debug information. Repeat up to three times to get more detail.
+Print debug information to the system log, or or to \fISTDERR\f1 if the \fB-u\f1 command line option is also chosen. Repeat up to three times to get more detail.
.TP
\fB-w | --wait-cmd\f1
Wait for commands specified using \fB-B\f1 or \fB-E\f1 to complete before continuing execution.
diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml
index 93b24146d..e5dc1c568 100644
--- a/man/shairport-sync.7.xml
+++ b/man/shairport-sync.7.xml
@@ -7,7 +7,7 @@
Copyright (c) Mike Brady 2014-2019
All rights reserved.
-
+
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
@@ -15,10 +15,10 @@
copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
-
+
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
-
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -61,64 +61,63 @@
-
Shairport Sync plays
- audio streamed from iTunes
- or from an AirPlay device to an ALSA-compatible audio output device (available on
- Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
+
Shairport Sync plays
+ audio streamed from iTunes
+ or from an AirPlay device to an ALSA-compatible audio output device (available on
+ Linux and FreeBSD), to a "sndio" output device (available on OpenBSD, FreeBSD and
Linux), to a PulseAudio output stream or to Jack Audio.
-
+
Shairport Sync offers full audio synchronisation.
- Full audio synchronisation means that audio is played on the output device at exactly
+ Full audio synchronisation means that audio is played on the output device at exactly
the time specified by the audio source.
This means that if many devices are playing the same stream at the same
time, all the outputs will stay in synchrony with one another.
- This allows multiple devices to play the same source without getting out of step with
+ This allows multiple devices to play the same source without getting out of step with
one another, enabling, for example, simultaneous multi-room operation.
-
-
Shairport Sync can stream synchronised audio to a unix
- pipe or to standard output, or to audio systems that do not provide timing
- information. This could perhaps be described as partial audio synchronisation, where
- synchronised audio is provided by Shairport Sync, but what happens to it in the
- subsequent processing chain, before it reaches the listener's ear, is outside the
+
+
Shairport Sync can stream synchronised audio to a unix
+ pipe or to standard output, or to audio systems that do not provide timing
+ information. This could perhaps be described as partial audio synchronisation, where
+ synchronised audio is provided by Shairport Sync, but what happens to it in the
+ subsequent processing chain, before it reaches the listener's ear, is outside the
control of shairport-sync.
-
Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
+
Shairport Sync can be compiled to stream metadata, including cover art, to a pipe
or socket.
-
Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
- D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
+
Shairport Sync can be compiled to offer a standard MPRIS interface, a "native"
+ D-Bus interface and an MQTT client interface. Through these interfaces, it can provide
metadata, including cover art, and can offer remote control of the audio source.
-
-
Settings can be made using the configuration file (recommended for all new
+
+
Settings can be made using the configuration file (recommended for all new
installations) or by using command-line options.
-
+
The name of the Shairport Sync executable is shairport-sync.
Both names are used in these man pages.
-
+
-
-
+
+
You should use the configuration file for setting up Shairport Sync.
This file is usually shairport-sync.conf and is generally located in the
- System Configuration Directory, which is normally the /etc directory in
+ System Configuration Directory, which is normally the /etc directory in
Linux or the /usr/local/etc directory in BSD unixes.
You may need to have root privileges to modify it.
-
-
(Note: Shairport Sync may have been compiled to use a different configuration
- directory. You can determine which by performing the command $ shairport-sync
- -V. One of the items in the output string is the value of the
+
+
(Note: Shairport Sync may have been compiled to use a different configuration
+ directory. You can determine which by performing the command $ shairport-sync
+ -V. One of the items in the output string is the value of the
sysconfdir,
i.e. the System Configuration Directory.)
-
-
+
+
Within the configuration file, settings are organised into groups, for
example, there is a "general" group of
standard settings, and there is an "alsa" group with settings that pertain to the ALSA
back end. Here is an example of a typical configuration file:
-
+
general = {
name = "Mike's Boombox";
-
interpolation = "soxr";
password = "secret";
output_backend = "alsa";
};
@@ -127,20 +126,20 @@
output_device = "hw:0";
mixer_control_name = "PCM";
};
-
-
Most settings have sensible default values, so -- as in the example above -- users
+
+
Most settings have sensible default values, so -- as in the example above -- users
generally only need to set (1) the service name, (2) a password (if desired) and
- (3) the output device. If the output device has a mixer that can be used for volume
- control, then (4) the volume control's name should be specified. It is highly
- desirable to use the output device's mixer for volume control, if available --
- response time is reduced to zero and the processor load is reduced. In the example
- above, "soxr" interpolation was also enabled.
-
-
A sample configuration file with all possible settings, but with all of them
- commented out, is installed at shairport-sync.conf.sample, within the
- System Configuration Directory -- /etc in Linux,
+ (3) the output device. If the output device has a mixer that can be used for volume
+ control, then (4) the mixer name should be specified. It is important
+ to do this if the mixer exists. Otherwise, the
+ maximum output from the output device will be whatever setting the mixer happens to
+ have, which will be a matter of chance and which could be very low or even silent.
+
+
A sample configuration file with all possible settings, but with all of them
+ commented out, is installed at shairport-sync.conf.sample, within the
+ System Configuration Directory -- /etc in Linux,
/usr/local/etc in BSD unixes.
-
+
To retain backwards compatibility with previous versions of shairport-sync
you can use still use command line options, but any new features, etc. will
be available only via configuration file settings.
@@ -157,88 +156,92 @@
Use this service_name to identify this player in iTunes, etc.
The following substitutions are allowed:
%h for the computer's hostname,
- %H for the computer's hostname with the first letter capitalised (ASCII
+ %H for the computer's hostname with the first letter capitalised (ASCII
only),
- %v for the shairport-sync version number, e.g. "3.0.1" and
- %V 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
+ %v for the shairport-sync version number, e.g. "3.3.6" and
+ %V for the shairport-sync version string, e.g.
+ "3.3.6-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc".
+
The default is "%H", which is replaced by the hostname with the first letter
capitalised.