From b9cf91b2bbecec8dafaff9400819435070319160 Mon Sep 17 00:00:00 2001
From: Chris Boot
Date: Sat, 27 Jul 2019 00:05:38 -0300
Subject: [PATCH 01/59] More typo fixes across the tree
Previously I have made spelling changes as caught by Debian's lintian
tool, which has a short list of common misspellings that it tests for.
This time I decided to run codespell against the source and fix all the
obvious problems it came up with. It also uses a list of misspellings
but it's much larger than lintian's, but also has some false positives
so I went over the list by hand.
The command I used to do check the source was:
git ls-tree -rz --name-only HEAD | xargs -0 codespell -L minimise,errorstring
---
CAR INSTALL.md | 2 +-
INSTALL.md | 2 +-
README-DEVELOPMENT.md | 2 +-
README.md | 4 ++--
RELEASENOTES-DEVELOPMENT.md | 44 ++++++++++++++++++-------------------
RELEASENOTES.md | 40 ++++++++++++++++-----------------
TROUBLESHOOTING.md | 2 +-
activity_monitor.c | 2 +-
alac.h | 2 +-
audio.c | 2 +-
audio_alsa.c | 2 +-
audio_jack.c | 4 ++--
audio_sndio.c | 2 +-
common.h | 6 ++---
dbus-service.c | 4 ++--
man/shairport-sync.7 | 8 +++----
man/shairport-sync.7.xml | 8 +++----
metadata_hub.h | 2 +-
player.c | 8 +++----
player.h | 2 +-
rtsp.c | 4 ++--
scripts/shairport-sync.in | 2 +-
shairport-sync.spec | 4 ++--
shairport.c | 6 ++---
tinyhttp/chunk.h | 2 +-
tinysvcmdns.c | 4 ++--
26 files changed, 85 insertions(+), 85 deletions(-)
diff --git a/CAR INSTALL.md b/CAR INSTALL.md
index 879015ad..062b6df5 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/INSTALL.md b/INSTALL.md
index 8d8dc7df..a5a61991 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -2,7 +2,7 @@ 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.
-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:
diff --git a/README-DEVELOPMENT.md b/README-DEVELOPMENT.md
index 23367609..8193f84e 100644
--- a/README-DEVELOPMENT.md
+++ b/README-DEVELOPMENT.md
@@ -81,7 +81,7 @@ Please refer to the relevant pages for building for the above systems.
**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
diff --git a/README.md b/README.md
index 5a7acbe7..d9976d82 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ You should also remove the initialisation script files `/etc/systemd/system/shai
**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
@@ -609,7 +609,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-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index a5fc6ce8..da976bb9 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -90,7 +90,7 @@ Version 3.3d56 to Version 3.3d40
**Enhancements**
* Changes to the Jack Audio back end. The back end for Jack Audio, `audio_jack.c`, has been extensively rewritten by [Jörn Nettingsmeier](https://github.com/nettings) in a way that is more in keeping with the Jack Audio style. It uses native Jack Audio lockless buffers and offers autoconnect facilities that the previous version didn't have. Many thanks to him.
-* The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume contol changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. see the new `volume_range_combined_hardware_priority` setting in the `general` section opf the configuration file.
+* The volume-control software has been completely rewritten. From a user's point of view, the result should be a much smoother response to volume control changes, free from artefacts. It is now also possible to combine the hardware mixer and the software attenuator in two ways -- giving priority to the software mixer or giving priority to the hardware mixer. see the new `volume_range_combined_hardware_priority` setting in the `general` section opf the configuration file.
* The muting/unmuting code has been rewritten to be simpler and more consistent.
* In the `alsa` backend, new `play()` and `delay()` functions minimise the use of `snd_pcm_recover()` to prevent unnecessary resets of the output DACs.
* In the `alsa` backend driver, hardware isn't accessed until the first time it is needed. That is, when Shairport Sync starts up, it no longer needs to access the device momentarily. Instead, it waits for the first use.
@@ -378,7 +378,7 @@ Update [TROUBLESHOOTING.md](https://github.com/mikebrady/shairport-sync/blob/dev
Version 3.2d45
====
**Enhancement**
-* Restore the old method for calculating latency for older AirPlay sources: an AirPlay source displaying an AirPlay User Agent string verion of 353 or older -- corresponding to iOS 11.1.2 or older -- will add an extra 0.25 seconds to the latency requested. This seems to be right.
+* Restore the old method for calculating latency for older AirPlay sources: an AirPlay source displaying an AirPlay User Agent string version of 353 or older -- corresponding to iOS 11.1.2 or older -- will add an extra 0.25 seconds to the latency requested. This seems to be right.
Version 3.2d44
====
@@ -398,7 +398,7 @@ Version 3.2d41
Version 3.2d40
====
**Bug Fixes**
-* A number of serious and long-standing bugs have been identified and fixed in the threads that handle audio, control and timing packets. Specifically, if UDP reception or transmission errors occured (a rare occurence on a good network, but possible on noisy or congested networks), the threads would quit. In this way, an error on the reception of the first control packet could mute an entire play session.
+* A number of serious and long-standing bugs have been identified and fixed in the threads that handle audio, control and timing packets. Specifically, if UDP reception or transmission errors occurred (a rare occurrence on a good network, but possible on noisy or congested networks), the threads would quit. In this way, an error on the reception of the first control packet could mute an entire play session.
**Enhancements**
* The code used to request the retransmission of missing audio packets has been significantly improved.
@@ -427,7 +427,7 @@ Version 3.2d34
* `pfls` and `prsm` messages are less frequent, especially when a play session starts.
**Other Developments**
-* 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.
Version 3.2d33
====
@@ -437,7 +437,7 @@ Version 3.2d30
====
**Enhancements**
* A "native" D-Bus Remote Control 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. It is likely that a time limit will be put on this, so that after, say, 30 minutes of inactivity, the remote contol connection will be dropped.
+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. It is likely that a time limit will be put on this, so that after, say, 30 minutes of inactivity, the remote control connection will be dropped.
Version 3.2d29
====
@@ -577,7 +577,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.
@@ -604,7 +604,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.
@@ -707,7 +707,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
----
@@ -850,7 +850,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
----
@@ -971,13 +971,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).
@@ -1056,7 +1056,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.
@@ -1088,7 +1088,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.
@@ -1110,7 +1110,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**
@@ -1129,7 +1129,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.
@@ -1141,8 +1141,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**
@@ -1189,7 +1189,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.
@@ -1208,7 +1208,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
@@ -1285,7 +1285,7 @@ Version 2.3.1
Some big changes "under the hood" have been made, leading to limited support for unsynchronised output to `stdout` or to a named pipe and continuation of defacto support for unsynchronised PulseAudio. Also, support for a configuration file in preference to command line options, an option to ignore volume control and other improvements are provided.
In this release, Shairport Sync gains the ability to read settings from `/etc/shairport-sync.conf`.
-This gives more flexibility in adding features gives better compatability across different versions of Linux.
+This gives more flexibility in adding features gives better compatibility across different versions of Linux.
Existing command-line options continue to work, but some will be deprecated and may disappear in a future version of Shairport Sync. New settings will only be available via the configuration file.
Note that, for the present, settings in the configuration will have priority over command line options for Shairport Sync itself, in contravention of the normal unix convention. Audio back end command line options, i.e. those after the `--`, have priority over configuration file settings for the audio backends.
@@ -1363,7 +1363,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
-----
@@ -1418,7 +1418,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=
-
Within the configuraton file, settings are organised into groups, for
+
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:
@@ -223,7 +223,7 @@
@@ -583,7 +583,7 @@
specify.
"S" means signed; "U" means unsigned; BE means big-endian and LE means
little-endian. Except where stated (using *LE or *BE), endianness matches that of the
processor. The default is "S16".
If you are using a hardware mixer, the best
- setting is S16, as audio will pass through Shairport Sync unmodifed except for
+ 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.
diff --git a/metadata_hub.h b/metadata_hub.h
index 784f6621..47965d5d 100644
--- a/metadata_hub.h
+++ b/metadata_hub.h
@@ -106,7 +106,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/player.c b/player.c
index 17982813..0b0ec65b 100644
--- a/player.c
+++ b/player.c
@@ -170,7 +170,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
@@ -1921,7 +1921,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;
@@ -1942,7 +1942,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 +1964,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);
diff --git a/player.h b/player.h
index cb197e4a..724d5a78 100644
--- a/player.h
+++ b/player.h
@@ -55,7 +55,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/rtsp.c b/rtsp.c
index 4a72250a..a802ba96 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1137,7 +1137,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.
//
@@ -1869,7 +1869,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
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
+ // 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]);
diff --git a/scripts/shairport-sync.in b/scripts/shairport-sync.in
index f229f19b..017de079 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.spec b/shairport-sync.spec
index 53daf65e..bcc3b5b7 100644
--- a/shairport-sync.spec
+++ b/shairport-sync.spec
@@ -79,12 +79,12 @@ getent passwd %{name} &> /dev/null || useradd --system -c "%{name} User" \
* Thu Dec 21 2017 Mike Brady 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 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 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 6d74cae4..21d20dfd 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1143,7 +1143,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);
}
@@ -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;
@@ -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 9736e836..e0df0600 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 bb89e50f..4cb6fb4c 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
From 2e7e5b6856260eedb1f63f80f725da46793cee28 Mon Sep 17 00:00:00 2001
From: Henrik Nilsson
Date: Wed, 31 Jul 2019 14:16:06 +0200
Subject: [PATCH 02/59] Check help and version argument early
Check for the help and version arguments makes sure that it's
always possible to get the version and usage even if there are
issues in other parts of the code.
---
shairport.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/shairport.c b/shairport.c
index 21d20dfd..cc1a790f 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1363,6 +1363,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_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
pid = getpid();
#endif
@@ -1468,18 +1480,6 @@ int main(int argc, char **argv) {
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 */
From 14de9e27ab863ef0381fb84d4cb269e4c1d90d79 Mon Sep 17 00:00:00 2001
From: Henrik Nilsson
Date: Thu, 1 Aug 2019 09:59:17 +0200
Subject: [PATCH 03/59] Return success on version and help argument
It's not a failure to print version or usage, so use return code
success.
---
shairport.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/shairport.c b/shairport.c
index cc1a790f..d458516d 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1366,13 +1366,13 @@ 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_FAILURE);
+ 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_FAILURE);
+ exit(EXIT_SUCCESS);
}
#ifdef CONFIG_LIBDAEMON
From 333e3904e7d891c6126e6a1a1ac0255b66c99450 Mon Sep 17 00:00:00 2001
From: Angus71
Date: Sat, 24 Aug 2019 22:02:33 +0200
Subject: [PATCH 04/59] Fixed seg. fault with ALAC and incomplete a:fmtp
---
rtsp.c | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index a802ba96..c830bb25 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1866,9 +1866,31 @@ 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.");
+
+ // Set 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;
- for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++)
- conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t"));
+ char* found;
+ for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) {
+ found = strsep(&pfmtp, " \t");
+ if (found != NULL) {
+ conn->stream.fmtp[i] = atoi(found);
+ } else {
+ break;
+ }
+ }
// 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]);
From bf4bd8ef04539fd7064e10886cce4f6895ea529c Mon Sep 17 00:00:00 2001
From: Angus71
Date: Sun, 25 Aug 2019 09:50:40 +0200
Subject: [PATCH 05/59] Clean up
---
rtsp.c | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index c830bb25..1b198c69 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1867,7 +1867,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
conn->stream.type = ast_apple_lossless;
debug(3, "An ALAC stream has been detected.");
- // Set connection defaults
+ // Set reasonable connection defaults
conn->stream.fmtp[0] = 96;
conn->stream.fmtp[1] = 352;
conn->stream.fmtp[2] = 0;
@@ -1881,15 +1881,11 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
conn->stream.fmtp[10] = 0;
conn->stream.fmtp[11] = 44100;
- unsigned int i;
+ unsigned int i = 0;
+ unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]);
char* found;
- for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) {
- found = strsep(&pfmtp, " \t");
- if (found != NULL) {
- conn->stream.fmtp[i] = atoi(found);
- } else {
- break;
- }
+ 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++)
From 9fdfa5c23519ba376cd2b07ab4094edafb08c2cb Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 28 Aug 2019 09:07:13 +0100
Subject: [PATCH 06/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index da976bb9..e1c3e9ff 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,10 @@
+Version 3.3.2d4
+**Bug Fix**
+* 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.
+
+**Enhancement**
+* 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.
+
Version 3.3.2d2
====
**Bug Fix**
From d846bc53be6e1a6d6cc5412a963fc70ec8f8d551 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 28 Aug 2019 09:07:41 +0100
Subject: [PATCH 07/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index e1c3e9ff..e9c4b13f 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,4 +1,5 @@
-Version 3.3.2d4
+Version 3.3.2d3
+====
**Bug Fix**
* 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.
From 86b6d7bfffbed00432a596b83e96507f308a78e8 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 29 Aug 2019 11:47:49 +0100
Subject: [PATCH 08/59] Squashed commit of the following: Fix Issue #890 --
https://github.com/mikebrady/shairport-sync/issues/890 Recognise
situation where a track has no artwork properly. Less likely to drop the
'availabile' property. Always output the full metadata whenever any part
of it has changed. Metadata coming from dacp is now incorporated,
---
common.c | 36 +++++
common.h | 2 +
dacp.c | 113 ++++++--------
dbus-service.c | 82 +++++-----
metadata_hub.c | 403 ++++++++++++++++++++----------------------------
metadata_hub.h | 91 ++++++++---
mpris-service.c | 147 +++++++++---------
7 files changed, 435 insertions(+), 439 deletions(-)
diff --git a/common.c b/common.c
index 39648161..5221e0ee 100644
--- a/common.c
+++ b/common.c
@@ -1519,3 +1519,39 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma
}
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 e1b18d88..cc8c93c0 100644
--- a/common.h
+++ b/common.h
@@ -393,4 +393,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/dacp.c b/dacp.c
index 0b6db70e..1c5271e1 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;
@@ -320,9 +323,10 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// 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?",
+ 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
}
@@ -419,8 +423,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(3,
+ "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 +473,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 dacp_server_active and 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) {
@@ -483,7 +489,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
scan_index++;
result = dacp_get_volume(&the_volume); // just want the http code
- if ((result == 496) || (result == 403) || (result == 501)) {
+ if ((result == 403) || (result == 501)) {
bad_result_count++;
// debug(1,"Bad Scan : %d.",result);
} else
@@ -511,8 +517,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
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
+ (result == 494) || (result == 495) ||
+ (result == 496); // 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) &&
@@ -525,12 +531,12 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
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);
+ debug(1, "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.",
+ debug(1, "Setting dacp_advanced_server_active to %d because of a response of %d.",
(result == 200), result);
}
metadata_hub_modify_epilog(inactive + (!same));
@@ -574,6 +580,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 +665,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 +709,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;
/*
diff --git a/dbus-service.c b/dbus-service.c
index 8ab4cd2c..2294a706 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -146,77 +146,73 @@ void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused))
debug(1, "This should never happen.");
}
- GVariantBuilder *dict_builder, *aa;
+ // Build the metadata array
+ debug(2, "Build metadata");
+ GVariantBuilder *dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
- /* 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
+ // 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;
diff --git a/metadata_hub.c b/metadata_hub.c
index 400c1e55..dc2f3433 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) {
@@ -163,48 +137,21 @@ void metadata_hub_modify_prolog(void) {
}
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);
}
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.");
}
}
@@ -223,7 +170,7 @@ char *metadata_write_image_file(const char *buf, int len) {
char *path = NULL; // this will be what is returned
uint8_t img_md5[16];
-// uint8_t ap_md5[16];
+ // uint8_t ap_md5[16];
#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
@@ -345,124 +292,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 +435,121 @@ 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':
+ metadata_hub_modify_prolog();
+ debug(2, "MH Picture received, length %u bytes.", length);
+ char uri[2048];
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);
+ 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);
- }
- 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': {
metadata_hub_modify_prolog();
- int changed = (metadata_store.active_state != AM_ACTIVE);
+ 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;
+ case 'abeg':
+ metadata_hub_modify_prolog();
+ 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_state = PS_PLAYING;
metadata_store.player_thread_active = 1;
metadata_hub_modify_epilog(changed);
- } break;
- case 'pend': {
+ break;
+ case 'pend':
metadata_hub_modify_prolog();
+ changed = (metadata_store.player_state != PS_STOPPED);
metadata_store.player_thread_active = 0;
metadata_store.player_state = PS_STOPPED;
- metadata_hub_modify_epilog(1);
- } break;
- case 'pfls': {
+ 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;
+ 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 47965d5d..5b565bba 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; // if non-zero, it will have been assigned with malloc.
+ 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;
+
+ // end
enum play_status_type
player_state; // this is the state of the actual player itself, which can be a bit noisy.
diff --git a/mpris-service.c b/mpris-service.c
index 668d776e..37e7bf8a 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -82,56 +82,90 @@ 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;
+ *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)) {
+ 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);
+ }
+
+ // 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;
+ // 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 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 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 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);
@@ -139,48 +173,9 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength);
}
- // 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 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 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 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);
- }
-
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,
From c1f6e2ea18e5e4d32959d803a7fe195b26dbc9f8 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 29 Aug 2019 11:56:13 +0100
Subject: [PATCH 09/59] Fix Issue #890 --
https://github.com/mikebrady/shairport-sync/issues/890 Fix metadata issues
with the dbus and the MPRIS interfaces -- handle missing artwork properly,
make the 'available' property more resilient.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index adcefe3c..fbffd0b7 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.2d3], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.2d4], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 9aa5fa90f9518f90f29a698f21facbc6469ec9a5 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 29 Aug 2019 12:19:51 +0100
Subject: [PATCH 10/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index e9c4b13f..0f9e5e1b 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,8 @@
+Version 3.3.2d4
+====
+**Bug Fixes and Enhancements**
+* Fixes and enhancements 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 mnore resilient to netowkr problems. Addresses Issue #890. Thanks to [exoqrtx](https://github.com/exoqrtx) for bringinig these issues to light andf for testing.
+
Version 3.3.2d3
====
**Bug Fix**
From 8c940516a9e20cd30bf54c11181a1e0128eeed43 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 29 Aug 2019 12:21:15 +0100
Subject: [PATCH 11/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 0f9e5e1b..0f6861e8 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,7 +1,7 @@
Version 3.3.2d4
====
**Bug Fixes and Enhancements**
-* Fixes and enhancements 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 mnore resilient to netowkr problems. Addresses Issue #890. Thanks to [exoqrtx](https://github.com/exoqrtx) for bringinig these issues to light andf for testing.
+* Fixes and enhancements 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 mnore resilient to network problems. Addresses Issue [#890](https://github.com/mikebrady/shairport-sync/issues/890). Thanks to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
Version 3.3.2d3
====
From 4d6f5b4844074aa74f2c3bc1808a065446f17e61 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 3 Sep 2019 10:47:10 +0100
Subject: [PATCH 12/59] Remove second avahi threaded poll, remove service
client.
---
mdns_avahi.c | 149 ++++++++++++---------------------------------------
1 file changed, 35 insertions(+), 114 deletions(-)
diff --git a/mdns_avahi.c b/mdns_avahi.c
index 2d5cf569..b22d92c5 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,12 +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) {
+ // debug(1,"browse_callback, event %d.", event);
dacp_browser_struct *dbs = (dacp_browser_struct *)userdata;
assert(b);
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
@@ -132,18 +133,19 @@ 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
char *dacpid = strstr(name, "iTunes_Ctrl_");
if (dacpid) {
@@ -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",
From a3a1884f719d9eda5d7389f453bf249ee08f8cbf Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 4 Sep 2019 09:19:44 +0100
Subject: [PATCH 13/59] Remove obselete SIG setup and action stuff
---
shairport.c | 78 -----------------------------------------------------
1 file changed, 78 deletions(-)
diff --git a/shairport.c b/shairport.c
index d458516d..d97a6fea 100644
--- a/shairport.c
+++ b/shairport.c
@@ -116,42 +116,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) {
@@ -1227,46 +1191,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";
@@ -1626,8 +1550,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);
From 4f09a3299e97a51f733910ffb61901ea923a7046 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 4 Sep 2019 09:24:34 +0100
Subject: [PATCH 14/59] Fix occasional crashing bug in Ububntu 14.04 (sometimes
triggered by DHCP renewal and Avahi / dbus issues) by removing second avahi
threaded poll and an unnecessary avahi service client (very sparse Avahi
documentation), also remove obselete SIG setup code.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index fbffd0b7..5a35adc9 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.2d4], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.2d5], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From cf70c123924eae16b27d9748cca2779a0e530e77 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 4 Sep 2019 09:37:48 +0100
Subject: [PATCH 15/59] avoid a compilation warning when the dacp stuff isn't
needed
---
mdns_avahi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mdns_avahi.c b/mdns_avahi.c
index b22d92c5..caec909d 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -123,7 +123,6 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
void *userdata) {
// debug(1,"browse_callback, event %d.", event);
- dacp_browser_struct *dbs = (dacp_browser_struct *)userdata;
assert(b);
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
switch (event) {
@@ -147,6 +146,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
case AVAHI_BROWSER_REMOVE:
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_");
From 2965900b6cdfa435ea606e388695b8a12d60d59c Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 4 Sep 2019 09:47:20 +0100
Subject: [PATCH 16/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 0f6861e8..e69a79b5 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,10 @@
+Version 3.3.2d5
+====
+**Bug Fix**
+* Fixes an issue that occured 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.
+
+
Version 3.3.2d4
====
**Bug Fixes and Enhancements**
From daab7a634cc8bb0ac94e425e19b1465d571e9537 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 4 Sep 2019 20:24:34 +0100
Subject: [PATCH 17/59] Clean up the scanning code for detecting either a
standard DACP server or an advanced DACP server.
---
dacp.c | 439 ++++++++++++++++++++++++++-----------------------
metadata_hub.c | 26 +--
shairport.c | 10 +-
3 files changed, 255 insertions(+), 220 deletions(-)
diff --git a/dacp.c b/dacp.c
index 1c5271e1..424cabea 100644
--- a/dacp.c
+++ b/dacp.c
@@ -149,203 +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.
+ // 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
+ // 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 addrinfo hints, *res;
+ int sockfd;
- struct HttpResponse response;
- response.body = NULL;
- response.malloced_size = 0;
- response.size = 0;
- response.code = 0;
+ 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);
+ 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.");
+ pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
+ // debug(2, "dacp_send_command: open socket %d.",sockfd);
- 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
+ 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.");
- response.body = malloc(2048); // it can resize this if necessary
- response.malloced_size = 2048;
- pthread_cleanup_push(malloc_cleanup, response.body);
+ 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);
- struct http_roundtripper rt;
- http_init(&rt, responseFuncs, &response);
- pthread_cleanup_push(http_cleanup, &rt);
+ // 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
- 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(1, "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) {
@@ -423,7 +436,7 @@ 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,
+ 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);
@@ -474,7 +487,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
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 "
+ "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);
@@ -483,17 +496,44 @@ 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 == 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 (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++;
@@ -503,45 +543,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) ||
- (result == 496); // 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(1, "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(1, "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;
@@ -555,7 +587,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) {
@@ -775,7 +807,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");
}
@@ -815,7 +847,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);
@@ -901,6 +932,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
@@ -947,7 +979,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
}
@@ -957,7 +989,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
}
@@ -970,6 +1002,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/metadata_hub.c b/metadata_hub.c
index dc2f3433..4f2d00af 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -132,7 +132,9 @@ void metadata_hub_modify_prolog(void) {
if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) {
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.");
+ debug(1, "Okay -- acquired the metadata_hub write lock.");
+ } else {
+ debug(3, "Metadata_hub write lock acquired.");
}
}
@@ -143,6 +145,7 @@ void metadata_hub_modify_epilog(int modified) {
run_metadata_watchers();
}
pthread_rwlock_unlock(&metadata_hub_re_lock);
+ debug(3, "Metadata_hub write lock unlocked.");
}
void metadata_hub_read_prolog(void) {
@@ -511,16 +514,16 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
break;
case 'pbeg':
metadata_hub_modify_prolog();
- 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':
metadata_hub_modify_prolog();
- changed = (metadata_store.player_state != PS_STOPPED);
- 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_store.player_thread_active = 0;
metadata_hub_modify_epilog(changed);
break;
case 'pfls':
@@ -537,14 +540,15 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_hub_modify_epilog(changed);
break;
case 'pvol': {
- // 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;
- }
+ // 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;
changed = 1;
diff --git a/shairport.c b/shairport.c
index d97a6fea..1594c4f7 100644
--- a/shairport.c
+++ b/shairport.c
@@ -407,14 +407,12 @@ int parse_options(int argc, char **argv) {
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;
From 874dfefc4c9dadd3f8c76eed7ad0971aa16ed98e Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 4 Sep 2019 20:39:35 +0100
Subject: [PATCH 18/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index e69a79b5..add7a5d4 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,14 +1,14 @@
Version 3.3.2d5
====
-**Bug Fix**
-* Fixes an issue that occured 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.
+**Bug Fixes**
+* Improves 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. Follows on from issues raised in [#890](https://github.com/mikebrady/shairport-sync/issues/890). Thanks again to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
+* Fixes an issue that occured 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.
Version 3.3.2d4
====
**Bug Fixes and Enhancements**
-* Fixes and enhancements 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 mnore resilient to network problems. Addresses Issue [#890](https://github.com/mikebrady/shairport-sync/issues/890). Thanks to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
+* Fixes and enhancements 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 mnore resilient to network problems. Addresses issue [#890](https://github.com/mikebrady/shairport-sync/issues/890). Thanks to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
Version 3.3.2d3
====
From 89487c16bba3a1d7ecb9c302afa771143472d397 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Wed, 4 Sep 2019 20:40:23 +0100
Subject: [PATCH 19/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index add7a5d4..fd9d563e 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -8,7 +8,7 @@ Version 3.3.2d5
Version 3.3.2d4
====
**Bug Fixes and Enhancements**
-* Fixes and enhancements 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 mnore resilient to network problems. Addresses issue [#890](https://github.com/mikebrady/shairport-sync/issues/890). Thanks to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
+* Fixes and enhancements 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). Thanks to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
Version 3.3.2d3
====
From 839c778a7c999bf7b8bdd7ca54b5c9df98e7fcb2 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 8 Sep 2019 15:53:52 +0100
Subject: [PATCH 20/59] Only send shuffle commands if a change is necessary
---
dbus-service.c | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/dbus-service.c b/dbus-service.c
index 2294a706..2393675b 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -25,6 +25,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,21 +131,30 @@ 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);
}
// Build the metadata array
From 185000d0bde05b330909c4f545cb42b7a6a1aabe Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 8 Sep 2019 17:03:36 +0100
Subject: [PATCH 21/59] Remove some old code and clean up the exit cleanup
function so that it works better with libdaemon and its parent and child
processes
---
common.c | 34 +----------------------------
common.h | 5 -----
player.c | 5 +----
shairport.c | 63 +++++++++++++++++++++++++----------------------------
4 files changed, 32 insertions(+), 75 deletions(-)
diff --git a/common.c b/common.c
index 5221e0ee..0fa2c158 100644
--- a/common.c
+++ b/common.c
@@ -1078,38 +1078,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;
@@ -1424,7 +1392,7 @@ int64_t generate_zero_frames(char *outp, size_t number_of_frames, enum sps_forma
int64_t hyper_sample = 0;
- int64_t r = ranarray64i();
+ int64_t r = r64i();
int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask);
diff --git a/common.h b/common.h
index cc8c93c0..96b2f817 100644
--- a/common.h
+++ b/common.h
@@ -295,11 +295,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
diff --git a/player.c b/player.c
index 0b0ec65b..e089c31f 100644
--- a/player.c
+++ b/player.c
@@ -697,10 +697,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;
diff --git a/shairport.c b/shairport.c
index 1594c4f7..7d148588 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;
@@ -1200,18 +1201,25 @@ 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...");
#ifdef CONFIG_MQTT
if (config.mqtt_enabled) {
- // terminate_mqtt();
+ terminate_mqtt();
}
#endif
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
#ifdef CONFIG_MPRIS_INTERFACE
-// stop_mpris_service();
+ stop_mpris_service();
#endif
#ifdef CONFIG_DBUS_INTERFACE
stop_dbus_service();
@@ -1249,34 +1257,25 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) {
pthread_join(soxr_time_check_thread, NULL);
#endif
+
+ if (conns)
+ free(conns); // make sure the connections have been deleted first
+
+ if (config.service_name)
+ free(config.service_name);
+
+ if (config.regtype)
+ free(config.regtype);
+
#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();
+ daemon_retval_send(0);
+ daemon_pid_file_remove();
+ daemon_signal_done();
+ if (config.computed_piddir)
+ free(config.computed_piddir);
}
#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);
- if (config.regtype)
- free(config.regtype);
-#ifdef CONFIG_LIBDAEMON
- if (config.computed_piddir)
- free(config.computed_piddir);
-#endif
- if (ranarray)
- free((void *)ranarray);
if (config.cfg)
config_destroy(config.cfg);
if (config.appName)
@@ -1398,10 +1397,6 @@ int main(int argc, char **argv) {
r64init(0);
- // initialise the randomw number array
-
- r64arrayinit();
-
#ifdef CONFIG_LIBDAEMON
/* Reset signal handlers */
@@ -1503,6 +1498,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) {
From 2b980ff2a85aceb5dd51c93879392331c9921554 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 8 Sep 2019 17:26:06 +0100
Subject: [PATCH 22/59] make dbus interface set more quickly an
AdvancedRemoteControlAvailable to false when going from an iTunes to an
AirPlay service.
---
dacp.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dacp.c b/dacp.c
index 424cabea..46d92b0b 100644
--- a/dacp.c
+++ b/dacp.c
@@ -530,6 +530,8 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
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;
From d734b7e71d24f304ee62aa8a42520e07fc137a02 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 8 Sep 2019 17:43:07 +0100
Subject: [PATCH 23/59] add volume-up and volume-down to the dbus test client.
Quieten a debug message.
---
dacp.c | 2 +-
shairport-sync-dbus-test-client.c | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/dacp.c b/dacp.c
index 46d92b0b..79447afb 100644
--- a/dacp.c
+++ b/dacp.c
@@ -358,7 +358,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
int send_simple_dacp_command(const char *command) {
int reply = 0;
char *server_reply = NULL;
- debug(1, "send_simple_dacp_command: 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) {
diff --git a/shairport-sync-dbus-test-client.c b/shairport-sync-dbus-test-client.c
index 4d5703a4..96a7de8d 100644
--- a/shairport-sync-dbus-test-client.c
+++ b/shairport-sync-dbus-test-client.c
@@ -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);
From c7a290c36a00dfce50e603efc215e71dbf6038e7 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 09:29:21 +0100
Subject: [PATCH 24/59] remove a call to a non-existent terminate_mqtt function
in the clean-up.
---
shairport.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/shairport.c b/shairport.c
index 7d148588..45464777 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1211,11 +1211,15 @@ void exit_function() {
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)
#ifdef CONFIG_MPRIS_INTERFACE
From b59dd4229e007eb598eaded9bfb18306719bf06b Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 09:56:25 +0100
Subject: [PATCH 25/59] fix regression no volume metadata was being sent if
configured to ignore volume control -- issue #903.
---
player.c | 27 +++++++++++++++++++++------
1 file changed, 21 insertions(+), 6 deletions(-)
diff --git a/player.c b/player.c
index e089c31f..ba1c07a6 100644
--- a/player.c
+++ b/player.c
@@ -2870,20 +2870,19 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
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
- snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
+ snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0,
min_db / 100.0, max_db / 100.0);
send_ssnc_metadata('pvol', dv, strlen(dv), 1);
}
#endif
- // here, store the volume for possible use in the future
+
if (config.output->mute)
config.output->mute(0);
@@ -2895,6 +2894,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 not 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);
}
From 15d6338adf61ece1c072624a384c5504f40627be Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 10:06:54 +0100
Subject: [PATCH 26/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index fd9d563e..5334c43b 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,10 +1,15 @@
+Version 3.3.2d6
+====
+**Bug Fixes**
+* 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.
+* Fixes a compilation error if including the MQTT client -- thanks to [exoqrtx](https://github.com/exoqrtx) for reporting.
+
Version 3.3.2d5
====
**Bug Fixes**
* Improves 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. Follows on from issues raised in [#890](https://github.com/mikebrady/shairport-sync/issues/890). Thanks again to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
* Fixes an issue that occured 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.
-
Version 3.3.2d4
====
**Bug Fixes and Enhancements**
From 677684de04e1806fb9f8203ed755dfb070282663 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 10:08:47 +0100
Subject: [PATCH 27/59] Bug fixes
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.
Fixes a compilation error if including the MQTT client.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 5a35adc9..d9a29464 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.2d5], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.2d6], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From e8a92d3c3b37ff7e697cd4f7c2b7b9c4a5694102 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 13:03:54 +0100
Subject: [PATCH 28/59] ensure alsa mixer is initialised before trying to set
volume. Normalise the 'pvol' volume mesasages to the hardware devices'
maximum volume when using combined hardware and software attenuation.
---
audio_alsa.c | 50 ++++++++++++++++++++++++++++++--------------------
player.c | 12 +++++++++---
2 files changed, 39 insertions(+), 23 deletions(-)
diff --git a/audio_alsa.c b/audio_alsa.c
index 454d27ff..22aad333 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -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);
@@ -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/player.c b/player.c
index ba1c07a6..9ad4d051 100644
--- a/player.c
+++ b/player.c
@@ -2689,7 +2689,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
@@ -2701,6 +2700,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;
@@ -2877,8 +2877,14 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
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, scaled_attenuation / 100.0,
+ 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
@@ -2898,7 +2904,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
#ifdef CONFIG_METADATA
else {
// here, send the 'pvol' metadata message when the airplay volume information
- // is not being used by shairport sync to control the output volume
+ // 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);
From 1b4c9b178387ce37bda8644ee5cd5e8a962c40fb Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 13:10:49 +0100
Subject: [PATCH 29/59] quieten a debug message
---
audio_alsa.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/audio_alsa.c b/audio_alsa.c
index 22aad333..e0a16b9a 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -1804,7 +1804,7 @@ int prepare(void) {
if (alsa_backend_state == abm_disconnected) {
if (alsa_device_initialised == 0) {
- debug(1, "alsa: prepare() calling alsa_device_init.");
+ // debug(1, "alsa: prepare() calling alsa_device_init.");
alsa_device_init();
alsa_device_initialised = 1;
}
From e17a151f7a4f16bfcdba648b267435e244afa3e3 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 13:14:11 +0100
Subject: [PATCH 30/59] quieten a debug message
---
metadata_hub.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/metadata_hub.c b/metadata_hub.c
index 4f2d00af..e125fc15 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -132,7 +132,7 @@ void metadata_hub_modify_prolog(void) {
if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) {
debug(2, "Metadata_hub write lock is already taken -- must wait.");
pthread_rwlock_wrlock(&metadata_hub_re_lock);
- debug(1, "Okay -- acquired the metadata_hub write lock.");
+ debug(2, "Okay -- acquired the metadata_hub write lock.");
} else {
debug(3, "Metadata_hub write lock acquired.");
}
From 4a10ca6e8998db63615b4df4f9f1db6e68e48c50 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 13:59:46 +0100
Subject: [PATCH 31/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 5334c43b..e963d427 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,8 +1,12 @@
Version 3.3.2d6
====
+**Enhancements**
+* Normalise 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 corresponods to the maximum output level of the hardware mixer.
+
**Bug Fixes**
* 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.
* Fixes a compilation error if including the MQTT client -- thanks to [exoqrtx](https://github.com/exoqrtx) for reporting.
+* Ensure the hardware mixer of an `alsa` device is detected and initialised before responding to the first volume setting.
Version 3.3.2d5
====
From ee748305f08d6f9daab79dff10b18dd89152a088 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 14:08:39 +0100
Subject: [PATCH 32/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index e963d427..7b393ca3 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,7 +1,7 @@
Version 3.3.2d6
====
**Enhancements**
-* Normalise 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 corresponods to the maximum output level of the hardware mixer.
+* Normalise 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.
**Bug Fixes**
* 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.
From 3381e3359d0f837dfa3e2213453843b3ea5a3d2c Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 14:11:08 +0100
Subject: [PATCH 33/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 7b393ca3..3b5bfdda 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -10,8 +10,9 @@ Version 3.3.2d6
Version 3.3.2d5
====
-**Bug Fixes**
+**Enhancement**
* Improves 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. Follows on from issues raised in [#890](https://github.com/mikebrady/shairport-sync/issues/890). Thanks again to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
+**Bug Fix**
* Fixes an issue that occured 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.
Version 3.3.2d4
From b427156a74dd62f19c5ec4b64ae6d2605b2d6ab1 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 14:11:28 +0100
Subject: [PATCH 34/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 3b5bfdda..2ce363f2 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -12,6 +12,7 @@ Version 3.3.2d5
====
**Enhancement**
* Improves 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. Follows on from issues raised in [#890](https://github.com/mikebrady/shairport-sync/issues/890). Thanks again to [exoqrtx](https://github.com/exoqrtx) for bringing these issues to light and for testing.
+
**Bug Fix**
* Fixes an issue that occured 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.
From bd84702d84a50e9ab247ef97743ee1abde851f99 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 16:10:30 +0100
Subject: [PATCH 35/59] Remove reference to non-existent stop_mpris_service().
---
shairport.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/shairport.c b/shairport.c
index 45464777..54647976 100644
--- a/shairport.c
+++ b/shairport.c
@@ -1222,9 +1222,13 @@ Actually, there is no terminate_mqtt() function.
*/
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
+
+/*
+Actually, there is no stop_mpris_service() function.
#ifdef CONFIG_MPRIS_INTERFACE
stop_mpris_service();
#endif
+*/
#ifdef CONFIG_DBUS_INTERFACE
stop_dbus_service();
#endif
From 1680df0e3d484b6f8f3b0f202fe8bf83ea9b8a8d Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 10 Sep 2019 16:14:50 +0100
Subject: [PATCH 36/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 2ce363f2..bb2bc9d8 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -6,6 +6,7 @@ Version 3.3.2d6
**Bug Fixes**
* 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.
* Fixes a compilation error if including the MQTT client -- thanks to [exoqrtx](https://github.com/exoqrtx) for reporting.
+* Fixes a compilation error if including the MPRIS interface.
* Ensure the hardware mixer of an `alsa` device is detected and initialised before responding to the first volume setting.
Version 3.3.2d5
From db8f10cfa160818681eebd2169cd11553760028b Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 17 Sep 2019 12:14:55 +0100
Subject: [PATCH 37/59] add the option (normally on) of including the file name
and line number of the originrating message in a debug log message
---
common.c | 222 ++++++++++++++++++++++--------------
common.h | 45 ++++++--
dbus-service.c | 26 +++++
org.gnome.ShairportSync.xml | 1 +
scripts/shairport-sync.conf | 3 +-
shairport.c | 13 +++
6 files changed, 216 insertions(+), 94 deletions(-)
diff --git a/common.c b/common.c
index 0fa2c158..2cadc203 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;
@@ -1157,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,
@@ -1388,10 +1437,10 @@ 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 = r64i();
int64_t tpdf = (r & dither_mask) - (previous_random_number & dither_mask);
@@ -1485,6 +1534,7 @@ 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;
}
diff --git a/common.h b/common.h
index 96b2f817..2bb2ec09 100644
--- a/common.h
+++ b/common.h
@@ -100,6 +100,7 @@ enum sps_format_t {
const char *sps_format_description_string(enum sps_format_t format);
typedef struct {
+ pthread_mutex_t lock;
config_t *cfg;
int endianness;
double airplay_volume; // stored here for reloading when necessary
@@ -148,13 +149,13 @@ typedef struct {
int volume_max_db;
int no_sync; // disable synchronisation, even if it's available
int no_mmap; // disable use of mmap-based output, even if it's available
- double resyncthreshold; // if it gets out of whack my more than this number of seconds, resync.
+ double resyncthreshold; // if it get's out of whack my more than this number of seconds, resync.
// Zero means never
// resync.
int allow_session_interruption;
int timeout; // while in play mode, exit if no packets of audio come in for more than this number
// of seconds . Zero means never exit.
- int dont_check_timeout; // this is used to maintain backward compatibility with the old -t option
+ int dont_check_timeout; // this is used to maintain backward compatability with the old -t option
// behaviour; only set by -t 0, cleared by everything else
char *output_name;
audio_output *output;
@@ -176,6 +177,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;
@@ -194,7 +196,7 @@ typedef struct {
char *configfile;
char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by
// default “_raop._tcp.”.
- char *interface; // a string containing the interface name, or NULL if nothing specified
+ char *interface; // a string containg the interface name, or NULL if nothing specified
int interface_index; // only valid if the interface string is non-NULL
double audio_backend_buffer_desired_length; // this will be the length in seconds of the
// audio backend buffer -- the DAC buffer for ALSA
@@ -266,6 +268,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
@@ -308,10 +314,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);
@@ -357,6 +368,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);
@@ -378,6 +396,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,
diff --git a/dbus-service.c b/dbus-service.c
index 2393675b..76cc0456 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -359,6 +359,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.");
@@ -698,6 +711,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",
@@ -854,6 +870,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/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml
index cea574bc..e4a7d1e6 100644
--- a/org.gnome.ShairportSync.xml
+++ b/org.gnome.ShairportSync.xml
@@ -22,6 +22,7 @@
+
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 5e6089aa..6bca6d0e 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -96,7 +96,7 @@ alsa =
// disable_standby_mode_silence_threshold = 0.040; // Use this optional advanced setting to control how little audio should remain in the output buffer before the disable_standby code should start sending silence to the output device.
// disable_standby_mode_silence_scan_interval = 0.004; // Use this optional advanced setting to control how often the amount of audio remaining in the output buffer should be checked.
};
-
+G
// Parameters for the "sndio" audio back end. All are optional.
// For this section to be operative, Shairport Sync must be built with the following configuration flag:
// --with-sndio
@@ -224,6 +224,7 @@ 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.
diff --git a/shairport.c b/shairport.c
index 54647976..603446f0 100644
--- a/shairport.c
+++ b/shairport.c
@@ -576,6 +576,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)
@@ -1365,6 +1376,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
From 1385c5dfa7d4477c3be6b62677b83eb5c5e9f73a Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 17 Sep 2019 12:23:00 +0100
Subject: [PATCH 38/59] add an example of the file and line number
---
documents/sample dbus commands | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/documents/sample dbus commands b/documents/sample dbus commands
index 23804159..2e88003d 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
From 615e41d4c88299651bbe922788f425d567507f91 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Tue, 17 Sep 2019 12:26:05 +0100
Subject: [PATCH 39/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index bb2bc9d8..6f3ad451 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -2,6 +2,7 @@ Version 3.3.2d6
====
**Enhancements**
* Normalise 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.
**Bug Fixes**
* 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.
From e2294decb117abfcbd6987e8bc5526168cc5372f Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 22 Sep 2019 21:04:07 +0100
Subject: [PATCH 40/59] Squashed commit of the following:
commit 3dda7928b6df6ba7febc20f22faff5c8be024ef4
Author: Mike Brady
Date: Sun Sep 22 20:59:50 2019 +0100
Update RELEASENOTES-DEVELOPMENT.md
commit 153eb84cff292bd127c516e6486ba9c1f9a3ce16
Author: Mike Brady
Date: Sat Sep 21 19:30:26 2019 +0100
Update RELEASENOTES-DEVELOPMENT.md
commit b574d49b37f874cc7b91163a81fcb9dc6c760ef4
Author: Mike Brady
Date: Sat Sep 21 19:03:13 2019 +0100
Update RELEASENOTES-DEVELOPMENT.md
commit bce0613283e233ddfa24d080adff3450b4ddd909
Author: Mike Brady
Date: Sat Sep 21 18:59:18 2019 +0100
Update configure.ac
commit 7dc145361186806a560495d8171a387f5856f2f0
Author: Mike Brady
Replace the code for determining when resends should be sent for missing audio packets.
Multiple sequential resend requests are now rolled up into one.
Should address Issue #865.
---
RELEASENOTES-DEVELOPMENT.md | 9 +++
common.h | 3 +
player.c | 154 +++++++++++++++++++++++++-----------
player.h | 5 +-
shairport.c | 3 +
5 files changed, 128 insertions(+), 46 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 6f3ad451..a6b17146 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,12 @@
+Version 3.3.3d7
+====
+
+**Duh**
+* Versions 3.3.2d3 to 3.3.2d6 should have been labelled 3.3.3d\.
+
+**Enhancement**
+* 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.
+
Version 3.3.2d6
====
**Enhancements**
diff --git a/common.h b/common.h
index 2bb2ec09..f05f107e 100644
--- a/common.h
+++ b/common.h
@@ -100,6 +100,9 @@ enum sps_format_t {
const char *sps_format_description_string(enum sps_format_t format);
typedef struct {
+ double resend_wait_before_check; // wait this long before asking for a missing packet to be resent
+ double resend_wait_between_checks; // wait this long between making requests
+ double resend_last_check_before_use; // if the packet is missing this close to the time of use, give up
pthread_mutex_t lock;
config_t *cfg;
int endianness;
diff --git a/player.c b/player.c
index 9ad4d051..e8039065 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) {
+ 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;
@@ -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,9 +478,10 @@ 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) &&
@@ -496,16 +531,15 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
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);
@@ -522,8 +556,9 @@ 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->resend_time = 0;
abuf->given_timestamp = 0;
abuf->sequence_number = 0;
}
@@ -553,17 +588,17 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
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->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->resend_request_number = 0;
abuf->given_timestamp = 0;
abuf->sequence_number = 0;
}
@@ -573,39 +608,69 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
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_wait_before_check * (uint64_t)0x100000000);
+ uint64_t resend_repeat_interval = (uint64_t)(config.resend_wait_between_checks * (uint64_t)0x100000000);
+ uint64_t minimum_remaining_time = (uint64_t)((config.resend_last_check_before_use + 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;
+ }
+
+ int missing_frame_run_count = 0;
+ int start_of_missing_frame_run = -1;
+ int number_of_missing_frames = 0;
+ debug(2,"check start");
+ while (x != conn->ab_write) {
+ abuf_t *check_buf = conn->audio_buffer + BUFIDX(x);
+ if (!check_buf->ready) {
+ 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_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(2,"Frame %d is missing with ab_read of %d.", x, conn->ab_read);
}
+ // 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;
}
}
}
@@ -933,7 +998,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;
@@ -1330,7 +1395,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);
@@ -1928,8 +1992,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, after %d resend requests.",
+ SUCCESSOR(conn->last_seqno_read), play_number, inframe->resend_request_number);
conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
0xffff); // manage the packet out of sequence minder
@@ -2472,6 +2536,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)) {
diff --git a/player.h b/player.h
index 724d5a78..0d89da44 100644
--- a/player.h
+++ b/player.h
@@ -39,8 +39,9 @@ typedef uint16_t seq_t;
typedef struct audio_buffer_entry { // decoded audio packets
int ready;
- int resend_level;
- // int64_t timestamp;
+ 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
+ int resend_request_number;
seq_t sequence_number;
uint32_t given_timestamp; // for debugging and checking
signed short *data;
diff --git a/shairport.c b/shairport.c
index 603446f0..2ed31deb 100644
--- a/shairport.c
+++ b/shairport.c
@@ -402,6 +402,9 @@ 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_wait_before_check = 0.10; // wait this many seconds before requesting the resending of a missing packet
+ config.resend_wait_between_checks = 0.25; // wait this many seconds before again requesting the resending of a missing packet
+ config.resend_last_check_before_use = 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";
From cd25d6a83652e38b162f27f356b03ad4e580a378 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 22 Sep 2019 21:09:57 +0100
Subject: [PATCH 41/59] Set the debug level of the silent frame message to 2.
---
player.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/player.c b/player.c
index e8039065..a42536dc 100644
--- a/player.c
+++ b/player.c
@@ -1992,7 +1992,7 @@ void *player_thread_func(void *arg) {
// debug(3, "Play frame %d.", play_number);
conn->play_number_after_flush++;
if (inframe->given_timestamp == 0) {
- debug(1, "Player has supplied a silent frame, (possibly frame %u) for play number %d, after %d resend requests.",
+ debug(2, "Player has supplied a silent frame, (possibly frame %u) for play number %d, after %d resend requests.",
SUCCESSOR(conn->last_seqno_read), play_number, inframe->resend_request_number);
conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
0xffff); // manage the packet out of sequence minder
From 1fcf7f66ab9c85c179ac999b62183a2468315328 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 22 Sep 2019 21:11:12 +0100
Subject: [PATCH 42/59] Rewrite the resend code to improve the logic and roll
up sequential resend requests into one.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index d9a29464..f1d47933 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.2d6], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.3d7], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From a1137064a440f24a5bcab3d33c6838080e8e2fbf Mon Sep 17 00:00:00 2001
From: sidneys
Date: Wed, 25 Sep 2019 22:46:04 +0200
Subject: [PATCH 43/59] fix(player.c): increase the log level of the constantly
repeated resend check startup message ("check start") from "2" to "3" as to
maintain readabiility of log verbosity level 2.
---
player.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/player.c b/player.c
index a42536dc..31098598 100644
--- a/player.c
+++ b/player.c
@@ -628,7 +628,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
int missing_frame_run_count = 0;
int start_of_missing_frame_run = -1;
int number_of_missing_frames = 0;
- debug(2,"check start");
+ debug(3,"check start");
while (x != conn->ab_write) {
abuf_t *check_buf = conn->audio_buffer + BUFIDX(x);
if (!check_buf->ready) {
From 686fc2a5c1952588b0fee04be361201696a476ba Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 10 Oct 2019 09:23:20 +0100
Subject: [PATCH 44/59] Add missing frame status field, fix a bug with the
first missing frame variable, expose the settings for resending, remove some
commented out code.
---
common.c | 1 +
common.h | 6 +--
player.c | 87 +++++++++++++------------------------
player.h | 9 ++--
rtp.c | 10 ++---
rtsp.c | 32 +++++---------
scripts/shairport-sync.conf | 4 ++
shairport.c | 44 +++++++++++++++++--
8 files changed, 100 insertions(+), 93 deletions(-)
diff --git a/common.c b/common.c
index 2cadc203..ff643ef3 100644
--- a/common.c
+++ b/common.c
@@ -1299,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); }
diff --git a/common.h b/common.h
index f05f107e..b50d4844 100644
--- a/common.h
+++ b/common.h
@@ -100,9 +100,9 @@ enum sps_format_t {
const char *sps_format_description_string(enum sps_format_t format);
typedef struct {
- double resend_wait_before_check; // wait this long before asking for a missing packet to be resent
- double resend_wait_between_checks; // wait this long between making requests
- double resend_last_check_before_use; // if the packet is missing this close to the time of use, give up
+ 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;
diff --git a/player.c b/player.c
index 31098598..e98331f4 100644
--- a/player.c
+++ b/player.c
@@ -139,7 +139,7 @@ static void ab_resync(rtsp_conn_info *conn) {
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) {
+ if (x < start) { // this means that it's up around the wrap
if (pos)
*pos = UINT16_MAX - start + 1 + x;
} else {
@@ -483,7 +483,6 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
conn->packet_count_since_flush++;
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
@@ -494,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);
@@ -514,21 +501,6 @@ 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...)
if (conn->input_frame_rate_starting_point_is_valid == 0) {
@@ -538,17 +510,11 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
conn->input_frame_rate_starting_point_is_valid = 1; // valid now
}
}
-
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);
@@ -558,6 +524,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
abuf->ready = 0; // to be sure, to be sure
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;
@@ -567,24 +534,12 @@ 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;
@@ -592,28 +547,29 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
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->given_timestamp = actual_timestamp;
abuf->sequence_number = seqno;
} else {
debug(1, "Bad audio packet detected and discarded.");
abuf->ready = 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.");
// resend checks
{
- uint64_t minimum_wait_time = (uint64_t)(config.resend_wait_before_check * (uint64_t)0x100000000);
- uint64_t resend_repeat_interval = (uint64_t)(config.resend_wait_between_checks * (uint64_t)0x100000000);
- uint64_t minimum_remaining_time = (uint64_t)((config.resend_last_check_before_use + config.audio_backend_buffer_desired_length)* (uint64_t)0x100000000);
+ 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;
@@ -624,19 +580,36 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
} 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;
- debug(3,"check start");
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;
@@ -646,7 +619,7 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in
}
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(2,"Frame %d is missing with ab_read of %d.", x, conn->ab_read);
+ 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);
@@ -1992,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(2, "Player has supplied a silent frame, (possibly frame %u) for play number %d, after %d resend requests.",
- SUCCESSOR(conn->last_seqno_read), play_number, inframe->resend_request_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
diff --git a/player.h b/player.h
index 0d89da44..9246f448 100644
--- a/player.h
+++ b/player.h
@@ -38,13 +38,14 @@ typedef struct time_ping_record {
typedef uint16_t seq_t;
typedef struct audio_buffer_entry { // decoded audio packets
- int ready;
+ 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
- int resend_request_number;
- seq_t sequence_number;
uint32_t given_timestamp; // for debugging and checking
- signed short *data;
int length; // the length of the decoded data
} abuf_t;
diff --git a/rtp.c b/rtp.c
index 2e1e7f9f..6a90a5cd 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 1b198c69..ded7035f 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -591,15 +591,6 @@ 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);
reply = rtsp_read_request_response_immediate_shutdown_requested;
@@ -684,16 +675,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 +692,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;
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 6bca6d0e..54e22e87 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
diff --git a/shairport.c b/shairport.c
index 2ed31deb..84c3e1f5 100644
--- a/shairport.c
+++ b/shairport.c
@@ -402,9 +402,9 @@ 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_wait_before_check = 0.10; // wait this many seconds before requesting the resending of a missing packet
- config.resend_wait_between_checks = 0.25; // wait this many seconds before again requesting the resending of a missing packet
- config.resend_last_check_before_use = 0.10; // give up if the packet is still missing this close to when it's needed
+ 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";
@@ -766,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))
From cb1b15ed708519351d47d3a6a124bcec3af1aeee Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 10 Oct 2019 09:24:17 +0100
Subject: [PATCH 45/59] Add missing frame status field, fix a bug with the
first missing frame variable, expose the settings for resending, remove some
commented out code.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index f1d47933..b8e15cf8 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.3d7], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.3d8], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From b87b7a8b1f324a0cc22641eaf6ee3604c1a854dd Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 10 Oct 2019 10:02:18 +0100
Subject: [PATCH 46/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index a6b17146..6822b768 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,17 @@
+Version 3.3.3d8
+====
+
+**Bug Fix**
+* Fix a bug in the resend request logic.
+
+
+**Enhancement**
+* Expose the 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.
+
+
Version 3.3.3d7
====
From 6c57ce97d5fd8aac9904eeb0bfe7821599493415 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 20 Oct 2019 15:43:31 +0100
Subject: [PATCH 47/59] Add two cover_art control settings -- the cache
directory to use, (or the empty string to prevent caching) and a flag to
retain all art work
---
common.h | 1 +
metadata_hub.c | 202 ++++++++++++++++++------------------
scripts/shairport-sync.conf | 2 +
shairport.c | 13 +++
4 files changed, 119 insertions(+), 99 deletions(-)
diff --git a/common.h b/common.h
index b50d4844..8308e5f3 100644
--- a/common.h
+++ b/common.h
@@ -126,6 +126,7 @@ typedef struct {
int metadata_sockport;
size_t metadata_sockmsglength;
int get_coverart;
+ int retain_coverart;
#endif
#ifdef CONFIG_MQTT
int mqtt_enabled;
diff --git a/metadata_hub.c b/metadata_hub.c
index e125fc15..0b5aa412 100644
--- a/metadata_hub.c
+++ b/metadata_hub.c
@@ -170,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
+ 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];
+ 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_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_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
+ #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-";
+ 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);
+ 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);
+ 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;
+ // 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);
+ 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;
}
@@ -455,7 +459,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
metadata_hub_modify_prolog();
debug(2, "MH Picture received, length %u bytes.", length);
char uri[2048];
- if (length > 16) {
+ 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);
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 54e22e87..91dc3d5f 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -191,6 +191,8 @@ 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
+// 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.
// 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"
diff --git a/shairport.c b/shairport.c
index 84c3e1f5..4726682d 100644
--- a/shairport.c
+++ b/shairport.c
@@ -836,6 +836,19 @@ int parse_options(int argc, char **argv) {
config.metadata_pipename = (char *)str;
}
+ 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, "metadata.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\"");
+ }
if (config_lookup_string(config.cfg, "metadata.socket_address", &str)) {
config.metadata_sockaddr = (char *)str;
}
From cfe25834a6e4973112c8f4c4c41a48475ec0f0eb Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 20 Oct 2019 15:44:20 +0100
Subject: [PATCH 48/59] Add two cover_art control settings -- the cache
directory to use, (or the empty string to prevent caching) and a flag to
retain all art work
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index b8e15cf8..dd174da0 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.3d8], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.3d9], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 460e73dc63d10981397b526d4d6e7ce451fd1da6 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 20 Oct 2019 16:43:28 +0100
Subject: [PATCH 49/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 6822b768..32fb6a02 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,11 @@
+Version 3.3.3d9
+====
+
+**Enhamcements**
+* Expose two `metadata` settings related to handling cover art:
+ 1. 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.
+ 2. The setting `retain_cover_art` is normally set to `"no"` so that cover art cache files are deleted when no longer needed. Set it `"yes"` to retain them permanently. Be aware that cover art files might take up considerable space after a while.
+
Version 3.3.3d8
====
From 41de3e5c39d0fb6006137f24a07772c6865871db Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 20 Oct 2019 22:04:29 +0100
Subject: [PATCH 50/59] Fix a small conditional compilation flag error
---
shairport.c | 25 ++++++++++++++-----------
1 file changed, 14 insertions(+), 11 deletions(-)
diff --git a/shairport.c b/shairport.c
index 4726682d..8657e801 100644
--- a/shairport.c
+++ b/shairport.c
@@ -836,6 +836,20 @@ int parse_options(int argc, char **argv) {
config.metadata_pipename = (char *)str;
}
+ if (config_lookup_string(config.cfg, "metadata.socket_address", &str)) {
+ config.metadata_sockaddr = (char *)str;
+ }
+ if (config_lookup_int(config.cfg, "metadata.socket_port", &value)) {
+ config.metadata_sockport = value;
+ }
+ config.metadata_sockmsglength = 500;
+ if (config_lookup_int(config.cfg, "metadata.socket_msglength", &value)) {
+ config.metadata_sockmsglength = value < 500 ? 500 : value > 65000 ? 65000 : value;
+ }
+
+#endif
+
+#ifdef CONFIG_METADATA_HUB
if (config_lookup_string(config.cfg, "metadata.cover_art_cache_directory", &str)) {
config.cover_art_cache_dir = (char *)str;
}
@@ -849,17 +863,6 @@ int parse_options(int argc, char **argv) {
die("Invalid metadata retain_cover_art option choice \"%s\". It should be \"yes\" or "
"\"no\"");
}
- if (config_lookup_string(config.cfg, "metadata.socket_address", &str)) {
- config.metadata_sockaddr = (char *)str;
- }
- if (config_lookup_int(config.cfg, "metadata.socket_port", &value)) {
- config.metadata_sockport = value;
- }
- config.metadata_sockmsglength = 500;
- if (config_lookup_int(config.cfg, "metadata.socket_msglength", &value)) {
- config.metadata_sockmsglength = value < 500 ? 500 : value > 65000 ? 65000 : value;
- }
-
#endif
if (config_lookup_string(config.cfg, "sessioncontrol.run_this_before_play_begins", &str)) {
From aa57f06ce321edcfda23d9f4f760644b1318d408 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sun, 20 Oct 2019 22:11:51 +0100
Subject: [PATCH 51/59] Move the retain_coverart definition to under a more
appropriate compilation flag
---
common.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/common.h b/common.h
index 8308e5f3..bb6eedd1 100644
--- a/common.h
+++ b/common.h
@@ -126,7 +126,6 @@ typedef struct {
int metadata_sockport;
size_t metadata_sockmsglength;
int get_coverart;
- int retain_coverart;
#endif
#ifdef CONFIG_MQTT
int mqtt_enabled;
@@ -253,6 +252,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
From 9745d794c9a0aa74622e0f659808cabef80f6e31 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Thu, 24 Oct 2019 10:35:27 +0100
Subject: [PATCH 52/59] Update shairport-sync.conf
---
scripts/shairport-sync.conf | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 91dc3d5f..025347d8 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -100,7 +100,7 @@ alsa =
// disable_standby_mode_silence_threshold = 0.040; // Use this optional advanced setting to control how little audio should remain in the output buffer before the disable_standby code should start sending silence to the output device.
// disable_standby_mode_silence_scan_interval = 0.004; // Use this optional advanced setting to control how often the amount of audio remaining in the output buffer should be checked.
};
-G
+
// Parameters for the "sndio" audio back end. All are optional.
// For this section to be operative, Shairport Sync must be built with the following configuration flag:
// --with-sndio
@@ -222,7 +222,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 =
From eec06397269a2d73458296579444c46c0405f4c0 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 26 Oct 2019 10:40:10 +0100
Subject: [PATCH 53/59] Fix crash when a non-RTSP packet is sent to the
session's RTSP port.
---
rtsp.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index ded7035f..b303faa8 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,13 +570,15 @@ 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;
@@ -622,6 +625,17 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
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;
@@ -629,8 +643,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
msg_size = msg_handle_line(the_packet, buf);
if (!(*the_packet)) {
- warn("no RTSP header received");
- reply = rtsp_read_request_response_bad_packet;
+ warn("Closing the session because an RTSP header was missing in an incoming packet.");
+ reply = rtsp_read_request_response_read_error;
goto shutdown;
}
From 85a3fe6ac48c7d8c1a7ca849a1a98d9924da5325 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 26 Oct 2019 10:40:37 +0100
Subject: [PATCH 54/59] Fix crash when a non-RTSP packet is sent to the
session's RTSP port.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index dd174da0..59756dab 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.3d9], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.3d10], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 86174aa27ebe7b098a86b0b85d32367e4feaf304 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 26 Oct 2019 10:50:13 +0100
Subject: [PATCH 55/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 32fb6a02..2ac400d5 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,10 +1,14 @@
+Version 3.3.3d10
+====
+**Bug Fix**
+* Fixed a crashing bug -- 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.
+
Version 3.3.3d9
====
-**Enhamcements**
-* Expose two `metadata` settings related to handling cover art:
- 1. 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.
- 2. The setting `retain_cover_art` is normally set to `"no"` so that cover art cache files are deleted when no longer needed. Set it `"yes"` to retain them permanently. Be aware that cover art files might take up considerable space after a while.
+**Enhancements**
+* Expose a `metadata` setting 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.
Version 3.3.3d8
====
From 501ad047c44445a5d80436c4f6176fb3dbe05e05 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 26 Oct 2019 11:05:55 +0100
Subject: [PATCH 56/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 2ac400d5..9050870c 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,7 +1,7 @@
Version 3.3.3d10
====
**Bug Fix**
-* Fixed a crashing bug -- 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.
+* Fixed a crashing bug -- 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).
Version 3.3.3d9
====
From e0ffb18dbfcc0af2623950152ee40c2ccd6cbeeb Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 26 Oct 2019 16:11:54 +0100
Subject: [PATCH 57/59] Modify behaviour when receiving an invalid RTSP packet
to be the same as a genuine AirPort Express, but time out after two minutes
anyway. Also move the retain_artwork flag to diagnostics.
---
rtsp.c | 35 ++++++++++++++++++-----------------
scripts/shairport-sync.conf | 2 +-
shairport.c | 2 +-
3 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index b303faa8..757bcd36 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -585,7 +585,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
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);
@@ -595,7 +595,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
while (msg_size < 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;
}
@@ -604,7 +604,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
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;
}
@@ -613,7 +613,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
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) {
@@ -643,8 +643,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
msg_size = msg_handle_line(the_packet, buf);
if (!(*the_packet)) {
- warn("Closing the session because an RTSP header was missing in an incoming packet.");
- reply = rtsp_read_request_response_read_error;
+ 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;
}
@@ -657,7 +657,7 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes
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;
}
@@ -2409,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) {
@@ -2470,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 025347d8..bec0d4fc 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -192,7 +192,6 @@ 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
-// 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.
// 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"
@@ -234,4 +233,5 @@ diagnostics =
// 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/shairport.c b/shairport.c
index 8657e801..4216d5bb 100644
--- a/shairport.c
+++ b/shairport.c
@@ -854,7 +854,7 @@ int parse_options(int argc, char **argv) {
config.cover_art_cache_dir = (char *)str;
}
- if (config_lookup_string(config.cfg, "metadata.retain_cover_art", &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)
From f93871d5b52ca179d00ba79145aad3ac653f0a65 Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 26 Oct 2019 16:13:07 +0100
Subject: [PATCH 58/59] Modify behaviour when receiving an invalid RTSP packet
to be the same as a genuine AirPort Express, but time out after two minutes
anyway. Also move the retain_artwork flag to diagnostics.
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 59756dab..2ee37eea 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.3d10], [mikebrady@eircom.net])
+AC_INIT([shairport-sync], [3.3.3d11], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
From 0e85ec370a08c3443b0b9e28707d1022efc0f74f Mon Sep 17 00:00:00 2001
From: Mike Brady
Date: Sat, 26 Oct 2019 16:22:02 +0100
Subject: [PATCH 59/59] Update RELEASENOTES-DEVELOPMENT.md
---
RELEASENOTES-DEVELOPMENT.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 9050870c..3816fe18 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,8 +1,11 @@
-Version 3.3.3d10
+Version 3.3.3d11
====
**Bug Fix**
* Fixed a crashing bug -- 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).
+**Pesky Change**
+* The setting `retain_cover_art` is now 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.3d9
====