summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Boot <bootc@debian.org>2019-12-01 19:17:08 +0000
committerChris Boot <bootc@debian.org>2019-12-01 19:17:08 +0000
commit37114008397bd900c111c8bda912b2a486401ae9 (patch)
tree876f6346de32e490c497000c08da19fd9e38eb73
parent2034d40f508884ff003bd707590d0a39191a716d (diff)
parentb1a505649427832b3e7240eabb27325a57a747e1 (diff)
New upstream version 3.3.5
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--CAR INSTALL.md2
-rw-r--r--FFTConvolver/convolver.cpp105
-rw-r--r--FFTConvolver/convolver.h2
-rw-r--r--INSTALL.md10
-rw-r--r--README.md16
-rw-r--r--RELEASENOTES.md95
-rw-r--r--TROUBLESHOOTING.md2
-rw-r--r--activity_monitor.c2
-rw-r--r--alac.h2
-rw-r--r--audio.c2
-rw-r--r--audio_alsa.c54
-rw-r--r--audio_jack.c4
-rw-r--r--audio_sndio.c2
-rw-r--r--common.c293
-rw-r--r--common.h54
-rw-r--r--configure.ac4
-rw-r--r--dacp.c550
-rw-r--r--dbus-service.c241
-rw-r--r--documents/sample dbus commands50
-rw-r--r--loudness.c2
-rw-r--r--man/shairport-sync.78
-rw-r--r--man/shairport-sync.7.xml6
-rw-r--r--mdns_avahi.c151
-rw-r--r--metadata_hub.c635
-rw-r--r--metadata_hub.h93
-rw-r--r--mpris-service.c139
-rw-r--r--org.gnome.ShairportSync.xml6
-rw-r--r--player.c302
-rw-r--r--player.h12
-rw-r--r--rtp.c10
-rw-r--r--rtsp.c111
-rw-r--r--scripts/shairport-sync.conf9
-rwxr-xr-xscripts/shairport-sync.in2
-rw-r--r--shairport-sync-dbus-test-client.c15
-rw-r--r--shairport-sync.spec10
-rw-r--r--shairport.c258
-rw-r--r--tinyhttp/chunk.h2
-rw-r--r--tinysvcmdns.c4
39 files changed, 1830 insertions, 1436 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..b7d5ff8
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: [paypal.me/UMBr]
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index 879015a..062b6df 100644
--- a/CAR INSTALL.md
+++ b/CAR INSTALL.md
@@ -19,7 +19,7 @@ In this example, a Raspberry Pi Zero W and a Pimoroni PHAT DAC are used. This co
### Prepare the initial SD Image
* Download the latest version of Raspbian Lite -- Stretch Lite of 2018-03-13 at the time of writing -- and install it onto an SD Card.
* Mount the card on a Linux machine. Two drives should appear -- a `boot` drive and a `rootfs` drive. Both of these need a little modification.
-* Enable SSH service by creating a file called `ssh` on the `boot` drive. To do this, mount the drive and CD to its `boot` partiton (since my username is `mike`, the drive is at `/media/mike/boot`):
+* Enable SSH service by creating a file called `ssh` on the `boot` drive. To do this, mount the drive and CD to its `boot` partition (since my username is `mike`, the drive is at `/media/mike/boot`):
```
$ touch ssh
```
diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp
index f115cf8..a282482 100644
--- a/FFTConvolver/convolver.cpp
+++ b/FFTConvolver/convolver.cpp
@@ -1,66 +1,85 @@
-#include "convolver.h"
+#include <pthread.h>
#include <sndfile.h>
+#include "convolver.h"
#include "FFTConvolver.h"
#include "Utilities.h"
-extern "C" void die(const char *format, ...);
-extern "C" void debug(int level, const char *format, ...);
+extern "C" void _warn(const char *filename, const int linenumber, const char *format, ...);
+extern "C" void _debug(const char *filename, const int linenumber, int level, const char *format, ...);
+
+#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__)
+#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__)
-static fftconvolver::FFTConvolver convolver_l;
-static fftconvolver::FFTConvolver convolver_r;
+fftconvolver::FFTConvolver convolver_l;
+fftconvolver::FFTConvolver convolver_r;
+// always lock use this when accessing the playing conn value
+pthread_mutex_t convolver_lock = PTHREAD_MUTEX_INITIALIZER;
-void convolver_init(const char* filename, int max_length)
-{
+
+int convolver_init(const char* filename, int max_length) {
+ int success = 0;
SF_INFO info;
- assert(filename);
- SNDFILE* file = sf_open(filename, SFM_READ, &info);
- assert(file);
-
- if (info.samplerate != 44100)
- die("Impulse file \"%s\" sample rate is %d Hz. Only 44100 Hz is supported", filename, info.samplerate);
+ if (filename) {
+ SNDFILE* file = sf_open(filename, SFM_READ, &info);
+ if (file) {
- if (info.channels != 1 && info.channels != 2)
- die("Impulse file \"%s\" contains %d channels. Only 1 or 2 is supported.", filename, info.channels);
+ if (info.samplerate == 44100) {
+ if ((info.channels == 1) || (info.channels == 2)) {
+ const size_t size = info.frames > max_length ? max_length : info.frames;
+ float buffer[size*info.channels];
- const size_t size = info.frames > max_length ? max_length : info.frames;
- float buffer[size*info.channels];
+ size_t l = sf_readf_float(file, buffer, size);
+ if (l != 0) {
+ pthread_mutex_lock(&convolver_lock);
+ convolver_l.reset(); // it is possible that init could be called more than once
+ convolver_r.reset(); // so it could be necessary to remove all previous settings
- size_t l = sf_readf_float(file, buffer, size);
- assert(l == size);
-
- if (info.channels == 1) {
- convolver_l.init(352, buffer, size);
- convolver_r.init(352, buffer, size);
- } else {
- // deinterleave
- float buffer_l[size];
- float buffer_r[size];
+ if (info.channels == 1) {
+ convolver_l.init(352, buffer, size);
+ convolver_r.init(352, buffer, size);
+ } else {
+ // deinterleave
+ float buffer_l[size];
+ float buffer_r[size];
- unsigned int i;
- for (i=0; i<size; ++i)
- {
- buffer_l[i] = buffer[2*i+0];
- buffer_r[i] = buffer[2*i+1];
- }
+ unsigned int i;
+ for (i=0; i<size; ++i)
+ {
+ buffer_l[i] = buffer[2*i+0];
+ buffer_r[i] = buffer[2*i+1];
+ }
- convolver_l.init(352, buffer_l, size);
- convolver_r.init(352, buffer_r, size);
+ convolver_l.init(352, buffer_l, size);
+ convolver_r.init(352, buffer_r, size);
+
+ }
+ pthread_mutex_unlock(&convolver_lock);
+ success = 1;
+ }
+ debug(1, "IR initialized from \"%s\" with %d channels and %d samples", filename, info.channels, size);
+ } else {
+ warn("Impulse file \"%s\" contains %d channels. Only 1 or 2 is supported.", filename, info.channels);
+ }
+ } else {
+ warn("Impulse file \"%s\" sample rate is %d Hz. Only 44100 Hz is supported", filename, info.samplerate);
+ }
+ sf_close(file);
+ }
}
-
- debug(1, "IR initialized from \"%s\" with %d channels and %d samples", filename, info.channels, size);
-
- sf_close(file);
+ return success;
}
-void convolver_process_l(float* data, int length)
-{
+void convolver_process_l(float* data, int length) {
+ pthread_mutex_lock(&convolver_lock);
convolver_l.process(data, data, length);
+ pthread_mutex_unlock(&convolver_lock);
}
-void convolver_process_r(float* data, int length)
-{
+void convolver_process_r(float* data, int length) {
+ pthread_mutex_lock(&convolver_lock);
convolver_r.process(data, data, length);
+ pthread_mutex_unlock(&convolver_lock);
}
diff --git a/FFTConvolver/convolver.h b/FFTConvolver/convolver.h
index 1b5a98a..56f7bc2 100644
--- a/FFTConvolver/convolver.h
+++ b/FFTConvolver/convolver.h
@@ -5,7 +5,7 @@
extern "C" {
#endif
-void convolver_init(const char* file, int max_length);
+int convolver_init(const char* file, int max_length);
void convolver_process_l(float* data, int length);
void convolver_process_r(float* data, int length);
diff --git a/INSTALL.md b/INSTALL.md
index 8d8dc7d..2d21b9c 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -1,8 +1,8 @@
Simple Installation Instructions
==
-Here are simple instructions for building and installing Shairport Sync on a Raspberry Pi B, 2B, 3B or 3B+. It is assumed that the Pi is running Raspbian Stretch Lite – a GUI isn't needed, since Shairport Sync runs as a daemon program. For a more thorough treatment, please go to the [README.md](https://github.com/mikebrady/shairport-sync/blob/master/README.md#building-and-installing) page.
+Here are simple instructions for building and installing Shairport Sync on a Raspberry Pi B, 2B, 3B, 3B+ or 4B. It is assumed that the Pi is running Raspbian Buster Lite – a GUI isn't needed, since Shairport Sync runs as a daemon program. For a more thorough treatment, please go to the [README.md](https://github.com/mikebrady/shairport-sync/blob/master/README.md#building-and-installing) page.
-In the commands below, note the convention that a `#` prompt means you are in superuser mode and a `$` prompt means you are in a regular non-priviliged user mode. You can use `sudo` *("SUperuser DO")* to temporarily promote yourself from user to superuser, if permitted. For example, if you want to execute `apt-get update` in superuser mode and you are in user mode, enter `sudo apt-get update`.
+In the commands below, note the convention that a `#` prompt means you are in superuser mode and a `$` prompt means you are in a regular unprivileged user mode. You can use `sudo` *("SUperuser DO")* to temporarily promote yourself from user to superuser, if permitted. For example, if you want to execute `apt-get update` in superuser mode and you are in user mode, enter `sudo apt-get update`.
### Configure and Update
Do the usual update and upgrade:
@@ -33,6 +33,12 @@ Remove it as follows:
```
Do this until no more copies of `shairport-sync` are found.
+### Remove Old Startup Scripts
+You should also remove the startup script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
+
+### Reboot after Cleaning Up
+If you removed any installations of Shairport Sync or any of its startup script files in the last two steps, you should reboot.
+
### Build and Install
Okay, now let's get the tools and sources for building and installing Shairport Sync.
diff --git a/README.md b/README.md
index 5a7acbe..d9b5c8f 100644
--- a/README.md
+++ b/README.md
@@ -39,9 +39,9 @@ What else?
* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in iOS.
* Compiles on Linux, Cygwin, FreeBSD, OpenBSD.
* Outputs to [`alsa`](https://www.alsa-project.org/wiki/Main_Page), [`sndio`](http://www.sndio.org), [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/), [JACK](http://jackaudio.org), to a unix pipe or to `STDOUT`. It also has limited support for [libao](https://xiph.org/ao/) and for [`soundio`](http://libsound.io).
-* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/) interface, partially complete and very functional, including access to metadata and artwork, and some remote control.
+* 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 remote control and some system settings.
+* A native D-Bus interface, including access to metadata and artwork, some limited remote control and some system settings.
Heritage
-------
@@ -93,11 +93,13 @@ If you wish to build and install the latest version of Shairport Sync on Debian,
You should check to see if `shairport-sync` is already installed – you can use the command `$ which shairport-sync` to find where it is located, if installed. If it is installed you should delete it – you may need superuser privileges. After deleting, check again in case further copies are installed elsewhere.
-You should also remove the initialisation script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
+You should also remove the startup script files `/etc/systemd/system/shairport-sync.service` and `/etc/init.d/shairport-sync` if they exist – new ones will be installed in necessary.
+
+If you removed any installations of Shairport Sync or any of its startup script files, you should reboot.
**Determine The Configuration Needed**
-Shairport Sync has a number of different "backends" that connnect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary:
+Shairport Sync has a number of different "backends" that connect it to the system's audio handling infrastructure. Most recent Linux distributions that have a GUI – including Ubuntu, Debian and others – use PulseAudio to handle sound. In such cases, it is inadvisable to attempt to disable or remove PulseAudio. Thus, if your system uses PulseAudio, you should build Shairport Sync with the PulseAudio backend. You can check to see if PulseAudio is running by opening a Terminal window and entering the command `$ pactl info`. Here is an example of what you'll get if PulseAudio is installed, though the exact details may vary:
```
$ pactl info
Server String: unix:/run/user/1000/pulse/native
@@ -240,7 +242,7 @@ SYNOPSIS
...
```
-If your system is definitely a `systemd` system, choose `--with-libdaemon --with-systemd` below. Otherwise, choose `--with-systemv`.
+If your system is definitely a `systemd` system, choose `--with-systemd` below. Otherwise, choose `--with-libdaemon --with-systemv`.
**Choose the location of the configuration file**
@@ -248,7 +250,7 @@ A final consideration is the location of the configuration file `shairport-sync.
**Sample `./configure` command with parameters for a typical Linux `systemd` installation:**
-Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and later, and Raspbian Stretch and Jessie. It specifies both the ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot:
+Here is a recommended set of configuration options suitable for Linux installations that use `systemd`, such as Ubuntu 15.10 and later, and Raspbian Buster, Stretch and Jessie. It specifies both the ALSA and PulseAudio backends and includes a sample configuration file and an script for automatic startup on system boot:
`$ ./configure --sysconfdir=/etc --with-alsa --with-pa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd`
@@ -609,7 +611,7 @@ This will be followed by the statistics themselves at regular intervals, for exa
"Output frames per second" is the actual rate at which frames of audio are taken by the output device. On a system with a well-conditioned `ntp`-based clock (and without output underruns) this figure should be very accurate after playing material continuously for a period.
-"Source clock drift in ppm" is an estimate of the difference in timekeeping between the audio source and the Shairport Sync devive. It is calculated from a linear regression of drift sample data. The number of samples the estimate is based on is given in the next column, "Source clock drift sample count".
+"Source clock drift in ppm" is an estimate of the difference in timekeeping between the audio source and the Shairport Sync device. It is calculated from a linear regression of drift sample data. The number of samples the estimate is based on is given in the next column, "Source clock drift sample count".
"Rough calculated correction in ppm" is a very crude estimate of the amount of interpolation that needs to applied, on average, to keep sync. It is not really to be relied on at this time.
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 42537bb..76f198c 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -1,7 +1,60 @@
-Version 3.3.2
-===
Please see the [Release Notes for 3.3](https://github.com/mikebrady/shairport-sync/releases/tag/3.3).
+Version 3.3.5
+====
+
+**Bug Fixes**
+* Fix a crashing bug if output format `S24` was chosen.
+* Fix a bug whereby if `Loudness` was enabled through the D-Bus interface, the output would be muted until the volume was changed.
+
+**Enhancements**
+* D-Bus interface enhancements: add `Convolution`, `ConvolutionGain` and `ConvolutionImpulseResponseFile` properties to the D-Bus interface. These properties can be set and changed at any time, even while playing.
+* Update the [sample dbus commands](https://github.com/mikebrady/shairport-sync/blob/master/documents/sample%20dbus%20commands) document.
+
+**Pesky Changes**
+* D-Bus interface change: the D-Bus `LoudnessFilterActive` property has been changed to `Loudness`. The sample D-Bus client has been updated accordingly.
+
+Version 3.3.4
+====
+
+This is Version 3.3.3 with a small compilation error fixed.
+
+Version 3.3.3
+====
+
+**Bug Fixes**
+* Fixes a deferred crash that occurred in Ubuntu 14.04: the `shairport-sync` daemon would silently die after a fairly long period. It typically happened just after a DHCP address was renewed. The problem seemed to be related to having more than one `avahi` threaded polling loop (though this isn't documented anywhere). The fix was to consolidate the `avahi` stuff down to one threaded polling loop. Addresses issue [#895](https://github.com/mikebrady/shairport-sync/issues/895). Thanks to [Hans (the) MCUdude](https://github.com/MCUdude) for reporting and for initial troubleshooting.
+
+* Fixes a potential crash when an incomplete `fmtp` parameter set is sent by the requesting client. Thanks to [Angus71](https://github.com/Angus71) for the fault report and for the repair.
+
+* Fixed a potential crash -- if a plain HTTP packet (in fact, any packet that didn't have an RTSP-style header) was sent to the TCP session port (usually port 5000), Shairport Sync would crash! Thanks to @[dubo-dubon-duponey](https://github.com/dubo-dubon-duponey) for reporting. Fixes [#921](https://github.com/mikebrady/shairport-sync/issues/921).
+
+* A fix ensures the hardware mixer of an `alsa` device is detected and initialised before responding to the first volume setting.
+
+* Fixes were made to the MPRIS and native D-Bus interfaces. In particular, situations where artwork is absent are better handled, and the remote interface and advanced remote interface `availability` properties should be more resilient in the face of network problems. Addresses issue [#890](https://github.com/mikebrady/shairport-sync/issues/890). Improvements were made to the detection of the remote services available when an audio source is playing. If the source is minimally compatible, e.g. iOS, Shairport Sync's `org.gnome.ShairportSync.RemoteControl` native `dbus` interface becomes "`available`". If the source is iTunes, then the `org.gnome.ShairportSync.AdvancedRemoteControl` interface also becomes `available`. Artwork, metadata, status and limited remote control facilities are accessible through these interfaces when they are in the `available` state. Thanks to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
+
+* Fixes an error whereby the `'pvol'`volume metadata was no longer sent if Shairport Sync was configured to ignore volume control information coming from the audio source. Addresses issue [#903](https://github.com/mikebrady/shairport-sync/issues/903). Thanks to [Jordan Bass](https://github.com/jorbas) for reporting the regression and for identifying the commit and code in which the regression occurred.
+
+**Enhancements**
+* Instead of returning `EXIT_FAILURE`, return `EXIT_WITH_SUCCESS` on early exit with either "version" (`–version` or `-V`) or "help" (`–help` or `-h`) arguments. Thanks to [Henrik Nilsson](https://github.com/henriknil) for the patch.
+
+* Normalises the `'pvol`' volume outputs so that when both the software and hardware attenuators are in use to extend the overall attenuation range, the maximum output level corresponds to the maximum output level of the hardware mixer.
+
+* Add the option of including the file and line number of each log entry's source. The option is on by default and is settable in the configuration file and in the `dbus` interface.
+
+* Rewrite the logic for identifying missing packets of audio and for asking for resends. It seems more robust -- there was a suspicion of the previous logic that resend requests were not made for some missing packets. In addition, requests for resends of continuous sequences of packets are rolled into one.
+
+* Expose the advanced settings controlling the resend request logic. The new settings are in the `general` section:
+ * `resend_control_first_check_time` is the time allowed to elapse before a packet is considered missing, defaulting to 0.1 sec. UDP packets don't always arrive in order and they don't need to be re-requested just because they arrive out of sequence. Essentially, therefore, this parameter is to prevent needless resend requests for packets that are already in transit.
+ * `resend_control_check_interval_time` is the interval between repeated requests for a missing packet, defaulting to 0.25 seconds.
+ * `resend_control_last_check_time` is the time by which the last check should be done before the estimated time of a missing packet's transfer to the output buffer, defaulting to 0.1 seconds. In other words, if a packet is still missing 0.1 seconds before it is due to be transferred to the DAC's output buffer, don't bother asking for a resend.
+
+* Exposes two advanced `metadata` settings related to handling cover art:
+ * The setting `cover_art_cache_directory` allows you to specify where cover art files will be cached if Shairport Sync has been built with native D-Bus, MPRIS or MQTT support. The default is `/tmp/shairport-sync/.cache/coverart`. If you set it to an empty list: `""`, caching is disabled. This might be useful in, say, an embedded device, or wherever you want to minimise file writing.
+ * The setting `retain_cover_art` is part of the `diagnostics` group. Set it to `"yes"` to retain cover art cached by the D-Bus, MPRIS or `mqtt` interfaces. Your directory may fill up if you leave it set!
+
+Version 3.3.2
+===
**Bug Fixes**
* Fix a bug that sometimes caused a crash when a service name was specified in the configuration file. The fix was to be more systematic in allocating and deallocating memory for temporary strings. Thanks to [Chris Boot](https://github.com/bootc), [Ari Sovijarvi](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925577#5), [Bernhard Übelacker](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925577#10) and [Jeroen Massar](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925577#17) for the bug report. Fixes [Debian Bug report #925577]( https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925577) and supercedes [Pull Request #879](https://github.com/mikebrady/shairport-sync/pull/879).
* Correct some documentation typos – thanks again to [Chris Boot](https://github.com/bootc).
@@ -143,7 +196,7 @@ Version 3.2RC2
Version 3.2RC1
====
**Enhancements**
-* Shairport Sync now offers an IPC interface via D-Bus. It provides an incomplete but functional MPRIS interface and also provides a native Shairport Sync interface. Both provide some metadata and some remote control. The native D-Bus interface permits remote control of the current AirPlay or iTunes client. It includes status information about whether the remote control connection is viable or not, i.e. whether it can still be used to control the client. A remote control connection to the audio client becomes valid when the client starts AirPlaying to Shairport Sync. The connections remains valid until the audio source deselects Shairport Sync for AirPlay, or until the client disappears, or until another client starts AirPlaying to Shairport Sync. After 15 minutes of inactivity, the remote contol connection will be dropped.
+* Shairport Sync now offers an IPC interface via D-Bus. It provides an incomplete but functional MPRIS interface and also provides a native Shairport Sync interface. Both provide some metadata and some remote control. The native D-Bus interface permits remote control of the current AirPlay or iTunes client. It includes status information about whether the remote control connection is viable or not, i.e. whether it can still be used to control the client. A remote control connection to the audio client becomes valid when the client starts AirPlaying to Shairport Sync. The connections remains valid until the audio source deselects Shairport Sync for AirPlay, or until the client disappears, or until another client starts AirPlaying to Shairport Sync. After 15 minutes of inactivity, the remote control connection will be dropped.
* OpenBSD compatibility. Shairport Sync now compiles on OpenBSD -- please consult the OpenBSD note for details.
* A new `general` option `volume_control_profile`, for advanced use only, with two options: `"standard"` which uses the standard volume control profile -- this has a higher transfer profile at low volumes and a lower transfer profile at high volumes -- or `"flat"` which uses a uniform transfer profile to linearly scale the output mixer's dB according to the AirPlay volume.
* Some DACs have a feature that the lowest permissible "attenuation" value that the built-in hardware mixer can be set to is not an attenuation value at all – it is in fact a command to mute the output completely. Shairport Sync has always checked for this feature, basically in order to ignore it when getting the true range of attenuation values offered by the mixer.
@@ -167,7 +220,7 @@ However, with this enhancement, Shairport Sync can actually use this feature to
**Other Changes**
* `clip` and `svip` metadata messages are now only generated for a play connection, not for all connections (e.g. connections that just enquire if the service is present).
* `pfls` and `prsm` metadata messages are less frequent, especially when a play session starts.
-* Shairport Sync now uses about an extra half megabyte of RAM for compatability with TuneBlade's option to have a very long latency -- up to five seconds.
+* Shairport Sync now uses about an extra half megabyte of RAM for compatibility with TuneBlade's option to have a very long latency -- up to five seconds.
* Only ask for missing packets to be resent once, and if any error occurs making the request, stop for 10 seconds.
* Include the `-pthread` flag -- including the pthread library with `-lpthread` isn't always enough.
* Add optional timing annotations to debug messages -- see the new settings in the diagnostic stanza of the configuration file.
@@ -184,7 +237,7 @@ Version 3.1.7
* Stable 3.1.5 and 3.1.6 skipped to synchronise the shairport-sync.spec file contents with the release.
**Enhancement**
-* The metdata output stream can include a `dapo` message carrying the DACP port number to be used when communicating with the DACP remote control. This might be useful because the port number is actually pretty hard to find and requires the use of asynchronous mdns operations. You must be using the Avahi mdns back end.
+* The metadata output stream can include a `dapo` message carrying the DACP port number to be used when communicating with the DACP remote control. This might be useful because the port number is actually pretty hard to find and requires the use of asynchronous mdns operations. You must be using the Avahi mdns back end.
Version 3.1.4
====
@@ -216,7 +269,7 @@ Shairport Sync is more stable playing audio from YouTube and SoundCloud on the M
* When you update from a previous version of Shairport Sync, your output device may have been left in a muted state. You should use a command line tool like `alsamixer` or `amixer` to unmute the output device before further use.
**Change of Default**
-* The default value for the `alsa` setting `mute_using_playback_switch` has been changed to `"no"` for compatability with other audio players on the same machine. The reason is that when this setting is set to `"yes"`, the output device will be muted when Shairport Sync releases it. Unfortunately, other audio players using the output device expect it to be unmuted, causing problems. Thanks to [Tim Curtis](https://github.com/moodeaudio) at [Moode Audio](http://moodeaudio.org) and [Peter Pablo](https://github.com/PeterPablo) for clarifying the issue.
+* The default value for the `alsa` setting `mute_using_playback_switch` has been changed to `"no"` for compatibility with other audio players on the same machine. The reason is that when this setting is set to `"yes"`, the output device will be muted when Shairport Sync releases it. Unfortunately, other audio players using the output device expect it to be unmuted, causing problems. Thanks to [Tim Curtis](https://github.com/moodeaudio) at [Moode Audio](http://moodeaudio.org) and [Peter Pablo](https://github.com/PeterPablo) for clarifying the issue.
**Bug Fixes**
* Fixed bugs that made Shairport Sync drop out or become unavailable when playing YouTube videos, SoundCloud streams etc. from the Mac. Background: there has been a persistent problem with Shairport Sync becoming unavailable after playing, say, a YouTube clip in a browser on the Mac. Shairport Sync 3.1.2 incorporates a change to how certain AirPlay messages are handled. Introduced in nascent form in 3.1.1, further follow-on changes have improved the handling of player lock and have simplified and improved the handling of unexpected loss of connection. Shairport Sync also now works properly with SoundCloud clips played in a browser on the Mac.
@@ -243,7 +296,7 @@ Version 3.1 brings two new backends, optional loudness and convolution filters,
**Pesky Changes You Should Know About**
* The `audio_backend_buffer_desired_length_in_seconds` and `audio_backend_latency_offset_in_seconds` settings have been moved from individual backend stanzas to the `general` stanza. They now have an effect on every type of backend.
-* If you are using a System V (aka `systemv`) installation, please note that the default location for PID file has moved -- it is now stored at `/var/run/shairport-sync/shairport-sync.pid`. This change is needed to improve security a little and to improve compatability across platforms. If you're not doing anything strange, this should make no difference.
+* If you are using a System V (aka `systemv`) installation, please note that the default location for PID file has moved -- it is now stored at `/var/run/shairport-sync/shairport-sync.pid`. This change is needed to improve security a little and to improve compatibility across platforms. If you're not doing anything strange, this should make no difference.
**Enhancements**
* Resynchronisation, which happens when the synchronisation is incorrect by more than 50 ms by default, should be a lot less intrusive when it occurs – it should now either insert silence or skip frames, as appropriate.
@@ -345,7 +398,7 @@ Version 3.0d20 – Development Version
Note: all Version 3 changes are summarized above.
**Bug Fix**
-* Fix a small and generally silent error in configure.ac so that it only looks for the systemd direcotry if systemd has been chosen. It caused a warning when cross-compiling.
+* Fix a small and generally silent error in configure.ac so that it only looks for the systemd directory if systemd has been chosen. It caused a warning when cross-compiling.
Version 3.0d19 – Development Version
----
@@ -488,7 +541,7 @@ The following is a summary of the bug fixes and enhancements since version 2.8.3
* Metadata can now be provided via UDP -- thanks to [faceless2](https://github.com/faceless2).
* Statistics output is more machine readable -- thanks to [Jörg Krause](https://github.com/joerg-krause)
-* The `shairport-sync.spec` file has been updated for compatability with building Debian packages using `checkinstall` -- thanks to [vru1](https://github.com/vru1).
+* The `shairport-sync.spec` file has been updated for compatibility with building Debian packages using `checkinstall` -- thanks to [vru1](https://github.com/vru1).
Version 2.8.3.11 – Development Version
----
@@ -609,13 +662,13 @@ For full details, please refer to the release notes here, back as far as 2.9.1.
Version 2.9.2 – Development Version
----
-Version 2.9.2 focusses on further bug fixes and stability improvements.
+Version 2.9.2 focuses on further bug fixes and stability improvements.
* Enhanced stability: an important bug has been fixed in the handling of missing audio frames – i.e. what happens when a frame of audio is truly missing, after all attempts to fetch it have been unsuccessful. The bug would cause Shairport Sync to do an unnecessary resynchronisation, or, if resync was turned off, to jump out of sync. This is a long-standing bug – thanks to [Jörg Krause](https://github.com/joerg-krause) for identifying it.
* An extra diagnostic has been added which gives the mean, standard deviation and maximum values for inter-packet reception time on the audio port. It may be useful for exploring line quality.
Version 2.9.1 – Development Version
----
-Version 2.9.1 focusses on bug fixes and stability improvements.
+Version 2.9.1 focuses on bug fixes and stability improvements.
* Stability improvements are concentrated on what happens when a play sessions ends and is followed immediately by a new session. This happens in iOS 9.2 when you click to the next track or to the previous track. It also happens playing YouTube videos when a Mac's System Audio is routed through AirPlay. Thanks to [Tim Curtis](https://github.com/moodeaudio) for help with these issues.
* A workaround for an apparent flushing issue in TuneBlade has been included. Thanks to [gibman](https://github.com/gibman) for reporting this issue.
* A number of bug fixes have been made to `configure.ac` – thanks to [Jörg Krause](https://github.com/joerg-krause).
@@ -694,7 +747,7 @@ Version 2.7.5 -- Development Version
Version 2.7.4 -- Development Version
----
**Enhancements**
-* Use the correct method for finding the `systemd` unit path, as recomended by debain maintainers and
+* Use the correct method for finding the `systemd` unit path, as recommended by Debian maintainers and
http://www.freedesktop.org/software/systemd/man/daemon.html#Installing%20Systemd%20Service%20Files. Thanks to [dantheperson](https://github.com/dantheperson).
* Rather than hardwire the path `/usr/local/bin` as the path to the shairport-sync executable, the value of `$PREFIX` is now used during configuration. Thanks to [Nick Steel](https://github.com/kingosticks).
* Add some extra diagnostic messages if the hardware buffer in the DAC is smaller than desired.
@@ -726,7 +779,7 @@ Version 2.7.1 -- Development Version
Version 2.7 -- Development Version
----
**New Features**
-* Extend the volume range for some DACs. Background: some of the cheaper DACS have a very small volume range (that is, the ratio of the highest to the lowest volume, expressed in decibels, is very small). In some really cheap DACs it's only around 30 dB. That means that the difference betweeen the lowest and highest volume settings isn't large enough. With the new feature, if you set the `general` `volume_range_db` to more than the hardware mixer's range, Shairport Sync will combine the hardware mixer's range with a software attenuator to give the desired range. For example, suppose you want a volume range of 70 dB and the hardware mixer offers only 30 dB, then Shairport Sync will make up the other 40 dB with a software attenuator. One drawback is that, when the volume is being changed, there may be a slight delay (0.15 seconds by default) as the audio, whose volume may have been adjusted in software, propagates through the system. Another slight possible drawback is a slightly heavier load on the processor.
+* Extend the volume range for some DACs. Background: some of the cheaper DACS have a very small volume range (that is, the ratio of the highest to the lowest volume, expressed in decibels, is very small). In some really cheap DACs it's only around 30 dB. That means that the difference between the lowest and highest volume settings isn't large enough. With the new feature, if you set the `general` `volume_range_db` to more than the hardware mixer's range, Shairport Sync will combine the hardware mixer's range with a software attenuator to give the desired range. For example, suppose you want a volume range of 70 dB and the hardware mixer offers only 30 dB, then Shairport Sync will make up the other 40 dB with a software attenuator. One drawback is that, when the volume is being changed, there may be a slight delay (0.15 seconds by default) as the audio, whose volume may have been adjusted in software, propagates through the system. Another slight possible drawback is a slightly heavier load on the processor.
* Check for underflow a little better when buffer aliasing occurs on very bad connections...
* Add extra debug messages to the alsa back end to diagnose strange DACs.
* Add configuration file for the `libao` back end -- to change the buffer size and the latency offset, same as for stdout.
@@ -748,7 +801,7 @@ This is basically version 2.4.2 with two small fixes. It's been bumped to 2.6 be
Version 2.4.2
----
-This release has important enhancements, bug fixes and documentation updates. It also appears to bring compatiblity with Synology NAS devices.
+This release has important enhancements, bug fixes and documentation updates. It also appears to bring compatibility with Synology NAS devices.
**New Features**
@@ -767,7 +820,7 @@ Using source-specified latencies is now automatic unless non-standard static lat
* Volume ratios expressed in decibels are now consistently denominated in voltage decibels rather than power decibels. The rationale is that the levels refer to voltage levels, and power is proportional to the square of voltage.
Thus a ratio of levels of 65535 to 1 is 96.3 dB rather than the 48.15 dB used before.
* The latency figure returned to the source as part of the response to an rtsp request packet is 11,025, which may (?) be meant to indicate the minimum latency the device is capable of.
-* An experimental handler for a GET_PARAMETER rtsp request has been added. It does nothing except log the occurence.
+* An experimental handler for a GET_PARAMETER rtsp request has been added. It does nothing except log the occurrence.
* The RTSP request dispatcher now logs an event whenever an unrecognised rtsp has been made.
@@ -779,8 +832,8 @@ This release has three small bug fixes and some small documentation updates.
Changes from the previous stable version -- 2.4 -- are summarised here:
* The USE_CUSTOM_LOCAL_STATE_DIR macro was still being used when it should have been USE_CUSTOM_PID_DIR. This could affect users using a custom location for the PID directory.
- * A compiler error has been fixed that occured if metadata was enabled and tinysvcmdns was included.
- * A crash has been fixed that occured if metadata was enabled and a metadata pipename was not specified.
+ * A compiler error has been fixed that occurred if metadata was enabled and tinysvcmdns was included.
+ * A crash has been fixed that occurred if metadata was enabled and a metadata pipe name was not specified.
(Thanks to the contributors who reported bugs.)
**Small Changes**
@@ -827,7 +880,7 @@ Version 2.3.12
**Enhancements**
-* Larger range of interpolation. Shairport Sync was previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 frames. This contraint has been relaxed, and it is now able to make corrections of up to 1 in 352 frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows:
+* Larger range of interpolation. Shairport Sync was previously constrained not to make interpolations ("corrections") of more than about 1 per 1000 frames. This constraint has been relaxed, and it is now able to make corrections of up to 1 in 352 frames. This might result in a faster and undesirably sudden correction early during a play session, so a number of further changes have been made. The full set of these changes is as follows:
* No corrections happen for the first five seconds.
* Corrections of up to about 1 in 1000 for the next 25 seconds.
* Corrections of up to 1 in 352 thereafter.
@@ -846,7 +899,7 @@ Bug fix
* The "pipe" backend used output code that would block if the pipe didn't have a reader. This has been replaced by non-blocking code. Here are some implications:
* When the pipe is created, Shairport Sync will not block if a reader isn't present.
* If the pipe doesn't have a reader when Shairport Sync wants to output to it, the output will be discarded.
- * If a reader disappears while writing is occuring, the write will time out after five seconds.
+ * If a reader disappears while writing is occurring, the write will time out after five seconds.
* Shairport Sync will only close the pipe on termination.
Version 2.3.9
@@ -1001,7 +1054,7 @@ Version 2.2
-----
* Enhancements:
* New password option: `--password=SECRET`
- * New tolerance option: `--tolerance=FRAMES`. Use this option to specify the largest synchronisation error to allow before making corrections. The default is 88 frames, i.e. 2 milliseconds. The default tolerance is fine for streaming over wired ethernet; however, if some of the stream's path is via WiFi, or if the source is a third-party product, it may lead to much overcorrection -- i.e. the difference between "corrections" and "net correction" in the `--statistics` option. Increasing the tolerence may reduce the amount of overcorrection.
+ * New tolerance option: `--tolerance=FRAMES`. Use this option to specify the largest synchronisation error to allow before making corrections. The default is 88 frames, i.e. 2 milliseconds. The default tolerance is fine for streaming over wired ethernet; however, if some of the stream's path is via WiFi, or if the source is a third-party product, it may lead to much overcorrection -- i.e. the difference between "corrections" and "net correction" in the `--statistics` option. Increasing the tolerance may reduce the amount of overcorrection.
Version 2.1.15
-----
@@ -1056,7 +1109,7 @@ With this feature, you can allow Shairport Sync always to advertise and provide
* Annoying things you should know about if you're updating from a previous version:
* Options `--with-openssl`, `--with-polarssl` have been replaced with a new option `--with-ssl=<option>` where `<option>` is either `openssl` or `polarssl`.
- * Option `--with-localstatedir` has been replaced with `--with-piddir`. This compilation option allows you to specify the directory in which the PID file will be written. The directory must exist and be writable. Supercedes the `--with-localstatedir` and describes the intended functionality a little more accurately.
+ * Option `--with-localstatedir` has been replaced with `--with-piddir`. This compilation option allows you to specify the directory in which the PID file will be written. The directory must exist and be writable. Supersedes the `--with-localstatedir` and describes the intended functionality a little more accurately.
* Bugfixes
* A small (?) bug in the flush logic has been corrected. Not causing any known problem.
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index 262bca6..8a4e14e 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -188,7 +188,7 @@ card 1: MP3 [Sound Blaster MP3+], device 0: USB Audio [USB Audio]
Subdevice #0: subdevice #0
````
-or look at your exisiting '/etc/asound.conf' file, which may look something like this
+or look at your existing '/etc/asound.conf' file, which may look something like this
````
pcm.!default {
diff --git a/activity_monitor.c b/activity_monitor.c
index 544f036..bcc25d2 100644
--- a/activity_monitor.c
+++ b/activity_monitor.c
@@ -123,7 +123,7 @@ void activity_monitor_signify_activity(int active) {
// execute the going_inactive() function.
//
// The reason for all this is that we might want to perform the attached scripts
- // and wait for them to complete before continuing. If they were perfomed in the
+ // and wait for them to complete before continuing. If they were performed in the
// activity monitor thread, then we couldn't wait for them to complete.
// Thus, the only time the thread will execute a going_... function is when a non-zero
diff --git a/alac.h b/alac.h
index a56c29f..33a3592 100644
--- a/alac.h
+++ b/alac.h
@@ -13,7 +13,7 @@ void alac_free(alac_file *alac);
struct alac_file {
unsigned char *input_buffer;
- int input_buffer_bitaccumulator; /* used so we can do arbitary
+ int input_buffer_bitaccumulator; /* used so we can do arbitrary
bit reads */
int samplesize;
diff --git a/audio.c b/audio.c
index cd3f2cb..c71ac93 100644
--- a/audio.c
+++ b/audio.c
@@ -157,7 +157,7 @@ void parse_general_audio_options(void) {
}
}
- /* Get the minumum buffer size for fancy interpolation setting in seconds. */
+ /* Get the minimum buffer size for fancy interpolation setting in seconds. */
if (config_lookup_float(config.cfg,
"general.audio_backend_buffer_interpolation_threshold_in_seconds",
&dvalue)) {
diff --git a/audio_alsa.c b/audio_alsa.c
index 69aaa0a..27e35ed 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -73,7 +73,7 @@ int mute(int do_mute); // returns true if it actually is allowed to use the mute
static double set_volume;
static int output_method_signalled = 0; // for reporting whether it's using mmap or not
int delay_type_notified = -1; // for controlling the reporting of whether the output device can do
- // precison delays (e.g. alsa->pulsaudio virtual devices can't)
+ // precision delays (e.g. alsa->pulsaudio virtual devices can't)
int use_monotonic_clock = 0; // this value will be set when the hardware is initialised
audio_output audio_alsa = {
@@ -139,9 +139,8 @@ static long alsa_mix_mindb, alsa_mix_maxdb;
static char *alsa_out_dev = "default";
static char *alsa_mix_dev = NULL;
-static char *alsa_mix_ctrl = "Master";
+static char *alsa_mix_ctrl = NULL;
static int alsa_mix_index = 0;
-static int hardware_mixer = 0;
static int has_softvol = 0;
int64_t dither_random_number_store = 0;
@@ -204,7 +203,7 @@ int precision_delay_available() {
} else {
pthread_cleanup_push(malloc_cleanup, silence);
int use_dither = 0;
- if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) &&
+ if ((alsa_mix_ctrl == NULL) && (config.ignore_volume_control == 0) &&
(config.airplay_volume != 0.0))
use_dither = 1;
dither_random_number_store =
@@ -279,7 +278,7 @@ void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; }
// assuming pthread cancellation is disabled
int open_mixer() {
int response = 0;
- if (hardware_mixer) {
+ if (alsa_mix_ctrl != NULL) {
debug(3, "Open Mixer");
int ret = 0;
snd_mixer_selem_id_alloca(&alsa_mix_sid);
@@ -372,7 +371,7 @@ unsigned int auto_speed_output_rates[] = {
format_record fr[] = {
{SND_PCM_FORMAT_UNKNOWN, 0}, // unknown
{SND_PCM_FORMAT_S8, 2}, {SND_PCM_FORMAT_U8, 2}, {SND_PCM_FORMAT_S16, 4},
- {SND_PCM_FORMAT_S16_LE, 4}, {SND_PCM_FORMAT_S16_BE, 4}, {SND_PCM_FORMAT_S24, 4},
+ {SND_PCM_FORMAT_S16_LE, 4}, {SND_PCM_FORMAT_S16_BE, 4}, {SND_PCM_FORMAT_S24, 8},
{SND_PCM_FORMAT_S24_LE, 8}, {SND_PCM_FORMAT_S24_BE, 8}, {SND_PCM_FORMAT_S24_3LE, 6},
{SND_PCM_FORMAT_S24_3BE, 6}, {SND_PCM_FORMAT_S32, 8}, {SND_PCM_FORMAT_S32_LE, 8},
{SND_PCM_FORMAT_S32_BE, 8}, {SND_PCM_FORMAT_UNKNOWN, 0}, // auto
@@ -865,15 +864,17 @@ int open_alsa_device(int do_auto_setup) {
return result;
}
-int do_alsa_device_init_if_needed() {
+int prepare_mixer() {
int response = 0;
- // do any alsa device initialisation (general case) if needed
+ // 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 first use
- if (alsa_device_initialised == 0) {
- alsa_device_initialised = 1;
- if (hardware_mixer) {
- debug(2, "alsa: hardware mixer init");
+ // 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
@@ -975,10 +976,14 @@ int do_alsa_device_init_if_needed() {
pthread_cleanup_pop(0);
pthread_setcancelstate(oldState, NULL);
}
- }
return response;
}
+int alsa_device_init() {
+ return prepare_mixer();
+}
+
+
static int init(int argc, char **argv) {
// for debugging
snd_output_stdio_attach(&output, stdout, 0);
@@ -1050,7 +1055,6 @@ static int init(int argc, char **argv) {
/* Get the Mixer Control Name. */
if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) {
alsa_mix_ctrl = (char *)str;
- hardware_mixer = 1;
}
/* Get the disable_synchronization setting. */
@@ -1325,7 +1329,6 @@ static int init(int argc, char **argv) {
break;
case 'c':
alsa_mix_ctrl = optarg;
- hardware_mixer = 1;
break;
case 'i':
alsa_mix_index = strtol(optarg, NULL, 10);
@@ -1412,8 +1415,9 @@ static void start(__attribute__((unused)) int i_sample_rate,
stall_monitor_start_time = 0;
stall_monitor_frame_count = 0;
if (alsa_device_initialised == 0) {
- debug(2, "alsa: start() calling do_alsa_device_init_if_needed.");
- do_alsa_device_init_if_needed();
+ debug(1, "alsa: start() calling alsa_device_init.");
+ alsa_device_init();
+ alsa_device_initialised = 1;
}
}
@@ -1799,6 +1803,11 @@ int prepare(void) {
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0);
if (alsa_backend_state == abm_disconnected) {
+ if (alsa_device_initialised == 0) {
+ // debug(1, "alsa: prepare() calling alsa_device_init.");
+ alsa_device_init();
+ alsa_device_initialised = 1;
+ }
ret = do_open(1); // do auto setup
if (ret == 0)
debug(2, "alsa: prepare() -- opened output device");
@@ -1891,7 +1900,7 @@ void volume(double vol) {
static void linear_volume(double vol) {
debug(2, "Setting linear volume to %f.", vol);
set_volume = vol;
- if (hardware_mixer && alsa_mix_handle) {
+ if ((alsa_mix_ctrl == NULL) && alsa_mix_handle) {
double linear_volume = pow(10, vol);
// debug(1,"Linear volume is %f.",linear_volume);
long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) *
@@ -1933,8 +1942,9 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
}
if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) {
debug(2, "alsa: alsa_buffer_monitor_thread_code() calling "
- "do_alsa_device_init_if_needed.");
- do_alsa_device_init_if_needed();
+ "alsa_device_init.");
+ alsa_device_init();
+ alsa_device_initialised = 1;
}
int sleep_time_ms = (int)(config.disable_standby_mode_silence_scan_interval * 1000);
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0);
@@ -1992,7 +2002,7 @@ void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
int ret;
pthread_cleanup_push(malloc_cleanup, silence);
int use_dither = 0;
- if ((hardware_mixer == 0) && (config.ignore_volume_control == 0) &&
+ if ((alsa_mix_ctrl == NULL) && (config.ignore_volume_control == 0) &&
(config.airplay_volume != 0.0))
use_dither = 1;
dither_random_number_store =
diff --git a/audio_jack.c b/audio_jack.c
index 83398e2..0aff502 100644
--- a/audio_jack.c
+++ b/audio_jack.c
@@ -229,7 +229,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
if (jack_activate(client)) {
die("Could not activate %s JACK client.", config.jack_client_name);
} else {
- debug(2, "JACK client %s activated sucessfully.", config.jack_client_name);
+ debug(2, "JACK client %s activated successfully.", config.jack_client_name);
}
if (config.jack_autoconnect_pattern != NULL) {
inform("config.jack_autoconnect_pattern is %s. If you see the program die after this,"
@@ -257,7 +257,7 @@ int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) char **a
// success
break;
default:
- warn("JACK error no. %d occured while trying to connect %s to %s.", err,
+ warn("JACK error no. %d occurred while trying to connect %s to %s.", err,
full_port_name[i], port_list[i]);
break;
}
diff --git a/audio_sndio.c b/audio_sndio.c
index 54e1763..9fd497a 100644
--- a/audio_sndio.c
+++ b/audio_sndio.c
@@ -256,7 +256,7 @@ static int delay(long *_delay) {
pthread_mutex_lock(&sndio_mutex);
size_t estimated_extra_frames_output = 0;
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 occoured,
+ // 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;
diff --git a/common.c b/common.c
index 3964816..ff643ef 100644
--- a/common.c
+++ b/common.c
@@ -87,6 +87,11 @@
void set_alsa_out_dev(char *);
#endif
+// always lock use this when accessing the fp_time_at_last_debug_message
+static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER;
+
+pthread_mutex_t the_conn_lock = PTHREAD_MUTEX_INITIALIZER;
+
const char *sps_format_description_string_array[] = {
"unknown", "S8", "U8", "S16", "S16_LE", "S16_BE", "S24", "S24_LE",
"S24_BE", "S24_3LE", "S24_3BE", "S32", "S32_LE", "S32_BE", "auto", "invalid"};
@@ -124,6 +129,21 @@ void log_to_stderr() { sps_log = do_sps_log; }
shairport_cfg config;
+// accessors for multi-thread-access fields in the conn structure
+
+double get_config_airplay_volume() {
+ config_lock;
+ double v = config.airplay_volume;
+ config_unlock;
+ return v;
+}
+
+void set_config_airplay_volume(double v) {
+ config_lock;
+ config.airplay_volume = v;
+ config_unlock;
+}
+
volatile int debuglev = 0;
sigset_t pselect_sigset;
@@ -159,115 +179,140 @@ int get_requested_connection_state_to_output() { return requested_connection_sta
void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; }
-void die(const char *format, ...) {
+char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss, double tsl,
+ const char *filename, const int linenumber, const char *prefix) {
+ size_t space_remaining = buffer_length;
+ char *insertion_point = buffer;
+ if (config.debugger_show_elapsed_time) {
+ snprintf(insertion_point, space_remaining, "% 20.9f", tss);
+ insertion_point = insertion_point + strlen(insertion_point);
+ space_remaining = space_remaining - strlen(insertion_point);
+ }
+ if (config.debugger_show_relative_time) {
+ snprintf(insertion_point, space_remaining, "% 20.9f", tsl);
+ insertion_point = insertion_point + strlen(insertion_point);
+ space_remaining = space_remaining - strlen(insertion_point);
+ }
+ if (config.debugger_show_file_and_line) {
+ snprintf(insertion_point, space_remaining, " \"%s:%d\"", filename, linenumber);
+ 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);
+ }
+ return insertion_point;
+}
+
+void _die(const char *filename, const int linenumber, const char *format, ...) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- char s[1024];
- s[0] = 0;
- 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 divisor = (uint64_t)1 << 32;
- double tss = 1.0 * time_since_start / divisor;
- double tsl = 1.0 * time_since_last_debug_message / divisor;
+ char b[1024];
+ b[0] = 0;
+ 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;
+ 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,
+ linenumber, " *fatal error: ");
+ } else {
+ s = b;
+ }
va_list args;
va_start(args, format);
- vsnprintf(s, sizeof(s), format, args);
+ vsnprintf(s, sizeof(b) - (s - b), format, args);
va_end(args);
-
- if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
- sps_log(LOG_ERR, "|% 20.9f|% 20.9f|*fatal error: %s", tss, tsl, s);
- else if ((debuglev) && (config.debugger_show_relative_time))
- sps_log(LOG_ERR, "% 20.9f|*fatal error: %s", tsl, s);
- else if ((debuglev) && (config.debugger_show_elapsed_time))
- sps_log(LOG_ERR, "% 20.9f|*fatal error: %s", tss, s);
- else
- sps_log(LOG_ERR, "fatal error: %s", s);
+ sps_log(LOG_ERR, "%s", b);
pthread_setcancelstate(oldState, NULL);
abort(); // exit() doesn't always work, by heaven.
}
-void warn(const char *format, ...) {
+void _warn(const char *filename, const int linenumber, const char *format, ...) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- char s[1024];
- s[0] = 0;
- 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 divisor = (uint64_t)1 << 32;
- double tss = 1.0 * time_since_start / divisor;
- double tsl = 1.0 * time_since_last_debug_message / divisor;
+ char b[1024];
+ b[0] = 0;
+ 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;
+ 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,
+ linenumber, " *warning: ");
+ } else {
+ s = b;
+ }
va_list args;
va_start(args, format);
- vsnprintf(s, sizeof(s), format, args);
+ vsnprintf(s, sizeof(b) - (s - b), format, args);
va_end(args);
- if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
- sps_log(LOG_WARNING, "|% 20.9f|% 20.9f|*warning: %s", tss, tsl, s);
- else if ((debuglev) && (config.debugger_show_relative_time))
- sps_log(LOG_WARNING, "% 20.9f|*warning: %s", tsl, s);
- else if ((debuglev) && (config.debugger_show_elapsed_time))
- sps_log(LOG_WARNING, "% 20.9f|*warning: %s", tss, s);
- else
- sps_log(LOG_WARNING, "%s", s);
+ sps_log(LOG_WARNING, "%s", b);
pthread_setcancelstate(oldState, NULL);
}
-void debug(int level, const char *format, ...) {
+void _debug(const char *filename, const int linenumber, int level, const char *format, ...) {
if (level > debuglev)
return;
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- char s[1024];
- s[0] = 0;
+ 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;
+ pthread_mutex_unlock(&debug_timing_lock);
uint64_t divisor = (uint64_t)1 << 32;
- double tss = 1.0 * time_since_start / divisor;
- double tsl = 1.0 * time_since_last_debug_message / divisor;
+ char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / divisor,
+ 1.0 * time_since_last_debug_message / divisor, filename,
+ linenumber, " ");
va_list args;
va_start(args, format);
- vsnprintf(s, sizeof(s), format, args);
+ vsnprintf(s, sizeof(b) - (s - b), format, args);
va_end(args);
- if ((config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
- sps_log(LOG_DEBUG, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
- else if (config.debugger_show_relative_time)
- sps_log(LOG_DEBUG, "% 20.9f|%s", tsl, s);
- else if (config.debugger_show_elapsed_time)
- sps_log(LOG_DEBUG, "% 20.9f|%s", tss, s);
- else
- sps_log(LOG_DEBUG, "%s", s);
+ sps_log(LOG_DEBUG, "%s", b);
pthread_setcancelstate(oldState, NULL);
}
-void inform(const char *format, ...) {
+void _inform(const char *filename, const int linenumber, const char *format, ...) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
- char s[1024];
- s[0] = 0;
- 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 divisor = (uint64_t)1 << 32;
- double tss = 1.0 * time_since_start / divisor;
- double tsl = 1.0 * time_since_last_debug_message / divisor;
+ char b[1024];
+ b[0] = 0;
+ 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;
+ 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,
+ linenumber, " ");
+ } else {
+ s = b;
+ }
va_list args;
va_start(args, format);
- vsnprintf(s, sizeof(s), format, args);
+ vsnprintf(s, sizeof(b) - (s - b), format, args);
va_end(args);
- if ((debuglev) && (config.debugger_show_elapsed_time) && (config.debugger_show_relative_time))
- sps_log(LOG_INFO, "|% 20.9f|% 20.9f|%s", tss, tsl, s);
- else if ((debuglev) && (config.debugger_show_relative_time))
- sps_log(LOG_INFO, "% 20.9f|%s", tsl, s);
- else if ((debuglev) && (config.debugger_show_elapsed_time))
- sps_log(LOG_INFO, "% 20.9f|%s", tss, s);
- else
- sps_log(LOG_INFO, "%s", s);
+ sps_log(LOG_INFO, "%s", b);
pthread_setcancelstate(oldState, NULL);
}
@@ -862,17 +907,17 @@ double flat_vol2attn(double vol, long max_db, long min_db) {
double vol2attn(double vol, long max_db, long min_db) {
-// We use a little coordinate geometry to build a transfer function from the volume passed in to
-// the device's dynamic range. (See the diagram in the documents folder.) The x axis is the
-// "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from
-// the bottom of the range to the top. We build the transfer function from one or more lines. We
-// characterise each line with two numbers: the first is where on x the line starts when y=0 (x
-// can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the
-// line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a
-// dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer
-// function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
-// Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
-// Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30
+ // We use a little coordinate geometry to build a transfer function from the volume passed in to
+ // the device's dynamic range. (See the diagram in the documents folder.) The x axis is the
+ // "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from
+ // the bottom of the range to the top. We build the transfer function from one or more lines. We
+ // characterise each line with two numbers: the first is where on x the line starts when y=0 (x
+ // can be from 0 to -30); the second is where on y the line stops when when x is -30. thus, if the
+ // line was characterised as {0,-30}, it would be an identity transfer. Assuming, for example, a
+ // dynamic range of lv=-60 to hv=0 Typically we'll use three lines -- a three order transfer
+ // function First: {0,30} giving a gentle slope -- the 30 comes from half the dynamic range
+ // Second: {-5,-30-(lv+30)/2} giving a faster slope from y=0 at x=-12 to y=-42.5 at x=-30
+ // Third: {-17,lv} giving a fast slope from y=0 at x=-19 to y=-60 at x=-30
#define order 3
@@ -1044,6 +1089,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
+pthread_mutex_t r64_mutex = PTHREAD_MUTEX_INITIALIZER;
+
// typedef uint64_t u8;
typedef struct ranctx {
uint64_t a;
@@ -1078,38 +1126,6 @@ uint64_t r64u() { return (ranval(&rx)); }
int64_t r64i() { return (ranval(&rx) >> 1); }
-/* generate an array of 64-bit random numbers */
-const int ranarraylength = 1009 * 203; // these will be 8-byte numbers.
-
-int ranarraynext;
-
-void ranarrayinit() {
- ranarray = (uint64_t *)malloc(ranarraylength * sizeof(uint64_t));
- if (ranarray) {
- int i;
- for (i = 0; i < ranarraylength; i++)
- ranarray[i] = r64u();
- ranarraynext = 0;
- } else {
- die("failed to allocate space for the ranarray.");
- }
-}
-
-uint64_t ranarrayval() {
- uint64_t v = ranarray[ranarraynext];
- ranarraynext++;
- ranarraynext = ranarraynext % ranarraylength;
- return v;
-}
-
-void r64arrayinit() { ranarrayinit(); }
-
-// uint64_t ranarray64u() { return (ranarrayval()); }
-uint64_t ranarray64u() { return (ranval(&rx)); }
-
-// int64_t ranarray64i() { return (ranarrayval() >> 1); }
-int64_t ranarray64i() { return (ranval(&rx) >> 1); }
-
uint32_t nctohl(const uint8_t *p) { // read 4 characters from *p and do ntohl on them
// this is to avoid possible aliasing violations
uint32_t holder;
@@ -1189,8 +1205,9 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
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 "
- "waiting time of %d microseconds. \"%s\".",
+ debug(debuglevel,
+ "timed out waiting for a mutex, having waiting %f seconds, with a maximum "
+ "waiting time of %d microseconds. \"%s\".",
(1.0 * et) / 1000000, dally_time, debugmessage);
else
debug(debuglevel, "error %d: \"%s\" waiting for a mutex: \"%s\".", r,
@@ -1282,6 +1299,7 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha
void malloc_cleanup(void *arg) {
// debug(1, "malloc cleanup called.");
free(arg);
+ arg = NULL;
}
void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); }
@@ -1420,11 +1438,11 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma
int64_t previous_random_number = random_number_in;
char *p = outp;
size_t sample_number;
+ r64_lock; // the random number generator is not thread safe, so we need to lock it while using it
for (sample_number = 0; sample_number < number_of_frames * 2; sample_number++) {
int64_t hyper_sample = 0;
-
- int64_t r = ranarray64i();
+ int64_t r = r64i();
int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask);
@@ -1517,5 +1535,42 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma
p += sample_length;
previous_random_number = r;
}
+ r64_unlock;
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.
+
+// 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
+// If they are the same, the flag is cleared
+
+int string_update_with_size(char **str, int *flag, char *s, size_t len) {
+ if (*str) {
+ if ((s) && (len)) {
+ if (strncmp(*str, s, len) != 0) {
+ free(*str);
+ *str = strndup(s, len);
+ *flag = 1;
+ } else {
+ *flag = 0;
+ }
+ } else {
+ // old string is non-NULL, new string is NULL or length 0
+ free(*str);
+ *str = NULL;
+ *flag = 1;
+ }
+ } else { // old string is NULL
+ if ((s) && (len)) {
+ *str = strndup(s, len);
+ *flag = 1;
+ } else {
+ // old string is NULL and new string is NULL or length 0
+ *flag = 0; // so no change
+ }
+ }
+ return *flag;
+}
diff --git a/common.h b/common.h
index ea58344..34cd10b 100644
--- a/common.h
+++ b/common.h
@@ -100,6 +100,10 @@ enum sps_format_t {
const char *sps_format_description_string(enum 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 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
+ pthread_mutex_t lock;
config_t *cfg;
int endianness;
double airplay_volume; // stored here for reloading when necessary
@@ -176,6 +180,7 @@ typedef struct {
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
+ int debugger_show_file_and_line; // in the debug message, display the filename and line number
int statistics_requested, use_negotiated_latencies;
enum playback_mode_type playback_mode;
char *cmd_start, *cmd_stop, *cmd_set_volume, *cmd_unfixable;
@@ -225,7 +230,8 @@ typedef struct {
#ifdef CONFIG_CONVOLUTION
int convolution;
- const char *convolution_ir_file;
+ int convolver_valid;
+ char *convolution_ir_file;
float convolution_gain;
int convolution_max_length;
#endif
@@ -247,6 +253,8 @@ typedef struct {
#ifdef CONFIG_METADATA_HUB
char *cover_art_cache_dir;
+ int retain_coverart;
+
int scan_interval_when_active; // number of seconds between DACP server scans when playing
// something (1)
int scan_interval_when_inactive; // number of seconds between DACP server scans playing nothing
@@ -266,6 +274,10 @@ typedef struct {
} shairport_cfg;
+// accessors to config for multi-thread access
+double get_config_airplay_volume();
+void set_config_airplay_volume(double v);
+
uint32_t nctohl(const uint8_t *p); // read 4 characters from *p and do ntohl on them
uint16_t nctohs(const uint8_t *p); // read 2 characters from *p and do ntohs on them
@@ -295,11 +307,6 @@ void r64init(uint64_t seed);
uint64_t r64u();
int64_t r64i();
-uint64_t *ranarray;
-void r64arrayinit();
-uint64_t ranarray64u();
-int64_t ranarray64i();
-
// if you are breaking in to a session, you need to avoid the ports of the current session
// if you are law-abiding, then you can reuse the ports.
// so, you can reset the free UDP ports minder when you're legit, and leave it otherwise
@@ -313,10 +320,15 @@ uint16_t nextFreeUDPPort();
volatile int debuglev;
-void die(const char *format, ...);
-void warn(const char *format, ...);
-void inform(const char *format, ...);
-void debug(int level, const char *format, ...);
+void _die(const char *filename, const int linenumber, const char *format, ...);
+void _warn(const char *filename, const int linenumber, const char *format, ...);
+void _inform(const char *filename, const int linenumber, const char *format, ...);
+void _debug(const char *filename, const int linenumber, int level, const char *format, ...);
+
+#define die(...) _die(__FILE__, __LINE__, __VA_ARGS__)
+#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__)
+#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__)
+#define inform(...) _inform(__FILE__, __LINE__, __VA_ARGS__)
uint8_t *base64_dec(char *input, int *outlen);
char *base64_enc(uint8_t *input, int length);
@@ -362,6 +374,13 @@ void shairport_shutdown();
extern sigset_t pselect_sigset;
+pthread_mutex_t the_conn_lock;
+
+#define conn_lock(arg) \
+ pthread_mutex_lock(&the_conn_lock); \
+ arg; \
+ pthread_mutex_unlock(&the_conn_lock);
+
// wait for the specified time in microseconds -- it checks every 20 milliseconds
int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
const char *debugmessage, int debuglevel);
@@ -383,6 +402,19 @@ void pthread_cleanup_debug_mutex_unlock(void *arg);
if (_debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d) == 0) \
pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock, (void *)mu)
+#define config_lock \
+ if (pthread_mutex_trylock(&config.lock) != 0) { \
+ debug(1, "config_lock: cannot acquire config.lock"); \
+ }
+
+#define config_unlock pthread_mutex_unlock(&config.lock)
+
+pthread_mutex_t r64_mutex;
+
+#define r64_lock pthread_mutex_lock(&r64_mutex)
+
+#define r64_unlock pthread_mutex_unlock(&r64_mutex)
+
char *get_version_string(); // mallocs a string space -- remember to free it afterwards
void sps_nanosleep(const time_t sec,
@@ -393,4 +425,6 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma
void malloc_cleanup(void *arg);
+int string_update_with_size(char **str, int *flag, char *s, size_t len);
+
#endif // _COMMON_H
diff --git a/configure.ac b/configure.ac
index 36f0b06..df0db32 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [3.3.2], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.5], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
@@ -298,7 +298,7 @@ AC_ARG_WITH(convolution, [ --with-convolution = choose audio DSP convolution su
REQUESTED_CONVOLUTION=1
AM_INIT_AUTOMAKE([subdir-objects])
AC_DEFINE([CONFIG_CONVOLUTION], 1, [Needed by the compiler.])
- AC_CHECK_LIB([sndfile], [sf_open], , AC_MSG_ERROR(Convolution support requires the sndfile library!))], )
+ AC_CHECK_LIB([sndfile], [sf_open], , AC_MSG_ERROR(Convolution support requires the sndfile library -- libsndfile1-dev suggested!))], )
AM_CONDITIONAL([USE_CONVOLUTION], [test "x$REQUESTED_CONVOLUTION" = "x1"])
# Look for dns_sd flag
diff --git a/dacp.c b/dacp.c
index 0b6db70..79447af 100644
--- a/dacp.c
+++ b/dacp.c
@@ -112,7 +112,10 @@ static void response_code(void *opaque, int code) {
}
static const struct http_funcs responseFuncs = {
- response_realloc, response_body, response_header, response_code,
+ response_realloc,
+ response_body,
+ response_header,
+ response_code,
};
// static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -146,202 +149,216 @@ void http_cleanup(void *arg) {
}
int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
+ int result;
+ // debug(1,"dacp_send_command: command is: \"%s\".",command);
+
+ if (dacp_server.port == 0) {
+ debug(1,"No DACP port specified yet");
+ result = 490; // no port specified
+ } else {
+
+ // will malloc space for the body or set it to NULL -- the caller should free it.
+
+ // Using some custom HTTP-like return codes
+ // 498 Bad Address information for the DACP server
+ // 497 Can't establish a socket to the DACP server
+ // 496 Can't connect to the DACP server
+ // 495 Error receiving response
+ // 494 This client is already busy
+ // 493 Client failed to send a message
+ // 492 Argument out of range
+ // 491 Client refused connection
+
+ struct addrinfo hints, *res;
+ int sockfd;
+
+ struct HttpResponse response;
+ response.body = NULL;
+ response.malloced_size = 0;
+ response.size = 0;
+ response.code = 0;
+
+ char portstring[10], server[1024], message[1024];
+ memset(&portstring, 0, sizeof(portstring));
+ if (dacp_server.connection_family == AF_INET6) {
+ snprintf(server, sizeof(server), "%s%%%u", dacp_server.ip_string, dacp_server.scope_id);
+ } else {
+ strcpy(server, dacp_server.ip_string);
+ }
+ snprintf(portstring, sizeof(portstring), "%u", dacp_server.port);
- // will malloc space for the body or set it to NULL -- the caller should free it.
-
- // Using some custom HTTP-like return codes
- // 498 Bad Address information for the DACP server
- // 497 Can't establish a socket to the DACP server
- // 496 Can't connect to the DACP server
- // 495 Error receiving response
- // 494 This client is already busy
- // 493 Client failed to send a message
- // 492 Argument out of range
-
- struct addrinfo hints, *res;
- int sockfd;
-
- struct HttpResponse response;
- response.body = NULL;
- response.malloced_size = 0;
- response.size = 0;
- response.code = 0;
-
- char portstring[10], server[1024], message[1024];
- memset(&portstring, 0, sizeof(portstring));
- if (dacp_server.connection_family == AF_INET6) {
- snprintf(server, sizeof(server), "%s%%%u", dacp_server.ip_string, dacp_server.scope_id);
- } else {
- strcpy(server, dacp_server.ip_string);
- }
- snprintf(portstring, sizeof(portstring), "%u", dacp_server.port);
-
- // first, load up address structs with getaddrinfo():
+ // first, load up address structs with getaddrinfo():
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
- // debug(1, "DACP port string is \"%s:%s\".", server, portstring);
+ // debug(1, "DACP port string is \"%s:%s\".", server, portstring);
- int ires = getaddrinfo(server, portstring, &hints, &res);
- if (ires) {
- // debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
- response.code = 498; // Bad Address information for the DACP server
- } else {
- uint64_t start_time = get_absolute_time_in_fp();
- pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
- // only do this one at a time -- not sure it is necessary, but better safe than sorry
+ int ires = getaddrinfo(server, portstring, &hints, &res);
+ if (ires) {
+ // debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
+ response.code = 498; // Bad Address information for the DACP server
+ } else {
+ uint64_t start_time = get_absolute_time_in_fp();
+ pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
+ // only do this one at a time -- not sure it is necessary, but better safe than sorry
- int mutex_reply = sps_pthread_mutex_timedlock(&dacp_conversation_lock, 2000000, command, 1);
- // int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
- if (mutex_reply == 0) {
- pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock);
+ int mutex_reply = sps_pthread_mutex_timedlock(&dacp_conversation_lock, 2000000, command, 1);
+ // int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
+ if (mutex_reply == 0) {
+ pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock);
- // make a socket:
- sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ // make a socket:
+ sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
- if (sockfd == -1) {
- // debug(1, "DACP socket could not be created -- error %d: \"%s\".",errno,strerror(errno));
- response.code = 497; // Can't establish a socket to the DACP server
- } else {
- pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
- // debug(2, "dacp_send_command: open socket %d.",sockfd);
-
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 80000;
- if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
- if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
- 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) {
- debug(3, "dacp_send_command: connect failed with errno %d.", errno);
- response.code = 496; // Can't connect to the DACP server
+ if (sockfd == -1) {
+ // debug(1, "DACP socket could not be created -- error %d: \"%s\".",errno,strerror(errno));
+ response.code = 497; // Can't establish a socket to the DACP server
} else {
- // debug(1,"DACP connect succeeded.");
-
- snprintf(message, sizeof(message),
- "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
- command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id);
-
- // Send command
- debug(3, "dacp_send_command: \"%s\".", command);
- ssize_t wresp = send(sockfd, message, strlen(message), 0);
- if (wresp == -1) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring);
- struct linger so_linger;
- so_linger.l_onoff = 1; // "true"
- so_linger.l_linger = 0;
- int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
- if (err)
- debug(1, "Could not set the dacp socket to abort due to a write error on closing.");
- }
- if (wresp != (ssize_t)strlen(message)) {
- // debug(1, "dacp_send_command: send failed.");
- response.code = 493; // Client failed to send a message
-
+ pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
+ // debug(2, "dacp_send_command: open socket %d.",sockfd);
+
+ 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);
+ 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) {
+ // debug(1, "dacp_send_command: connect failed with errno %d.", errno);
+ if (errno == ECONNREFUSED)
+ response.code = 491; // DACP server doesn't want to talk anymore...
+ else
+ response.code = 496; // Can't connect to the DACP server
} else {
+ // debug(1,"DACP connect succeeded.");
+
+ snprintf(message, sizeof(message),
+ "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
+ command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id);
+
+ // Send command
+ debug(3, "dacp_send_command: \"%s\".", command);
+ ssize_t wresp = send(sockfd, message, strlen(message), 0);
+ if (wresp == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring);
+ struct linger so_linger;
+ so_linger.l_onoff = 1; // "true"
+ so_linger.l_linger = 0;
+ int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
+ if (err)
+ debug(1, "Could not set the dacp socket to abort due to a write error on closing.");
+ }
+ if (wresp != (ssize_t)strlen(message)) {
+ // debug(1, "dacp_send_command: send failed.");
+ response.code = 493; // Client failed to send a message
- response.body = malloc(2048); // it can resize this if necessary
- response.malloced_size = 2048;
- pthread_cleanup_push(malloc_cleanup, response.body);
-
- struct http_roundtripper rt;
- http_init(&rt, responseFuncs, &response);
- pthread_cleanup_push(http_cleanup, &rt);
-
- int needmore = 1;
- int looperror = 0;
- char buffer[8192];
- memset(buffer, 0, sizeof(buffer));
- while (needmore && !looperror) {
- const char *data = buffer;
- if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
- debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
- ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0);
- // debug(3, "Received %d bytes: \"%s\".", ndata, buffer);
- if (ndata <= 0) {
- if (ndata == -1) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,
- (char *)errorstring);
- struct linger so_linger;
- so_linger.l_onoff = 1; // "true"
- so_linger.l_linger = 0;
- int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
- if (err)
- debug(1,
- "Could not set the dacp socket to abort due to a read error on closing.");
+ } else {
+
+ response.body = malloc(2048); // it can resize this if necessary
+ response.malloced_size = 2048;
+ pthread_cleanup_push(malloc_cleanup, response.body);
+
+ struct http_roundtripper rt;
+ http_init(&rt, responseFuncs, &response);
+ pthread_cleanup_push(http_cleanup, &rt);
+
+ int needmore = 1;
+ int looperror = 0;
+ char buffer[8192];
+ memset(buffer, 0, sizeof(buffer));
+ while (needmore && !looperror) {
+ const char *data = buffer;
+ if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
+ debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
+ ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0);
+ // debug(3, "Received %d bytes: \"%s\".", ndata, buffer);
+ if (ndata <= 0) {
+ if (ndata == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,
+ (char *)errorstring);
+ struct linger so_linger;
+ so_linger.l_onoff = 1; // "true"
+ so_linger.l_linger = 0;
+ int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
+ if (err)
+ debug(1,
+ "Could not set the dacp socket to abort due to a read error on closing.");
+ }
+
+ free(response.body);
+ response.body = NULL;
+ response.malloced_size = 0;
+ response.size = 0;
+ response.code = 495; // Error receiving response
+ looperror = 1;
+ }
+
+ while (needmore && ndata && !looperror) {
+ int read;
+ needmore = http_data(&rt, data, ndata, &read);
+ ndata -= read;
+ data += read;
}
+ }
+ if (http_iserror(&rt)) {
+ debug(3, "dacp_send_command: error parsing data.");
free(response.body);
response.body = NULL;
response.malloced_size = 0;
response.size = 0;
- response.code = 495; // Error receiving response
- looperror = 1;
- }
-
- while (needmore && ndata && !looperror) {
- int read;
- needmore = http_data(&rt, data, ndata, &read);
- ndata -= read;
- data += read;
}
+ // debug(1,"Size of response body is %d",response.size);
+ pthread_cleanup_pop(1); // this should call http_cleanup
+ // http_free(&rt);
+ pthread_cleanup_pop(
+ 0); // this should *not* free the malloced buffer -- just pop the malloc cleanup
}
-
- if (http_iserror(&rt)) {
- debug(3, "dacp_send_command: error parsing data.");
- free(response.body);
- response.body = NULL;
- response.malloced_size = 0;
- response.size = 0;
- }
- // debug(1,"Size of response body is %d",response.size);
- pthread_cleanup_pop(1); // this should call http_cleanup
- // http_free(&rt);
- pthread_cleanup_pop(
- 0); // this should *not* free the malloced buffer -- just pop the malloc cleanup
}
+ pthread_cleanup_pop(1); // this should close the socket
+ // close(sockfd);
+ // debug(1,"DACP socket closed.");
}
- pthread_cleanup_pop(1); // this should close the socket
- // close(sockfd);
- // debug(1,"DACP socket closed.");
+ pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
+ // pthread_mutex_unlock(&dacp_conversation_lock);
+ // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size);
+ // debug(1,"dacp_conversation_lock released.");
+ } else {
+ debug(3,
+ "dacp_send_command: could not acquire a lock on the dacp transmit/receive section "
+ "when attempting to "
+ "send the command \"%s\". Possible timeout?",
+ command);
+ response.code = 494; // This client is already busy
}
- pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
- // pthread_mutex_unlock(&dacp_conversation_lock);
- // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size);
- // debug(1,"dacp_conversation_lock released.");
- } else {
- debug(3, "dacp_send_command: could not acquire a lock on the dacp transmit/receive section "
- "when attempting to "
- "send the command \"%s\". Possible timeout?",
- command);
- response.code = 494; // This client is already busy
+ pthread_cleanup_pop(1); // this should free the addrinfo
+ // freeaddrinfo(res);
+ uint64_t et = get_absolute_time_in_fp() - start_time;
+ et = (et * 1000000) >> 32; // microseconds
+ debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
+ (1.0 * et) / 1000000, response.code, command);
}
- pthread_cleanup_pop(1); // this should free the addrinfo
- // freeaddrinfo(res);
- uint64_t et = get_absolute_time_in_fp() - start_time;
- et = (et * 1000000) >> 32; // microseconds
- debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
- (1.0 * et) / 1000000, response.code, command);
+ *body = response.body;
+ *bodysize = response.size;
+ result = response.code;
}
- *body = response.body;
- *bodysize = response.size;
- return response.code;
+ return result;
}
int send_simple_dacp_command(const char *command) {
int reply = 0;
char *server_reply = NULL;
- debug(3, "Sending command \"%s\".", command);
+ debug(2, "send_simple_dacp_command: sending command \"%s\".", command);
ssize_t reply_size = 0;
reply = dacp_send_command(command, &server_reply, &reply_size);
if (server_reply) {
@@ -419,8 +436,9 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
- debug(3, "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port "
- "number %d.",
+ debug(2,
+ "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port "
+ "number %d.",
dacp_id, dacp_server.dacp_id, port);
if (strcmp(dacp_id, dacp_server.dacp_id) == 0) {
dacp_server.port = port;
@@ -468,8 +486,9 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
(metadata_store.advanced_dacp_server_active != 0);
metadata_store.dacp_server_active = 0;
metadata_store.advanced_dacp_server_active = 0;
- debug(2, "setting dacp_server_active and advanced_dacp_server_active to 0 with an update "
- "flag value of %d",
+ debug(2,
+ "setting 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) {
@@ -477,17 +496,46 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
pthread_cond_wait(&dacp_server_information_cv, &dacp_server_information_lock);
// debug(1,"dacp_monitor_thread_code wake up.");
}
+ // so dacp_server.scan_enable will be true at this point
bad_result_count = 0;
idle_scan_count = 0;
+
}
- scan_index++;
+
result = dacp_get_volume(&the_volume); // just want the http code
+ pthread_cleanup_pop(1);
- if ((result == 496) || (result == 403) || (result == 501)) {
- bad_result_count++;
- // debug(1,"Bad Scan : %d.",result);
- } else
+ scan_index++;
+ // debug(1,"DACP Scan Result: %d.", result);
+
+ 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++;
+ }
+
+ // 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;
+ }
if (metadata_store.player_thread_active == 0)
idle_scan_count++;
@@ -497,45 +545,37 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result, bad_result_count,
idle_scan_count);
- if ((bad_result_count == config.scan_max_bad_response_count) ||
- (idle_scan_count == config.scan_max_inactive_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;
}
- pthread_cleanup_pop(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;
+ }
+ }
+ 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);
- if (dacp_server.scan_enable ==
- 1) { // if it hasn't been turned off, continue looking for information.
- int transient_problem =
- (result == 494) ||
- (result == 495); // this just means that it couldn't send the query because something
- // else
- // was sending a command or something
- if ((!transient_problem) && (bad_result_count == 0) && (idle_scan_count == 0) &&
- (dacp_server.scan_enable == 1)) {
-
- // Any other result means that the DACP server is active, but not providing advanced
- // features
-
- metadata_hub_modify_prolog();
- int inactive = metadata_store.dacp_server_active == 0;
- if (inactive) {
- metadata_store.dacp_server_active = 1;
- debug(2, "Setting dacp_server_active to active because of a response of %d.", result);
- }
- int same = metadata_store.advanced_dacp_server_active == (result == 200);
- if (!same) {
- metadata_store.advanced_dacp_server_active = (result == 200);
- debug(2, "Setting dacp_advanced_server_active to %d because of a response of %d.",
- (result == 200), result);
- }
- metadata_hub_modify_epilog(inactive + (!same));
- }
-
if (result == 200) {
metadata_hub_modify_prolog();
int diff = metadata_store.speaker_volume != the_volume;
@@ -549,7 +589,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
char command[1024] = "";
snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
revision_number);
- // debug(1,"Command: \"%s\"",command);
+ // 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);
if (result == 200) {
@@ -574,6 +614,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
// char u;
// char *st;
int32_t r;
+ uint32_t ui;
// uint64_t v;
// int i;
@@ -658,64 +699,42 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
break;
}
break;
- /*
case 'cann': // track name
- t = sp - item_size;
- if ((metadata_store.track_name == NULL) ||
- (strncmp(metadata_store.track_name, t, item_size) != 0)) {
- if (metadata_store.track_name)
- free(metadata_store.track_name);
- metadata_store.track_name = strndup(t, item_size);
- debug(1, "Track name changed to: \"%s\"", metadata_store.track_name);
- metadata_store.track_name_changed = 1;
- metadata_store.changed = 1;
+ 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
- t = sp - item_size;
- if ((metadata_store.artist_name == NULL) ||
- (strncmp(metadata_store.artist_name, t, item_size) != 0)) {
- if (metadata_store.artist_name)
- free(metadata_store.artist_name);
- metadata_store.artist_name = strndup(t, item_size);
- debug(1, "Artist name changed to: \"%s\"", metadata_store.artist_name);
- metadata_store.artist_name_changed = 1;
- metadata_store.changed = 1;
+ 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
- t = sp - item_size;
- if ((metadata_store.album_name == NULL) ||
- (strncmp(metadata_store.album_name, t, item_size) != 0)) {
- if (metadata_store.album_name)
- free(metadata_store.album_name);
- metadata_store.album_name = strndup(t, item_size);
- debug(1, "Album name changed to: \"%s\"", metadata_store.album_name);
- metadata_store.album_name_changed = 1;
- metadata_store.changed = 1;
+ 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
- t = sp - item_size;
- if ((metadata_store.genre == NULL) ||
- (strncmp(metadata_store.genre, t, item_size) != 0)) {
- if (metadata_store.genre)
- free(metadata_store.genre);
- metadata_store.genre = strndup(t, item_size);
- debug(1, "Genre changed to: \"%s\"", metadata_store.genre);
- metadata_store.genre_changed = 1;
- metadata_store.changed = 1;
+ 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)
- t = sp - item_size;
- if (memcmp(metadata_store.item_composite_id, t,
+ 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, t,
+ memcpy(metadata_store.item_composite_id, sp - item_size,
sizeof(metadata_store.item_composite_id));
-
char st[33];
char *pt = st;
int it;
@@ -724,18 +743,20 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
pt += 2;
}
*pt = 0;
- // debug(1, "Item composite ID set to 0x%s.", st);
- metadata_store.item_id_changed = 1;
- metadata_store.changed = 1;
+ debug(2, "Item composite ID changed to 0x%s.", st);
+ metadata_store.item_composite_id_changed = 1;
}
break;
- */
case 'astm':
t = sp - item_size;
- r = ntohl(*(uint32_t *)(t));
- if (metadata_store.track_metadata)
- metadata_store.track_metadata->songtime_in_milliseconds =
- ntohl(*(uint32_t *)(t));
+ 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;
/*
@@ -788,7 +809,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
}
// finished possibly writing to the metadata hub
- metadata_hub_modify_epilog(1);
+ metadata_hub_modify_epilog(1); // should really see if this can be made responsive to changes
} else {
debug(1, "Status Update not found.\n");
}
@@ -828,7 +849,6 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
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);
@@ -914,6 +934,7 @@ int dacp_get_client_volume(int32_t *result) {
char *server_reply = NULL;
int32_t overall_volume = -1;
ssize_t reply_size;
+ // debug(1,"dacp_get_client_volume: dacp_send_command");
int response =
dacp_send_command("getproperty?properties=dmcp.volume", &server_reply, &reply_size);
if (response == 200) { // if we get an okay
@@ -960,7 +981,7 @@ int dacp_set_include_speaker_volume(int64_t machine_number, int32_t vo) {
snprintf(message, sizeof(message),
"setproperty?include-speaker-id=%" PRId64 "&dmcp.volume=%" PRId32 "", machine_number,
vo);
- debug(2, "sending \"%s\"", message);
+ debug(1, "sending \"%s\"", message);
return send_simple_dacp_command(message);
// should return 204
}
@@ -970,7 +991,7 @@ int dacp_set_speaker_volume(int64_t machine_number, int32_t vo) {
memset(message, 0, sizeof(message));
snprintf(message, sizeof(message), "setproperty?speaker-id=%" PRId64 "&dmcp.volume=%" PRId32 "",
machine_number, vo);
- debug(2, "sending \"%s\"", message);
+ debug(1, "sending \"%s\"", message);
return send_simple_dacp_command(message);
// should return 204
}
@@ -983,6 +1004,7 @@ int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
int speaker_count = -1; // will be fixed if there is no problem
ssize_t le;
+ // debug(1,"dacp_speaker_list: dacp_send_command");
int response = dacp_send_command("getspeakers", &server_reply, &le);
if (response == 200) {
char *sp = server_reply;
diff --git a/dbus-service.c b/dbus-service.c
index e18a351..0c85a98 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -15,6 +15,10 @@
#include "dbus-service.h"
+#ifdef CONFIG_CONVOLUTION
+#include <FFTConvolver/convolver.h>
+#endif
+
int service_is_running = 0;
ShairportSyncDiagnostics *shairportSyncDiagnosticsSkeleton = NULL;
@@ -25,6 +29,8 @@ guint ownerID = 0;
void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
char response[100];
+ gboolean current_status, new_status;
+
const char *th;
shairport_sync_advanced_remote_control_set_volume(shairportSyncAdvancedRemoteControlSkeleton,
argc->speaker_volume);
@@ -129,94 +135,99 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
shairportSyncAdvancedRemoteControlSkeleton, response);
}
+
switch (argc->shuffle_status) {
case SS_NOT_AVAILABLE:
- shairport_sync_advanced_remote_control_set_shuffle(shairportSyncAdvancedRemoteControlSkeleton,
- FALSE);
+ new_status = FALSE;
break;
case SS_OFF:
- shairport_sync_advanced_remote_control_set_shuffle(shairportSyncAdvancedRemoteControlSkeleton,
- FALSE);
+ new_status = FALSE;
break;
case SS_ON:
- shairport_sync_advanced_remote_control_set_shuffle(shairportSyncAdvancedRemoteControlSkeleton,
- TRUE);
+ new_status = TRUE;
break;
default:
- debug(1, "This should never happen.");
+ new_status = FALSE;
+ debug(1, "Unknown shuffle status -- this should never happen.");
+ }
+
+ current_status = shairport_sync_advanced_remote_control_get_shuffle(
+ shairportSyncAdvancedRemoteControlSkeleton);
+
+ // 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);
}
- GVariantBuilder *dict_builder, *aa;
-
- /* Build the metadata array */
- // debug(1,"Build metadata");
- dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+ // Build the metadata array
+ debug(2, "Build metadata");
+ GVariantBuilder *dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
- // Make up the artwork URI if we have one
+ // Add in the artwork URI if it exists.
if (argc->cover_art_pathname) {
- char artURIstring[1024];
- snprintf(artURIstring, sizeof(artURIstring), "file://%s", argc->cover_art_pathname);
- // debug(1,"artURI String: \"%s\".",artURIstring);
- GVariant *artUrl = g_variant_new("s", artURIstring);
+ GVariant *artUrl = g_variant_new("s", argc->cover_art_pathname);
g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl);
}
- // Add the TrackID if we have one
- if ((argc->track_metadata) && (argc->track_metadata->item_id)) {
+ // Add in the Track ID based on the 'mper' metadata if it is non-zero
+ if (argc->item_id != 0) {
char trackidstring[128];
- // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id);
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
- argc->track_metadata->item_id);
+ argc->item_id);
GVariant *trackid = g_variant_new("o", trackidstring);
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
}
- // Add the track name if there is one
- if ((argc->track_metadata) && (argc->track_metadata->track_name)) {
- // debug(1, "Track name set to \"%s\".", argc->track_name);
- GVariant *trackname = g_variant_new("s", argc->track_metadata->track_name);
- g_variant_builder_add(dict_builder, "{sv}", "xesam:title", trackname);
+ // Add the track name if it exists
+ if (argc->track_name) {
+ GVariant *track_name = g_variant_new("s", argc->track_name);
+ g_variant_builder_add(dict_builder, "{sv}", "xesam:title", track_name);
}
- // Add the album name if there is one
- if ((argc->track_metadata) && (argc->track_metadata->album_name)) {
- // debug(1, "Album name set to \"%s\".", argc->album_name);
- GVariant *albumname = g_variant_new("s", argc->track_metadata->album_name);
- g_variant_builder_add(dict_builder, "{sv}", "xesam:album", albumname);
+ // Add the album name if it exists
+ if (argc->album_name) {
+ GVariant *album_name = g_variant_new("s", argc->album_name);
+ g_variant_builder_add(dict_builder, "{sv}", "xesam:album", album_name);
}
- // Add the artists if there are any (actually there will be at most one, but put it in an array)
- if ((argc->track_metadata) && (argc->track_metadata->artist_name)) {
- /* Build the artists array */
- // debug(1,"Build artist array");
- aa = g_variant_builder_new(G_VARIANT_TYPE("as"));
- g_variant_builder_add(aa, "s", argc->track_metadata->artist_name);
- GVariant *artists = g_variant_builder_end(aa);
- g_variant_builder_unref(aa);
+ // Add the artist name if it exists
+ if (argc->artist_name) {
+ GVariantBuilder *artist_as = g_variant_builder_new(G_VARIANT_TYPE("as"));
+ g_variant_builder_add(artist_as, "s", argc->artist_name);
+ GVariant *artists = g_variant_builder_end(artist_as);
+ g_variant_builder_unref(artist_as);
g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists);
}
- // Add the genres if there are any (actually there will be at most one, but put it in an array)
- if ((argc->track_metadata) && (argc->track_metadata->genre)) {
- // debug(1,"Build genre");
- aa = g_variant_builder_new(G_VARIANT_TYPE("as"));
- g_variant_builder_add(aa, "s", argc->track_metadata->genre);
- GVariant *genres = g_variant_builder_end(aa);
- g_variant_builder_unref(aa);
- g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genres);
+ // Add the genre if it exists
+ if (argc->genre) {
+ GVariantBuilder *genre_as = g_variant_builder_new(G_VARIANT_TYPE("as"));
+ g_variant_builder_add(genre_as, "s", argc->genre);
+ GVariant *genre = g_variant_builder_end(genre_as);
+ g_variant_builder_unref(genre_as);
+ g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genre);
}
+ if (argc->songtime_in_milliseconds) {
+ uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds;
+ track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision
+ // Make up the track name and album name
+ // debug(1, "Set tracklength to %lu.", track_length_in_microseconds);
+ GVariant *tracklength = g_variant_new("x", track_length_in_microseconds);
+ g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength);
+ }
+
GVariant *dict = g_variant_builder_end(dict_builder);
g_variant_builder_unref(dict_builder);
-
- // debug(1,"Set metadata");
shairport_sync_remote_control_set_metadata(shairportSyncRemoteControlSkeleton, dict);
}
static gboolean on_handle_set_volume(ShairportSyncAdvancedRemoteControl *skeleton,
GDBusMethodInvocation *invocation, const gint volume,
__attribute__((unused)) gpointer user_data) {
- debug(1, "Set volume to %d.", volume);
+ debug(2, "Set volume to %d.", volume);
dacp_set_volume(volume);
shairport_sync_advanced_remote_control_complete_set_volume(skeleton, invocation);
return TRUE;
@@ -352,6 +363,19 @@ gboolean notify_delta_time_callback(ShairportSyncDiagnostics *skeleton,
return TRUE;
}
+gboolean notify_file_and_line_callback(ShairportSyncDiagnostics *skeleton,
+ __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;
+ debug(1, ">> start including file and line in logs");
+ } else {
+ config.debugger_show_file_and_line = 0;
+ debug(1, ">> stop including file and line in logs");
+ }
+ return TRUE;
+}
+
gboolean notify_statistics_callback(ShairportSyncDiagnostics *skeleton,
__attribute__((unused)) gpointer user_data) {
// debug(1, "\"notify_statistics_callback\" called.");
@@ -393,14 +417,78 @@ gboolean notify_disable_standby_callback(ShairportSync *skeleton,
return TRUE;
}
-gboolean notify_loudness_filter_active_callback(ShairportSync *skeleton,
+#ifdef CONFIG_CONVOLUTION
+gboolean notify_convolution_callback(ShairportSync *skeleton,
+ __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);
+ } else {
+ debug(1, ">> deactivating convolution");
+ config.convolution = 0;
+ }
+ return TRUE;
+}
+#else
+gboolean notify_convolution_callback(__attribute__((unused)) ShairportSync *skeleton,
+ __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_gain_callback(ShairportSync *skeleton,
+ __attribute__((unused)) gpointer user_data) {
+
+ gdouble th = shairport_sync_get_convolution_gain(skeleton);
+ if ((th <= 0.0) && (th >= -100.0)) {
+ debug(1, ">> setting convolution gain to %f.", th);
+ config.convolution_gain = th;
+ } else {
+ debug(1, ">> invalid convolution gain: %f. Ignored.", th);
+ shairport_sync_set_convolution_gain(skeleton, config.convolution_gain);
+ }
+ return TRUE;
+}
+#else
+gboolean notify_convolution_gain_callback(__attribute__((unused)) ShairportSync *skeleton,
+ __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) {
+ 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);
+ return TRUE;
+}
+#else
+gboolean notify_convolution_impulse_response_file_callback(__attribute__((unused)) ShairportSync *skeleton,
+ __attribute__((unused)) gpointer user_data) {
+ 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) {
- // debug(1, "\"notify_loudness_filter_active_callback\" called.");
- if (shairport_sync_get_loudness_filter_active(skeleton)) {
- debug(1, ">> activating loudness filter");
+ // debug(1, "\"notify_loudness_callback\" called.");
+ if (shairport_sync_get_loudness(skeleton)) {
+ debug(1, ">> activating loudness");
config.loudness = 1;
} else {
- debug(1, ">> deactivating loudness filter");
+ debug(1, ">> deactivating loudness");
config.loudness = 0;
}
return TRUE;
@@ -410,10 +498,10 @@ gboolean notify_loudness_threshold_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
gdouble th = shairport_sync_get_loudness_threshold(skeleton);
if ((th <= 0.0) && (th >= -100.0)) {
- debug(1, ">> setting loudness threshhold to %f.", th);
+ debug(1, ">> setting loudness threshold to %f.", th);
config.loudness_reference_volume_db = th;
} else {
- debug(1, ">> invalid loudness threshhold: %f. Ignored.", th);
+ debug(1, ">> invalid loudness threshold: %f. Ignored.", th);
shairport_sync_set_loudness_threshold(skeleton, config.loudness_reference_volume_db);
}
return TRUE;
@@ -667,8 +755,14 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
G_CALLBACK(notify_volume_control_profile_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::disable-standby",
G_CALLBACK(notify_disable_standby_callback), NULL);
- g_signal_connect(shairportSyncSkeleton, "notify::loudness-filter-active",
- G_CALLBACK(notify_loudness_filter_active_callback), NULL);
+ g_signal_connect(shairportSyncSkeleton, "notify::convolution",
+ G_CALLBACK(notify_convolution_callback), NULL);
+ g_signal_connect(shairportSyncSkeleton, "notify::convolution-gain",
+ 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-threshold",
G_CALLBACK(notify_loudness_threshold_callback), NULL);
g_signal_connect(shairportSyncSkeleton, "notify::drift-tolerance",
@@ -691,6 +785,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
g_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::delta-time",
G_CALLBACK(notify_delta_time_callback), NULL);
+ g_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::file-and-line",
+ G_CALLBACK(notify_file_and_line_callback), NULL);
+
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-fast-forward",
G_CALLBACK(on_handle_fast_forward), NULL);
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-rewind",
@@ -801,11 +898,23 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
}
if (config.loudness == 0) {
- shairport_sync_set_loudness_filter_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
+ shairport_sync_set_loudness(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
} else {
- shairport_sync_set_loudness_filter_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
+ shairport_sync_set_loudness(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
}
+#ifdef CONFIG_CONVOLUTION
+ if (config.convolution == 0) {
+ shairport_sync_set_convolution(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
+ } else {
+ 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);
+// else
+// shairport_sync_set_convolution_impulse_response_file(SHAIRPORT_SYNC(shairportSyncSkeleton), NULL);
+#endif
+
shairport_sync_set_version(SHAIRPORT_SYNC(shairportSyncSkeleton), PACKAGE_VERSION);
char *vs = get_version_string();
shairport_sync_set_version_string(SHAIRPORT_SYNC(shairportSyncSkeleton), vs);
@@ -847,6 +956,16 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
// debug(1, ">> delta time is not included in log entries");
}
+ if (config.debugger_show_file_and_line == 0) {
+ shairport_sync_diagnostics_set_file_and_line(
+ SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), FALSE);
+ // debug(1, ">> file and line is included in log entries");
+ } else {
+ shairport_sync_diagnostics_set_file_and_line(
+ SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), TRUE);
+ // debug(1, ">> file and line is not included in log entries");
+ }
+
shairport_sync_remote_control_set_player_state(shairportSyncRemoteControlSkeleton,
"Not Available");
shairport_sync_advanced_remote_control_set_playback_status(
diff --git a/documents/sample dbus commands b/documents/sample dbus commands
index 2380415..805480c 100644
--- a/documents/sample dbus commands
+++ b/documents/sample dbus commands
@@ -10,6 +10,11 @@ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/Shair
# Set Statistics-Requested Status to true
dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync.Diagnostics string:Statistics variant:boolean:true
+# Are File Name and Line Number included in Log Entries?
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:FileAndLine
+# Include File Name and Line Number in Log Entries
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync.Diagnostics string:FileAndLine variant:boolean:true
+
# Is Elapsed Time included in Log Entries?
dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.Diagnostics string:ElapsedTime
# Include Elapsed Time in Log Entries
@@ -19,10 +24,49 @@ dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/Shair
dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.RemoteControl.Play
#Remote Control commands include: Play, Pause, PlayPause, Resume, Stop, Next, Previous, VolumeUp, VolumeDown, ToggleMute, FastForward, Rewind, ShuffleSongs
-# Set Volume using Advanced Remote Control
-dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50
-
# Get Drift Tolerance
dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:DriftTolerance
# Set Drift Tolerance to 1 millisecond
dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:DriftTolerance variant:double:0.001
+
+# Is Loudness Enabled:
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:LoudnessThreshold
+
+# Enable Loudness Filter
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:Loudness variant:boolean:true
+
+# Get Loudness Threshold
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:LoudnessThreshold
+
+# Set Loudness Threshold
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:LoudnessThreshold variant:double:-15.0
+
+# Is Convolution enabled:
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:Convolution
+
+# Enable Convolution
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:Convolution variant:boolean:true
+
+# Get Convolution Gain:
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:ConvolutionGain
+
+# Set Convolution Gain -- the gain applied before convolution is applied -- to -10.0 dB
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:ConvolutionGain variant:double:-10
+
+# Get Convolution Impulse Response File:
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync string:ConvolutionImpulseResponseFile
+
+# Set Convolution Impulse Response File:
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Set string:org.gnome.ShairportSync string:ConvolutionImpulseResponseFile variant:string:"/etc/shairport-sync/boom.wav"
+
+# Some commands and properties are accessible only through the AdvancedRemoteControl interface.
+# Unfortunately, only iTunes provides the functionality to allow the AdvancedRemoteControl interface to work.
+# (The macOS "Music" app replacing iTunes appears to have no remote interface whatever (at least as far as is known).)
+
+# You can check to see if AdvancedRemoteControl is available using the command:
+
+dbus-send --print-reply --system --dest=org.gnome.ShairportSync /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get string:org.gnome.ShairportSync.AdvancedRemoteControl string:Available
+
+# Set Volume using Advanced Remote Control -- only works if the org.gnome.ShairportSync.AdvancedRemoteControl is available.
+dbus-send --system --print-reply --type=method_call --dest=org.gnome.ShairportSync '/org/gnome/ShairportSync' org.gnome.ShairportSync.AdvancedRemoteControl.SetVolume int32:50
+
diff --git a/loudness.c b/loudness.c
index b4dbc3c..79862ec 100644
--- a/loudness.c
+++ b/loudness.c
@@ -44,7 +44,7 @@ void loudness_set_volume(float volume) {
if (gain < 0)
gain = 0;
- inform("Volume: %.1f dB - Loudness gain @10Hz: %.1f dB", volume, gain);
+ debug(2, "Volume: %.1f dB - Loudness gain @10Hz: %.1f dB", volume, gain);
_loudness_set_volume(&loudness_l, volume);
_loudness_set_volume(&loudness_r, volume);
}
diff --git a/man/shairport-sync.7 b/man/shairport-sync.7
index 0116333..9048b55 100644
--- a/man/shairport-sync.7
+++ b/man/shairport-sync.7
@@ -29,7 +29,7 @@ You should use the configuration file for setting up Shairport Sync. This file i
(Note: Shairport Sync may have been compiled to use a different configuration directory. You can determine which by performing the command \fI$ shairport-sync -V\f1. One of the items in the output string is the value of the \fBsysconfdir\f1, i.e. the System Configuration Directory.)
-Within the configuraton 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:
+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:
\fBgeneral = {\f1
@@ -90,7 +90,7 @@ Use this to specify the \fIportnumber\f1 shairport-sync uses to listen for servi
When shairport-sync starts to play audio, it establises three UDP connections to the audio source. Use this setting to specify the starting \fIportnumber\f1 for these three ports. It will pick the first three unused ports starting from \fIportnumber\f1. The default is port 6001.
.TP
\fBudp_port_range=\f1\fIrange\f1\fB;\f1
-Use this in conjunction with the prevous setting to specify the \fIrange\f1 of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.
+Use this in conjunction with the previous setting to specify the \fIrange\f1 of ports that can be checked for availability. Only three ports are needed. The default is 100, thus 100 ports will be checked from port 6001 upwards until three are found.
.TP
\fBdrift_tolerance_in_seconds=\f1\fIseconds\f1\fB;\f1
Allow playback to drift up to \fIseconds\f1 out of exact synchronization before attempting to correct it. The default is 0.002 seconds, i.e. 2 milliseconds. The smaller the tolerance, the more likely it is that overcorrection will occur. Overcorrection is when more corrections (insertions and deletions) are made than are strictly necessary to keep the stream in sync. Use the \fBstatistics\f1 setting to monitor correction levels. Corrections should not greatly exceed net corrections. This setting replaces the deprecated \fBdrift\f1 setting.
@@ -140,7 +140,7 @@ The \fImode\f1 can be "stereo", "mono", "reverse stereo", "both left" or "both r
This can be "hammerton" or "apple". This advanced setting allows you to choose the original Shairport decoder by David Hammerton or the Apple Lossless Audio Codec (ALAC) decoder written by Apple. Shairport Sync must have been compiled with the configuration setting "--with-apple-alac" and the Apple ALAC decoder library must be present for this to work.
.TP
\fBinterface=\f1\fI"name"\f1\fB;\f1
-Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default bahaviour.
+Use this advanced setting if you want to confine Shairport Sync to the named interface. Leave it commented out to get the default behaviour.
.TP
\fBaudio_backend_latency_offset_in_seconds=\f1 \fIoffset_in_seconds\f1\fB;\f1
Set this \fIoffset_in_seconds\f1 to compensate for a fixed delay in the audio back end. For example, if the output device delays by 100 ms, set this to -0.1.
@@ -225,7 +225,7 @@ Use this setting to specify the format that should be used to send data to the A
"S" means signed; "U" means unsigned; BE means big-endian and LE means little-endian. Except where stated (using *LE or *BE), endianness matches that of the processor. The default is "S16".
-If you are using a hardware mixer, the best setting is S16, as audio will pass through Shairport Sync unmodifed except for interpolation. If you are using the software mixer, use 32- or 24-bit, if your device is capable of it, to get the lowest possible levels of dither.
+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.
.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.
diff --git a/man/shairport-sync.7.xml b/man/shairport-sync.7.xml
index 993a85c..93b2414 100644
--- a/man/shairport-sync.7.xml
+++ b/man/shairport-sync.7.xml
@@ -111,7 +111,7 @@
i.e. the System Configuration Directory.)</p>
- <p>Within the configuration file, settings are organised into <i>groups</i>, for
+ <p>Within the configuration file, settings are organised into <i>groups</i>, for
example, there is a "general" group of
standard settings, and there is an "alsa" group with settings that pertain to the ALSA
back end. Here is an example of a typical configuration file:</p>
@@ -223,7 +223,7 @@
<option>
<p><opt>udp_port_range=</opt><arg>range</arg><opt>;</opt></p>
- <optdesc><p>Use this in conjunction with the previous setting to specify the
+ <optdesc><p>Use this in conjunction with the previous setting to specify the
<arg>range</arg> of ports that can be checked for availability. Only three ports are
needed.
The default is 100, thus 100 ports will be checked from port 6001 upwards until three
@@ -364,7 +364,7 @@
<option>
<p><opt>interface=</opt><arg>"name"</arg><opt>;</opt></p>
<optdesc><p>Use this advanced setting if you want to confine Shairport Sync to the
- named interface. Leave it commented out to get the default bahaviour.</p></optdesc>
+ named interface. Leave it commented out to get the default behaviour.</p></optdesc>
</option>
<option>
diff --git a/mdns_avahi.c b/mdns_avahi.c
index 2d5cf56..caec909 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -1,7 +1,7 @@
/*
* Embedded Avahi client. This file is part of Shairport.
* Copyright (c) James Laird 2013
- * Additions for metadata and for detecting IPv6 Copyright (c) Mike Brady 2015--2018
+ * Additions for metadata and for detecting IPv6 Copyright (c) Mike Brady 2015--2019
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -55,8 +55,6 @@
}
typedef struct {
- AvahiThreadedPoll *service_poll;
- AvahiClient *service_client;
AvahiServiceBrowser *service_browser;
char *dacp_id;
} dacp_browser_struct;
@@ -80,6 +78,7 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
__attribute__((unused)) const AvahiAddress *address, uint16_t port,
__attribute__((unused)) AvahiStringList *txt,
__attribute__((unused)) AvahiLookupResultFlags flags, void *userdata) {
+ // debug(1,"resolve_callback, event %d.", event);
assert(r);
dacp_browser_struct *dbs = (dacp_browser_struct *)userdata;
@@ -92,13 +91,13 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
break;
case AVAHI_RESOLVER_FOUND: {
// char a[AVAHI_ADDRESS_STR_MAX], *t;
- debug(3, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain);
+ debug(3, "resolve_callback: Service '%s' of type '%s' in domain '%s':", name, type, domain);
if (dbs->dacp_id) {
char *dacpid = strstr(name, "iTunes_Ctrl_");
if (dacpid) {
dacpid += strlen("iTunes_Ctrl_");
if (strcmp(dacpid, dbs->dacp_id) == 0) {
- debug(3, "Client \"%s\"'s DACP port: %u.", dbs->dacp_id, port);
+ debug(3, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
#ifdef CONFIG_DACP_CLIENT
dacp_monitor_port_update_callback(dacpid, port);
#endif
@@ -116,13 +115,14 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
}
}
// debug(1,"service resolver freed by resolve_callback");
- check_avahi_response(1, avahi_service_resolver_free(r));
+ avahi_service_resolver_free(r);
}
+
static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
AvahiBrowserEvent event, const char *name, const char *type,
const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
void *userdata) {
- dacp_browser_struct *dbs = (dacp_browser_struct *)userdata;
+ // debug(1,"browse_callback, event %d.", event);
assert(b);
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
switch (event) {
@@ -132,19 +132,21 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
avahi_threaded_poll_quit(tpoll);
break;
case AVAHI_BROWSER_NEW:
- // debug(1, "(Browser) NEW: service '%s' of type '%s' in domain '%s'.", name, type, domain);
+ // debug(1, "browse_callback: avahi_service_resolver_new for service '%s' of type '%s' in domain
+ // '%s'.", name, type, domain);
/* We ignore the returned resolver object. In the callback
function we free it. If the server is terminated before
the callback function is called the server will free
the resolver for us. */
- if (!(avahi_service_resolver_new(dbs->service_client, interface, protocol, name, type, domain,
+ if (!(avahi_service_resolver_new(client, interface, protocol, name, type, domain,
AVAHI_PROTO_UNSPEC, 0, resolve_callback, userdata)))
debug(1, "Failed to resolve service '%s': %s.", name,
- avahi_strerror(avahi_client_errno(dbs->service_client)));
+ avahi_strerror(avahi_client_errno(client)));
break;
case AVAHI_BROWSER_REMOVE:
- debug(3, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'.", name, type, domain);
+ debug(2, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'.", name, type, domain);
#ifdef CONFIG_DACP_CLIENT
+ dacp_browser_struct *dbs = (dacp_browser_struct *)userdata;
char *dacpid = strstr(name, "iTunes_Ctrl_");
if (dacpid) {
dacpid += strlen("iTunes_Ctrl_");
@@ -167,10 +169,11 @@ static void register_service(AvahiClient *c);
static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
AVAHI_GCC_UNUSED void *userdata) {
+ // debug(1,"egroup_callback, state %d.", state);
switch (state) {
case AVAHI_ENTRY_GROUP_ESTABLISHED:
/* The entry group has been established successfully */
- debug(1, "avahi: service '%s' successfully added.", service_name);
+ debug(2, "avahi: service '%s' successfully added.", service_name);
break;
case AVAHI_ENTRY_GROUP_COLLISION: {
@@ -178,7 +181,7 @@ static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
/* A service name collision with a remote service
* happened. Let's pick a new name */
- debug(1, "avahi name collision -- look for another");
+ debug(2, "avahi name collision -- look for another");
n = avahi_alternative_service_name(service_name);
if (service_name)
avahi_free(service_name);
@@ -213,13 +216,12 @@ static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
}
static void register_service(AvahiClient *c) {
- // debug(1, "avahi: register_service.");
if (!group)
group = avahi_entry_group_new(c, egroup_callback, NULL);
if (!group)
- debug(2, "avahi: avahi_entry_group_new failed");
+ debug(1, "avahi: avahi_entry_group_new failed");
else {
-
+ // debug(2, "register_service -- go ahead and register.");
if (!avahi_entry_group_is_empty(group))
return;
@@ -259,6 +261,7 @@ static void register_service(AvahiClient *c) {
static void client_callback(AvahiClient *c, AvahiClientState state,
AVAHI_GCC_UNUSED void *userdata) {
+ // debug(1,"client_callback, state %d.", state);
int err;
switch (state) {
@@ -310,60 +313,8 @@ static void client_callback(AvahiClient *c, AvahiClientState state,
}
}
-static void service_client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
- int err;
-
- dacp_browser_struct *dbs = (dacp_browser_struct *)userdata;
-
- switch (state) {
- case AVAHI_CLIENT_S_REGISTERING:
- break;
-
- case AVAHI_CLIENT_S_RUNNING:
- break;
-
- case AVAHI_CLIENT_FAILURE:
- err = avahi_client_errno(c);
- debug(1, "avahi: service client failure: %s", avahi_strerror(err));
-
- if (err == AVAHI_ERR_DISCONNECTED) {
- debug(1, "avahi client has been disconnected, so reconnect");
- /* We have been disconnected, so lets reconnect */
- if (c)
- avahi_client_free(c);
- else
- debug(1, "avahi attempt to free a NULL client");
- c = NULL;
-
- if (!(dbs->service_client =
- avahi_client_new(avahi_threaded_poll_get(dbs->service_poll), AVAHI_CLIENT_NO_FAIL,
- service_client_callback, userdata, &err))) {
- warn("avahi: failed to create service client object: %s", avahi_strerror(err));
- avahi_threaded_poll_quit(dbs->service_poll);
- }
- } else {
- warn("avahi: service client failure: %s", avahi_strerror(err));
- avahi_threaded_poll_quit(dbs->service_poll);
- }
- break;
-
- case AVAHI_CLIENT_S_COLLISION:
- debug(2, "avahi: service client state is AVAHI_CLIENT_S_COLLISION...needs a rename: %s",
- service_name);
- break;
-
- case AVAHI_CLIENT_CONNECTING:
- debug(2, "avahi: service client received AVAHI_CLIENT_CONNECTING");
- break;
-
- default:
- debug(1, "avahi: unexpected and unhandled avahi service client state: %d", state);
- break;
- }
-}
-
static int avahi_register(char *srvname, int srvport) {
- // debug(1, "avahi: avahi_register.");
+ // debug(1, "avahi_register.");
service_name = strdup(srvname);
port = srvport;
@@ -387,7 +338,7 @@ static int avahi_register(char *srvname, int srvport) {
}
static void avahi_unregister(void) {
- debug(1, "avahi: avahi_unregister.");
+ // debug(1, "avahi_unregister.");
if (tpoll) {
debug(1, "avahi: stop the threaded poll.");
avahi_threaded_poll_stop(tpoll);
@@ -415,39 +366,14 @@ static void avahi_unregister(void) {
}
void avahi_dacp_monitor_start(void) {
- dacp_browser_struct *dbs = &private_dbs;
+ // debug(1, "avahi_dacp_monitor_start.");
memset((void *)&private_dbs, 0, sizeof(dacp_browser_struct));
-
- // create the threaded poll code
- int err;
- if (!(dbs->service_poll = avahi_threaded_poll_new())) {
- warn("couldn't create avahi threaded service_poll!");
- return;
- }
-
- // create the service client
- if (!(dbs->service_client =
- avahi_client_new(avahi_threaded_poll_get(dbs->service_poll), AVAHI_CLIENT_NO_FAIL,
- service_client_callback, (void *)dbs, &err))) {
- warn("couldn't create avahi service client: %s!", avahi_strerror(err));
- avahi_threaded_poll_free(dbs->service_poll);
- return;
- }
-
- // start the polling thread
- if (avahi_threaded_poll_start(dbs->service_poll) < 0) {
- warn("couldn't start avahi service_poll thread");
- avahi_service_browser_free(dbs->service_browser);
- avahi_client_free(dbs->service_client);
- avahi_threaded_poll_free(dbs->service_poll);
- return;
- }
- debug(3, "Avahi DACP monitor successfully started");
+ debug(1, "avahi_dacp_monitor_start Avahi DACP monitor successfully started");
return;
}
void avahi_dacp_monitor_set_id(const char *dacp_id) {
- debug(3, "avahi_dacp_dont_monitor");
+ // debug(1, "avahi_dacp_monitor_set_id: Search for DACP ID \"%s\".", t);
dacp_browser_struct *dbs = &private_dbs;
if (dbs->dacp_id)
@@ -458,40 +384,35 @@ void avahi_dacp_monitor_set_id(const char *dacp_id) {
char *t = strdup(dacp_id);
if (t) {
dbs->dacp_id = t;
- avahi_threaded_poll_lock(dbs->service_poll);
+ avahi_threaded_poll_lock(tpoll);
if (dbs->service_browser)
avahi_service_browser_free(dbs->service_browser);
if (!(dbs->service_browser =
- avahi_service_browser_new(dbs->service_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
- "_dacp._tcp", NULL, 0, browse_callback, (void *)dbs))) {
+ avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_dacp._tcp",
+ NULL, 0, browse_callback, (void *)dbs))) {
warn("failed to create avahi service browser: %s\n",
- avahi_strerror(avahi_client_errno(dbs->service_client)));
+ avahi_strerror(avahi_client_errno(client)));
}
- avahi_threaded_poll_unlock(dbs->service_poll);
+ avahi_threaded_poll_unlock(tpoll);
} else {
warn("avahi_dacp_set_id: can not allocate a dacp_id string in dacp_browser_struct.");
}
- debug(3, "Search for DACP ID \"%s\".", t);
}
}
void avahi_dacp_monitor_stop() {
- debug(3, "avahi_dacp_dont_monitor");
+ // debug(1, "avahi_dacp_monitor_stop");
dacp_browser_struct *dbs = &private_dbs;
// stop and dispose of everything
- if (dbs->service_poll) {
- avahi_threaded_poll_stop(dbs->service_poll);
- avahi_threaded_poll_lock(dbs->service_poll);
- if (dbs->service_browser)
- avahi_service_browser_free(dbs->service_browser);
- if (dbs->service_client)
- avahi_client_free(dbs->service_client);
- avahi_threaded_poll_unlock(dbs->service_poll);
- avahi_threaded_poll_free(dbs->service_poll);
+ avahi_threaded_poll_lock(tpoll);
+ if (dbs->service_browser) {
+ avahi_service_browser_free(dbs->service_browser);
+ dbs->service_browser = NULL;
}
+ avahi_threaded_poll_unlock(tpoll);
free(dbs->dacp_id);
- debug(3, "Avahi DACP monitor successfully stopped");
+ debug(1, "avahi_dacp_monitor_stop Avahi DACP monitor successfully stopped");
}
mdns_backend mdns_avahi = {.name = "avahi",
diff --git a/metadata_hub.c b/metadata_hub.c
index 400c1e5..0b5aa41 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -61,64 +61,21 @@
int metadata_hub_initialised = 0;
pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER;
-struct track_metadata_bundle *track_metadata; // used for a temporary track metadata store
-void release_char_string(char **str) {
- if (*str) {
- free(*str);
- *str = NULL;
- }
-}
-
-void metadata_hub_release_track_metadata(struct track_metadata_bundle *track_metadata) {
- // debug(1,"release track metadata");
- if (track_metadata) {
- release_char_string(&track_metadata->track_name);
- release_char_string(&track_metadata->artist_name);
- release_char_string(&track_metadata->album_artist_name);
- release_char_string(&track_metadata->album_name);
- release_char_string(&track_metadata->genre);
- release_char_string(&track_metadata->comment);
- release_char_string(&track_metadata->composer);
- release_char_string(&track_metadata->file_kind);
- release_char_string(&track_metadata->song_description);
- release_char_string(&track_metadata->song_album_artist);
- release_char_string(&track_metadata->sort_name);
- release_char_string(&track_metadata->sort_artist);
- release_char_string(&track_metadata->sort_album);
- release_char_string(&track_metadata->sort_composer);
- free((char *)track_metadata);
- } else {
- debug(3, "Asked to release non-existent track metadata");
- }
-}
-
-void metadata_hub_release_track_artwork(void) {
- // debug(1,"release track artwork");
- release_char_string(&metadata_store.cover_art_pathname);
+int string_update(char **str, int *flag, char *s) {
+ if (s)
+ return string_update_with_size(str, flag, s, strlen(s));
+ else
+ return string_update_with_size(str, flag, NULL, 0);
}
void metadata_hub_init(void) {
// debug(1, "Metadata bundle initialisation.");
memset(&metadata_store, 0, sizeof(metadata_store));
- track_metadata = NULL;
metadata_hub_initialised = 1;
}
-void metadata_hub_stop(void) {
- if (metadata_hub_initialised) {
- debug(2, "metadata_hub_stop.");
- metadata_hub_release_track_artwork();
- if (metadata_store.track_metadata) {
- metadata_hub_release_track_metadata(metadata_store.track_metadata);
- metadata_store.track_metadata = NULL;
- }
- if (track_metadata) {
- metadata_hub_release_track_metadata(track_metadata);
- track_metadata = NULL;
- }
- }
-}
+void metadata_hub_stop(void) {}
void add_metadata_watcher(metadata_watcher fn, void *userdata) {
int i;
@@ -132,24 +89,41 @@ void add_metadata_watcher(metadata_watcher fn, void *userdata) {
}
}
+/*
void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) {
// debug(1, "metadata_hub_unlock_hub_mutex_cleanup called.");
pthread_rwlock_unlock(&metadata_hub_re_lock);
}
+*/
void run_metadata_watchers(void) {
int i;
- // debug(1, "locking metadata hub for reading");
- pthread_rwlock_rdlock(&metadata_hub_re_lock);
- pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL);
for (i = 0; i < number_of_watchers; i++) {
if (metadata_store.watchers[i]) {
metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]);
}
}
- // debug(1, "unlocking metadata hub for reading");
- // pthread_rwlock_unlock(&metadata_hub_re_lock);
- pthread_cleanup_pop(1);
+ // turn off changed flags
+ metadata_store.cover_art_pathname_changed = 0;
+ metadata_store.client_ip_changed = 0;
+ metadata_store.server_ip_changed = 0;
+ metadata_store.progress_string_changed = 0;
+ metadata_store.item_id_changed = 0;
+ metadata_store.item_composite_id_changed = 0;
+ metadata_store.artist_name_changed = 0;
+ metadata_store.album_artist_name_changed = 0;
+ metadata_store.album_name_changed = 0;
+ metadata_store.track_name_changed = 0;
+ metadata_store.genre_changed = 0;
+ metadata_store.comment_changed = 0;
+ metadata_store.composer_changed = 0;
+ metadata_store.file_kind_changed = 0;
+ metadata_store.song_description_changed = 0;
+ metadata_store.song_album_artist_changed = 0;
+ metadata_store.sort_artist_changed = 0;
+ metadata_store.sort_album_changed = 0;
+ metadata_store.sort_composer_changed = 0;
+ metadata_store.songtime_in_milliseconds_changed = 0;
}
void metadata_hub_modify_prolog(void) {
@@ -159,52 +133,28 @@ void metadata_hub_modify_prolog(void) {
debug(2, "Metadata_hub write lock is already taken -- must wait.");
pthread_rwlock_wrlock(&metadata_hub_re_lock);
debug(2, "Okay -- acquired the metadata_hub write lock.");
+ } else {
+ debug(3, "Metadata_hub write lock acquired.");
}
}
void metadata_hub_modify_epilog(int modified) {
- // always run this after changing an entry or a sequence of entries in the metadata_hub
- // debug(1, "unlocking metadata hub for writing");
-
- // Here, we check to see if the dacp_server is transitioning between active and inactive
- // If it's going off, we will release track metadata and image stuff
- // If it's already off, we do nothing
- // If it's transitioning to on, we will record it for use later.
-
- int m = 0;
- int tm = modified;
-
- if ((metadata_store.dacp_server_active == 0) &&
- (metadata_store.dacp_server_has_been_active != 0)) {
- debug(2, "dacp_scanner going inactive -- release track metadata and artwork");
- if (metadata_store.track_metadata) {
- m = 1;
- metadata_hub_release_track_metadata(metadata_store.track_metadata);
- metadata_store.track_metadata = NULL;
- }
- if (metadata_store.cover_art_pathname) {
- m = 1;
- metadata_hub_release_track_artwork();
- }
- if (m)
- debug(2, "Release track metadata after dacp server goes inactive.");
- tm += m;
- }
metadata_store.dacp_server_has_been_active =
metadata_store.dacp_server_active; // set the scanner_has_been_active now.
- pthread_rwlock_unlock(&metadata_hub_re_lock);
- if (tm) {
+ if (modified) {
run_metadata_watchers();
}
+ pthread_rwlock_unlock(&metadata_hub_re_lock);
+ debug(3, "Metadata_hub write lock unlocked.");
}
void metadata_hub_read_prolog(void) {
// always run this before reading an entry or a sequence of entries in the metadata_hub
// debug(1, "locking metadata hub for reading");
if (pthread_rwlock_tryrdlock(&metadata_hub_re_lock) != 0) {
- debug(1, "Metadata_hub read lock is already taken -- must wait.");
+ debug(2, "Metadata_hub read lock is already taken -- must wait.");
pthread_rwlock_rdlock(&metadata_hub_re_lock);
- debug(1, "Okay -- acquired the metadata_hub read lock.");
+ debug(2, "Okay -- acquired the metadata_hub read lock.");
}
}
@@ -220,121 +170,125 @@ char *metadata_write_image_file(const char *buf, int len) {
// it will return a path to the image file allocated with malloc.
// free it if you don't need it.
- char *path = NULL; // this will be what is returned
-
- uint8_t img_md5[16];
-// uint8_t ap_md5[16];
-
-#ifdef CONFIG_OPENSSL
- MD5_CTX ctx;
- MD5_Init(&ctx);
- MD5_Update(&ctx, buf, len);
- MD5_Final(img_md5, &ctx);
-#endif
-
-#ifdef CONFIG_MBEDTLS
-#if MBEDTLS_VERSION_MINOR >= 7
- mbedtls_md5_context tctx;
- mbedtls_md5_starts_ret(&tctx);
- mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len);
- mbedtls_md5_finish_ret(&tctx, img_md5);
-#else
- mbedtls_md5_context tctx;
- mbedtls_md5_starts(&tctx);
- mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
- mbedtls_md5_finish(&tctx, img_md5);
-#endif
-#endif
-
-#ifdef CONFIG_POLARSSL
- md5_context tctx;
- md5_starts(&tctx);
- md5_update(&tctx, (const unsigned char *)buf, len);
- md5_finish(&tctx, img_md5);
-#endif
-
- char img_md5_str[33];
- memset(img_md5_str, 0, sizeof(img_md5_str));
- char *ext;
- char png[] = "png";
- char jpg[] = "jpg";
- int i;
- for (i = 0; i < 16; i++)
- snprintf(&img_md5_str[i * 2], 3, "%02x", (uint8_t)img_md5[i]);
- // see if the file is a jpeg or a png
- if (strncmp(buf, "\xFF\xD8\xFF", 3) == 0)
- ext = jpg;
- else if (strncmp(buf, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0)
- ext = png;
- else {
- debug(1, "Unidentified image type of cover art -- jpg extension used.");
- ext = jpg;
- }
- mode_t oldumask = umask(000);
- int result = mkpath(config.cover_art_cache_dir, 0777);
- umask(oldumask);
- if ((result == 0) || (result == -EEXIST)) {
- // see if the file exists by opening it.
- // if it exists, we're done
- char *prefix = "cover-";
-
- size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 +
- strlen(ext);
-
- path = malloc(pl + 1);
- snprintf(path, pl + 1, "%s/%s%s.%s", config.cover_art_cache_dir, prefix, img_md5_str, ext);
- int cover_fd = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRWXU | S_IRGRP | S_IROTH);
- if (cover_fd > 0) {
- // write the contents
- if (write(cover_fd, buf, len) < len) {
- warn("Writing cover art file \"%s\" failed!", path);
- free(path);
- path = NULL;
- }
- close(cover_fd);
-
- // now delete all other files, if there are any
- DIR *d;
- struct dirent *dir;
- d = opendir(config.cover_art_cache_dir);
- if (d) {
- int fnl = strlen(prefix) + strlen(img_md5_str) + 1 + strlen(ext) + 1;
-
- char *full_filename = malloc(fnl);
- if (full_filename == NULL)
- die("Can't allocate memory at metadata_write_image_file.");
- memset(full_filename, 0, fnl);
- snprintf(full_filename, fnl, "%s%s.%s", prefix, img_md5_str, ext);
- int dir_fd = open(config.cover_art_cache_dir, O_DIRECTORY);
- if (dir_fd > 0) {
- while ((dir = readdir(d)) != NULL) {
- if (dir->d_type == DT_REG) {
- if (strcmp(full_filename, dir->d_name) != 0) {
- if (unlinkat(dir_fd, dir->d_name, 0) != 0) {
- debug(1, "Error %d deleting cover art file \"%s\".", errno, dir->d_name);
+ char *path = NULL; // this will be what is returned
+ if (strcmp(config.cover_art_cache_dir,"") != 0) { // an empty string means do not write the file
+
+ uint8_t img_md5[16];
+ // uint8_t ap_md5[16];
+
+ #ifdef CONFIG_OPENSSL
+ MD5_CTX ctx;
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, buf, len);
+ MD5_Final(img_md5, &ctx);
+ #endif
+
+ #ifdef CONFIG_MBEDTLS
+ #if MBEDTLS_VERSION_MINOR >= 7
+ mbedtls_md5_context tctx;
+ mbedtls_md5_starts_ret(&tctx);
+ mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len);
+ mbedtls_md5_finish_ret(&tctx, img_md5);
+ #else
+ mbedtls_md5_context tctx;
+ mbedtls_md5_starts(&tctx);
+ mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
+ mbedtls_md5_finish(&tctx, img_md5);
+ #endif
+ #endif
+
+ #ifdef CONFIG_POLARSSL
+ md5_context tctx;
+ md5_starts(&tctx);
+ md5_update(&tctx, (const unsigned char *)buf, len);
+ md5_finish(&tctx, img_md5);
+ #endif
+
+ char img_md5_str[33];
+ memset(img_md5_str, 0, sizeof(img_md5_str));
+ char *ext;
+ char png[] = "png";
+ char jpg[] = "jpg";
+ int i;
+ for (i = 0; i < 16; i++)
+ snprintf(&img_md5_str[i * 2], 3, "%02x", (uint8_t)img_md5[i]);
+ // see if the file is a jpeg or a png
+ if (strncmp(buf, "\xFF\xD8\xFF", 3) == 0)
+ ext = jpg;
+ else if (strncmp(buf, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0)
+ ext = png;
+ else {
+ debug(1, "Unidentified image type of cover art -- jpg extension used.");
+ ext = jpg;
+ }
+ mode_t oldumask = umask(000);
+ int result = mkpath(config.cover_art_cache_dir, 0777);
+ umask(oldumask);
+ if ((result == 0) || (result == -EEXIST)) {
+ // see if the file exists by opening it.
+ // if it exists, we're done
+ char *prefix = "cover-";
+
+ size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 +
+ strlen(ext);
+
+ path = malloc(pl + 1);
+ snprintf(path, pl + 1, "%s/%s%s.%s", config.cover_art_cache_dir, prefix, img_md5_str, ext);
+ int cover_fd = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRWXU | S_IRGRP | S_IROTH);
+ if (cover_fd > 0) {
+ // write the contents
+ if (write(cover_fd, buf, len) < len) {
+ warn("Writing cover art file \"%s\" failed!", path);
+ free(path);
+ path = NULL;
+ }
+ close(cover_fd);
+
+ // now delete all other files, if requested
+ if (config.retain_coverart == 0) {
+ DIR *d;
+ struct dirent *dir;
+ d = opendir(config.cover_art_cache_dir);
+ if (d) {
+ int fnl = strlen(prefix) + strlen(img_md5_str) + 1 + strlen(ext) + 1;
+
+ char *full_filename = malloc(fnl);
+ if (full_filename == NULL)
+ die("Can't allocate memory at metadata_write_image_file.");
+ memset(full_filename, 0, fnl);
+ snprintf(full_filename, fnl, "%s%s.%s", prefix, img_md5_str, ext);
+ int dir_fd = open(config.cover_art_cache_dir, O_DIRECTORY);
+ if (dir_fd > 0) {
+ while ((dir = readdir(d)) != NULL) {
+ if (dir->d_type == DT_REG) {
+ if (strcmp(full_filename, dir->d_name) != 0) {
+ if (unlinkat(dir_fd, dir->d_name, 0) != 0) {
+ debug(1, "Error %d deleting cover art file \"%s\".", errno, dir->d_name);
+ }
+ }
}
}
+ } else {
+ debug(1, "Can't open the directory for deletion.");
}
+ free(full_filename);
+ closedir(d);
}
- } else {
- debug(1, "Can't open the directory for deletion.");
}
- free(full_filename);
- closedir(d);
+ } else {
+ // if (errno == EEXIST)
+ // debug(1, "Cover art file \"%s\" already exists!", path);
+ // else {
+ if (errno != EEXIST) {
+ warn("Could not open file \"%s\" for writing cover art", path);
+ free(path);
+ path = NULL;
+ }
}
} else {
- // if (errno == EEXIST)
- // debug(1, "Cover art file \"%s\" already exists!", path);
- // else {
- if (errno != EEXIST) {
- warn("Could not open file \"%s\" for writing cover art", path);
- free(path);
- path = NULL;
- }
+ debug(1, "Couldn't access or create the cover art cache directory \"%s\".",
+ config.cover_art_cache_dir);
}
- } else {
- debug(1, "Couldn't access or create the cover art cache directory \"%s\".",
- config.cover_art_cache_dir);
}
return path;
}
@@ -345,124 +299,125 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
// https://code.google.com/p/ytrack/wiki/DMAP
// all the following items of metadata are contained in one metadata packet
- // they are preseded by an 'ssnc' 'mdst' item and followed by an 'ssnc 'mden' item.
+ // they are preceded by an 'ssnc' 'mdst' item and followed by an 'ssnc 'mden' item.
+ uint32_t ui;
+ char *cs;
+ int changed = 0;
if (type == 'core') {
switch (code) {
case 'mper':
- if (track_metadata) {
- track_metadata->item_id = ntohl(*(uint32_t *)data);
- track_metadata->item_id_received = 1;
- debug(2, "MH Item ID set to: \"%u\"", track_metadata->item_id);
- } else {
- debug(1, "No track metadata memory allocated when item id received!");
+ ui = ntohl(*(uint32_t *)data);
+ debug(2, "MH Item ID seen: \"%u\" of length %u.", ui, length);
+ if (ui != metadata_store.item_id) {
+ metadata_store.item_id = ui;
+ metadata_store.item_id_changed = 1;
+ metadata_store.item_id_received = 1;
+ debug(2, "MH Item ID set to: \"%u\"", metadata_store.item_id);
+ }
+ break;
+ case 'astm':
+ ui = ntohl(*(uint32_t *)data);
+ debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
+ if (ui != metadata_store.songtime_in_milliseconds) {
+ metadata_store.songtime_in_milliseconds = ui;
+ metadata_store.songtime_in_milliseconds_changed = 1;
+ debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
}
break;
case 'asal':
- if (track_metadata) {
- track_metadata->album_name = strndup(data, length);
- debug(2, "MH Album name set to: \"%s\"", track_metadata->album_name);
- } else {
- debug(1, "No track metadata memory allocated when album name received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.album_name, &metadata_store.album_name_changed, cs)) {
+ debug(2, "MH Album name set to: \"%s\"", metadata_store.album_name);
}
+ free(cs);
break;
case 'asar':
- if (track_metadata) {
- track_metadata->artist_name = strndup(data, length);
- debug(2, "MH Artist name set to: \"%s\"", track_metadata->artist_name);
- } else {
- debug(1, "No track metadata memory allocated when artist name received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.artist_name, &metadata_store.artist_name_changed, cs)) {
+ debug(2, "MH Artist name set to: \"%s\"", metadata_store.artist_name);
}
+ free(cs);
break;
case 'assl':
- if (track_metadata) {
- track_metadata->album_artist_name = strndup(data, length);
- debug(2, "MH Album Artist name set to: \"%s\"", track_metadata->album_artist_name);
- } else {
- debug(1, "No track metadata memory allocated when album artist name received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.album_artist_name,
+ &metadata_store.album_artist_name_changed, cs)) {
+ debug(2, "MH Album Artist name set to: \"%s\"", metadata_store.album_artist_name);
}
+ free(cs);
break;
case 'ascm':
- if (track_metadata) {
- track_metadata->comment = strndup(data, length);
- debug(2, "MH Comment set to: \"%s\"", track_metadata->comment);
- } else {
- debug(1, "No track metadata memory allocated when comment received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.comment, &metadata_store.comment_changed, cs)) {
+ debug(2, "MH Comment set to: \"%s\"", metadata_store.comment);
}
+ free(cs);
break;
case 'asgn':
- if (track_metadata) {
- track_metadata->genre = strndup(data, length);
- debug(2, "MH Genre set to: \"%s\"", track_metadata->genre);
- } else {
- debug(1, "No track metadata memory allocated when genre received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.genre, &metadata_store.genre_changed, cs)) {
+ debug(2, "MH Genre set to: \"%s\"", metadata_store.genre);
}
+ free(cs);
break;
case 'minm':
- if (track_metadata) {
- track_metadata->track_name = strndup(data, length);
- debug(2, "MH Track name set to: \"%s\"", track_metadata->track_name);
- } else {
- debug(1, "No track metadata memory allocated when track name received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.track_name, &metadata_store.track_name_changed, cs)) {
+ debug(2, "MH Track Name set to: \"%s\"", metadata_store.track_name);
}
+ free(cs);
break;
case 'ascp':
- if (track_metadata) {
- track_metadata->composer = strndup(data, length);
- debug(2, "MH Composer set to: \"%s\"", track_metadata->composer);
- } else {
- debug(1, "No track metadata memory allocated when track name received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.composer, &metadata_store.composer_changed, cs)) {
+ debug(2, "MH Composer set to: \"%s\"", metadata_store.composer);
}
+ free(cs);
break;
case 'asdt':
- if (track_metadata) {
- track_metadata->song_description = strndup(data, length);
- debug(2, "MH Song Description set to: \"%s\"", track_metadata->song_description);
- } else {
- debug(1, "No track metadata memory allocated when song description received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.song_description, &metadata_store.song_description_changed,
+ cs)) {
+ debug(2, "MH Song Description set to: \"%s\"", metadata_store.song_description);
}
+ free(cs);
break;
case 'asaa':
- if (track_metadata) {
- track_metadata->song_album_artist = strndup(data, length);
- debug(2, "MH Song Album Artist set to: \"%s\"", track_metadata->song_album_artist);
- } else {
- debug(1, "No track metadata memory allocated when song artist received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.song_album_artist,
+ &metadata_store.song_album_artist_changed, cs)) {
+ debug(2, "MH Song Album Artist set to: \"%s\"", metadata_store.song_album_artist);
}
+ free(cs);
break;
case 'assn':
- if (track_metadata) {
- track_metadata->sort_name = strndup(data, length);
- debug(2, "MH Sort Name set to: \"%s\"", track_metadata->sort_name);
- } else {
- debug(1, "No track metadata memory allocated when sort name description received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.sort_name, &metadata_store.sort_name_changed, cs)) {
+ debug(2, "MH Sort Name set to: \"%s\"", metadata_store.sort_name);
}
+ free(cs);
break;
case 'assa':
- if (track_metadata) {
- track_metadata->sort_artist = strndup(data, length);
- debug(2, "MH Sort Artist set to: \"%s\"", track_metadata->sort_artist);
- } else {
- debug(1, "No track metadata memory allocated when sort artist description received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.sort_artist, &metadata_store.sort_artist_changed, cs)) {
+ debug(2, "MH Sort Artist set to: \"%s\"", metadata_store.sort_artist);
}
+ free(cs);
break;
case 'assu':
- if (track_metadata) {
- track_metadata->sort_album = strndup(data, length);
- debug(2, "MH Sort Album set to: \"%s\"", track_metadata->sort_album);
- } else {
- debug(1, "No track metadata memory allocated when sort album description received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.sort_album, &metadata_store.sort_album_changed, cs)) {
+ debug(2, "MH Sort Album set to: \"%s\"", metadata_store.sort_album);
}
+ free(cs);
break;
case 'assc':
- if (track_metadata) {
- track_metadata->sort_composer = strndup(data, length);
- debug(2, "MH Sort Composer set to: \"%s\"", track_metadata->sort_composer);
- } else {
- debug(1, "No track metadata memory allocated when sort composer description received!");
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.sort_composer, &metadata_store.sort_composer_changed, cs)) {
+ debug(2, "MH Sort Composer set to: \"%s\"", metadata_store.sort_composer);
}
- break;
-
+ free(cs);
default:
/*
{
@@ -487,132 +442,122 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
}
} else if (type == 'ssnc') {
switch (code) {
-
// ignore the following
case 'pcst':
case 'pcen':
break;
-
case 'mdst':
debug(2, "MH Metadata stream processing start.");
- if (track_metadata) {
- debug(1, "This track metadata bundle still seems to exist -- releasing it");
- metadata_hub_release_track_metadata(track_metadata);
- }
- track_metadata = (struct track_metadata_bundle *)malloc(sizeof(struct track_metadata_bundle));
- if (track_metadata == NULL)
- die("Could not allocate memory for track metadata.");
- memset(track_metadata, 0, sizeof(struct track_metadata_bundle));
+ metadata_hub_modify_prolog();
break;
case 'mden':
- if (track_metadata) {
- metadata_hub_modify_prolog();
- metadata_hub_release_track_metadata(metadata_store.track_metadata);
- metadata_store.track_metadata = track_metadata;
- track_metadata = NULL;
- metadata_hub_modify_epilog(1);
- }
debug(2, "MH Metadata stream processing end.");
+ metadata_hub_modify_epilog(1);
+ debug(2, "MH Metadata stream processing epilog complete.");
break;
case 'PICT':
- if (length > 16) {
- metadata_hub_modify_prolog();
- debug(2, "MH Picture received, length %u bytes.", length);
- release_char_string(&metadata_store.cover_art_pathname);
- metadata_store.cover_art_pathname = metadata_write_image_file(data, length);
- metadata_hub_modify_epilog(1);
+ metadata_hub_modify_prolog();
+ debug(2, "MH Picture received, length %u bytes.", length);
+ char uri[2048];
+ if ((length > 16) && (strcmp(config.cover_art_cache_dir,"")!=0)) { // if it's okay to write the file
+ char *pathname = metadata_write_image_file(data, length);
+ snprintf(uri, sizeof(uri), "file://%s", pathname);
+ free(pathname);
+ } else {
+ uri[0] = '\0';
}
+ if (string_update(&metadata_store.cover_art_pathname,
+ &metadata_store.cover_art_pathname_changed,
+ uri)) // if the picture's file path is different from the stored one...
+ metadata_hub_modify_epilog(1);
+ else
+ metadata_hub_modify_epilog(0);
break;
- /*
case 'clip':
- if ((metadata_store.client_ip == NULL) ||
- (strncmp(metadata_store.client_ip, data, length) != 0)) {
- metadata_hub_modify_prolog();
- if (metadata_store.client_ip)
- free(metadata_store.client_ip);
- metadata_store.client_ip = strndup(data, length);
- debug(1, "MH Client IP set to: \"%s\"", metadata_store.client_ip);
- metadata_store.client_ip_changed = 1;
- metadata_store.changed = 1;
- metadata_hub_modify_epilog(1);
+ metadata_hub_modify_prolog();
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.client_ip, &metadata_store.client_ip_changed, cs)) {
+ changed = 1;
+ debug(2, "MH Client IP set to: \"%s\"", metadata_store.client_ip);
}
+ free(cs);
+ metadata_hub_modify_epilog(changed);
break;
- */
case 'prgr':
- if ((metadata_store.progress_string == NULL) ||
- (strncmp(metadata_store.progress_string, data, length) != 0)) {
- metadata_hub_modify_prolog();
- release_char_string(&metadata_store.progress_string);
- metadata_store.progress_string = strndup(data, length);
+ metadata_hub_modify_prolog();
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.progress_string, &metadata_store.progress_string_changed,
+ cs)) {
+ changed = 1;
debug(2, "MH Progress String set to: \"%s\"", metadata_store.progress_string);
- metadata_hub_modify_epilog(1);
}
+ free(cs);
+ metadata_hub_modify_epilog(changed);
break;
case 'svip':
- if ((metadata_store.server_ip == NULL) ||
- (strncmp(metadata_store.server_ip, data, length) != 0)) {
- metadata_hub_modify_prolog();
- release_char_string(&metadata_store.server_ip);
- metadata_store.server_ip = strndup(data, length);
- // debug(1, "MH Server IP set to: \"%s\"", metadata_store.server_ip);
- metadata_hub_modify_epilog(1);
+ metadata_hub_modify_prolog();
+ cs = strndup(data, length);
+ if (string_update(&metadata_store.server_ip, &metadata_store.server_ip_changed, cs)) {
+ changed = 1;
+ debug(2, "MH Server IP set to: \"%s\"", metadata_store.server_ip);
}
+ free(cs);
+ metadata_hub_modify_epilog(changed);
break;
- // these could tell us about play / pause etc. but will only occur if metadata is enabled, so
- // we'll just ignore them
- case 'abeg': {
+ case 'abeg':
metadata_hub_modify_prolog();
- int changed = (metadata_store.active_state != AM_ACTIVE);
+ changed = (metadata_store.active_state != AM_ACTIVE);
metadata_store.active_state = AM_ACTIVE;
metadata_hub_modify_epilog(changed);
- } break;
- case 'aend': {
+ break;
+ case 'aend':
metadata_hub_modify_prolog();
- int changed = (metadata_store.active_state != AM_INACTIVE);
+ changed = (metadata_store.active_state != AM_INACTIVE);
metadata_store.active_state = AM_INACTIVE;
metadata_hub_modify_epilog(changed);
- } break;
- case 'pbeg': {
+ break;
+ case 'pbeg':
metadata_hub_modify_prolog();
- int changed = (metadata_store.player_state != PS_PLAYING);
+ changed = ((metadata_store.player_state != PS_PLAYING) || (metadata_store.player_thread_active == 0));
metadata_store.player_state = PS_PLAYING;
metadata_store.player_thread_active = 1;
metadata_hub_modify_epilog(changed);
- } break;
- case 'pend': {
+ break;
+ case 'pend':
metadata_hub_modify_prolog();
- metadata_store.player_thread_active = 0;
+ changed = ((metadata_store.player_state != PS_STOPPED) || (metadata_store.player_thread_active == 1));
metadata_store.player_state = PS_STOPPED;
- metadata_hub_modify_epilog(1);
- } break;
- case 'pfls': {
+ metadata_store.player_thread_active = 0;
+ metadata_hub_modify_epilog(changed);
+ break;
+ case 'pfls':
metadata_hub_modify_prolog();
- int changed = (metadata_store.player_state != PS_PAUSED);
+ changed = (metadata_store.player_state != PS_PAUSED);
metadata_store.player_state = PS_PAUSED;
metadata_hub_modify_epilog(changed);
- } break;
+ break;
case 'pffr': // this is sent when the first frame has been received
- case 'prsm': {
+ case 'prsm':
metadata_hub_modify_prolog();
int changed = (metadata_store.player_state != PS_PLAYING);
metadata_store.player_state = PS_PLAYING;
metadata_hub_modify_epilog(changed);
- } break;
+ break;
case 'pvol': {
- // Note: it's assumed that the config.airplay volume has already been correctly set.
- int modified = 0;
- int32_t actual_volume;
- int gv = dacp_get_volume(&actual_volume);
metadata_hub_modify_prolog();
- if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
- metadata_store.speaker_volume = actual_volume;
- modified = 1;
- }
+ // Note: it's assumed that the config.airplay volume has already been correctly set.
+ //int32_t actual_volume;
+ //int gv = dacp_get_volume(&actual_volume);
+ //metadata_hub_modify_prolog();
+ //if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
+ // metadata_store.speaker_volume = actual_volume;
+ // changed = 1;
+ //}
if (metadata_store.airplay_volume != config.airplay_volume) {
metadata_store.airplay_volume = config.airplay_volume;
- modified = 1;
+ changed = 1;
}
- metadata_hub_modify_epilog(modified); // change
+ metadata_hub_modify_epilog(changed); // change
} break;
default: {
diff --git a/metadata_hub.h b/metadata_hub.h
index 784f662..5b565bb 100644
--- a/metadata_hub.h
+++ b/metadata_hub.h
@@ -30,27 +30,8 @@ enum repeat_status_type {
RS_ALL,
} repeat_status_type;
-typedef struct track_metadata_bundle {
- uint32_t item_id; // seems to be a track ID -- see itemid in DACP.c
- int item_id_received; // important for deciding if the track information should be ignored.
- unsigned char
- item_composite_id[16]; // seems to be nowplaying 4 ids: dbid, plid, playlistItem, itemid
- char *track_name; // a malloced string -- if non-zero, free it before replacing it
- char *artist_name; // a malloced string -- if non-zero, free it before replacing it
- char *album_artist_name; // a malloced string -- if non-zero, free it before replacing it
- char *album_name; // a malloced string -- if non-zero, free it before replacing it
- char *genre; // a malloced string -- if non-zero, free it before replacing it
- char *comment; // a malloced string -- if non-zero, free it before replacing it
- char *composer; // a malloced string -- if non-zero, free it before replacing it
- char *file_kind; // a malloced string -- if non-zero, free it before replacing it
- char *song_description; // a malloced string -- if non-zero, free it before replacing it
- char *song_album_artist; // a malloced string -- if non-zero, free it before replacing it
- char *sort_name; // a malloced string -- if non-zero, free it before replacing it
- char *sort_artist; // a malloced string -- if non-zero, free it before replacing it
- char *sort_album; // a malloced string -- if non-zero, free it before replacing it
- char *sort_composer; // a malloced string -- if non-zero, free it before replacing it
- uint32_t songtime_in_milliseconds;
-} track_metadata_bundle;
+int string_update(char **str, int *changed, char *s);
+int int_update(int *receptacle, int *changed, int value);
struct metadata_bundle;
@@ -60,8 +41,14 @@ typedef struct metadata_bundle {
char *client_ip; // IP number used by the audio source (i.e. the "client"), which is also the DACP
// server
+ int client_ip_changed;
+
char *server_ip; // IP number used by Shairport Sync
- char *progress_string; // progress string, emitted by the source from time to time
+ int server_ip_changed;
+
+ char *progress_string; // progress string, emitted by the source from time to time
+ int progress_string_changed;
+
int player_thread_active; // true if a play thread is running
int dacp_server_active; // true if there's a reachable DACP server (assumed to be the Airplay
// client) ; false otherwise
@@ -76,9 +63,65 @@ typedef struct metadata_bundle {
enum shuffle_status_type shuffle_status;
enum repeat_status_type repeat_status;
- struct track_metadata_bundle *track_metadata;
+ // the following pertain to the track playing
+
+ char *cover_art_pathname;
+ int cover_art_pathname_changed;
+
+ uint32_t item_id; // seems to be a track ID -- see itemid in DACP.c
+ int item_id_changed;
+ int item_id_received; // important for deciding if the track information should be ignored.
+
+ unsigned char
+ item_composite_id[16]; // seems to be nowplaying 4 ids: dbid, plid, playlistItem, itemid
+ int item_composite_id_changed;
+
+ char *track_name;
+ int track_name_changed;
+
+ char *artist_name;
+ int artist_name_changed;
+
+ char *album_artist_name;
+ int album_artist_name_changed;
+
+ char *album_name;
+ int album_name_changed;
+
+ char *genre;
+ int genre_changed;
+
+ char *comment;
+ int comment_changed;
+
+ char *composer;
+ int composer_changed;
+
+ char *file_kind;
+ int file_kind_changed;
+
+ char *song_description;
+ int song_description_changed;
+
+ char *song_album_artist;
+ int song_album_artist_changed;
+
+ char *sort_name;
+ int sort_name_changed;
+
+ char *sort_artist;
+ int sort_artist_changed;
+
+ char *sort_album;
+ int sort_album_changed;
+
+ char *sort_composer;
+ int sort_composer_changed;
+
+ uint32_t songtime_in_milliseconds;
+ int songtime_in_milliseconds_changed;
- char *cover_art_pathname; // if non-zero, it will have been assigned with malloc.
+ // end
enum play_status_type
player_state; // this is the state of the actual player itself, which can be a bit noisy.
@@ -106,7 +149,7 @@ void metadata_hub_release_track_artwork(void);
// these functions lock and unlock the read-write mutex on the metadata hub and run the watchers
// afterwards
void metadata_hub_modify_prolog(void);
-void metadata_hub_modify_epilog(int modified); // set to true if modifications occured, 0 otherwise
+void metadata_hub_modify_epilog(int modified); // set to true if modifications occurred, 0 otherwise
// these are for safe reading
void metadata_hub_read_prolog(void);
diff --git a/mpris-service.c b/mpris-service.c
index 668d776..37e7bf8 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -82,105 +82,100 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
debug(1, "This should never happen.");
}
- GVariantBuilder *dict_builder, *aa;
-
- /* Build the metadata array */
- // debug(1,"Build metadata");
- dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
-
- // Make up the artwork URI if we have one
- if (argc->cover_art_pathname) {
- char artURIstring[1024];
- snprintf(artURIstring, sizeof(artURIstring), "file://%s", argc->cover_art_pathname);
- // debug(1,"artURI String: \"%s\".",artURIstring);
- GVariant *artUrl = g_variant_new("s", artURIstring);
- g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl);
- }
-
- // Add the TrackID if we have one
- // Build the Track ID from the 16-byte item_composite_id in hex prefixed by
- // /org/gnome/ShairportSync
- char st[33];
- char *pt = st;
- int it;
- int non_zero = 0;
- if (argc->track_metadata) {
+ /*
+ // Add the TrackID if we have one
+ // Build the Track ID from the 16-byte item_composite_id in hex prefixed by
+ // /org/gnome/ShairportSync
+ char st[33];
+ char *pt = st;
+ int it;
+ int non_zero = 0;
for (it = 0; it < 16; it++) {
if (argc->track_metadata->item_composite_id[it])
non_zero = 1;
snprintf(pt, 3, "%02X", argc->track_metadata->item_composite_id[it]);
pt += 2;
}
+ *pt = 0;
+
+ if (non_zero) {
+ // debug(1, "Set ID using composite ID: \"0x%s\".", st);
+ char trackidstring[1024];
+ snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%s", st);
+ GVariant *trackid = g_variant_new("o", trackidstring);
+ g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
+ } else if ((argc->track_metadata) && (argc->track_metadata->item_id)) {
+ char trackidstring[128];
+ // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id);
+ snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
+ argc->track_metadata->item_id);
+ GVariant *trackid = g_variant_new("o", trackidstring);
+ g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
+ }
+
+ */
+
+ // Build the metadata array
+ debug(2, "Build metadata");
+ GVariantBuilder *dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+
+ // Add in the artwork URI if it exists.
+ if (argc->cover_art_pathname) {
+ GVariant *artUrl = g_variant_new("s", argc->cover_art_pathname);
+ g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl);
}
- *pt = 0;
- if (non_zero) {
- // debug(1, "Set ID using composite ID: \"0x%s\".", st);
- char trackidstring[1024];
- snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%s", st);
- GVariant *trackid = g_variant_new("o", trackidstring);
- g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
- } else if ((argc->track_metadata) && (argc->track_metadata->item_id)) {
+ // Add in the Track ID based on the 'mper' metadata if it is non-zero
+ if (argc->item_id != 0) {
char trackidstring[128];
- // debug(1, "Set ID using mper ID: \"%u\".",argc->item_id);
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
- argc->track_metadata->item_id);
+ argc->item_id);
GVariant *trackid = g_variant_new("o", trackidstring);
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
}
- // Add the track length if it's non-zero
- if ((argc->track_metadata) && (argc->track_metadata->songtime_in_milliseconds)) {
- uint64_t track_length_in_microseconds = argc->track_metadata->songtime_in_milliseconds;
- track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision
- // Make up the track name and album name
- // debug(1, "Set tracklength to %lu.", track_length_in_microseconds);
- GVariant *tracklength = g_variant_new("x", track_length_in_microseconds);
- g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength);
+ // Add the track name if it exists
+ if (argc->track_name) {
+ GVariant *track_name = g_variant_new("s", argc->track_name);
+ g_variant_builder_add(dict_builder, "{sv}", "xesam:title", track_name);
}
- // Add the track name if there is one
- if ((argc->track_metadata) && (argc->track_metadata->track_name)) {
- // debug(1, "Track name set to \"%s\".", argc->track_name);
- GVariant *trackname = g_variant_new("s", argc->track_metadata->track_name);
- g_variant_builder_add(dict_builder, "{sv}", "xesam:title", trackname);
+ // Add the album name if it exists
+ if (argc->album_name) {
+ GVariant *album_name = g_variant_new("s", argc->album_name);
+ g_variant_builder_add(dict_builder, "{sv}", "xesam:album", album_name);
}
- // Add the album name if there is one
- if ((argc->track_metadata) && (argc->track_metadata->album_name)) {
- // debug(1, "Album name set to \"%s\".", argc->album_name);
- GVariant *albumname = g_variant_new("s", argc->track_metadata->album_name);
- g_variant_builder_add(dict_builder, "{sv}", "xesam:album", albumname);
+ // Add the artist name if it exists
+ if (argc->artist_name) {
+ GVariantBuilder *artist_as = g_variant_builder_new(G_VARIANT_TYPE("as"));
+ g_variant_builder_add(artist_as, "s", argc->artist_name);
+ GVariant *artists = g_variant_builder_end(artist_as);
+ g_variant_builder_unref(artist_as);
+ g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists);
}
- // Add the artists if there are any (actually there will be at most one, but put it in an array)
- if ((argc->track_metadata) && (argc->track_metadata->artist_name)) {
- /* Build the artists array */
- // debug(1,"Build artist array");
- aa = g_variant_builder_new(G_VARIANT_TYPE("as"));
- g_variant_builder_add(aa, "s", argc->track_metadata->artist_name);
- GVariant *artists = g_variant_builder_end(aa);
- g_variant_builder_unref(aa);
- g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists);
+ // Add the genre if it exists
+ if (argc->genre) {
+ GVariantBuilder *genre_as = g_variant_builder_new(G_VARIANT_TYPE("as"));
+ g_variant_builder_add(genre_as, "s", argc->genre);
+ GVariant *genre = g_variant_builder_end(genre_as);
+ g_variant_builder_unref(genre_as);
+ g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genre);
}
- // Add the genres if there are any (actually there will be at most one, but put it in an array)
- if ((argc->track_metadata) && (argc->track_metadata->genre)) {
- // debug(1,"Build genre");
- aa = g_variant_builder_new(G_VARIANT_TYPE("as"));
- g_variant_builder_add(aa, "s", argc->track_metadata->genre);
- GVariant *genres = g_variant_builder_end(aa);
- g_variant_builder_unref(aa);
- g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genres);
+ if (argc->songtime_in_milliseconds) {
+ uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds;
+ track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision
+ // Make up the track name and album name
+ // debug(1, "Set tracklength to %lu.", track_length_in_microseconds);
+ GVariant *tracklength = g_variant_new("x", track_length_in_microseconds);
+ g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength);
}
GVariant *dict = g_variant_builder_end(dict_builder);
g_variant_builder_unref(dict_builder);
-
- // debug(1,"Set metadata");
media_player2_player_set_metadata(mprisPlayerPlayerSkeleton, dict);
-
- // media_player2_player_set_volume(mprisPlayerPlayerSkeleton, metadata_store.speaker_volume);
}
static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation,
diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml
index cea574b..15317fa 100644
--- a/org.gnome.ShairportSync.xml
+++ b/org.gnome.ShairportSync.xml
@@ -5,8 +5,11 @@
<property name='Active' type='b' access='read'/>
<property name="DisableStandby" type="b" access="readwrite" />
<property name="DisableStandbyMode" type="s" access="readwrite" />
- <property name="LoudnessFilterActive" type="b" access="readwrite" />
+ <property name="Loudness" type="b" access="readwrite" />
<property name="LoudnessThreshold" type="d" access="readwrite" />
+ <property name="Convolution" type="b" access="readwrite" />
+ <property name="ConvolutionGain" type="d" access="readwrite" />
+ <property name="ConvolutionImpulseResponseFile" type="s" access="readwrite" />
<property name="DriftTolerance" type="d" access="readwrite" />
<method name="RemoteCommand">
<arg name="command" type="s" direction="in" />
@@ -22,6 +25,7 @@
<property name="Statistics" type="b" access="readwrite" />
<property name="ElapsedTime" type="b" access="readwrite" />
<property name="DeltaTime" type="b" access="readwrite" />
+ <property name="FileAndLine" type="b" access="readwrite" />
</interface>
<interface name="org.gnome.ShairportSync.RemoteControl">
<method name='FastForward'/>
diff --git a/player.c b/player.c
index 1798281..21eb432 100644
--- a/player.c
+++ b/player.c
@@ -122,7 +122,9 @@ static void ab_resync(rtsp_conn_info *conn) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++) {
conn->audio_buffer[i].ready = 0;
- conn->audio_buffer[i].resend_level = 0;
+ conn->audio_buffer[i].resend_request_number = 0;
+ conn->audio_buffer[i].resend_time = 0; // this is either zero or the time the last resend was requested.
+ conn->audio_buffer[i].initialisation_time = 0; // this is either the time the packet was received or the time it was noticed the packet was missing.
conn->audio_buffer[i].sequence_number = 0;
}
conn->ab_synced = 0;
@@ -130,6 +132,36 @@ static void ab_resync(rtsp_conn_info *conn) {
conn->ab_buffering = 1;
}
+// given starting and ending points as unsigned 16-bit integers running modulo 2^16, returns the
+// position of x in the interval in *pos
+// returns true if x is actually within the buffer
+
+int position_in_modulo_uint16_t_buffer(uint16_t x, uint16_t start, uint16_t end, uint16_t *pos) {
+ int response = 0; // not in the buffer
+ if (start <= end) {
+ if (x < start) { // this means that it's up around the wrap
+ if (pos)
+ *pos = UINT16_MAX - start + 1 + x;
+ } else {
+ if (pos)
+ *pos = x - start;
+ if (x < end)
+ response = 1;
+ }
+ } else if ((x >= start)) { // && (x <= UINT16_MAX)) { // always true
+ response = 1;
+ if (pos)
+ *pos = x - start;
+ } else {
+ if (pos)
+ *pos = UINT16_MAX - start + 1 + x;
+ if (x < end) {
+ response = 1;
+ }
+ }
+ return response;
+}
+
// given starting and ending points as unsigned 32-bit integers running modulo 2^32, returns the
// position of x in the interval in *pos
// returns true if x is actually within the buffer
@@ -146,7 +178,7 @@ int position_in_modulo_uint32_t_buffer(uint32_t x, uint32_t start, uint32_t end,
if (x < end)
response = 1;
}
- } else if ((x >= start) && (x <= UINT32_MAX)) {
+ } else if ((x >= start)) { // && (x <= UINT32_MAX)) { // always true
response = 1;
if (pos)
*pos = x - start;
@@ -170,7 +202,7 @@ static inline seq_t SUCCESSOR(seq_t x) {
// used in seq_diff and seq_order
-// anything with ORDINATE in it must be proctected by the ab_mutex
+// anything with ORDINATE in it must be protected by the ab_mutex
static inline int32_t ORDINATE(seq_t x, seq_t base) {
int32_t p = x; // int32_t from seq_t, i.e. uint16_t, so okay
int32_t q = base; // int32_t from seq_t, i.e. uint16_t, so okay
@@ -432,6 +464,8 @@ static void free_audio_buffers(rtsp_conn_info *conn) {
free(conn->audio_buffer[i].data);
}
+int first_possibly_missing_frame = -1;
+
void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, int len,
rtsp_conn_info *conn) {
@@ -444,11 +478,11 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
}
debug_mutex_lock(&conn->ab_mutex, 30000, 0);
+ uint64_t time_now = get_absolute_time_in_fp();
conn->packet_count++;
conn->packet_count_since_flush++;
- conn->time_of_last_audio_packet = get_absolute_time_in_fp();
+ conn->time_of_last_audio_packet = time_now;
if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
-
if ((conn->flush_rtp_timestamp != 0) && (actual_timestamp != conn->flush_rtp_timestamp) &&
(modulo_32_offset(actual_timestamp, conn->flush_rtp_timestamp) <
conn->input_rate * 10)) { // if it's less than 10 seconds
@@ -459,19 +493,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
conn->initial_reference_time = 0;
conn->initial_reference_timestamp = 0;
} else {
- /*
- if ((conn->flush_rtp_timestamp != 0) &&
- (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) > conn->input_rate / 5)
- &&
- (modulo_32_offset(conn->flush_rtp_timestamp, actual_timestamp) < conn->input_rate)) {
- // between 0.2 and 1 second
- debug(2, "Dropping flush request in player_put_packet");
- conn->flush_rtp_timestamp = 0;
- }
- */
-
abuf_t *abuf = 0;
-
if (!conn->ab_synced) {
// if this is the first packet...
debug(3, "syncing to seqno %u.", seqno);
@@ -479,42 +501,20 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
conn->ab_read = seqno;
conn->ab_synced = 1;
}
-
- // here, we should check for missing frames
- int resend_interval = (((250 * 44100) / 352) / 1000); // approximately 250 ms intervals
- const int number_of_resend_attempts = 8;
- int latency_based_resend_interval =
- (conn->latency) / (number_of_resend_attempts * conn->max_frames_per_packet);
- if (latency_based_resend_interval > resend_interval)
- resend_interval = latency_based_resend_interval;
-
- if (conn->resend_interval != resend_interval) {
- debug(2, "Resend interval for latency of %u frames is %d frames.", conn->latency,
- resend_interval);
- conn->resend_interval = resend_interval;
- }
-
if (conn->ab_write ==
seqno) { // if this is the expected packet (which could be the first packet...)
- uint64_t reception_time = get_absolute_time_in_fp();
if (conn->input_frame_rate_starting_point_is_valid == 0) {
if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) {
- conn->frames_inward_measurement_start_time = reception_time;
+ conn->frames_inward_measurement_start_time = time_now;
conn->frames_inward_frames_received_at_measurement_start_time = actual_timestamp;
conn->input_frame_rate_starting_point_is_valid = 1; // valid now
}
}
-
- conn->frames_inward_measurement_time = reception_time;
+ conn->frames_inward_measurement_time = time_now;
conn->frames_inward_frames_received_at_measurement_time = actual_timestamp;
-
abuf = conn->audio_buffer + BUFIDX(seqno);
conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space
} else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
- // if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8)
- // debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and
- // seqno:
- // %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno);
int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read);
if (gap <= 0)
debug(1, "Unexpected gap size: %d.", gap);
@@ -522,8 +522,10 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
for (i = 0; i < gap; i++) {
abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
abuf->ready = 0; // to be sure, to be sure
- abuf->resend_level = 0;
- // abuf->timestamp = 0;
+ abuf->resend_request_number = 0;
+ abuf->initialisation_time = time_now; // this represents when the packet was noticed to be missing
+ abuf->status = 1 << 0; // signifying missing
+ abuf->resend_time = 0;
abuf->given_timestamp = 0;
abuf->sequence_number = 0;
}
@@ -532,80 +534,116 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
// rtp_request_resend(ab_write, gap);
// resend_requests++;
conn->ab_write = SUCCESSOR(seqno);
- } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played
+ } else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // older than expected but not too late
conn->late_packets++;
abuf = conn->audio_buffer + BUFIDX(seqno);
- /*
- if (abuf->ready)
- debug(1,"Late apparently duplicate packet received that is %d packets
- late.",seq_diff(seqno, conn->ab_write, conn->ab_read));
- else
- debug(1,"Late packet received that is %d packets late.",seq_diff(seqno,
- conn->ab_write, conn->ab_read));
- */
} else { // too late.
-
- // debug(1,"Too late packet received that is %d packets late.",seq_diff(seqno,
- // conn->ab_write, conn->ab_read));
conn->too_late_packets++;
}
- // pthread_mutex_unlock(&ab_mutex);
if (abuf) {
int datalen = conn->max_frames_per_packet;
+ abuf->initialisation_time = time_now;
+ abuf->resend_time = 0;
if (audio_packet_decode(abuf->data, &datalen, data, len, conn) == 0) {
abuf->ready = 1;
+ abuf->status = 0; // signifying that it was received
abuf->length = datalen;
- // abuf->timestamp = ltimestamp;
abuf->given_timestamp = actual_timestamp;
abuf->sequence_number = seqno;
} else {
debug(1, "Bad audio packet detected and discarded.");
abuf->ready = 0;
- abuf->resend_level = 0;
- // abuf->timestamp = 0;
+ abuf->status = 1 << 1; // bad packet, discarded
+ abuf->resend_request_number = 0;
abuf->given_timestamp = 0;
abuf->sequence_number = 0;
}
}
- // pthread_mutex_lock(&ab_mutex);
int rc = pthread_cond_signal(&conn->flowcontrol);
if (rc)
debug(1, "Error signalling flowcontrol.");
-
- // if it's at the expected time, do a look back for missing packets
- // but release the ab_mutex when doing a resend
- if (!conn->ab_buffering) {
- int j;
- for (j = 1; j <= number_of_resend_attempts; j++) {
- // check j times, after a short period of has elapsed, assuming 352 frames per packet
- // the higher the step_exponent, the less it will try. 1 means it will try very
- // hard. 2.0 seems good.
- float step_exponent = 2.0;
- int back_step = (int)(resend_interval * pow(j, step_exponent));
- int k;
- for (k = -1; k <= 1; k++) {
- if ((back_step + k) <
- seq_diff(conn->ab_read, conn->ab_write,
- conn->ab_read)) { // if it's within the range of frames in use...
- int item_to_check = (conn->ab_write - (back_step + k)) & 0xffff;
- seq_t next = item_to_check;
- abuf_t *check_buf = conn->audio_buffer + BUFIDX(next);
- if ((!check_buf->ready) &&
- (check_buf->resend_level <
- j)) { // prevent multiple requests from the same level of lookback
- check_buf->resend_level = j;
- if (config.disable_resend_requests == 0) {
- debug_mutex_unlock(&conn->ab_mutex, 3);
- rtp_request_resend(next, 1, conn);
- conn->resend_requests++;
- debug_mutex_lock(&conn->ab_mutex, 20000, 1);
- }
+
+ // resend checks
+ {
+ uint64_t minimum_wait_time = (uint64_t)(config.resend_control_first_check_time * (uint64_t)0x100000000);
+ uint64_t resend_repeat_interval = (uint64_t)(config.resend_control_check_interval_time * (uint64_t)0x100000000);
+ uint64_t minimum_remaining_time = (uint64_t)((config.resend_control_last_check_time + config.audio_backend_buffer_desired_length)* (uint64_t)0x100000000);
+ uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)0x100000000);
+ latency_time = latency_time / (uint64_t)conn->input_rate;
+
+ int x; // this is the first frame to be checked
+ // if we detected a first empty frame before and if it's still in the buffer!
+ if ((first_possibly_missing_frame >= 0) && (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read, conn->ab_write, NULL))) {
+ x = first_possibly_missing_frame;
+ } else {
+ x = conn->ab_read;
+ }
+
+ first_possibly_missing_frame = -1; // has not been set
+
+ int missing_frame_run_count = 0;
+ int start_of_missing_frame_run = -1;
+ int number_of_missing_frames = 0;
+ while (x != conn->ab_write) {
+ abuf_t *check_buf = conn->audio_buffer + BUFIDX(x);
+ if (!check_buf->ready) {
+ if (first_possibly_missing_frame < 0)
+ first_possibly_missing_frame = x;
+ number_of_missing_frames++;
+ // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%" PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x, check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time);
+ int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) || ((check_buf->initialisation_time - (time_now - latency_time)) < minimum_remaining_time));
+ int too_early = ((time_now - check_buf->initialisation_time) < minimum_wait_time);
+ int too_soon_after_last_request = ((check_buf->resend_time != 0) && ((time_now - check_buf->resend_time) < resend_repeat_interval)); // time_now can never be less than the time_tag
+
+ if (too_late)
+ check_buf->status |= 1<<2; // too late
+ else
+ check_buf->status &= 0xFF-(1<<2); // not too late
+ if (too_early)
+ check_buf->status |= 1<<3; // too early
+ else
+ check_buf->status &= 0xFF-(1<<3); // not too early
+ if (too_soon_after_last_request)
+ check_buf->status |= 1<<4; // too soon after last request
+ else
+ check_buf->status &= 0xFF-(1<<4); // not too soon after last request
+
+ if ((!too_soon_after_last_request) && (!too_late) && (!too_early)){
+ if (start_of_missing_frame_run == -1) {
+ start_of_missing_frame_run = x;
+ missing_frame_run_count = 1;
+ } else {
+ missing_frame_run_count++;
}
+ check_buf->resend_time = time_now; // setting the time to now because we are definitely going to take action
+ check_buf->resend_request_number++;
+ debug(3,"Frame %d is missing with ab_read of %u and ab_write of %u.", x, conn->ab_read, conn->ab_write);
}
+ // if (too_late) {
+ // debug(1,"too late to get missing frame %u.", x);
+ // }
+ }
+ //if (number_of_missing_frames != 0)
+ // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame);
+ x = (x + 1) & 0xffff;
+ if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) {
+ // send a resend request
+ if (missing_frame_run_count > 1)
+ debug(2,"request resend of %d packets starting at seqno %u.", missing_frame_run_count, start_of_missing_frame_run);
+ if (config.disable_resend_requests == 0) {
+ debug_mutex_unlock(&conn->ab_mutex, 3);
+ rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn);
+ debug_mutex_lock(&conn->ab_mutex, 20000, 1);
+ conn->resend_requests++;
+ }
+ start_of_missing_frame_run = -1;
+ missing_frame_run_count = 0;
}
}
+ if (number_of_missing_frames == 0)
+ first_possibly_missing_frame = conn->ab_write;
}
}
}
@@ -697,10 +735,7 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t
break;
}
dither_mask -= 1;
- // int64_t r = r64i();
- int64_t r = ranarray64i(); // use an array of precalculated pseudorandom numbers rather than
- // calculating them on the fly. Should be easier on low-powered
- // processors
+ int64_t r = r64i();
int64_t tpdf = (r & dither_mask) - (conn->previous_random_number & dither_mask);
conn->previous_random_number = r;
@@ -936,7 +971,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
"timestamp: %" PRIu32 ".",
curframe->sequence_number, curframe->given_timestamp, conn->flush_rtp_timestamp);
curframe->ready = 0;
- curframe->resend_level = 0;
+ curframe->resend_request_number = 0;
curframe = NULL; // this will be returned and will cause the loop to go around again
conn->initial_reference_time = 0;
conn->initial_reference_timestamp = 0;
@@ -1333,7 +1368,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
curframe->given_timestamp = 0; // indicate a silent frame should be substituted
}
curframe->ready = 0;
- curframe->resend_level = 0;
}
conn->ab_read = SUCCESSOR(conn->ab_read);
pthread_cleanup_pop(1);
@@ -1921,7 +1955,7 @@ void *player_thread_func(void *arg) {
while (1) {
pthread_testcancel(); // allow a pthread_cancel request to take effect.
abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s), but it's not
- // guaranteed that they'll aways be executed
+ // guaranteed that they'll always be executed
if (inframe) {
inbuf = inframe->data;
inbuflength = inframe->length;
@@ -1931,8 +1965,8 @@ void *player_thread_func(void *arg) {
// debug(3, "Play frame %d.", play_number);
conn->play_number_after_flush++;
if (inframe->given_timestamp == 0) {
- debug(3, "Player has supplied a silent frame, (possibly frame %u) for play number %d.",
- SUCCESSOR(conn->last_seqno_read), play_number);
+ debug(1, "Player has supplied a silent frame, (possibly frame %u) for play number %d, status 0x%X after %u resend requests.",
+ SUCCESSOR(conn->last_seqno_read), play_number, inframe->status, inframe->resend_request_number);
conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
0xffff); // manage the packet out of sequence minder
@@ -1942,7 +1976,7 @@ void *player_thread_func(void *arg) {
debug(1, "Failed to allocate memory for a silent frame silence buffer.");
} else {
// the player may change the contents of the buffer, so it has to be zeroed each time;
- // might as well malloc and freee it locally
+ // might as well malloc and free it locally
conn->previous_random_number = generate_zero_frames(
silence, conn->max_frames_per_packet * conn->output_sample_ratio,
config.output_format, conn->enable_dither, conn->previous_random_number);
@@ -1964,7 +1998,7 @@ void *player_thread_func(void *arg) {
debug(1, "Failed to allocate memory for a flush silence buffer.");
} else {
// the player may change the contents of the buffer, so it has to be zeroed each time;
- // might as well malloc and freee it locally
+ // might as well malloc and free it locally
conn->previous_random_number = generate_zero_frames(
silence, conn->max_frames_per_packet * conn->output_sample_ratio,
config.output_format, conn->enable_dither, conn->previous_random_number);
@@ -2331,9 +2365,27 @@ void *player_thread_func(void *arg) {
amount_to_stuff = 0; // no stuffing if it's been disabled
// Apply DSP here
- if (config.loudness
+
+ // check the state of loudness and convolution flags here and don't change them for the frame
+
+ int do_loudness = config.loudness;
+
+
+#ifdef CONFIG_CONVOLUTION
+ int do_convolution = 0;
+ if ((config.convolution) && (config.convolver_valid))
+ do_convolution = 1;
+
+ // we will apply the convolution gain if convolution is enabled, even if there is no valid convolution happening
+
+ int convolution_is_enabled = 0;
+ if (config.convolution)
+ convolution_is_enabled = 1;
+#endif
+
+ if (do_loudness
#ifdef CONFIG_CONVOLUTION
- || config.convolution
+ || convolution_is_enabled
#endif
) {
int32_t *tbuf32 = (int32_t *)conn->tbuf;
@@ -2349,10 +2401,11 @@ void *player_thread_func(void *arg) {
#ifdef CONFIG_CONVOLUTION
// Apply convolution
- if (config.convolution) {
+ if (do_convolution) {
convolver_process_l(fbuf_l, inbuflength);
convolver_process_r(fbuf_r, inbuflength);
-
+ }
+ if (convolution_is_enabled) {
float gain = pow(10.0, config.convolution_gain / 20.0);
for (i = 0; i < inbuflength; ++i) {
fbuf_l[i] *= gain;
@@ -2361,7 +2414,7 @@ void *player_thread_func(void *arg) {
}
#endif
- if (config.loudness) {
+ if (do_loudness) {
// Apply volume and loudness
// Volume must be applied here because the loudness filter will increase the
// signal level and it would saturate the int32_t otherwise
@@ -2475,6 +2528,8 @@ void *player_thread_func(void *arg) {
// mark the frame as finished
inframe->given_timestamp = 0;
inframe->sequence_number = 0;
+ inframe->resend_time = 0;
+ inframe->initialisation_time = 0;
// update the watchdog
if ((config.dont_check_timeout == 0) && (config.timeout != 0)) {
@@ -2692,7 +2747,6 @@ void *player_thread_func(void *arg) {
void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) {
debug_mutex_lock(&conn->volume_control_mutex, 5000, 1);
- debug(2, "player_volume_without_notification %f", airplay_volume);
// first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw
// attenuation on top
@@ -2704,6 +2758,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
int32_t hw_max_db = 0, hw_min_db = 0; // zeroed to quieten an incorrect uninitialised warning
int32_t sw_max_db = 0, sw_min_db = -9630;
+
if (config.output->parameters) {
volume_mode = vol_hw_only;
audio_parameters audio_information;
@@ -2866,27 +2921,32 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
conn->fix_volume = temp_fix_volume;
- if (config.loudness)
- loudness_set_volume(software_attenuation / 100);
+ // if (config.loudness)
+ loudness_set_volume(software_attenuation / 100);
}
if (config.logOutputLevel) {
inform("Output Level set to: %.2f dB.", scaled_attenuation / 100.0);
}
-
+
#ifdef CONFIG_METADATA
+ // here, send the 'pvol' metadata message when the airplay volume information
+ // is being used by shairport sync to control the output volume
char *dv = malloc(128); // will be freed in the metadata thread
if (dv) {
memset(dv, 0, 128);
- if (config.ignore_volume_control == 1)
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
- else
+ if (volume_mode == vol_both) {
+ // normalise the maximum output to the hardware device's max output
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, (scaled_attenuation - max_db + hw_max_db) / 100.0,
+ (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0);
+ } else {
snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
min_db / 100.0, max_db / 100.0);
+ }
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
}
#endif
- // here, store the volume for possible use in the future
+
if (config.output->mute)
config.output->mute(0);
@@ -2898,6 +2958,22 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
volume_mode, airplay_volume, software_attenuation, hardware_attenuation);
}
}
+
+#ifdef CONFIG_METADATA
+ else {
+ // here, send the 'pvol' metadata message when the airplay volume information
+ // is being used by shairport sync to control the output volume
+ char *dv = malloc(128); // will be freed in the metadata thread
+ if (dv) {
+ memset(dv, 0, 128);
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0);
+ send_ssnc_metadata('pvol', dv, strlen(dv), 1);
+ }
+ }
+#endif
+
+
+ // here, store the volume for possible use in the future
config.airplay_volume = airplay_volume;
debug_mutex_unlock(&conn->volume_control_mutex, 3);
}
diff --git a/player.h b/player.h
index cb197e4..9246f44 100644
--- a/player.h
+++ b/player.h
@@ -38,12 +38,14 @@ typedef struct time_ping_record {
typedef uint16_t seq_t;
typedef struct audio_buffer_entry { // decoded audio packets
- int ready;
- int resend_level;
- // int64_t timestamp;
+ uint8_t ready;
+ uint8_t status; // flags
+ uint16_t resend_request_number;
+ signed short *data;
seq_t sequence_number;
+ uint64_t initialisation_time; // the time the packet was added or the time it was noticed the packet was missing
+ uint64_t resend_time; // time of last resend request or zero
uint32_t given_timestamp; // for debugging and checking
- signed short *data;
int length; // the length of the decoded data
} abuf_t;
@@ -55,7 +57,7 @@ typedef struct audio_buffer_entry { // decoded audio packets
// Resend requests will be spaced out evenly in the latency period, subject to a minimum interval of
// about 0.25 seconds.
// Each buffer occupies 352*4 bytes plus about, say, 64 bytes of overhead in various places, say
-// rougly 1,500 bytes per buffer.
+// roughly 1,500 bytes per buffer.
// Thus, 2048 buffers will occupy about 3 megabytes -- no big deal in a normal machine but maybe a
// problem in an embedded device.
diff --git a/rtp.c b/rtp.c
index 2e1e7f9..6a90a5c 100644
--- a/rtp.c
+++ b/rtp.c
@@ -1218,7 +1218,8 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
}
#endif
uint64_t time_of_sending_fp = get_absolute_time_in_fp();
- uint64_t resend_error_backoff_time = (uint64_t)1 << (32 - 4); // one sixteenth of a second
+ uint64_t resend_error_backoff_time = (uint64_t)1000000 * 0.3; // 0.3 seconds
+ resend_error_backoff_time = (resend_error_backoff_time << 32) / 1000000;
if ((conn->rtp_time_of_last_resend_request_error_fp == 0) ||
((time_of_sending_fp - conn->rtp_time_of_last_resend_request_error_fp) >
resend_error_backoff_time)) {
@@ -1238,8 +1239,7 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
(struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) {
char em[1024];
strerror_r(errno, em, sizeof(em));
- debug(1, "Error %d using sendto to an audio socket: \"%s\". Backing off for 1/16th of a "
- "second.",
+ debug(2, "Error %d using sendto to request a resend: \"%s\".",
errno, em);
conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
} else {
@@ -1249,12 +1249,12 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
} else {
debug(
3,
- "Dropping resend request packet to simulate a bad network. Backing off for 1/16th of a "
+ "Dropping resend request packet to simulate a bad network. Backing off for 0.3 "
"second.");
conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
}
} else {
- debug(3, "Backing off sending resend requests due to a previous send-to error");
+ debug(1, "Suppressing a resend request due to a resend sendto error in the last 0.3 seconds.");
}
} else {
// if (!request_sent) {
diff --git a/rtsp.c b/rtsp.c
index 4a72250..757bcd3 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -392,8 +392,9 @@ static char *nextline(char *in, int inbuf) {
if (*in == '\r') {
*in++ = 0;
out = in;
+ inbuf--;
}
- if (*in == '\n') {
+ if ((*in == '\n') && (inbuf)) {
*in++ = 0;
out = in;
}
@@ -569,20 +570,22 @@ int msg_handle_line(rtsp_message **pmsg, char *line) {
}
fail:
+ debug(3,"msg_handle_line fail");
msg_free(pmsg);
+ *pmsg = NULL;
return 0;
}
enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) {
- *the_packet = NULL; // need this for erro handling
+ *the_packet = NULL; // need this for error handling
enum rtsp_read_request_response reply = rtsp_read_request_response_ok;
ssize_t buflen = 4096;
int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay
char *buf = malloc(buflen + 1); // add a NUL at the end
if (!buf) {
- warn("rtsp_read_request: can't get a buffer.");
+ warn("Connection %d: rtsp_read_request: can't get a buffer.", conn->connection_number);
return (rtsp_read_request_response_error);
}
pthread_cleanup_push(malloc_cleanup, buf);
@@ -591,17 +594,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
int msg_size = -1;
while (msg_size < 0) {
- /*
-fd_set readfds;
-FD_ZERO(&readfds);
-FD_SET(conn->fd, &readfds);
-do {
- memory_barrier();
-} while (conn->stop == 0 &&
- pselect(conn->fd + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
-*/
if (conn->stop != 0) {
- debug(3, "RTSP conversation thread %d shutdown requested.", conn->connection_number);
+ debug(3, "Connection %d: shutdown requested.", conn->connection_number);
reply = rtsp_read_request_response_immediate_shutdown_requested;
goto shutdown;
}
@@ -610,7 +604,7 @@ do {
if (nread == 0) {
// a blocking read that returns zero means eof -- implies connection closed
- debug(3, "RTSP conversation thread %d -- connection closed.", conn->connection_number);
+ debug(3, "Connection %d: -- connection closed.", conn->connection_number);
reply = rtsp_read_request_response_channel_closed;
goto shutdown;
}
@@ -619,7 +613,7 @@ do {
if (errno == EINTR)
continue;
if (errno == EAGAIN) {
- debug(1, "Getting Error 11 -- EAGAIN from a blocking read!");
+ debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!", conn->connection_number);
continue;
}
if (errno != ECONNRESET) {
@@ -631,6 +625,17 @@ do {
reply = rtsp_read_request_response_read_error;
goto shutdown;
}
+
+/* // this outputs the message received
+ {
+ void *pt = malloc(nread+1);
+ memset(pt, 0, nread+1);
+ memcpy(pt, buf + inbuf, nread);
+ debug(1, "Incoming string on port: \"%s\"",pt);
+ free(pt);
+ }
+*/
+
inbuf += nread;
char *next;
@@ -638,7 +643,7 @@ do {
msg_size = msg_handle_line(the_packet, buf);
if (!(*the_packet)) {
- warn("no RTSP header received");
+ debug(1,"Connection %d: rtsp_read_request can't find an RTSP header.", conn->connection_number);
reply = rtsp_read_request_response_bad_packet;
goto shutdown;
}
@@ -652,7 +657,7 @@ do {
if (msg_size > buflen) {
buf = realloc(buf, msg_size + 1);
if (!buf) {
- warn("too much content");
+ warn("Connection %d: too much content.", conn->connection_number);
reply = rtsp_read_request_response_error;
goto shutdown;
}
@@ -684,16 +689,6 @@ do {
}
}
- /*
- fd_set readfds;
- FD_ZERO(&readfds);
- FD_SET(conn->fd, &readfds);
- do {
- memory_barrier();
- } while (conn->stop == 0 &&
- pselect(conn->fd + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
- */
-
if (conn->stop != 0) {
debug(1, "RTSP shutdown requested.");
reply = rtsp_read_request_response_immediate_shutdown_requested;
@@ -711,8 +706,17 @@ do {
if (nread < 0) {
if (errno == EINTR)
continue;
- perror("read failure");
- reply = rtsp_read_request_response_error;
+ if (errno == EAGAIN) {
+ debug(1, "Getting Error 11 -- EAGAIN from a blocking read!");
+ continue;
+ }
+ if (errno != ECONNRESET) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".",
+ conn->connection_number, errno, (char *)errorstring);
+ }
+ reply = rtsp_read_request_response_read_error;
goto shutdown;
}
inbuf += nread;
@@ -1137,7 +1141,7 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
// Specifically, it's the "X-Apple-Client-Name" string
// 'snua' -- A "user agent" -- e.g. "iTunes/12..." -- has opened a play
// session. Specifically, it's the "User-Agent" string
-// The next two two tokens are to facilitiate remote control of the source.
+// The next two two tokens are to facilitate remote control of the source.
// There is some information at http://nto.github.io/AirPlay.html about
// remote control of the source.
//
@@ -1866,10 +1870,28 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
if (pfmtp) {
conn->stream.type = ast_apple_lossless;
debug(3, "An ALAC stream has been detected.");
- unsigned int i;
- for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
- conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t"));
- // here we should check the sanity ot the fmtp values
+
+ // Set reasonable connection defaults
+ conn->stream.fmtp[0] = 96;
+ conn->stream.fmtp[1] = 352;
+ conn->stream.fmtp[2] = 0;
+ conn->stream.fmtp[3] = 16;
+ conn->stream.fmtp[4] = 40;
+ conn->stream.fmtp[5] = 10;
+ conn->stream.fmtp[6] = 14;
+ conn->stream.fmtp[7] = 2;
+ conn->stream.fmtp[8] = 255;
+ conn->stream.fmtp[9] = 0;
+ conn->stream.fmtp[10] = 0;
+ conn->stream.fmtp[11] = 44100;
+
+ unsigned int i = 0;
+ unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]);
+ char* found;
+ while ((found = strsep(&pfmtp, " \t")) != NULL && i < max_param) {
+ conn->stream.fmtp[i++] = atoi(found);
+ }
+ // here we should check the sanity of the fmtp values
// for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
// debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]);
@@ -2387,16 +2409,6 @@ static void *rtsp_conversation_thread_func(void *pconn) {
debug(debug_level, "Connection %d: RTSP Response:", conn->connection_number);
debug_print_msg_headers(debug_level, resp);
- /*
- fd_set writefds;
- FD_ZERO(&writefds);
- FD_SET(conn->fd, &writefds);
- do {
- memory_barrier();
- } while (conn->stop == 0 &&
- pselect(conn->fd + 1, NULL, &writefds, NULL, NULL, &pselect_sigset) <= 0);
- */
-
if (conn->stop == 0) {
int err = msg_write_response(conn->fd, resp);
if (err) {
@@ -2448,6 +2460,17 @@ static void *rtsp_conversation_thread_func(void *pconn) {
} else {
tstop = 1;
}
+ } else if (reply == rtsp_read_request_response_bad_packet) {
+ char *response_text = "RTSP/1.0 400 Bad Request\r\nServer: AirTunes/105.1\r\n\r\n";
+ ssize_t reply = write(conn->fd, response_text, strlen(response_text));
+ if (reply == -1) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno, (char *)errorstring);
+ } else if (reply != (ssize_t)strlen(response_text)) {
+ debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.", strlen(response_text),
+ reply);
+ }
} else {
debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.",
conn->connection_number, (int)reply);
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 5e6089a..bec0d4f 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -51,6 +51,10 @@ general =
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
// mpris_service_bus = "system"; // The Shairport Sync mpris interface, if selected at compilation, will appear
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
+// resend_control_first_check_time = 0.10; // Use this optional advanced setting to set the wait time in seconds before deciding a packet is missing.
+// resend_control_check_interval_time = 0.25; // Use this optional advanced setting to set the time in seconds between requests for a missing packet.
+// resend_control_last_check_time = 0.10; // Use this optional advanced setting to set the latest time, in seconds, by which the last check should be done before the estimated time of a missing packet's transfer to the output buffer.
+//
};
// Advanced parameters for controlling how Shairport Sync stays active and how it runs a session
@@ -187,6 +191,7 @@ metadata =
{
// enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
// include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
+// cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems
// pipe_name = "/tmp/shairport-sync-metadata";
// pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up
// socket_address = "226.0.0.1"; // if set to a host name or IP address, UDP packets containing metadata will be sent to this address. May be a multicast address. "socket-port" must be non-zero and "enabled" must be set to yes"
@@ -216,7 +221,7 @@ mqtt =
// publish_cover = "no"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker
// enable_remote = "no"; //whether to remote control via MQTT. RC is available under `topic`/remote.
// Available commands are "command", "beginff", "beginrew", "mutetoggle", "nextitem", "previtem", "pause", "playpause", "play", "stop", "playresume", "shuffle_songs", "volumedown", "volumeup"
-}
+};
// Diagnostic settings. These are for diagnostic and debugging only. Normally you should leave them commented out
diagnostics =
@@ -224,7 +229,9 @@ diagnostics =
// disable_resend_requests = "no"; // set this to yes to stop Shairport Sync from requesting the retransmission of missing packets. Default is "no".
// statistics = "no"; // set to "yes" to print statistics in the log
// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose.
+// log_show_file_and_line = "yes"; // set this to yes if you want the file and line number of the message source in the log file
// log_show_time_since_startup = "no"; // set this to yes if you want the time since startup in the debug message -- seconds down to nanoseconds
// log_show_time_since_last_message = "yes"; // set this to yes if you want the time since the last debug message in the debug message -- seconds down to nanoseconds
// drop_this_fraction_of_audio_packets = 0.0; // use this to simulate a noisy network where this fraction of UDP packets are lost in transmission. E.g. a value of 0.001 would mean an average of 0.1% of packets are lost, which is actually quite a high figure.
+// retain_cover_art = "no"; // artwork is deleted when its corresponding track has been played. Set this to "yes" to retain all artwork permanently. Warning -- your directory might fill up.
};
diff --git a/scripts/shairport-sync.in b/scripts/shairport-sync.in
index f229f19..017de07 100755
--- a/scripts/shairport-sync.in
+++ b/scripts/shairport-sync.in
@@ -47,7 +47,7 @@ do_start()
{
# Return
# 0 if daemon has been started
- # 1 if PID directory didn't exist and couldn't be created with approproate permission
+ # 1 if PID directory didn't exist and couldn't be created with appropriate permission
# 2 if daemon was already running
# 3 if daemon could not be started
[ -e /var/run/shairport-sync ] || ( mkdir -p /var/run/shairport-sync && chown shairport-sync:shairport-sync /var/run/shairport-sync ) || return 1
diff --git a/shairport-sync-dbus-test-client.c b/shairport-sync-dbus-test-client.c
index 4d5703a..bf968c4 100644
--- a/shairport-sync-dbus-test-client.c
+++ b/shairport-sync-dbus-test-client.c
@@ -40,11 +40,11 @@ void on_properties_changed(__attribute__((unused)) GDBusProxy *proxy, GVariant *
}
}
-void notify_loudness_filter_active_callback(ShairportSync *proxy,
+void notify_loudness_callback(ShairportSync *proxy,
__attribute__((unused)) gpointer user_data) {
- // printf("\"notify_loudness_filter_active_callback\" called with a gpointer of
+ // printf("\"notify_loudness_callback\" called with a gpointer of
// %lx.\n",(int64_t)user_data);
- gboolean ebl = shairport_sync_get_loudness_filter_active(proxy);
+ gboolean ebl = shairport_sync_get_loudness(proxy);
if (ebl == TRUE)
printf("Client reports loudness is enabled.\n");
else
@@ -175,6 +175,15 @@ int main(int argc, char *argv[]) {
sleep(5);
shairport_sync_advanced_remote_control_call_set_volume(
SHAIRPORT_SYNC_ADVANCED_REMOTE_CONTROL(proxy4), 60, NULL, NULL, 0);
+
+ sleep(5);
+ g_print("Volume up for five seconds...\n");
+ shairport_sync_remote_control_call_volume_up(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, NULL);
+
+ sleep(5);
+ g_print("Volume down\n");
+ shairport_sync_remote_control_call_volume_down(SHAIRPORT_SYNC_REMOTE_CONTROL(proxy3), NULL, NULL, NULL);
+
/*
// sleep(1);
shairport_sync_set_loudness_filter_active(SHAIRPORT_SYNC(proxy), TRUE);
diff --git a/shairport-sync.spec b/shairport-sync.spec
index dd85fe8..aec7a48 100644
--- a/shairport-sync.spec
+++ b/shairport-sync.spec
@@ -1,5 +1,5 @@
Name: shairport-sync
-Version: 3.3.2
+Version: 3.3.5
Release: 1%{?dist}
Summary: AirTunes emulator. Multi-Room with Audio Synchronisation
# MIT licensed except for tinysvcmdns under BSD,
@@ -66,6 +66,10 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
%license LICENSES
%changelog
+* Wed Nov 13 2019 Mike Brady <mikebrady@eircom.net) 3.3.5
+- Bug fixes and additions to the D-Bus interface.
+* Mon Oct 28 2019 Mike Brady <mikebrady@eircom.net) 3.3.4
+- Bug fixes and minor enhancements.
* Fri Jul 26 2019 Mike Brady <mikebrady@eircom.net) 3.3.2
- Minor bug fixes.
* Wed Jun 05 2019 Mike Brady <mikebrady@eircom.net) 3.3.1
@@ -81,12 +85,12 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
* Thu Dec 21 2017 Mike Brady <mikebrady@eircom.net> 3.1.7
- Bug fix for unexpectedly resuming play at full volume from iOS 11.2 and macOS 10.3.2.
* Mon Dec 11 2017 Mike Brady <mikebrady@eircom.net> 3.1.5
-- Bug fixes and better compatability with iOS 11.2 and mac OS 10.13.2.
+- Bug fixes and better compatibility with iOS 11.2 and mac OS 10.13.2.
- Better AirPlay synchronisation.
* Wed Sep 13 2017 Bill Peck <bpeck@redhat.com> 3.1.2-1
- New upstream release
- The default value for the alsa setting mute_using_playback_switch has
- been changed to "no" for compatability with other audio players on the
+ been changed to "no" for compatibility with other audio players on the
same machine. Because of this you may need to unmute your audio device
if you are upgrading from an older release.
- Fixed bugs that made Shairport Sync drop out or become unavailable when
diff --git a/shairport.c b/shairport.c
index 6d74cae..d2a1a49 100644
--- a/shairport.c
+++ b/shairport.c
@@ -106,6 +106,7 @@
#ifdef CONFIG_LIBDAEMON
pid_t pid;
+int this_is_the_daemon_process = 0;
#endif
int killOption = 0;
@@ -116,42 +117,6 @@ int daemonisewithout = 0;
char configuration_file_path[4096 + 1];
char actual_configuration_file_path[4096 + 1];
-static void sig_ignore(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar,
- __attribute__((unused)) void *baz) {}
-static void sig_shutdown(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar,
- __attribute__((unused)) void *baz) {
- debug(2, "shutdown requested...");
-#ifdef CONFIG_LIBDAEMON
- if (pid == 0) {
- daemon_retval_send(255);
- daemon_pid_file_remove();
- }
-#endif
- exit(EXIT_SUCCESS);
-}
-
-static void sig_child(__attribute__((unused)) int foo, __attribute__((unused)) siginfo_t *bar,
- __attribute__((unused)) void *baz) {
- // wait for child processes to exit
- pid_t pid;
- while ((pid = waitpid((pid_t)-1, 0, WNOHANG)) > 0) {
- }
-}
-
-static void sig_disconnect_audio_output(__attribute__((unused)) int foo,
- __attribute__((unused)) siginfo_t *bar,
- __attribute__((unused)) void *baz) {
- debug(1, "disconnect audio output requested.");
- set_requested_connection_state_to_output(0);
-}
-
-static void sig_connect_audio_output(__attribute__((unused)) int foo,
- __attribute__((unused)) siginfo_t *bar,
- __attribute__((unused)) void *baz) {
- debug(1, "connect audio output requested.");
- set_requested_connection_state_to_output(1);
-}
-
void print_version(void) {
char *version_string = get_version_string();
if (version_string) {
@@ -437,20 +402,21 @@ int parse_options(int argc, char **argv) {
// i.e. when reducing volume, reduce the sw first before reducing the software.
// this is because some hw mixers mute at the bottom of their range, and they don't always advertise
// this fact
+ config.resend_control_first_check_time = 0.10; // wait this many seconds before requesting the resending of a missing packet
+ config.resend_control_check_interval_time = 0.25; // wait this many seconds before again requesting the resending of a missing packet
+ config.resend_control_last_check_time = 0.10; // give up if the packet is still missing this close to when it's needed
#ifdef CONFIG_METADATA_HUB
config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart";
config.scan_interval_when_active =
1; // number of seconds between DACP server scans when playing something
config.scan_interval_when_inactive =
- 3; // number of seconds between DACP server scans playing nothing
+ 1; // number of seconds between DACP server scans when playing nothing
config.scan_max_bad_response_count =
5; // number of successive bad results to ignore before giving up
- config.scan_max_inactive_count =
- (15 * 60) / config.scan_interval_when_inactive; // number of scans to do before stopping if
- // not made active again (15 minutes)
-// config.scan_max_inactive_count = 5; // number of scans to do before stopping if not made active
-// again (15 minutes )
+ //config.scan_max_inactive_count =
+ // (365 * 24 * 60 * 60) / config.scan_interval_when_inactive; // number of scans to do before stopping if
+ // not made active again (not used)
#endif
// config_setting_t *setting;
@@ -613,6 +579,17 @@ int parse_options(int argc, char **argv) {
value);
}
+ /* Get the config.debugger_show_file_and_line in debug messages setting. */
+ if (config_lookup_string(config.cfg, "diagnostics.log_show_file_and_line", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.debugger_show_file_and_line = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.debugger_show_file_and_line = 1;
+ else
+ die("Invalid diagnostics log_show_file_and_line option choice \"%s\". It should be "
+ "\"yes\" or \"no\"");
+ }
+
/* Get the show elapsed time in debug messages setting. */
if (config_lookup_string(config.cfg, "diagnostics.log_show_time_since_startup", &str)) {
if (strcasecmp(str, "no") == 0)
@@ -789,6 +766,44 @@ int parse_options(int argc, char **argv) {
} else
die("Invalid alac_decoder option choice \"%s\". It should be \"hammerton\" or \"apple\"");
}
+
+
+ /* Get the resend control settings. */
+ if (config_lookup_float(config.cfg, "general.resend_control_first_check_time",
+ &dvalue)) {
+ if ((dvalue >= 0.0) && (dvalue <= 3.0))
+ config.resend_control_first_check_time = dvalue;
+ else
+ warn("Invalid general resend_control_first_check_time setting \"%d\". It should "
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_first_check_time);
+ }
+
+ if (config_lookup_float(config.cfg, "general.resend_control_check_interval_time",
+ &dvalue)) {
+ if ((dvalue >= 0.0) && (dvalue <= 3.0))
+ config.resend_control_check_interval_time = dvalue;
+ else
+ warn("Invalid general resend_control_check_interval_time setting \"%d\". It should "
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_check_interval_time);
+ }
+
+ if (config_lookup_float(config.cfg, "general.resend_control_last_check_time",
+ &dvalue)) {
+ if ((dvalue >= 0.0) && (dvalue <= 3.0))
+ config.resend_control_last_check_time = dvalue;
+ else
+ warn("Invalid general resend_control_last_check_time setting \"%d\". It should "
+ "be "
+ "between 0.0 and 3.0, "
+ "inclusive. The setting remains at %f seconds.",
+ dvalue, config.resend_control_last_check_time);
+ }
/* Get the default latency. Deprecated! */
if (config_lookup_int(config.cfg, "latencies.default", &value))
@@ -834,6 +849,22 @@ int parse_options(int argc, char **argv) {
#endif
+#ifdef CONFIG_METADATA_HUB
+ if (config_lookup_string(config.cfg, "metadata.cover_art_cache_directory", &str)) {
+ config.cover_art_cache_dir = (char *)str;
+ }
+
+ if (config_lookup_string(config.cfg, "diagnostics.retain_cover_art", &str)) {
+ if (strcasecmp(str, "no") == 0)
+ config.retain_coverart = 0;
+ else if (strcasecmp(str, "yes") == 0)
+ config.retain_coverart = 1;
+ else
+ die("Invalid metadata retain_cover_art option choice \"%s\". It should be \"yes\" or "
+ "\"no\"");
+ }
+#endif
+
if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) {
config.cmd_start = (char *)str;
}
@@ -931,12 +962,12 @@ int parse_options(int argc, char **argv) {
}
if (config_lookup_string(config.cfg, "dsp.convolution_ir_file", &str)) {
- config.convolution_ir_file = str;
- convolver_init(config.convolution_ir_file, config.convolution_max_length);
+ config.convolution_ir_file = strdup(str);
+ config.convolver_valid = convolver_init(config.convolution_ir_file, config.convolution_max_length);
}
if (config.convolution && config.convolution_ir_file == NULL) {
- die("Convolution enabled but no convolution_ir_file provided");
+ warn("Convolution enabled but no convolution_ir_file provided");
}
#endif
if (config_lookup_string(config.cfg, "dsp.loudness", &str)) {
@@ -1143,7 +1174,7 @@ int parse_options(int argc, char **argv) {
/* Check if we are called with -d or --daemon or -j or justDaemoniseNoPIDFile options*/
if ((daemonisewith != 0) || (daemonisewithout != 0)) {
fprintf(stderr, "%s was built without libdaemon, so does not support daemonisation using the "
- "-d, --deamon, -j or --justDaemoniseNoPIDFile options\n",
+ "-d, --daemon, -j or --justDaemoniseNoPIDFile options\n",
config.appName);
exit(EXIT_FAILURE);
}
@@ -1227,46 +1258,6 @@ void *dbus_thread_func(__attribute__((unused)) void *arg) {
}
#endif
-void signal_setup(void) {
- // mask off all signals before creating threads.
- // this way we control which thread gets which signals.
- // for now, we don't care which thread gets the following.
- sigset_t set;
- sigfillset(&set);
- sigdelset(&set, SIGINT);
- sigdelset(&set, SIGTERM);
- sigdelset(&set, SIGHUP);
- sigdelset(&set, SIGSTOP);
- sigdelset(&set, SIGCHLD);
- sigdelset(&set, SIGUSR2);
- pthread_sigmask(SIG_BLOCK, &set, NULL);
-
- // SIGUSR1 is used to interrupt a thread if blocked in pselect
- pthread_sigmask(SIG_SETMASK, NULL, &pselect_sigset);
- sigdelset(&pselect_sigset, SIGUSR1);
-
- // setting this to SIG_IGN would prevent signalling any threads.
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_flags = SA_SIGINFO;
- sa.sa_sigaction = &sig_ignore;
- sigaction(SIGUSR1, &sa, NULL);
-
- sa.sa_flags = SA_SIGINFO | SA_RESTART;
- sa.sa_sigaction = &sig_shutdown;
- sigaction(SIGINT, &sa, NULL);
- sigaction(SIGTERM, &sa, NULL);
-
- sa.sa_sigaction = &sig_disconnect_audio_output;
- sigaction(SIGUSR2, &sa, NULL);
-
- sa.sa_sigaction = &sig_connect_audio_output;
- sigaction(SIGHUP, &sa, NULL);
-
- sa.sa_sigaction = &sig_child;
- sigaction(SIGCHLD, &sa, NULL);
-}
-
#ifdef CONFIG_LIBDAEMON
char pid_file_path_string[4096] = "\0";
@@ -1278,19 +1269,34 @@ const char *pid_file_proc(void) {
}
#endif
-void main_cleanup_handler(__attribute__((unused)) void *arg) {
- // it doesn't look like this is called when the main function is cancelled with a pthread cancel.
- debug(1, "main cleanup handler called.");
+
+void exit_function() {
+
+// the following is to ensure that if libdaemon has been included
+// that most of this code will be skipped when the parent process is exiting
+// exec
+#ifdef CONFIG_LIBDAEMON
+ if (this_is_the_daemon_process) { //this is the daemon that is exiting
+#endif
+ debug(1, "exit function called...");
+
+/*
+Actually, there is no terminate_mqtt() function.
#ifdef CONFIG_MQTT
if (config.mqtt_enabled) {
- // terminate_mqtt();
+ terminate_mqtt();
}
#endif
+*/
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
+
+/*
+Actually, there is no stop_mpris_service() function.
#ifdef CONFIG_MPRIS_INTERFACE
-// stop_mpris_service();
+ stop_mpris_service();
#endif
+*/
#ifdef CONFIG_DBUS_INTERFACE
stop_dbus_service();
#endif
@@ -1327,34 +1333,30 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) {
pthread_join(soxr_time_check_thread, NULL);
#endif
-#ifdef CONFIG_LIBDAEMON
- // only do this if you are the daemon
- if (pid == 0) {
- daemon_retval_send(0);
- daemon_pid_file_remove();
- daemon_signal_done();
- }
-#endif
-
- debug(2, "Exit...");
- exit(EXIT_SUCCESS);
-}
-
-void exit_function() {
- debug(1, "exit function called...");
- main_cleanup_handler(NULL);
+
if (conns)
free(conns); // make sure the connections have been deleted first
+
if (config.service_name)
free(config.service_name);
+
+#ifdef CONFIG_CONVOLUTION
+ if (config.convolution_ir_file)
+ free(config.convolution_ir_file);
+#endif
+
if (config.regtype)
free(config.regtype);
+
#ifdef CONFIG_LIBDAEMON
+ daemon_retval_send(0);
+ daemon_pid_file_remove();
+ daemon_signal_done();
if (config.computed_piddir)
free(config.computed_piddir);
+ }
#endif
- if (ranarray)
- free((void *)ranarray);
+
if (config.cfg)
config_destroy(config.cfg);
if (config.appName)
@@ -1363,6 +1365,18 @@ void exit_function() {
}
int main(int argc, char **argv) {
+ /* Check if we are called with -V or --version parameter */
+ if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
+ print_version();
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Check if we are called with -h or --help parameter */
+ if (argc >= 2 && ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0))) {
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+
#ifdef CONFIG_LIBDAEMON
pid = getpid();
#endif
@@ -1424,6 +1438,8 @@ int main(int argc, char **argv) {
// config.statistics_requested = 0; // don't print stats in the log
// config.userSuppliedLatency = 0; // zero means none supplied
+ config.debugger_show_file_and_line =
+ 1; // by default, log the file and line of the originating message
config.debugger_show_relative_time =
1; // by default, log the time back to the previous debug message
config.resyncthreshold = 0.05; // 50 ms
@@ -1464,22 +1480,6 @@ int main(int argc, char **argv) {
r64init(0);
- // initialise the randomw number array
-
- r64arrayinit();
-
- /* Check if we are called with -V or --version parameter */
- if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
- print_version();
- exit(EXIT_FAILURE);
- }
-
- /* Check if we are called with -h or --help parameter */
- if (argc >= 2 && ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0))) {
- usage(argv[0]);
- exit(EXIT_FAILURE);
- }
-
#ifdef CONFIG_LIBDAEMON
/* Reset signal handlers */
@@ -1494,7 +1494,7 @@ int main(int argc, char **argv) {
return 1;
}
- /* Set indentification string for the daemon for both syslog and PID file */
+ /* Set identification string for the daemon for both syslog and PID file */
daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]);
daemon_pid_file_proc = pid_file_proc;
@@ -1581,6 +1581,8 @@ int main(int argc, char **argv) {
}
return ret;
} else { /* pid == 0 means we are the daemon */
+
+ this_is_the_daemon_process = 1; //
/* Close FDs */
if (daemon_close_all(-1) < 0) {
@@ -1626,8 +1628,6 @@ int main(int argc, char **argv) {
if (!main_thread_id)
debug(1, "Main thread is set up to be NULL!");
- signal_setup();
-
// make sure the program can create files that group and world can read
umask(S_IWGRP | S_IWOTH);
@@ -1696,7 +1696,7 @@ int main(int argc, char **argv) {
debug(1, "statistics_requester status is %d.", config.statistics_requested);
#if CONFIG_LIBDAEMON
debug(1, "daemon status is %d.", config.daemonise);
- debug(1, "deamon pid file path is \"%s\".", pid_file_proc());
+ debug(1, "daemon pid file path is \"%s\".", pid_file_proc());
#endif
debug(1, "rtsp listening port is %d.", config.port);
debug(1, "udp base port is %d.", config.udp_port_base);
diff --git a/tinyhttp/chunk.h b/tinyhttp/chunk.h
index 9736e83..e0df060 100644
--- a/tinyhttp/chunk.h
+++ b/tinyhttp/chunk.h
@@ -33,7 +33,7 @@ extern "C" {
/**
* Parses the size out of a chunk-encoded HTTP response. Returns non-zero if it
- * needs more data. Retuns zero success or error. When error: size == -1 On
+ * needs more data. Returns zero success or error. When error: size == -1 On
* success, size = size of following chunk data excluding trailing \r\n. User is
* expected to process or otherwise seek past chunk data up to the trailing
* \r\n. The state parameter is used for internal state and should be
diff --git a/tinysvcmdns.c b/tinysvcmdns.c
index bb89e50..4cb6fb4 100644
--- a/tinysvcmdns.c
+++ b/tinysvcmdns.c
@@ -1576,7 +1576,7 @@ void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip) {
struct rr_entry *a_e = NULL, *nsec_e = NULL;
// currently can't be called twice
- // dont ask me what happens if the IP changes
+ // don't ask me what happens if the IP changes
assert(svr->hostname == NULL);
a_e = rr_create_a(create_nlabel(hostname), ip); // 120 seconds automatically
@@ -1596,7 +1596,7 @@ void mdnsd_set_hostname_v6(struct mdnsd *svr, const char *hostname, struct in6_a
struct rr_entry *aaaa_e = NULL, *nsec_e = NULL;
// currently can't be called twice
- // dont ask me what happens if the IP changes
+ // don't ask me what happens if the IP changes
assert(svr->hostname == NULL);
aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); // 120 seconds automatically