From 61f1730402e215f69b17469c5e50e407dbc2c5af Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 25 Apr 2026 11:23:41 +0100
Subject: [PATCH 01/67] Update release notes for version 5.0.3 [skip ci]
---
RELEASENOTES-DEVELOPMENT.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index b12b9aa9..83b603a3 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,5 +1,8 @@
Version 5.0.3-dev-4-g07ca1891
==
+**Release**
+* This is effectively release 5.0.3
+
**Bug Fix**
* Make code using `getifaddrs()` uncancellable to prevent a mysterious `Unexpected error 9 on netlink descriptor` error. Thanks to [microfx](https://github.com/microfx) for reporting the [issue](https://github.com/mikebrady/shairport-sync/issues/2184).
From eba76c21af5401ed4cf719417229976d3401c61a Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 25 Apr 2026 11:33:40 +0100
Subject: [PATCH 02/67] 5.0.4-dev
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index 1b99df38..74d13192 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [5.0.3-dev], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [5.0.4-dev], [4265913+mikebrady@users.noreply.github.com])
: ${CFLAGS="-O3"}
: ${CXXFLAGS="-O3"}
AM_INIT_AUTOMAKE([subdir-objects])
From 11052b789630295f259e8622bbf748c0b49da2fc Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 26 Apr 2026 10:23:18 +0100
Subject: [PATCH 03/67] Lot of changes here.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. check_classic_mac_basic.yml — update to latest macOS-latest
2. docker-on-push-tag-or-pr — dependant updates
3. FFTConvolver — fix a compilation warning when the DEBUG flag is turned off
4. Reorganise some source files — e.g. move most of the metadata stuff into a subdirectory
5. Remove unused special-purpose AirPlay 2 RC event handler.
6. Reorganise source files and source code relating to the standard event handler.
7. Check configuration settings a little more carefully to offer more meaningful diagnostics.
8. Add password to AirPlay 2 and reorganise initialisation to set AirPlay 2 flags accordingly. Also, remove redundant airplay_pin.
9. Remove redundant debug facility definitions from common.h
10. Make it possible to omit all the dacp stuff from a build.
11. Make it possible to omit metadata multicast from a build.
12. Add AP2-native metadata by setting bit 50 of features to true.
13. Improve stability when pre-empting an existing play session.
14. Fix a few compiler warnings in the pair_ap library.
15. Ensure that the audio format is known before starting synchronisation.
16. Improve handle_configure to set/clear a new config setting. Doesn’t seem to do anything…
17. Experimentally turn off bit 46 of AirPlay 2 features — seems to stop failing pair-verify attempts.
---
.github/workflows/check_classic_mac_basic.yml | 4 +-
.../workflows/docker-on-push-tag-or-pr.yaml | 6 +-
FFTConvolver/ConvolverThreadPool.cpp | 41 +
FFTConvolver/convolver.cpp | 30 +-
Makefile.am | 24 +-
README.md | 2 +-
RELEASENOTES-DEVELOPMENT.md | 44 +-
activity_monitor.c | 7 +-
ap2_buffered_audio_processor.c | 41 +-
ap2_event_message_handler.c | 140 ++
ap2_event_message_handler.h | 14 +
ap2_event_receiver.c | 180 +-
ap2_rc_event_receiver.c | 161 --
ap2_rc_event_receiver.h | 8 -
audio.c | 4 +-
audio_alsa.c | 12 +-
bonjour_strings.c | 2 +-
common.c | 56 +-
common.h | 17 +-
configure.ac | 59 +-
dacp.c | 6 +-
dbus-service.c | 80 +-
definitions.h | 4 +
mdns_avahi.c | 6 +-
metadata/core.c | 310 ++++
metadata/core.h | 39 +
metadata_hub.c => metadata/hub.c | 65 +-
metadata_hub.h => metadata/hub.h | 9 +
metadata/multicast.c | 180 ++
metadata/multicast.h | 10 +
metadata/pc_queue.c | 172 ++
metadata/pc_queue.h | 26 +
metadata/pipe.c | 186 ++
metadata/pipe.h | 10 +
mpris-service.c | 18 +-
mqtt.c | 74 +-
mqtt.h | 6 +
org.gnome.ShairportSync.xml | 2 +-
pair_ap/pair.c | 2 +-
pair_ap/pair_fruit.c | 10 +-
pair_ap/pair_homekit.c | 8 +-
player.c | 182 +-
player.h | 6 +-
rtp.c | 9 +-
rtsp.c | 1559 ++++-------------
rtsp.h | 34 +-
scripts/shairport-sync.conf | 2 +-
shairport.c | 225 +--
utilities/debug.c | 121 +-
utilities/generate_device_uuid.c | 48 +
utilities/generate_device_uuid.h | 3 +
utilities/generate_random_uuid.c | 47 +
utilities/generate_random_uuid.h | 6 +
utilities/network_utilities.c | 4 +-
utilities/rtsp_message_utilities.c | 265 +++
utilities/rtsp_message_utilities.h | 33 +
56 files changed, 2672 insertions(+), 1947 deletions(-)
create mode 100644 ap2_event_message_handler.c
create mode 100644 ap2_event_message_handler.h
delete mode 100644 ap2_rc_event_receiver.c
delete mode 100644 ap2_rc_event_receiver.h
create mode 100644 metadata/core.c
create mode 100644 metadata/core.h
rename metadata_hub.c => metadata/hub.c (91%)
rename metadata_hub.h => metadata/hub.h (94%)
create mode 100644 metadata/multicast.c
create mode 100644 metadata/multicast.h
create mode 100644 metadata/pc_queue.c
create mode 100644 metadata/pc_queue.h
create mode 100644 metadata/pipe.c
create mode 100644 metadata/pipe.h
create mode 100644 utilities/generate_device_uuid.c
create mode 100644 utilities/generate_device_uuid.h
create mode 100644 utilities/generate_random_uuid.c
create mode 100644 utilities/generate_random_uuid.h
create mode 100644 utilities/rtsp_message_utilities.c
create mode 100644 utilities/rtsp_message_utilities.h
diff --git a/.github/workflows/check_classic_mac_basic.yml b/.github/workflows/check_classic_mac_basic.yml
index e0288d77..44f0def6 100644
--- a/.github/workflows/check_classic_mac_basic.yml
+++ b/.github/workflows/check_classic_mac_basic.yml
@@ -9,9 +9,7 @@ on:
jobs:
build:
-
- runs-on: macos-13
-
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v6.0.2
- name: Install Dependencies
diff --git a/.github/workflows/docker-on-push-tag-or-pr.yaml b/.github/workflows/docker-on-push-tag-or-pr.yaml
index de8f857e..c3380f4e 100644
--- a/.github/workflows/docker-on-push-tag-or-pr.yaml
+++ b/.github/workflows/docker-on-push-tag-or-pr.yaml
@@ -59,7 +59,7 @@ jobs:
fi
- name: Login to Docker Registry
- uses: docker/login-action@v3.7.0
+ uses: docker/login-action@v4.1.0
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_REGISTRY_USER }}
@@ -67,13 +67,13 @@ jobs:
if: needs.docker-vars.outputs.push_docker_image == 'true'
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3.7.0
+ uses: docker/setup-qemu-action@v4.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4.0.0
- name: Build and push ${{ matrix.name }}
- uses: docker/build-push-action@v7.0.0
+ uses: docker/build-push-action@v7.1.0
env:
registry_and_name: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE_NAME }}
with:
diff --git a/FFTConvolver/ConvolverThreadPool.cpp b/FFTConvolver/ConvolverThreadPool.cpp
index 8287417e..ef969572 100644
--- a/FFTConvolver/ConvolverThreadPool.cpp
+++ b/FFTConvolver/ConvolverThreadPool.cpp
@@ -1,7 +1,37 @@
+/*
+ * Convolver Thread Pool. This file is part of Shairport Sync
+ * Copyright (c) Mike Brady 2026
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
#include "ConvolverThreadPool.h"
#include "FFTConvolver.h"
#include "config.h"
+extern "C" void _debug(const char *filename, const int linenumber, int level, const char *format,
+ ...);
+#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__)
+
ConvolverThreadPool::ConvolverThreadPool()
: _convolvers(), _threads(), _taskQueue(), _queueMutex(), _condition(), _completionCV(),
_stop(false), _activeTasks(0) {}
@@ -105,6 +135,16 @@ void ConvolverThreadPool::waitForAll() {
_completionCV.wait(lock, [this]() { return _taskQueue.empty() && _activeTasks == 0; });
}
+void ConvolverThreadPool::clearState(size_t convolverId) {
+ // Do the replacement assertion check first, and then wait for all tasks to stop
+ if (convolverId < _convolvers.size()) {
+ waitForAll();
+ } else {
+ debug(1, "assert(convolverId < _convolvers.size()) failed, with convolverId: %u and _convolvers.size(): %u.", convolverId, _convolvers.size());
+ }
+}
+
+/* this is the old version
void ConvolverThreadPool::clearState(size_t convolverId) {
assert(convolverId < _convolvers.size());
@@ -113,6 +153,7 @@ void ConvolverThreadPool::clearState(size_t convolverId) {
// _convolvers[convolverId]->clearState();
}
+*/
void ConvolverThreadPool::clearAllStates() {
// Make sure no tasks are running before clearing state
diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp
index 16eba015..c276d778 100644
--- a/FFTConvolver/convolver.cpp
+++ b/FFTConvolver/convolver.cpp
@@ -1,4 +1,28 @@
-
+/*
+ * Convolver. This file is part of Shairport Sync
+ * Copyright (c) Mike Brady 2026
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
#include "convolver.h"
#include "ConvolverThreadPool.h"
@@ -22,8 +46,8 @@ void convolver_pool_init(size_t numThreads, size_t numConvolvers) {
if (!pool.init(numThreads, numConvolvers)) {
debug(1, "failed to initialize thread pool!");
} else {
- debug(1, "thread pool initialized with %u threads and %u convolvers.", numThreads,
- numConvolvers);
+ debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads, numThreads == 1 ? "" : "s",
+ numConvolvers, numConvolvers == 1 ? "" : "s");
}
}
diff --git a/Makefile.am b/Makefile.am
index 1d5bf176..b5c724d6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -11,7 +11,7 @@ homedir = @HOME@
man_MANS = $(top_srcdir)/man/shairport-sync.1
# endif
-lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread
+lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread --include=utilities/debug.h
lib_tinyhttp_a_CFLAGS = -pthread
lib_dbus_interface_a_CFLAGS = -pthread
lib_mpris_interface_a_CFLAGS = -pthread
@@ -27,7 +27,7 @@ noinst_LIBRARIES =
# See below for the flags for the test client program
-shairport_sync_SOURCES = shairport.c bonjour_strings.c rtsp.c mdns.c common.c rtp.c player.c audio.c loudness.c activity_monitor.c utilities/debug.c utilities/network_utilities.c
+shairport_sync_SOURCES = shairport.c bonjour_strings.c rtsp.c mdns.c common.c rtp.c player.c audio.c loudness.c activity_monitor.c utilities/debug.c utilities/network_utilities.c utilities/rtsp_message_utilities.c
if BUILD_FOR_DARWIN
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-deprecated-declarations -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
@@ -132,8 +132,20 @@ if USE_DNS_SD
shairport_sync_SOURCES += mdns_dns_sd.c
endif
+if USE_METADATA
+shairport_sync_SOURCES += metadata/pc_queue.c metadata/core.c
+endif
+
+if USE_METADATA_PIPE
+shairport_sync_SOURCES += metadata/pipe.c
+endif
+
+if USE_METADATA_MULTICAST
+shairport_sync_SOURCES += metadata/multicast.c
+endif
+
if USE_METADATA_HUB
-shairport_sync_SOURCES += metadata_hub.c
+shairport_sync_SOURCES += metadata/hub.c
endif
if USE_MQTT
@@ -148,7 +160,11 @@ lib_tinyhttp_a_SOURCES = tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c
endif
if USE_AIRPLAY_2
-shairport_sync_SOURCES += ap2_buffered_audio_processor.c ap2_event_receiver.c ap2_rc_event_receiver.c ptp-utilities.c utilities/buffered_read.c utilities/structured_buffer.c utilities/mod23.c plists/get_info_response.c
+shairport_sync_SOURCES += ap2_buffered_audio_processor.c ap2_event_receiver.c \
+ ap2_event_message_handler.c ptp-utilities.c \
+ utilities/buffered_read.c utilities/structured_buffer.c utilities/mod23.c \
+ utilities/generate_random_uuid.c utilities/generate_device_uuid.c \
+ plists/get_info_response.c
shairport_sync_LDADD += lib_pair_ap.a
lib_pair_ap_a_SOURCES = pair_ap/pair.c pair_ap/pair_fruit.c pair_ap/pair_homekit.c pair_ap/pair-tlv.c
noinst_LIBRARIES += lib_pair_ap.a
diff --git a/README.md b/README.md
index 2e74fd8c..f03b97df 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Shairport Sync does not support AirPlay video or photo streaming.
* Next Steps and Advanced Topics are [here](ADVANCED%20TOPICS/README.md).
* Runtime settings are documented [here](scripts/shairport-sync.conf).
* Build configuration options are detailed in [CONFIGURATION FLAGS.md](CONFIGURATION%20FLAGS.md).
-* The `man` page, detailing command line options, is [here](https://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html).
+* The `man` page, detailing command line options, is [here](https://raw.githack.com/mikebrady/shairport-sync/development/man/shairport-sync.1.xml).
* Some advanced topics and developed in [ADVANCED TOPICS](https://github.com/mikebrady/shairport-sync/tree/master/ADVANCED%20TOPICS).
# Features
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index bf5e06e5..b12b9aa9 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,7 +1,49 @@
+Version 5.0.3-dev-4-g07ca1891
+==
+**Bug Fix**
+* Make code using `getifaddrs()` uncancellable to prevent a mysterious `Unexpected error 9 on netlink descriptor` error. Thanks to [microfx](https://github.com/microfx) for reporting the [issue](https://github.com/mikebrady/shairport-sync/issues/2184).
+
+Version 5.0.3-dev
+==
+**Small Changes**
+* Add warnings if string or boolean configuration parameters were present but not strings.
+* Improve behaviour when there is a large timing uncertainty/granularity and a large audio buffer. e.g. on some virtual machines.
+* Make the SoxR delay checker thread behave better when being cancelled.
+* Remove some debug messages, quieten some others, add a few.
+
+Version 5.0.1+-38-gf784e463
+==
+**Build Update**
+* The Apple ALAC Decoder can no longer be included in an AirPlay 2 build. The reason is that FFmpeg -- used instead -- deals comprehensively with ALAC, AAC, multichannel, mixdown and rate transcoding and is well maintained.
+
+ The Apple ALAC Decoder is known to have security issues and is no longer maintained. This update prevents it from being included in Shairport Sync for AirPlay 2.
+
+Version 5.0.1+-36-g6729802d
+==
+**Update**
+* The Shairport Sync `systemd` service file has been updated as follows:
+ * It uses POSIX shell parameter expansion functionality to prevent adding leading space in option values.
+ * It adds `nqptp.service` to the `After` and `Requires` options when building for Airplay 2.
+
+ Many thanks to [mikelei8291](https://github.com/mikelei8291) for the [PR](https://github.com/mikebrady/shairport-sync/pull/2174).
+
+Version 5.0.1+-32-g20a87fef
+==
+**Build Bugfix**
+* Fix a bug at the `.configure...` stage of a build of Classic Shairport Sync whereby the UUID library was incorrectly required if the FFmpeg library was being used. (The wording on a few associated warning messages was also fixed.) Thanks to [phexian](https://github.com/phexian) for the [report](https://github.com/mikebrady/shairport-sync/issues/2177).
+
+**Dependabot Update**
+* A Dependabot update to a building tool was added.
+
+Version 5.0.1+-23-g7fb3506e
+==
+**Documentation Update**
+* Fixed dead link to the online [`man`](https://raw.githack.com/mikebrady/shairport-sync/development/man/shairport-sync.1.xml) page. Thanks to [CornelisJ](https://github.com/CornelisJ) for the [report](https://github.com/mikebrady/shairport-sync/issues/2158).
+
Version 5.0.1+-13-ga14517d7
==
**Change**
-* When built for AirPlay 2, add pw=true to the Bonjour raop properties if the classic AirPlay password is set. This might help with Owntone compatibility.
+* When built for AirPlay 2, add `pw=true` to the Bonjour raop properties if the classic AirPlay password is set. This might help with Owntone compatibility.
Version 5.0.1+-11-g07546c35
==
diff --git a/activity_monitor.c b/activity_monitor.c
index bdd81c59..06fe4fee 100644
--- a/activity_monitor.c
+++ b/activity_monitor.c
@@ -40,7 +40,10 @@
#include "activity_monitor.h"
#include "common.h"
-#include "rtsp.h"
+
+#ifdef CONFIG_METADATA
+#include "metadata/core.h"
+#endif
#ifdef CONFIG_DBUS_INTERFACE
#include "dbus-service.h"
@@ -128,7 +131,7 @@ void activity_monitor_signify_activity(int active) {
pthread_mutex_unlock(&activity_monitor_mutex);
}
// lock the mutex again to send a signal
- pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 1);
+ pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 4);
pthread_cond_signal(&activity_monitor_cv);
pthread_cleanup_pop(1); // release the mutex
}
diff --git a/ap2_buffered_audio_processor.c b/ap2_buffered_audio_processor.c
index 30f3b00f..09581ded 100644
--- a/ap2_buffered_audio_processor.c
+++ b/ap2_buffered_audio_processor.c
@@ -235,6 +235,7 @@ void *rtp_buffered_audio_processor(void *arg) {
uint16_t data_len;
// here we read from the buffer that our thread has been reading
+ // debug(1,"read a block");
size_t bytes_remaining_in_buffer;
nread =
read_sized_block(buffered_audio, &data_len, sizeof(data_len), &bytes_remaining_in_buffer);
@@ -249,6 +250,7 @@ void *rtp_buffered_audio_processor(void *arg) {
// get the block itself
// debug(1,"buffered audio packet of size %u detected.", data_len - 2);
nread = read_sized_block(buffered_audio, packet, data_len - 2, &bytes_remaining_in_buffer);
+ // debug(1,"block read");
// diagnostic
if ((conn->ap2_audio_buffer_minimum_size < 0) ||
@@ -442,7 +444,7 @@ void *rtp_buffered_audio_processor(void *arg) {
// to decode it and pass it to the player
if (new_audio_block_needed == 0) {
// is there space in the player thread's buffer system?
- size_t player_buffer_occupancy = get_audio_buffer_occupancy(conn);
+ // size_t player_buffer_occupancy = get_audio_buffer_occupancy(conn);
// debug(1,"player buffer size and occupancy: %u and %u", player_buffer_size,
// player_buffer_occupancy);
@@ -451,17 +453,41 @@ void *rtp_buffered_audio_processor(void *arg) {
// and send it to the player. Otherwise, keep the block and sleep for a while.
// calculate if there is room in the decoded audio buffer...
- int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <= config.audio_decoded_buffer_desired_length;
+
+ // debug(1, "frames buffered: %f seconds, desired length: %f seconds.", (1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate, config.audio_decoded_buffer_desired_length);
+
+ // int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <= config.audio_decoded_buffer_desired_length;
uint64_t buffer_should_be_time;
int have_valid_time = (frame_to_local_time(timestamp, &buffer_should_be_time, conn) == 0);
// calculate the lead time to make sure it's not too early...
int64_t lead_time = buffer_should_be_time - get_absolute_time_in_ns();
-
- if ((play_enabled != 0) && (have_valid_time != 0) &&
- (audio_decoded_buffer_below_desired_length != 0) &&
- (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))) {
+
+
+ // debug(1,"play_enabled: %d, have_valid_time: %d, audio_decoded_buffer_below_desired_length: %d, lead_time * 1E-9: %f, (config.audio_decoded_buffer_desired_length + 0.1): %f, player_buffer_occupancy: %zu",
+ // play_enabled, have_valid_time, audio_decoded_buffer_below_desired_length, lead_time * 1E-9, (config.audio_decoded_buffer_desired_length + 0.1), player_buffer_occupancy
+ // );
+
+ // A slight problem here is that counting the number of buffers may not be sufficient,
+ // because the actual device may be
+ // taking data in large quantities at a single time.
+
+ // So we just have to ensure that there
+ // is enough of a lead time maintained for sufficient audio to be available to prevent
+ // the device from under-running.
+
+ // If means that the Shairport Sync player might riun out of audio occasionally, but
+ // as long as the device has enough in its buffer, everything is fine.
+
+ // But it also means that Shairport Sync's buffers must be sufficient to hold all the
+ // entire lead-time's amount of audio in case the device has a zero-sized buffer.
+
+ if ((play_enabled != 0) && (have_valid_time != 0)
+// (audio_decoded_buffer_below_desired_length != 0) &&
+ && (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))
+// && (audio_decoded_buffer_below_desired_length != 0)
+ ) {
very_early_packets_signalled = 0; //reset very early packet warning signaller
@@ -639,8 +665,7 @@ void *rtp_buffered_audio_processor(void *arg) {
debug(1, "incoming frame suddenly (?) has a lead time of %f seconds, with a desired decoded buffer length of %f.", 1.0 * lead_time * 1E-9, config.audio_decoded_buffer_desired_length);
very_early_packets_signalled = 1;
}
-
- usleep(20000); // wait for a while
+ usleep(((1000000 * conn->frames_per_packet) / conn->input_rate) * 2); // wait for approximately the length of two packets
}
}
}
diff --git a/ap2_event_message_handler.c b/ap2_event_message_handler.c
new file mode 100644
index 00000000..7b03f436
--- /dev/null
+++ b/ap2_event_message_handler.c
@@ -0,0 +1,140 @@
+/*
+ * AirPlay 2 Event Port Message Handler. This file is part of Shairport Sync
+ * Copyright (c) Mike Brady 2025
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "ap2_event_message_handler.h"
+#include "common.h"
+#include "rtsp.h"
+#include "utilities/structured_buffer.h"
+#include "utilities/generate_random_uuid.h"
+
+
+void decodeAndLogPlist(plist_t plist_to_log) {
+ if (plist_to_log != NULL) {
+ char *plist_as_string = plist_as_xml_text(plist_to_log);
+ if (plist_as_string != NULL) {
+ debug(3, "--\n%s\n--\n", plist_as_string);
+ free(plist_as_string);
+ }
+ }
+}
+
+// will return -1 if there is an error or port is not open, 0 if the port was closed and a positive
+// number if okay
+ssize_t ap2_event_port_send_message(rtsp_conn_info *conn, char *data, size_t data_length) {
+ ssize_t result = -1; // assume a problem
+ debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
+ pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
+ if (conn->event_channel_fd != 0) {
+ result = write_encrypted(conn->event_channel_fd, &conn->ap2_pairing_context.event_cipher_bundle,
+ data, data_length);
+ if ((result != -1) && ((size_t)result == data_length)) {
+ debug(3, "Connection %d: Packet of %zu bytes successfully written on the Event Port.",
+ conn->connection_number, result);
+ uint8_t packet[4096];
+ result =
+ read_encrypted(conn->event_channel_fd, &conn->ap2_pairing_context.event_cipher_bundle,
+ packet, sizeof(packet));
+ debug(3, "Connection %d: Packet of %zu bytes successfully read on the Event Port.",
+ conn->connection_number, result);
+ if (result > 0) {
+ packet[result] = '\0';
+ debug(3, "Connection %d: Packet Received on Event Port with contents: \n--\n%s\n--\n",
+ conn->connection_number, packet);
+ } else {
+ debug(2, "Connection %d: Event Port connection closed by client", conn->connection_number);
+ }
+ } else {
+ result = -1; // this covers a situation where the result is positive but not the same as the
+ // data_length
+ }
+ } else {
+ debug(1, "Connection %d: attempt to send a command to the event port over a closed socket",
+ conn->connection_number);
+ }
+ pthread_cleanup_pop(1); // unlock the mutex
+ return result;
+}
+
+ssize_t ap2_event_port_post_command(rtsp_conn_info *conn, plist_t command) {
+ ssize_t result = 0;
+ decodeAndLogPlist(command);
+ structured_buffer *sbuf = sbuf_new(4096);
+ if (sbuf != NULL) {
+ pthread_cleanup_push(sbuf_cleanup, sbuf);
+ char *plistString = NULL;
+ uint32_t plistStringLength = 0;
+
+ plist_to_bin(command, &plistString, &plistStringLength);
+ if (plistString != NULL) {
+ sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n", plistStringLength);
+ sbuf_printf(sbuf, "Content-Type: application/x-apple-binary-plist\r\n\r\n");
+ sbuf_append(sbuf, plistString, plistStringLength);
+ free(plistString); // should be plist_to_bin_free, but it's not defined in older
+ // libraries
+ char *b = 0;
+ size_t l = 0;
+ sbuf_buf_and_length(sbuf, &b, &l);
+ result = ap2_event_port_send_message(conn, b, l);
+ debug(3, "Connection %d: POST /command sent on the event port. Result is %zd.",
+ conn->connection_number, result);
+ sbuf_clear(sbuf);
+ }
+ pthread_cleanup_pop(1); // delete the structured buffer
+ }
+ return result;
+}
+
+ssize_t ap2_event_send_update_info(rtsp_conn_info *conn) {
+ // sends the updateInfo plist on the event port
+ ssize_t result = -1;
+ plist_t value_plist = generateInfoPlist(conn);
+ if (value_plist != NULL) {
+ void *txtData = NULL;
+ size_t txtDataLength = 0;
+ generateTxtDataValueInfo(conn, &txtData, &txtDataLength);
+ plist_dict_set_item(value_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength));
+ free(txtData);
+ plist_t update_info_plist = plist_new_dict();
+ if (update_info_plist != NULL) {
+ plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo"));
+ plist_dict_set_item(update_info_plist, "value", value_plist);
+
+ char *plist_as_string = plist_as_xml_text(update_info_plist);
+ if (plist_as_string != NULL) {
+ debug(3, "update_info_plist is:\n--\n\"%s\"\n--\n", plist_as_string);
+ free(plist_as_string);
+ }
+
+ result = ap2_event_port_post_command(conn, update_info_plist);
+ plist_free(update_info_plist);
+ } else {
+ debug(1, "Could not build an updateInfo plist");
+ }
+ } else {
+ debug(1, "Could not build an updateInfo value plist");
+ }
+ return result;
+}
diff --git a/ap2_event_message_handler.h b/ap2_event_message_handler.h
new file mode 100644
index 00000000..fef68626
--- /dev/null
+++ b/ap2_event_message_handler.h
@@ -0,0 +1,14 @@
+#ifndef _AP2_EVENT_COMMAND_SENDER_H
+#define _AP2_EVENT_COMMAND_SENDER_H
+
+#include
+#include
+#include "player.h"
+
+ssize_t ap2_event_send_unit_volume_notification(rtsp_conn_info *conn, double volume);
+ssize_t ap2_event_send_modern_media_remote_command(rtsp_conn_info *conn, unsigned int command_number);
+ssize_t ap2_event_send_update_info(rtsp_conn_info *conn);
+
+ssize_t ap2_event_send_dev_mule();
+
+#endif // _AP2_EVENT_COMMAND_SENDER_H
diff --git a/ap2_event_receiver.c b/ap2_event_receiver.c
index 48d10d54..abff7c23 100644
--- a/ap2_event_receiver.c
+++ b/ap2_event_receiver.c
@@ -1,6 +1,6 @@
/*
* Apple AirPlay 2 Event Receiver. This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2014--2025
+ * Copyright (c) Mike Brady 2014--2026
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -25,23 +25,27 @@
*/
#include "ap2_event_receiver.h"
-#include "bonjour_strings.h"
+#include "ap2_event_message_handler.h"
#include "common.h"
#include "player.h"
-#include "ptp-utilities.h"
-#include "rtsp.h"
#include "utilities/network_utilities.h"
-#include "utilities/structured_buffer.h"
+
+#ifdef CONFIG_METADATA
+#include "metadata/core.h"
+#endif
void ap2_event_receiver_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
// debug(1, "Connection %d: AP2 Event Receiver Cleanup start.", conn->connection_number);
- // only update these things if you're (still) the principal conn
-
#ifdef CONFIG_METADATA
// this is here to ensure it's only performed once during a teardown of a ptp stream
send_ssnc_metadata('disc', conn->client_ip_string, strlen(conn->client_ip_string), 1);
#endif
+ debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
+ pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
+ close(conn->event_channel_fd);
+ conn->event_channel_fd = 0;
+ pthread_cleanup_pop(1); // unlock the mutex
if (conn->airplay_gid != NULL) {
free(conn->airplay_gid);
@@ -52,161 +56,43 @@ void ap2_event_receiver_cleanup_handler(void *arg) {
free(conn->dacp_active_remote);
conn->dacp_active_remote = NULL;
}
- if (conn->ap2_client_name) {
- free(conn->ap2_client_name);
- conn->ap2_client_name = NULL;
- }
- /*
- pthread_rwlock_wrlock(&principal_conn_lock); // don't let the principal_conn be changed
- pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
- if (principal_conn)
- debug(1, "principal_conn: %d.", principal_conn->connection_number);
- else
- debug(1, "principal_conn: is NULL.");
- if (principal_conn == conn) {
- config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
- build_bonjour_strings(conn);
- debug(1, "Connection %d: SETUP mdns_update on %s.", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- mdns_update(NULL, secondary_txt_records);
- principal_conn = NULL;
- }
- pthread_cleanup_pop(1); // release the principal_conn lock
- */
debug(2, "Connection %d: AP2 Event Receiver Cleanup is complete.", conn->connection_number);
}
void *ap2_event_receiver(void *arg) {
- // #include
- // debug(1, "rtp_event_receiver PID %d", syscall(SYS_gettid));
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
debug(2, "Connection %d: AP2 Event Receiver started", conn->connection_number);
- structured_buffer *sbuf = sbuf_new(4096);
- if (sbuf != NULL) {
- pthread_cleanup_push(sbuf_cleanup, sbuf);
+ SOCKADDR remote_addr;
+ memset(&remote_addr, 0, sizeof(remote_addr));
+ socklen_t addr_size = sizeof(remote_addr);
- /*
- // only update these things if you're (still) the principal conn
- pthread_rwlock_wrlock(&principal_conn_lock); // don't let the principal_conn be changed
- pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
- if (principal_conn == conn) {
- config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
- // config.airplay_statusflags |= 1 << 17; // ReceiverSessionIsActive
- build_bonjour_strings(conn);
- debug(2, "Connection %d: SETUP mdns_update on %s.", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- mdns_update(NULL, secondary_txt_records);
- }
- pthread_cleanup_pop(1); // release the principal_conn lock
- */
- pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg);
-
- // listen(conn->event_socket, 5); // this is now done in the handle_setup_2 code
-
- uint8_t packet[4096];
- ssize_t nread;
- SOCKADDR remote_addr;
- memset(&remote_addr, 0, sizeof(remote_addr));
- socklen_t addr_size = sizeof(remote_addr);
-
- int fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size);
+ debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
+ pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
+ conn->event_channel_fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size);
+ pthread_cleanup_pop(1); // unlock the mutex
+ if (conn->event_channel_fd > 0) {
debug(2,
"Connection %d: ap2_event_receiver accepted a connection on socket %d and moved to a new "
"socket %d.",
- conn->connection_number, conn->event_socket, fd);
- intptr_t pfd = fd;
- pthread_cleanup_push(socket_cleanup, (void *)pfd);
- int finished = 0;
- do {
-
- plist_t value_plist = generateInfoPlist(conn);
- if (value_plist != NULL) {
- void *txtData = NULL;
- size_t txtDataLength = 0;
- generateTxtDataValueInfo(conn, &txtData, &txtDataLength);
- plist_dict_set_item(value_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength));
- free(txtData);
- plist_t update_info_plist = plist_new_dict();
- if (update_info_plist != NULL) {
- plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo"));
- plist_dict_set_item(update_info_plist, "value", value_plist);
- char *plistString = NULL;
- uint32_t plistStringLength = 0;
- plist_to_bin(update_info_plist, &plistString, &plistStringLength);
- if (plistString != NULL) {
- char *plist_as_string = plist_as_xml_text(update_info_plist);
- if (plist_as_string != NULL) {
- debug(3, "Plist is: \"%s\".", plist_as_string);
- free(plist_as_string);
- }
- sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n",
- plistStringLength);
- sbuf_printf(sbuf, "Content-Type: application/x-apple-binary-plist\r\n\r\n");
- sbuf_append(sbuf, plistString, plistStringLength);
-
- free(plistString); // should be plist_to_bin_free, but it's not defined in older
- // libraries
- char *b = 0;
- size_t l = 0;
- sbuf_buf_and_length(sbuf, &b, &l);
- ssize_t wres =
- write_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, b, l);
- if ((wres == -1) || ((size_t)wres != l))
- debug(1, "Encrypted write error");
-
- sbuf_clear(sbuf);
- } else {
- debug(1, "plist string not created!");
- }
- plist_free(update_info_plist);
- } else {
- debug(1, "Could not build an updateInfo plist");
- }
- // plist_free(value_plist);
- } else {
- debug(1, "Could not build an value plist");
- }
-
- while (finished == 0) {
- nread = read_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, packet,
- sizeof(packet));
-
- // nread = recv(fd, packet, sizeof(packet), 0);
-
- if (nread < 0) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1,
- "Connection %d: error in ap2_event_receiver %d: \"%s\". Could not recv a packet.",
- conn->connection_number, errno, errorstring);
- // if ((config.diagnostic_drop_packet_fraction == 0.0) ||
- // (drand48() > config.diagnostic_drop_packet_fraction)) {
- } else if (nread > 0) {
-
- // ssize_t plen = nread;
- packet[nread] = '\0';
- debug(3, "Connection %d: Packet Received on Event Port with contents: \"%s\".",
- conn->connection_number, packet);
- } else {
- debug(2, "Connection %d: Event Port connection closed by client",
- conn->connection_number);
- finished = 1;
- }
- }
-
- } while (finished == 0);
-
+ conn->connection_number, conn->event_socket, conn->event_channel_fd);
+ pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg);
+
+ ap2_event_send_update_info(conn);
+
+
+ while (1) {
+ usleep(1000000);
+ };
+
debug(3, "Connection %d: AP2 Event Receiver RTP thread starting \"normal\" exit.",
conn->connection_number);
- pthread_cleanup_pop(1); // close the socket
-
pthread_cleanup_pop(1); // do the cleanup
- pthread_cleanup_pop(1); // delete the structured buffer
- debug(2, "Connection %d: AP2 Event Receiver RTP thread \"normal\" exit.",
- conn->connection_number);
} else {
- debug(1, "Could not allocate a structured buffer!");
+ debug(1, "Connection %d: could not accept an event socket", conn->connection_number);
+ conn->event_channel_fd = 0;
}
+ debug(2, "Connection %d: AP2 Event Receiver RTP thread \"normal\" exit.",
+ conn->connection_number);
conn->ap2_event_receiver_exited = 1;
pthread_exit(NULL);
}
diff --git a/ap2_rc_event_receiver.c b/ap2_rc_event_receiver.c
deleted file mode 100644
index 0fba8562..00000000
--- a/ap2_rc_event_receiver.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Apple AirPlay 2 Remote Control (RC) Event Receiver. This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2014--2025
- * All rights reserved.
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#include "ap2_rc_event_receiver.h"
-#include "common.h"
-#include "player.h"
-#include "rtsp.h"
-#include "utilities/network_utilities.h"
-#include "utilities/structured_buffer.h"
-
-void ap2_rc_event_receiver_cleanup_handler(void *arg) {
- rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- debug(2, "Connection %d: AP2 Event Receiver Cleanup.", conn->connection_number);
-}
-
-void *ap2_rc_event_receiver(void *arg) {
- // #include
- // debug(1, "rtp_event_receiver PID %d", syscall(SYS_gettid));
- rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- if (conn->airplay_stream_category == remote_control_stream)
- debug(2, "Connection %d (RC): AP2 RC Event Receiver started", conn->connection_number);
- else
- debug(2, "Connection %d: AP2 RC Event Receiver started", conn->connection_number);
-
- structured_buffer *sbuf = sbuf_new(4096);
- if (sbuf != NULL) {
- pthread_cleanup_push(sbuf_cleanup, sbuf);
-
- pthread_cleanup_push(ap2_rc_event_receiver_cleanup_handler, arg);
-
- // listen(conn->event_socket, 5); // this is now done in the handle_setup_2 code
-
- uint8_t packet[4096];
- ssize_t nread;
- SOCKADDR remote_addr;
- memset(&remote_addr, 0, sizeof(remote_addr));
- socklen_t addr_size = sizeof(remote_addr);
-
- int fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size);
- debug(2,
- "Connection %d: ap2_rc_event_receiver accepted a connection on socket %d and moved to a "
- "new "
- "socket %d.",
- conn->connection_number, conn->event_socket, fd);
- intptr_t pfd = fd;
- pthread_cleanup_push(socket_cleanup, (void *)pfd);
- int finished = 0;
- do {
-
- plist_t value_plist = generateInfoPlist(conn);
- if (value_plist != NULL) {
- void *txtData = NULL;
- size_t txtDataLength = 0;
- generateTxtDataValueInfo(conn, &txtData, &txtDataLength);
- plist_dict_set_item(value_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength));
- free(txtData);
- plist_t update_info_plist = plist_new_dict();
- if (update_info_plist != NULL) {
- plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo"));
- plist_dict_set_item(update_info_plist, "value", value_plist);
- char *plistString = NULL;
- uint32_t plistStringLength = 0;
- plist_to_bin(update_info_plist, &plistString, &plistStringLength);
- if (plistString != NULL) {
- char *plist_as_string = plist_as_xml_text(update_info_plist);
- if (plist_as_string != NULL) {
- debug(3, "Plist is: \"%s\".", plist_as_string);
- free(plist_as_string);
- }
- sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n",
- plistStringLength);
- sbuf_printf(sbuf, "Content-Type: application/x-apple-binary-plist\r\n\r\n");
- sbuf_append(sbuf, plistString, plistStringLength);
-
- free(plistString); // should be plist_to_bin_free, but it's not defined in older
- // libraries
- char *b = 0;
- size_t l = 0;
- sbuf_buf_and_length(sbuf, &b, &l);
- ssize_t wres =
- write_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, b, l);
- if ((wres == -1) || ((size_t)wres != l))
- debug(1, "Encrypted write error");
-
- sbuf_clear(sbuf);
- } else {
- debug(1, "plist string not created!");
- }
- plist_free(update_info_plist);
- } else {
- debug(1, "Could not build an updateInfo plist");
- }
- // plist_free(value_plist);
- } else {
- debug(1, "Could not build an value plist");
- }
-
- while (finished == 0) {
- nread = read_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, packet,
- sizeof(packet));
-
- // nread = recv(fd, packet, sizeof(packet), 0);
-
- if (nread < 0) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(
- 1,
- "Connection %d: error in ap2_rc_event_receiver %d: \"%s\". Could not recv a packet.",
- conn->connection_number, errno, errorstring);
- // if ((config.diagnostic_drop_packet_fraction == 0.0) ||
- // (drand48() > config.diagnostic_drop_packet_fraction)) {
- } else if (nread > 0) {
-
- // ssize_t plen = nread;
- packet[nread] = '\0';
- debug(2,
- "Connection %d: ap2_rc_event_receiver Packet Received on Event Port with contents: "
- "\"%s\".",
- conn->connection_number, packet);
- } else {
- debug(2, "Connection %d: ap2_rc_event_receiver Event Port connection closed by client",
- conn->connection_number);
- finished = 1;
- }
- }
-
- } while (finished == 0);
- pthread_cleanup_pop(1); // close the socket
- pthread_cleanup_pop(1); // do the cleanup
- pthread_cleanup_pop(1); // delete the structured buffer
- debug(2, "Connection %d: AP2 ap2_rc_event_receiver \"normal\" exit.", conn->connection_number);
- } else {
- debug(1, "Could not allocate a structured buffer!");
- }
- conn->ap2_event_receiver_exited = 1;
- pthread_exit(NULL);
-}
diff --git a/ap2_rc_event_receiver.h b/ap2_rc_event_receiver.h
deleted file mode 100644
index 200e785b..00000000
--- a/ap2_rc_event_receiver.h
+++ /dev/null
@@ -1,8 +0,0 @@
-// RC means Remote Control
-
-#ifndef _AP2_RC_EVENT_RECEIVER_H
-#define _AP2_RC_EVENT_RECEIVER_H
-
-void *ap2_rc_event_receiver(void *arg);
-
-#endif // _AP2_RC_EVENT_RECEIVER_H
diff --git a/audio.c b/audio.c
index 7c08de00..a703d660 100644
--- a/audio.c
+++ b/audio.c
@@ -985,7 +985,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra
}
#ifdef CONFIG_FFMPEG
if (local_format == SPS_FORMAT_UNKNOWN) {
- debug(3, "check for an next highest rate above %u with %u channels.", rate,
+ debug(3, "check for the next highest rate above %u with %u channels.", rate,
local_channels);
unsigned int rate_pointer = 0;
while ((rate_pointer < sizeof(rates) / sizeof(unsigned int)) &&
@@ -1003,7 +1003,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra
if (local_format == SPS_FORMAT_UNKNOWN) {
int rate_pointer = (int)(sizeof(rates) / sizeof(unsigned int) - 1);
- debug(3, "check for an next lowest rate below %u with %u channels.", rate,
+ debug(3, "check for the next lowest rate below %u with %u channels.", rate,
local_channels);
while ((rate_pointer >= 0) && (local_format == SPS_FORMAT_UNKNOWN)) {
local_rate = rates[rate_pointer];
diff --git a/audio_alsa.c b/audio_alsa.c
index b105457c..bf03dfc8 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -306,7 +306,7 @@ static int get_permissible_configuration_settings() {
debug(3, "\"%s\" can handle %u channels.", alsa_out_dev, c);
} else {
// the device can't handle this number of channels
- debug(3, "\"%s\" can not handle %u channels.", alsa_out_dev, c);
+ debug(4, "\"%s\" can not handle %u channels.", alsa_out_dev, c);
config.channel_set &=
~(1 << c); // the alsa device can't accommodate this number of channels
}
@@ -332,7 +332,7 @@ static int get_permissible_configuration_settings() {
debug(3, "\"%s\" can handle a rate of %u fps.", alsa_out_dev,
sps_rate_actual_rate(r));
} else {
- debug(3, "\"%s\" can not handle a rate of %u fps.", alsa_out_dev,
+ debug(4, "\"%s\" can not handle a rate of %u fps.", alsa_out_dev,
sps_rate_actual_rate(r));
config.rate_set &= ~(1 << r); // the alsa device doesn't do this rate
}
@@ -357,7 +357,7 @@ static int get_permissible_configuration_settings() {
debug(3, "\"%s\" can handle the %s format.", alsa_out_dev,
sps_format_description_string(f));
} else {
- debug(3, "\"%s\" can not handle the %s format.", alsa_out_dev,
+ debug(4, "\"%s\" can not handle the %s format.", alsa_out_dev,
sps_format_description_string(f));
config.format_set &= ~(1 << f); // the alsa device doesn't do this format
}
@@ -2177,14 +2177,14 @@ static void flush(void) {
}
static void stop(void) {
- pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1);
+ pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 4);
if (alsa_backend_state != abm_disconnected) { // must be playing or connected...
if (config.keep_dac_busy == 0) {
do_close();
}
} else
debug(3, "alsa: stop() -- called on a disconnected alsa backend");
- debug_mutex_unlock(&alsa_mutex, 3);
+ debug_mutex_unlock(&alsa_mutex, 4);
pthread_cleanup_pop(0); // release the mutex
}
@@ -2252,7 +2252,7 @@ static void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg)
// #include
// debug(1, "alsa_buffer_monitor_thread_code PID %d", syscall(SYS_gettid));
// Wait until the output configuration has been set by the main program
- debug(2, "alsa: alsa_buffer_monitor_thread_code started.");
+ debug(1, "alsa: alsa_buffer_monitor_thread_code started.");
int frame_count = 0;
int error_count = 0;
int error_detected = 0;
diff --git a/bonjour_strings.c b/bonjour_strings.c
index 19d63e98..ff9c37a7 100644
--- a/bonjour_strings.c
+++ b/bonjour_strings.c
@@ -149,7 +149,7 @@ void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) {
entry_number = 0;
secondary_txt_records[entry_number++] = "acl=0";
- secondary_txt_records[entry_number++] = "btaddr=00:00:00:00:00:00";
+ // secondary_txt_records[entry_number++] = "btaddr=00:00:00:00:00:00";
secondary_txt_records[entry_number++] =
bnprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
secondary_txt_records[entry_number++] =
diff --git a/common.c b/common.c
index e552af4c..e5961c98 100644
--- a/common.c
+++ b/common.c
@@ -1041,32 +1041,49 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
#endif
int config_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value) {
- int response = config_lookup_string(cfg, path, value);
- if (response == CONFIG_TRUE) {
- if ((value != NULL) && ((*value == NULL) || (*value[0] == 0))) {
- warn("The \"%s\" parameter is an empty string and has been ignored.", path);
- response = CONFIG_FALSE;
+ int response = CONFIG_FALSE;
+ config_setting_t *s = config_lookup(cfg, path);
+ if (s != NULL) {
+ // the setting exists, but might not be a string
+ if (config_setting_type(s) == CONFIG_TYPE_STRING) {
+ if (value != NULL) {
+ *value = config_setting_get_string(s);
+ response = CONFIG_TRUE;
+ // the string might be empty...
+ if ((*value == NULL) || (*value[0] == 0)) {
+ warn("The \"%s\" parameter is an empty string and has been ignored.", path);
+ response = CONFIG_FALSE;
+ }
+ }
+ } else {
+ warn("the \"%s\" parameter is not a string, as required, and has been ignored.", path);
}
}
return response;
}
-int config_set_lookup_bool(config_t *cfg, char *where, int *dst) {
- const char *str = 0;
- if (config_lookup_string(cfg, where, &str)) {
- if (strcasecmp(str, "no") == 0) {
- (*dst) = 0;
- return 1;
- } else if (strcasecmp(str, "yes") == 0) {
- (*dst) = 1;
- return 1;
+int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) {
+ const char *str = NULL;
+ int response = CONFIG_FALSE;
+ config_setting_t *s = config_lookup(cfg, where);
+ if (s != NULL) {
+ if (config_setting_type(s) == CONFIG_TYPE_STRING) {
+ str = config_setting_get_string(s);
+ if (strcasecmp(str, "no") == 0) {
+ (*dst) = 0;
+ response = CONFIG_TRUE;
+ } else if (strcasecmp(str, "yes") == 0) {
+ (*dst) = 1;
+ response = CONFIG_TRUE;
+ } else {
+ die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
+ return 0;
+ }
} else {
- die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
- return 0;
+ warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, and has been ignored.", where);
}
- } else {
- return 0;
}
+ return response;
}
// remember to free the returned array of strings.
@@ -2322,6 +2339,8 @@ int get_device_id(uint8_t *id, int int_length) {
int64_t time_to_wait;
do {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
if (getifaddrs(&ifaddr) == 0) {
t = id;
int found = 0;
@@ -2361,6 +2380,7 @@ int get_device_id(uint8_t *id, int int_length) {
}
freeifaddrs(ifaddr);
}
+ pthread_setcancelstate(oldState, NULL);
// wait a little time if we haven't got a response
if (response != 0) {
usleep(100000);
diff --git a/common.h b/common.h
index bf472829..a7a0604d 100644
--- a/common.h
+++ b/common.h
@@ -396,13 +396,13 @@ typedef struct {
uint32_t airplay_statusflags;
char *airplay_fex; // a base64-encoded version of the airplay_features in little-endian form
char *airplay_device_id; // for the Bonjour advertisement and the GETINFO PList
- char *airplay_pin; // non-NULL, 4 char PIN, if required for pairing
char *airplay_pi; // UUID in the Bonjour advertisement and the GETINFO Plist
char *airplay_pgid; // UUID in the txtAirPlay data sent on the event channel
char *airplay_psi; // type 4 fixed UUID
uint8_t airplay_pk[32]; // public key
char *pk_string;
char *nqptp_shared_memory_interface_name; // client name for nqptp service
+ int enable_HK_Access_Control; // true if the device is part of an Apple Home
#endif
int unfixable_error_reported; // only report once.
@@ -478,19 +478,6 @@ uint16_t nextFreeUDPPort();
extern volatile int debuglev;
-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, ...);
-void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *buf,
- size_t buf_len);
-
-#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__)
-#define debug_print_buffer(...) _debug_print_buffer(__FILE__, __LINE__, __VA_ARGS__)
-
// Thanks to https://stackoverflow.com/a/1597129 for the inspiration for this identifier generation
#define MAKEUNIQUEID2(x, y) x##y
#define MADEID(x, y) MAKEUNIQUEID2(x, y)
@@ -561,7 +548,7 @@ extern int type_of_exit_cleanup; // normal, emergency, dbus requested...
extern uint64_t minimum_dac_queue_size;
int config_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value);
-int config_set_lookup_bool(config_t *cfg, char *where, int *dst);
+int config_set_lookup_bool(config_t *cfg, const char *where, int *dst);
int check_string_or_list_setting(config_setting_t *setting, const char *item);
int check_int_or_list_setting(config_setting_t *setting, const int item);
diff --git a/configure.ac b/configure.ac
index 002639c0..dbf3901f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [5.0.1+], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [5.0.4-dev], [4265913+mikebrady@users.noreply.github.com])
: ${CFLAGS="-O3"}
: ${CXXFLAGS="-O3"}
AM_INIT_AUTOMAKE([subdir-objects])
@@ -219,6 +219,12 @@ fi
# Look for metadata flag and resolve it further down the script
AC_ARG_WITH(metadata,[AS_HELP_STRING([--with-metadata],[include support for a metadata feed])])
+# Look for metadata_pipe flag and resolve it further down the script
+AC_ARG_WITH(metadata_pipe,[AS_HELP_STRING([--with-metadata-pipe],[include support for piping metadata out through a unix pipe])])
+
+# Look for metadata_multicast flag and resolve it further down the script
+AC_ARG_WITH(metadata_multicast,[AS_HELP_STRING([--with-metadata-multicast],[include support for metadata multicasting])])
+
# What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation
# and the relevant link files are added.
@@ -228,8 +234,8 @@ if test "x$with_avahi" = "xyes" ; then
AC_DEFINE([CONFIG_AVAHI], 1, [Include Avahi-based mDNS support.])
AC_CHECK_LIB([avahi-client], [avahi_client_new], , AC_MSG_ERROR(Avahi support requires the avahi-client library!))
AC_CHECK_LIB([avahi-common],[avahi_strerror], , AC_MSG_ERROR(Avahi support requires the avahi-common library!))
- systemd_after_args="${systemd_after_args} avahi-daemon.service"
- systemd_requires_args="${systemd_requires_args} avahi-daemon.service"
+ systemd_after_args="${systemd_after_args}${systemd_after_args:+ }avahi-daemon.service"
+ systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }avahi-daemon.service"
fi
AM_CONDITIONAL([USE_AVAHI], [test "x$with_avahi" = "xyes"])
@@ -404,14 +410,33 @@ fi
AM_CONDITIONAL([USE_METADATA_HUB], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"])
AM_CONDITIONAL([USE_DACP_CLIENT], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"])
-if test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1" || test "x$with_metadata" = "xyes" ; then
+if test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1" || test "x$with_metadata" = "xyes" || test "x$with_metadata_pipe" = "xyes" || test "x$with_metadata_multicast" = "xyes" ; then
+ REQUESTED_METADATA_SUPPORT=1
+fi
+
+if test "x$REQUESTED_METADATA_SUPPORT" = "x1" ; then
AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])
fi
-AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"])
+AM_CONDITIONAL([USE_METADATA], [test "x$REQUESTED_METADATA_SUPPORT" = "x1"])
+
+if test "x$with_metadata" = "xyes" ; then
+ DEFAULT_INCLUDE_PIPE_METADATA=1
+fi
+
+if test "x$DEFAULT_INCLUDE_PIPE_METADATA" = "x1" || test "x$with_metadata_pipe" = "xyes" ; then
+ AC_DEFINE([CONFIG_METADATA_PIPE], 1, [Needed by the compiler.])
+fi
+AM_CONDITIONAL([USE_METADATA_PIPE], [test "x$DEFAULT_INCLUDE_PIPE_METADATA" = "x1" || test "x$with_metadata_pipe" = "xyes"])
+
+if test "x$with_metadata_multicast" = "xyes" ; then
+ AC_DEFINE([CONFIG_METADATA_MULTICAST], 1, [Needed by the compiler.])
+fi
+AM_CONDITIONAL([USE_METADATA_MULTICAST], [test "x$with_metadata_multicast" = "xyes"])
# Look for AirPlay 2 flag
AC_ARG_WITH(airplay-2, [AS_HELP_STRING([--with-airplay-2],[Build for AirPlay 2])])
if test "x$with_airplay_2" = "xyes" ; then
+ using_ffmpeg=true
AC_DEFINE([CONFIG_AIRPLAY_2], 1, [Build for AirPlay 2])
AC_CHECK_PROG(PLISTUTIL_CHECK,plistutil,yes)
AS_IF([test x"$PLISTUTIL_CHECK" != x"yes"], [AC_MSG_ERROR([plistutil can not be found. Please install plistutil for building for AirPlay 2. Maybe it's in the apt package libplist-utils.])])
@@ -443,6 +468,8 @@ if test "x$with_airplay_2" = "xyes" ; then
)
])
AC_CHECK_LIB([uuid],[uuid_generate], [], [AC_MSG_ERROR([AirPlay 2 support requires the uuid library -- uuid-dev suggested])])
+ systemd_after_args="${systemd_after_args}${systemd_after_args:+ }nqptp.service"
+ systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }nqptp.service"
fi
AM_CONDITIONAL([USE_AIRPLAY_2], [test "x$with_airplay_2" = "xyes"])
@@ -455,7 +482,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
PKG_CHECK_MODULES(
[libavutil], [libavutil],
[CFLAGS="${libavutil_CFLAGS} ${CFLAGS}" LIBS="${libavutil_LIBS} ${LIBS}"],
- [AC_MSG_ERROR(AirPlay 2 support requires libavutil -- libavutil-dev suggested)]
+ [AC_MSG_ERROR(FFmpeg requires libavutil -- libavutil-dev suggested)]
)
PKG_CHECK_MODULES(
[libavcodec], [libavcodec],
@@ -463,7 +490,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
[AC_CHECK_LIB(
[avcodec], [avcodec_find_decoder],
[],
- [AC_MSG_ERROR(AirPlay 2 support requires libavcodec -- libavcodec-dev suggested)]
+ [AC_MSG_ERROR(FFmpeg requires libavcodec -- libavcodec-dev suggested)]
)]
)
PKG_CHECK_MODULES(
@@ -472,7 +499,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
[AC_CHECK_LIB(
[avformat], [avformat_new_stream],
[],
- [AC_MSG_ERROR([AirPlay 2 support requires libavformat -- libavformat-dev suggested])]
+ [AC_MSG_ERROR([FFmpeg requires libavformat -- libavformat-dev suggested])]
)]
)
PKG_CHECK_MODULES(
@@ -481,16 +508,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
[AC_CHECK_LIB(
[swresample], [swr_convert],
[],
- [AC_MSG_ERROR([AirPlay 2 support requires libswresample -- libswresample-dev suggested])]
- )]
- )
- PKG_CHECK_MODULES(
- [libuuid], [uuid],
- [CFLAGS="${libuuid_CFLAGS} ${CFLAGS}" LIBS="${libuuid_LIBS} ${LIBS}"],
- [AC_CHECK_LIB(
- [uuid], [uuid_generate],
- [],
- [AC_MSG_ERROR([AirPlay 2 support requires the uuid library -- uuid-dev suggested])]
+ [AC_MSG_ERROR([FFmpeg requires libswresample -- libswresample-dev suggested])]
)]
)
else
@@ -505,8 +523,11 @@ AM_CONDITIONAL([USE_HAMMERTON], [test $using_ffmpeg = false])
# Look for Apple ALAC flag
AC_ARG_WITH(apple-alac,[AS_HELP_STRING([--with-apple-alac],[include support for the Apple ALAC decoder (deprecated)])])
if test "x${with_apple_alac}" = "xyes" ; then
+ if test "x${with_airplay_2}" = "xyes" ; then
+ AC_MSG_ERROR([the Apple ALAC Decoder (deprecated) can not be used in AirPlay 2.])
+ fi
if test $using_ffmpeg = true ; then
- AC_MSG_RESULT([Note: the FFMPEG decoder, included in the build, supercedes the Apple ALAC decoder, which is deprecated. Consider omitting the Apple ALAC decoder.])
+ AC_MSG_WARN([the FFMPEG decoder, included in the build, supercedes the Apple ALAC Decoder, which is deprecated. Consider omitting the Apple ALAC Decoder.])
fi
AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Include support for using the Apple ALAC Decoder (deprecated)])
if test "x${with_pkg_config}" = xyes ; then
diff --git a/dacp.c b/dacp.c
index aade558f..2252482f 100644
--- a/dacp.c
+++ b/dacp.c
@@ -43,7 +43,7 @@
#include
#include
-#include "metadata_hub.h"
+#include "metadata/hub.h"
#include "tinyhttp/http.h"
typedef struct {
@@ -397,10 +397,10 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) {
// as the conn's connection number
// this is to signify that the player has stopped, but only if another thread (with a different
// index) hasn't already taken over the dacp service
- debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
+ debug_mutex_lock(&dacp_server_information_lock, 500000, 4);
if (dacp_server.players_connection_thread_index == conn->connection_number)
dacp_server.players_connection_thread_index = 0;
- debug_mutex_unlock(&dacp_server_information_lock, 3);
+ debug_mutex_unlock(&dacp_server_information_lock, 4);
}
// this will be running on the thread of its caller, not of the conversation thread...
diff --git a/dbus-service.c b/dbus-service.c
index 15d02fa0..781fed0e 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -37,8 +37,11 @@
#include "rtp.h"
+#ifdef CONFIG_DACP_CLIENT
#include "dacp.h"
-#include "metadata_hub.h"
+#endif
+
+#include "metadata/hub.h"
#include "dbus-service.h"
@@ -335,7 +338,9 @@ static gboolean on_handle_set_volume(ShairportSyncAdvancedRemoteControl *skeleto
GDBusMethodInvocation *invocation, const gint volume,
__attribute__((unused)) gpointer user_data) {
debug(2, "Set volume to %d.", volume);
+#ifdef CONFIG_DACP_CLIENT
dacp_set_volume(volume);
+#endif
shairport_sync_advanced_remote_control_complete_set_volume(skeleton, invocation);
return TRUE;
}
@@ -343,7 +348,9 @@ static gboolean on_handle_set_volume(ShairportSyncAdvancedRemoteControl *skeleto
static gboolean on_handle_fast_forward(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("beginff");
+#endif
shairport_sync_remote_control_complete_fast_forward(skeleton, invocation);
return TRUE;
}
@@ -351,7 +358,9 @@ static gboolean on_handle_fast_forward(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_rewind(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("beginrew");
+#endif
shairport_sync_remote_control_complete_rewind(skeleton, invocation);
return TRUE;
}
@@ -359,7 +368,9 @@ static gboolean on_handle_rewind(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_toggle_mute(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("mutetoggle");
+#endif
shairport_sync_remote_control_complete_toggle_mute(skeleton, invocation);
return TRUE;
}
@@ -367,7 +378,9 @@ static gboolean on_handle_toggle_mute(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_next(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("nextitem");
+#endif
shairport_sync_remote_control_complete_next(skeleton, invocation);
return TRUE;
}
@@ -375,7 +388,9 @@ static gboolean on_handle_next(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_previous(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("previtem");
+#endif
shairport_sync_remote_control_complete_previous(skeleton, invocation);
return TRUE;
}
@@ -383,7 +398,9 @@ static gboolean on_handle_previous(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_pause(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("pause");
+#endif
shairport_sync_remote_control_complete_pause(skeleton, invocation);
return TRUE;
}
@@ -391,7 +408,9 @@ static gboolean on_handle_pause(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("playpause");
+#endif
shairport_sync_remote_control_complete_play_pause(skeleton, invocation);
return TRUE;
}
@@ -399,7 +418,9 @@ static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("play");
+#endif
shairport_sync_remote_control_complete_play(skeleton, invocation);
return TRUE;
}
@@ -407,7 +428,9 @@ static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_stop(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("stop");
+#endif
shairport_sync_remote_control_complete_stop(skeleton, invocation);
return TRUE;
}
@@ -415,7 +438,9 @@ static gboolean on_handle_stop(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_resume(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("playresume");
+#endif
shairport_sync_remote_control_complete_resume(skeleton, invocation);
return TRUE;
}
@@ -423,7 +448,9 @@ static gboolean on_handle_resume(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_shuffle_songs(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("shuffle_songs");
+#endif
shairport_sync_remote_control_complete_shuffle_songs(skeleton, invocation);
return TRUE;
}
@@ -431,7 +458,9 @@ static gboolean on_handle_shuffle_songs(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_volume_up(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("volumeup");
+#endif
shairport_sync_remote_control_complete_volume_up(skeleton, invocation);
return TRUE;
}
@@ -439,7 +468,9 @@ static gboolean on_handle_volume_up(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("volumedown");
+#endif
shairport_sync_remote_control_complete_volume_down(skeleton, invocation);
return TRUE;
}
@@ -449,9 +480,11 @@ static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleto
const gdouble volume,
__attribute__((unused)) gpointer user_data) {
debug(2, "Set airplay volume to %.6f.", volume);
+#ifdef CONFIG_DACP_CLIENT
char command[256] = "";
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume);
send_simple_dacp_command(command);
+#endif
shairport_sync_remote_control_complete_set_airplay_volume(skeleton, invocation);
return TRUE;
}
@@ -683,17 +716,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton,
gdouble iv = shairport_sync_get_volume(skeleton);
if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) {
debug(2, ">> set volume to %7.4f.", iv);
-
- pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed
- pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
-
- if (principal_conn != NULL) {
- player_volume(iv, principal_conn);
- principal_conn->own_airplay_volume = iv;
- principal_conn->own_airplay_volume_set = 1;
- }
- pthread_cleanup_pop(1); // release the principal_conn lock
- config.airplay_volume = iv;
+ config.airplay_volume = iv;
} else {
debug(1, ">> invalid volume: %f. Ignored.", iv);
shairport_sync_set_volume(skeleton, config.airplay_volume);
@@ -737,6 +760,12 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
+#ifdef CONFIG_AIRPLAY_2
+ if (strcasecmp(th, "ffmpeg") != 0) {
+ warn(" This request, to set the decoder to \"%s\", is ignored. For AirPlay 2, the FFmpeg decoder is always used.",
+ th);
+ }
+#else
if ((strcasecmp(th, "hammerton") == 0) &&
((config.decoders_supported & (1 << decoder_hammerton)) != 0))
config.decoder_in_use = 1 << decoder_hammerton;
@@ -747,12 +776,13 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
((config.decoders_supported & (1 << decoder_ffmpeg_alac)) != 0))
config.decoder_in_use = 1 << decoder_ffmpeg_alac;
else {
- warn("An unrecognised or unsupported ALAC decoder: \"%s\" was requested via D-Bus interface. "
+ warn("An unrecognised or unsupported decoder: \"%s\" was requested via D-Bus interface. "
"(Possibly "
"support for this decoder was not compiled "
"into this version of Shairport Sync.)",
th);
}
+#endif
return TRUE;
}
@@ -838,6 +868,7 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton,
return TRUE;
}
+#ifdef CONFIG_DACP_CLIENT
gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton,
__attribute__((unused)) gpointer user_data) {
// debug(1,"notify_shuffle_callback called");
@@ -847,9 +878,17 @@ gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton,
send_simple_dacp_command("setproperty?dacp.shufflestate=0");
return TRUE;
}
+#else
+gboolean notify_shuffle_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton,
+ __attribute__((unused)) gpointer user_data) {
+ return TRUE;
+}
+#endif
+#ifdef CONFIG_DACP_CLIENT
gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleton,
__attribute__((unused)) gpointer user_data) {
+
// debug(1,"notify_loop_status_callback called");
char *th = (char *)shairport_sync_advanced_remote_control_get_loop_status(skeleton);
// enum volume_control_profile_type previous_volume_control_profile =
@@ -884,6 +923,12 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto
}
return TRUE;
}
+#else
+gboolean notify_loop_status_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton,
+ __attribute__((unused)) gpointer user_data) {
+ return TRUE;
+}
+#endif
static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) const gchar *command,
@@ -900,10 +945,12 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
__attribute__((unused)) gpointer user_data) {
debug(1, "RemoteCommand with command \"%s\".", command);
int reply = 0;
+ char *client_reply_hex = "";
+#ifdef CONFIG_DACP_CLIENT
char *client_reply = NULL;
ssize_t reply_size = 0;
reply = dacp_send_command((const char *)command, &client_reply, &reply_size);
- char *client_reply_hex = alloca(reply_size * 2 + 1);
+ client_reply_hex = alloca(reply_size * 2 + 1);
if (client_reply_hex) {
char *p = client_reply_hex;
if (client_reply) {
@@ -917,6 +964,7 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
}
*p = '\0';
}
+#endif
shairport_sync_complete_remote_command(skeleton, invocation, reply, client_reply_hex);
return TRUE;
}
@@ -958,7 +1006,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
shairportSyncAdvancedRemoteControlSkeleton =
shairport_sync_advanced_remote_control_skeleton_new();
-
g_dbus_interface_skeleton_export(
G_DBUS_INTERFACE_SKELETON(shairportSyncAdvancedRemoteControlSkeleton), connection,
"/org/gnome/ShairportSync", NULL);
@@ -1015,9 +1062,10 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
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",
G_CALLBACK(on_handle_rewind), NULL);
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-toggle-mute",
diff --git a/definitions.h b/definitions.h
index 3eeaf79c..18a74d97 100644
--- a/definitions.h
+++ b/definitions.h
@@ -5,6 +5,10 @@
#include "config.h"
+
+// for generating our own UUIDs
+#define SHAIRPORT_SYNC_DEVICE_NAMESPACE "01d7c137-6316-455d-a52f-dfb529f26adf"
+
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). ------------------------------ */
#include
diff --git a/mdns_avahi.c b/mdns_avahi.c
index cf45ba23..fd0deedc 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -32,7 +32,11 @@
#include "common.h"
#include "mdns.h"
-#include "rtsp.h"
+
+#ifdef CONFIG_METADATA
+#include "metadata/core.h"
+#endif
+
#ifdef CONFIG_DACP_CLIENT
#include "dacp.h"
#endif
diff --git a/metadata/core.c b/metadata/core.c
new file mode 100644
index 00000000..d4537cf5
--- /dev/null
+++ b/metadata/core.c
@@ -0,0 +1,310 @@
+/*
+ * Core metadata handler.
+ * This file is part of Shairport Sync.
+ * Copyright (c) Mike Brady 2017--2026
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "core.h"
+#include "pc_queue.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef CONFIG_METADATA_PIPE
+#include "pipe.h"
+#endif
+
+#ifdef CONFIG_METADATA_MULTICAST
+#include "multicast.h"
+#endif
+
+#ifdef CONFIG_METADATA_HUB
+#include "hub.h"
+#endif
+
+#ifdef CONFIG_MQTT
+#include "mqtt.h"
+#endif
+
+
+// Metadata is not used by shairport-sync.
+// Instead we send all metadata to a fifo pipe, so that other apps can listen to
+// the pipe and use the metadata.
+
+// We use two 4-character codes to identify each piece of data and we send the
+// data itself, if any,
+// in base64 form.
+
+// The first 4-character code, called the "type", is either:
+// 'core' for all the regular metadadata coming from iTunes, etc., or
+// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync
+// itself, such as
+// start/end delimiters, etc.
+
+// For 'core' metadata, the second 4-character code is the 4-character metadata
+// code coming from
+// iTunes etc.
+// For 'ssnc' metadata, the second 4-character code is used to distinguish the
+// messages.
+
+// Cover art is not tagged in the same way as other metadata, it seems, so is
+// sent as an 'ssnc' type
+// metadata message with the code 'PICT'
+// Here are the 'ssnc' codes defined so far:
+// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the
+// first few bytes to see
+// which.
+// 'abeg' -- active mode entered. No arguments
+// 'aend' -- active mode exited. No arguments
+// 'pbeg' -- play stream begin. No arguments
+// 'pend' -- play stream end. No arguments
+// 'pfls' -- play stream flush. The argument is an unsigned 32-bit
+// frame number. It seems that all frames up to but not
+// including this frame are to be flushed.
+//
+// 'prsm' -- play stream resume. No arguments. (deprecated)
+// 'paus' -- buffered audio stream paused. No arguments.
+// 'pres' -- buffered audio stream resumed. No arguments.
+// 'pffr' -- the first frame of a play session has been received and has been validly
+// timed. The argument is
+// "/"."
+// 'pdis' -- a discontinuity in the timestamps of incoming frames has been detected.
+// timed. The argument is "/".
+// discontinuity is the actual frame number less the expected frame number
+// positive means a gap
+// 'pvol' -- play volume. The volume is sent as a string --
+// "airplay_volume,volume,lowest_volume,highest_volume"
+// volume, lowest_volume and highest_volume are given in dB.
+// The "airplay_volume" is what's sent to the player, and is from
+// 0.00 down to -30.00,
+// with -144.00 meaning mute.
+// This is linear on the volume control slider of iTunes or iOS
+// AirPlay.
+// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP
+// timestamps for the start
+// of the current play sequence, the current play point and the end of the
+// play sequence.
+// I guess the timestamps wrap at 2^32.
+// 'mdst' -- a sequence of metadata is about to start; will have, as data,
+// the rtptime associated with the metadata, if available
+// 'mden' -- a sequence of metadata has ended; will have, as data, the
+// rtptime associated with the metadata, if available
+// 'pcst' -- a picture is about to be sent; will have, as data, the rtptime
+// associated with the picture, if available
+// 'pcen' -- a picture has been sent; will have, as data, the rtptime
+// associated with the metadata, if available
+// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session.
+// 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 facilitate remote control of the source.
+// There is some information at http://nto.github.io/AirPlay.html about
+// remote control of the source.
+//
+// 'daid' -- this is the source's DACP-ID (if it has one -- it's not
+// guaranteed), useful if you want to remotely control the source. Use this
+// string to identify the source's remote control on the network.
+// 'acre' -- this is the source's Active-Remote token, necessary if you want
+// to send commands to the source's remote control (if it has one).
+// `clip` -- the payload is the IP number of the client, i.e. the sender of audio.
+// Can be an IPv4 or an IPv6 number. In AirPlay 2 operation, it is sent as soon
+// as the client has exclusive access to the player and after any existing
+// play session has been interrupted and terminated.
+// `conn` -- the payload is the IP number of the client, i.e. the sender of audio.
+// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message.
+// It is sent as soon as the client requests access to the player.
+// If Shairport Sync is already playing, this message is sent before the current
+// play session is stopped.
+// `svip` -- the payload is the IP number of the server, i.e. the player itself.
+// Can be an IPv4 or an IPv6 number.
+// `svna` -- the payload is the service name of the player, i.e. the name by
+// which it is seen in the AirPlay menu.
+// `disc` -- the payload is the IP number of the client, i.e. the sender of audio.
+// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message.
+// It is sent when a client has been disconnected.
+// `dapo` -- the payload is the port number (as text) on the server to which remote
+// control commands should be sent. It is 3689 for iTunes but varies for iOS devices.
+// ``
+
+int metadata_running = 0;
+
+void metadata_pack_cleanup_function(void *arg) {
+ // debug(1, "metadata_pack_cleanup_function called");
+ metadata_package *pack = (metadata_package *)arg;
+ if (pack->carrier)
+ msg_free(&pack->carrier); // release the message
+ else if (pack->data)
+ free(pack->data);
+ // debug(1, "metadata_pack_cleanup_function exit");
+}
+
+void metadata_init(void) {
+
+#ifdef CONFIG_METADATA_PIPE
+ metadata_pipe_queue_init();
+#endif
+
+#ifdef CONFIG_METADATA_MULTICAST
+ metadata_multicast_queue_init();
+#endif
+
+#ifdef CONFIG_METADATA_HUB
+ metadata_hub_queue_init();
+#endif
+
+#ifdef CONFIG_MQTT
+ metadata_mqtt_queue_init();
+#endif
+ metadata_running = 1;
+}
+
+void metadata_stop(void) {
+ if (metadata_running) {
+ debug(2, "metadata_stop called.");
+
+#ifdef CONFIG_MQTT
+ metadata_mqtt_queue_stop();
+#endif
+
+#ifdef CONFIG_METADATA_HUB
+ metadata_hub_queue_stop();
+#endif
+
+#ifdef CONFIG_METADATA_MULTICAST
+ metadata_multicast_queue_stop();
+#endif
+
+#ifdef CONFIG_METADATA_PIPE
+ metadata_pipe_queue_stop();
+#endif
+
+ }
+}
+
+int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block) {
+
+ // clang-format off
+ // parameters:
+ // type,
+ // code,
+ // pointer to data or NULL,
+ // length of data or NULL,
+ // the rtsp_message or NULL,
+
+ // the rtsp_message is sent for 'core' messages, because it contains the data
+ // and must not be freed until the data has been read.
+ // So, it is passed to send_metadata to be retained, sent to the thread where metadata
+ // is processed and released (and probably freed).
+
+ // The rtsp_message is also sent for certain non-'core' messages.
+
+ // The reading of the parameters is a bit complex:
+ // If the rtsp_message field is non-null, then it represents an rtsp_message
+ // and the data pointer is assumed to point to something within it.
+ // The reference counter of the rtsp_message is incremented here and
+ // should be decremented by the metadata handler when finished.
+ // If the reference count reduces to zero, the message will be freed.
+
+ // If the rtsp_message is NULL, then if the pointer is non-null then the data it
+ // points to, of the length specified, is memcpy'd and passed to the metadata
+ // handler. The handler should free it when done.
+
+ // If the rtsp_message is NULL and the pointer is also NULL, nothing further
+ // is done.
+ // clang-format on
+
+ metadata_package pack;
+ pack.type = type;
+ pack.code = code;
+ pack.length = length;
+ pack.carrier = carrier;
+ pack.data = (char *)data;
+ if (pack.carrier) {
+ msg_retain(pack.carrier);
+ } else {
+ if (data)
+ pack.data = memdup(data, length); // only if it's not a null
+ }
+
+ // debug(1, "send_metadata_to_queue %x/%x", type, code);
+ int rc = pc_queue_add_item(queue, &pack, block);
+ if (rc != 0) {
+ if (pack.carrier) {
+ if (rc == EWOULDBLOCK)
+ debug(2,
+ "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR ", "
+ "length %u, message %d.",
+ queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length,
+ pack.carrier->index_number);
+ msg_free(&pack.carrier);
+ } else {
+ if (rc == EWOULDBLOCK)
+ debug(
+ 2,
+ "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR ", length %u.",
+ queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length);
+ if (pack.data)
+ free(pack.data);
+ }
+ }
+ return rc;
+}
+
+int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length,
+ rtsp_message *carrier, int block) {
+ int rc = 0;
+ if (config.metadata_enabled) {
+
+#ifdef CONFIG_METADATA_PIPE
+ rc = send_metadata_to_pipe_queue(type, code, data, length, carrier, block);
+#endif
+
+#ifdef CONFIG_METADATA_MULTICAST
+ rc =
+ send_metadata_to_multicast_queue(type, code, data, length, carrier, block);
+#endif
+
+#ifdef CONFIG_METADATA_HUB
+ rc = send_metadata_to_hub_queue(type, code, data, length, carrier, block);
+#endif
+
+#ifdef CONFIG_MQTT
+ rc = send_metadata_to_mqtt_queue(type, code, data, length, carrier, block);
+#endif
+
+ }
+ return rc;
+}
+
+int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length,
+ const int block) {
+ return send_metadata('ssnc', code, data, length, NULL, block);
+}
+
diff --git a/metadata/core.h b/metadata/core.h
new file mode 100644
index 00000000..85ed7646
--- /dev/null
+++ b/metadata/core.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "common.h"
+#include "config.h"
+#include "../rtsp.h"
+#include "pc_queue.h"
+#include
+
+typedef struct {
+ uint32_t type;
+ uint32_t code;
+ char *data;
+ uint32_t length;
+ rtsp_message *carrier;
+} metadata_package;
+
+void metadata_pack_cleanup_function(void *arg);
+
+// initialise and completely delete the metadata stuff
+
+void metadata_init(void);
+void metadata_stop(void);
+
+
+int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block);
+
+// sends metadata out to the metadata system.
+
+int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length,
+ rtsp_message *carrier, int block);
+
+// It is sent with the type 'ssnc' the given code, data and length
+// The handler at the other end must know what to do with the data
+// e.g. if it's malloced, to free it, etc.
+// nothing is done automatically
+int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length,
+ const int block);
\ No newline at end of file
diff --git a/metadata_hub.c b/metadata/hub.c
similarity index 91%
rename from metadata_hub.c
rename to metadata/hub.c
index db3bac70..fe2c2e14 100644
--- a/metadata_hub.c
+++ b/metadata/hub.c
@@ -5,7 +5,7 @@
* then you need a metadata hub,
* where everything is stored
* This file is part of Shairport Sync.
- * Copyright (c) Mike Brady 2017--2025
+ * Copyright (c) Mike Brady 2017--2026
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -44,7 +44,9 @@
#include "common.h"
#include "dacp.h"
-#include "metadata_hub.h"
+#include "hub.h"
+#include "pc_queue.h"
+#include "core.h"
#ifdef CONFIG_MBEDTLS
#include
@@ -59,6 +61,12 @@
#include
#endif
+// metadata queue definitions
+pc_queue metadata_hub_queue;
+#define metadata_hub_queue_size 500
+metadata_package metadata_hub_queue_items[metadata_hub_queue_size];
+pthread_t metadata_hub_thread;
+
struct metadata_bundle metadata_store;
int metadata_hub_initialised = 0;
@@ -710,3 +718,56 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin
pthread_cleanup_pop(0); // don't remove the lock
metadata_hub_modify_epilog(changed);
}
+
+void metadata_hub_close(void) {}
+
+void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) {
+ // debug(2, "metadata_hub_thread_cleanup_function called");
+ metadata_hub_close();
+}
+
+void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
+ // #include
+ // debug(1, "metadata_hub_thread_function PID %d", syscall(SYS_gettid));
+ metadata_package pack;
+ pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL);
+ while (1) {
+ pc_queue_get_item(&metadata_hub_queue, &pack);
+ pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
+ if (pack.carrier) {
+ debug(4, " hub: type %x, code %x, length %u, message %d.", pack.type,
+ pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(4, " hub: type %x, code %x, length %u.", pack.type, pack.code,
+ pack.length);
+ }
+ metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
+ debug(4, " hub: done.");
+ pthread_cleanup_pop(1);
+ }
+ pthread_cleanup_pop(1); // will never happen
+ pthread_exit(NULL);
+}
+
+void metadata_hub_queue_init() {
+ // create a pc_queue for the metadata hub
+ pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package),
+ metadata_hub_queue_size, "hub");
+ if (named_pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL,
+ "metadata hub") != 0)
+ debug(1, "Failed to create metadata hub thread!");
+}
+
+void metadata_hub_queue_stop() {
+ // debug(2, "metadata stop hub thread.");
+ pthread_cancel(metadata_hub_thread);
+ pthread_join(metadata_hub_thread, NULL);
+ pc_queue_delete(&metadata_hub_queue);
+ // debug(2, "metadata stop hub done.");
+}
+
+int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block) {
+ return send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
+}
diff --git a/metadata_hub.h b/metadata/hub.h
similarity index 94%
rename from metadata_hub.h
rename to metadata/hub.h
index aadd51e4..754f9c4a 100644
--- a/metadata_hub.h
+++ b/metadata/hub.h
@@ -1,6 +1,7 @@
#pragma once
#include "common.h"
#include "config.h"
+#include "rtsp.h"
#include
#define number_of_watchers 2
@@ -189,3 +190,11 @@ void _metadata_hub_read_epilog(const char *filename, const int linenumber);
#define metadata_hub_read_prolog(void) _metadata_hub_read_prolog(__FILE__, __LINE__)
#define metadata_hub_read_epilog(void) _metadata_hub_modify_epilog(__FILE__, __LINE__)
+
+// metadata queue stuff
+void metadata_hub_queue_init();
+void metadata_hub_queue_stop();
+int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block);
+
diff --git a/metadata/multicast.c b/metadata/multicast.c
new file mode 100644
index 00000000..de4930fb
--- /dev/null
+++ b/metadata/multicast.c
@@ -0,0 +1,180 @@
+ #include "multicast.h"
+ #include "core.h"
+ #include "pc_queue.h"
+
+ #include
+ #include
+
+
+// A special sub-protocol is used for sending large data items over UDP
+// If the payload exceeded 4 MB, it is chunked using the following format:
+// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
+// Notice that the number of items is different to the standard
+
+#define METADATA_SNDBUF (4 * 1024 * 1024)
+static int metadata_sock = -1;
+static struct sockaddr_in metadata_sockaddr;
+static char *metadata_sockmsg;
+pc_queue metadata_multicast_queue;
+#define metadata_multicast_queue_size 500
+metadata_package metadata_multicast_queue_items[metadata_multicast_queue_size];
+pthread_t metadata_multicast_thread;
+
+int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block) {
+ return send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
+}
+
+void metadata_create_multicast_socket(void) {
+ if (config.metadata_enabled == 0)
+ return;
+
+ // Unlike metadata pipe, socket is opened once and stays open,
+ // so we can call it in create
+ if (config.metadata_sockaddr && config.metadata_sockport) {
+ metadata_sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (metadata_sock < 0) {
+ debug(1, "Could not open metadata socket");
+ } else {
+ int buffer_size = METADATA_SNDBUF;
+ setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
+ bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr));
+ metadata_sockaddr.sin_family = AF_INET;
+ metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr);
+ metadata_sockaddr.sin_port = htons(config.metadata_sockport);
+ metadata_sockmsg = malloc(config.metadata_sockmsglength);
+ if (metadata_sockmsg) {
+ memset(metadata_sockmsg, 0, config.metadata_sockmsglength);
+ } else {
+ die("Could not malloc metadata multicast socket buffer");
+ }
+ }
+ }
+}
+
+void metadata_delete_multicast_socket(void) {
+ if (config.metadata_enabled == 0)
+ return;
+ if (metadata_sock != -1) {
+ shutdown(metadata_sock, SHUT_RDWR); // we want to immediately deallocate the buffer
+ close(metadata_sock);
+ }
+ if (metadata_sockmsg)
+ free(metadata_sockmsg);
+}
+
+void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
+ // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code,
+ // length);
+ if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) {
+ char *ptr = metadata_sockmsg;
+ uint32_t v;
+ v = htonl(type);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(code);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ memcpy(ptr, data, length);
+ sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr,
+ sizeof(metadata_sockaddr));
+ } else if (metadata_sock >= 0) {
+ // send metadata in numbered chunks using the protocol:
+ // ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data)
+
+ uint32_t chunk_ix = 0;
+ if (config.metadata_sockmsglength == 24)
+ die("A divide by zero almost occurred (config.metadata_sockmsglength = 24).");
+ uint32_t chunk_total = length / (config.metadata_sockmsglength - 24);
+ if (chunk_total * (config.metadata_sockmsglength - 24) < length) {
+ chunk_total++;
+ }
+ uint32_t remaining = length;
+ uint32_t v;
+ char *data_crsr = data;
+ do {
+ char *ptr = metadata_sockmsg;
+ memcpy(ptr, "ssncchnk", 8);
+ ptr += 8;
+ v = htonl(chunk_ix);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(chunk_total);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(type);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ v = htonl(code);
+ memcpy(ptr, &v, 4);
+ ptr += 4;
+ size_t datalen = remaining;
+ if (datalen > config.metadata_sockmsglength - 24) {
+ datalen = config.metadata_sockmsglength - 24;
+ }
+ memcpy(ptr, data_crsr, datalen);
+ data_crsr += datalen;
+ sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0,
+ (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr));
+ chunk_ix++;
+ remaining -= datalen;
+ if (remaining == 0)
+ break;
+ } while (1);
+ }
+}
+
+void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) {
+ // debug(2, "metadata_multicast_thread_cleanup_function called");
+ metadata_delete_multicast_socket();
+}
+
+void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
+ // #include
+ // debug(1, "metadata_multicast_thread_function PID %d", syscall(SYS_gettid));
+ metadata_create_multicast_socket();
+ metadata_package pack;
+ pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL);
+ while (1) {
+ pc_queue_get_item(&metadata_multicast_queue, &pack);
+ pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
+ if (config.metadata_enabled) {
+ if (pack.carrier) {
+ debug(4,
+ " multicast: type "
+ "%x, code %x, length %u, message %d.",
+ pack.type, pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(4,
+ " multicast: type "
+ "%x, code %x, length %u.",
+ pack.type, pack.code, pack.length);
+ }
+ metadata_multicast_process(pack.type, pack.code, pack.data, pack.length);
+ debug(4,
+ " multicast: done.");
+ }
+ pthread_cleanup_pop(1);
+ }
+ pthread_cleanup_pop(1); // will never happen
+ pthread_exit(NULL);
+}
+
+void metadata_multicast_queue_init() {
+ // create a pc_queue for the metadata_multicast_queue
+ pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
+ sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
+ if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function,
+ NULL, "metadata mcst") != 0)
+ debug(1, "Failed to create metadata multicast thread!");
+}
+
+void metadata_multicast_queue_stop() {
+ if (metadata_multicast_thread) {
+ pthread_cancel(metadata_multicast_thread);
+ pthread_join(metadata_multicast_thread, NULL);
+ pc_queue_delete(&metadata_multicast_queue);
+ // debug(2, "metadata stop multicast done.");
+ }
+}
diff --git a/metadata/multicast.h b/metadata/multicast.h
new file mode 100644
index 00000000..0f35b1be
--- /dev/null
+++ b/metadata/multicast.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include
+#include "rtsp.h"
+
+void metadata_multicast_queue_init();
+void metadata_multicast_queue_stop();
+int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block);
\ No newline at end of file
diff --git a/metadata/pc_queue.c b/metadata/pc_queue.c
new file mode 100644
index 00000000..296ccbdc
--- /dev/null
+++ b/metadata/pc_queue.c
@@ -0,0 +1,172 @@
+/*
+ * Producer--consumer queue and access methods.
+ * This file is part of Shairport Sync.
+ * Copyright (c) Mike Brady 2017--2026
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pc_queue.h"
+
+#include
+#include
+#include
+
+void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
+ const char *name) {
+ if (name)
+ debug(4, "Creating metadata queue \"%s\".", name);
+ else
+ debug(1, "Creating an unnamed metadata queue.");
+ pthread_mutex_init(&the_queue->pc_queue_lock, NULL);
+ pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL);
+ pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL);
+ the_queue->item_size = item_size;
+ the_queue->items = items;
+ the_queue->count = 0;
+ the_queue->capacity = number_of_items;
+ the_queue->toq = 0;
+ the_queue->eoq = 0;
+ if (name == NULL)
+ the_queue->name = NULL;
+ else
+ the_queue->name = strdup(name);
+}
+
+void pc_queue_delete(pc_queue *the_queue) {
+ if (the_queue->name)
+ debug(2, "Deleting metadata queue \"%s\".", the_queue->name);
+ else
+ debug(1, "Deleting an unnamed metadata queue.");
+ if (the_queue->name != NULL)
+ free(the_queue->name);
+ // debug(2, "destroying pc_queue_item_removed_signal");
+ pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal);
+ // debug(2, "destroying pc_queue_item_added_signal");
+ pthread_cond_destroy(&the_queue->pc_queue_item_added_signal);
+ // debug(2, "destroying pc_queue_lock");
+ pthread_mutex_destroy(&the_queue->pc_queue_lock);
+ // debug(2, "destroying signals and locks done");
+}
+
+void pc_queue_cleanup_handler(void *arg) {
+ // debug(1, "pc_queue_cleanup_handler called.");
+ pc_queue *the_queue = (pc_queue *)arg;
+ int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock);
+ if (rc)
+ debug(1, "Error unlocking for pc_queue_add_item or pc_queue_get_item.");
+}
+
+int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
+ int response = 0;
+ int rc;
+ if (the_queue) {
+ if (block == 0) {
+ rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 4);
+ if (rc == EBUSY)
+ return EBUSY;
+ } else
+ rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4);
+ if (rc)
+ debug(1, "Error %d (\"%s\") locking for pc_queue_add_item. Block is %d.", rc, strerror(rc),
+ block);
+ pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
+ // leave this out if you want this to return if the queue is already full
+ // irrespective of the block flag.
+ /*
+ while (the_queue->count == the_queue->capacity) {
+ rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal,
+ &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed");
+ }
+ */
+ if (the_queue->count < the_queue->capacity) {
+ uint32_t i = the_queue->eoq;
+ void *p = the_queue->items + the_queue->item_size * i;
+ // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
+ memcpy(p, the_stuff, the_queue->item_size);
+
+ // update the pointer
+ i++;
+ if (i == the_queue->capacity)
+ // fold pointer if necessary
+ i = 0;
+ the_queue->eoq = i;
+ the_queue->count++;
+ // debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count,
+ // the_queue->capacity);
+ if (the_queue->count == the_queue->capacity)
+ debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name,
+ the_queue->count);
+ rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
+ if (rc)
+ debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item",
+ the_queue->name);
+ } else {
+ response = EWOULDBLOCK; // a bit arbitrary, this.
+ debug(3,
+ "metadata queue \"%s\": is already full with %d items in it. Not adding this item to "
+ "the queue.",
+ the_queue->name, the_queue->count);
+ }
+ pthread_cleanup_pop(1); // unlock the queue lock.
+ } else {
+ debug(1, "Adding an item to a NULL queue");
+ }
+ return response;
+}
+
+int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
+ int rc;
+ if (the_queue) {
+ rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4);
+ if (rc)
+ debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name);
+ pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
+ while (the_queue->count == 0) {
+ rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock);
+ if (rc)
+ debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name);
+ }
+ uint32_t i = the_queue->toq;
+ // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq;
+ void *p = the_queue->items + the_queue->item_size * i;
+ memcpy(the_stuff, p, the_queue->item_size);
+
+ // update the pointer
+ i++;
+ if (i == the_queue->capacity)
+ // fold pointer if necessary
+ i = 0;
+ the_queue->toq = i;
+ the_queue->count--;
+ debug(4, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count,
+ the_queue->capacity);
+ rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal);
+ if (rc)
+ debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name);
+ pthread_cleanup_pop(1); // unlock the queue lock.
+ } else {
+ debug(1, "Removing an item from a NULL queue");
+ }
+ return 0;
+}
+
diff --git a/metadata/pc_queue.h b/metadata/pc_queue.h
new file mode 100644
index 00000000..c5afacc8
--- /dev/null
+++ b/metadata/pc_queue.h
@@ -0,0 +1,26 @@
+
+#pragma once
+
+#include "common.h"
+#include "config.h"
+#include
+
+typedef struct {
+ pthread_mutex_t pc_queue_lock;
+ pthread_cond_t pc_queue_item_added_signal;
+ pthread_cond_t pc_queue_item_removed_signal;
+ char *name;
+ size_t item_size; // number of bytes in each item
+ uint32_t count; // number of items in the queue
+ uint32_t capacity; // maximum number of items
+ uint32_t toq; // first item to take
+ uint32_t eoq; // free space at end of queue
+ void *items; // a pointer to where the items are actually stored
+} pc_queue; // producer-consumer queue
+
+void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
+ const char *name);
+
+void pc_queue_delete(pc_queue *the_queue);
+int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block);
+int pc_queue_get_item(pc_queue *the_queue, void *the_stuff);
diff --git a/metadata/pipe.c b/metadata/pipe.c
new file mode 100644
index 00000000..35128861
--- /dev/null
+++ b/metadata/pipe.c
@@ -0,0 +1,186 @@
+ #include "pipe.h"
+ #include "core.h"
+ #include "pc_queue.h"
+
+ #include
+ #include
+ #include
+ #include
+
+static int fd = -1; // the pipe file descriptor
+
+pc_queue metadata_queue;
+#define metadata_queue_size 500
+metadata_package metadata_queue_items[metadata_queue_size];
+pthread_t metadata_thread;
+
+void metadata_open(void) {
+ if (config.metadata_enabled == 0)
+ return;
+
+ size_t pl = strlen(config.metadata_pipename) + 1;
+
+ char *path = malloc(pl + 1);
+ snprintf(path, pl + 1, "%s", config.metadata_pipename);
+
+ fd = try_to_open_pipe_for_writing(path);
+ free(path);
+}
+
+static void metadata_close(void) {
+ if (fd < 0)
+ return;
+ close(fd);
+ fd = -1;
+}
+
+void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
+ // debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length);
+ int ret = 0;
+ // readers may go away and come back
+
+ if (fd < 0)
+ metadata_open();
+ if (fd < 0)
+ return;
+ char thestring[1024];
+ snprintf(thestring, 1024, "- %x
%x%u", type, code,
+ length);
+ // ret = non_blocking_write(fd, thestring, strlen(thestring));
+ ret = write(fd, thestring, strlen(thestring));
+ if (ret < 0) {
+ // debug(1,"metadata_process error %d exit 1",ret);
+ return;
+ }
+ if ((data != NULL) && (length > 0)) {
+ snprintf(thestring, 1024, "\n\n");
+ // ret = non_blocking_write(fd, thestring, strlen(thestring));
+ ret = write(fd, thestring, strlen(thestring));
+ if (ret < 0) {
+ // debug(1,"metadata_process error %d exit 2",ret);
+ return;
+ }
+ // here, we write the data in base64 form using our nice base64 encoder
+ // but, we break it into lines of 76 output characters, except for the last
+ // one.
+ // thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time
+ size_t remaining_count = length;
+ char *remaining_data = data;
+ // size_t towrite_count;
+ char outbuf[76];
+ while ((remaining_count) && (ret >= 0)) {
+ size_t towrite_count = remaining_count;
+ if (towrite_count > 57)
+ towrite_count = 57;
+ size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit
+ if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) ==
+ NULL)
+ debug(1, "Error encoding base64 data.");
+ // debug(1,"Remaining count: %d ret: %d, outbuf_size:
+ // %d.",remaining_count,ret,outbuf_size);
+ // ret = non_blocking_write(fd, outbuf, outbuf_size);
+ ret = write(fd, outbuf, outbuf_size);
+ if (ret < 0) {
+ // debug(1,"metadata_process error %d exit 3",ret);
+ return;
+ }
+ remaining_data += towrite_count;
+ remaining_count -= towrite_count;
+ }
+ snprintf(thestring, 1024, "");
+ // ret = non_blocking_write(fd, thestring, strlen(thestring));
+ ret = write(fd, thestring, strlen(thestring));
+ if (ret < 0) {
+ // debug(1,"metadata_process error %d exit 4",ret);
+ return;
+ }
+ }
+ snprintf(thestring, 1024, " \n");
+ // ret = non_blocking_write(fd, thestring, strlen(thestring));
+ ret = write(fd, thestring, strlen(thestring));
+ if (ret < 0) {
+ // debug(1,"metadata_process error %d exit 5",ret);
+ return;
+ }
+}
+
+void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) {
+ // debug(2, "metadata_thread_cleanup_function called");
+ metadata_close();
+}
+
+void *metadata_thread_function(__attribute__((unused)) void *ignore) {
+ // #include
+ // debug(1, "metadata_thread_function PID %d", syscall(SYS_gettid));
+ metadata_package pack;
+ pthread_cleanup_push(metadata_thread_cleanup_function, NULL);
+ while (1) {
+ pc_queue_get_item(&metadata_queue, &pack);
+ pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
+ if (config.metadata_enabled) {
+ if (pack.carrier) {
+ debug(4, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code,
+ pack.length, pack.carrier->index_number);
+ } else {
+ debug(4, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
+ }
+ metadata_process(pack.type, pack.code, pack.data, pack.length);
+ debug(4, " pipe: done.");
+ }
+ pthread_cleanup_pop(1);
+ }
+ pthread_cleanup_pop(1); // will never happen
+ pthread_exit(NULL);
+}
+
+void metadata_pipe_queue_init() {
+ // create the metadata pipe, if necessary
+ size_t pl = strlen(config.metadata_pipename) + 1;
+ char *path = malloc(pl + 1);
+ snprintf(path, pl + 1, "%s", config.metadata_pipename);
+ mode_t oldumask = umask(000);
+ if (mkfifo(path, 0666) && errno != EEXIST)
+ die("Could not create metadata pipe \"%s\".", path);
+ umask(oldumask);
+ debug(1, "metadata pipe name is \"%s\".", path);
+
+ // try to open it
+ fd = try_to_open_pipe_for_writing(path);
+ // we check that it's not a "real" error. From the "man 2 open" page:
+ // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
+ // open for reading." Which is okay.
+ if ((fd == -1) && (errno != ENXIO)) {
+ char errorstring[1024];
+ strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
+ (char *)errorstring, path);
+ }
+ free(path);
+
+ // initialise the metadata queues first, otherwise the might be a race condition
+ // create a pc_queue for the metadata pipe
+ pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package),
+ metadata_queue_size, "pipe");
+
+ if (named_pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL,
+ "metadata pipe") != 0)
+ debug(1, "Failed to create metadata thread!");
+}
+
+void metadata_pipe_queue_stop() {
+ if (metadata_thread) {
+ // debug(2, "metadata stop metadata_thread thread.");
+ pthread_cancel(metadata_thread);
+ pthread_join(metadata_thread, NULL);
+ pc_queue_delete(&metadata_queue);
+ // debug(2, "metadata_stop finished successfully.");
+ }
+}
+
+int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block) {
+ return send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
+}
diff --git a/metadata/pipe.h b/metadata/pipe.h
new file mode 100644
index 00000000..2a242983
--- /dev/null
+++ b/metadata/pipe.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include
+#include "rtsp.h"
+
+void metadata_pipe_queue_init();
+void metadata_pipe_queue_stop();
+int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block);
\ No newline at end of file
diff --git a/mpris-service.c b/mpris-service.c
index b7877a98..06d506c2 100644
--- a/mpris-service.c
+++ b/mpris-service.c
@@ -35,9 +35,11 @@
#include "rtp.h"
+#ifdef CONFIG_DACP_CLIENT
#include "dacp.h"
+#endif
-#include "metadata_hub.h"
+#include "metadata/hub.h"
#include "mpris-service.h"
static guint ownerID = 0;
@@ -240,28 +242,36 @@ static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *in
static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("nextitem");
+#endif
media_player2_player_complete_next(skeleton, invocation);
return TRUE;
}
static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("previtem");
+#endif
media_player2_player_complete_previous(skeleton, invocation);
return TRUE;
}
static gboolean on_handle_stop(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("stop");
+#endif
media_player2_player_complete_stop(skeleton, invocation);
return TRUE;
}
static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("pause");
+#endif
media_player2_player_complete_pause(skeleton, invocation);
return TRUE;
}
@@ -269,14 +279,18 @@ static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocat
static gboolean on_handle_play_pause(MediaPlayer2Player *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("playpause");
+#endif
media_player2_player_complete_play_pause(skeleton, invocation);
return TRUE;
}
static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("play");
+#endif
media_player2_player_complete_play(skeleton, invocation);
return TRUE;
}
@@ -284,11 +298,13 @@ static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocati
static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton,
GDBusMethodInvocation *invocation, const gdouble volume,
__attribute__((unused)) gpointer user_data) {
+#ifdef CONFIG_DACP_CLIENT
double ap_volume = mpris_volume_to_airplay_volume(volume);
debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume);
char command[256] = "";
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", ap_volume);
send_simple_dacp_command(command);
+#endif
media_player2_player_complete_play(skeleton, invocation);
return TRUE;
}
diff --git a/mqtt.c b/mqtt.c
index 64cc1aa9..9035986b 100644
--- a/mqtt.c
+++ b/mqtt.c
@@ -11,10 +11,21 @@
#include "rtp.h"
+#ifdef CONFIG_DACP_CLIENT
#include "dacp.h"
-#include "metadata_hub.h"
+#endif
+#include "metadata/hub.h"
+#include "metadata/core.h"
+#include "metadata/pc_queue.h"
+
#include "mqtt.h"
#include
+// this is for receiving metadata
+
+pc_queue metadata_mqtt_queue;
+#define metadata_mqtt_queue_size 500
+metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size];
+pthread_t metadata_mqtt_thread;
// this holds the mosquitto client
struct mosquitto *global_mosq = NULL;
@@ -71,7 +82,9 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
release_play_lock(NULL); // stop any current session and don't replace it
} else {
debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command(commands[it]);
+#endif
}
break;
}
@@ -443,3 +456,62 @@ int initialise_mqtt() {
return 0;
}
+
+// metadata handling stuff
+void metadata_mqtt_close(void) {}
+
+void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) {
+ // debug(2, "metadata_mqtt_thread_cleanup_function called");
+ metadata_mqtt_close();
+ // debug(2, "metadata_mqtt_thread_cleanup_function done");
+}
+
+void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
+ // #include
+ // debug(1, "metadata_mqtt_thread_function PID %d", syscall(SYS_gettid));
+ metadata_package pack;
+ pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL);
+ while (1) {
+ pc_queue_get_item(&metadata_mqtt_queue, &pack);
+ pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
+ if (config.mqtt_enabled) {
+ if (pack.carrier) {
+ debug(3,
+ " mqtt: type %x, code %x, length %u, message "
+ "%d.",
+ pack.type, pack.code, pack.length, pack.carrier->index_number);
+ } else {
+ debug(3, " mqtt: type %x, code %x, length %u.",
+ pack.type, pack.code, pack.length);
+ }
+ mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
+ debug(3, " mqtt: done.");
+ }
+
+ pthread_cleanup_pop(1);
+ }
+ pthread_cleanup_pop(1); // will never happen
+ pthread_exit(NULL);
+}
+
+void metadata_mqtt_queue_init() {
+ // create a pc_queue for the MQTT handler
+ pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package),
+ metadata_mqtt_queue_size, "mqtt");
+ if (named_pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL,
+ "metadata mqtt") != 0)
+ debug(1, "Failed to create metadata mqtt thread!");
+}
+void metadata_mqtt_queue_stop() {
+ // debug(2, "metadata stop mqtt thread.");
+ pthread_cancel(metadata_mqtt_thread);
+ pthread_join(metadata_mqtt_thread, NULL);
+ pc_queue_delete(&metadata_mqtt_queue);
+ // debug(2, "metadata stop mqtt done.");
+}
+int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block) {
+ return send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
+}
+
diff --git a/mqtt.h b/mqtt.h
index 69ad75bd..f450b416 100644
--- a/mqtt.h
+++ b/mqtt.h
@@ -12,4 +12,10 @@ void on_connect(struct mosquitto *mosq, void *userdata, int rc);
void on_disconnect(struct mosquitto *mosq, void *userdata, int rc);
void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg);
void _cb_log(struct mosquitto *mosq, void *userdata, int level, const char *str);
+
+void metadata_mqtt_queue_init();
+void metadata_mqtt_queue_stop();
+int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code,
+ const char *data, const uint32_t length, rtsp_message *carrier,
+ int block);
#endif /* #ifndef MQTT_H */
diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml
index dd1673ac..d24787b2 100644
--- a/org.gnome.ShairportSync.xml
+++ b/org.gnome.ShairportSync.xml
@@ -40,7 +40,7 @@
-
+
diff --git a/pair_ap/pair.c b/pair_ap/pair.c
index 966f45bc..80a48950 100644
--- a/pair_ap/pair.c
+++ b/pair_ap/pair.c
@@ -95,7 +95,7 @@ hash_init(enum hash_alg alg, HashCTX *c)
}
int
-hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len)
+hash_update(__attribute__((unused)) enum hash_alg alg, HashCTX *c, const void *data, size_t len)
{
#if CONFIG_OPENSSL
switch (alg)
diff --git a/pair_ap/pair_fruit.c b/pair_ap/pair_fruit.c
index 5b81a56a..8faf04fa 100644
--- a/pair_ap/pair_fruit.c
+++ b/pair_ap/pair_fruit.c
@@ -593,7 +593,7 @@ encrypt_ctr(unsigned char *ciphertext, int ciphertext_len,
/* -------------------------- IMPLEMENTATION -------------------------------- */
static int
-client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id)
+client_setup_new(struct pair_setup_context *handle, const char *pin, __attribute__((unused)) pair_cb add_cb, __attribute__((unused)) void *cb_arg, __attribute__((unused)) const char *device_id)
{
struct pair_client_setup_context *sctx = &handle->sctx.client;
@@ -853,7 +853,7 @@ client_setup_result(struct pair_setup_context *handle)
{
struct pair_client_setup_context *sctx = &handle->sctx.client;
char *ptr;
- int i;
+ unsigned int i;
// Last 32 bytes of the private key is the public key, so we don't need to
// explicitly export that
@@ -867,13 +867,13 @@ client_setup_result(struct pair_setup_context *handle)
static int
-client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id)
+client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, __attribute__((unused)) pair_cb cb, __attribute__((unused)) void *cb_arg, const char *device_id)
{
struct pair_client_verify_context *vctx = &handle->vctx.client;
char hex[] = { 0, 0, 0 };
size_t hexkey_len;
const char *ptr;
- int i;
+ unsigned int i;
if (!is_initialized())
return -1;
@@ -1024,7 +1024,7 @@ client_verify_response1(struct pair_verify_context *handle, const uint8_t *data,
}
static int
-client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
+client_verify_response2(struct pair_verify_context *handle, __attribute__((unused)) const uint8_t *data, __attribute__((unused)) size_t data_len)
{
struct pair_client_verify_context *vctx = &handle->vctx.client;
// TODO actually check response
diff --git a/pair_ap/pair_homekit.c b/pair_ap/pair_homekit.c
index baf94e43..525ee695 100644
--- a/pair_ap/pair_homekit.c
+++ b/pair_ap/pair_homekit.c
@@ -822,7 +822,7 @@ static void
hexread(uint8_t *out, size_t out_len, const char *in)
{
char hex[] = { 0, 0, 0 };
- int i;
+ unsigned int i;
for (i = 0; i < out_len; i++, in+=2)
{
@@ -1668,7 +1668,7 @@ static int
client_setup_result(struct pair_setup_context *handle)
{
char *ptr;
- int i;
+ unsigned int i;
assert(sizeof(handle->result_str) >= 2 * sizeof(handle->result.client_private_key) + 2 * sizeof(handle->result.server_public_key) + 1);
@@ -1686,7 +1686,7 @@ client_setup_result(struct pair_setup_context *handle)
}
static int
-client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id)
+client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, __attribute__((unused)) pair_cb cb, __attribute__((unused)) void *cb_arg, __attribute__((unused)) const char *device_id)
{
struct pair_client_verify_context *vctx = &handle->vctx.client;
size_t hexkey_len;
@@ -2888,7 +2888,7 @@ server_list_response(size_t *len, pair_list_cb cb, void *cb_arg)
}
static int
-server_list(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len)
+server_list(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, __attribute__((unused)) const uint8_t *in, __attribute__((unused)) size_t in_len)
{
// Skip reading the request, it just has state = 1 and pair method =
// PairingMethodListPairings
diff --git a/player.c b/player.c
index e15de80e..2d8b9081 100644
--- a/player.c
+++ b/player.c
@@ -72,8 +72,12 @@
#include
#endif
+#ifdef CONFIG_METADATA
+#include "metadata/core.h"
+#endif
+
#ifdef CONFIG_METADATA_HUB
-#include "metadata_hub.h"
+#include "metadata/hub.h"
#endif
#ifdef CONFIG_DACP_CLIENT
@@ -383,18 +387,6 @@ const char *get_category_string(airplay_stream_c cat) {
#ifdef CONFIG_FFMPEG
-static void avcodec_alloc_context3_cleanup_handler(void *arg) {
- debug(3, "avcodec_alloc_context3_cleanup_handler");
- AVCodecContext *codec_context = arg;
- av_free(codec_context);
-}
-
-static void avcodec_open2_cleanup_handler(__attribute__((unused)) void *arg) {
- debug(3, "avcodec_open2_cleanup_handler");
- // AVCodecContext *codec_context = arg;
- // avcodec_close(codec_context);
-}
-
static void swr_alloc_cleanup_handler(void *arg) {
debug(3, "swr_alloc_cleanup_handler");
SwrContext **swr = arg;
@@ -407,25 +399,9 @@ static void av_packet_alloc_cleanup_handler(void *arg) {
av_packet_free(pkt);
}
-/*
-static void av_frame_alloc_cleanup_handler(void *arg) {
- debug(3, "av_frame_alloc_cleanup_handler");
- AVFrame **frame = arg;
- av_frame_free(frame);
-}
-*/
-
void clear_decoding_chain(rtsp_conn_info *conn) {
if (conn->incoming_ssrc != 0) {
- // debug_mutex_lock(&conn->ab_mutex, 30000, 0);
- // ab_resync(conn);
- // debug_mutex_unlock(&conn->ab_mutex, 0);
- pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, conn->codec_context);
- pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata);
- pthread_cleanup_push(avcodec_open2_cleanup_handler, conn->codec_context);
- pthread_cleanup_pop(1); // avcodec_open2_cleanup_handler
- pthread_cleanup_pop(1); // deallocate the malloc
- pthread_cleanup_pop(1); // avcodec_alloc_context3_cleanup_handler
+ avcodec_free_context(&conn->codec_context);
conn->incoming_ssrc = SSRC_NONE;
}
}
@@ -776,6 +752,9 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
// get information about the output from the resampler
int64_t resampler_output_format = 0;
int resampler_channels_found = 0;
+
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
#if LIBAVUTIL_VERSION_MAJOR >= 57
@@ -886,6 +865,8 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
free(device_channels);
}
}
+
+ pthread_setcancelstate(oldState, NULL);
if (output_channel_map_faulty != 0) {
once(inform("The output device's %u-channel map is incomplete or faulty: \"%s\".",
@@ -1064,70 +1045,70 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
conn->codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
break;
default:
- die("Can't find a suitable codec for SSRC: %s", get_ssrc_name(ssrc));
+ die("Connection %d: can't find a suitable codec for SSRC: %s", conn->connection_number, get_ssrc_name(ssrc));
break;
}
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
+
// Get a decoder-dependent codec context
conn->codec_context = avcodec_alloc_context3(conn->codec);
- if (conn->codec_context == NULL) {
- debug(1, "Could not allocate a codec context!");
- }
- // push a deallocator -- av_free(codec_context)
- pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, conn->codec_context);
-
- // prepare to open the codec context with that codec
- // but first, if it's the ALAC decoder, prepare a magic cookie
- if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
- alac_ffmpeg_magic_cookie *extradata =
- malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it
- if (extradata == NULL)
- die("Could not allocate memory for a magic cookie.");
- // creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?)
- memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie));
- extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie));
- extradata->cookie_tag = htonl('alac');
- extradata->alac_config.frameLength = htonl(352);
- if (ssrc == ALAC_48000_S24_2) {
- extradata->alac_config.bitDepth = 24; // Seems to be S24
- extradata->alac_config.sampleRate = htonl(48000);
+ if (conn->codec_context != NULL) {
+ // push a deallocator -- av_free(codec_context)
+ // pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, &conn->codec_context);
+
+ // prepare to open the codec context with that codec
+ // but first, if it's the ALAC decoder, prepare a magic cookie
+ if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
+ alac_ffmpeg_magic_cookie *extradata =
+ malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it
+ if (extradata == NULL)
+ die("connection %d: could not allocate memory for a magic cookie.", conn->connection_number);
+ // creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?)
+ memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie));
+ extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie));
+ extradata->cookie_tag = htonl('alac');
+ extradata->alac_config.frameLength = htonl(352);
+ if (ssrc == ALAC_48000_S24_2) {
+ extradata->alac_config.bitDepth = 24; // Seems to be S24
+ extradata->alac_config.sampleRate = htonl(48000);
+ } else {
+ extradata->alac_config.bitDepth = 16; // Seems to be S16
+ extradata->alac_config.sampleRate = htonl(44100);
+ }
+ extradata->alac_config.pb = 40;
+ extradata->alac_config.mb = 10;
+ extradata->alac_config.kb = 14;
+ extradata->alac_config.numChannels = 2;
+ extradata->alac_config.maxRun = htons(255);
+ conn->codec_context->extradata = (uint8_t *)extradata;
+ conn->codec_context->extradata_size = sizeof(alac_ffmpeg_magic_cookie);
} else {
- extradata->alac_config.bitDepth = 16; // Seems to be S16
- extradata->alac_config.sampleRate = htonl(44100);
+ conn->codec_context->extradata = NULL;
+ }
+ // pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata);
+ // avcodec_free_context() will free extradata
+
+ if (avcodec_open2(conn->codec_context, conn->codec, NULL) < 0) {
+ pthread_setcancelstate(oldState, NULL);
+ die("connection %d: could not initialise the codec context", conn->connection_number);
}
- extradata->alac_config.pb = 40;
- extradata->alac_config.mb = 10;
- extradata->alac_config.kb = 14;
- extradata->alac_config.numChannels = 2;
- extradata->alac_config.maxRun = htons(255);
- conn->codec_context->extradata = (uint8_t *)extradata;
- conn->codec_context->extradata_size = sizeof(alac_ffmpeg_magic_cookie);
} else {
- conn->codec_context->extradata = NULL;
- }
- pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata);
-
- if (avcodec_open2(conn->codec_context, conn->codec, NULL) < 0) {
- die("Could not initialise the codec context");
- }
-
- // push a closer -- avcodec_close(codec_context);
- pthread_cleanup_push(avcodec_open2_cleanup_handler, conn->codec_context);
+ die("connection %d: could not allocate a codec context!", conn->connection_number);
+ }
+ pthread_setcancelstate(oldState, NULL);
conn->input_rate = get_ssrc_rate(ssrc);
+ debug(2, "Connection %d: set conn->input_rate: %u.", conn->connection_number, conn->input_rate);
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
conn->frames_per_packet = 352;
} else {
conn->frames_per_packet = 1024;
}
-
conn->codec_context->sample_rate = conn->input_rate;
-
conn->ffmpeg_decoding_chain_initialised = 1;
- pthread_cleanup_pop(0); // successful exit -- don't run the avcodec_open2_cleanup_handler
- pthread_cleanup_pop(0); // successful exit -- don't deallocate the malloc
- pthread_cleanup_pop(
- 0); // successful exit -- don't run the avcodec_alloc_context3_cleanup_handler
+ conn->input_format_is_valid = 1;
}
}
}
@@ -1142,6 +1123,9 @@ int64_t avframe_to_audio(rtsp_conn_info *conn, AVFrame *decoded_frame, uint8_t *
uint8_t *pcm_audio = NULL;
int dst_linesize;
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+
int number_of_output_samples_expected = swr_get_out_samples(conn->swr, decoded_frame->nb_samples);
debug(4, "A maximum of %d output samples expected for %d input samples.",
@@ -1243,10 +1227,12 @@ int64_t avframe_to_audio(rtsp_conn_info *conn, AVFrame *decoded_frame, uint8_t *
*decoded_audio_samples_count = 0;
}
av_freep(&pcm_audio);
- return swr_get_delay(
+ int64_t response = swr_get_delay(
conn->swr,
RATE_FROM_ENCODED_FORMAT(
config.current_output_configuration)); // number of frames left in the resampler
+ pthread_setcancelstate(oldState, NULL);
+ return response;
}
// take a block of incoming data and decode it.
@@ -1254,8 +1240,11 @@ int64_t avframe_to_audio(rtsp_conn_info *conn, AVFrame *decoded_frame, uint8_t *
// transcoded and maybe resampled later
AVFrame *block_to_avframe(rtsp_conn_info *conn, uint8_t *incoming_data,
size_t incoming_data_length) {
+
AVFrame *decoded_frame = NULL;
if (incoming_data_length > 8) {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
AVPacket *pkt = av_packet_alloc();
if (pkt) {
// push a deallocator -- av_packet_free(pkt);
@@ -1314,6 +1303,7 @@ AVFrame *block_to_avframe(rtsp_conn_info *conn, uint8_t *incoming_data,
} else {
debug(1, "Can't allocate an AVPacket!");
}
+ pthread_setcancelstate(oldState, NULL);
}
return decoded_frame;
}
@@ -2032,7 +2022,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
do {
// debug(3, "buffer_get_frame is iterating");
// we must have timing information before we can do anything here
- if (have_timestamp_timing_information(conn)) {
+ if ((have_timestamp_timing_information(conn)) && (conn->input_format_is_valid != 0)) {
int rco = get_requested_connection_state_to_output();
@@ -2481,10 +2471,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
RATE_FROM_ENCODED_FORMAT(config.current_output_configuration)) /
1000000000;
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
- debug(3,
+
+
+ debug(4,
"Exact frame gap: %" PRId64
- ". DAC delay: %ld. First packet timestamp: %u.",
- exact_frame_gap, dac_delay, conn->first_packet_timestamp);
+ ". DAC delay: %ld. Total: %" PRId64 ". First packet timestamp: %u. ",
+ exact_frame_gap, dac_delay, gross_frame_gap, conn->first_packet_timestamp);
// int64_t frames_needed_to_maintain_desired_buffer =
// (int64_t)(config.audio_backend_buffer_desired_length *
// config.current_output_configuration->rate) -
@@ -2687,7 +2679,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
// wait if the buffer is empty
if ((conn->ab_synced != 0) && (conn->ab_read == conn->ab_write)) { // the buffer is empty!
if (notified_buffer_empty == 0) {
- debug(2, "Connection %d: Buffer Empty", conn->connection_number);
+ debug(4, "Connection %d: Buffer Empty", conn->connection_number);
notified_buffer_empty = 1;
// reset_input_flow_metrics(conn); // don't do a full flush parameters reset
// conn->initial_reference_time = 0;
@@ -2701,14 +2693,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
wait = 1; // keep waiting until the timing information becomes available
}
if (wait) {
- if (conn->frames_per_packet == 0)
- debug(1, "frames_per_packet is zero!");
-
- uint64_t time_to_wait_for_wakeup_ns = 20000000; // default if no input rate is set
- if (conn->input_rate != 0) {
- time_to_wait_for_wakeup_ns =
- 1000000000 / conn->input_rate; // this is time period of one frame
- time_to_wait_for_wakeup_ns *= 4 * conn->frames_per_packet;
+ uint64_t time_to_wait_for_wakeup_ns = 10000000; // default
+ if (conn->input_format_is_valid != 0) {
+ time_to_wait_for_wakeup_ns = 1000000000 / conn->input_rate; // this is time period of one frame
+ time_to_wait_for_wakeup_ns *= 4 * conn->frames_per_packet; // about 4 * 7 mS for 352 frames per second
}
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
@@ -2719,12 +2707,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
struct timespec time_of_wakeup;
time_of_wakeup.tv_sec = sec;
time_of_wakeup.tv_nsec = nsec;
-
+ // debug(1, "wait for up to %f mS or for the next packet...", time_to_wait_for_wakeup_ns * 1E-6);
int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex,
&time_of_wakeup); // this is a pthread cancellation point
if ((rc != 0) && (rc != ETIMEDOUT))
// if (rc)
debug(3, "pthread_cond_timedwait returned error code %d.", rc);
+ // debug(1, "waited");
#endif
#ifdef COMPILE_FOR_OSX
uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
@@ -2804,12 +2793,15 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
debug(1, "error %d", ret);
// We need to get those frames of silence out of the resampler
// so we'll pass in an empty AVFrame to flush them through
- AVFrame *avf = av_frame_alloc(); // empty frame
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
+ AVFrame *avf = av_frame_alloc();
conn->frames_retained_in_the_resampler =
avframe_to_audio(conn, avf, &pp, &pl, &number_of_output_frames);
+ av_frame_free(&avf);
+ pthread_setcancelstate(oldState, NULL);
curframe->data = (short *)pp;
curframe->length = number_of_output_frames;
- av_frame_free(&avf);
}
}
#ifndef CONFIG_AIRPLAY_2
@@ -5262,7 +5254,7 @@ int player_stop(rtsp_conn_info *conn) {
// note -- this may be called from another connection thread.
debug(2, "Connection %d: player_stop.", conn->connection_number);
int response = 0; // okay
- pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 1);
+ pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 4);
pthread_t *pt = conn->player_thread;
if (pt) {
debug(3, "player_thread cancel...");
diff --git a/player.h b/player.h
index 2f993c24..e2626a06 100644
--- a/player.h
+++ b/player.h
@@ -257,6 +257,7 @@ typedef struct {
typedef struct {
int connection_number; // for debug ID purposes, nothing else...
int is_playing; // set true by player_play, set false by player_stop
+ int input_format_is_valid; // set when the input format is known and set in this structure
unsigned int sync_samples_index; // for estimating the gap between the highest and lowest timing
// error over the past n samples
unsigned int sync_samples_count; // the array of samples is defined locally
@@ -272,7 +273,7 @@ typedef struct {
// otherwise
int software_mute_enabled; // if we don't have a real mute that we can use
int fd;
- int authorized; // set if a password is required and has been supplied
+ int authorized; // set if a password is required and has been supplied or not required. Also always set in AirPlay 2 mode.
char *auth_nonce; // the session nonce, if needed
stream_cfg stream;
SOCKADDR remote, local;
@@ -412,6 +413,7 @@ typedef struct {
// (will be unspecified if not build for AirPlay 2)
#ifdef CONFIG_AIRPLAY_2
+ plist_t sessionPlist;
char *airplay_gid; // UUID in the Bonjour advertisement -- if NULL, the group UUID is the same as
// the pi UUID
airplay_t airplay_type; // are we using AirPlay 1 or AirPlay 2 protocol on this connection?
@@ -423,6 +425,8 @@ typedef struct {
pthread_t rtp_ap2_control_thread;
pthread_t rtp_realtime_audio_thread;
pthread_t rtp_buffered_audio_thread;
+ pthread_mutex_t event_sender_mutex;
+ int event_channel_fd; // zero if closed, controlled by event_sender_mutex
int ap2_event_receiver_exited;
diff --git a/rtp.c b/rtp.c
index 79032ede..2c8f9915 100644
--- a/rtp.c
+++ b/rtp.c
@@ -1513,9 +1513,12 @@ int frame_to_ptp_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *
int32_t frame_difference = timestamp - anchor_rtptime;
int64_t time_difference = frame_difference;
time_difference = time_difference * 1000000000;
- if (conn->input_rate == 0)
- die("conn->input_rate is zero!");
- time_difference = time_difference / conn->input_rate;
+ if (conn->input_rate == 0) {
+ debug(1,"Connection %d: in a call to frame_to_ptp_local_time, conn->input_rate is zero!", conn->connection_number);
+ time_difference = 0;
+ } else {
+ time_difference = time_difference / conn->input_rate;
+ }
uint64_t ltime = anchor_local_time + time_difference;
*time = ltime;
result = 0;
diff --git a/rtsp.c b/rtsp.c
index 7da2ffaf..0fbee800 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -3,7 +3,7 @@
* Copyright (c) James Laird 2013
* Modifications, including those associated with audio synchronization, multithreading and
- * metadata handling copyright (c) Mike Brady 2014--2025
+ * metadata handling copyright (c) Mike Brady 2014--2026
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -53,6 +53,7 @@
#include "activity_monitor.h"
#include "config.h"
+#include "utilities/rtsp_message_utilities.h"
#ifdef CONFIG_OPENSSL
#include
@@ -74,12 +75,13 @@
#include "rtp.h"
#include "rtsp.h"
-#ifdef CONFIG_METADATA_HUB
-#include "metadata_hub.h"
+#ifdef CONFIG_METADATA
+#include "metadata/pc_queue.h"
+#include "metadata/core.h"
#endif
-#ifdef CONFIG_MQTT
-#include "mqtt.h"
+#ifdef CONFIG_METADATA_HUB
+#include "metadata/hub.h"
#endif
#ifdef AF_INET6
@@ -89,11 +91,10 @@
#endif
#ifdef CONFIG_AIRPLAY_2
+#include
#include "ap2_buffered_audio_processor.h"
#include "ap2_event_receiver.h"
-#include "ap2_rc_event_receiver.h"
#include "pair_ap/pair.h"
-#include "plist/plist.h"
#include "plists/get_info_response.h"
#include "ptp-utilities.h"
@@ -114,8 +115,6 @@
#include "mdns.h"
#include "utilities/network_utilities.h"
-#define METADATA_SNDBUF (4 * 1024 * 1024)
-
enum rtsp_read_request_response {
rtsp_read_request_response_ok,
rtsp_read_request_response_pending,
@@ -129,8 +128,6 @@ enum rtsp_read_request_response {
rtsp_conn_info *principal_conn;
rtsp_conn_info **conns;
-int metadata_running = 0;
-
// always lock this when accessing the principal conn value
// use a read lock when consulting and holding it
// use a write lock if you want to change it
@@ -139,10 +136,6 @@ pthread_rwlock_t principal_conn_lock = PTHREAD_RWLOCK_INITIALIZER;
// always lock this when accessing the list of connection threads
pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER;
-// every time we want to retain or release a reference count, lock it with this
-// if a reference count is read as zero, it means the it's being deallocated.
-static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
-
// only one thread is allowed to use the player at once.
// it monitors the request variable (at least when interrupted)
// static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -151,236 +144,6 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
int RTSP_connection_index = 1;
-#ifdef CONFIG_METADATA
-typedef struct {
- pthread_mutex_t pc_queue_lock;
- pthread_cond_t pc_queue_item_added_signal;
- pthread_cond_t pc_queue_item_removed_signal;
- char *name;
- size_t item_size; // number of bytes in each item
- uint32_t count; // number of items in the queue
- uint32_t capacity; // maximum number of items
- uint32_t toq; // first item to take
- uint32_t eoq; // free space at end of queue
- void *items; // a pointer to where the items are actually stored
-} pc_queue; // producer-consumer queue
-#endif
-
-static int msg_indexes = 1;
-
-typedef struct {
- int index_number;
- uint32_t referenceCount; // we might start using this...
- unsigned int nheaders;
- char *name[16];
- char *value[16];
-
- uint32_t contentlength;
- char *content;
-
- // for requests
- char method[16];
- char path[256];
-
- // for responses
- int respcode;
-} rtsp_message;
-
-#ifdef CONFIG_AIRPLAY_2
-
-int add_pstring_to_malloc(const char *s, void **allocation, size_t *size) {
- int response = 0;
- void *p = *allocation;
- if (p == NULL) {
- p = malloc(strlen(s) + 1);
- if (p == NULL) {
- debug(1, "error allocating memory");
- } else {
- *allocation = p;
- *size = *size + strlen(s) + 1;
- uint8_t *b = (uint8_t *)p;
- *b = strlen(s);
- p = p + 1;
- memcpy(p, s, strlen(s));
- response = 1;
- }
- } else {
- p = realloc(p, *size + strlen(s) + 1);
- if (p == NULL) { // assuming we never allocate a zero byte space
- debug(1, "error reallocating memory");
- } else {
- *allocation = p;
- uint8_t *b = (uint8_t *)p + *size;
- *b = strlen(s);
- p = p + *size + 1;
- memcpy(p, s, strlen(s));
- *size = *size + strlen(s) + 1;
- response = 1;
- }
- }
- return response;
-}
-
-#endif
-
-#ifdef CONFIG_METADATA
-typedef struct {
- uint32_t type;
- uint32_t code;
- char *data;
- uint32_t length;
- rtsp_message *carrier;
-} metadata_package;
-
-void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
- const char *name) {
- if (name)
- debug(4, "Creating metadata queue \"%s\".", name);
- else
- debug(1, "Creating an unnamed metadata queue.");
- pthread_mutex_init(&the_queue->pc_queue_lock, NULL);
- pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL);
- pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL);
- the_queue->item_size = item_size;
- the_queue->items = items;
- the_queue->count = 0;
- the_queue->capacity = number_of_items;
- the_queue->toq = 0;
- the_queue->eoq = 0;
- if (name == NULL)
- the_queue->name = NULL;
- else
- the_queue->name = strdup(name);
-}
-
-void pc_queue_delete(pc_queue *the_queue) {
- if (the_queue->name)
- debug(2, "Deleting metadata queue \"%s\".", the_queue->name);
- else
- debug(1, "Deleting an unnamed metadata queue.");
- if (the_queue->name != NULL)
- free(the_queue->name);
- // debug(2, "destroying pc_queue_item_removed_signal");
- pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal);
- // debug(2, "destroying pc_queue_item_added_signal");
- pthread_cond_destroy(&the_queue->pc_queue_item_added_signal);
- // debug(2, "destroying pc_queue_lock");
- pthread_mutex_destroy(&the_queue->pc_queue_lock);
- // debug(2, "destroying signals and locks done");
-}
-
-int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length,
- rtsp_message *carrier, int block);
-
-int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length,
- const int block) {
- return send_metadata('ssnc', code, data, length, NULL, block);
-}
-
-void pc_queue_cleanup_handler(void *arg) {
- // debug(1, "pc_queue_cleanup_handler called.");
- pc_queue *the_queue = (pc_queue *)arg;
- int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock);
- if (rc)
- debug(1, "Error unlocking for pc_queue_add_item or pc_queue_get_item.");
-}
-
-int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
- int response = 0;
- int rc;
- if (the_queue) {
- if (block == 0) {
- rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 4);
- if (rc == EBUSY)
- return EBUSY;
- } else
- rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4);
- if (rc)
- debug(1, "Error %d (\"%s\") locking for pc_queue_add_item. Block is %d.", rc, strerror(rc),
- block);
- pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
- // leave this out if you want this to return if the queue is already full
- // irrespective of the block flag.
- /*
- while (the_queue->count == the_queue->capacity) {
- rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal,
- &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed");
- }
- */
- if (the_queue->count < the_queue->capacity) {
- uint32_t i = the_queue->eoq;
- void *p = the_queue->items + the_queue->item_size * i;
- // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
- memcpy(p, the_stuff, the_queue->item_size);
-
- // update the pointer
- i++;
- if (i == the_queue->capacity)
- // fold pointer if necessary
- i = 0;
- the_queue->eoq = i;
- the_queue->count++;
- // debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count,
- // the_queue->capacity);
- if (the_queue->count == the_queue->capacity)
- debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name,
- the_queue->count);
- rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
- if (rc)
- debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item",
- the_queue->name);
- } else {
- response = EWOULDBLOCK; // a bit arbitrary, this.
- debug(3,
- "metadata queue \"%s\": is already full with %d items in it. Not adding this item to "
- "the queue.",
- the_queue->name, the_queue->count);
- }
- pthread_cleanup_pop(1); // unlock the queue lock.
- } else {
- debug(1, "Adding an item to a NULL queue");
- }
- return response;
-}
-
-int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
- int rc;
- if (the_queue) {
- rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4);
- if (rc)
- debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name);
- pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
- while (the_queue->count == 0) {
- rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock);
- if (rc)
- debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name);
- }
- uint32_t i = the_queue->toq;
- // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq;
- void *p = the_queue->items + the_queue->item_size * i;
- memcpy(the_stuff, p, the_queue->item_size);
-
- // update the pointer
- i++;
- if (i == the_queue->capacity)
- // fold pointer if necessary
- i = 0;
- the_queue->toq = i;
- the_queue->count--;
- debug(4, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count,
- the_queue->capacity);
- rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal);
- if (rc)
- debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name);
- pthread_cleanup_pop(1); // unlock the queue lock.
- } else {
- debug(1, "Removing an item from a NULL queue");
- }
- return 0;
-}
-
-#endif
-
// The principal_conn variable points to the connection that
// controls the mDNS status and flags and that is potentially
// in control of the playing subsystem to output audio to a backend
@@ -469,12 +232,13 @@ play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption)
principal_conn->client_ip_string, principal_conn->client_rtsp_port,
principal_conn->self_ip_string, principal_conn->self_rtsp_port);
close(principal_conn->fd);
- // principal_conn->fd = 0;
+ principal_conn->fd = 0;
+ usleep(1000000);
}
rtsp_conn_info *previous_principal_conn = principal_conn;
principal_conn = conn; // make the conn the new principal_conn
pthread_cancel(previous_principal_conn->thread); // cancel the previous one...
-
+ usleep(1000000);
if (principal_conn == NULL) {
#ifdef CONFIG_AIRPLAY_2
config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay
@@ -509,7 +273,7 @@ play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption)
// keep track of the threads we have spawned so we can join() them
static int nconns = 0;
static void track_thread(rtsp_conn_info *conn) {
- debug_mutex_lock(&conns_lock, 1000000, 3);
+ debug_mutex_lock(&conns_lock, 1000000, 4);
// look for an empty slot first
int i = 0;
int found = 0;
@@ -629,270 +393,6 @@ static char *nextline(char *in, int inbuf) {
return out;
}
-void msg_retain(rtsp_message *msg) {
- int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
- if (rc)
- debug(1, "Error %d locking reference counter lock", rc);
- if (msg > (rtsp_message *)0x00010000) {
- msg->referenceCount++;
- debug(4, "msg_free increment reference counter message %d to %d.", msg->index_number,
- msg->referenceCount);
- // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
- rc = pthread_mutex_unlock(&reference_counter_lock);
- if (rc)
- debug(1, "Error %d unlocking reference counter lock", rc);
- } else {
- debug(1, "invalid rtsp_message pointer 0x%" PRIxPTR " passed to retain", (uintptr_t)msg);
- }
-}
-
-rtsp_message *msg_init(void) {
- // no thread cancellation points here
- int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
- if (rc)
- debug(1, "Error %d locking reference counter lock", rc);
-
- rtsp_message *msg = malloc(sizeof(rtsp_message));
- if (msg) {
- memset(msg, 0, sizeof(rtsp_message));
- msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
- msg->index_number = msg_indexes++;
- debug(4, "msg_init message %d", msg->index_number);
- } else {
- die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
- }
- // debug(1,"msg_init -- create item %d.", msg->index_number);
-
- rc = pthread_mutex_unlock(&reference_counter_lock);
- if (rc)
- debug(1, "Error %d unlocking reference counter lock", rc);
-
- return msg;
-}
-
-int msg_add_header(rtsp_message *msg, char *name, char *value) {
- if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) {
- warn("too many headers?!");
- return 1;
- }
-
- msg->name[msg->nheaders] = strdup(name);
- msg->value[msg->nheaders] = strdup(value);
- msg->nheaders++;
-
- return 0;
-}
-
-char *msg_get_header(rtsp_message *msg, char *name) {
- unsigned int i;
- for (i = 0; i < msg->nheaders; i++)
- if (!strcasecmp(msg->name[i], name))
- return msg->value[i];
- return NULL;
-}
-
-void _debug_print_msg_headers(const char *filename, const int linenumber, int level,
- rtsp_message *msg) {
- unsigned int i;
- if (msg->respcode != 0)
- _debug(filename, linenumber, level, " Response Code: %d.", msg->respcode);
- for (i = 0; i < msg->nheaders; i++) {
- _debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i],
- msg->value[i]);
- }
-}
-
-/*
-static void debug_print_msg_content(int level, rtsp_message *msg) {
- if (msg->contentlength) {
- char *obf = malloc(msg->contentlength * 2 + 1);
- if (obf) {
- char *obfp = obf;
- int obfc;
- for (obfc = 0; obfc < msg->contentlength; obfc++) {
- snprintf(obfp, 3, "%02X", msg->content[obfc]);
- obfp += 2;
- };
- *obfp = 0;
- debug(level, "Content (hex): \"%s\"", obf);
- free(obf);
- } else {
- debug(level, "Can't allocate space for debug buffer");
- }
- } else {
- debug(level, "No content");
- }
-}
-*/
-
-void msg_free(rtsp_message **msgh) {
- debug_mutex_lock(&reference_counter_lock, 1000, 0);
- if (*msgh > (rtsp_message *)0x00010000) {
- rtsp_message *msg = *msgh;
- msg->referenceCount--;
- if (msg->referenceCount)
- debug(4, "msg_free decrement reference counter message %d to %d", msg->index_number,
- msg->referenceCount);
- if (msg->referenceCount == 0) {
- unsigned int i;
- for (i = 0; i < msg->nheaders; i++) {
- free(msg->name[i]);
- free(msg->value[i]);
- }
- if (msg->content)
- free(msg->content);
- // debug(1,"msg_free item %d -- free.",msg->index_number);
- uintptr_t index = (msg->index_number) & 0xFFFF;
- if (index == 0)
- index = 0x10000; // ensure it doesn't fold to zero.
- *msgh =
- (rtsp_message *)(index); // put a version of the index number of the freed message in here
- debug(4, "msg_free freed message %d", msg->index_number);
- free(msg);
- } else {
- // debug(1,"msg_free item %d -- decrement reference to
- // %d.",msg->index_number,msg->referenceCount);
- }
- } else if (*msgh != NULL) {
- debug(1,
- "msg_free: error attempting to free an allocated but already-freed rtsp_message, number "
- "%" PRIxPTR ".",
- (uintptr_t)*msgh);
- }
- debug_mutex_unlock(&reference_counter_lock, 0);
-}
-
-int msg_handle_line(rtsp_message **pmsg, char *line) {
- rtsp_message *msg = *pmsg;
-
- if (!msg) {
- msg = msg_init();
- *pmsg = msg;
- char *sp, *p;
- sp = NULL; // this is to quieten a compiler warning
-
- debug(4, "RTSP/HTTP Message Received: \"%s\".", line);
-
- p = strtok_r(line, " ", &sp);
- if (!p)
- goto fail;
- strncpy(msg->method, p, sizeof(msg->method) - 1);
-
- p = strtok_r(NULL, " ", &sp);
- if (!p)
- goto fail;
- strncpy(msg->path, p, sizeof(msg->path) - 1);
-
- p = strtok_r(NULL, " ", &sp);
- if (!p)
- goto fail;
- if ((strcmp(p, "RTSP/1.0") != 0) && (strcmp(p, "HTTP/1.1") != 0)) {
- debug(1, "Problem with Message: \"%s\"", p);
- goto fail;
- }
-
- return -1;
- }
-
- if (strlen(line)) {
- char *p;
- p = strstr(line, ": ");
- if (!p) {
- warn("bad header: >>%s<<", line);
- goto fail;
- }
- *p = 0;
- p += 2;
- msg_add_header(msg, line, p);
- debug(4, " %s: %s.", line, p);
- return -1;
- } else {
- char *cl = msg_get_header(msg, "Content-Length");
- if (cl)
- return atoi(cl);
- else
- return 0;
- }
-
-fail:
- debug(1, "msg_handle_line fail");
- msg_free(pmsg);
- *pmsg = NULL;
- return 0;
-}
-
-#ifdef CONFIG_AIRPLAY_2
-
-int rtsp_message_contains_plist(rtsp_message *message) {
- int reply = 0; // assume there is no plist in the message
- if ((message->contentlength >= strlen("bplist00")) &&
- (strstr(message->content, "bplist00") == message->content))
- reply = 1;
- return reply;
-}
-
-plist_t plist_from_rtsp_content(rtsp_message *message) {
- plist_t the_plist = NULL;
- if (rtsp_message_contains_plist(message)) {
- plist_from_memory(message->content, message->contentlength, &the_plist);
- }
- return the_plist;
-}
-
-char *plist_as_xml_text(plist_t the_plist) {
- // caller must free the returned character buffer
- // convert it to xml format
- uint32_t size;
- char *plist_out = NULL;
- plist_to_xml(the_plist, &plist_out, &size);
-
- // put it into a NUL-terminated string
- char *reply = malloc(size + 1);
- if (reply) {
- memcpy(reply, plist_out, size);
- reply[size] = '\0';
- }
- if (plist_out)
- free(plist_out);
- return reply;
-}
-
-// caller must free the returned character buffer
-char *rtsp_plist_content(rtsp_message *message) {
- char *reply = NULL;
- // first, check if it has binary plist content
- if (rtsp_message_contains_plist(message))
- reply = plist_as_xml_text(plist_from_rtsp_content(message));
- return reply;
-}
-
-#endif
-
-void _debug_log_rtsp_message(const char *filename, const int linenumber, int level, char *prompt,
- rtsp_message *message) {
- if (level > debug_level())
- return;
- if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list...
- _debug(filename, linenumber, level, "%s", prompt);
- _debug_print_msg_headers(filename, linenumber, level, message);
-#ifdef CONFIG_AIRPLAY_2
- char *plist_content = rtsp_plist_content(message);
- if (plist_content) {
- _debug(filename, linenumber, level, " Content Plist (as XML):\n--\n%s--", plist_content);
- free(plist_content);
- } else
-#endif
- {
- _debug(filename, linenumber, level, " No Content Plist. Content length: %d.",
- message->contentlength);
- }
-}
-
-#define debug_log_rtsp_message(level, prompt, message) \
- _debug_log_rtsp_message(__FILE__, __LINE__, level, prompt, message)
-#define debug_print_msg_headers(level, message) \
- _debug_print_msg_headers(__FILE__, __LINE__, level, message)
-
#ifdef CONFIG_AIRPLAY_2
static void buf_add(sized_buffer *buf, uint8_t *in, size_t in_len) {
if (buf->length + in_len > buf->size) {
@@ -1343,9 +843,9 @@ int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) {
#ifdef CONFIG_AIRPLAY_2
void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req,
rtsp_message *resp) {
- debug(2, "Connection %d: RECORD on %s", conn->connection_number,
+ debug(2, "Connection %d: RECORD (AP2) on %s", conn->connection_number,
get_category_string(conn->airplay_stream_category));
- debug_log_rtsp_message(3, "RECORD incoming message", req);
+ debug_log_rtsp_message_conn(conn, 2, "RECORD (AP2) incoming message", req);
msg_add_header(resp, "Audio-Latency", "0");
resp->respcode = 200;
}
@@ -1400,6 +900,39 @@ void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
#ifdef CONFIG_AIRPLAY_2
+int add_pstring_to_malloc(const char *s, void **allocation, size_t *size) {
+ int response = 0;
+ void *p = *allocation;
+ if (p == NULL) {
+ p = malloc(strlen(s) + 1);
+ if (p == NULL) {
+ debug(1, "error allocating memory");
+ } else {
+ *allocation = p;
+ *size = *size + strlen(s) + 1;
+ uint8_t *b = (uint8_t *)p;
+ *b = strlen(s);
+ p = p + 1;
+ memcpy(p, s, strlen(s));
+ response = 1;
+ }
+ } else {
+ p = realloc(p, *size + strlen(s) + 1);
+ if (p == NULL) { // assuming we never allocate a zero byte space
+ debug(1, "error reallocating memory");
+ } else {
+ *allocation = p;
+ uint8_t *b = (uint8_t *)p + *size;
+ *b = strlen(s);
+ p = p + *size + 1;
+ memcpy(p, s, strlen(s));
+ *size = *size + strlen(s) + 1;
+ response = 1;
+ }
+ }
+ return response;
+}
+
void generateTxtDataValueInfo(rtsp_conn_info *conn, void **response, size_t *responseLength) {
void *qualifier_response_data = NULL;
size_t qualifier_response_data_length = 0;
@@ -1419,11 +952,20 @@ void generateTxtDataValueInfo(rtsp_conn_info *conn, void **response, size_t *res
&qualifier_response_data, &qualifier_response_data_length) == 0)
debug(1, "Problem");
+/*
+ uint64_t features_hi = 0x0001C340445D0A00L;
+ features_hi = (features_hi >> 32) & 0xffffffff;
+ uint64_t features_lo = 0x0001C340445D0A00L;
+ features_lo = features_lo & 0xffffffff;
+*/
+
+
uint64_t features_hi = config.airplay_features;
features_hi = (features_hi >> 32) & 0xffffffff;
uint64_t features_lo = config.airplay_features;
features_lo = features_lo & 0xffffffff;
+
if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString),
"features=0x%" PRIX64 ",0x%" PRIX64 "", features_lo,
features_hi),
@@ -1453,7 +995,7 @@ void generateTxtDataValueInfo(rtsp_conn_info *conn, void **response, size_t *res
gcgl = conn->groupContainsGroupLeader;
// should have igl here;
- if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "igl=%0"),
+ if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "igl=%d", 0),
&qualifier_response_data, &qualifier_response_data_length) == 0)
debug(1, "Problem");
@@ -1519,6 +1061,8 @@ plist_t generateInfoPlist(rtsp_conn_info *conn) {
// debug(1,"qualifier_response_data_length: %u.", qualifier_response_data_length);
+ plist_dict_set_item(response_plist, "psi", plist_new_string(config.airplay_psi));
+
plist_dict_set_item(response_plist, "featuresEx", plist_new_string(config.airplay_fex));
plist_dict_set_item(response_plist, "features", plist_new_uint(config.airplay_features));
@@ -1526,9 +1070,7 @@ plist_t generateInfoPlist(rtsp_conn_info *conn) {
plist_dict_set_item(response_plist, "deviceID", plist_new_string(config.airplay_device_id));
plist_dict_set_item(response_plist, "pi", plist_new_string(config.airplay_pi));
plist_dict_set_item(response_plist, "name", plist_new_string(config.service_name));
- char *vs = get_version_string();
plist_dict_set_item(response_plist, "model", plist_new_string(config.model));
- free(vs);
plist_dict_set_item(response_plist, "pk",
plist_new_data((const char *)config.airplay_pk, sizeof(config.airplay_pk)));
char senderAddress[256];
@@ -1561,7 +1103,7 @@ plist_t generateInfoPlist(rtsp_conn_info *conn) {
}
plist_dict_set_item(supported_formats_plist, "bufferStream",
plist_new_uint(bufferStreamFormats));
- debug(3, "bufferedStream formats: 0x%" PRIX64 ".", bufferStreamFormats);
+ debug(4, "bufferedStream formats: 0x%" PRIX64 ".", bufferStreamFormats);
plist_dict_set_item(response_plist, "supportedFormats", supported_formats_plist);
}
}
@@ -1967,7 +1509,7 @@ static void pairing_remove(struct pairings *pairing) {
static int pairing_add_cb(uint8_t public_key[32], const char *device_id,
void *cb_arg __attribute__((unused))) {
- debug(2, "pair-add cb for %s", device_id);
+ debug(1, "pair-add cb for %s", device_id);
struct pairings *pairing = pairing_find(device_id);
if (pairing) {
@@ -1981,7 +1523,7 @@ static int pairing_add_cb(uint8_t public_key[32], const char *device_id,
static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id,
void *cb_arg __attribute__((unused))) {
- debug(2, "pair-remove cb for %s", device_id);
+ debug(1, "pair-remove cb for %s", device_id);
struct pairings *pairing = pairing_find(device_id);
if (!pairing) {
@@ -1995,7 +1537,7 @@ static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), con
static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg,
void *cb_arg __attribute__((unused))) {
- debug(2, "pair-list cb");
+ debug(1, "pair-list cb");
for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) {
enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg);
@@ -2004,7 +1546,15 @@ static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg,
void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
rtsp_message *resp) {
- debug(2, "Connection %d: handle_pair_add", conn->connection_number);
+
+ char *hdr = msg_get_header(req, "X-Apple-Client-Name");
+ if (hdr) {
+ if (conn->ap2_client_name)
+ free(conn->ap2_client_name);
+ conn->ap2_client_name = strdup(hdr);
+ }
+ debug(1, "Connection %d from \"%s\": handle_pair_add", conn->connection_number, conn->ap2_client_name);
+ debug_log_rtsp_message_conn(conn, 1, "pair-add request", req);
uint8_t *body = NULL;
size_t body_len = 0;
int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL,
@@ -2017,12 +1567,18 @@ void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
msg_add_header(resp, "Content-Type", "application/octet-stream");
- debug_log_rtsp_message(2, "pair-add response", resp);
+ debug_log_rtsp_message_conn(conn, 1, "pair-add response", resp);
}
void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
rtsp_message *resp) {
- debug(2, "Connection %d: handle_pair_list", conn->connection_number);
+ char *hdr = msg_get_header(req, "X-Apple-Client-Name");
+ if (hdr) {
+ if (conn->ap2_client_name)
+ free(conn->ap2_client_name);
+ conn->ap2_client_name = strdup(hdr);
+ }
+ debug(1, "Connection %d from \"%s\": handle_pair_list", conn->connection_number, conn->ap2_client_name);
uint8_t *body = NULL;
size_t body_len = 0;
int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL,
@@ -2035,12 +1591,19 @@ void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
msg_add_header(resp, "Content-Type", "application/octet-stream");
- debug_log_rtsp_message(2, "pair-list response", resp);
+ debug_log_rtsp_message_conn(conn, 1, "pair-list response", resp);
}
void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req,
rtsp_message *resp) {
- debug(3, "Connection %d: handle_pair_remove", conn->connection_number);
+
+ char *hdr = msg_get_header(req, "X-Apple-Client-Name");
+ if (hdr) {
+ if (conn->ap2_client_name)
+ free(conn->ap2_client_name);
+ conn->ap2_client_name = strdup(hdr);
+ }
+ debug(1, "Connection %d from \"%s\": handle_pair_remove", conn->connection_number, conn->ap2_client_name);
uint8_t *body = NULL;
size_t body_len = 0;
int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL,
@@ -2053,12 +1616,25 @@ void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_messa
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
msg_add_header(resp, "Content-Type", "application/octet-stream");
- debug_log_rtsp_message(3, "pair-remove response", resp);
+ debug_log_rtsp_message_conn(conn, 1, "pair-remove response", resp);
}
void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
- debug(3, "Connection %d: handle_pair_verify Content-Length %d", conn->connection_number,
- req->contentlength);
+ char *hdr = msg_get_header(req, "X-Apple-Client-Name");
+ if (hdr) {
+ if (conn->ap2_client_name)
+ free(conn->ap2_client_name);
+ conn->ap2_client_name = strdup(hdr);
+ }
+ // try to pick up the stages
+ uint8_t *b = (uint8_t *)req->content;
+ char mstage = '-';
+ if ((req->contentlength >= 3) && (b[0] == 6) && (b[1] == 1) && (b[1] <= 9)) {
+ mstage = '0' + b[2];
+ }
+
+ debug(1, "Connection %d from \"%s\": handle_pair_verify, stage M%c, Content-Length %d", conn->connection_number, conn->ap2_client_name, mstage, req->contentlength);
+ debug_log_rtsp_message_conn(conn, 2, "pair-verify request", req);
int ret;
uint8_t *body = NULL;
size_t body_len = 0;
@@ -2105,24 +1681,45 @@ out:
resp->contentlength = body_len;
if (body)
msg_add_header(resp, "Content-Type", "application/octet-stream");
- debug_log_rtsp_message(3, "pair-verify response", resp);
+ debug_log_rtsp_message_conn(conn, 2, "pair-verify response", resp);
}
void handle_pair_pin_start(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+
+ char *hdr = msg_get_header(req, "X-Apple-Client-Name");
+ if (hdr) {
+ if (conn->ap2_client_name)
+ free(conn->ap2_client_name);
+ conn->ap2_client_name = strdup(hdr);
+ }
+ debug(1, "Connection %d from \"%s\": handle_pair_pin_start, Content-Length %d", conn->connection_number,
+ conn->ap2_client_name,
+ req->contentlength);
+ debug_log_rtsp_message_conn(conn, 1, "handle_pair_pin_start", req);
+
uint8_t *body = NULL;
size_t body_len = 0;
- debug(2, "Connection %d: pair-pin-start Content-Length %d", conn->connection_number,
- req->contentlength);
- debug_log_rtsp_message(2, "handle_pair_pin_start", req);
resp->content = (char *)body; // these will be freed when the data is sent
resp->contentlength = body_len;
if (body != NULL)
msg_add_header(resp, "Content-Type", "application/octet-stream");
- debug_log_rtsp_message(2, "pair-pin-start response", resp);
+ debug_log_rtsp_message(1, "pair-pin-start response", resp);
}
void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
+
+ char *hdr = msg_get_header(req, "X-Apple-Client-Name");
+ if (hdr) {
+ if (conn->ap2_client_name)
+ free(conn->ap2_client_name);
+ conn->ap2_client_name = strdup(hdr);
+ }
+ debug(2, "Connection %d from \"%s\": handle_pair_setup, Content-Length %d", conn->connection_number,
+ conn->ap2_client_name,
+ req->contentlength);
+ debug_log_rtsp_message_conn(conn, 2, "pair-setup request", req);
+
int ret;
uint8_t *body = NULL;
size_t body_len = 0;
@@ -2131,7 +1728,7 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re
debug_log_rtsp_message(3, "handle_pair_setup", req);
if (!conn->ap2_pairing_context.setup_ctx) {
- conn->ap2_pairing_context.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.airplay_pin,
+ conn->ap2_pairing_context.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.password,
NULL, NULL, config.airplay_device_id);
if (!conn->ap2_pairing_context.setup_ctx) {
debug(1, "Error creating setup context");
@@ -2154,19 +1751,18 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re
// don't activate yet, the response to this request is still plaintext
conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx =
pair_cipher_new(PAIR_SERVER_HOMEKIT, 3, conn->pair_setup_result->shared_secret,
- conn->pair_setup_result->shared_secret_len,
- ""); // last argument is the (possible) dynamic salt suffix
+ conn->pair_setup_result->shared_secret_len, ""); // last argument is the (possible) dynamic salt suffix
if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) {
- debug(1, "Error setting up rtsp control channel ciphering\n");
+ debug(1, "Error setting up rtsp control channel ciphering");
goto out;
}
conn->ap2_pairing_context.control_cipher_bundle.description = strdup("Control Stream");
+
conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx =
pair_cipher_new(PAIR_SERVER_HOMEKIT, 4, conn->pair_setup_result->shared_secret,
- conn->pair_setup_result->shared_secret_len,
- ""); // last argument is the (possible) dynamic salt suffix
+ conn->pair_setup_result->shared_secret_len, ""); // last argument is the (possible) dynamic salt suffix
if (!conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx) {
- debug(1, "Error setting up rtsp control channel ciphering\n");
+ debug(1, "Error setting up rtsp event channel ciphering");
goto out;
}
conn->ap2_pairing_context.event_cipher_bundle.description = strdup("Event Stream");
@@ -2177,12 +1773,23 @@ out:
resp->contentlength = body_len;
if (body)
msg_add_header(resp, "Content-Type", "application/octet-stream");
- debug_log_rtsp_message(3, "pair-setup response", resp);
+ debug_log_rtsp_message_conn(conn, 2, "pair-setup response", resp);
}
void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp) {
+ char *hdr = msg_get_header(req, "X-Apple-Client-Name");
+ if (hdr) {
+ if (conn->ap2_client_name)
+ free(conn->ap2_client_name);
+ conn->ap2_client_name = strdup(hdr);
+ }
+ debug(2, "Connection %d from \"%s\": handle_fp_setup,", conn->connection_number,
+ conn->ap2_client_name);
+ debug_log_rtsp_message_conn(conn, 2, "fp-setup request", req);
+
+
/* Fairplay magic */
static uint8_t server_fp_reply1[] =
"\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x00\x0f\x9f\x3f\x9e\x0a"
@@ -2298,24 +1905,63 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message
*/
void handle_configure(rtsp_conn_info *conn __attribute__((unused)),
rtsp_message *req __attribute__((unused)), rtsp_message *resp) {
- uint8_t public_key[32];
-
- pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, config.airplay_device_id);
+ debug_log_rtsp_message_conn(conn, 1, "POST /configure req:", req);
+
+ int existingEnable_HK_Access_Control = config.enable_HK_Access_Control;
+
plist_t response_plist = plist_new_dict();
- plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi));
- plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1));
- plist_dict_set_item(response_plist, "PublicKey",
- plist_new_data((const char *)public_key, sizeof(public_key)));
- plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name));
- plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0));
-
+ // look for a configuration dictionary
+ plist_t messagePlist = plist_from_rtsp_content(req);
+ if (messagePlist != NULL) {
+ // look for the keyed dict "ConfigurationDictionary"
+ plist_t configurationDict = plist_dict_get_item(messagePlist, "ConfigurationDictionary");
+ if (configurationDict != NULL) {
+ uint8_t enable_HK_Access_Control = 0;
+ plist_t enableItem = plist_dict_get_item(configurationDict,"Enable_HK_Access_Control");
+ if (enableItem != NULL) {
+ plist_get_bool_val(enableItem, &enable_HK_Access_Control);
+
+ if (enable_HK_Access_Control != 0) {
+ config.enable_HK_Access_Control = 1;
+ plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi));
+ plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1));
+ plist_dict_set_item(response_plist, "PublicKey",
+ plist_new_data((const char *)config.airplay_pk, sizeof(config.airplay_pk)));
+ plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name));
+ plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0));
+ } else {
+ config.enable_HK_Access_Control = 0;
+ // leave the response dict empty
+ }
+ } else {
+ debug(1, "no Enable_HK_Access_Control item in POST /configure ConfigurationDictionary");
+ }
+ debug(1, "enable_HK_Access_Control is %u.", enable_HK_Access_Control);
+ } else {
+ debug(1, "no ConfigurationDictionary in POST /configure plist");
+ }
+ plist_free(messagePlist);
+ } else {
+ debug(1, "no plist in POST /configure request");
+ }
+
+ if (config.enable_HK_Access_Control != 0) {
+ config.airplay_statusflags |= (1 << 10); // DeviceWasSetupForHKAccessControl
+ } else {
+ config.airplay_statusflags &= (0xffffffff - (1 << 10));
+ }
+
+ if (config.enable_HK_Access_Control != existingEnable_HK_Access_Control) {
+ build_bonjour_strings(principal_conn);
+ mdns_update(NULL, secondary_txt_records);
+ }
plist_to_bin(response_plist, &resp->content, &resp->contentlength);
plist_free(response_plist);
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
- debug_log_rtsp_message(2, "POST /configure response:", resp);
+ debug_log_rtsp_message_conn(conn, 1, "POST /configure response:", resp);
}
void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
@@ -2325,6 +1971,7 @@ void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message
debug_log_rtsp_message(4, NULL, req);
int is_playing = 0;
+ int connection_number = 0;
int type = 0;
double rate = 0.0;
@@ -2334,6 +1981,7 @@ void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message
pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
if ((principal_conn != NULL) && (principal_conn->is_playing != 0)) {
is_playing = 1;
+ connection_number = principal_conn->connection_number;
type = principal_conn->type;
rate = 1.0 * principal_conn->input_rate;
}
@@ -2343,9 +1991,9 @@ void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message
if (is_playing != 0) {
if ((type != 96) && (type != 103))
- debug(1, "Connection %d, feedback unexpected type: %u.", conn->connection_number, type);
+ debug(1, "Connection %d, feedback unexpected type: %u.", connection_number, type);
if ((rate != 44100.0) && (rate != 48000.0))
- debug(2, "Connection %d, feedback unexpected rate: %f.", conn->connection_number, rate);
+ debug(2, "Connection %d, feedback unexpected rate: %f.", connection_number, rate);
plist_t payload_plist = plist_new_dict();
plist_dict_set_item(payload_plist, "type", plist_new_uint(type));
plist_dict_set_item(payload_plist, "sr", plist_new_real(rate));
@@ -2372,6 +2020,11 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req,
req->contentlength);
debug_log_rtsp_message(3, NULL, req);
if (rtsp_message_contains_plist(req)) {
+ // we are not going to load the plist here because we don't wamt
+ // to incur the memory and processing cost. So we'll just send it to the
+ // metadata handling code and it can be dealt with there.
+ send_metadata('ssnc', 'copl', req->content, req->contentlength, req, 1); // COmmand PList (and release 'req' afterwards)
+ /*
plist_t command_dict = NULL;
plist_from_memory(req->content, req->contentlength, &command_dict);
if (command_dict != NULL) {
@@ -2380,6 +2033,9 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req,
if (item != NULL) {
char *typeValue = NULL;
plist_get_string_val(item, &typeValue);
+ debug(1, "Connection %d: POST /command plist type \"%s\" received.",
+ conn->connection_number, typeValue);
+ debug_log_rtsp_message(1, NULL, req);
if ((typeValue != NULL) && (strcmp(typeValue, "updateMRSupportedCommands") == 0)) {
item = plist_dict_get_item(command_dict, "params");
if (item != NULL) {
@@ -2404,7 +2060,7 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req,
if (subsidiary_plist) {
char *printable_plist = plist_as_xml_text(subsidiary_plist);
if (printable_plist) {
- debug(3, "Connection %d:\n==\n%s\n==", conn->connection_number, printable_plist);
+ debug(4, "Connection %d:\n==\n%s\n==", conn->connection_number, printable_plist);
free(printable_plist);
} else {
debug(1, "Can't print the plist!");
@@ -2426,10 +2082,6 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req,
debug(1, "Connection %d: POST /command no params dict.", conn->connection_number);
}
resp->respcode = 200;
- } else {
- debug(1, "Connection %d: POST /command plist type \"%s\" received.",
- conn->connection_number, typeValue);
- debug_log_rtsp_message(2, NULL, req);
}
if (typeValue != NULL)
free(typeValue);
@@ -2441,6 +2093,7 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req,
} else {
debug(1, "Connection %d: POST /command plist cannot be inputted.", conn->connection_number);
}
+ */
} else {
debug(1, "Connection %d: POST /command contains no plist", conn->connection_number);
}
@@ -2564,7 +2217,7 @@ void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message
#ifdef CONFIG_AIRPLAY_2
void handle_options_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
- debug_log_rtsp_message(2, "OPTIONS request", req);
+ debug_log_rtsp_message_conn(conn, 1, "OPTIONS request", req);
debug(3, "Connection %d: OPTIONS", conn->connection_number);
resp->respcode = 200;
msg_add_header(resp, "Public",
@@ -2578,10 +2231,11 @@ void handle_options_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message
void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
rtsp_message *resp) {
- debug(2, "Connection %d: TEARDOWN 2 %s.", conn->connection_number,
+ debug(2, "Connection %d from \"%s\": TEARDOWN 2 %s.", conn->connection_number,
+ conn->ap2_client_name,
get_category_string(conn->airplay_stream_category));
debug_log_rtsp_message(2, "TEARDOWN 2: ", req);
-
+
if (conn->player_thread) {
debug(2, "TEARDOWN 2 is stopping a player thread before exiting...");
player_stop(conn); // this nulls the player_thread and cancels the threads...
@@ -2650,23 +2304,24 @@ static void check_and_send_plist_metadata(plist_t messagePlist, const char *plis
void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
int err;
- debug(2, "Connection %d: SETUP (AirPlay 2) on %s", conn->connection_number,
- get_category_string(conn->airplay_stream_category));
- debug_log_rtsp_message(2, "SETUP (AirPlay 2) incoming message", req);
plist_t messagePlist = plist_from_rtsp_content(req);
+
+ if (messagePlist != NULL) {
+ // if (conn->sessionPlist)
+ // plist_free(conn->sessionPlist);
+ conn->sessionPlist = messagePlist;
+ }
plist_t setupResponsePlist = plist_new_dict();
resp->respcode = 501;
// see if we can get a name for the client
- char *clientNameString = NULL;
plist_t nameItem = plist_dict_get_item(messagePlist, "name");
if (nameItem != NULL) {
- plist_get_string_val(nameItem, &conn->ap2_client_name); // generates a malloced string
- clientNameString = strdup(conn->ap2_client_name);
- } else {
- clientNameString = strdup("");
+ if (conn->ap2_client_name)
+ free(conn->ap2_client_name);
+ plist_get_string_val(nameItem, &conn->ap2_client_name); // we need to free it!
}
// see if the incoming plist contains a "streams" array
@@ -2688,7 +2343,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
if (strcmp(timingProtocolString, "PTP") == 0) {
debug(2, "Connection %d: AP2 PTP connection from %s:%u (\"%s\") to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
- clientNameString, conn->self_ip_string, conn->self_rtsp_port);
+ conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port);
conn->airplay_stream_category = ptp_stream;
conn->timing_type = ts_ptp;
@@ -2697,7 +2352,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
} else if (strcmp(timingProtocolString, "NTP") == 0) {
debug(1, "Connection %d: SETUP: NTP setup from %s:%u (\"%s\") to self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
- clientNameString, conn->self_ip_string, conn->self_rtsp_port);
+ conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port);
conn->airplay_stream_category = ntp_stream;
conn->timing_type = ts_ntp;
do_pthread_setname(&conn->thread, "ap2_ntp_%d", conn->connection_number);
@@ -2706,7 +2361,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
"Connection %d: SETUP: a \"None\" setup detected from %s:%u (\"%s\") to self at "
"%s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
- clientNameString, conn->self_ip_string, conn->self_rtsp_port);
+ conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port);
// now check to see if it's got the "isRemoteControlOnly" item and check it's true
plist_t isRemoteControlOnly = plist_dict_get_item(messagePlist, "isRemoteControlOnly");
if (isRemoteControlOnly != NULL) {
@@ -2717,7 +2372,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
"Connection %d: SETUP: Remote Control Only connection from %s:%u (\"%s\") to "
"self at %s:%u.",
conn->connection_number, conn->client_ip_string, conn->client_rtsp_port,
- clientNameString, conn->self_ip_string, conn->self_rtsp_port);
+ conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port);
conn->airplay_stream_category = remote_control_stream;
do_pthread_setname(&conn->thread, "ap2_rc_%d", conn->connection_number);
} else {
@@ -2733,6 +2388,11 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->connection_number);
}
}
+
+ debug(2, "Connection %d from \"%s\": Initial (i.e. no streams array) SETUP (AirPlay 2) on %s", conn->connection_number,
+ conn->ap2_client_name,
+ get_category_string(conn->airplay_stream_category));
+ debug_log_rtsp_message_conn(conn, 2, "Initial (i.e. no streams array) SETUP (AirPlay 2) incoming message", req);
// here, we know it's an initial setup and we know the kind of setup being requested
// if it's a full service PTP stream, we get groupUUID, groupContainsGroupLeader and
@@ -2828,6 +2488,9 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_array_append_item(addresses, plist_new_string(conn->self_ip_string));
// debug(1,"self ip: \"%s\"", conn->self_ip_string);
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+
struct ifaddrs *addrs, *iap;
getifaddrs(&addrs);
for (iap = addrs; iap != NULL; iap = iap->ifa_next) {
@@ -2862,6 +2525,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
}
}
freeifaddrs(addrs);
+ pthread_setcancelstate(oldState, NULL);
// debug(1,"initial timing peer command: \"%s\".", timing_list_message);
// ptp_send_control_message_string(timing_list_message);
@@ -2891,6 +2555,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
debug(2, "Connection %d: TCP PTP event port opened: %u.", conn->connection_number,
conn->local_event_port);
+
if (conn->rtp_event_thread != NULL)
debug(1, "previous rtp_event_thread allocation not freed, it seems.");
conn->ap2_event_receiver_exited = 0;
@@ -2962,7 +2627,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->rtp_event_thread = malloc(sizeof(pthread_t));
if (conn->rtp_event_thread == NULL)
die("Couldn't allocate space for pthread_t");
- named_pthread_create(conn->rtp_event_thread, NULL, &ap2_rc_event_receiver, (void *)conn,
+ named_pthread_create(conn->rtp_event_thread, NULL, &ap2_event_receiver, (void *)conn,
"ap2_rc_evt_%d", conn->connection_number);
plist_dict_set_item(setupResponsePlist, "eventPort",
plist_new_uint(conn->local_event_port));
@@ -2993,9 +2658,12 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
warn("Unrecognised SETUP incoming message -- ignored.");
}
} else {
- debug(2, "Connection %d: SETUP on %s. A \"streams\" array has been found",
- conn->connection_number, get_category_string(conn->airplay_stream_category));
- debug_log_rtsp_message(3, "SETUP (AirPlay 2) SETUP with streams incoming message", req);
+
+ debug(2, "Connection %d from \"%s\": Subsequent (i.e. with streams array) SETUP (AirPlay 2) on %s", conn->connection_number,
+ conn->ap2_client_name,
+ get_category_string(conn->airplay_stream_category));
+ debug_log_rtsp_message_conn(conn, 2, "Subsequent (i.e. with streams array) SETUP (AirPlay 2) incoming message", req);
+
if (conn->airplay_stream_category == ptp_stream) {
if (conn->player_thread) {
@@ -3107,8 +2775,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
conn->type = item_value;
switch (item_value) {
case 96: {
- debug(1, "Connection %d. AP2 Realtime ALAC/44100/S16/2 Stream.", conn->connection_number);
- debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req);
+ debug(4, "Connection %d. AP2 Realtime Audio Stream SETUP.", conn->connection_number);
+ debug_log_rtsp_message(4, "AP2 Realtime Audio Stream SETUP incoming message:", req);
conn->stream.type = ast_apple_lossless;
conn->airplay_stream_type = realtime_stream;
@@ -3117,12 +2785,20 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
if (item != NULL) {
plist_get_uint_val(item, &item_value);
// see https://emanuelecozzi.net/docs/airplay2/audio/ for values
- debug(2, "Sample rate: %" PRId64 ".", item_value);
conn->input_rate = item_value;
+ debug(4, "Set conn->input_rate: %u.", conn->input_rate);
} else {
- debug(1, "No sample rate (sr) property found in setup.");
+ debug(1, "Connection %d. No sample rate (sr) property found in setup.", conn->connection_number);
+ }
+
+ item = plist_dict_get_item(stream0, "spf"); // samples per frame
+ if (item != NULL) {
+ plist_get_uint_val(item, &item_value);
+ conn->frames_per_packet = item_value;
+ debug(4, "Set conn->frames_per_packet: %u.", conn->frames_per_packet);
+ } else {
+ debug(1, "Connection %d. No samples per frame (spf) property found in setup.", conn->connection_number);
}
- conn->frames_per_packet = 352;
// bind a new UDP port and get a socket
conn->local_realtime_audio_port = 0; // any port
@@ -3235,8 +2911,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
snprintf(salt_suffix, sizeof(salt_suffix), "%" PRIu64 "", seed);
conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx =
pair_cipher_new(PAIR_SERVER_HOMEKIT, 5, conn->pair_setup_result->shared_secret,
- conn->pair_setup_result->shared_secret_len,
- salt_suffix); // last argument is the (possible) dynamic salt suffix
+ conn->pair_setup_result->shared_secret_len, salt_suffix); // last argument is the (possible) dynamic salt suffix
if (conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx != NULL) {
conn->ap2_pairing_context.data_cipher_bundle.description = strdup("DataStream");
// get a port to use as an data port
@@ -3294,8 +2969,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist");
}
plist_free(messagePlist);
- if (clientNameString != NULL)
- free(clientNameString);
}
#endif
@@ -3463,11 +3136,11 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
debug(3, "Connection %d: request to set AirPlay Volume to: %f.", conn->connection_number,
volume);
// if we are playing, go ahead and change the volume
-#ifdef CONFIG_DBUS_INTERFACE
- if (dbus_service_is_running()) {
- shairport_sync_set_volume(shairportSyncSkeleton, volume);
- } else {
-#endif
+// #ifdef CONFIG_DBUS_INTERFACE
+// if (dbus_service_is_running()) {
+// shairport_sync_set_volume(shairportSyncSkeleton, volume);
+// } else {
+// #endif
pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed
pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
if (principal_conn == conn) {
@@ -3488,9 +3161,9 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
conn->own_airplay_volume_set = 1;
}
pthread_cleanup_pop(1); // release the principal_conn lock
-#ifdef CONFIG_DBUS_INTERFACE
- }
-#endif
+// #ifdef CONFIG_DBUS_INTERFACE
+// }
+// #endif
} else if (strncmp(cp, "progress: ", strlen("progress: ")) ==
0) { // this can be sent even when metadata is not solicited
@@ -3509,663 +3182,6 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req,
}
#ifdef CONFIG_METADATA
-// Metadata is not used by shairport-sync.
-// Instead we send all metadata to a fifo pipe, so that other apps can listen to
-// the pipe and use the metadata.
-
-// We use two 4-character codes to identify each piece of data and we send the
-// data itself, if any,
-// in base64 form.
-
-// The first 4-character code, called the "type", is either:
-// 'core' for all the regular metadadata coming from iTunes, etc., or
-// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync
-// itself, such as
-// start/end delimiters, etc.
-
-// For 'core' metadata, the second 4-character code is the 4-character metadata
-// code coming from
-// iTunes etc.
-// For 'ssnc' metadata, the second 4-character code is used to distinguish the
-// messages.
-
-// Cover art is not tagged in the same way as other metadata, it seems, so is
-// sent as an 'ssnc' type
-// metadata message with the code 'PICT'
-// Here are the 'ssnc' codes defined so far:
-// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the
-// first few bytes to see
-// which.
-// 'abeg' -- active mode entered. No arguments
-// 'aend' -- active mode exited. No arguments
-// 'pbeg' -- play stream begin. No arguments
-// 'pend' -- play stream end. No arguments
-// 'pfls' -- play stream flush. The argument is an unsigned 32-bit
-// frame number. It seems that all frames up to but not
-// including this frame are to be flushed.
-//
-// 'prsm' -- play stream resume. No arguments. (deprecated)
-// 'paus' -- buffered audio stream paused. No arguments.
-// 'pres' -- buffered audio stream resumed. No arguments.
-// 'pffr' -- the first frame of a play session has been received and has been validly
-// timed. The argument is
-// "/"."
-// 'pdis' -- a discontinuity in the timestamps of incoming frames has been detected.
-// timed. The argument is "/".
-// discontinuity is the actual frame number less the expected frame number
-// positive means a gap
-// 'pvol' -- play volume. The volume is sent as a string --
-// "airplay_volume,volume,lowest_volume,highest_volume"
-// volume, lowest_volume and highest_volume are given in dB.
-// The "airplay_volume" is what's sent to the player, and is from
-// 0.00 down to -30.00,
-// with -144.00 meaning mute.
-// This is linear on the volume control slider of iTunes or iOS
-// AirPlay.
-// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP
-// timestamps for the start
-// of the current play sequence, the current play point and the end of the
-// play sequence.
-// I guess the timestamps wrap at 2^32.
-// 'mdst' -- a sequence of metadata is about to start; will have, as data,
-// the rtptime associated with the metadata, if available
-// 'mden' -- a sequence of metadata has ended; will have, as data, the
-// rtptime associated with the metadata, if available
-// 'pcst' -- a picture is about to be sent; will have, as data, the rtptime
-// associated with the picture, if available
-// 'pcen' -- a picture has been sent; will have, as data, the rtptime
-// associated with the metadata, if available
-// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session.
-// 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 facilitate remote control of the source.
-// There is some information at http://nto.github.io/AirPlay.html about
-// remote control of the source.
-//
-// 'daid' -- this is the source's DACP-ID (if it has one -- it's not
-// guaranteed), useful if you want to remotely control the source. Use this
-// string to identify the source's remote control on the network.
-// 'acre' -- this is the source's Active-Remote token, necessary if you want
-// to send commands to the source's remote control (if it has one).
-// `clip` -- the payload is the IP number of the client, i.e. the sender of audio.
-// Can be an IPv4 or an IPv6 number. In AirPlay 2 operation, it is sent as soon
-// as the client has exclusive access to the player and after any existing
-// play session has been interrupted and terminated.
-// `conn` -- the payload is the IP number of the client, i.e. the sender of audio.
-// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message.
-// It is sent as soon as the client requests access to the player.
-// If Shairport Sync is already playing, this message is sent before the current
-// play session is stopped.
-// `svip` -- the payload is the IP number of the server, i.e. the player itself.
-// Can be an IPv4 or an IPv6 number.
-// `svna` -- the payload is the service name of the player, i.e. the name by
-// which it is seen in the AirPlay menu.
-// `disc` -- the payload is the IP number of the client, i.e. the sender of audio.
-// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message.
-// It is sent when a client has been disconnected.
-// `dapo` -- the payload is the port number (as text) on the server to which remote
-// control commands should be sent. It is 3689 for iTunes but varies for iOS devices.
-// ``
-
-// A special sub-protocol is used for sending large data items over UDP
-// If the payload exceeded 4 MB, it is chunked using the following format:
-// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
-// Notice that the number of items is different to the standard
-
-static int fd = -1;
-// static int dirty = 0;
-
-pc_queue metadata_queue;
-#define metadata_queue_size 500
-metadata_package metadata_queue_items[metadata_queue_size];
-pthread_t metadata_thread;
-
-#ifdef CONFIG_METADATA_HUB
-pc_queue metadata_hub_queue;
-#define metadata_hub_queue_size 500
-metadata_package metadata_hub_queue_items[metadata_hub_queue_size];
-pthread_t metadata_hub_thread;
-#endif
-
-#ifdef CONFIG_MQTT
-pc_queue metadata_mqtt_queue;
-#define metadata_mqtt_queue_size 500
-metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size];
-pthread_t metadata_mqtt_thread;
-#endif
-
-static int metadata_sock = -1;
-static struct sockaddr_in metadata_sockaddr;
-static char *metadata_sockmsg;
-pc_queue metadata_multicast_queue;
-#define metadata_multicast_queue_size 500
-metadata_package metadata_multicast_queue_items[metadata_queue_size];
-pthread_t metadata_multicast_thread;
-
-void metadata_create_multicast_socket(void) {
- if (config.metadata_enabled == 0)
- return;
-
- // Unlike metadata pipe, socket is opened once and stays open,
- // so we can call it in create
- if (config.metadata_sockaddr && config.metadata_sockport) {
- metadata_sock = socket(AF_INET, SOCK_DGRAM, 0);
- if (metadata_sock < 0) {
- debug(1, "Could not open metadata socket");
- } else {
- int buffer_size = METADATA_SNDBUF;
- setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
- bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr));
- metadata_sockaddr.sin_family = AF_INET;
- metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr);
- metadata_sockaddr.sin_port = htons(config.metadata_sockport);
- metadata_sockmsg = malloc(config.metadata_sockmsglength);
- if (metadata_sockmsg) {
- memset(metadata_sockmsg, 0, config.metadata_sockmsglength);
- } else {
- die("Could not malloc metadata multicast socket buffer");
- }
- }
- }
-}
-
-void metadata_delete_multicast_socket(void) {
- if (config.metadata_enabled == 0)
- return;
- if (metadata_sock != -1) {
- shutdown(metadata_sock, SHUT_RDWR); // we want to immediately deallocate the buffer
- close(metadata_sock);
- }
- if (metadata_sockmsg)
- free(metadata_sockmsg);
-}
-
-void metadata_open(void) {
- if (config.metadata_enabled == 0)
- return;
-
- size_t pl = strlen(config.metadata_pipename) + 1;
-
- char *path = malloc(pl + 1);
- snprintf(path, pl + 1, "%s", config.metadata_pipename);
-
- fd = try_to_open_pipe_for_writing(path);
- free(path);
-}
-
-static void metadata_close(void) {
- if (fd < 0)
- return;
- close(fd);
- fd = -1;
-}
-
-void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
- // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code,
- // length);
- if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) {
- char *ptr = metadata_sockmsg;
- uint32_t v;
- v = htonl(type);
- memcpy(ptr, &v, 4);
- ptr += 4;
- v = htonl(code);
- memcpy(ptr, &v, 4);
- ptr += 4;
- memcpy(ptr, data, length);
- sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr,
- sizeof(metadata_sockaddr));
- } else if (metadata_sock >= 0) {
- // send metadata in numbered chunks using the protocol:
- // ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data)
-
- uint32_t chunk_ix = 0;
- if (config.metadata_sockmsglength == 24)
- die("A divide by zero almost occurred (config.metadata_sockmsglength = 24).");
- uint32_t chunk_total = length / (config.metadata_sockmsglength - 24);
- if (chunk_total * (config.metadata_sockmsglength - 24) < length) {
- chunk_total++;
- }
- uint32_t remaining = length;
- uint32_t v;
- char *data_crsr = data;
- do {
- char *ptr = metadata_sockmsg;
- memcpy(ptr, "ssncchnk", 8);
- ptr += 8;
- v = htonl(chunk_ix);
- memcpy(ptr, &v, 4);
- ptr += 4;
- v = htonl(chunk_total);
- memcpy(ptr, &v, 4);
- ptr += 4;
- v = htonl(type);
- memcpy(ptr, &v, 4);
- ptr += 4;
- v = htonl(code);
- memcpy(ptr, &v, 4);
- ptr += 4;
- size_t datalen = remaining;
- if (datalen > config.metadata_sockmsglength - 24) {
- datalen = config.metadata_sockmsglength - 24;
- }
- memcpy(ptr, data_crsr, datalen);
- data_crsr += datalen;
- sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0,
- (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr));
- chunk_ix++;
- remaining -= datalen;
- if (remaining == 0)
- break;
- } while (1);
- }
-}
-
-void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
- // debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length);
- int ret = 0;
- // readers may go away and come back
-
- if (fd < 0)
- metadata_open();
- if (fd < 0)
- return;
- char thestring[1024];
- snprintf(thestring, 1024, "- %x
%x%u", type, code,
- length);
- // ret = non_blocking_write(fd, thestring, strlen(thestring));
- ret = write(fd, thestring, strlen(thestring));
- if (ret < 0) {
- // debug(1,"metadata_process error %d exit 1",ret);
- return;
- }
- if ((data != NULL) && (length > 0)) {
- snprintf(thestring, 1024, "\n\n");
- // ret = non_blocking_write(fd, thestring, strlen(thestring));
- ret = write(fd, thestring, strlen(thestring));
- if (ret < 0) {
- // debug(1,"metadata_process error %d exit 2",ret);
- return;
- }
- // here, we write the data in base64 form using our nice base64 encoder
- // but, we break it into lines of 76 output characters, except for the last
- // one.
- // thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time
- size_t remaining_count = length;
- char *remaining_data = data;
- // size_t towrite_count;
- char outbuf[76];
- while ((remaining_count) && (ret >= 0)) {
- size_t towrite_count = remaining_count;
- if (towrite_count > 57)
- towrite_count = 57;
- size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit
- if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) ==
- NULL)
- debug(1, "Error encoding base64 data.");
- // debug(1,"Remaining count: %d ret: %d, outbuf_size:
- // %d.",remaining_count,ret,outbuf_size);
- // ret = non_blocking_write(fd, outbuf, outbuf_size);
- ret = write(fd, outbuf, outbuf_size);
- if (ret < 0) {
- // debug(1,"metadata_process error %d exit 3",ret);
- return;
- }
- remaining_data += towrite_count;
- remaining_count -= towrite_count;
- }
- snprintf(thestring, 1024, "");
- // ret = non_blocking_write(fd, thestring, strlen(thestring));
- ret = write(fd, thestring, strlen(thestring));
- if (ret < 0) {
- // debug(1,"metadata_process error %d exit 4",ret);
- return;
- }
- }
- snprintf(thestring, 1024, " \n");
- // ret = non_blocking_write(fd, thestring, strlen(thestring));
- ret = write(fd, thestring, strlen(thestring));
- if (ret < 0) {
- // debug(1,"metadata_process error %d exit 5",ret);
- return;
- }
-}
-
-void metadata_pack_cleanup_function(void *arg) {
- // debug(1, "metadata_pack_cleanup_function called");
- metadata_package *pack = (metadata_package *)arg;
- if (pack->carrier)
- msg_free(&pack->carrier); // release the message
- else if (pack->data)
- free(pack->data);
- // debug(1, "metadata_pack_cleanup_function exit");
-}
-
-void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) {
- // debug(2, "metadata_thread_cleanup_function called");
- metadata_close();
-}
-
-void *metadata_thread_function(__attribute__((unused)) void *ignore) {
- // #include
- // debug(1, "metadata_thread_function PID %d", syscall(SYS_gettid));
- metadata_create_multicast_socket();
- metadata_package pack;
- pthread_cleanup_push(metadata_thread_cleanup_function, NULL);
- while (1) {
- pc_queue_get_item(&metadata_queue, &pack);
- pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
- if (config.metadata_enabled) {
- if (pack.carrier) {
- debug(4, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code,
- pack.length, pack.carrier->index_number);
- } else {
- debug(4, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
- }
- metadata_process(pack.type, pack.code, pack.data, pack.length);
- debug(4, " pipe: done.");
- }
- pthread_cleanup_pop(1);
- }
- pthread_cleanup_pop(1); // will never happen
- pthread_exit(NULL);
-}
-
-void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) {
- // debug(2, "metadata_multicast_thread_cleanup_function called");
- metadata_delete_multicast_socket();
-}
-
-void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
- // #include
- // debug(1, "metadata_multicast_thread_function PID %d", syscall(SYS_gettid));
- metadata_create_multicast_socket();
- metadata_package pack;
- pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL);
- while (1) {
- pc_queue_get_item(&metadata_multicast_queue, &pack);
- pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
- if (config.metadata_enabled) {
- if (pack.carrier) {
- debug(4,
- " multicast: type "
- "%x, code %x, length %u, message %d.",
- pack.type, pack.code, pack.length, pack.carrier->index_number);
- } else {
- debug(4,
- " multicast: type "
- "%x, code %x, length %u.",
- pack.type, pack.code, pack.length);
- }
- metadata_multicast_process(pack.type, pack.code, pack.data, pack.length);
- debug(4,
- " multicast: done.");
- }
- pthread_cleanup_pop(1);
- }
- pthread_cleanup_pop(1); // will never happen
- pthread_exit(NULL);
-}
-
-#ifdef CONFIG_METADATA_HUB
-void metadata_hub_close(void) {}
-
-void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) {
- // debug(2, "metadata_hub_thread_cleanup_function called");
- metadata_hub_close();
-}
-
-void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
- // #include
- // debug(1, "metadata_hub_thread_function PID %d", syscall(SYS_gettid));
- metadata_package pack;
- pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL);
- while (1) {
- pc_queue_get_item(&metadata_hub_queue, &pack);
- pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
- if (pack.carrier) {
- debug(4, " hub: type %x, code %x, length %u, message %d.", pack.type,
- pack.code, pack.length, pack.carrier->index_number);
- } else {
- debug(4, " hub: type %x, code %x, length %u.", pack.type, pack.code,
- pack.length);
- }
- metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
- debug(4, " hub: done.");
- pthread_cleanup_pop(1);
- }
- pthread_cleanup_pop(1); // will never happen
- pthread_exit(NULL);
-}
-#endif
-
-#ifdef CONFIG_MQTT
-void metadata_mqtt_close(void) {}
-
-void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) {
- // debug(2, "metadata_mqtt_thread_cleanup_function called");
- metadata_mqtt_close();
- // debug(2, "metadata_mqtt_thread_cleanup_function done");
-}
-
-void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
- // #include
- // debug(1, "metadata_mqtt_thread_function PID %d", syscall(SYS_gettid));
- metadata_package pack;
- pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL);
- while (1) {
- pc_queue_get_item(&metadata_mqtt_queue, &pack);
- pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
- if (config.mqtt_enabled) {
- if (pack.carrier) {
- debug(3,
- " mqtt: type %x, code %x, length %u, message "
- "%d.",
- pack.type, pack.code, pack.length, pack.carrier->index_number);
- } else {
- debug(3, " mqtt: type %x, code %x, length %u.",
- pack.type, pack.code, pack.length);
- }
- mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
- debug(3, " mqtt: done.");
- }
-
- pthread_cleanup_pop(1);
- }
- pthread_cleanup_pop(1); // will never happen
- pthread_exit(NULL);
-}
-#endif
-
-void metadata_init(void) {
- if (config.metadata_enabled) {
- // create the metadata pipe, if necessary
- size_t pl = strlen(config.metadata_pipename) + 1;
- char *path = malloc(pl + 1);
- snprintf(path, pl + 1, "%s", config.metadata_pipename);
- mode_t oldumask = umask(000);
- if (mkfifo(path, 0666) && errno != EEXIST)
- die("Could not create metadata pipe \"%s\".", path);
- umask(oldumask);
- debug(1, "metadata pipe name is \"%s\".", path);
-
- // try to open it
- fd = try_to_open_pipe_for_writing(path);
- // we check that it's not a "real" error. From the "man 2 open" page:
- // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
- // open for reading." Which is okay.
- if ((fd == -1) && (errno != ENXIO)) {
- char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
- debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
- warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
- (char *)errorstring, path);
- }
- free(path);
-
- // initialise the metadata queues first, otherwise the might be a race condition
- // create a pc_queue for the metadata pipe
- pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package),
- metadata_queue_size, "pipe");
-
- if (named_pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL,
- "metadata pipe") != 0)
- debug(1, "Failed to create metadata thread!");
-
- // create a pc_queue for the metadata_multicast_queue
- pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
- sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
- if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function,
- NULL, "metadata mcst") != 0)
- debug(1, "Failed to create metadata multicast thread!");
- }
-#ifdef CONFIG_METADATA_HUB
- // create a pc_queue for the metadata hub
- pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package),
- metadata_hub_queue_size, "hub");
- if (named_pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL,
- "metadata hub") != 0)
- debug(1, "Failed to create metadata hub thread!");
-#endif
-#ifdef CONFIG_MQTT
- // create a pc_queue for the MQTT handler
- pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package),
- metadata_mqtt_queue_size, "mqtt");
- if (named_pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL,
- "metadata mqtt") != 0)
- debug(1, "Failed to create metadata mqtt thread!");
-#endif
- metadata_running = 1;
-}
-
-void metadata_stop(void) {
- if (metadata_running) {
- debug(2, "metadata_stop called.");
-#ifdef CONFIG_MQTT
- // debug(2, "metadata stop mqtt thread.");
- pthread_cancel(metadata_mqtt_thread);
- pthread_join(metadata_mqtt_thread, NULL);
- pc_queue_delete(&metadata_mqtt_queue);
- // debug(2, "metadata stop mqtt done.");
-#endif
-#ifdef CONFIG_METADATA_HUB
- // debug(2, "metadata stop hub thread.");
- pthread_cancel(metadata_hub_thread);
- pthread_join(metadata_hub_thread, NULL);
- pc_queue_delete(&metadata_hub_queue);
- // debug(2, "metadata stop hub done.");
-#endif
- if (config.metadata_enabled) {
- // debug(2, "metadata stop multicast thread.");
- if (metadata_multicast_thread) {
- pthread_cancel(metadata_multicast_thread);
- pthread_join(metadata_multicast_thread, NULL);
- pc_queue_delete(&metadata_multicast_queue);
- // debug(2, "metadata stop multicast done.");
- }
- if (metadata_thread) {
- // debug(2, "metadata stop metadata_thread thread.");
- pthread_cancel(metadata_thread);
- pthread_join(metadata_thread, NULL);
- pc_queue_delete(&metadata_queue);
- // debug(2, "metadata_stop finished successfully.");
- }
- }
- }
-}
-
-int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code,
- const char *data, const uint32_t length, rtsp_message *carrier,
- int block) {
-
- // clang-format off
- // parameters:
- // type,
- // code,
- // pointer to data or NULL,
- // length of data or NULL,
- // the rtsp_message or NULL,
-
- // the rtsp_message is sent for 'core' messages, because it contains the data
- // and must not be freed until the data has been read.
- // So, it is passed to send_metadata to be retained, sent to the thread where metadata
- // is processed and released (and probably freed).
-
- // The rtsp_message is also sent for certain non-'core' messages.
-
- // The reading of the parameters is a bit complex:
- // If the rtsp_message field is non-null, then it represents an rtsp_message
- // and the data pointer is assumed to point to something within it.
- // The reference counter of the rtsp_message is incremented here and
- // should be decremented by the metadata handler when finished.
- // If the reference count reduces to zero, the message will be freed.
-
- // If the rtsp_message is NULL, then if the pointer is non-null then the data it
- // points to, of the length specified, is memcpy'd and passed to the metadata
- // handler. The handler should free it when done.
-
- // If the rtsp_message is NULL and the pointer is also NULL, nothing further
- // is done.
- // clang-format on
-
- metadata_package pack;
- pack.type = type;
- pack.code = code;
- pack.length = length;
- pack.carrier = carrier;
- pack.data = (char *)data;
- if (pack.carrier) {
- msg_retain(pack.carrier);
- } else {
- if (data)
- pack.data = memdup(data, length); // only if it's not a null
- }
-
- // debug(1, "send_metadata_to_queue %x/%x", type, code);
- int rc = pc_queue_add_item(queue, &pack, block);
- if (rc != 0) {
- if (pack.carrier) {
- if (rc == EWOULDBLOCK)
- debug(2,
- "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR ", "
- "length %u, message %d.",
- queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length,
- pack.carrier->index_number);
- msg_free(&pack.carrier);
- } else {
- if (rc == EWOULDBLOCK)
- debug(
- 2,
- "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR ", length %u.",
- queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length);
- if (pack.data)
- free(pack.data);
- }
- }
- return rc;
-}
-
-int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length,
- rtsp_message *carrier, int block) {
- int rc = 0;
- if (config.metadata_enabled) {
- rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
- rc =
- send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
- }
-
-#ifdef CONFIG_METADATA_HUB
- rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
-#endif
-
-#ifdef CONFIG_MQTT
- rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
-#endif
-
- return rc;
-}
static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn,
rtsp_message *req,
@@ -4301,7 +3317,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m
} else
#endif
if (!strncmp(ct, "text/parameters", 15)) {
- // debug(2, "received parameters in SET_PARAMETER request.");
+ debug(3, "received parameters in SET_PARAMETER request.");
handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress
} else {
debug(1, "Connection %d: received unknown Content-Type \"%s\" in SET_PARAMETER request.",
@@ -4404,6 +3420,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
debug(2, "An uncompressed PCM stream has been detected.");
conn->stream.type = ast_uncompressed;
conn->frames_per_packet = 352; // number of audio frames per packet.
+ debug(2, "Set conn->input_rate: 44100");
conn->input_rate = 44100;
conn->input_num_channels = 2;
conn->input_bit_depth = 16;
@@ -4510,6 +3527,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
// done later).
conn->frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet.
conn->input_rate = conn->stream.fmtp[11];
+ debug(2, "Set conn->input_rate: %u.", conn->input_rate);
conn->input_num_channels = conn->stream.fmtp[7];
conn->input_bit_depth = conn->stream.fmtp[3];
conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8);
@@ -4540,7 +3558,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
debug(2, "AirPlay version %d detected.", conn->AirPlayVersion);
}
}
-
+ conn->input_format_is_valid = 1;
#ifdef CONFIG_METADATA
send_metadata('ssnc', 'snua', hdr, strlen(hdr), req, 1);
#endif
@@ -4677,7 +3695,7 @@ static char *make_nonce(void) {
return base64_enc(random, 8); // returns a pointer to malloc'ed memory
}
-static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
+static int rtsp_classic_airplay_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
if (!config.password)
return 0;
@@ -5011,6 +4029,12 @@ void rtsp_conversation_thread_cleanup_function(void *arg) {
rc = pthread_mutex_destroy(&conn->flush_mutex);
if (rc)
debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc);
+#ifdef CONFIG_AIRPLAY_2
+ rc = pthread_mutex_destroy(&conn->event_sender_mutex);
+ if (rc)
+ debug(1, "Connection %d: error %d destroying event_sender_mutex.", conn->connection_number,
+ rc);
+#endif
debug(3, "Connection %d: Closed.", conn->connection_number);
conn->running = 0; // for the garbage collector
release_play_lock(conn);
@@ -5047,6 +4071,12 @@ static void *rtsp_conversation_thread_func(void *pconn) {
die("Connection %d: error %d initialising player_create_delete_mutex.", conn->connection_number,
rc);
+ #ifdef CONFIG_AIRPLAY_2
+ rc = pthread_mutex_init(&conn->event_sender_mutex, NULL);
+ if (rc)
+ die("Connection %d: error %d initialising event_sender_mutex.", conn->connection_number, rc);
+ #endif
+
// nothing before this is cancellable
pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn);
@@ -5106,8 +4136,18 @@ static void *rtsp_conversation_thread_func(void *pconn) {
msg_add_header(resp, "Server", "AirTunes/105.1");
#endif
- if ((conn->authorized == 1) || (rtsp_auth(&conn->auth_nonce, req, resp)) == 0) {
- conn->authorized = 1; // it must have been authorized or didn't need a password
+
+
+// we are only concerned with classic_airplay_authorized if we are running in classic AirPlay mode
+// either because the build is for classic AirPlay only or, if the build is for AirPlay 2,
+// we happen to be running in classic AirPlay compatibility mode
+
+ if ((conn->authorized == 1) ||
+#ifdef CONFIG_AIRPLAY_2
+ (conn->airplay_type != ap_1) ||
+#endif
+ (rtsp_classic_airplay_auth(&conn->auth_nonce, req, resp) == 0)) {
+ conn->authorized = 1; // it must have been classic airplay and authorized or didn't need a password, or not classic AirPlay at all
struct method_handler *mh;
int method_selected = 0;
for (mh = method_handlers; mh->method; mh++) {
@@ -5167,7 +4207,8 @@ static void *rtsp_conversation_thread_func(void *pconn) {
so_linger.l_linger = 0;
err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
if (err)
- debug(1, "Could not set the RTSP socket to abort due to a write error on closing.");
+ debug(1, "Connection %d: Could not set the RTSP socket to abort due to a write error on closing.",
+ conn->connection_number);
conn->stop = 1;
// if (debuglev >= 1)
// debuglev = 3; // see what happens next
diff --git a/rtsp.h b/rtsp.h
index 39a40dc8..b873425f 100644
--- a/rtsp.h
+++ b/rtsp.h
@@ -3,6 +3,27 @@
#include "player.h"
+typedef struct {
+ int index_number;
+ uint32_t referenceCount; // we might start using this...
+ unsigned int nheaders;
+ char *name[16];
+ char *value[16];
+
+ uint32_t contentlength;
+ char *content;
+
+ // for requests
+ char method[16];
+ char path[256];
+
+ // for responses
+ int respcode;
+} rtsp_message;
+
+void msg_retain(rtsp_message *msg);
+void msg_free(rtsp_message **msgh);
+
extern pthread_rwlock_t principal_conn_lock;
extern rtsp_conn_info *principal_conn;
extern rtsp_conn_info **conns;
@@ -27,19 +48,6 @@ play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption);
// this will release the play lock only if the conn has it or if the conn is NULL
void release_play_lock(rtsp_conn_info *conn);
-// initialise and completely delete the metadata stuff
-
-void metadata_init(void);
-void metadata_stop(void);
-
-// sends metadata out to the metadata pipe, if enabled.
-// It is sent with the type 'ssnc' the given code, data and length
-// The handler at the other end must know what to do with the data
-// e.g. if it's malloced, to free it, etc.
-// nothing is done automatically
-
-int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length,
- const int block);
#ifdef CONFIG_AIRPLAY_2
ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count);
diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf
index 88ee0e72..35f1d7a5 100644
--- a/scripts/shairport-sync.conf
+++ b/scripts/shairport-sync.conf
@@ -14,7 +14,7 @@ general =
// %v for the version number, e.g. 3.0 and
// %V for the full version string, e.g. 3.3-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
// Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H".
-// password = "secret"; // (AirPlay 1 only) leave this commented out if you don't want to require a password
+// password = "secret"; // leave this commented out if you don't want to require a password
// The interpolation setting below controls how Shairport Sync adds or removes frames of audio to keep in sync.
// "auto" (default) measures the processor's floating point speed and chooses "soxr" if available and it is fast enough. Otherwise, "vernier" is selected.
// "soxr" uses the SoX library to recode a packet of frames to a new packet containing more or fewer frames. This needs a processor with fast floating point capability.
diff --git a/shairport.c b/shairport.c
index 448f17e6..695c1f79 100644
--- a/shairport.c
+++ b/shairport.c
@@ -54,6 +54,9 @@
#include
#include
#include
+#include "utilities/generate_random_uuid.h"
+#include "utilities/generate_device_uuid.h"
+
#endif
#ifdef CONFIG_MBEDTLS
@@ -84,8 +87,12 @@
#include "dacp.h"
#endif
+#if defined(CONFIG_METADATA)
+#include "metadata/core.h"
+#endif
+
#if defined(CONFIG_METADATA_HUB)
-#include "metadata_hub.h"
+#include "metadata/hub.h"
#endif
#ifdef CONFIG_DBUS_INTERFACE
@@ -195,13 +202,12 @@ int has_fltp_capable_aac_decoder(void) {
#endif
#ifdef CONFIG_SOXR
-pthread_t soxr_time_check_thread;
-int soxr_time_check_thread_started = 0;
+pthread_t *soxr_time_check_thread = NULL;
void *soxr_time_check(__attribute__((unused)) void *arg) {
// this just checks how long it takes to process adding and subtracing a frame
// from a buffer at 44100
- // #include
- // debug(1, "soxr_time_check PID %d", syscall(SYS_gettid));
+ // #include
+ // debug(1, "soxr_time_check PID %ld", syscall(SYS_gettid));
const int buffer_length = 352;
int32_t inbuffer[buffer_length * 2];
@@ -221,7 +227,7 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
(uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be
// able to cancel it, do _don't even try_!
while (get_absolute_time_in_ns() < loop_until_time) {
-
+ pthread_testcancel();
number_of_iterations++;
for (i = 0; i < buffer_length; i++) {
double w = sin(i * (frequency + number_of_iterations * 2) * 2 * M_PI / 44100);
@@ -230,6 +236,9 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
inbuffer[i * 2 + 1] = wint;
}
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+
soxr_io_spec_t io_spec;
io_spec.itype = SOXR_INT32_I;
io_spec.otype = SOXR_INT32_I;
@@ -256,6 +265,8 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
outbuffer, buffer_length - 1, &odone, // Output.
&io_spec, // Input, output and transfer spec.
NULL, NULL); // Default configuration.
+
+ pthread_setcancelstate(oldState, NULL);
}
int64_t soxr_execution_time =
@@ -271,8 +282,8 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
debug(1, "No soxr-timing iterations performed, so \"vernier\" iteration will be used.");
config.soxr_delay_index = 0; // used as a flag
}
- debug(2, "soxr_delay: %d nanoseconds, soxr_delay_threshold: %d milliseconds.",
- config.soxr_delay_index, config.soxr_delay_threshold / 1000000);
+ debug(2, "soxr_delay maximum permissible: %d milliseconds, actual: %f milliseconds.",
+ config.soxr_delay_threshold / 1000000, 1E-6 * config.soxr_delay_index);
if ((config.packet_stuffing == ST_soxr) &&
(config.soxr_delay_index > config.soxr_delay_threshold))
inform("Note: this device may be too slow for \"soxr\" interpolation. Consider choosing the "
@@ -337,6 +348,7 @@ void usage(char *progname) {
printf(" -t, --timeout=SECONDS Go back to idle mode from play mode after a break in communications of this many seconds (default 60). Set to 0 never to exit play mode.\n");
printf(" --tolerance=TOLERANCE [Deprecated] Allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n");
printf(" --logOutputLevel Log the output level setting -- a debugging option, useful for determining the optimum maximum volume.\n");
+
#ifdef CONFIG_LIBDAEMON
printf(" -d, --daemon Daemonise.\n");
printf(" -j, --justDaemoniseNoPIDFile Daemonise without a PID file.\n");
@@ -347,6 +359,10 @@ void usage(char *progname) {
printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. --metadata-pipename=/tmp/%s-metadata.\n", config.appName);
printf(" The default is /tmp/%s-metadata.\n", config.appName);
printf(" -g, --get-coverart Include cover art in the metadata to be gathered and sent.\n");
+#endif
+#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
+ printf(" --dbus-default-message-bus=BUS Use the BUS specified for the native D-Bus and MPRIS interfaces.\n");
+ printf(" BUS must be \"system\" (default) or \"session\".\n");
#endif
printf(" --log-to-syslog Send debug and statistics information through syslog\n");
printf(" If used, this should be the first command line argument.\n");
@@ -535,6 +551,7 @@ int parse_options(int argc, char **argv) {
// for unexpected circumstances
config.model = strdup("ShairportSync");
+ //config.model = strdup("AirPort10,115");
// config.model = strdup("AudioAccessory5,1");
// config.srcvers = strdup(PACKAGE_VERSION);
@@ -553,6 +570,9 @@ int parse_options(int argc, char **argv) {
#endif
config.firmware_version = strdup(PACKAGE_VERSION);
+ free(config.firmware_version);
+ config.firmware_version = strdup("5.2");
+
#ifdef CONFIG_METADATA
/* Get the metadata setting. */
config.metadata_enabled = 1; // if metadata support is included, then enable it by default
@@ -581,83 +601,6 @@ int parse_options(int argc, char **argv) {
// not made active again (not used)
#endif
-#ifdef CONFIG_AIRPLAY_2
- // the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32
- // bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as
- // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
- // 496155702020608 this setting here is the source of both the plist features response and the
- // mDNS string.
- // note: 0x300401F4A00 works but with weird delays and stuff
- // config.airplay_features = 0x1C340405FCA00;
- uint64_t mask =
- ((uint64_t)1 << 17) | ((uint64_t)1 << 16) | ((uint64_t)1 << 15) | ((uint64_t)1 << 50);
- config.airplay_features =
- // 0x114BD04A5FCA00 & (~mask);
- 0x1C340405D4A00 & (~mask); // APX + Authentication4 (b14) with no metadata (see below)
-
- /*
- config.airplay_features |= (uint64_t)1 << 21; // Audio Format 4
-
- config.airplay_features |= (uint64_t)1 << 25; // Unknown
- config.airplay_features |= (uint64_t)1 << 36; // Unknown
- config.airplay_features |= (uint64_t)1 << 39; // Unknown
- config.airplay_features |= (uint64_t)1 << 43; // Supports System Pairing
- config.airplay_features |= (uint64_t)1 << 47; // Unknown
-
- // 0xB
- config.airplay_features |= (uint64_t)1 << 63; // Unknown
- config.airplay_features |= (uint64_t)1 << 61; // Unknown
- config.airplay_features |= (uint64_t)1 << 60; // Unknown
- // ...0xC
- // // config.airplay_features |= (uint64_t)1 << 59; // Unknown
- config.airplay_features |= (uint64_t)1 << 58; // Unknown
-
- // ...0x3
- config.airplay_features |= (uint64_t)1 << 53; // Unknown
- config.airplay_features |= (uint64_t)1 << 52; // Unknown
- */
-
- // config.airplay_features |= ((uint64_t)1 << 58) | ((uint64_t)1 << 60) | ((uint64_t)1 << 58);
-
- // 60 seems to interfere with disconnecting from a group
-
- // Advertised with mDNS and returned with GET /info, see
- // https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN
- // required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio
- // cable attached, OneTimePairingRequired, device was setup for Homekit access control
- config.airplay_statusflags = 0;
- config.airplay_statusflags |= 1 << 2; // Audio cable is attached
- // config.airplay_statusflags |= 1 << 10; // DeviceWasSetupForHKAccessControl
- // config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
- // config.airplay_statusflags |= 1 << 19; // Unknown. Seems to control whether individual volume
- // controls are shown and whether the SPS devices shows when its active.
-
- // Set to NULL to work with transient pairing
- config.airplay_pin = NULL;
-
- // use the MAC address placed in config.hw_addr to generate the default airplay_device_id
- uint64_t temporary_airplay_id = nctoh64(config.hw_addr);
- temporary_airplay_id =
- temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8.
-
- // now generate a UUID
- // from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c
- // with thanks
- uuid_t binuuid;
- uuid_generate_random(binuuid);
-
- char *uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end
- // Produces a UUID string at uuid consisting of lower-case letters
- uuid_unparse_lower(binuuid, uuid);
- config.airplay_pi = uuid;
-
- char *pgid_uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end
- uuid_generate_random(binuuid);
- uuid_unparse_lower(binuuid, pgid_uuid);
- config.airplay_pgid = pgid_uuid;
-
-#endif
-
// config_setting_t *setting;
const char *str = NULL;
int value = 0;
@@ -665,6 +608,11 @@ int parse_options(int argc, char **argv) {
// debug(1, "Looking for the configuration file \"%s\".", config.configfile);
+ // use the MAC address placed in config.hw_addr to generate the default airplay_device_id
+ uint64_t temporary_airplay_id = nctoh64(config.hw_addr);
+ temporary_airplay_id =
+ temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8.
+
config_init(&config_file_stuff);
config_file_real_path = realpath(config.configfile, NULL);
@@ -1465,7 +1413,6 @@ int parse_options(int argc, char **argv) {
"avahi enabled, or disable remote control.");
}
#endif
-#endif
#ifdef CONFIG_AIRPLAY_2
long long aid;
@@ -1481,6 +1428,8 @@ int parse_options(int argc, char **argv) {
}
#endif
+#endif
+
}
// now, do the command line options again, but this time do them fully -- it's a unix convention
@@ -1600,7 +1549,10 @@ int parse_options(int argc, char **argv) {
// a uuid_t and an md5 hash are both 128 bits, 16 bytes
uuid_t result;
memset(result, 0, sizeof(result));
- memcpy(result, config.ap1_prefix, sizeof(result));
+ if (sizeof(config.ap1_prefix) < sizeof(result))
+ memcpy(result, config.ap1_prefix, sizeof(config.ap1_prefix));
+ else
+ memcpy(result, config.ap1_prefix, sizeof(result));
// OpenSSL is mandatory for AirPlay 2 anyway
#ifdef CONFIG_OPENSSL
@@ -1627,8 +1579,8 @@ int parse_options(int argc, char **argv) {
// Produces a UUID string at uuid consisting of lower-case letters
uuid_unparse_lower(result, psi_uuid);
config.airplay_psi = psi_uuid;
+
debug(3, "size of pk is %zu.", sizeof(config.airplay_pk));
-
pair_public_key_get(PAIR_SERVER_HOMEKIT, config.airplay_pk, config.airplay_device_id);
char buf[128];
char *ptr = buf;
@@ -1637,18 +1589,38 @@ int parse_options(int argc, char **argv) {
ptr += sprintf(ptr, "%02x", config.airplay_pk[pk_index]);
*ptr = '\0';
config.pk_string = strdup(buf);
+
+ // the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32
+ // bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as
+ // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist:
+ // 496155702020608 this setting here is the source of both the plist features response and the
+ // mDNS string.
+
+ config.airplay_features =
+ 0x00018340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no AP1 artwork (b15)
+ // 0x0001C340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no AP1 artwork (b15)
+ // 0x0001C340445D0A00;
+ // config.airplay_features |= (1 << 26); // 0x0x4000000
+
+ // features=0x0001C340445D0A00 -- AirPort Express
#ifdef CONFIG_METADATA
// If we are asking for metadata, turn on the relevant bits
- if (config.metadata_enabled != 0) {
- config.airplay_features |= (uint64_t)1 << 16; // progress, 17 is text, 50 is in a binary plist
- config.airplay_features |= (uint64_t)1 << 17; // text, 50 is in a binary plist
- // config.airplay_features |=
- // (uint64_t)1 << 50; // binary plist
+
+ // If bit 50 is set, metadata is sent via plists in POST /command payloads.
+ // The data consists of textual information about what is playing and cover art
+ // It does not seem possible to turn off the cover art.
- // If we are asking for artwork, turn on the relevant bit
- if (config.get_coverart)
- config.airplay_features |= (uint64_t)1 << 15; // artwork
+ // While bit 50 is set, no data comes through the "classic" way. That is
+ // no progress, text or picture data comes through in the way that
+ // it comes through in Classic AirPlay (aka AirPlay 1).
+
+ // Although it is less flexible about what metadata is sent, bit 50 being set
+ // provides much more information, so should be the default for AirPlay 2
+
+ if (config.metadata_enabled != 0) {
+ config.airplay_features |= (uint64_t)1 << 50; // metadata in a binary plist, including more state information
+ // config.airplay_features |= ((uint64_t)1 << 15) | ((uint64_t)1 << 16) | ((uint64_t)1 << 17); // older metadata flags artwork, progress and text respectively
}
#endif
@@ -1669,6 +1641,23 @@ int parse_options(int argc, char **argv) {
if (padding)
*padding = 0;
debug(2, "airplay_fex is \"%s\"", config.airplay_fex);
+
+ // now the status flags
+ // Advertised with mDNS and returned with GET /info, see
+ // https://openairplay.github.io/airplay-spec/status_flags.html
+
+ config.airplay_statusflags = 0;
+ config.airplay_statusflags |= 1 << 2; // Audio cable is attached
+ if (config.password != NULL) {
+ config.airplay_statusflags |= 1 << 7; // Password required
+ }
+ // config.airplay_statusflags |= 1 << 10; // DeviceWasSetupForHKAccessControl
+ // config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay
+ // config.airplay_statusflags |= 1 << 19; // Unknown. Seems to control whether individual volume
+ // controls are shown and whether the SPS devices shows when its active.
+
+ config.airplay_pi = generate_device_uuid(config.airplay_device_id);
+ config.airplay_pgid = generate_random_uuid();
#endif
#ifdef CONFIG_LIBDAEMON
@@ -1856,7 +1845,6 @@ void exit_function() {
(config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not
// actually daemonised at all
#endif
- debug(2, "exit function called...");
/*
Actually, there is no terminate_mqtt() function.
#ifdef CONFIG_MQTT
@@ -1902,12 +1890,16 @@ void exit_function() {
}
#ifdef CONFIG_SOXR
- // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down
- if (soxr_time_check_thread_started != 0) {
- debug(2, "Waiting for SoXr timecheck to terminate...");
- pthread_join(soxr_time_check_thread, NULL);
- soxr_time_check_thread_started = 0;
- debug(2, "Waiting for SoXr timecheck to terminate done");
+ {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ if (soxr_time_check_thread != NULL) {
+ pthread_cancel(*soxr_time_check_thread);
+ pthread_join(*soxr_time_check_thread, NULL);
+ free(soxr_time_check_thread);
+ soxr_time_check_thread = NULL;
+ }
+ pthread_setcancelstate(oldState, NULL);
}
#endif
@@ -1948,8 +1940,6 @@ void exit_function() {
free(config.nqptp_shared_memory_interface_name);
if (config.airplay_device_id)
free(config.airplay_device_id);
- if (config.airplay_pin)
- free(config.airplay_pin);
if (config.airplay_pi)
free(config.airplay_pi);
if (config.airplay_pgid)
@@ -1994,7 +1984,7 @@ void exit_function() {
#else
mdns_unregister(); // once the dacp handler is done and all player threrads are done it should
// be safe
- debug(1, "normal exit");
+ debug(2, "normal exit");
#endif
} else {
debug(1, "emergency exit");
@@ -2346,6 +2336,7 @@ int main(int argc, char **argv) {
openlog(NULL, 0, LOG_DAEMON);
#endif
type_of_exit_cleanup = TOE_normal; // what kind of exit cleanup needed
+ debug(1, "adding the exit function");
atexit(exit_function);
// get a device id -- the first non-local MAC address
@@ -2957,7 +2948,9 @@ int main(int argc, char **argv) {
config.output_channel_map[i] = strdup(channel_id);
debug(2, "output channel %d is \"%s\".", i, config.output_channel_map[i]);
} else {
- warn("channel \"%s\" is not recognised -- output channel %d will be silent.",
+
+
+ warn("during channel mapping, \"%s\" was not recognised as a channel name -- as a result, output channel %d will be silent.",
channel_id, i);
config.output_channel_map[i] = strdup("--");
}
@@ -3153,8 +3146,20 @@ int main(int argc, char **argv) {
debug(option_print_level, "loudness reference level is %f", config.loudness_reference_volume_db);
#ifdef CONFIG_SOXR
- named_pthread_create(&soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker");
- soxr_time_check_thread_started = 1;
+
+ {
+ int oldState;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
+ soxr_time_check_thread = malloc(sizeof(pthread_t));
+ if (soxr_time_check_thread != NULL) {
+ named_pthread_create(soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker");
+ } else {
+ debug(1,"couldn't get memory to start the soxr_checker");
+ }
+ pthread_setcancelstate(oldState, NULL); // make this un-cancellable
+
+ }
+
#endif
#ifdef CONFIG_FFMPEG
@@ -3180,7 +3185,7 @@ int main(int argc, char **argv) {
if (idx != 0)
strncat(channel_map, " ", sizeof(channel_map) - 1);
strncat(channel_map, chName, sizeof(channel_map) - 1 - strlen(channel_map));
- debug(3, "Channel %d: \"%s\"", idx, chName);
+ debug(4, "Channel %d: \"%s\"", idx, chName);
} else {
debug(1, "Insufficient space for the name of channel %d.", idx);
}
diff --git a/utilities/debug.c b/utilities/debug.c
index ea3a9365..e99cf80c 100644
--- a/utilities/debug.c
+++ b/utilities/debug.c
@@ -23,6 +23,7 @@ SOFTWARE.
*/
#include "debug.h"
+#include // for isprint()
#include
#include
#include
@@ -64,13 +65,9 @@ void debug_init(int level, int show_elapsed_time, int show_relative_time, int sh
debugger_show_file_and_line = show_file_and_line;
}
-int debug_level() {
- return debuglev;
-};
+int debug_level() { return debuglev; };
-void set_debug_level(int level) {
- debuglev = level;
-}
+void set_debug_level(int level) { debuglev = level; }
void increase_debug_level() {
if (debuglev < 3)
@@ -82,28 +79,15 @@ void decrease_debug_level() {
debuglev--;
}
-int get_show_elapsed_time() {
- return debugger_show_elapsed_time;
-
-}
-void set_show_elapsed_time(int setting) {
- debugger_show_elapsed_time = setting;
-}
-int get_show_relative_timel() {
- return debugger_show_relative_time;
-}
+int get_show_elapsed_time() { return debugger_show_elapsed_time; }
+void set_show_elapsed_time(int setting) { debugger_show_elapsed_time = setting; }
+int get_show_relative_timel() { return debugger_show_relative_time; }
-void set_show_relative_time(int setting) {
- debugger_show_relative_time = setting;
-}
+void set_show_relative_time(int setting) { debugger_show_relative_time = setting; }
-int get_show_file_and_line() {
- return debugger_show_file_and_line;
-}
+int get_show_file_and_line() { return debugger_show_file_and_line; }
-void set_show_file_and_line(int setting) {
- debugger_show_file_and_line = setting;
-}
+void set_show_file_and_line(int setting) { debugger_show_file_and_line = setting; }
char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss, double tsl,
const char *filename, const int linenumber, const char *prefix) {
@@ -244,10 +228,94 @@ void _inform(const char *filename, const int linenumber, const char *format, ...
pthread_setcancelstate(oldState, NULL);
}
+
+void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *ibuf,
+ size_t buf_len) {
+
+ if (level > debuglev)
+ return;
+
+ char *vbuf = (char *)ibuf; // this to stop the compiler complaining about indexing through a void
+ unsigned int i;
+ unsigned int hexdump_cols = 16;
+
+ // 0x123456: \n
+ const size_t buffer_size = 2 + 6 + 2 + hexdump_cols * 3 + hexdump_cols + 1;
+ char *buf = malloc(buffer_size);
+ if (buf) {
+ //char *bufp = buf;
+ // *buf = '\0';
+
+ // if (msg)
+ // printf("%s", msg);
+
+ #define ADDR_PREFIX_LEN 10 // "0x000000: "
+
+ // each complete row
+ for (i = 0; i < (buf_len / hexdump_cols); i++) {
+ char *bufp = buf;
+ *buf = '\0';
+ snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06x ", (i * hexdump_cols) & 0xFFFFFF);
+ bufp += ADDR_PREFIX_LEN;
+
+ unsigned int j;
+ for (j = 0; j < hexdump_cols; j++) {
+ snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[i * hexdump_cols + j]);
+ bufp += strlen("12 ");
+ }
+
+ for (j = 0; j < hexdump_cols; j++) {
+ if (isprint(vbuf[i * hexdump_cols + j])) {
+ *bufp = 0xFF & vbuf[i * hexdump_cols + j];
+ } else {
+ *bufp = '.';
+ }
+ bufp++;
+ }
+ *bufp = '\0';
+ _debug(thefilename, linenumber, level, "%s", buf);
+ }
+
+ size_t remaining = buf_len % hexdump_cols;
+ if (remaining != 0) {
+ size_t starting_offset = buf_len - remaining;
+ char *bufp = buf;
+ *buf = '\0';
+ snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06zx ", starting_offset & 0xFFFFFF);
+ bufp += ADDR_PREFIX_LEN;
+
+ unsigned int j;
+ for (j = 0; j < hexdump_cols; j++) {
+ if (j < remaining) {
+ snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[starting_offset + j]);
+ } else {
+ snprintf(bufp, strlen(" ") + 1, " "); // fille with blanks
+ }
+ bufp += strlen("12 ");
+ }
+
+ for (j = 0; j < remaining; j++) {
+ if (isprint(vbuf[i * hexdump_cols + j])) {
+ *bufp = 0xFF & vbuf[starting_offset + j];
+ } else {
+ *bufp = '.';
+ }
+ bufp++;
+ }
+ *bufp = '\0';
+ _debug(thefilename, linenumber, level, "%s", buf);
+ }
+ free(buf);
+ }
+}
+
+
+/*
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf,
size_t buf_len) {
if (level > debuglev)
return;
+ new_debug_print_buffer(thefilename, linenumber, level, vbuf, buf_len);
char *buf = (char *)vbuf;
char *obf =
malloc(buf_len * 4 + 1); // to be on the safe side -- 4 characters on average for each byte
@@ -274,4 +342,5 @@ void _debug_print_buffer(const char *thefilename, const int linenumber, int leve
_debug(thefilename, linenumber, level, "%s", obf);
free(obf);
}
-}
\ No newline at end of file
+}
+*/
\ No newline at end of file
diff --git a/utilities/generate_device_uuid.c b/utilities/generate_device_uuid.c
new file mode 100644
index 00000000..51dcef4e
--- /dev/null
+++ b/utilities/generate_device_uuid.c
@@ -0,0 +1,48 @@
+/*
+ * Generate a device-related UUID. This file is part of Shairport Sync
+ * Copyright (c) Mike Brady 2026
+
+ * Modifications, including those associated with audio synchronization, multithreading and
+ * metadata handling copyright (c) Mike Brady 2014--2025
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include
+#include
+#include
+
+#include "generate_device_uuid.h"
+#include "definitions.h"
+
+// user is responsible for deallocating returned string
+char *generate_device_uuid(const char *device_id) {
+ uuid_t namespace_uuid;
+ uuid_t derived_uuid;
+
+ uuid_parse(SHAIRPORT_SYNC_DEVICE_NAMESPACE, namespace_uuid);
+ uuid_generate_sha1(derived_uuid, namespace_uuid, device_id, strlen(device_id));
+
+ char *uuid = malloc(UUID_STR_LEN + 1);
+ uuid_unparse_lower(derived_uuid, uuid);
+ return uuid;
+}
\ No newline at end of file
diff --git a/utilities/generate_device_uuid.h b/utilities/generate_device_uuid.h
new file mode 100644
index 00000000..96a26952
--- /dev/null
+++ b/utilities/generate_device_uuid.h
@@ -0,0 +1,3 @@
+#pragma once
+
+char *generate_device_uuid(const char *device_id);
\ No newline at end of file
diff --git a/utilities/generate_random_uuid.c b/utilities/generate_random_uuid.c
new file mode 100644
index 00000000..848345ff
--- /dev/null
+++ b/utilities/generate_random_uuid.c
@@ -0,0 +1,47 @@
+/*
+ * Generate a random UUID. This file is part of Shairport Sync
+ * Copyright (c) Mike Brady 2026
+
+ * Modifications, including those associated with audio synchronization, multithreading and
+ * metadata handling copyright (c) Mike Brady 2014--2025
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include
+#include
+
+#include "generate_random_uuid.h"
+
+// user is responsible for deallocating returned string
+char *generate_random_uuid() {
+ // generate a UUID
+ // from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c
+ // with thanks
+ uuid_t binuuid;
+ uuid_generate_random(binuuid);
+
+ char *uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end
+ // Produces a UUID string at uuid consisting of lower-case letters
+ uuid_unparse_lower(binuuid, uuid);
+ return uuid;
+}
\ No newline at end of file
diff --git a/utilities/generate_random_uuid.h b/utilities/generate_random_uuid.h
new file mode 100644
index 00000000..80d47a25
--- /dev/null
+++ b/utilities/generate_random_uuid.h
@@ -0,0 +1,6 @@
+#ifndef _GENERATE_RANDOM_UUID_H
+#define _GENERATE_RANDOM_UUID_H
+
+char *generate_random_uuid();
+
+#endif // _GENERATE_RANDOM_UUID_H
diff --git a/utilities/network_utilities.c b/utilities/network_utilities.c
index 63882e4b..edb0741f 100644
--- a/utilities/network_utilities.c
+++ b/utilities/network_utilities.c
@@ -38,8 +38,8 @@ int eintr_checked_accept(int sockfd, struct sockaddr *addr,
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1,
- "error %d accept()ing a socketin ap2_event_receiver: \"%s\". (Note: error %d will be ignored.)",
- errno, errorstring, EINTR);
+ "error %d accept()ing a socket %d: \"%s\". (Note: error %d will be ignored.)",
+ errno, sockfd, errorstring, EINTR);
}
} while((response == -1) && (errno == EINTR));
diff --git a/utilities/rtsp_message_utilities.c b/utilities/rtsp_message_utilities.c
new file mode 100644
index 00000000..21393cd1
--- /dev/null
+++ b/utilities/rtsp_message_utilities.c
@@ -0,0 +1,265 @@
+#include
+#include
+#include
+#include
+#include "rtsp_message_utilities.h"
+#include "../rtsp.h"
+#include "../common.h"
+
+#ifdef HAVE_LIBPLIST_GE_2_3_0
+#define plist_from_memory(plist_data, length, plist) \
+ plist_from_memory((plist_data), (length), (plist), NULL)
+#endif
+
+// every time we want to retain or release a reference count, lock it with this
+// if a reference count is read as zero, it means the it's being deallocated.
+static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
+static int msg_indexes = 1;
+
+void msg_retain(rtsp_message *msg) {
+ int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
+ if (rc)
+ debug(1, "Error %d locking reference counter lock", rc);
+ if (msg > (rtsp_message *)0x00010000) {
+ msg->referenceCount++;
+ debug(4, "msg_free increment reference counter message %d to %d.", msg->index_number,
+ msg->referenceCount);
+ // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
+ rc = pthread_mutex_unlock(&reference_counter_lock);
+ if (rc)
+ debug(1, "Error %d unlocking reference counter lock", rc);
+ } else {
+ debug(1, "invalid rtsp_message pointer 0x%" PRIxPTR " passed to retain", (uintptr_t)msg);
+ }
+}
+
+rtsp_message *msg_init(void) {
+ // no thread cancellation points here
+ int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
+ if (rc)
+ debug(1, "Error %d locking reference counter lock", rc);
+
+ rtsp_message *msg = malloc(sizeof(rtsp_message));
+ if (msg) {
+ memset(msg, 0, sizeof(rtsp_message));
+ msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
+ msg->index_number = msg_indexes++;
+ debug(4, "msg_init message %d", msg->index_number);
+ } else {
+ die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
+ }
+ // debug(1,"msg_init -- create item %d.", msg->index_number);
+
+ rc = pthread_mutex_unlock(&reference_counter_lock);
+ if (rc)
+ debug(1, "Error %d unlocking reference counter lock", rc);
+
+ return msg;
+}
+
+int msg_add_header(rtsp_message *msg, char *name, char *value) {
+ if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) {
+ warn("too many headers?!");
+ return 1;
+ }
+
+ msg->name[msg->nheaders] = strdup(name);
+ msg->value[msg->nheaders] = strdup(value);
+ msg->nheaders++;
+
+ return 0;
+}
+
+char *msg_get_header(rtsp_message *msg, char *name) {
+ unsigned int i;
+ for (i = 0; i < msg->nheaders; i++)
+ if (!strcasecmp(msg->name[i], name))
+ return msg->value[i];
+ return NULL;
+}
+
+void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
+ rtsp_message *msg) {
+ unsigned int i;
+ if (conn != NULL) {
+ if (msg->respcode != 0)
+ _debug(filename, linenumber, level, "Connection: %d, Response Code: %d.", conn->connection_number, msg->respcode);
+ for (i = 0; i < msg->nheaders; i++) {
+ _debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"", conn->connection_number, msg->name[i],
+ msg->value[i]);
+ }
+ } else {
+ if (msg->respcode != 0)
+ _debug(filename, linenumber, level, " Response Code: %d.", msg->respcode);
+ for (i = 0; i < msg->nheaders; i++) {
+ _debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i],
+ msg->value[i]);
+ }
+ }
+}
+
+void msg_free(rtsp_message **msgh) {
+ debug_mutex_lock(&reference_counter_lock, 1000, 0);
+ if (*msgh > (rtsp_message *)0x00010000) {
+ rtsp_message *msg = *msgh;
+ msg->referenceCount--;
+ if (msg->referenceCount)
+ debug(4, "msg_free decrement reference counter message %d to %d", msg->index_number,
+ msg->referenceCount);
+ if (msg->referenceCount == 0) {
+ unsigned int i;
+ for (i = 0; i < msg->nheaders; i++) {
+ free(msg->name[i]);
+ free(msg->value[i]);
+ }
+ if (msg->content)
+ free(msg->content);
+ // debug(1,"msg_free item %d -- free.",msg->index_number);
+ uintptr_t index = (msg->index_number) & 0xFFFF;
+ if (index == 0)
+ index = 0x10000; // ensure it doesn't fold to zero.
+ *msgh =
+ (rtsp_message *)(index); // put a version of the index number of the freed message in here
+ debug(4, "msg_free freed message %d", msg->index_number);
+ free(msg);
+ } else {
+ // debug(1,"msg_free item %d -- decrement reference to
+ // %d.",msg->index_number,msg->referenceCount);
+ }
+ } else if (*msgh != NULL) {
+ debug(1,
+ "msg_free: error attempting to free an allocated but already-freed rtsp_message, number "
+ "%" PRIxPTR ".",
+ (uintptr_t)*msgh);
+ }
+ debug_mutex_unlock(&reference_counter_lock, 0);
+}
+
+int msg_handle_line(rtsp_message **pmsg, char *line) {
+ rtsp_message *msg = *pmsg;
+
+ if (!msg) {
+ msg = msg_init();
+ *pmsg = msg;
+ char *sp, *p;
+ sp = NULL; // this is to quieten a compiler warning
+
+ debug(4, "RTSP/HTTP Message Received: \"%s\".", line);
+
+ p = strtok_r(line, " ", &sp);
+ if (!p)
+ goto fail;
+ strncpy(msg->method, p, sizeof(msg->method) - 1);
+
+ p = strtok_r(NULL, " ", &sp);
+ if (!p)
+ goto fail;
+ strncpy(msg->path, p, sizeof(msg->path) - 1);
+
+ p = strtok_r(NULL, " ", &sp);
+ if (!p)
+ goto fail;
+ if ((strcmp(p, "RTSP/1.0") != 0) && (strcmp(p, "HTTP/1.1") != 0)) {
+ debug(1, "Problem with Message: \"%s\"", p);
+ goto fail;
+ }
+
+ return -1;
+ }
+
+ if (strlen(line)) {
+ char *p;
+ p = strstr(line, ": ");
+ if (!p) {
+ warn("bad header: >>%s<<", line);
+ goto fail;
+ }
+ *p = 0;
+ p += 2;
+ msg_add_header(msg, line, p);
+ debug(4, " %s: %s.", line, p);
+ return -1;
+ } else {
+ char *cl = msg_get_header(msg, "Content-Length");
+ if (cl)
+ return atoi(cl);
+ else
+ return 0;
+ }
+
+fail:
+ debug(1, "msg_handle_line fail");
+ msg_free(pmsg);
+ *pmsg = NULL;
+ return 0;
+}
+
+#ifdef CONFIG_AIRPLAY_2
+
+int rtsp_message_contains_plist(rtsp_message *message) {
+ int reply = 0; // assume there is no plist in the message
+ if ((message->contentlength >= strlen("bplist00")) &&
+ (strstr(message->content, "bplist00") == message->content))
+ reply = 1;
+ return reply;
+}
+
+plist_t plist_from_rtsp_content(rtsp_message *message) {
+ plist_t the_plist = NULL;
+ if (rtsp_message_contains_plist(message)) {
+ plist_from_memory(message->content, message->contentlength, &the_plist);
+ }
+ return the_plist;
+}
+
+char *plist_as_xml_text(plist_t the_plist) {
+ // caller must free the returned character buffer
+ // convert it to xml format
+ uint32_t size;
+ char *plist_out = NULL;
+ plist_to_xml(the_plist, &plist_out, &size);
+
+ // put it into a NUL-terminated string
+ char *reply = malloc(size + 1);
+ if (reply) {
+ memcpy(reply, plist_out, size);
+ reply[size] = '\0';
+ }
+ if (plist_out)
+ free(plist_out);
+ return reply;
+}
+
+// caller must free the returned character buffer
+char *rtsp_plist_content(rtsp_message *message) {
+ char *reply = NULL;
+ // first, check if it has binary plist content
+ if (rtsp_message_contains_plist(message))
+ reply = plist_as_xml_text(plist_from_rtsp_content(message));
+ return reply;
+}
+
+#endif
+
+void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
+ rtsp_message *message) {
+ if (level > debug_level())
+ return;
+ if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list...
+ _debug(filename, linenumber, level, "%s", prompt);
+ _debug_print_msg_headers(conn, filename, linenumber, level, message);
+#ifdef CONFIG_AIRPLAY_2
+ char *plist_content = rtsp_plist_content(message);
+ if (plist_content) {
+ _debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--", message->contentlength, plist_content);
+ free(plist_content);
+ } else
+#endif
+ {
+ _debug(filename, linenumber, level, " No Content Plist. Content length: %u.",
+ message->contentlength);
+ if (message->contentlength > 0) {
+ _debug_print_buffer(filename, linenumber, level, message->content, message->contentlength);
+ }
+ }
+}
diff --git a/utilities/rtsp_message_utilities.h b/utilities/rtsp_message_utilities.h
new file mode 100644
index 00000000..3a42c031
--- /dev/null
+++ b/utilities/rtsp_message_utilities.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "player.h"
+#include "rtsp.h"
+
+rtsp_message *msg_init(void);
+int msg_handle_line(rtsp_message **pmsg, char *line);
+int msg_add_header(rtsp_message *msg, char *name, char *value);
+char *msg_get_header(rtsp_message *msg, char *name);
+
+void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
+ rtsp_message *message);
+
+#define debug_log_rtsp_message_conn(conn, level, prompt, message) \
+ _debug_log_rtsp_message(conn, __FILE__, __LINE__, level, prompt, message)
+
+#define debug_log_rtsp_message(level, prompt, message) \
+ _debug_log_rtsp_message(NULL, __FILE__, __LINE__, level, prompt, message)
+
+
+void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
+ rtsp_message *msg);
+#define debug_print_msg_headers(level, message) \
+ _debug_print_msg_headers(NULL, __FILE__, __LINE__, level, message)
+
+#define debug_print_msg_headers_conn(level, message) \
+ _debug_print_msg_headers(conn, __FILE__, __LINE__, level, message)
+
+
+#ifdef CONFIG_AIRPLAY_2
+int rtsp_message_contains_plist(rtsp_message *message);
+plist_t plist_from_rtsp_content(rtsp_message *message);
+#endif
\ No newline at end of file
From 9ab4fee926105bbdf30ca071b80bf2d6f09fc469 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 26 Apr 2026 10:54:47 +0100
Subject: [PATCH 04/67] Update release notes for version 5.0.4-dev-2 [skip ci]
Added new features, bug fixes, and other changes for version 5.0.4-dev-2.
---
RELEASENOTES-DEVELOPMENT.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 83b603a3..ba594792 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,16 @@
+Version 5.0.4-dev-2-g83de0207
+==
+**New Features**
+* AirPlay 2 Passwords. Until now, you could set a password only for Classic AirPlay. Now, setting a password is effective for both AirPlay 2 and Classic AirPlay.
+* Metadata multicast is now optional, so can, in principle, be omitted. This is still experimental.
+* DACP support is optional and can, in principle, be omitted. This is still experimental.
+
+**Bug Fixes and Stability Improvements**
+* Calls to some libraries have been made uncancellable in order to improve application stability when terminating or when pre-empting an active play session. These changes are not yet complete.
+
+**Other Changes**
+* Some rationalisation of source files is being undertaken.
+
Version 5.0.3-dev-4-g07ca1891
==
**Release**
From ec199d69ba5d12a7e00059a7ded6429e3596dd60 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 26 Apr 2026 11:00:17 +0100
Subject: [PATCH 05/67] Don't require plist library for Classic AirPlay rtsp
message utilities.
---
utilities/rtsp_message_utilities.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/utilities/rtsp_message_utilities.c b/utilities/rtsp_message_utilities.c
index 21393cd1..b3415fc4 100644
--- a/utilities/rtsp_message_utilities.c
+++ b/utilities/rtsp_message_utilities.c
@@ -1,15 +1,17 @@
#include
#include
#include
-#include
#include "rtsp_message_utilities.h"
#include "../rtsp.h"
#include "../common.h"
+#ifdef CONFIG_AIRPLAY_2
+#include
#ifdef HAVE_LIBPLIST_GE_2_3_0
#define plist_from_memory(plist_data, length, plist) \
plist_from_memory((plist_data), (length), (plist), NULL)
#endif
+#endif
// every time we want to retain or release a reference count, lock it with this
// if a reference count is read as zero, it means the it's being deallocated.
@@ -256,7 +258,7 @@ void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const i
} else
#endif
{
- _debug(filename, linenumber, level, " No Content Plist. Content length: %u.",
+ _debug(filename, linenumber, level, " Content length: %u.",
message->contentlength);
if (message->contentlength > 0) {
_debug_print_buffer(filename, linenumber, level, message->content, message->contentlength);
From c3443a13be183e0467190a6dfc574c210a25cacc Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 26 Apr 2026 11:22:27 +0100
Subject: [PATCH 06/67] Update version and add AirPlay 2 features [skip ci]
Updated version number and added new features related to AirPlay.
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index ba594792..5873b867 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,4 +1,4 @@
-Version 5.0.4-dev-2-g83de0207
+Version 5.0.4-dev-5-g936b8387
==
**New Features**
* AirPlay 2 Passwords. Until now, you could set a password only for Classic AirPlay. Now, setting a password is effective for both AirPlay 2 and Classic AirPlay.
From 1431a1c529051dd9e75c0ab3f53a96e9eae2eabc Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 27 Apr 2026 09:44:03 +0100
Subject: [PATCH 07/67] Update BUILD.md with warning about systemd-dev [skip
ci]
Added caution regarding installing systemd-dev on backported systems.
---
BUILD.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/BUILD.md b/BUILD.md
index 3fda1c08..5b16c4ad 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -83,7 +83,7 @@ If you are building classic Shairport Sync, the list of packages is shorter:
libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev \
libavutil-dev libavcodec-dev libavformat-dev
```
-Building on Ubuntu 24.10 or Debian 13 ("Trixie") and later – and possibly on other distributions – requires `systemd-dev`. It does no harm to attempt to install it – the install will simply fail if the package doesn't exist:
+Building on Ubuntu 24.10 or Debian 13 ("Trixie") and later – and possibly on other distributions – requires `systemd-dev`. Be very careful here if you are using backports -- there is a [report](https://github.com/mikebrady/shairport-sync/issues/2195#issuecomment-4325326002) that installing `systemd-dev` on a backported system can severely damage it:
```
# apt install --no-install-recommends systemd-dev # it's okay if this fails because the package doesn't exist
From 323fa1eee8a35553fadd2b627012641fc4a26817 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 27 Apr 2026 09:48:44 +0100
Subject: [PATCH 08/67] Update systemd-dev installation instructions in
BUILD.md [skip ci]
Clarified instructions for installing systemd-dev and added a dry-run command for safety.
---
BUILD.md | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/BUILD.md b/BUILD.md
index 5b16c4ad..4f52a56b 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -83,10 +83,13 @@ If you are building classic Shairport Sync, the list of packages is shorter:
libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev \
libavutil-dev libavcodec-dev libavformat-dev
```
-Building on Ubuntu 24.10 or Debian 13 ("Trixie") and later – and possibly on other distributions – requires `systemd-dev`. Be very careful here if you are using backports -- there is a [report](https://github.com/mikebrady/shairport-sync/issues/2195#issuecomment-4325326002) that installing `systemd-dev` on a backported system can severely damage it:
-
+Building on Ubuntu 24.10 or Debian 13 ("Trixie") and later – and possibly on other distributions – requires `systemd-dev`. Be very careful here if you are using backports -- there is a [report](https://github.com/mikebrady/shairport-sync/issues/2195#issuecomment-4325326002) that installing `systemd-dev` on a backported system can severely damage it.
```
-# apt install --no-install-recommends systemd-dev # it's okay if this fails because the package doesn't exist
+$ apt install --dry-run --no-install-recommends systemd-dev
+```
+Only if that looks okay, proceed to:
+```
+# apt install --no-install-recommends systemd-dev
```
### Fedora (Fedora 40)
From 68b6d4dfb7c82f6aa1dd3546d043d800320c52b0 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 27 Apr 2026 12:46:13 +0100
Subject: [PATCH 09/67] Fix missing configuration mapping parameter in
PulseAudio backend initialisation.
---
audio_pa.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/audio_pa.c b/audio_pa.c
index 998dbc27..a0c6afec 100644
--- a/audio_pa.c
+++ b/audio_pa.c
@@ -504,9 +504,9 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
config.pa_server = (char *)str;
}
- // get the default channel mapping setting basis -- native ("no") or pa ("yes").
+ // get the default channel mapping setting basis -- "alsa" or "pulseaudio".
- if (config_lookup_non_empty_string(config.cfg, "pulseaudio", &default_channel_layouts)) {
+ if (config_lookup_non_empty_string(config.cfg, "pulseaudio.default_channel_layouts", &default_channel_layouts)) {
if ((strcasecmp(default_channel_layouts, "alsa") == 0) ||
(strcasecmp(default_channel_layouts, "pulseaudio") == 0)) {
debug(1, "pulseaudio default_channel_layouts setting: \"%s\".", default_channel_layouts);
From 280e30ce788e302f50b1a21fa4a44dfca0008809 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 27 Apr 2026 13:18:00 +0100
Subject: [PATCH 10/67] Update release notes for version 5.0.4-dev-9 [skip ci]
Added bug fix for PulseAudio backend initialization issue.
---
RELEASENOTES-DEVELOPMENT.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 5873b867..3d65b8fd 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,8 @@
+Version 5.0.4-dev-9-g68b6d4df
+==
+**Bug Fix**
+* Fixed missing configuration mapping parameter in PulseAudio backend initialisation. This bug would prevent the PulseAudio backend from initialising, preventing Shairport Sync from working.
+
Version 5.0.4-dev-5-g936b8387
==
**New Features**
From 41a5cee6545cd5063a7123d4f7371f6d39925c0a Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 27 Apr 2026 13:18:57 +0100
Subject: [PATCH 11/67] 5.0.5-dev
---
configure.ac | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/configure.ac b/configure.ac
index dbf3901f..e066b94f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
-AC_INIT([shairport-sync], [5.0.4-dev], [4265913+mikebrady@users.noreply.github.com])
+AC_INIT([shairport-sync], [5.0.5-dev], [4265913+mikebrady@users.noreply.github.com])
: ${CFLAGS="-O3"}
: ${CXXFLAGS="-O3"}
AM_INIT_AUTOMAKE([subdir-objects])
From 052fd10acc75af192f8ac2e7d9b474bb02681d48 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 27 Apr 2026 13:23:40 +0100
Subject: [PATCH 12/67] Update release notes for version 5.0.5-dev-2 [skip ci]
Updated version number to 5.0.5-dev-2-g2400aae4 and clarified it as effectively 5.0.4-dev-9-g68b6d4df.
---
RELEASENOTES-DEVELOPMENT.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 3d65b8fd..b52843fa 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,7 @@
+Version 5.0.5-dev-2-g2400aae4
+==
+This is effectively 5.0.4-dev-9-g68b6d4df.
+
Version 5.0.4-dev-9-g68b6d4df
==
**Bug Fix**
From 4f2a64fa39e1ab808066d575db271b54a8009fb9 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 1 May 2026 16:09:02 +0100
Subject: [PATCH 13/67] Suppress am unused_result warning.
---
ap2_buffered_audio_processor.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ap2_buffered_audio_processor.c b/ap2_buffered_audio_processor.c
index 09581ded..d8a6bada 100644
--- a/ap2_buffered_audio_processor.c
+++ b/ap2_buffered_audio_processor.c
@@ -327,7 +327,7 @@ void *rtp_buffered_audio_processor(void *arg) {
finished = 1;
} else if (nread < 0) {
char errorstring[1024];
- strerror_r(errno, (char *)errorstring, sizeof(errorstring));
+ (void) !strerror_r(errno, (char *)errorstring, sizeof(errorstring)); // (void) ! to suppress unused response warning
debug(1, "error in rtp_buffered_audio_processor %d: \"%s\". Could not recv a data_len .",
errno, errorstring);
finished = 1;
From eb82fa32b7b85d703c9a7c5d2f3379333c7bb247 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 1 May 2026 16:09:58 +0100
Subject: [PATCH 14/67] Fix an error if built without metadata support
---
rtsp.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/rtsp.c b/rtsp.c
index 025c2900..d167503c 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -2023,7 +2023,9 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req,
// we are not going to load the plist here because we don't wamt
// to incur the memory and processing cost. So we'll just send it to the
// metadata handling code and it can be dealt with there.
+#ifdef CONFIG_METADATA
send_metadata('ssnc', 'copl', req->content, req->contentlength, req, 1); // COmmand PList (and release 'req' afterwards)
+#endif
/*
plist_t command_dict = NULL;
plist_from_memory(req->content, req->contentlength, &command_dict);
From 0e4bb2a8ff2a4dbb1e7e1930c691c1495ce1e1ff Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Tue, 5 May 2026 12:02:51 +0100
Subject: [PATCH 15/67] Quieten a compiler warning on Ubuntu 26.04.
---
mdns_avahi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mdns_avahi.c b/mdns_avahi.c
index fd0deedc..d7ecef6d 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -168,7 +168,7 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
debug(3, "(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_");
+ const char *dacpid = strstr(name, "iTunes_Ctrl_");
if (dacpid) {
dacpid += strlen("iTunes_Ctrl_");
while (*dacpid == '0')
From 3a1bfd718a8f076dace8bf8b77ccb8e9a2844810 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 6 May 2026 15:39:55 +0100
Subject: [PATCH 16/67] Update Docker images to use Alpine 3.23.4 and FFmpeg
8.1
---
docker/Dockerfile | 11 +++++------
docker/classic/Dockerfile | 5 ++---
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 1cda43e3..dcae96ca 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,7 +1,7 @@
ARG NQPTP_BRANCH=main
-ARG FFMPEG_BRANCH=release/7.1
+ARG FFMPEG_BRANCH=release/8.1
ARG SHAIRPORT_SYNC_BRANCH=.
-ARG ALPINE_VERSION=20250108
+ARG ALPINE_VERSION=3.23.4
##### FFMPEGLITE #####
FROM alpine:$ALPINE_VERSION AS ffmpeglite
@@ -28,10 +28,9 @@ RUN ./configure \
--disable-network \
--disable-iamf \
--disable-pixelutils \
- --disable-postproc \
--enable-decoder=alac \
--enable-decoder=aac
-RUN make -j $(nproc)
+RUN make
RUN make install
##### FFMPEGLITE END #####
@@ -51,7 +50,7 @@ RUN git clone --depth=1 -b "$NQPTP_BRANCH" https://github.com/mikebrady/nqptp
WORKDIR /nqptp
RUN autoreconf -i
RUN ./configure
-RUN make -j $(nproc)
+RUN make
WORKDIR /
##### NQPTP END #####
@@ -102,7 +101,7 @@ RUN ../configure --sysconfdir=/etc --with-alsa --with-pulseaudio --with-soxr --w
--with-airplay-2 --with-metadata --with-dummy --with-pipe --with-dbus-interface \
--with-stdout --with-mpris-interface --with-mqtt-client \
--with-convolution --with-pipewire
-RUN make -j $(nproc)
+RUN make
RUN DESTDIR=install make install
WORKDIR /
##### SPS END #####
diff --git a/docker/classic/Dockerfile b/docker/classic/Dockerfile
index a71a41c0..67d57929 100644
--- a/docker/classic/Dockerfile
+++ b/docker/classic/Dockerfile
@@ -1,8 +1,8 @@
# Classic Shairport-Sync build. Note: the only decoder in FFmpeg is ALAC.
ARG SHAIRPORT_SYNC_BRANCH=.
-ARG FFMPEG_BRANCH=release/7.1
-ARG ALPINE_VERSION=20250108
+ARG FFMPEG_BRANCH=release/8.1
+ARG ALPINE_VERSION=3.23.4
##### FFMPEGLITE #####
FROM alpine:$ALPINE_VERSION AS ffmpeglite
@@ -29,7 +29,6 @@ RUN ./configure \
--disable-network \
--disable-iamf \
--disable-pixelutils \
- --disable-postproc \
--enable-decoder=alac
RUN make
RUN make install
From 281fb34d579451938353553a5ca47a4dc204a3cf Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 7 May 2026 08:51:55 +0100
Subject: [PATCH 17/67] Update release notes for version 5.0.5-dev-7 [skip ci]
Updated Docker images to use Alpine 3.23.4 and FFmpeg 8.1. Fixed benign compiler warnings from the updated compiler in Ubuntu 26.04.
---
RELEASENOTES-DEVELOPMENT.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index b52843fa..bfc75f89 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,12 @@
+Version 5.0.5-dev-7-g3a1bfd71
+==
+**Docker Updates**
+* Update Docker images to use Alpine 3.23.4 (up from build "20250108").
+* Update Docker images to use FFmpeg 8.1 (up from 7.1).
+
+**Other Updates**
+* Fix some benign compiler warnings coming from the updated compiler in Ubuntu 26.04.
+
Version 5.0.5-dev-2-g2400aae4
==
This is effectively 5.0.4-dev-9-g68b6d4df.
From a50eed043e45027514cf319280fba925e742e7ce Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 7 May 2026 14:06:00 +0100
Subject: [PATCH 18/67] Attempt to allow up to 31 channels of output, up from a
limit of 8. Modify the mixdown setting so that "auto" only mixes downwards,
e.g. 7.1 to 5.1, and never upwards, e.g. stereo to 5.1. Upmixing is not
functional anyway, as the upmixing matrices are all such that the input
channels are copied to their equivalent output channels and other output
channels remain silent.
---
audio.c | 4 ++--
common.h | 8 +++++++-
player.c | 29 +++++++++++++++++++++++++----
3 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/audio.c b/audio.c
index a703d660..90ca8fac 100644
--- a/audio.c
+++ b/audio.c
@@ -458,7 +458,7 @@ uint32_t get_channel_settings(const char *stanza_name, const char *setting_name)
for (i = 0; i < channel_counts_count; i++) {
debug(3, "channel count setting %d: %d.", i, channel_counts[i]);
- if ((channel_counts[i] >= 1) && (channel_counts[i] <= 8)) {
+ if ((channel_counts[i] >= 1) && (channel_counts[i] <= SPS_GREATEST_CHANNEL_COUNT)) {
#ifdef CONFIG_FFMPEG
channel_set |= (1 << channel_counts[i]);
#else
@@ -492,7 +492,7 @@ uint32_t get_channel_settings(const char *stanza_name, const char *setting_name)
char numbuf[32];
unsigned int c;
uint32_t t_channel_set = channel_set;
- for (c = 0; c <= 8; c++) {
+ for (c = 0; c <= SPS_GREATEST_CHANNEL_COUNT; c++) {
if ((t_channel_set & (1 << c)) != 0) {
snprintf(numbuf, sizeof(numbuf) - 1, "%u", c);
snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", numbuf);
diff --git a/common.h b/common.h
index a7a0604d..e18af0f9 100644
--- a/common.h
+++ b/common.h
@@ -146,7 +146,13 @@ typedef enum {
// these sets omit the _UNKNOWN, _AUTO and _ILLEGAL values
#define SPS_FORMAT_SET (((1 << (SPS_FORMAT_HIGHEST_NATIVE + 1)) - 1) - (1 << SPS_FORMAT_UNKNOWN))
#define SPS_RATE_SET (((1 << (SPS_RATE_HIGHEST + 1)) - 1) - (1 << SPS_RATE_UNKNOWN))
-#define SPS_CHANNEL_SET (((1 << (8 + 1)) - 1) - (1 << 0)) // channels 1 to 8, not 0-based!
+
+// in SPS_CHANNEL_SET, bit 0 set means a channel set of no channels, bit 1 set means a channel set of 1 channel
+// and so on to bit 31 meaning a channel set of 31 channels. We want to consider all possible channel sets
+// apart from channel set 0.
+#define SPS_GREATEST_CHANNEL_COUNT 31 // should be 32 to be fully in line with ALSA limits
+#define SPS_CHANNEL_SET 0xFFFFFFFE // channel sets 31 to 1, but no channel set 0
+// #define SPS_CHANNEL_SET (((1 << (SPS_GREATEST_CHANNEL_COUNT + 1)) - 1) - (1 << 0)) // channels 1 to 31, not 0-based!
#ifndef CONFIG_AIRPLAY_2
#define SPS_FORMAT_NON_FFMPEG_SET SPS_FORMAT_SET
diff --git a/player.c b/player.c
index 2d8b9081..3ccd33de 100644
--- a/player.c
+++ b/player.c
@@ -656,24 +656,43 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
// set the resampler's channel layout either automatically or use the
// setting that has been given
+ // We impose the rule that, if the mixdown setting is "auto",
+ // we will not upmix if the number of output channels available
+ // is greater than the number of channels needed.
+
+ // To do an upmix, you will have to specify the upmix target, e.g. "7.1"
+ // in the mixdown setting.
+
+ // NOTE: upmixing at present, by default, simply copies the input channels to their
+ // equivalents in the output channel set. All the other channels are
+ // left alone.
+
#if LIBAVUTIL_VERSION_MAJOR >= 57
{
AVChannelLayout input_channel_layout;
av_channel_layout_from_mask(&input_channel_layout, input_layout);
+ int input_channel_count = input_channel_layout.nb_channels;
av_opt_set_chlayout(swr, "in_chlayout", &input_channel_layout, 0);
av_channel_layout_uninit(&input_channel_layout);
AVChannelLayout output_channel_layout;
if (config.mixdown_enable != 0) {
if (config.mixdown_channel_layout == 0) {
- av_channel_layout_default(&output_channel_layout,
+ if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) > (unsigned) input_channel_count) {
+ av_channel_layout_from_mask(&output_channel_layout, input_layout);
+ } else {
+ av_channel_layout_default(&output_channel_layout,
CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
+ }
} else {
av_channel_layout_from_mask(&output_channel_layout, config.mixdown_channel_layout);
}
} else {
av_channel_layout_from_mask(&output_channel_layout, output_layout);
}
+ char layout_desc[2048];
+ av_channel_layout_describe(&output_channel_layout, layout_desc, sizeof(layout_desc));
+ debug(1,"output channel layout: \"%s\"", layout_desc);
av_opt_set_chlayout(swr, "out_chlayout", &output_channel_layout, 0);
av_channel_layout_uninit(&output_channel_layout);
}
@@ -681,8 +700,10 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
av_opt_set_int(swr, "in_channel_layout", input_layout, 0);
if (config.mixdown_enable != 0) {
if (config.mixdown_channel_layout == 0) {
- output_layout =
- av_get_default_channel_layout(CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
+ if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) < av_get_channel_layout_nb_channels(input_layout)) {
+ output_layout =
+ av_get_default_channel_layout(CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
+ } // else leave output_layout as it was: the sames as the input_layout.
} else {
output_layout = config.mixdown_channel_layout;
}
@@ -962,7 +983,7 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
if (output_configuration_changed != 0) {
char channel_mapping_list[256] = "";
- for (c = 0; c < 8; c++) {
+ for (c = 0; c < SPS_GREATEST_CHANNEL_COUNT; c++) {
if ((output_channels[c].allocated != 0) &&
(conn->output_channel_to_resampler_channel_map[c] != silent_channel_index)) {
char channel_mapping[32] = "";
From 4b020a6b22eac8d11c4363eccbe638677627a036 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 7 May 2026 14:18:55 +0100
Subject: [PATCH 19/67] Clarify the mixdown comments.
---
player.c | 35 ++++++++++++++++++++---------------
1 file changed, 20 insertions(+), 15 deletions(-)
diff --git a/player.c b/player.c
index 3ccd33de..64671819 100644
--- a/player.c
+++ b/player.c
@@ -652,20 +652,25 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
av_opt_set_sample_fmt(swr, "in_sample_fmt", input_format, 0);
- // remember that if mixdown is enabled,
- // set the resampler's channel layout either automatically or use the
- // setting that has been given
+ // If mixdown is enabled, set the resampler's channel layout,
+ // either automatically based on the number of ooutput channels,
+ // or by using the setting that has been given.
- // We impose the rule that, if the mixdown setting is "auto",
- // we will not upmix if the number of output channels available
- // is greater than the number of channels needed.
-
- // To do an upmix, you will have to specify the upmix target, e.g. "7.1"
- // in the mixdown setting.
+ // Upmixing will not be done if the setting is "auto".
- // NOTE: upmixing at present, by default, simply copies the input channels to their
- // equivalents in the output channel set. All the other channels are
- // left alone.
+ // Similarly, on the "auto" setting, downmixing will be done
+ // only if the number of output channels available
+ // is strictly less than the number of input channels.
+
+ // If the number of output channels equals the number of input channels,
+ // no mixing is done on the "auto" setting.
+
+ // To do an upmix or a custom downmix, specify
+ // the target format, e.g. "7.1" in the mixdown setting.
+
+ // NOTE: upmixing, by default, simply copies the input channels to their
+ // equivalents in the output channels. All other channels are
+ // left silent.
#if LIBAVUTIL_VERSION_MAJOR >= 57
{
@@ -678,11 +683,11 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
AVChannelLayout output_channel_layout;
if (config.mixdown_enable != 0) {
if (config.mixdown_channel_layout == 0) {
- if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) > (unsigned) input_channel_count) {
- av_channel_layout_from_mask(&output_channel_layout, input_layout);
- } else {
+ if (CHANNELS_FROM_ENCODED_FORMAT(output_configuration) < (unsigned) input_channel_count) {
av_channel_layout_default(&output_channel_layout,
CHANNELS_FROM_ENCODED_FORMAT(output_configuration));
+ } else {
+ av_channel_layout_from_mask(&output_channel_layout, input_layout);
}
} else {
av_channel_layout_from_mask(&output_channel_layout, config.mixdown_channel_layout);
From 2028639a03f541783e33298bc67479c2fb31ccb2 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Thu, 7 May 2026 14:39:21 +0100
Subject: [PATCH 20/67] Update release notes for version 5.0.5-dev-10 [skip ci]
Added enhancements for audio channel support and upmixing details.
---
RELEASENOTES-DEVELOPMENT.md | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index bfc75f89..4fc30de8 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,7 +1,23 @@
+Version 5.0.5-dev-10-g4b020a6b
+==
+**Enhancements**
+* Experimentally allow up to 31 audio channels. This is up from a previous maximum of eight. The ALSA system allows up to 32 channels, one more than Shairport Sync can handle, so let's see...
+
+ Unfortunately, this can not be tested due to a lack of suitable output devices, so if anyone can test it out, it would be appreciated.
+
+ Thanks to [24fpsDaVinci](https://github.com/24fpsDaVinci) for the [issue](https://github.com/mikebrady/shairport-sync/issues/2199).
+
+**Upmixing**
+* "Upmixing" adds channels to an audio stream, e.g. you could upmix stereo (two channels) to 5.1 surround sound (six channels) by copying and blending the stereo channels to drive the extra surround channels. The FFmpeg resampler used by Shairport Sync does implement upmixing, but by default it just copies the source channels to their equivalent channels in the output, leaving the extra channels silent.
+
+ So, while Shairport Sync does technically implement upmixing, it makes no audible difference. Maybe in the future, upmixing matrices will be added, but it is not a priority just now.
+
+ Thanks to [iliaspapan](https://github.com/iliaspapan) for [enquiring](https://github.com/mikebrady/shairport-sync/issues/2194) about this.
+
Version 5.0.5-dev-7-g3a1bfd71
==
**Docker Updates**
-* Update Docker images to use Alpine 3.23.4 (up from build "20250108").
+* Update Docker images to use Alpine 3.23.4 (up from build 20250108).
* Update Docker images to use FFmpeg 8.1 (up from 7.1).
**Other Updates**
From 9d102497354aeb75f61df48a8b48f88f15cc55e2 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 8 May 2026 07:51:51 +0100
Subject: [PATCH 21/67] Update help information about password and stuffing
modes. Quieten some debug messages.
---
rtsp.c | 6 +++---
shairport.c | 9 +++++----
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/rtsp.c b/rtsp.c
index d167503c..7bc90389 100644
--- a/rtsp.c
+++ b/rtsp.c
@@ -1692,10 +1692,10 @@ void handle_pair_pin_start(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
free(conn->ap2_client_name);
conn->ap2_client_name = strdup(hdr);
}
- debug(1, "Connection %d from \"%s\": handle_pair_pin_start, Content-Length %d", conn->connection_number,
+ debug(4, "Connection %d from \"%s\": handle_pair_pin_start, Content-Length %d", conn->connection_number,
conn->ap2_client_name,
req->contentlength);
- debug_log_rtsp_message_conn(conn, 1, "handle_pair_pin_start", req);
+ debug_log_rtsp_message_conn(conn, 4, "handle_pair_pin_start", req);
uint8_t *body = NULL;
size_t body_len = 0;
@@ -1704,7 +1704,7 @@ void handle_pair_pin_start(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
resp->contentlength = body_len;
if (body != NULL)
msg_add_header(resp, "Content-Type", "application/octet-stream");
- debug_log_rtsp_message(1, "pair-pin-start response", resp);
+ debug_log_rtsp_message(4, "pair-pin-start response", resp);
}
void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
diff --git a/shairport.c b/shairport.c
index 695c1f79..17c1ce0a 100644
--- a/shairport.c
+++ b/shairport.c
@@ -326,14 +326,15 @@ void usage(char *progname) {
printf(" -v, --verbose Print debug information; -v some; -vv more; -vvv lots -- generally too much.\n");
printf(" -c, --configfile=FILE Read configuration settings from FILE. Default is %s.\n", configuration_file_path);
printf(" -a, --name=NAME Set service name. Default is the hostname with first letter capitalised.\n");
- printf(" --password=PASSWORD Require PASSWORD to connect. Default is no password. (Classic AirPlay only.)\n");
+ printf(" --password=PASSWORD Require PASSWORD to connect. Default is no password.\n");
printf(" -p, --port=PORT Set RTSP listening port. Default 5000; 7000 for AirPlay 2.\n");
printf(" -L, --latency=FRAMES [Deprecated] Set the latency for audio sent from an unknown device.\n");
printf(" The default is to set it automatically.\n");
printf(" -S, --stuffing=MODE Set how to adjust current latency to match desired latency, where:\n");
- printf(" \"basic\" inserts or deletes audio frames from packet frames with low processor overhead, and\n");
- printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- moderate processor overhead.\n");
- printf(" The default \"auto\" setting chooses basic or soxr depending on processor capability.\n");
+ printf(" \"vernier\" recodes a packet of frames to a new packet containing more or fewer frames. Recommended for low powered devices;\n");
+ printf(" \"basic\" inserts or deletes audio frames from packet frames with low processor overhead; and\n");
+ printf(" \"soxr\" uses libsoxr to minimally resample packet frames -- moderate floating point processor overhead.\n");
+ printf(" The default \"auto\" setting chooses vernier or soxr depending on processor capability.\n");
printf(" The \"soxr\" option is only available if built with soxr support.\n");
printf(" -B, --on-start=PROGRAM Run PROGRAM when playback is about to begin.\n");
printf(" -E, --on-stop=PROGRAM Run PROGRAM when playback has ended.\n");
From fd71a15dc863924b0e1b7f9c8060b592a9a99ef2 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Fri, 8 May 2026 07:58:11 +0100
Subject: [PATCH 22/67] Update release notes for version 5.0.5-dev-12 [skip ci]
Added details about password functionality for AirPlay and updated command line settings information.
---
RELEASENOTES-DEVELOPMENT.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 4fc30de8..8cb1b832 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,9 @@
+Version 5.0.5-dev-12-g9d102497
+==
+**Enhancements**
+* Setting a password now works with AirPlay 2 as well as Classic AirPlay. Use the `password` setting in the `general` sectioon of the configuration file or the `--password=""` command line option to set a password.
+* Update information about the password and interpolation (aka "stuffing") command line settings.
+
Version 5.0.5-dev-10-g4b020a6b
==
**Enhancements**
From d30493cbc487e124e89c73aff445adb2bbd12937 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 9 May 2026 17:32:30 +0100
Subject: [PATCH 23/67] Quieten a debug message.
---
player.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/player.c b/player.c
index 64671819..b4e505ce 100644
--- a/player.c
+++ b/player.c
@@ -697,7 +697,7 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
}
char layout_desc[2048];
av_channel_layout_describe(&output_channel_layout, layout_desc, sizeof(layout_desc));
- debug(1,"output channel layout: \"%s\"", layout_desc);
+ // debug(1,"output channel layout: \"%s\"", layout_desc);
av_opt_set_chlayout(swr, "out_chlayout", &output_channel_layout, 0);
av_channel_layout_uninit(&output_channel_layout);
}
From 3cbc4b18997d7d0c30f496ca084c526fa4532263 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 9 May 2026 17:44:37 +0100
Subject: [PATCH 24/67] Fix the incorrect signalling of the DACP port to the
DACP monitor, send 'dapo' port info as text and send a token of "0" when the
port becomes inaccessible.
---
mdns_avahi.c | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/mdns_avahi.c b/mdns_avahi.c
index d7ecef6d..10d89d92 100644
--- a/mdns_avahi.c
+++ b/mdns_avahi.c
@@ -90,8 +90,9 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event,
const char *name, const char *type, const char *domain,
__attribute__((unused)) const char *host_name,
-#ifdef CONFIG_METADATA
- __attribute__((unused)) const AvahiAddress *address, uint16_t lport,
+#if defined(CONFIG_METADATA) || defined(CONFIG_DACP_CLIENT)
+ __attribute__((unused)) const AvahiAddress *address,
+ uint16_t lport,
#else
__attribute__((unused)) const AvahiAddress *address,
__attribute__((unused)) uint16_t lport,
@@ -119,14 +120,14 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn
while (*dacpid == '0')
dacpid++; // skip any leading zeroes
if (strcmp(dacpid, dbs->dacp_id) == 0) {
- debug(3, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, port);
+ debug(1, "resolve_callback: client dacp_id \"%s\" dacp port: %u.", dbs->dacp_id, lport);
#ifdef CONFIG_DACP_CLIENT
- dacp_monitor_port_update_callback(dacpid, port);
+ dacp_monitor_port_update_callback(dacpid, lport);
#endif
#ifdef CONFIG_METADATA
- uint32_t nport = lport;
- nport = htonl(nport);
- send_ssnc_metadata('dapo', (const char *)&nport, sizeof(nport), 0);
+ char port_in_chars[16];
+ snprintf(port_in_chars, 7, "%u", lport);
+ send_ssnc_metadata('dapo', port_in_chars, strlen(port_in_chars), 0);
#endif
}
} else {
@@ -166,19 +167,23 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, Avah
break;
case AVAHI_BROWSER_REMOVE:
debug(3, "(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;
const char *dacpid = strstr(name, "iTunes_Ctrl_");
if (dacpid) {
dacpid += strlen("iTunes_Ctrl_");
while (*dacpid == '0')
dacpid++; // skip any leading zeroes
- if ((dbs->dacp_id) && (strcmp(dacpid, dbs->dacp_id) == 0))
+ if ((dbs->dacp_id) && (strcmp(dacpid, dbs->dacp_id) == 0)) {
+#ifdef CONFIG_DACP_CLIENT
dacp_monitor_port_update_callback(dbs->dacp_id, 0); // say the port is withdrawn
+#endif
+#ifdef CONFIG_METADATA
+ send_ssnc_metadata('dapo', "0", strlen("0"), 0);
+#endif
+ }
} else {
debug(1, "Browse callback: Can't see a DACP string in a DACP Record!");
}
-#endif
break;
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED:
From 003e365aeeeed2bd09268653fac0e9419c6ad763 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 9 May 2026 18:13:03 +0100
Subject: [PATCH 25/67] Update release notes for version 5.0.5-dev-15 [skip ci]
Added release notes for version 5.0.5-dev-15, detailing bug fixes for Classic AirPlay Remote Control and enhancements for password settings.
---
RELEASENOTES-DEVELOPMENT.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index 8cb1b832..c0392edb 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,3 +1,14 @@
+Version 5.0.5-dev-15-g3cbc4b18
+==
+**Bug Fixes – Classic AirPlay Remote Control**
+* Fixed a significant Classic AirPlay Remote Control bug: in Classic AirPlay mode, the DACP command port was being incorrectly reported to the Remote Control system, causing it not to function!
+
+ This has been fixed, and limited remote control has been restored for Classic AirPlay. Check the D-Bus interface for the [`RemoteControl`](org.gnome.ShairportSync.xml#L44) suite -- most all of these now work. The [`AdvancedRemoteControl`](org.gnome.ShairportSync.xml#L72) suite does not work, and seems to be no longer supported by any Classic AirPlay clients.
+
+ [MQTT remote control commands](mqtt.c#L68) and some [MPRIS commands](org.mpris.MediaPlayer2.xml#L16) should now also work, although the MPRIS interface is quite incomplete.
+
+* The `dapo` metadata token now correctly reports the DACP port as a character string, and reports a DACP port of "0" when the DACP port is or becomes invalid.
+
Version 5.0.5-dev-12-g9d102497
==
**Enhancements**
From 5175c19a0fc4d21dbca218e4d486ed1b1172890a Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sat, 9 May 2026 18:15:56 +0100
Subject: [PATCH 26/67] Fix typo in RELEASENOTES-DEVELOPMENT.md [skip ci]
---
RELEASENOTES-DEVELOPMENT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index c0392edb..c31edee4 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -12,7 +12,7 @@ Version 5.0.5-dev-15-g3cbc4b18
Version 5.0.5-dev-12-g9d102497
==
**Enhancements**
-* Setting a password now works with AirPlay 2 as well as Classic AirPlay. Use the `password` setting in the `general` sectioon of the configuration file or the `--password=""` command line option to set a password.
+* Setting a password now works with AirPlay 2 as well as Classic AirPlay. Use the `password` setting in the `general` section of the configuration file or the `--password=""` command line option to set a password.
* Update information about the password and interpolation (aka "stuffing") command line settings.
Version 5.0.5-dev-10-g4b020a6b
From 2ee523231fa4163eeff6cf5a61fe63356fd50759 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 10 May 2026 08:18:26 +0100
Subject: [PATCH 27/67] Revise bug fix details for Classic AirPlay Remote
Control [skip ci]
Updated the bug fix section for Classic AirPlay Remote Control to clarify the issue and its resolution. Added additional details about the functionality of the AdvancedRemoteControl suite.
---
RELEASENOTES-DEVELOPMENT.md | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md
index c31edee4..bb3349bc 100644
--- a/RELEASENOTES-DEVELOPMENT.md
+++ b/RELEASENOTES-DEVELOPMENT.md
@@ -1,13 +1,15 @@
Version 5.0.5-dev-15-g3cbc4b18
==
-**Bug Fixes – Classic AirPlay Remote Control**
-* Fixed a significant Classic AirPlay Remote Control bug: in Classic AirPlay mode, the DACP command port was being incorrectly reported to the Remote Control system, causing it not to function!
+**Bug Fixes – Classic AirPlay Remote Control (Updated)**
+* A significant Classic AirPlay Remote Control bug was found: in Classic AirPlay mode, the DACP command port was being incorrectly reported to the Remote Control system, causing it not to function!
+
+ This has been fixed, and remote control has been restored for Classic AirPlay. Please check the D-Bus interface for the [`RemoteControl`](org.gnome.ShairportSync.xml#L44) suite -- most all of these now work.
+The [`AdvancedRemoteControl`](org.gnome.ShairportSync.xml#L72) suite also works with the macOS Music App but only in Classic AirPlay.
- This has been fixed, and limited remote control has been restored for Classic AirPlay. Check the D-Bus interface for the [`RemoteControl`](org.gnome.ShairportSync.xml#L44) suite -- most all of these now work. The [`AdvancedRemoteControl`](org.gnome.ShairportSync.xml#L72) suite does not work, and seems to be no longer supported by any Classic AirPlay clients.
[MQTT remote control commands](mqtt.c#L68) and some [MPRIS commands](org.mpris.MediaPlayer2.xml#L16) should now also work, although the MPRIS interface is quite incomplete.
-* The `dapo` metadata token now correctly reports the DACP port as a character string, and reports a DACP port of "0" when the DACP port is or becomes invalid.
+* The `dapo` metadata token now correctly reports the DACP port as a character string, and reports a DACP port of `"0"` when the DACP port is or becomes invalid.
Version 5.0.5-dev-12-g9d102497
==
From 4a4c887bdcc4a0134932084d1278391bbafca816 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 10 May 2026 08:25:16 +0100
Subject: [PATCH 28/67] Update D-Bus documentation for remote control features
[skip ci]
Clarified that Remote Control and Advanced Remote Control only work on Classic Airplay.
---
documents/D-Bus.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/documents/D-Bus.md b/documents/D-Bus.md
index 012e4f65..654fea71 100644
--- a/documents/D-Bus.md
+++ b/documents/D-Bus.md
@@ -1,6 +1,6 @@
## Shairport Sync D-Bus Interface
-Shairport Sync can have a D-Bus interface, which can be used to control aspects of its operation and get status information from it.
+Shairport Sync can have a D-Bus interface, which can be used to control aspects of its operation and get status information from it. For Classic AirPlay only, remote control commands, for example `play`, `pause` and volume control, can be sent to the player.
To include the D-Bus interface at build time, add the `--with-dbus-interface` flag at the `./configure…` stage. When the D-Bus interface is included in Shairport Sync, its Version String will include the term `dbus`, for instance:
```
@@ -143,7 +143,7 @@ The examples below are based on Shairport Sync running as a `system` service and
#### Remote Control
Remote Control commands are sent as requests to the player (iOS, iTunes, macOS Music, etc.). Different versions of the players implement different subsets of the following commands.
-**Note:** Unfortunately, at this time -- early 2026 -- these requests are ignored, so remote control doesn't work.
+**Note:** Remote Control only works on Classic Airplay
* Check if Remote Control is available:
```
@@ -162,7 +162,7 @@ Remote Control commands are sent as requests to the player (iOS, iTunes, macOS M
#### Advanced Remote Control
Some commands and properties are accessible only through the `AdvancedRemoteControl` interface.
-**Note:** Unfortunately, at this time -- early 2026 -- these requests are ignored, so advanced remote control doesn't work.
+**Note:** Advanced Remote Control only works on Classic Airplay and has only been observed to work with the macOS Music app.
* Check if Advanced Remote Control is available:
```
From 48559c45dfe91bb00fbdd7ea3c8d2ee152e55ac8 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 10 May 2026 08:25:44 +0100
Subject: [PATCH 29/67] Clarify D-Bus interface usage in Shairport Sync [skip
ci]
Reorganize content for clarity regarding the D-Bus interface.
---
documents/D-Bus.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/documents/D-Bus.md b/documents/D-Bus.md
index 654fea71..99c5ef3e 100644
--- a/documents/D-Bus.md
+++ b/documents/D-Bus.md
@@ -1,6 +1,8 @@
## Shairport Sync D-Bus Interface
-Shairport Sync can have a D-Bus interface, which can be used to control aspects of its operation and get status information from it. For Classic AirPlay only, remote control commands, for example `play`, `pause` and volume control, can be sent to the player.
+Shairport Sync can have a D-Bus interface, which can be used to control aspects of its operation and get status information from it.
+
+For Classic AirPlay only, remote control commands, for example `play`, `pause` and volume control, can be sent to the player.
To include the D-Bus interface at build time, add the `--with-dbus-interface` flag at the `./configure…` stage. When the D-Bus interface is included in Shairport Sync, its Version String will include the term `dbus`, for instance:
```
From f0d40ee2a81bb82b6ab74f964006912195f77909 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 10 May 2026 08:53:12 +0100
Subject: [PATCH 30/67] Clarify MPRIS and D-Bus interface descriptions [skip
ci]
Updated descriptions for MPRIS and D-Bus interfaces to clarify functionality.
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index f03b97df..55beabb4 100644
--- a/README.md
+++ b/README.md
@@ -24,8 +24,8 @@ Shairport Sync does not support AirPlay video or photo streaming.
* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in macOS and iOS.
* An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), a popular protocol for Inter Process Communication, Machine-to-Machine, Internet of Things and Home Automation projects. The interface provides access to metadata and artwork, and has limited remote control.
* Digital Signal Processing facilities – please see the [DSP Wiki Page Guide](https://github.com/mikebrady/shairport-sync/wiki/Digital-Signal-Processing-with-Shairport-Sync). (Thanks to [Yann Pomarède](https://github.com/yannpom) for the code and to [Paul Wieland](https://github.com/PaulWieland) for the guide.)
-* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/)-like interface, partially complete and very functional, including access to metadata and artwork, and limited remote control.
-* A native D-Bus interface, including access to metadata and artwork, limited remote control and system settings.
+* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/)-like interface, partially complete and very functional, including access to metadata and artwork, and partial remote control for Classic AirPlay.
+* A native D-Bus interface, including access to metadata, artwork and system settings. Remote control is also available for Classic AirPlay/.
* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range.
* Flexible output rates, formats and channels with built-in transcoding.
From 0bd9271ea90f17d9d0993a6d5d9232e86e6cba92 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Sun, 10 May 2026 09:02:07 +0100
Subject: [PATCH 31/67] Update MQTT and remote control features in README [skip
ci]
---
README.md | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 55beabb4..2c45b64a 100644
--- a/README.md
+++ b/README.md
@@ -22,12 +22,13 @@ Shairport Sync does not support AirPlay video or photo streaming.
# Features
* Outputs AirPlay audio to [ALSA](https://www.alsa-project.org/wiki/Main_Page), [sndio](http://www.sndio.org), [PipeWire](https://pipewire.org), [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/), to a unix pipe or to `STDOUT`. It also has limited support for [libao](https://xiph.org/ao/).
* Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in macOS and iOS.
-* An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), a popular protocol for Inter Process Communication, Machine-to-Machine, Internet of Things and Home Automation projects. The interface provides access to metadata and artwork, and has limited remote control.
+* An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), a popular protocol for Inter Process Communication, Machine-to-Machine, Internet of Things and Home Automation projects. The interface provides access to metadata and artwork, with remote control for Classic AirPlay clients.
* Digital Signal Processing facilities – please see the [DSP Wiki Page Guide](https://github.com/mikebrady/shairport-sync/wiki/Digital-Signal-Processing-with-Shairport-Sync). (Thanks to [Yann Pomarède](https://github.com/yannpom) for the code and to [Paul Wieland](https://github.com/PaulWieland) for the guide.)
-* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/)-like interface, partially complete and very functional, including access to metadata and artwork, and partial remote control for Classic AirPlay.
-* A native D-Bus interface, including access to metadata, artwork and system settings. Remote control is also available for Classic AirPlay/.
+* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/)-like interface, partially complete and very functional, including access to metadata and artwork, and partial remote control for Classic AirPlay clients.
+* A native D-Bus interface, including access to metadata, artwork and system settings. Remote control is also available for Classic AirPlay clients.
* Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range.
* Flexible output rates, formats and channels with built-in transcoding.
+* Remote Control (Classic AirPLay only) – Remote control commands -- such as `play`, `pause`, and volume-setting commands -- can be sent to Classic AirPlay clients via the D-Bus, MPRIS and MQTT interfaces. Remote Control is unfortunately not available for AirPlay 2 clients, as the system used has not been discovered.
Some features require configuration at build time – see [CONFIGURATION FLAGS.md](CONFIGURATION%20FLAGS.md).
From 0965077c0ee8d365602695e0fde7f0fe16e2bdc3 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Wed, 13 May 2026 15:12:28 +0100
Subject: [PATCH 32/67] Update PulseAudio and PipeWire documentation [skip ci]
Removed redundant information about user services and automatic startup of Shairport Sync. Added a link to the relevant section for starting Shairport Sync as a user service.
---
ADVANCED TOPICS/PulseAudioAndPipeWire.md | 30 +++++-------------------
1 file changed, 6 insertions(+), 24 deletions(-)
diff --git a/ADVANCED TOPICS/PulseAudioAndPipeWire.md b/ADVANCED TOPICS/PulseAudioAndPipeWire.md
index 9ba50812..e0c99e68 100644
--- a/ADVANCED TOPICS/PulseAudioAndPipeWire.md
+++ b/ADVANCED TOPICS/PulseAudioAndPipeWire.md
@@ -1,6 +1,9 @@
# Working with PulseAudio or PipeWire
Many Linux systems, especially desktop Linuxes with a GUI, have [PipeWire](https://pipewire.org) or [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) installed as [sound servers](https://en.wikipedia.org/wiki/Sound_server).
-PipeWire and PulseAudio are widely used and have the great advantage of being easily able to mix audio from multiple sources. The slight downside is that audio may be further processed (e.g. transcoded) on its way to the output device.
+PipeWire and PulseAudio are widely used and have the great advantage of being easily able to mix audio from multiple sources.
+
+However, the main thing to remember about PipeWire and PulseAudio sound servers is they only become available when a user logs in -- that is, they are set up as _user services_.
+Shairport Sync relies on them and therefore it must also be set up as a user service. Shairport Sync can not be set up as a system service because the PipeWire or PulseAudio services are not available when system services are launched just after system startup.
To use PipeWire or PulseAudio-based systems, Shairport Sync must be set up as a user service.
@@ -10,26 +13,5 @@ To use PipeWire or PulseAudio-based systems, Shairport Sync must be set up as a
3. To check if PipeWire support is built into Shairport Sync, check that the string `PipeWire` is included in the version string. (Enter `$ shairport-sync -V` to get the version string.) Similarly, the version string will include `PulseAudio` if the PulseAudio backend is built in.
4. Remember to specify which backend Shairport Sync should use in the configuration file or on the command line.
-## Automatic Startup of Shairport Sync
-
-The main thing to remember about PipeWire and PulseAudio sound servers is that the services they offer only become available when a user logs in -- that is, they are set up as _user services_.
-Shairport Sync relies on them, so it must also be set up as a user service; it can not be set up as a system service because the PipeWire or PulseAudio services needed by Shairport Sync are not available when system services are launched just after system startup.
-
-### Starting Shairport Sync as a User Service
-To make Shairport Sync start as a user service, (assuming you built and installed Shairport Sync using the BUILD.md guide), ensure you are logged in as the appropriate user and enter:
-```
-$ systemctl --user enable shairport-sync
-```
-Make sure it is _not enabled_ as a system service:
-```
-# systemctl disable shairport-sync
-```
-and then reboot.
-
-#### Problems with User Service
-Shairport Sync will function perfectly well as a user service, but there are a number of things to bear in mind:
-
-1. The AirPlay service will only be available when the user is logged in. When the user logs out, Shairport Sync will terminate and the AirPlay service will disappear.
-2. If the Linux system is a desktop Linux with a GUI, audio will be sent to the default output only when the user is logged in through the GUI.
-
-Automatic user login may help address these problems.
+## Starting Shairport Sync as a User Service
+Please refer to [this](https://github.com/mikebrady/shairport-sync/blob/development/BUILD.md#5-enable-and-start-service) section to discover how to start Shairport Sync as a user service.
From f30dd7aee69a2dc2f4bef9496995212503c45c80 Mon Sep 17 00:00:00 2001
From: Mike Brady <4265913+mikebrady@users.noreply.github.com>
Date: Mon, 18 May 2026 09:57:12 +0100
Subject: [PATCH 33/67] =?UTF-8?q?Enhancements=20=20=20Enable=20the=20AirPl?=
=?UTF-8?q?ay=202=20build=20to=20operate=20an=20optional=20classic-AirPlay?=
=?UTF-8?q?-only=20service=20or=20to=20gracefully=20degrade=20to=20classic?=
=?UTF-8?q?=20AirPlay,=20as=20follows:=20=20=201.=20Add=20a=20new=20comman?=
=?UTF-8?q?d-line=20option=20'-=E2=80=94service-type=3D'=20and=20an?=
=?UTF-8?q?=20equivalent=20configuration=20entry=20'service=5Ftype=20=3D?=
=?UTF-8?q?=20""'=20in=20the=20'general'=20section=20of=20the=20conf?=
=?UTF-8?q?iguration=20file,=20=20=20=20=20=20where=20=20can=20be=20?=
=?UTF-8?q?"auto",=20"classic"=20or=20"airplay2":=20=20=20=20=20=20=201.?=
=?UTF-8?q?=20"auto"=20(default)=20means=20that=20the=20service=20will=20b?=
=?UTF-8?q?e=20AirPlay=202=20if=20NQPTP=20is=20running.=20If=20NQPTP=20is?=
=?UTF-8?q?=20not=20running,=20classic=20AirPlay=20service=20will=20be=20p?=
=?UTF-8?q?rovided=20instead,=20and=20in=20that=20case,=20=20=20=20=20=20?=
=?UTF-8?q?=20"(Classic)"=20will=20be=20appended=20to=20the=20default=20Ai?=
=?UTF-8?q?rPlay=20service=20name=20visible=20to=20AirPlay=20clients=20lik?=
=?UTF-8?q?e=20Apple=20Music,=20for=20example=20"RaspberryPi3B=20(Classic)?=
=?UTF-8?q?".=20=20=20=20=20=20=202.=20"classic"=20means=20the=20service?=
=?UTF-8?q?=20will=20be=20classic=20AirPlay=20(aka=20AirPlay=201).=20=20?=
=?UTF-8?q?=20=20=20=20=203.=20"airplay2"=20means=20the=20service=20will?=
=?UTF-8?q?=20be=20the=20modern=20AirPlay=202.=20In=20this=20case,=20as=20?=
=?UTF-8?q?distinct=20from=20"auto",=20if=20NQPTP=20is=20not=20running,=20?=
=?UTF-8?q?Shairport=20Sync=20will=20log=20an=20error=20and=20terminate.?=
=?UTF-8?q?=20=20=202.=20In=20the=20systemd=20service=20file,=20NQPTP=20is?=
=?UTF-8?q?=20now=20a=20"Want"=20rather=20than=20a=20"Require".=20If=20it'?=
=?UTF-8?q?s=20present,=20then=20it=20will=20be=20launched=20before=20Shai?=
=?UTF-8?q?rport=20Sync.=20If=20it's=20absent,=20Shairport=20Sync=20will?=
=?UTF-8?q?=20launch=20anyway.=20=20=203.=20Improve=20the=20delivery=20of?=
=?UTF-8?q?=20input=20format=20changes=20and=20emit=20'sdsc'=20metadata=20?=
=?UTF-8?q?when=20changes=20occur.=20=20=204.=20Emit=20new=20format=20info?=
=?UTF-8?q?rmation=20in=20the=20log=20if=20statistics=20is=20enabled.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Docker Changes
1. Support for linux/arm/v6 has been dropped, as Docker is no longer supported.
2. NQPTP is not started in the AirPlay 2 Docker image if '--service-type=classic' or '--service-type=airplay1' is in the command line options at the end of the docker run command.
The purpose is to ensure that ports 319 and 320 are left alone when the AirPlay 2 image is set to provide Classic service only.
Note that setting the configuration file 'service_type' to 'classic' will not prevent NQPTP from starting up -- you must use the command line option.
3. A new "dev" target has been added. It is a large image containing the custom-built FFmpeg library, NQPTP, Avahi and D-Bus along with the Shairport Sync source and
all necessary development tools. When started, Avahi, D-Bus and NQPTP are all installed and running. The bash shell has also been added and is entered.
Stability Improvements
Reorganise session preemption to fully terminate the existing session before starting a new one.
Don't delay closing the event port to wait for it to be closed at the client end.
Add a safe_socket_close() function to ensure sockets are fully closed. Use -1 to designate closed rather than 0, to prevent attempts to reclose sockets, causing mayhem.
Re-order FFmpeg decommissioning during teardown.
---
.github/workflows/docker-vars.yaml | 2 +-
ADVANCED TOPICS/PulseAudioAndPipeWire.md | 30 +-
FFTConvolver/AudioFFT.cpp | 1600 ++++++++++------------
FFTConvolver/AudioFFT.h | 248 ++--
FFTConvolver/ConvolverThreadPool.cpp | 7 +-
FFTConvolver/ConvolverThreadPool.h | 2 +-
FFTConvolver/FFTConvolver.cpp | 138 +-
FFTConvolver/FFTConvolver.h | 97 +-
FFTConvolver/Utilities.cpp | 78 +-
FFTConvolver/Utilities.h | 300 ++--
FFTConvolver/convolver.cpp | 22 +-
FFTConvolver/convolver.h | 20 +-
Makefile.am | 14 +-
alac.c | 16 +-
ap2_buffered_audio_processor.c | 123 +-
ap2_event_message_handler.c | 17 +-
ap2_event_message_handler.h | 5 +-
ap2_event_receiver.c | 13 +-
audio_alsa.c | 20 +-
audio_pa.c | 3 +-
audio_pipe.c | 7 +-
bonjour_strings.c | 118 +-
common.c | 146 +-
common.h | 34 +-
configure.ac | 7 +-
dacp.c | 23 +-
dbus-service.c | 56 +-
definitions.h | 1 -
docker/Dockerfile | 27 +
docker/devrun.sh | 36 +
docker/run.sh | 18 +-
man/shairport-sync.1.xml | 38 +-
mdns_avahi.c | 7 +-
metadata/core.c | 37 +-
metadata/core.h | 3 +-
metadata/hub.c | 19 +-
metadata/hub.h | 6 +-
metadata/multicast.c | 37 +-
metadata/multicast.h | 7 +-
metadata/pc_queue.c | 3 +-
metadata/pipe.c | 29 +-
metadata/pipe.h | 7 +-
mqtt.c | 45 +-
mqtt.h | 5 +-
player.c | 225 +--
player.h | 3 +-
ptp-utilities.c | 3 +-
rtp.c | 27 +-
rtsp.c | 636 +++++----
rtsp.h | 21 +-
scripts/shairport-sync.conf | 7 +-
scripts/shairport-sync.service.in | 1 +
shairport.c | 676 ++++-----
tinysvcmdns.c | 13 +-
utilities/buffered_read.c | 15 +-
utilities/buffered_read.h | 10 +-
utilities/debug.c | 110 +-
utilities/debug.h | 17 +-
utilities/generate_device_uuid.c | 23 +-
utilities/generate_random_uuid.c | 7 +-
utilities/mod23.c | 9 +-
utilities/mod23.h | 7 +-
utilities/network_utilities.c | 47 +-
utilities/network_utilities.h | 10 +-
utilities/rtsp_message_utilities.c | 53 +-
utilities/rtsp_message_utilities.h | 10 +-
utilities/string_utilities.c | 194 +++
utilities/string_utilities.h | 8 +
utilities/structured_buffer.c | 10 +-
69 files changed, 2897 insertions(+), 2716 deletions(-)
mode change 100755 => 100644 FFTConvolver/AudioFFT.cpp
mode change 100755 => 100644 FFTConvolver/AudioFFT.h
mode change 100755 => 100644 FFTConvolver/FFTConvolver.cpp
mode change 100755 => 100644 FFTConvolver/FFTConvolver.h
create mode 100755 docker/devrun.sh
create mode 100644 utilities/string_utilities.c
create mode 100644 utilities/string_utilities.h
diff --git a/.github/workflows/docker-vars.yaml b/.github/workflows/docker-vars.yaml
index c9f9f8fb..614f70ef 100644
--- a/.github/workflows/docker-vars.yaml
+++ b/.github/workflows/docker-vars.yaml
@@ -23,7 +23,7 @@ jobs:
outputs:
nqptp_branch: ${{ env.NQPTP_BRANCH }}
push_docker_image: ${{ env.PUSH_DOCKER_IMAGE }}
- docker_platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
+ docker_platforms: linux/386,linux/amd64,linux/arm/v7,linux/arm64
steps:
- name: Push docker image if this is a tag.
if: github.ref_type == 'tag'
diff --git a/ADVANCED TOPICS/PulseAudioAndPipeWire.md b/ADVANCED TOPICS/PulseAudioAndPipeWire.md
index 9ba50812..e0c99e68 100644
--- a/ADVANCED TOPICS/PulseAudioAndPipeWire.md
+++ b/ADVANCED TOPICS/PulseAudioAndPipeWire.md
@@ -1,6 +1,9 @@
# Working with PulseAudio or PipeWire
Many Linux systems, especially desktop Linuxes with a GUI, have [PipeWire](https://pipewire.org) or [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) installed as [sound servers](https://en.wikipedia.org/wiki/Sound_server).
-PipeWire and PulseAudio are widely used and have the great advantage of being easily able to mix audio from multiple sources. The slight downside is that audio may be further processed (e.g. transcoded) on its way to the output device.
+PipeWire and PulseAudio are widely used and have the great advantage of being easily able to mix audio from multiple sources.
+
+However, the main thing to remember about PipeWire and PulseAudio sound servers is they only become available when a user logs in -- that is, they are set up as _user services_.
+Shairport Sync relies on them and therefore it must also be set up as a user service. Shairport Sync can not be set up as a system service because the PipeWire or PulseAudio services are not available when system services are launched just after system startup.
To use PipeWire or PulseAudio-based systems, Shairport Sync must be set up as a user service.
@@ -10,26 +13,5 @@ To use PipeWire or PulseAudio-based systems, Shairport Sync must be set up as a
3. To check if PipeWire support is built into Shairport Sync, check that the string `PipeWire` is included in the version string. (Enter `$ shairport-sync -V` to get the version string.) Similarly, the version string will include `PulseAudio` if the PulseAudio backend is built in.
4. Remember to specify which backend Shairport Sync should use in the configuration file or on the command line.
-## Automatic Startup of Shairport Sync
-
-The main thing to remember about PipeWire and PulseAudio sound servers is that the services they offer only become available when a user logs in -- that is, they are set up as _user services_.
-Shairport Sync relies on them, so it must also be set up as a user service; it can not be set up as a system service because the PipeWire or PulseAudio services needed by Shairport Sync are not available when system services are launched just after system startup.
-
-### Starting Shairport Sync as a User Service
-To make Shairport Sync start as a user service, (assuming you built and installed Shairport Sync using the BUILD.md guide), ensure you are logged in as the appropriate user and enter:
-```
-$ systemctl --user enable shairport-sync
-```
-Make sure it is _not enabled_ as a system service:
-```
-# systemctl disable shairport-sync
-```
-and then reboot.
-
-#### Problems with User Service
-Shairport Sync will function perfectly well as a user service, but there are a number of things to bear in mind:
-
-1. The AirPlay service will only be available when the user is logged in. When the user logs out, Shairport Sync will terminate and the AirPlay service will disappear.
-2. If the Linux system is a desktop Linux with a GUI, audio will be sent to the default output only when the user is logged in through the GUI.
-
-Automatic user login may help address these problems.
+## Starting Shairport Sync as a User Service
+Please refer to [this](https://github.com/mikebrady/shairport-sync/blob/development/BUILD.md#5-enable-and-start-service) section to discover how to start Shairport Sync as a user service.
diff --git a/FFTConvolver/AudioFFT.cpp b/FFTConvolver/AudioFFT.cpp
old mode 100755
new mode 100644
index 4f013d05..768e8e0f
--- a/FFTConvolver/AudioFFT.cpp
+++ b/FFTConvolver/AudioFFT.cpp
@@ -25,317 +25,258 @@
#include
#include
-
#if defined(AUDIOFFT_APPLE_ACCELERATE)
- #define AUDIOFFT_APPLE_ACCELERATE_USED
- #include
- #include
-#elif defined (AUDIOFFT_FFTW3)
- #define AUDIOFFT_FFTW3_USED
- #include
+#define AUDIOFFT_APPLE_ACCELERATE_USED
+#include
+#include
+#elif defined(AUDIOFFT_FFTW3)
+#define AUDIOFFT_FFTW3_USED
+#include
#else
- #if !defined(AUDIOFFT_OOURA)
- #define AUDIOFFT_OOURA
- #endif
- #define AUDIOFFT_OOURA_USED
- #include
+#if !defined(AUDIOFFT_OOURA)
+#define AUDIOFFT_OOURA
+#endif
+#define AUDIOFFT_OOURA_USED
+#include
#endif
+namespace audiofft {
-namespace audiofft
-{
+namespace detail {
- namespace detail
- {
+class AudioFFTImpl {
+public:
+ AudioFFTImpl() = default;
+ AudioFFTImpl(const AudioFFTImpl &) = delete;
+ AudioFFTImpl &operator=(const AudioFFTImpl &) = delete;
+ virtual ~AudioFFTImpl() = default;
+ virtual void init(size_t size) = 0;
+ virtual void fft(const float *data, float *re, float *im) = 0;
+ virtual void ifft(float *data, const float *re, const float *im) = 0;
+};
- class AudioFFTImpl
- {
- public:
- AudioFFTImpl() = default;
- AudioFFTImpl(const AudioFFTImpl&) = delete;
- AudioFFTImpl& operator=(const AudioFFTImpl&) = delete;
- virtual ~AudioFFTImpl() = default;
- virtual void init(size_t size) = 0;
- virtual void fft(const float* data, float* re, float* im) = 0;
- virtual void ifft(float* data, const float* re, const float* im) = 0;
- };
+constexpr bool IsPowerOf2(size_t val) { return (val == 1 || (val & (val - 1)) == 0); }
+template
+void ConvertBuffer(TypeDest *dest, const TypeSrc *src, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ dest[i] = static_cast(src[i]);
+ }
+}
- constexpr bool IsPowerOf2(size_t val)
- {
- return (val == 1 || (val & (val-1)) == 0);
- }
+template
+void ScaleBuffer(TypeDest *dest, const TypeSrc *src, const TypeFactor factor, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ dest[i] = static_cast(static_cast(src[i]) * factor);
+ }
+}
+} // End of namespace detail
- template
- void ConvertBuffer(TypeDest* dest, const TypeSrc* src, size_t len)
- {
- for (size_t i=0; i(src[i]);
- }
- }
-
-
- template
- void ScaleBuffer(TypeDest* dest, const TypeSrc* src, const TypeFactor factor, size_t len)
- {
- for (size_t i=0; i(static_cast(src[i]) * factor);
- }
- }
-
- } // End of namespace detail
-
-
- // ================================================================
-
+// ================================================================
#ifdef AUDIOFFT_OOURA_USED
- /**
- * @internal
- * @class OouraFFT
- * @brief FFT implementation based on the great radix-4 routines by Takuya Ooura
- */
- class OouraFFT : public detail::AudioFFTImpl
- {
- public:
- OouraFFT() :
- detail::AudioFFTImpl(),
- _size(0),
- _ip(),
- _w(),
- _buffer()
- {
+/**
+ * @internal
+ * @class OouraFFT
+ * @brief FFT implementation based on the great radix-4 routines by Takuya Ooura
+ */
+class OouraFFT : public detail::AudioFFTImpl {
+public:
+ OouraFFT() : detail::AudioFFTImpl(), _size(0), _ip(), _w(), _buffer() {}
+
+ OouraFFT(const OouraFFT &) = delete;
+ OouraFFT &operator=(const OouraFFT &) = delete;
+
+ virtual void init(size_t size) override {
+ if (_size != size) {
+ _ip.resize(2 + static_cast(std::sqrt(static_cast(size))));
+ _w.resize(size / 2);
+ _buffer.resize(size);
+ _size = size;
+
+ const int size4 = static_cast(_size) / 4;
+ makewt(size4, _ip.data(), _w.data());
+ makect(size4, _ip.data(), _w.data() + size4);
}
+ }
- OouraFFT(const OouraFFT&) = delete;
- OouraFFT& operator=(const OouraFFT&) = delete;
+ virtual void fft(const float *data, float *re, float *im) override {
+ // Convert into the format as required by the Ooura FFT
+ detail::ConvertBuffer(_buffer.data(), data, _size);
- virtual void init(size_t size) override
+ rdft(static_cast(_size), +1, _buffer.data(), _ip.data(), _w.data());
+
+ // Convert back to split-complex
{
- if (_size != size)
- {
- _ip.resize(2 + static_cast(std::sqrt(static_cast(size))));
- _w.resize(size / 2);
- _buffer.resize(size);
- _size = size;
-
- const int size4 = static_cast(_size) / 4;
- makewt(size4, _ip.data(), _w.data());
- makect(size4, _ip.data(), _w.data() + size4);
+ double *b = _buffer.data();
+ double *bEnd = b + _size;
+ float *r = re;
+ float *i = im;
+ while (b != bEnd) {
+ *(r++) = static_cast(*(b++));
+ *(i++) = static_cast(-(*(b++)));
}
}
+ const size_t size2 = _size / 2;
+ re[size2] = -im[0];
+ im[0] = 0.0;
+ im[size2] = 0.0;
+ }
- virtual void fft(const float* data, float* re, float* im) override
+ virtual void ifft(float *data, const float *re, const float *im) override {
+ // Convert into the format as required by the Ooura FFT
{
- // Convert into the format as required by the Ooura FFT
- detail::ConvertBuffer(_buffer.data(), data, _size);
-
- rdft(static_cast(_size), +1, _buffer.data(), _ip.data(), _w.data());
-
- // Convert back to split-complex
- {
- double* b = _buffer.data();
- double* bEnd = b + _size;
- float *r = re;
- float *i = im;
- while (b != bEnd)
- {
- *(r++) = static_cast(*(b++));
- *(i++) = static_cast(-(*(b++)));
- }
+ double *b = _buffer.data();
+ double *bEnd = b + _size;
+ const float *r = re;
+ const float *i = im;
+ while (b != bEnd) {
+ *(b++) = static_cast(*(r++));
+ *(b++) = -static_cast(*(i++));
}
- const size_t size2 = _size / 2;
- re[size2] = -im[0];
- im[0] = 0.0;
- im[size2] = 0.0;
+ _buffer[1] = re[_size / 2];
}
- virtual void ifft(float* data, const float* re, const float* im) override
- {
- // Convert into the format as required by the Ooura FFT
- {
- double* b = _buffer.data();
- double* bEnd = b + _size;
- const float *r = re;
- const float *i = im;
- while (b != bEnd)
- {
- *(b++) = static_cast(*(r++));
- *(b++) = -static_cast(*(i++));
- }
- _buffer[1] = re[_size / 2];
+ rdft(static_cast(_size), -1, _buffer.data(), _ip.data(), _w.data());
+
+ // Convert back to split-complex
+ detail::ScaleBuffer(data, _buffer.data(), 2.0 / static_cast(_size), _size);
+ }
+
+private:
+ size_t _size;
+ std::vector _ip;
+ std::vector _w;
+ std::vector _buffer;
+
+ void rdft(int n, int isgn, double *a, int *ip, double *w) {
+ int nw = ip[0];
+ int nc = ip[1];
+
+ if (isgn >= 0) {
+ if (n > 4) {
+ bitrv2(n, ip + 2, a);
+ cftfsub(n, a, w);
+ rftfsub(n, a, nc, w + nw);
+ } else if (n == 4) {
+ cftfsub(n, a, w);
}
-
- rdft(static_cast(_size), -1, _buffer.data(), _ip.data(), _w.data());
-
- // Convert back to split-complex
- detail::ScaleBuffer(data, _buffer.data(), 2.0 / static_cast(_size), _size);
- }
-
- private:
- size_t _size;
- std::vector _ip;
- std::vector _w;
- std::vector _buffer;
-
- void rdft(int n, int isgn, double *a, int *ip, double *w)
- {
- int nw = ip[0];
- int nc = ip[1];
-
- if (isgn >= 0)
- {
- if (n > 4)
- {
- bitrv2(n, ip + 2, a);
- cftfsub(n, a, w);
- rftfsub(n, a, nc, w + nw);
- }
- else if (n == 4)
- {
- cftfsub(n, a, w);
- }
- double xi = a[0] - a[1];
- a[0] += a[1];
- a[1] = xi;
- }
- else
- {
- a[1] = 0.5 * (a[0] - a[1]);
- a[0] -= a[1];
- if (n > 4)
- {
- rftbsub(n, a, nc, w + nw);
- bitrv2(n, ip + 2, a);
- cftbsub(n, a, w);
- }
- else if (n == 4)
- {
- cftfsub(n, a, w);
- }
+ double xi = a[0] - a[1];
+ a[0] += a[1];
+ a[1] = xi;
+ } else {
+ a[1] = 0.5 * (a[0] - a[1]);
+ a[0] -= a[1];
+ if (n > 4) {
+ rftbsub(n, a, nc, w + nw);
+ bitrv2(n, ip + 2, a);
+ cftbsub(n, a, w);
+ } else if (n == 4) {
+ cftfsub(n, a, w);
}
}
+ }
+ /* -------- initializing routines -------- */
- /* -------- initializing routines -------- */
+ void makewt(int nw, int *ip, double *w) {
+ int j, nwh;
+ double delta, x, y;
- void makewt(int nw, int *ip, double *w)
- {
- int j, nwh;
- double delta, x, y;
-
- ip[0] = nw;
- ip[1] = 1;
- if (nw > 2) {
- nwh = nw >> 1;
- delta = atan(1.0) / nwh;
- w[0] = 1;
- w[1] = 0;
- w[nwh] = cos(delta * nwh);
- w[nwh + 1] = w[nwh];
- if (nwh > 2) {
- for (j = 2; j < nwh; j += 2) {
- x = cos(delta * j);
- y = sin(delta * j);
- w[j] = x;
- w[j + 1] = y;
- w[nw - j] = y;
- w[nw - j + 1] = x;
- }
- bitrv2(nw, ip + 2, w);
+ ip[0] = nw;
+ ip[1] = 1;
+ if (nw > 2) {
+ nwh = nw >> 1;
+ delta = atan(1.0) / nwh;
+ w[0] = 1;
+ w[1] = 0;
+ w[nwh] = cos(delta * nwh);
+ w[nwh + 1] = w[nwh];
+ if (nwh > 2) {
+ for (j = 2; j < nwh; j += 2) {
+ x = cos(delta * j);
+ y = sin(delta * j);
+ w[j] = x;
+ w[j + 1] = y;
+ w[nw - j] = y;
+ w[nw - j + 1] = x;
}
+ bitrv2(nw, ip + 2, w);
}
}
+ }
+ void makect(int nc, int *ip, double *c) {
+ int j, nch;
+ double delta;
- void makect(int nc, int *ip, double *c)
- {
- int j, nch;
- double delta;
-
- ip[1] = nc;
- if (nc > 1) {
- nch = nc >> 1;
- delta = atan(1.0) / nch;
- c[0] = cos(delta * nch);
- c[nch] = 0.5 * c[0];
- for (j = 1; j < nch; j++) {
- c[j] = 0.5 * cos(delta * j);
- c[nc - j] = 0.5 * sin(delta * j);
- }
+ ip[1] = nc;
+ if (nc > 1) {
+ nch = nc >> 1;
+ delta = atan(1.0) / nch;
+ c[0] = cos(delta * nch);
+ c[nch] = 0.5 * c[0];
+ for (j = 1; j < nch; j++) {
+ c[j] = 0.5 * cos(delta * j);
+ c[nc - j] = 0.5 * sin(delta * j);
}
}
+ }
+ /* -------- child routines -------- */
- /* -------- child routines -------- */
+ void bitrv2(int n, int *ip, double *a) {
+ int j, j1, k, k1, l, m, m2;
+ double xr, xi, yr, yi;
-
- void bitrv2(int n, int *ip, double *a)
- {
- int j, j1, k, k1, l, m, m2;
- double xr, xi, yr, yi;
-
- ip[0] = 0;
- l = n;
- m = 1;
- while ((m << 3) < l) {
- l >>= 1;
- for (j = 0; j < m; j++) {
- ip[m + j] = ip[j] + l;
- }
- m <<= 1;
+ ip[0] = 0;
+ l = n;
+ m = 1;
+ while ((m << 3) < l) {
+ l >>= 1;
+ for (j = 0; j < m; j++) {
+ ip[m + j] = ip[j] + l;
}
- m2 = 2 * m;
- if ((m << 3) == l) {
- for (k = 0; k < m; k++) {
- for (j = 0; j < k; j++) {
- j1 = 2 * j + ip[k];
- k1 = 2 * k + ip[j];
- xr = a[j1];
- xi = a[j1 + 1];
- yr = a[k1];
- yi = a[k1 + 1];
- a[j1] = yr;
- a[j1 + 1] = yi;
- a[k1] = xr;
- a[k1 + 1] = xi;
- j1 += m2;
- k1 += 2 * m2;
- xr = a[j1];
- xi = a[j1 + 1];
- yr = a[k1];
- yi = a[k1 + 1];
- a[j1] = yr;
- a[j1 + 1] = yi;
- a[k1] = xr;
- a[k1 + 1] = xi;
- j1 += m2;
- k1 -= m2;
- xr = a[j1];
- xi = a[j1 + 1];
- yr = a[k1];
- yi = a[k1 + 1];
- a[j1] = yr;
- a[j1 + 1] = yi;
- a[k1] = xr;
- a[k1 + 1] = xi;
- j1 += m2;
- k1 += 2 * m2;
- xr = a[j1];
- xi = a[j1 + 1];
- yr = a[k1];
- yi = a[k1 + 1];
- a[j1] = yr;
- a[j1 + 1] = yi;
- a[k1] = xr;
- a[k1 + 1] = xi;
- }
- j1 = 2 * k + m2 + ip[k];
- k1 = j1 + m2;
+ m <<= 1;
+ }
+ m2 = 2 * m;
+ if ((m << 3) == l) {
+ for (k = 0; k < m; k++) {
+ for (j = 0; j < k; j++) {
+ j1 = 2 * j + ip[k];
+ k1 = 2 * k + ip[j];
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += 2 * m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 -= m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += 2 * m2;
xr = a[j1];
xi = a[j1 + 1];
yr = a[k1];
@@ -345,247 +286,59 @@ namespace audiofft
a[k1] = xr;
a[k1 + 1] = xi;
}
- } else {
- for (k = 1; k < m; k++) {
- for (j = 0; j < k; j++) {
- j1 = 2 * j + ip[k];
- k1 = 2 * k + ip[j];
- xr = a[j1];
- xi = a[j1 + 1];
- yr = a[k1];
- yi = a[k1 + 1];
- a[j1] = yr;
- a[j1 + 1] = yi;
- a[k1] = xr;
- a[k1 + 1] = xi;
- j1 += m2;
- k1 += m2;
- xr = a[j1];
- xi = a[j1 + 1];
- yr = a[k1];
- yi = a[k1 + 1];
- a[j1] = yr;
- a[j1 + 1] = yi;
- a[k1] = xr;
- a[k1 + 1] = xi;
- }
+ j1 = 2 * k + m2 + ip[k];
+ k1 = j1 + m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ }
+ } else {
+ for (k = 1; k < m; k++) {
+ for (j = 0; j < k; j++) {
+ j1 = 2 * j + ip[k];
+ k1 = 2 * k + ip[j];
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
+ j1 += m2;
+ k1 += m2;
+ xr = a[j1];
+ xi = a[j1 + 1];
+ yr = a[k1];
+ yi = a[k1 + 1];
+ a[j1] = yr;
+ a[j1 + 1] = yi;
+ a[k1] = xr;
+ a[k1 + 1] = xi;
}
}
}
+ }
+ void cftfsub(int n, double *a, double *w) {
+ int j, j1, j2, j3, l;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
- void cftfsub(int n, double *a, double *w)
- {
- int j, j1, j2, j3, l;
- double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
-
- l = 2;
- if (n > 8) {
- cft1st(n, a, w);
- l = 8;
- while ((l << 2) < n) {
- cftmdl(n, l, a, w);
- l <<= 2;
- }
- }
- if ((l << 2) == n) {
- for (j = 0; j < l; j += 2) {
- j1 = j + l;
- j2 = j1 + l;
- j3 = j2 + l;
- x0r = a[j] + a[j1];
- x0i = a[j + 1] + a[j1 + 1];
- x1r = a[j] - a[j1];
- x1i = a[j + 1] - a[j1 + 1];
- x2r = a[j2] + a[j3];
- x2i = a[j2 + 1] + a[j3 + 1];
- x3r = a[j2] - a[j3];
- x3i = a[j2 + 1] - a[j3 + 1];
- a[j] = x0r + x2r;
- a[j + 1] = x0i + x2i;
- a[j2] = x0r - x2r;
- a[j2 + 1] = x0i - x2i;
- a[j1] = x1r - x3i;
- a[j1 + 1] = x1i + x3r;
- a[j3] = x1r + x3i;
- a[j3 + 1] = x1i - x3r;
- }
- } else {
- for (j = 0; j < l; j += 2) {
- j1 = j + l;
- x0r = a[j] - a[j1];
- x0i = a[j + 1] - a[j1 + 1];
- a[j] += a[j1];
- a[j + 1] += a[j1 + 1];
- a[j1] = x0r;
- a[j1 + 1] = x0i;
- }
+ l = 2;
+ if (n > 8) {
+ cft1st(n, a, w);
+ l = 8;
+ while ((l << 2) < n) {
+ cftmdl(n, l, a, w);
+ l <<= 2;
}
}
-
-
- void cftbsub(int n, double *a, double *w)
- {
- int j, j1, j2, j3, l;
- double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
-
- l = 2;
- if (n > 8) {
- cft1st(n, a, w);
- l = 8;
- while ((l << 2) < n) {
- cftmdl(n, l, a, w);
- l <<= 2;
- }
- }
- if ((l << 2) == n) {
- for (j = 0; j < l; j += 2) {
- j1 = j + l;
- j2 = j1 + l;
- j3 = j2 + l;
- x0r = a[j] + a[j1];
- x0i = -a[j + 1] - a[j1 + 1];
- x1r = a[j] - a[j1];
- x1i = -a[j + 1] + a[j1 + 1];
- x2r = a[j2] + a[j3];
- x2i = a[j2 + 1] + a[j3 + 1];
- x3r = a[j2] - a[j3];
- x3i = a[j2 + 1] - a[j3 + 1];
- a[j] = x0r + x2r;
- a[j + 1] = x0i - x2i;
- a[j2] = x0r - x2r;
- a[j2 + 1] = x0i + x2i;
- a[j1] = x1r - x3i;
- a[j1 + 1] = x1i - x3r;
- a[j3] = x1r + x3i;
- a[j3 + 1] = x1i + x3r;
- }
- } else {
- for (j = 0; j < l; j += 2) {
- j1 = j + l;
- x0r = a[j] - a[j1];
- x0i = -a[j + 1] + a[j1 + 1];
- a[j] += a[j1];
- a[j + 1] = -a[j + 1] - a[j1 + 1];
- a[j1] = x0r;
- a[j1 + 1] = x0i;
- }
- }
- }
-
-
- void cft1st(int n, double *a, double *w)
- {
- int j, k1, k2;
- double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
- double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
-
- x0r = a[0] + a[2];
- x0i = a[1] + a[3];
- x1r = a[0] - a[2];
- x1i = a[1] - a[3];
- x2r = a[4] + a[6];
- x2i = a[5] + a[7];
- x3r = a[4] - a[6];
- x3i = a[5] - a[7];
- a[0] = x0r + x2r;
- a[1] = x0i + x2i;
- a[4] = x0r - x2r;
- a[5] = x0i - x2i;
- a[2] = x1r - x3i;
- a[3] = x1i + x3r;
- a[6] = x1r + x3i;
- a[7] = x1i - x3r;
- wk1r = w[2];
- x0r = a[8] + a[10];
- x0i = a[9] + a[11];
- x1r = a[8] - a[10];
- x1i = a[9] - a[11];
- x2r = a[12] + a[14];
- x2i = a[13] + a[15];
- x3r = a[12] - a[14];
- x3i = a[13] - a[15];
- a[8] = x0r + x2r;
- a[9] = x0i + x2i;
- a[12] = x2i - x0i;
- a[13] = x0r - x2r;
- x0r = x1r - x3i;
- x0i = x1i + x3r;
- a[10] = wk1r * (x0r - x0i);
- a[11] = wk1r * (x0r + x0i);
- x0r = x3i + x1r;
- x0i = x3r - x1i;
- a[14] = wk1r * (x0i - x0r);
- a[15] = wk1r * (x0i + x0r);
- k1 = 0;
- for (j = 16; j < n; j += 16) {
- k1 += 2;
- k2 = 2 * k1;
- wk2r = w[k1];
- wk2i = w[k1 + 1];
- wk1r = w[k2];
- wk1i = w[k2 + 1];
- wk3r = wk1r - 2 * wk2i * wk1i;
- wk3i = 2 * wk2i * wk1r - wk1i;
- x0r = a[j] + a[j + 2];
- x0i = a[j + 1] + a[j + 3];
- x1r = a[j] - a[j + 2];
- x1i = a[j + 1] - a[j + 3];
- x2r = a[j + 4] + a[j + 6];
- x2i = a[j + 5] + a[j + 7];
- x3r = a[j + 4] - a[j + 6];
- x3i = a[j + 5] - a[j + 7];
- a[j] = x0r + x2r;
- a[j + 1] = x0i + x2i;
- x0r -= x2r;
- x0i -= x2i;
- a[j + 4] = wk2r * x0r - wk2i * x0i;
- a[j + 5] = wk2r * x0i + wk2i * x0r;
- x0r = x1r - x3i;
- x0i = x1i + x3r;
- a[j + 2] = wk1r * x0r - wk1i * x0i;
- a[j + 3] = wk1r * x0i + wk1i * x0r;
- x0r = x1r + x3i;
- x0i = x1i - x3r;
- a[j + 6] = wk3r * x0r - wk3i * x0i;
- a[j + 7] = wk3r * x0i + wk3i * x0r;
- wk1r = w[k2 + 2];
- wk1i = w[k2 + 3];
- wk3r = wk1r - 2 * wk2r * wk1i;
- wk3i = 2 * wk2r * wk1r - wk1i;
- x0r = a[j + 8] + a[j + 10];
- x0i = a[j + 9] + a[j + 11];
- x1r = a[j + 8] - a[j + 10];
- x1i = a[j + 9] - a[j + 11];
- x2r = a[j + 12] + a[j + 14];
- x2i = a[j + 13] + a[j + 15];
- x3r = a[j + 12] - a[j + 14];
- x3i = a[j + 13] - a[j + 15];
- a[j + 8] = x0r + x2r;
- a[j + 9] = x0i + x2i;
- x0r -= x2r;
- x0i -= x2i;
- a[j + 12] = -wk2i * x0r - wk2r * x0i;
- a[j + 13] = -wk2i * x0i + wk2r * x0r;
- x0r = x1r - x3i;
- x0i = x1i + x3r;
- a[j + 10] = wk1r * x0r - wk1i * x0i;
- a[j + 11] = wk1r * x0i + wk1i * x0r;
- x0r = x1r + x3i;
- x0i = x1i - x3r;
- a[j + 14] = wk3r * x0r - wk3i * x0i;
- a[j + 15] = wk3r * x0i + wk3i * x0r;
- }
- }
-
-
- void cftmdl(int n, int l, double *a, double *w)
- {
- int j, j1, j2, j3, k, k1, k2, m, m2;
- double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
- double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
-
- m = l << 2;
+ if ((l << 2) == n) {
for (j = 0; j < l; j += 2) {
j1 = j + l;
j2 = j1 + l;
@@ -607,8 +360,235 @@ namespace audiofft
a[j3] = x1r + x3i;
a[j3 + 1] = x1i - x3r;
}
- wk1r = w[2];
- for (j = m; j < l + m; j += 2) {
+ } else {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ x0r = a[j] - a[j1];
+ x0i = a[j + 1] - a[j1 + 1];
+ a[j] += a[j1];
+ a[j + 1] += a[j1 + 1];
+ a[j1] = x0r;
+ a[j1 + 1] = x0i;
+ }
+ }
+ }
+
+ void cftbsub(int n, double *a, double *w) {
+ int j, j1, j2, j3, l;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ l = 2;
+ if (n > 8) {
+ cft1st(n, a, w);
+ l = 8;
+ while ((l << 2) < n) {
+ cftmdl(n, l, a, w);
+ l <<= 2;
+ }
+ }
+ if ((l << 2) == n) {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = -a[j + 1] - a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = -a[j + 1] + a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i - x2i;
+ a[j2] = x0r - x2r;
+ a[j2 + 1] = x0i + x2i;
+ a[j1] = x1r - x3i;
+ a[j1 + 1] = x1i - x3r;
+ a[j3] = x1r + x3i;
+ a[j3 + 1] = x1i + x3r;
+ }
+ } else {
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ x0r = a[j] - a[j1];
+ x0i = -a[j + 1] + a[j1 + 1];
+ a[j] += a[j1];
+ a[j + 1] = -a[j + 1] - a[j1 + 1];
+ a[j1] = x0r;
+ a[j1 + 1] = x0i;
+ }
+ }
+ }
+
+ void cft1st(int n, double *a, double *w) {
+ int j, k1, k2;
+ double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ x0r = a[0] + a[2];
+ x0i = a[1] + a[3];
+ x1r = a[0] - a[2];
+ x1i = a[1] - a[3];
+ x2r = a[4] + a[6];
+ x2i = a[5] + a[7];
+ x3r = a[4] - a[6];
+ x3i = a[5] - a[7];
+ a[0] = x0r + x2r;
+ a[1] = x0i + x2i;
+ a[4] = x0r - x2r;
+ a[5] = x0i - x2i;
+ a[2] = x1r - x3i;
+ a[3] = x1i + x3r;
+ a[6] = x1r + x3i;
+ a[7] = x1i - x3r;
+ wk1r = w[2];
+ x0r = a[8] + a[10];
+ x0i = a[9] + a[11];
+ x1r = a[8] - a[10];
+ x1i = a[9] - a[11];
+ x2r = a[12] + a[14];
+ x2i = a[13] + a[15];
+ x3r = a[12] - a[14];
+ x3i = a[13] - a[15];
+ a[8] = x0r + x2r;
+ a[9] = x0i + x2i;
+ a[12] = x2i - x0i;
+ a[13] = x0r - x2r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[10] = wk1r * (x0r - x0i);
+ a[11] = wk1r * (x0r + x0i);
+ x0r = x3i + x1r;
+ x0i = x3r - x1i;
+ a[14] = wk1r * (x0i - x0r);
+ a[15] = wk1r * (x0i + x0r);
+ k1 = 0;
+ for (j = 16; j < n; j += 16) {
+ k1 += 2;
+ k2 = 2 * k1;
+ wk2r = w[k1];
+ wk2i = w[k1 + 1];
+ wk1r = w[k2];
+ wk1i = w[k2 + 1];
+ wk3r = wk1r - 2 * wk2i * wk1i;
+ wk3i = 2 * wk2i * wk1r - wk1i;
+ x0r = a[j] + a[j + 2];
+ x0i = a[j + 1] + a[j + 3];
+ x1r = a[j] - a[j + 2];
+ x1i = a[j + 1] - a[j + 3];
+ x2r = a[j + 4] + a[j + 6];
+ x2i = a[j + 5] + a[j + 7];
+ x3r = a[j + 4] - a[j + 6];
+ x3i = a[j + 5] - a[j + 7];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j + 4] = wk2r * x0r - wk2i * x0i;
+ a[j + 5] = wk2r * x0i + wk2i * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j + 2] = wk1r * x0r - wk1i * x0i;
+ a[j + 3] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j + 6] = wk3r * x0r - wk3i * x0i;
+ a[j + 7] = wk3r * x0i + wk3i * x0r;
+ wk1r = w[k2 + 2];
+ wk1i = w[k2 + 3];
+ wk3r = wk1r - 2 * wk2r * wk1i;
+ wk3i = 2 * wk2r * wk1r - wk1i;
+ x0r = a[j + 8] + a[j + 10];
+ x0i = a[j + 9] + a[j + 11];
+ x1r = a[j + 8] - a[j + 10];
+ x1i = a[j + 9] - a[j + 11];
+ x2r = a[j + 12] + a[j + 14];
+ x2i = a[j + 13] + a[j + 15];
+ x3r = a[j + 12] - a[j + 14];
+ x3i = a[j + 13] - a[j + 15];
+ a[j + 8] = x0r + x2r;
+ a[j + 9] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j + 12] = -wk2i * x0r - wk2r * x0i;
+ a[j + 13] = -wk2i * x0i + wk2r * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j + 10] = wk1r * x0r - wk1i * x0i;
+ a[j + 11] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j + 14] = wk3r * x0r - wk3i * x0i;
+ a[j + 15] = wk3r * x0i + wk3i * x0r;
+ }
+ }
+
+ void cftmdl(int n, int l, double *a, double *w) {
+ int j, j1, j2, j3, k, k1, k2, m, m2;
+ double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;
+ double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+ m = l << 2;
+ for (j = 0; j < l; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ a[j2] = x0r - x2r;
+ a[j2 + 1] = x0i - x2i;
+ a[j1] = x1r - x3i;
+ a[j1 + 1] = x1i + x3r;
+ a[j3] = x1r + x3i;
+ a[j3 + 1] = x1i - x3r;
+ }
+ wk1r = w[2];
+ for (j = m; j < l + m; j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ a[j2] = x2i - x0i;
+ a[j2 + 1] = x0r - x2r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j1] = wk1r * (x0r - x0i);
+ a[j1 + 1] = wk1r * (x0r + x0i);
+ x0r = x3i + x1r;
+ x0i = x3r - x1i;
+ a[j3] = wk1r * (x0i - x0r);
+ a[j3 + 1] = wk1r * (x0i + x0r);
+ }
+ k1 = 0;
+ m2 = 2 * m;
+ for (k = m2; k < n; k += m2) {
+ k1 += 2;
+ k2 = 2 * k1;
+ wk2r = w[k1];
+ wk2i = w[k1 + 1];
+ wk1r = w[k2];
+ wk1i = w[k2 + 1];
+ wk3r = wk1r - 2 * wk2i * wk1i;
+ wk3i = 2 * wk2i * wk1r - wk1i;
+ for (j = k; j < l + k; j += 2) {
j1 = j + l;
j2 = j1 + l;
j3 = j2 + l;
@@ -622,420 +602,308 @@ namespace audiofft
x3i = a[j2 + 1] - a[j3 + 1];
a[j] = x0r + x2r;
a[j + 1] = x0i + x2i;
- a[j2] = x2i - x0i;
- a[j2 + 1] = x0r - x2r;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j2] = wk2r * x0r - wk2i * x0i;
+ a[j2 + 1] = wk2r * x0i + wk2i * x0r;
x0r = x1r - x3i;
x0i = x1i + x3r;
- a[j1] = wk1r * (x0r - x0i);
- a[j1 + 1] = wk1r * (x0r + x0i);
- x0r = x3i + x1r;
- x0i = x3r - x1i;
- a[j3] = wk1r * (x0i - x0r);
- a[j3 + 1] = wk1r * (x0i + x0r);
+ a[j1] = wk1r * x0r - wk1i * x0i;
+ a[j1 + 1] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j3] = wk3r * x0r - wk3i * x0i;
+ a[j3 + 1] = wk3r * x0i + wk3i * x0r;
}
- k1 = 0;
- m2 = 2 * m;
- for (k = m2; k < n; k += m2) {
- k1 += 2;
- k2 = 2 * k1;
- wk2r = w[k1];
- wk2i = w[k1 + 1];
- wk1r = w[k2];
- wk1i = w[k2 + 1];
- wk3r = wk1r - 2 * wk2i * wk1i;
- wk3i = 2 * wk2i * wk1r - wk1i;
- for (j = k; j < l + k; j += 2) {
- j1 = j + l;
- j2 = j1 + l;
- j3 = j2 + l;
- x0r = a[j] + a[j1];
- x0i = a[j + 1] + a[j1 + 1];
- x1r = a[j] - a[j1];
- x1i = a[j + 1] - a[j1 + 1];
- x2r = a[j2] + a[j3];
- x2i = a[j2 + 1] + a[j3 + 1];
- x3r = a[j2] - a[j3];
- x3i = a[j2 + 1] - a[j3 + 1];
- a[j] = x0r + x2r;
- a[j + 1] = x0i + x2i;
- x0r -= x2r;
- x0i -= x2i;
- a[j2] = wk2r * x0r - wk2i * x0i;
- a[j2 + 1] = wk2r * x0i + wk2i * x0r;
- x0r = x1r - x3i;
- x0i = x1i + x3r;
- a[j1] = wk1r * x0r - wk1i * x0i;
- a[j1 + 1] = wk1r * x0i + wk1i * x0r;
- x0r = x1r + x3i;
- x0i = x1i - x3r;
- a[j3] = wk3r * x0r - wk3i * x0i;
- a[j3 + 1] = wk3r * x0i + wk3i * x0r;
- }
- wk1r = w[k2 + 2];
- wk1i = w[k2 + 3];
- wk3r = wk1r - 2 * wk2r * wk1i;
- wk3i = 2 * wk2r * wk1r - wk1i;
- for (j = k + m; j < l + (k + m); j += 2) {
- j1 = j + l;
- j2 = j1 + l;
- j3 = j2 + l;
- x0r = a[j] + a[j1];
- x0i = a[j + 1] + a[j1 + 1];
- x1r = a[j] - a[j1];
- x1i = a[j + 1] - a[j1 + 1];
- x2r = a[j2] + a[j3];
- x2i = a[j2 + 1] + a[j3 + 1];
- x3r = a[j2] - a[j3];
- x3i = a[j2 + 1] - a[j3 + 1];
- a[j] = x0r + x2r;
- a[j + 1] = x0i + x2i;
- x0r -= x2r;
- x0i -= x2i;
- a[j2] = -wk2i * x0r - wk2r * x0i;
- a[j2 + 1] = -wk2i * x0i + wk2r * x0r;
- x0r = x1r - x3i;
- x0i = x1i + x3r;
- a[j1] = wk1r * x0r - wk1i * x0i;
- a[j1 + 1] = wk1r * x0i + wk1i * x0r;
- x0r = x1r + x3i;
- x0i = x1i - x3r;
- a[j3] = wk3r * x0r - wk3i * x0i;
- a[j3 + 1] = wk3r * x0i + wk3i * x0r;
- }
+ wk1r = w[k2 + 2];
+ wk1i = w[k2 + 3];
+ wk3r = wk1r - 2 * wk2r * wk1i;
+ wk3i = 2 * wk2r * wk1r - wk1i;
+ for (j = k + m; j < l + (k + m); j += 2) {
+ j1 = j + l;
+ j2 = j1 + l;
+ j3 = j2 + l;
+ x0r = a[j] + a[j1];
+ x0i = a[j + 1] + a[j1 + 1];
+ x1r = a[j] - a[j1];
+ x1i = a[j + 1] - a[j1 + 1];
+ x2r = a[j2] + a[j3];
+ x2i = a[j2 + 1] + a[j3 + 1];
+ x3r = a[j2] - a[j3];
+ x3i = a[j2 + 1] - a[j3 + 1];
+ a[j] = x0r + x2r;
+ a[j + 1] = x0i + x2i;
+ x0r -= x2r;
+ x0i -= x2i;
+ a[j2] = -wk2i * x0r - wk2r * x0i;
+ a[j2 + 1] = -wk2i * x0i + wk2r * x0r;
+ x0r = x1r - x3i;
+ x0i = x1i + x3r;
+ a[j1] = wk1r * x0r - wk1i * x0i;
+ a[j1 + 1] = wk1r * x0i + wk1i * x0r;
+ x0r = x1r + x3i;
+ x0i = x1i - x3r;
+ a[j3] = wk3r * x0r - wk3i * x0i;
+ a[j3 + 1] = wk3r * x0i + wk3i * x0r;
}
}
+ }
+ void rftfsub(int n, double *a, int nc, double *c) {
+ int j, k, kk, ks, m;
+ double wkr, wki, xr, xi, yr, yi;
- void rftfsub(int n, double *a, int nc, double *c)
- {
- int j, k, kk, ks, m;
- double wkr, wki, xr, xi, yr, yi;
-
- m = n >> 1;
- ks = 2 * nc / m;
- kk = 0;
- for (j = 2; j < m; j += 2) {
- k = n - j;
- kk += ks;
- wkr = 0.5 - c[nc - kk];
- wki = c[kk];
- xr = a[j] - a[k];
- xi = a[j + 1] + a[k + 1];
- yr = wkr * xr - wki * xi;
- yi = wkr * xi + wki * xr;
- a[j] -= yr;
- a[j + 1] -= yi;
- a[k] += yr;
- a[k + 1] -= yi;
- }
+ m = n >> 1;
+ ks = 2 * nc / m;
+ kk = 0;
+ for (j = 2; j < m; j += 2) {
+ k = n - j;
+ kk += ks;
+ wkr = 0.5 - c[nc - kk];
+ wki = c[kk];
+ xr = a[j] - a[k];
+ xi = a[j + 1] + a[k + 1];
+ yr = wkr * xr - wki * xi;
+ yi = wkr * xi + wki * xr;
+ a[j] -= yr;
+ a[j + 1] -= yi;
+ a[k] += yr;
+ a[k + 1] -= yi;
}
+ }
+ void rftbsub(int n, double *a, int nc, double *c) {
+ int j, k, kk, ks, m;
+ double wkr, wki, xr, xi, yr, yi;
- void rftbsub(int n, double *a, int nc, double *c)
- {
- int j, k, kk, ks, m;
- double wkr, wki, xr, xi, yr, yi;
-
- a[1] = -a[1];
- m = n >> 1;
- ks = 2 * nc / m;
- kk = 0;
- for (j = 2; j < m; j += 2) {
- k = n - j;
- kk += ks;
- wkr = 0.5 - c[nc - kk];
- wki = c[kk];
- xr = a[j] - a[k];
- xi = a[j + 1] + a[k + 1];
- yr = wkr * xr + wki * xi;
- yi = wkr * xi - wki * xr;
- a[j] -= yr;
- a[j + 1] = yi - a[j + 1];
- a[k] += yr;
- a[k + 1] = yi - a[k + 1];
- }
- a[m + 1] = -a[m + 1];
+ a[1] = -a[1];
+ m = n >> 1;
+ ks = 2 * nc / m;
+ kk = 0;
+ for (j = 2; j < m; j += 2) {
+ k = n - j;
+ kk += ks;
+ wkr = 0.5 - c[nc - kk];
+ wki = c[kk];
+ xr = a[j] - a[k];
+ xi = a[j + 1] + a[k + 1];
+ yr = wkr * xr + wki * xi;
+ yi = wkr * xi - wki * xr;
+ a[j] -= yr;
+ a[j + 1] = yi - a[j + 1];
+ a[k] += yr;
+ a[k + 1] = yi - a[k + 1];
}
- };
-
-
- /**
- * @internal
- * @brief Concrete FFT implementation
- */
- typedef OouraFFT AudioFFTImplementation;
+ a[m + 1] = -a[m + 1];
+ }
+};
+/**
+ * @internal
+ * @brief Concrete FFT implementation
+ */
+typedef OouraFFT AudioFFTImplementation;
#endif // AUDIOFFT_OOURA_USED
-
- // ================================================================
-
+// ================================================================
#ifdef AUDIOFFT_APPLE_ACCELERATE_USED
+/**
+ * @internal
+ * @class AppleAccelerateFFT
+ * @brief FFT implementation using the Apple Accelerate framework internally
+ */
+class AppleAccelerateFFT : public detail::AudioFFTImpl {
+public:
+ AppleAccelerateFFT()
+ : detail::AudioFFTImpl(), _size(0), _powerOf2(0), _fftSetup(0), _re(), _im() {}
- /**
- * @internal
- * @class AppleAccelerateFFT
- * @brief FFT implementation using the Apple Accelerate framework internally
- */
- class AppleAccelerateFFT : public detail::AudioFFTImpl
- {
- public:
- AppleAccelerateFFT() :
- detail::AudioFFTImpl(),
- _size(0),
- _powerOf2(0),
- _fftSetup(0),
- _re(),
- _im()
- {
+ AppleAccelerateFFT(const AppleAccelerateFFT &) = delete;
+ AppleAccelerateFFT &operator=(const AppleAccelerateFFT &) = delete;
+
+ virtual ~AppleAccelerateFFT() { init(0); }
+
+ virtual void init(size_t size) override {
+ if (_fftSetup) {
+ vDSP_destroy_fftsetup(_fftSetup);
+ _size = 0;
+ _powerOf2 = 0;
+ _fftSetup = 0;
+ _re.clear();
+ _im.clear();
}
- AppleAccelerateFFT(const AppleAccelerateFFT&) = delete;
- AppleAccelerateFFT& operator=(const AppleAccelerateFFT&) = delete;
-
- virtual ~AppleAccelerateFFT()
- {
- init(0);
- }
-
- virtual void init(size_t size) override
- {
- if (_fftSetup)
- {
- vDSP_destroy_fftsetup(_fftSetup);
- _size = 0;
- _powerOf2 = 0;
- _fftSetup = 0;
- _re.clear();
- _im.clear();
- }
-
- if (size > 0)
- {
- _size = size;
- _powerOf2 = 0;
- while ((1 << _powerOf2) < _size)
- {
- ++_powerOf2;
- }
- _fftSetup = vDSP_create_fftsetup(_powerOf2, FFT_RADIX2);
- _re.resize(_size / 2);
- _im.resize(_size / 2);
+ if (size > 0) {
+ _size = size;
+ _powerOf2 = 0;
+ while ((1 << _powerOf2) < _size) {
+ ++_powerOf2;
}
+ _fftSetup = vDSP_create_fftsetup(_powerOf2, FFT_RADIX2);
+ _re.resize(_size / 2);
+ _im.resize(_size / 2);
}
+ }
- virtual void fft(const float* data, float* re, float* im) override
- {
- const size_t size2 = _size / 2;
- DSPSplitComplex splitComplex;
- splitComplex.realp = re;
- splitComplex.imagp = im;
- vDSP_ctoz(reinterpret_cast(data), 2, &splitComplex, 1, size2);
- vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_FORWARD);
- const float factor = 0.5f;
- vDSP_vsmul(re, 1, &factor, re, 1, size2);
- vDSP_vsmul(im, 1, &factor, im, 1, size2);
- re[size2] = im[0];
- im[0] = 0.0f;
- im[size2] = 0.0f;
- }
+ virtual void fft(const float *data, float *re, float *im) override {
+ const size_t size2 = _size / 2;
+ DSPSplitComplex splitComplex;
+ splitComplex.realp = re;
+ splitComplex.imagp = im;
+ vDSP_ctoz(reinterpret_cast(data), 2, &splitComplex, 1, size2);
+ vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_FORWARD);
+ const float factor = 0.5f;
+ vDSP_vsmul(re, 1, &factor, re, 1, size2);
+ vDSP_vsmul(im, 1, &factor, im, 1, size2);
+ re[size2] = im[0];
+ im[0] = 0.0f;
+ im[size2] = 0.0f;
+ }
- virtual void ifft(float* data, const float* re, const float* im) override
- {
- const size_t size2 = _size / 2;
- ::memcpy(_re.data(), re, size2 * sizeof(float));
- ::memcpy(_im.data(), im, size2 * sizeof(float));
- _im[0] = re[size2];
- DSPSplitComplex splitComplex;
- splitComplex.realp = _re.data();
- splitComplex.imagp = _im.data();
- vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_INVERSE);
- vDSP_ztoc(&splitComplex, 1, reinterpret_cast(data), 2, size2);
- const float factor = 1.0f / static_cast(_size);
- vDSP_vsmul(data, 1, &factor, data, 1, _size);
- }
+ virtual void ifft(float *data, const float *re, const float *im) override {
+ const size_t size2 = _size / 2;
+ ::memcpy(_re.data(), re, size2 * sizeof(float));
+ ::memcpy(_im.data(), im, size2 * sizeof(float));
+ _im[0] = re[size2];
+ DSPSplitComplex splitComplex;
+ splitComplex.realp = _re.data();
+ splitComplex.imagp = _im.data();
+ vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_INVERSE);
+ vDSP_ztoc(&splitComplex, 1, reinterpret_cast(data), 2, size2);
+ const float factor = 1.0f / static_cast(_size);
+ vDSP_vsmul(data, 1, &factor, data, 1, _size);
+ }
- private:
- size_t _size;
- size_t _powerOf2;
- FFTSetup _fftSetup;
- std::vector _re;
- std::vector _im;
- };
-
-
- /**
- * @internal
- * @brief Concrete FFT implementation
- */
- typedef AppleAccelerateFFT AudioFFTImplementation;
+private:
+ size_t _size;
+ size_t _powerOf2;
+ FFTSetup _fftSetup;
+ std::vector _re;
+ std::vector _im;
+};
+/**
+ * @internal
+ * @brief Concrete FFT implementation
+ */
+typedef AppleAccelerateFFT AudioFFTImplementation;
#endif // AUDIOFFT_APPLE_ACCELERATE_USED
-
- // ================================================================
-
+// ================================================================
#ifdef AUDIOFFT_FFTW3_USED
+/**
+ * @internal
+ * @class FFTW3FFT
+ * @brief FFT implementation using FFTW3 internally (see fftw.org)
+ */
+class FFTW3FFT : public detail::AudioFFTImpl {
+public:
+ FFTW3FFT()
+ : detail::AudioFFTImpl(), _size(0), _complexSize(0), _planForward(0), _planBackward(0),
+ _data(0), _re(0), _im(0) {}
- /**
- * @internal
- * @class FFTW3FFT
- * @brief FFT implementation using FFTW3 internally (see fftw.org)
- */
- class FFTW3FFT : public detail::AudioFFTImpl
- {
- public:
- FFTW3FFT() :
- detail::AudioFFTImpl(),
- _size(0),
- _complexSize(0),
- _planForward(0),
- _planBackward(0),
- _data(0),
- _re(0),
- _im(0)
- {
- }
+ FFTW3FFT(const FFTW3FFT &) = delete;
+ FFTW3FFT &operator=(const FFTW3FFT &) = delete;
- FFTW3FFT(const FFTW3FFT&) = delete;
- FFTW3FFT& operator=(const FFTW3FFT&) = delete;
+ virtual ~FFTW3FFT() { init(0); }
- virtual ~FFTW3FFT()
- {
- init(0);
- }
+ virtual void init(size_t size) override {
+ if (_size != size) {
+ if (_size > 0) {
+ fftwf_destroy_plan(_planForward);
+ fftwf_destroy_plan(_planBackward);
+ _planForward = 0;
+ _planBackward = 0;
+ _size = 0;
+ _complexSize = 0;
- virtual void init(size_t size) override
- {
- if (_size != size)
- {
- if (_size > 0)
- {
- fftwf_destroy_plan(_planForward);
- fftwf_destroy_plan(_planBackward);
- _planForward = 0;
- _planBackward = 0;
- _size = 0;
- _complexSize = 0;
-
- if (_data)
- {
- fftwf_free(_data);
- _data = 0;
- }
-
- if (_re)
- {
- fftwf_free(_re);
- _re = 0;
- }
-
- if (_im)
- {
- fftwf_free(_im);
- _im = 0;
- }
+ if (_data) {
+ fftwf_free(_data);
+ _data = 0;
}
- if (size > 0)
- {
-
- _size = size;
- _complexSize = AudioFFT::ComplexSize(_size);
- const size_t complexSize = AudioFFT::ComplexSize(_size);
- _data = reinterpret_cast(fftwf_malloc(_size * sizeof(float)));
- _re = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float)));
- _im = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float)));
- fftwf_set_timelimit(0.01);
- fftw_iodim dim;
- dim.n = static_cast(size);
- dim.is = 1;
- dim.os = 1;
- _planForward = fftwf_plan_guru_split_dft_r2c(1, &dim, 0, 0, _data, _re, _im, FFTW_MEASURE);
- _planBackward = fftwf_plan_guru_split_dft_c2r(1, &dim, 0, 0, _re, _im, _data, FFTW_MEASURE);
+ if (_re) {
+ fftwf_free(_re);
+ _re = 0;
+ }
+
+ if (_im) {
+ fftwf_free(_im);
+ _im = 0;
}
}
+
+ if (size > 0) {
+
+ _size = size;
+ _complexSize = AudioFFT::ComplexSize(_size);
+ const size_t complexSize = AudioFFT::ComplexSize(_size);
+ _data = reinterpret_cast(fftwf_malloc(_size * sizeof(float)));
+ _re = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float)));
+ _im = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float)));
+ fftwf_set_timelimit(0.01);
+ fftw_iodim dim;
+ dim.n = static_cast(size);
+ dim.is = 1;
+ dim.os = 1;
+ _planForward = fftwf_plan_guru_split_dft_r2c(1, &dim, 0, 0, _data, _re, _im, FFTW_MEASURE);
+ _planBackward = fftwf_plan_guru_split_dft_c2r(1, &dim, 0, 0, _re, _im, _data, FFTW_MEASURE);
+ }
}
+ }
- virtual void fft(const float* data, float* re, float* im) override
- {
- ::memcpy(_data, data, _size * sizeof(float));
- fftwf_execute_split_dft_r2c(_planForward, _data, _re, _im);
- ::memcpy(re, _re, _complexSize * sizeof(float));
- ::memcpy(im, _im, _complexSize * sizeof(float));
- }
+ virtual void fft(const float *data, float *re, float *im) override {
+ ::memcpy(_data, data, _size * sizeof(float));
+ fftwf_execute_split_dft_r2c(_planForward, _data, _re, _im);
+ ::memcpy(re, _re, _complexSize * sizeof(float));
+ ::memcpy(im, _im, _complexSize * sizeof(float));
+ }
- virtual void ifft(float* data, const float* re, const float* im) override
- {
- ::memcpy(_re, re, _complexSize * sizeof(float));
- ::memcpy(_im, im, _complexSize * sizeof(float));
- fftwf_execute_split_dft_c2r(_planBackward, _re, _im, _data);
- detail::ScaleBuffer(data, _data, 1.0f / static_cast(_size), _size);
- }
+ virtual void ifft(float *data, const float *re, const float *im) override {
+ ::memcpy(_re, re, _complexSize * sizeof(float));
+ ::memcpy(_im, im, _complexSize * sizeof(float));
+ fftwf_execute_split_dft_c2r(_planBackward, _re, _im, _data);
+ detail::ScaleBuffer(data, _data, 1.0f / static_cast(_size), _size);
+ }
- private:
- size_t _size;
- size_t _complexSize;
- fftwf_plan _planForward;
- fftwf_plan _planBackward;
- float* _data;
- float* _re;
- float* _im;
- };
-
-
- /**
- * @internal
- * @brief Concrete FFT implementation
- */
- typedef FFTW3FFT AudioFFTImplementation;
+private:
+ size_t _size;
+ size_t _complexSize;
+ fftwf_plan _planForward;
+ fftwf_plan _planBackward;
+ float *_data;
+ float *_re;
+ float *_im;
+};
+/**
+ * @internal
+ * @brief Concrete FFT implementation
+ */
+typedef FFTW3FFT AudioFFTImplementation;
#endif // AUDIOFFT_FFTW3_USED
+// =============================================================
- // =============================================================
+AudioFFT::AudioFFT() : _impl(new AudioFFTImplementation()) {}
+AudioFFT::~AudioFFT() {}
- AudioFFT::AudioFFT() :
- _impl(new AudioFFTImplementation())
- {
- }
+void AudioFFT::init(size_t size) {
+ assert(detail::IsPowerOf2(size));
+ _impl->init(size);
+}
+void AudioFFT::fft(const float *data, float *re, float *im) { _impl->fft(data, re, im); }
- AudioFFT::~AudioFFT()
- {
- }
+void AudioFFT::ifft(float *data, const float *re, const float *im) { _impl->ifft(data, re, im); }
+size_t AudioFFT::ComplexSize(size_t size) { return (size / 2) + 1; }
- void AudioFFT::init(size_t size)
- {
- assert(detail::IsPowerOf2(size));
- _impl->init(size);
- }
-
-
- void AudioFFT::fft(const float* data, float* re, float* im)
- {
- _impl->fft(data, re, im);
- }
-
-
- void AudioFFT::ifft(float* data, const float* re, const float* im)
- {
- _impl->ifft(data, re, im);
- }
-
-
- size_t AudioFFT::ComplexSize(size_t size)
- {
- return (size / 2) + 1;
- }
-
-} // End of namespace
+} // namespace audiofft
diff --git a/FFTConvolver/AudioFFT.h b/FFTConvolver/AudioFFT.h
old mode 100755
new mode 100644
index dadf32e6..04bc7d07
--- a/FFTConvolver/AudioFFT.h
+++ b/FFTConvolver/AudioFFT.h
@@ -22,147 +22,143 @@
#ifndef _AUDIOFFT_H
#define _AUDIOFFT_H
-
/**
-* AudioFFT provides real-to-complex/complex-to-real FFT routines.
-*
-* Features:
-*
-* - Real-complex FFT and complex-real inverse FFT for power-of-2-sized real data.
-*
-* - Uniform interface to different FFT implementations (currently Ooura, FFTW3 and Apple Accelerate).
-*
-* - Complex data is handled in "split-complex" format, i.e. there are separate
-* arrays for the real and imaginary parts which can be useful for SIMD optimizations
-* (split-complex arrays have to be of length (size/2+1) representing bins from DC
-* to Nyquist frequency).
-*
-* - Output is "ready to use" (all scaling etc. is already handled internally).
-*
-* - No allocations/deallocations after the initialization which makes it usable
-* for real-time audio applications (that's what I wrote it for and using it).
-*
-*
-* How to use it in your project:
-*
-* - Add the .h and .cpp file to your project - that's all.
-*
-* - To get extra speed, you can link FFTW3 to your project and define
-* AUDIOFFT_FFTW3 (however, please check whether your project suits the
-* according license).
-*
-* - To get the best speed on Apple platforms, you can link the Apple
-* Accelerate framework to your project and define
-* AUDIOFFT_APPLE_ACCELERATE (however, please check whether your
-* project suits the according license).
-*
-*
-* Remarks:
-*
-* - AudioFFT is not intended to be the fastest FFT, but to be a fast-enough
-* FFT suitable for most audio applications.
-*
-* - AudioFFT uses the quite liberal MIT license.
-*
-*
-* Example usage:
-* @code
-* #include "AudioFFT.h"
-*
-* void Example()
-* {
-* const size_t fftSize = 1024; // Needs to be power of 2!
-*
-* std::vector input(fftSize, 0.0f);
-* std::vector re(audiofft::AudioFFT::ComplexSize(fftSize));
-* std::vector im(audiofft::AudioFFT::ComplexSize(fftSize));
-* std::vector output(fftSize);
-*
-* audiofft::AudioFFT fft;
-* fft.init(1024);
-* fft.fft(input.data(), re.data(), im.data());
-* fft.ifft(output.data(), re.data(), im.data());
-* }
-* @endcode
-*/
-
+ * AudioFFT provides real-to-complex/complex-to-real FFT routines.
+ *
+ * Features:
+ *
+ * - Real-complex FFT and complex-real inverse FFT for power-of-2-sized real data.
+ *
+ * - Uniform interface to different FFT implementations (currently Ooura, FFTW3 and Apple
+ * Accelerate).
+ *
+ * - Complex data is handled in "split-complex" format, i.e. there are separate
+ * arrays for the real and imaginary parts which can be useful for SIMD optimizations
+ * (split-complex arrays have to be of length (size/2+1) representing bins from DC
+ * to Nyquist frequency).
+ *
+ * - Output is "ready to use" (all scaling etc. is already handled internally).
+ *
+ * - No allocations/deallocations after the initialization which makes it usable
+ * for real-time audio applications (that's what I wrote it for and using it).
+ *
+ *
+ * How to use it in your project:
+ *
+ * - Add the .h and .cpp file to your project - that's all.
+ *
+ * - To get extra speed, you can link FFTW3 to your project and define
+ * AUDIOFFT_FFTW3 (however, please check whether your project suits the
+ * according license).
+ *
+ * - To get the best speed on Apple platforms, you can link the Apple
+ * Accelerate framework to your project and define
+ * AUDIOFFT_APPLE_ACCELERATE (however, please check whether your
+ * project suits the according license).
+ *
+ *
+ * Remarks:
+ *
+ * - AudioFFT is not intended to be the fastest FFT, but to be a fast-enough
+ * FFT suitable for most audio applications.
+ *
+ * - AudioFFT uses the quite liberal MIT license.
+ *
+ *
+ * Example usage:
+ * @code
+ * #include "AudioFFT.h"
+ *
+ * void Example()
+ * {
+ * const size_t fftSize = 1024; // Needs to be power of 2!
+ *
+ * std::vector input(fftSize, 0.0f);
+ * std::vector re(audiofft::AudioFFT::ComplexSize(fftSize));
+ * std::vector im(audiofft::AudioFFT::ComplexSize(fftSize));
+ * std::vector output(fftSize);
+ *
+ * audiofft::AudioFFT fft;
+ * fft.init(1024);
+ * fft.fft(input.data(), re.data(), im.data());
+ * fft.ifft(output.data(), re.data(), im.data());
+ * }
+ * @endcode
+ */
#include
#include
+namespace audiofft {
-namespace audiofft
-{
+namespace detail {
+class AudioFFTImpl;
+}
- namespace detail
- {
- class AudioFFTImpl;
- }
+// =============================================================
+/**
+ * @class AudioFFT
+ * @brief Performs 1D FFTs
+ */
+class AudioFFT {
+public:
+ /**
+ * @brief Constructor
+ */
+ AudioFFT();
- // =============================================================
-
+ AudioFFT(const AudioFFT &) = delete;
+ AudioFFT &operator=(const AudioFFT &) = delete;
/**
- * @class AudioFFT
- * @brief Performs 1D FFTs
+ * @brief Destructor
*/
- class AudioFFT
- {
- public:
- /**
- * @brief Constructor
- */
- AudioFFT();
-
- AudioFFT(const AudioFFT&) = delete;
- AudioFFT& operator=(const AudioFFT&) = delete;
-
- /**
- * @brief Destructor
- */
- ~AudioFFT();
-
- /**
- * @brief Initializes the FFT object
- * @param size Size of the real input (must be power 2)
- */
- void init(size_t size);
-
- /**
- * @brief Performs the forward FFT
- * @param data The real input data (has to be of the length as specified in init())
- * @param re The real part of the complex output (has to be of length as returned by ComplexSize())
- * @param im The imaginary part of the complex output (has to be of length as returned by ComplexSize())
- */
- void fft(const float* data, float* re, float* im);
-
- /**
- * @brief Performs the inverse FFT
- * @param data The real output data (has to be of the length as specified in init())
- * @param re The real part of the complex input (has to be of length as returned by ComplexSize())
- * @param im The imaginary part of the complex input (has to be of length as returned by ComplexSize())
- */
- void ifft(float* data, const float* re, const float* im);
-
- /**
- * @brief Calculates the necessary size of the real/imaginary complex arrays
- * @param size The size of the real data
- * @return The size of the real/imaginary complex arrays
- */
- static size_t ComplexSize(size_t size);
-
- private:
- std::unique_ptr _impl;
- };
-
+ ~AudioFFT();
/**
- * @deprecated
- * @brief Let's keep an AudioFFTBase type around for now because it has been here already in the 1st version in order to avoid breaking existing code.
+ * @brief Initializes the FFT object
+ * @param size Size of the real input (must be power 2)
*/
- typedef AudioFFT AudioFFTBase;
+ void init(size_t size);
-} // End of namespace
+ /**
+ * @brief Performs the forward FFT
+ * @param data The real input data (has to be of the length as specified in init())
+ * @param re The real part of the complex output (has to be of length as returned by
+ * ComplexSize())
+ * @param im The imaginary part of the complex output (has to be of length as returned by
+ * ComplexSize())
+ */
+ void fft(const float *data, float *re, float *im);
+
+ /**
+ * @brief Performs the inverse FFT
+ * @param data The real output data (has to be of the length as specified in init())
+ * @param re The real part of the complex input (has to be of length as returned by ComplexSize())
+ * @param im The imaginary part of the complex input (has to be of length as returned by
+ * ComplexSize())
+ */
+ void ifft(float *data, const float *re, const float *im);
+
+ /**
+ * @brief Calculates the necessary size of the real/imaginary complex arrays
+ * @param size The size of the real data
+ * @return The size of the real/imaginary complex arrays
+ */
+ static size_t ComplexSize(size_t size);
+
+private:
+ std::unique_ptr _impl;
+};
+
+/**
+ * @deprecated
+ * @brief Let's keep an AudioFFTBase type around for now because it has been here already in the 1st
+ * version in order to avoid breaking existing code.
+ */
+typedef AudioFFT AudioFFTBase;
+
+} // namespace audiofft
#endif // Header guard
diff --git a/FFTConvolver/ConvolverThreadPool.cpp b/FFTConvolver/ConvolverThreadPool.cpp
index ef969572..f41befd1 100644
--- a/FFTConvolver/ConvolverThreadPool.cpp
+++ b/FFTConvolver/ConvolverThreadPool.cpp
@@ -140,8 +140,11 @@ void ConvolverThreadPool::clearState(size_t convolverId) {
if (convolverId < _convolvers.size()) {
waitForAll();
} else {
- debug(1, "assert(convolverId < _convolvers.size()) failed, with convolverId: %u and _convolvers.size(): %u.", convolverId, _convolvers.size());
- }
+ debug(1,
+ "assert(convolverId < _convolvers.size()) failed, with convolverId: %u and "
+ "_convolvers.size(): %u.",
+ convolverId, _convolvers.size());
+ }
}
/* this is the old version
diff --git a/FFTConvolver/ConvolverThreadPool.h b/FFTConvolver/ConvolverThreadPool.h
index a690b34f..fc49daa1 100644
--- a/FFTConvolver/ConvolverThreadPool.h
+++ b/FFTConvolver/ConvolverThreadPool.h
@@ -61,7 +61,7 @@ public:
// Get the number of threads
size_t getNumThreads() const { return _threads.size(); }
-
+
void shutdown();
private:
diff --git a/FFTConvolver/FFTConvolver.cpp b/FFTConvolver/FFTConvolver.cpp
old mode 100755
new mode 100644
index 693b5b0f..d79620eb
--- a/FFTConvolver/FFTConvolver.cpp
+++ b/FFTConvolver/FFTConvolver.cpp
@@ -20,47 +20,25 @@
#include
#include
-#if defined (FFTCONVOLVER_USE_SSE)
- #include
+#if defined(FFTCONVOLVER_USE_SSE)
+#include
#endif
+namespace fftconvolver {
-namespace fftconvolver
-{
+FFTConvolver::FFTConvolver()
+ : _blockSize(0), _segSize(0), _segCount(0), _fftComplexSize(0), _segments(), _segmentsIR(),
+ _fftBuffer(), _fft(), _preMultiplied(), _conv(), _overlap(), _current(0), _inputBuffer(),
+ _inputBufferFill(0) {}
-FFTConvolver::FFTConvolver() :
- _blockSize(0),
- _segSize(0),
- _segCount(0),
- _fftComplexSize(0),
- _segments(),
- _segmentsIR(),
- _fftBuffer(),
- _fft(),
- _preMultiplied(),
- _conv(),
- _overlap(),
- _current(0),
- _inputBuffer(),
- _inputBufferFill(0)
-{
-}
+FFTConvolver::~FFTConvolver() { reset(); }
-
-FFTConvolver::~FFTConvolver()
-{
- reset();
-}
-
-
-void FFTConvolver::reset()
-{
- for (size_t i=0; i<_segCount; ++i)
- {
+void FFTConvolver::reset() {
+ for (size_t i = 0; i < _segCount; ++i) {
delete _segments[i];
delete _segmentsIR[i];
}
-
+
_blockSize = 0;
_segSize = 0;
_segCount = 0;
@@ -77,115 +55,101 @@ void FFTConvolver::reset()
_inputBufferFill = 0;
}
-void FFTConvolver::clearState()
-{
- if (_segCount == 0)
- {
+void FFTConvolver::clearState() {
+ if (_segCount == 0) {
return; // Not initialized
}
-
+
_inputBuffer.setZero();
_inputBufferFill = 0;
_overlap.setZero();
-
- for (size_t i = 0; i < _segCount; ++i)
- {
+
+ for (size_t i = 0; i < _segCount; ++i) {
_segments[i]->setZero();
}
-
+
_preMultiplied.setZero();
_conv.setZero();
_current = 0;
}
-
-bool FFTConvolver::init(size_t blockSize, const Sample* ir, size_t irLen)
-{
+
+bool FFTConvolver::init(size_t blockSize, const Sample *ir, size_t irLen) {
reset();
- if (blockSize == 0)
- {
+ if (blockSize == 0) {
return false;
}
-
+
// Ignore zeros at the end of the impulse response because they only waste computation time
- while (irLen > 0 && ::fabs(ir[irLen-1]) < 0.000001f)
- {
+ while (irLen > 0 && ::fabs(ir[irLen - 1]) < 0.000001f) {
--irLen;
}
- if (irLen == 0)
- {
+ if (irLen == 0) {
return true;
}
-
+
_blockSize = NextPowerOf2(blockSize);
_segSize = 2 * _blockSize;
- _segCount = static_cast(::ceil(static_cast(irLen) / static_cast(_blockSize)));
+ _segCount =
+ static_cast(::ceil(static_cast(irLen) / static_cast(_blockSize)));
_fftComplexSize = audiofft::AudioFFT::ComplexSize(_segSize);
-
+
// FFT
_fft.init(_segSize);
_fftBuffer.resize(_segSize);
-
+
// Prepare segments
- for (size_t i=0; i<_segCount; ++i)
- {
- _segments.push_back(new SplitComplex(_fftComplexSize));
+ for (size_t i = 0; i < _segCount; ++i) {
+ _segments.push_back(new SplitComplex(_fftComplexSize));
}
-
+
// Prepare IR
- for (size_t i=0; i<_segCount; ++i)
- {
- SplitComplex* segment = new SplitComplex(_fftComplexSize);
+ for (size_t i = 0; i < _segCount; ++i) {
+ SplitComplex *segment = new SplitComplex(_fftComplexSize);
const size_t remaining = irLen - (i * _blockSize);
const size_t sizeCopy = (remaining >= _blockSize) ? _blockSize : remaining;
- CopyAndPad(_fftBuffer, &ir[i*_blockSize], sizeCopy);
+ CopyAndPad(_fftBuffer, &ir[i * _blockSize], sizeCopy);
_fft.fft(_fftBuffer.data(), segment->re(), segment->im());
_segmentsIR.push_back(segment);
}
-
- // Prepare convolution buffers
+
+ // Prepare convolution buffers
_preMultiplied.resize(_fftComplexSize);
_conv.resize(_fftComplexSize);
_overlap.resize(_blockSize);
-
+
// Prepare input buffer
_inputBuffer.resize(_blockSize);
_inputBufferFill = 0;
// Reset current position
_current = 0;
-
+
return true;
}
-
-void FFTConvolver::process(const Sample* input, Sample* output, size_t len)
-{
- if (_segCount == 0)
- {
+void FFTConvolver::process(const Sample *input, Sample *output, size_t len) {
+ if (_segCount == 0) {
::memset(output, 0, len * sizeof(Sample));
return;
}
size_t processed = 0;
- while (processed < len)
- {
+ while (processed < len) {
const bool inputBufferWasEmpty = (_inputBufferFill == 0);
- const size_t processing = std::min(len-processed, _blockSize-_inputBufferFill);
+ const size_t processing = std::min(len - processed, _blockSize - _inputBufferFill);
const size_t inputBufferPos = _inputBufferFill;
- ::memcpy(_inputBuffer.data()+inputBufferPos, input+processed, processing * sizeof(Sample));
+ ::memcpy(_inputBuffer.data() + inputBufferPos, input + processed, processing * sizeof(Sample));
// Forward FFT
- CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize);
+ CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize);
_fft.fft(_fftBuffer.data(), _segments[_current]->re(), _segments[_current]->im());
// Complex multiplication
- if (inputBufferWasEmpty)
- {
+ if (inputBufferWasEmpty) {
_preMultiplied.setZero();
- for (size_t i=1; i<_segCount; ++i)
- {
+ for (size_t i = 1; i < _segCount; ++i) {
const size_t indexIr = i;
const size_t indexAudio = (_current + i) % _segCount;
ComplexMultiplyAccumulate(_preMultiplied, *_segmentsIR[indexIr], *_segments[indexAudio]);
@@ -198,18 +162,18 @@ void FFTConvolver::process(const Sample* input, Sample* output, size_t len)
_fft.ifft(_fftBuffer.data(), _conv.re(), _conv.im());
// Add overlap
- Sum(output+processed, _fftBuffer.data()+inputBufferPos, _overlap.data()+inputBufferPos, processing);
+ Sum(output + processed, _fftBuffer.data() + inputBufferPos, _overlap.data() + inputBufferPos,
+ processing);
// Input buffer full => Next block
_inputBufferFill += processing;
- if (_inputBufferFill == _blockSize)
- {
+ if (_inputBufferFill == _blockSize) {
// Input buffer is empty again now
_inputBuffer.setZero();
_inputBufferFill = 0;
// Save the overlap
- ::memcpy(_overlap.data(), _fftBuffer.data()+_blockSize, _blockSize * sizeof(Sample));
+ ::memcpy(_overlap.data(), _fftBuffer.data() + _blockSize, _blockSize * sizeof(Sample));
// Update current segment
_current = (_current > 0) ? (_current - 1) : (_segCount - 1);
@@ -218,5 +182,5 @@ void FFTConvolver::process(const Sample* input, Sample* output, size_t len)
processed += processing;
}
}
-
+
} // End of namespace fftconvolver
diff --git a/FFTConvolver/FFTConvolver.h b/FFTConvolver/FFTConvolver.h
old mode 100755
new mode 100644
index 125fd12b..a6ed4aba
--- a/FFTConvolver/FFTConvolver.h
+++ b/FFTConvolver/FFTConvolver.h
@@ -27,61 +27,58 @@
#include
-
-namespace fftconvolver
-{
+namespace fftconvolver {
/**
-* @class FFTConvolver
-* @brief Implementation of a partitioned FFT convolution algorithm with uniform block size
-*
-* Some notes on how to use it:
-*
-* - After initialization with an impulse response, subsequent data portions of
-* arbitrary length can be convolved. The convolver internally can handle
-* this by using appropriate buffering.
-*
-* - The convolver works without "latency" (except for the required
-* processing time, of course), i.e. the output always is the convolved
-* input for each processing call.
-*
-* - The convolver is suitable for real-time processing which means that no
-* "unpredictable" operations like allocations, locking, API calls, etc. are
-* performed during processing (all necessary allocations and preparations take
-* place during initialization).
-*/
-class FFTConvolver
-{
+ * @class FFTConvolver
+ * @brief Implementation of a partitioned FFT convolution algorithm with uniform block size
+ *
+ * Some notes on how to use it:
+ *
+ * - After initialization with an impulse response, subsequent data portions of
+ * arbitrary length can be convolved. The convolver internally can handle
+ * this by using appropriate buffering.
+ *
+ * - The convolver works without "latency" (except for the required
+ * processing time, of course), i.e. the output always is the convolved
+ * input for each processing call.
+ *
+ * - The convolver is suitable for real-time processing which means that no
+ * "unpredictable" operations like allocations, locking, API calls, etc. are
+ * performed during processing (all necessary allocations and preparations take
+ * place during initialization).
+ */
+class FFTConvolver {
public:
- FFTConvolver();
+ FFTConvolver();
virtual ~FFTConvolver();
-
- /**
- * @brief Initializes the convolver
- * @param blockSize Block size internally used by the convolver (partition size)
- * @param ir The impulse response
- * @param irLen Length of the impulse response
- * @return true: Success - false: Failed
- */
- bool init(size_t blockSize, const Sample* ir, size_t irLen);
/**
- * @brief Convolves the the given input samples and immediately outputs the result
- * @param input The input samples
- * @param output The convolution result
- * @param len Number of input/output samples
- */
- void process(const Sample* input, Sample* output, size_t len);
+ * @brief Initializes the convolver
+ * @param blockSize Block size internally used by the convolver (partition size)
+ * @param ir The impulse response
+ * @param irLen Length of the impulse response
+ * @return true: Success - false: Failed
+ */
+ bool init(size_t blockSize, const Sample *ir, size_t irLen);
/**
- * @brief Resets the convolver and discards the set impulse response
- */
+ * @brief Convolves the the given input samples and immediately outputs the result
+ * @param input The input samples
+ * @param output The convolution result
+ * @param len Number of input/output samples
+ */
+ void process(const Sample *input, Sample *output, size_t len);
+
+ /**
+ * @brief Resets the convolver and discards the set impulse response
+ */
void reset();
-
+
/**
- * @brief Clears audio history
- */
-
+ * @brief Clears audio history
+ */
+
void clearState();
private:
@@ -89,8 +86,8 @@ private:
size_t _segSize;
size_t _segCount;
size_t _fftComplexSize;
- std::vector _segments;
- std::vector _segmentsIR;
+ std::vector _segments;
+ std::vector _segmentsIR;
SampleBuffer _fftBuffer;
audiofft::AudioFFT _fft;
SplitComplex _preMultiplied;
@@ -101,10 +98,10 @@ private:
size_t _inputBufferFill;
// Prevent uncontrolled usage
- FFTConvolver(const FFTConvolver&);
- FFTConvolver& operator=(const FFTConvolver&);
+ FFTConvolver(const FFTConvolver &);
+ FFTConvolver &operator=(const FFTConvolver &);
};
-
+
} // End of namespace fftconvolver
#endif // Header guard
diff --git a/FFTConvolver/Utilities.cpp b/FFTConvolver/Utilities.cpp
index 3d00e6f5..fde81851 100644
--- a/FFTConvolver/Utilities.cpp
+++ b/FFTConvolver/Utilities.cpp
@@ -21,12 +21,9 @@
#include "Utilities.h"
+namespace fftconvolver {
-namespace fftconvolver
-{
-
-bool SSEEnabled()
-{
+bool SSEEnabled() {
#if defined(FFTCONVOLVER_USE_SSE)
return true;
#else
@@ -34,47 +31,35 @@ bool SSEEnabled()
#endif
}
-
-void Sum(Sample* FFTCONVOLVER_RESTRICT result,
- const Sample* FFTCONVOLVER_RESTRICT a,
- const Sample* FFTCONVOLVER_RESTRICT b,
- size_t len)
-{
+void Sum(Sample *FFTCONVOLVER_RESTRICT result, const Sample *FFTCONVOLVER_RESTRICT a,
+ const Sample *FFTCONVOLVER_RESTRICT b, size_t len) {
const size_t end4 = 4 * (len / 4);
- for (size_t i=0; i
#include
-
-namespace fftconvolver
-{
+namespace fftconvolver {
#if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
- #if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE)
- #define FFTCONVOLVER_USE_SSE
- #endif
+#if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE)
+#define FFTCONVOLVER_USE_SSE
+#endif
#endif
-
-#if defined (FFTCONVOLVER_USE_SSE)
- #include
+#if defined(FFTCONVOLVER_USE_SSE)
+#include
#endif
-
#if defined(__GNUC__)
- #define FFTCONVOLVER_RESTRICT __restrict__
+#define FFTCONVOLVER_RESTRICT __restrict__
#else
- #define FFTCONVOLVER_RESTRICT
+#define FFTCONVOLVER_RESTRICT
#endif
-
/**
-* @brief Returns whether SSE optimization for the convolver is enabled
-* @return true: Enabled - false: Disabled
-*/
+ * @brief Returns whether SSE optimization for the convolver is enabled
+ * @return true: Enabled - false: Disabled
+ */
bool SSEEnabled();
-
/**
-* @class Buffer
-* @brief Simple buffer implementation (uses 16-byte alignment if SSE optimization is enabled)
-*/
-template
-class Buffer
-{
-public:
- explicit Buffer(size_t initialSize = 0) :
- _data(0),
- _size(0)
- {
- resize(initialSize);
- }
+ * @class Buffer
+ * @brief Simple buffer implementation (uses 16-byte alignment if SSE optimization is enabled)
+ */
+template class Buffer {
+public:
+ explicit Buffer(size_t initialSize = 0) : _data(0), _size(0) { resize(initialSize); }
- virtual ~Buffer()
- {
- clear();
- }
+ virtual ~Buffer() { clear(); }
- void clear()
- {
+ void clear() {
deallocate(_data);
_data = 0;
_size = 0;
}
- void resize(size_t size)
- {
- if (_size != size)
- {
+ void resize(size_t size) {
+ if (_size != size) {
clear();
- if (size > 0)
- {
+ if (size > 0) {
assert(!_data && _size == 0);
_data = allocate(size);
_size = size;
@@ -101,172 +81,118 @@ public:
setZero();
}
- size_t size() const
- {
- return _size;
- }
+ size_t size() const { return _size; }
- void setZero()
- {
- ::memset(_data, 0, _size * sizeof(T));
- }
+ void setZero() { ::memset(_data, 0, _size * sizeof(T)); }
- void copyFrom(const Buffer& other)
- {
+ void copyFrom(const Buffer &other) {
assert(_size == other._size);
- if (this != &other)
- {
+ if (this != &other) {
::memcpy(_data, other._data, _size * sizeof(T));
}
}
- T& operator[](size_t index)
- {
+ T &operator[](size_t index) {
assert(_data && index < _size);
return _data[index];
}
- const T& operator[](size_t index) const
- {
+ const T &operator[](size_t index) const {
assert(_data && index < _size);
return _data[index];
}
- operator bool() const
- {
- return (_data != 0 && _size > 0);
- }
+ operator bool() const { return (_data != 0 && _size > 0); }
- T* data()
- {
- return _data;
- }
+ T *data() { return _data; }
- const T* data() const
- {
- return _data;
- }
+ const T *data() const { return _data; }
- static void Swap(Buffer& a, Buffer& b)
- {
+ static void Swap(Buffer &a, Buffer &b) {
std::swap(a._data, b._data);
std::swap(a._size, b._size);
}
private:
- T* allocate(size_t size)
- {
+ T *allocate(size_t size) {
#if defined(FFTCONVOLVER_USE_SSE)
- return static_cast(_mm_malloc(size * sizeof(T), 16));
+ return static_cast(_mm_malloc(size * sizeof(T), 16));
#else
return new T[size];
#endif
}
-
- void deallocate(T* ptr)
- {
+
+ void deallocate(T *ptr) {
#if defined(FFTCONVOLVER_USE_SSE)
_mm_free(ptr);
#else
- delete [] ptr;
+ delete[] ptr;
#endif
}
- T* _data;
+ T *_data;
size_t _size;
// Prevent uncontrolled usage
- Buffer(const Buffer&);
- Buffer& operator=(const Buffer&);
+ Buffer(const Buffer &);
+ Buffer &operator=(const Buffer &);
};
-
/**
-* @brief Type of one sample
-*/
+ * @brief Type of one sample
+ */
typedef float Sample;
-
/**
-* @brief Buffer for samples
-*/
+ * @brief Buffer for samples
+ */
typedef Buffer SampleBuffer;
-
/**
-* @class SplitComplex
-* @brief Buffer for split-complex representation of FFT results
-*
-* The split-complex representation stores the real and imaginary parts
-* of FFT results in two different memory buffers which is useful e.g. for
-* SIMD optimizations.
-*/
-class SplitComplex
-{
+ * @class SplitComplex
+ * @brief Buffer for split-complex representation of FFT results
+ *
+ * The split-complex representation stores the real and imaginary parts
+ * of FFT results in two different memory buffers which is useful e.g. for
+ * SIMD optimizations.
+ */
+class SplitComplex {
public:
- explicit SplitComplex(size_t initialSize = 0) :
- _size(0),
- _re(),
- _im()
- {
- resize(initialSize);
- }
+ explicit SplitComplex(size_t initialSize = 0) : _size(0), _re(), _im() { resize(initialSize); }
- ~SplitComplex()
- {
- clear();
- }
+ ~SplitComplex() { clear(); }
- void clear()
- {
+ void clear() {
_re.clear();
_im.clear();
_size = 0;
}
- void resize(size_t newSize)
- {
+ void resize(size_t newSize) {
_re.resize(newSize);
_im.resize(newSize);
_size = newSize;
}
- void setZero()
- {
+ void setZero() {
_re.setZero();
_im.setZero();
}
- void copyFrom(const SplitComplex& other)
- {
+ void copyFrom(const SplitComplex &other) {
_re.copyFrom(other._re);
_im.copyFrom(other._im);
}
- Sample* re()
- {
- return _re.data();
- }
+ Sample *re() { return _re.data(); }
- const Sample* re() const
- {
- return _re.data();
- }
+ const Sample *re() const { return _re.data(); }
- Sample* im()
- {
- return _im.data();
- }
+ Sample *im() { return _im.data(); }
- const Sample* im() const
- {
- return _im.data();
- }
+ const Sample *im() const { return _im.data(); }
- size_t size() const
- {
- return _size;
- }
+ size_t size() const { return _size; }
private:
size_t _size;
@@ -274,82 +200,68 @@ private:
SampleBuffer _im;
// Prevent uncontrolled usage
- SplitComplex(const SplitComplex&);
- SplitComplex& operator=(const SplitComplex&);
+ SplitComplex(const SplitComplex &);
+ SplitComplex &operator=(const SplitComplex &);
};
-
/**
-* @brief Returns the next power of 2 of a given number
-* @param val The number
-* @return The next power of 2
-*/
-template
-T NextPowerOf2(const T& val)
-{
+ * @brief Returns the next power of 2 of a given number
+ * @param val The number
+ * @return The next power of 2
+ */
+template T NextPowerOf2(const T &val) {
T nextPowerOf2 = 1;
- while (nextPowerOf2 < val)
- {
+ while (nextPowerOf2 < val) {
nextPowerOf2 *= 2;
}
return nextPowerOf2;
}
-
/**
-* @brief Sums two given sample arrays
-* @param result The result array
-* @param a The 1st array
-* @param b The 2nd array
-* @param len The length of the arrays
-*/
-void Sum(Sample* FFTCONVOLVER_RESTRICT result,
- const Sample* FFTCONVOLVER_RESTRICT a,
- const Sample* FFTCONVOLVER_RESTRICT b,
- size_t len);
-
+ * @brief Sums two given sample arrays
+ * @param result The result array
+ * @param a The 1st array
+ * @param b The 2nd array
+ * @param len The length of the arrays
+ */
+void Sum(Sample *FFTCONVOLVER_RESTRICT result, const Sample *FFTCONVOLVER_RESTRICT a,
+ const Sample *FFTCONVOLVER_RESTRICT b, size_t len);
/**
-* @brief Copies a source array into a destination buffer and pads the destination buffer with zeros
-* @param dest The destination buffer
-* @param src The source array
-* @param srcSize The size of the source array
-*/
-template
-void CopyAndPad(Buffer& dest, const T* src, size_t srcSize)
-{
+ * @brief Copies a source array into a destination buffer and pads the destination buffer with zeros
+ * @param dest The destination buffer
+ * @param src The source array
+ * @param srcSize The size of the source array
+ */
+template void CopyAndPad(Buffer &dest, const T *src, size_t srcSize) {
assert(dest.size() >= srcSize);
::memcpy(dest.data(), src, srcSize * sizeof(T));
- ::memset(dest.data() + srcSize, 0, (dest.size()-srcSize) * sizeof(T));
+ ::memset(dest.data() + srcSize, 0, (dest.size() - srcSize) * sizeof(T));
}
+/**
+ * @brief Adds the complex product of two split-complex buffers to a result buffer
+ * @param result The result buffer
+ * @param a The 1st factor of the complex product
+ * @param b The 2nd factor of the complex product
+ */
+void ComplexMultiplyAccumulate(SplitComplex &result, const SplitComplex &a, const SplitComplex &b);
/**
-* @brief Adds the complex product of two split-complex buffers to a result buffer
-* @param result The result buffer
-* @param a The 1st factor of the complex product
-* @param b The 2nd factor of the complex product
-*/
-void ComplexMultiplyAccumulate(SplitComplex& result, const SplitComplex& a, const SplitComplex& b);
+ * @brief Adds the complex product of two split-complex arrays to a result array
+ * @param re The real part of the result buffer
+ * @param im The imaginary part of the result buffer
+ * @param reA The real part of the 1st factor of the complex product
+ * @param imA The imaginary part of the 1st factor of the complex product
+ * @param reB The real part of the 2nd factor of the complex product
+ * @param imB The imaginary part of the 2nd factor of the complex product
+ */
+void ComplexMultiplyAccumulate(Sample *FFTCONVOLVER_RESTRICT re, Sample *FFTCONVOLVER_RESTRICT im,
+ const Sample *FFTCONVOLVER_RESTRICT reA,
+ const Sample *FFTCONVOLVER_RESTRICT imA,
+ const Sample *FFTCONVOLVER_RESTRICT reB,
+ const Sample *FFTCONVOLVER_RESTRICT imB, const size_t len);
-
-/**
-* @brief Adds the complex product of two split-complex arrays to a result array
-* @param re The real part of the result buffer
-* @param im The imaginary part of the result buffer
-* @param reA The real part of the 1st factor of the complex product
-* @param imA The imaginary part of the 1st factor of the complex product
-* @param reB The real part of the 2nd factor of the complex product
-* @param imB The imaginary part of the 2nd factor of the complex product
-*/
-void ComplexMultiplyAccumulate(Sample* FFTCONVOLVER_RESTRICT re,
- Sample* FFTCONVOLVER_RESTRICT im,
- const Sample* FFTCONVOLVER_RESTRICT reA,
- const Sample* FFTCONVOLVER_RESTRICT imA,
- const Sample* FFTCONVOLVER_RESTRICT reB,
- const Sample* FFTCONVOLVER_RESTRICT imB,
- const size_t len);
-
} // End of namespace fftconvolver
#endif // Header guard
diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp
index c276d778..a7372e29 100644
--- a/FFTConvolver/convolver.cpp
+++ b/FFTConvolver/convolver.cpp
@@ -46,8 +46,8 @@ void convolver_pool_init(size_t numThreads, size_t numConvolvers) {
if (!pool.init(numThreads, numConvolvers)) {
debug(1, "failed to initialize thread pool!");
} else {
- debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads, numThreads == 1 ? "" : "s",
- numConvolvers, numConvolvers == 1 ? "" : "s");
+ debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads,
+ numThreads == 1 ? "" : "s", numConvolvers, numConvolvers == 1 ? "" : "s");
}
}
@@ -56,8 +56,8 @@ void convolver_pool_closedown() {
debug(3, "thread pool shut down");
}
-int convolver_init(const char *filename, unsigned char channel_count,
- double max_length_in_seconds, size_t block_size) {
+int convolver_init(const char *filename, unsigned char channel_count, double max_length_in_seconds,
+ size_t block_size) {
debug(3, "convolver_init");
int success = 0;
SF_INFO info = {}; // Zero everything, including format
@@ -67,10 +67,10 @@ int convolver_init(const char *filename, unsigned char channel_count,
size_t max_length = (size_t)(max_length_in_seconds * info.samplerate);
const size_t size =
(unsigned int)info.frames > max_length ? max_length : (unsigned int)info.frames;
- float *buffer = (float*)malloc(sizeof(float) * size * info.channels);
+ float *buffer = (float *)malloc(sizeof(float) * size * info.channels);
if (buffer != NULL) {
// float buffer[size * info.channels];
- float *abuffer = (float*)malloc(sizeof(float) * size);
+ float *abuffer = (float *)malloc(sizeof(float) * size);
if (abuffer != NULL) {
size_t l = sf_readf_float(file, buffer, size);
if (l != 0) {
@@ -91,7 +91,7 @@ int convolver_init(const char *filename, unsigned char channel_count,
}
if (!pool.initConvolver(cc, block_size, abuffer, size)) {
debug(1, "new convolver failed to initialize convolver %u ", cc);
- }
+ }
}
}
success = 1;
@@ -101,11 +101,11 @@ int convolver_init(const char *filename, unsigned char channel_count,
"%d samples",
filename, info.channels, info.channels == 1 ? "" : "s", size);
sf_close(file);
- free((void*)abuffer);
+ free((void *)abuffer);
} else {
debug(1, "failed to init convolvers because insufficient memory was available");
}
- free((void*)buffer);
+ free((void *)buffer);
} else {
warn("failed to init convolvers because insufficient memory was available");
}
@@ -124,9 +124,7 @@ void convolver_process(unsigned int channel, float *data, int length) {
void convolver_wait_for_all() { pool.waitForAll(); }
-void convolver_clear_state() {
- pool.clearAllStates();
-}
+void convolver_clear_state() { pool.clearAllStates(); }
const unsigned int max_channels = 8;
fftconvolver::FFTConvolver convolvers[max_channels];
diff --git a/FFTConvolver/convolver.h b/FFTConvolver/convolver.h
index c49c70a2..5259fe67 100644
--- a/FFTConvolver/convolver.h
+++ b/FFTConvolver/convolver.h
@@ -5,22 +5,24 @@
extern "C" {
#endif
- #include
-
-// int convolver_init(const char* file, unsigned char channel_count, double max_length_in_seconds, size_t block_size);
+#include
+
+// int convolver_init(const char* file, unsigned char channel_count, double max_length_in_seconds,
+// size_t block_size);
void convolver_reset();
-//void convolver_clear_state();
-// void convolver_process(unsigned int channel, float *data, int length);
-// void convolver_process_l(float* data, int length);
-// void convolver_process_r(float* data, int length);
+// void convolver_clear_state();
+// void convolver_process(unsigned int channel, float *data, int length);
+// void convolver_process_l(float* data, int length);
+// void convolver_process_r(float* data, int length);
void convolver_pool_init(size_t numThreads, size_t numConvolvers);
void convolver_pool_closedown();
-int convolver_init(const char* file, unsigned char channel_count, double max_length_in_seconds, size_t block_size);
+int convolver_init(const char *file, unsigned char channel_count, double max_length_in_seconds,
+ size_t block_size);
void convolver_process(unsigned int channel, float *data, int length);
void convolver_clear_state();
void convolver_wait_for_all();
-
+
#ifdef __cplusplus
}
#endif
diff --git a/Makefile.am b/Makefile.am
index b5c724d6..64763533 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,11 +5,11 @@ homedir = @HOME@
# Note that xmlmantohtml isn't producing correct HTML.
# Uncomment the if/else and SUBDIR lines in this stanza to make xmltoman build shairport-sync.1
-# if USE_XMLTOMAN
-# SUBDIRS = man
-# else
+if USE_XMLTOMAN
+ SUBDIRS = man
+else
man_MANS = $(top_srcdir)/man/shairport-sync.1
-# endif
+endif
lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread --include=utilities/debug.h
lib_tinyhttp_a_CFLAGS = -pthread
@@ -27,7 +27,11 @@ noinst_LIBRARIES =
# See below for the flags for the test client program
-shairport_sync_SOURCES = shairport.c bonjour_strings.c rtsp.c mdns.c common.c rtp.c player.c audio.c loudness.c activity_monitor.c utilities/debug.c utilities/network_utilities.c utilities/rtsp_message_utilities.c
+shairport_sync_SOURCES = shairport.c bonjour_strings.c mdns.c common.c rtp.c \
+ player.c audio.c loudness.c activity_monitor.c \
+ rtsp.c \
+ utilities/debug.c utilities/network_utilities.c utilities/rtsp_message_utilities.c \
+ utilities/string_utilities.c
if BUILD_FOR_DARWIN
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-deprecated-declarations -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
diff --git a/alac.c b/alac.c
index 0b5587f3..daca81c9 100644
--- a/alac.c
+++ b/alac.c
@@ -44,13 +44,13 @@ static const int host_bigendian = 0;
#define _Swap32(v) \
do { \
- v = (((v)&0x000000FF) << 0x18) | (((v)&0x0000FF00) << 0x08) | (((v)&0x00FF0000) >> 0x08) | \
- (((v)&0xFF000000) >> 0x18); \
+ v = (((v) & 0x000000FF) << 0x18) | (((v) & 0x0000FF00) << 0x08) | \
+ (((v) & 0x00FF0000) >> 0x08) | (((v) & 0xFF000000) >> 0x18); \
} while (0)
#define _Swap16(v) \
do { \
- v = (((v)&0x00FF) << 0x08) | (((v)&0xFF00) >> 0x08); \
+ v = (((v) & 0x00FF) << 0x08) | (((v) & 0xFF00) >> 0x08); \
} while (0)
struct {
@@ -603,11 +603,11 @@ static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b, int uncompresse
right |= uncompressed_bytes_buffer_b[i] & mask;
}
- ((uint8_t *)buffer_out)[i * numchannels * 3] = (left)&0xFF;
+ ((uint8_t *)buffer_out)[i * numchannels * 3] = (left) & 0xFF;
((uint8_t *)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF;
((uint8_t *)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF;
- ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right)&0xFF;
+ ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF;
((uint8_t *)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF;
((uint8_t *)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF;
}
@@ -631,11 +631,11 @@ static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b, int uncompresse
right |= uncompressed_bytes_buffer_b[i] & mask;
}
- ((uint8_t *)buffer_out)[i * numchannels * 3] = (left)&0xFF;
+ ((uint8_t *)buffer_out)[i * numchannels * 3] = (left) & 0xFF;
((uint8_t *)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF;
((uint8_t *)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF;
- ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right)&0xFF;
+ ((uint8_t *)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF;
((uint8_t *)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF;
((uint8_t *)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF;
}
@@ -798,7 +798,7 @@ void alac_decode_frame(alac_file *alac, unsigned char *inbuffer, void *outbuffer
sample |= alac->uncompressed_bytes_buffer_a[i] & mask;
}
- ((uint8_t *)outbuffer)[i * alac->numchannels * 3] = (sample)&0xFF;
+ ((uint8_t *)outbuffer)[i * alac->numchannels * 3] = (sample) & 0xFF;
((uint8_t *)outbuffer)[i * alac->numchannels * 3 + 1] = (sample >> 8) & 0xFF;
((uint8_t *)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF;
}
diff --git a/ap2_buffered_audio_processor.c b/ap2_buffered_audio_processor.c
index d8a6bada..66067dd4 100644
--- a/ap2_buffered_audio_processor.c
+++ b/ap2_buffered_audio_processor.c
@@ -30,6 +30,7 @@
#include "rtp.h"
#include "utilities/buffered_read.h"
#include "utilities/mod23.h"
+#include "utilities/network_utilities.h"
#include
#include
@@ -91,10 +92,9 @@ void addADTStoPacket(uint8_t *packet, int packetLen, int rate, int channel_confi
void rtp_buffered_audio_cleanup_handler(__attribute__((unused)) void *arg) {
debug(2, "Buffered Audio Receiver Cleanup Start.");
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
- close(conn->buffered_audio_socket);
+ safe_socket_close(&conn->buffered_audio_socket);
debug(3, "Connection %d: closing TCP Buffered Audio port: %u.", conn->connection_number,
conn->local_buffered_audio_port);
- conn->buffered_audio_socket = 0;
debug(2, "Connection %d: rtp_buffered_audio_processor exit.", conn->connection_number);
}
@@ -198,7 +198,7 @@ void *rtp_buffered_audio_processor(void *arg) {
int packets_played_in_this_sequence = 0;
int play_enabled = 0;
-
+
int very_early_packets_signalled = 0;
// double requested_lead_time = 0.0; // normal lead time minimum -- maybe it should be about 0.1
@@ -276,20 +276,19 @@ void *rtp_buffered_audio_processor(void *arg) {
if (payload_ssrc != SSRC_NONE)
previous_ssrc = payload_ssrc;
payload_ssrc = nctohl(&packet[8]);
-
-
if ((payload_ssrc != previous_ssrc) && (payload_ssrc != SSRC_NONE)) {
if (ssrc_is_recognised(payload_ssrc) == 0) {
debug(2, "Unrecognised SSRC: %u.", payload_ssrc);
} else {
debug(2, "Connection %d: incoming audio encoding is%s \"%s\".",
- conn->connection_number, previous_ssrc == SSRC_NONE ? "" : " switching to", get_ssrc_name(payload_ssrc));
+ conn->connection_number, previous_ssrc == SSRC_NONE ? "" : " switching to",
+ get_ssrc_name(payload_ssrc));
}
}
if ((payload_ssrc != previous_ssrc) && (ssrc_is_recognised(payload_ssrc) == 0)) {
- debug(2, "Unrecognised SSRC: %u.", payload_ssrc);
+ debug(2, "Unrecognised SSRC: %u.", payload_ssrc);
}
if (blocks_read_since_play_began == 1) {
@@ -327,7 +326,8 @@ void *rtp_buffered_audio_processor(void *arg) {
finished = 1;
} else if (nread < 0) {
char errorstring[1024];
- (void) !strerror_r(errno, (char *)errorstring, sizeof(errorstring)); // (void) ! to suppress unused response warning
+ (void)!strerror_r(errno, (char *)errorstring,
+ sizeof(errorstring)); // (void) ! to suppress unused response warning
debug(1, "error in rtp_buffered_audio_processor %d: \"%s\". Could not recv a data_len .",
errno, errorstring);
finished = 1;
@@ -343,34 +343,42 @@ void *rtp_buffered_audio_processor(void *arg) {
debug(2, "immediate flush started at sequence number %u until sequence number of %u.",
seq_no, conn->ap2_immediate_flush_until_sequence_number);
}
- if ((blocks_read != 0) && ((a_minus_b_mod23(seq_no, conn->ap2_immediate_flush_until_sequence_number) > 0))) {
- debug(1, "immediate flush may have escaped its endpoint! Seq_no is %u, conn->ap2_immediate_flush_until_sequence_number is %u.", seq_no, conn->ap2_immediate_flush_until_sequence_number);
+ if ((blocks_read != 0) &&
+ ((a_minus_b_mod23(seq_no, conn->ap2_immediate_flush_until_sequence_number) > 0))) {
+ debug(1,
+ "immediate flush may have escaped its endpoint! Seq_no is %u, "
+ "conn->ap2_immediate_flush_until_sequence_number is %u.",
+ seq_no, conn->ap2_immediate_flush_until_sequence_number);
}
-
- if ((blocks_read != 0) && ((a_minus_b_mod23(seq_no, conn->ap2_immediate_flush_until_sequence_number) >= 0))) {
- debug(2, "immediate flush completed at seq_no: %u, conn->ap2_immediate_flush_until_sequence_number: %u.", seq_no, conn->ap2_immediate_flush_until_sequence_number);
+
+ if ((blocks_read != 0) &&
+ ((a_minus_b_mod23(seq_no, conn->ap2_immediate_flush_until_sequence_number) >= 0))) {
+ debug(2,
+ "immediate flush completed at seq_no: %u, "
+ "conn->ap2_immediate_flush_until_sequence_number: %u.",
+ seq_no, conn->ap2_immediate_flush_until_sequence_number);
conn->ap2_immediate_flush_requested = 0;
ap2_immediate_flush_requested = 0;
-
// turn off all deferred requests. Not sure if this is right...
unsigned int f = 0;
for (f = 0; f < MAX_DEFERRED_FLUSH_REQUESTS; f++) {
- if ((conn->ap2_deferred_flush_requests[f].inUse != 0) && (conn->ap2_deferred_flush_requests[f].active = 0)) {
+ if ((conn->ap2_deferred_flush_requests[f].inUse != 0) &&
+ (conn->ap2_deferred_flush_requests[f].active = 0)) {
debug(1,
- "deferred flush cancelled by an immediate flush: flushFromTS: %12u, flushFromSeq: %12u, "
- "flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.",
- conn->ap2_deferred_flush_requests[f].flushFromTS,
- conn->ap2_deferred_flush_requests[f].flushFromSeq,
- conn->ap2_deferred_flush_requests[f].flushUntilTS,
- conn->ap2_deferred_flush_requests[f].flushUntilSeq, timestamp);
+ "deferred flush cancelled by an immediate flush: flushFromTS: %12u, "
+ "flushFromSeq: %12u, "
+ "flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.",
+ conn->ap2_deferred_flush_requests[f].flushFromTS,
+ conn->ap2_deferred_flush_requests[f].flushFromSeq,
+ conn->ap2_deferred_flush_requests[f].flushUntilTS,
+ conn->ap2_deferred_flush_requests[f].flushUntilSeq, timestamp);
}
conn->ap2_deferred_flush_requests[f].inUse = 0;
conn->ap2_deferred_flush_requests[f].active = 0;
}
-
} else {
debug(4, "immediate flush of block %u until block %u", seq_no,
conn->ap2_immediate_flush_until_sequence_number);
@@ -426,11 +434,10 @@ void *rtp_buffered_audio_processor(void *arg) {
} else if (conn->ap2_deferred_flush_requests[f].active != 0) {
new_audio_block_needed = 1;
debug(4,
- "deferred flush of block: %u, timestamp: %u, SSRC: \"%s\". flushFromTS: %12u, flushFromSeq: %12u, "
+ "deferred flush of block: %u, timestamp: %u, SSRC: \"%s\". flushFromTS: %12u, "
+ "flushFromSeq: %12u, "
"flushUntilTS: %12u, flushUntilSeq: %12u, timestamp: %12u.",
- seq_no,
- timestamp,
- get_ssrc_name(payload_ssrc),
+ seq_no, timestamp, get_ssrc_name(payload_ssrc),
conn->ap2_deferred_flush_requests[f].flushFromTS,
conn->ap2_deferred_flush_requests[f].flushFromSeq,
conn->ap2_deferred_flush_requests[f].flushUntilTS,
@@ -453,22 +460,28 @@ void *rtp_buffered_audio_processor(void *arg) {
// and send it to the player. Otherwise, keep the block and sleep for a while.
// calculate if there is room in the decoded audio buffer...
-
- // debug(1, "frames buffered: %f seconds, desired length: %f seconds.", (1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate, config.audio_decoded_buffer_desired_length);
-
- // int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <= config.audio_decoded_buffer_desired_length;
+
+ // debug(1, "frames buffered: %f seconds, desired length: %f seconds.", (1.0 *
+ // player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate,
+ // config.audio_decoded_buffer_desired_length);
+
+ // int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy *
+ // conn->frames_per_packet) / conn->input_rate) <=
+ // config.audio_decoded_buffer_desired_length;
uint64_t buffer_should_be_time;
-
+
int have_valid_time = (frame_to_local_time(timestamp, &buffer_should_be_time, conn) == 0);
-
+
// calculate the lead time to make sure it's not too early...
int64_t lead_time = buffer_should_be_time - get_absolute_time_in_ns();
-
-
- // debug(1,"play_enabled: %d, have_valid_time: %d, audio_decoded_buffer_below_desired_length: %d, lead_time * 1E-9: %f, (config.audio_decoded_buffer_desired_length + 0.1): %f, player_buffer_occupancy: %zu",
- // play_enabled, have_valid_time, audio_decoded_buffer_below_desired_length, lead_time * 1E-9, (config.audio_decoded_buffer_desired_length + 0.1), player_buffer_occupancy
+
+ // debug(1,"play_enabled: %d, have_valid_time: %d,
+ // audio_decoded_buffer_below_desired_length: %d, lead_time * 1E-9: %f,
+ // (config.audio_decoded_buffer_desired_length + 0.1): %f, player_buffer_occupancy: %zu",
+ // play_enabled, have_valid_time, audio_decoded_buffer_below_desired_length, lead_time *
+ // 1E-9, (config.audio_decoded_buffer_desired_length + 0.1), player_buffer_occupancy
// );
-
+
// A slight problem here is that counting the number of buffers may not be sufficient,
// because the actual device may be
// taking data in large quantities at a single time.
@@ -476,34 +489,35 @@ void *rtp_buffered_audio_processor(void *arg) {
// So we just have to ensure that there
// is enough of a lead time maintained for sufficient audio to be available to prevent
// the device from under-running.
-
+
// If means that the Shairport Sync player might riun out of audio occasionally, but
// as long as the device has enough in its buffer, everything is fine.
-
+
// But it also means that Shairport Sync's buffers must be sufficient to hold all the
// entire lead-time's amount of audio in case the device has a zero-sized buffer.
-
- if ((play_enabled != 0) && (have_valid_time != 0)
-// (audio_decoded_buffer_below_desired_length != 0) &&
+
+ if ((play_enabled != 0) &&
+ (have_valid_time != 0)
+ // (audio_decoded_buffer_below_desired_length != 0) &&
&& (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))
-// && (audio_decoded_buffer_below_desired_length != 0)
- ) {
-
- very_early_packets_signalled = 0; //reset very early packet warning signaller
-
+ // && (audio_decoded_buffer_below_desired_length != 0)
+ ) {
+
+ very_early_packets_signalled = 0; // reset very early packet warning signaller
+
// try to identify blocks that are timed to before the last buffer, and drop 'em
int64_t time_from_last_buffer_time =
buffer_should_be_time - previous_buffer_should_be_time;
if ((packets_played_in_this_sequence == 0) || (time_from_last_buffer_time > 0)) {
-
+
payload_length = 0;
if (ssrc_is_recognised(payload_ssrc) != 0) {
// prepare_decoding_chain(conn, payload_ssrc);
unsigned long long new_payload_length = 0;
payload_pointer = m + leading_free_space_length;
if (lead_time >= 0) { // only decipher the packet if it's not too late
- int response = -1; // guess that there is a problem
+ int response = -1; // guess that there is a problem
if (conn->session_key != NULL) {
unsigned char nonce[12];
memset(nonce, 0, sizeof(nonce));
@@ -661,11 +675,16 @@ void *rtp_buffered_audio_processor(void *arg) {
}
new_audio_block_needed = 1; // the block has been used up and is no longer current
} else {
- if ((have_valid_time != 0) && (very_early_packets_signalled == 0) && (lead_time * 1E-9 > (config.audio_decoded_buffer_desired_length + 0.2))) {
- debug(1, "incoming frame suddenly (?) has a lead time of %f seconds, with a desired decoded buffer length of %f.", 1.0 * lead_time * 1E-9, config.audio_decoded_buffer_desired_length);
+ if ((have_valid_time != 0) && (very_early_packets_signalled == 0) &&
+ (lead_time * 1E-9 > (config.audio_decoded_buffer_desired_length + 0.2))) {
+ debug(1,
+ "incoming frame suddenly (?) has a lead time of %f seconds, with a desired "
+ "decoded buffer length of %f.",
+ 1.0 * lead_time * 1E-9, config.audio_decoded_buffer_desired_length);
very_early_packets_signalled = 1;
}
- usleep(((1000000 * conn->frames_per_packet) / conn->input_rate) * 2); // wait for approximately the length of two packets
+ usleep(((1000000 * conn->frames_per_packet) / conn->input_rate) *
+ 2); // wait for approximately the length of two packets
}
}
}
diff --git a/ap2_event_message_handler.c b/ap2_event_message_handler.c
index 7b03f436..94a84d0d 100644
--- a/ap2_event_message_handler.c
+++ b/ap2_event_message_handler.c
@@ -27,11 +27,10 @@
#include "ap2_event_message_handler.h"
#include "common.h"
#include "rtsp.h"
-#include "utilities/structured_buffer.h"
#include "utilities/generate_random_uuid.h"
+#include "utilities/structured_buffer.h"
-
-void decodeAndLogPlist(plist_t plist_to_log) {
+void decodeAndLogPlist(plist_t plist_to_log) {
if (plist_to_log != NULL) {
char *plist_as_string = plist_as_xml_text(plist_to_log);
if (plist_as_string != NULL) {
@@ -80,13 +79,13 @@ ssize_t ap2_event_port_send_message(rtsp_conn_info *conn, char *data, size_t dat
ssize_t ap2_event_port_post_command(rtsp_conn_info *conn, plist_t command) {
ssize_t result = 0;
- decodeAndLogPlist(command);
+ decodeAndLogPlist(command);
structured_buffer *sbuf = sbuf_new(4096);
if (sbuf != NULL) {
pthread_cleanup_push(sbuf_cleanup, sbuf);
char *plistString = NULL;
uint32_t plistStringLength = 0;
-
+
plist_to_bin(command, &plistString, &plistStringLength);
if (plistString != NULL) {
sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n", plistStringLength);
@@ -104,7 +103,7 @@ ssize_t ap2_event_port_post_command(rtsp_conn_info *conn, plist_t command) {
}
pthread_cleanup_pop(1); // delete the structured buffer
}
- return result;
+ return result;
}
ssize_t ap2_event_send_update_info(rtsp_conn_info *conn) {
@@ -121,14 +120,14 @@ ssize_t ap2_event_send_update_info(rtsp_conn_info *conn) {
if (update_info_plist != NULL) {
plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo"));
plist_dict_set_item(update_info_plist, "value", value_plist);
-
+
char *plist_as_string = plist_as_xml_text(update_info_plist);
if (plist_as_string != NULL) {
debug(3, "update_info_plist is:\n--\n\"%s\"\n--\n", plist_as_string);
free(plist_as_string);
}
-
- result = ap2_event_port_post_command(conn, update_info_plist);
+
+ result = ap2_event_port_post_command(conn, update_info_plist);
plist_free(update_info_plist);
} else {
debug(1, "Could not build an updateInfo plist");
diff --git a/ap2_event_message_handler.h b/ap2_event_message_handler.h
index fef68626..a632c33a 100644
--- a/ap2_event_message_handler.h
+++ b/ap2_event_message_handler.h
@@ -1,12 +1,13 @@
#ifndef _AP2_EVENT_COMMAND_SENDER_H
#define _AP2_EVENT_COMMAND_SENDER_H
+#include "player.h"
#include
#include
-#include "player.h"
ssize_t ap2_event_send_unit_volume_notification(rtsp_conn_info *conn, double volume);
-ssize_t ap2_event_send_modern_media_remote_command(rtsp_conn_info *conn, unsigned int command_number);
+ssize_t ap2_event_send_modern_media_remote_command(rtsp_conn_info *conn,
+ unsigned int command_number);
ssize_t ap2_event_send_update_info(rtsp_conn_info *conn);
ssize_t ap2_event_send_dev_mule();
diff --git a/ap2_event_receiver.c b/ap2_event_receiver.c
index abff7c23..98fa5191 100644
--- a/ap2_event_receiver.c
+++ b/ap2_event_receiver.c
@@ -43,8 +43,7 @@ void ap2_event_receiver_cleanup_handler(void *arg) {
#endif
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
- close(conn->event_channel_fd);
- conn->event_channel_fd = 0;
+ safe_socket_close(&conn->event_channel_fd);
pthread_cleanup_pop(1); // unlock the mutex
if (conn->airplay_gid != NULL) {
@@ -68,7 +67,8 @@ void *ap2_event_receiver(void *arg) {
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
- conn->event_channel_fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size);
+ conn->event_channel_fd =
+ eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size);
pthread_cleanup_pop(1); // unlock the mutex
if (conn->event_channel_fd > 0) {
debug(2,
@@ -76,14 +76,13 @@ void *ap2_event_receiver(void *arg) {
"socket %d.",
conn->connection_number, conn->event_socket, conn->event_channel_fd);
pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg);
-
+
ap2_event_send_update_info(conn);
-
-
+
while (1) {
usleep(1000000);
};
-
+
debug(3, "Connection %d: AP2 Event Receiver RTP thread starting \"normal\" exit.",
conn->connection_number);
pthread_cleanup_pop(1); // do the cleanup
diff --git a/audio_alsa.c b/audio_alsa.c
index bf03dfc8..83ed20df 100644
--- a/audio_alsa.c
+++ b/audio_alsa.c
@@ -40,6 +40,7 @@
#include "activity_monitor.h"
#include "audio.h"
#include "common.h"
+#include "utilities/string_utilities.h"
enum alsa_backend_mode {
abm_disconnected,
@@ -951,8 +952,8 @@ static int actual_open_alsa_device() {
if ((snd_pcm_hw_params_get_rate_numden(alsa_params, &uval, &uval2) == 0) && (uval2 != 0))
// watch for a divide by zero too!
- debug(log_level, " precise (rational) rate = %.3f frames per second (i.e. %u/%u).", (1.0 * uval) / uval2, uval,
- uval2);
+ debug(log_level, " precise (rational) rate = %.3f frames per second (i.e. %u/%u).",
+ (1.0 * uval) / uval2, uval, uval2);
else
debug(log_level, " precise (rational) rate information unavailable.");
@@ -1260,7 +1261,8 @@ static int init(int argc, char **argv) {
else {
warn("Invalid disable_synchronization option choice \"%s\". It should "
"be \"yes\" or "
- "\"no\". It is set to \"no\".", str);
+ "\"no\". It is set to \"no\".",
+ str);
config.no_sync = 0;
}
}
@@ -1276,7 +1278,8 @@ static int init(int argc, char **argv) {
else {
warn("Invalid mute_using_playback_switch option choice \"%s\". It "
"should be \"yes\" or "
- "\"no\". It is set to \"no\".", str);
+ "\"no\". It is set to \"no\".",
+ str);
config.alsa_use_hardware_mute = 0;
}
}
@@ -1290,7 +1293,8 @@ static int init(int argc, char **argv) {
else {
warn("Invalid use_hardware_mute_if_available option choice \"%s\". It "
"should be \"yes\" or "
- "\"no\". It is set to \"no\".", str);
+ "\"no\". It is set to \"no\".",
+ str);
config.alsa_use_hardware_mute = 0;
}
}
@@ -1304,7 +1308,8 @@ static int init(int argc, char **argv) {
else {
warn("Invalid use_mmap_if_available option choice \"%s\". It should be "
"\"yes\" or \"no\". "
- "It remains set to \"yes\".", str);
+ "It remains set to \"yes\".",
+ str);
config.no_mmap = 0;
}
}
@@ -1424,7 +1429,8 @@ static int init(int argc, char **argv) {
else {
warn("Invalid use_precision_timing option choice \"%s\". It should be "
"\"yes\", \"auto\" or \"no\". "
- "It remains set to \"%s\".", str,
+ "It remains set to \"%s\".",
+ str,
config.use_precision_timing == YNA_NO ? "no"
: config.use_precision_timing == YNA_AUTO ? "auto"
: "yes");
diff --git a/audio_pa.c b/audio_pa.c
index a0c6afec..ce7eb328 100644
--- a/audio_pa.c
+++ b/audio_pa.c
@@ -506,7 +506,8 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char *
// get the default channel mapping setting basis -- "alsa" or "pulseaudio".
- if (config_lookup_non_empty_string(config.cfg, "pulseaudio.default_channel_layouts", &default_channel_layouts)) {
+ if (config_lookup_non_empty_string(config.cfg, "pulseaudio.default_channel_layouts",
+ &default_channel_layouts)) {
if ((strcasecmp(default_channel_layouts, "alsa") == 0) ||
(strcasecmp(default_channel_layouts, "pulseaudio") == 0)) {
debug(1, "pulseaudio default_channel_layouts setting: \"%s\".", default_channel_layouts);
diff --git a/audio_pipe.c b/audio_pipe.c
index cc86b380..203bd2c5 100644
--- a/audio_pipe.c
+++ b/audio_pipe.c
@@ -39,6 +39,8 @@
#include
#include
+#include "utilities/network_utilities.h"
+
static int fd = -1;
static int warned = 0;
static unsigned int bytes_per_frame = 0;
@@ -146,10 +148,7 @@ static int init(int argc, char **argv) {
return 0;
}
-static void deinit(void) {
- if (fd > 0)
- close(fd);
-}
+static void deinit(void) { safe_socket_close(&fd); }
static void help(void) {
printf(" Provide the pipe's pathname. The default is \"%s\".\n", default_pipe_name);
diff --git a/bonjour_strings.c b/bonjour_strings.c
index ff9c37a7..5edf400c 100644
--- a/bonjour_strings.c
+++ b/bonjour_strings.c
@@ -76,72 +76,76 @@ void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) {
// the secondary_txt_records are for the _airplay._tcp items.
#ifdef CONFIG_AIRPLAY_2
- txt_records[entry_number++] = "cn=0,1";
- txt_records[entry_number++] = "da=true";
- txt_records[entry_number++] = "et=0,1";
- if (config.password != NULL)
- txt_records[entry_number++] = "pw=true";
uint64_t features_hi = config.airplay_features;
features_hi = (features_hi >> 32) & 0xffffffff;
uint64_t features_lo = config.airplay_features;
features_lo = features_lo & 0xffffffff;
- txt_records[entry_number++] =
- bnprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
- features_lo, features_hi);
+ if (config.service_type == APST_airplay2) {
+ txt_records[entry_number++] = "cn=0,1";
+ txt_records[entry_number++] = "da=true";
+ txt_records[entry_number++] = "et=0,1";
+ if (config.password != NULL)
+ txt_records[entry_number++] = "pw=true";
- txt_records[entry_number++] =
- bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version);
- txt_records[entry_number++] = bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString),
- "sf=0x%" PRIX32, config.airplay_statusflags);
-#ifdef CONFIG_METADATA
- if (config.get_coverart == 0)
- txt_records[entry_number++] = "md=0,2";
- else
- txt_records[entry_number++] = "md=0,1,2";
-#endif
- txt_records[entry_number++] =
- bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model);
- txt_records[entry_number++] = bnprintf(pkString, sizeof(pkString), "pk=%s", config.pk_string);
- txt_records[entry_number++] = "tp=UDP";
- txt_records[entry_number++] = "vn=65537";
- txt_records[entry_number++] =
- bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=%s", config.srcvers);
- txt_records[entry_number++] =
- bnprintf(ap1OsversString, sizeof(ap1OsversString), "ov=%s", config.osvers);
- txt_records[entry_number++] = NULL;
+ txt_records[entry_number++] =
+ bnprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
+ features_lo, features_hi);
-#else
- // here, just replicate what happens in mdns.h when using those #defines
- txt_records[entry_number++] =
- bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString), "sf=0x4");
- txt_records[entry_number++] =
- bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version);
- txt_records[entry_number++] =
- bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model);
- txt_records[entry_number++] = bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=105.1");
- txt_records[entry_number++] = "tp=TCP,UDP";
- txt_records[entry_number++] = "vn=65537";
+ txt_records[entry_number++] =
+ bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version);
+ txt_records[entry_number++] = bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString),
+ "sf=0x%" PRIX32, config.airplay_statusflags);
#ifdef CONFIG_METADATA
- if (config.get_coverart == 0)
- txt_records[entry_number++] = "md=0,2";
- else
- txt_records[entry_number++] = "md=0,1,2";
+ if (config.get_coverart == 0)
+ txt_records[entry_number++] = "md=0,2";
+ else
+ txt_records[entry_number++] = "md=0,1,2";
#endif
- txt_records[entry_number++] = "ss=16";
- txt_records[entry_number++] = "sr=44100";
- txt_records[entry_number++] = "da=true";
- txt_records[entry_number++] = "sv=false";
- txt_records[entry_number++] = "et=0,1";
- txt_records[entry_number++] = "ek=1";
- txt_records[entry_number++] = "cn=0,1";
- txt_records[entry_number++] = "ch=2";
- txt_records[entry_number++] = "txtvers=1";
- if (config.password == NULL)
- txt_records[entry_number++] = "pw=false";
- else
- txt_records[entry_number++] = "pw=true";
- txt_records[entry_number++] = NULL;
+ txt_records[entry_number++] =
+ bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model);
+ txt_records[entry_number++] = bnprintf(pkString, sizeof(pkString), "pk=%s", config.pk_string);
+ txt_records[entry_number++] = "tp=UDP";
+ txt_records[entry_number++] = "vn=65537";
+ txt_records[entry_number++] =
+ bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=%s", config.srcvers);
+ txt_records[entry_number++] =
+ bnprintf(ap1OsversString, sizeof(ap1OsversString), "ov=%s", config.osvers);
+ txt_records[entry_number++] = NULL;
+ } else {
+#endif
+ // here, just replicate what happens in mdns.h when using those #defines
+ txt_records[entry_number++] =
+ bnprintf(ap1StatusFlagsString, sizeof(ap1StatusFlagsString), "sf=0x4");
+ txt_records[entry_number++] =
+ bnprintf(fwString, sizeof(fwString), "fv=%s", config.firmware_version);
+ txt_records[entry_number++] =
+ bnprintf(ap1ModelString, sizeof(ap1ModelString), "am=%s", config.model);
+ txt_records[entry_number++] = bnprintf(ap1SrcversString, sizeof(ap1SrcversString), "vs=105.1");
+ txt_records[entry_number++] = "tp=TCP,UDP";
+ txt_records[entry_number++] = "vn=65537";
+#ifdef CONFIG_METADATA
+ if (config.get_coverart == 0)
+ txt_records[entry_number++] = "md=0,2";
+ else
+ txt_records[entry_number++] = "md=0,1,2";
+#endif
+ txt_records[entry_number++] = "ss=16";
+ txt_records[entry_number++] = "sr=44100";
+ txt_records[entry_number++] = "da=true";
+ txt_records[entry_number++] = "sv=false";
+ txt_records[entry_number++] = "et=0,1";
+ txt_records[entry_number++] = "ek=1";
+ txt_records[entry_number++] = "cn=0,1";
+ txt_records[entry_number++] = "ch=2";
+ txt_records[entry_number++] = "txtvers=1";
+ if (config.password == NULL)
+ txt_records[entry_number++] = "pw=false";
+ else
+ txt_records[entry_number++] = "pw=true";
+ txt_records[entry_number++] = NULL;
+#ifdef CONFIG_AIRPLAY_2
+ }
#endif
#ifdef CONFIG_AIRPLAY_2
diff --git a/common.c b/common.c
index e5961c98..c513077e 100644
--- a/common.c
+++ b/common.c
@@ -27,6 +27,7 @@
*/
#include "common.h"
+#include "utilities/network_utilities.h"
#ifdef CONFIG_USE_GIT_VERSION_STRING
#include "gitversion.h"
@@ -51,8 +52,8 @@
#include
#include
-#include
#include
+#include
#ifdef COMPILE_FOR_LINUX
#include
@@ -411,7 +412,7 @@ int bind_socket_and_port(int type, int ip_family, const char *self_ip_address, u
#endif
if (ret < 0) {
ret = errno;
- close(local_socket);
+ safe_socket_close(&local_socket);
char errorstring[1024];
getErrorText((char *)errorstring, sizeof(errorstring));
warn("error %d: \"%s\". Could not bind a port!", errno, errorstring);
@@ -422,7 +423,7 @@ int bind_socket_and_port(int type, int ip_family, const char *self_ip_address, u
ret = getsockname(local_socket, (struct sockaddr *)&local, &local_len);
if (ret < 0) {
ret = errno;
- close(local_socket);
+ safe_socket_close(&local_socket);
char errorstring[1024];
getErrorText((char *)errorstring, sizeof(errorstring));
warn("error %d: \"%s\". Could not retrieve socket's port!", errno, errorstring);
@@ -494,7 +495,7 @@ uint16_t bind_UDP_port(int ip_family, const char *self_ip_address, uint32_t scop
// debug(1,"UDP port chosen: %d.",desired_port);
if (ret < 0) {
- close(local_socket);
+ safe_socket_close(&local_socket);
char errorstring[1024];
getErrorText((char *)errorstring, sizeof(errorstring));
die("error %d: \"%s\". Could not bind a UDP port! Check the udp_port_range is large enough -- "
@@ -950,7 +951,8 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
outbuf = malloc(trsa->MBEDTLS_PRIVATE_V3_ONLY(len));
#if MBEDTLS_VERSION_MAJOR == 3
- rc = mbedtls_pk_sign(&pkctx, MBEDTLS_MD_NONE, input, inlen, outbuf, mbedtls_pk_get_len(&pkctx), &olen, mbedtls_ctr_drbg_random, &ctr_drbg);
+ rc = mbedtls_pk_sign(&pkctx, MBEDTLS_MD_NONE, input, inlen, outbuf, mbedtls_pk_get_len(&pkctx),
+ &olen, mbedtls_ctr_drbg_random, &ctr_drbg);
*outlen = olen;
#else
rc = mbedtls_rsa_pkcs1_encrypt(trsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE,
@@ -1066,7 +1068,7 @@ int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) {
const char *str = NULL;
int response = CONFIG_FALSE;
config_setting_t *s = config_lookup(cfg, where);
- if (s != NULL) {
+ if (s != NULL) {
if (config_setting_type(s) == CONFIG_TYPE_STRING) {
str = config_setting_get_string(s);
if (strcasecmp(str, "no") == 0) {
@@ -1076,11 +1078,14 @@ int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) {
(*dst) = 1;
response = CONFIG_TRUE;
} else {
- die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
+ die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"",
+ where, str);
return 0;
}
} else {
- warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, and has been ignored.", where);
+ warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, "
+ "and has been ignored.",
+ where);
}
}
return response;
@@ -1245,6 +1250,59 @@ int check_int_or_list_setting(config_setting_t *setting, const int item) {
return result;
}
+void service_type_to_string(APST_t service_type, char *string_space) {
+ if (string_space != NULL) {
+ string_space[0] = '\0';
+ switch (service_type) {
+ case APST_auto:
+ strcpy(string_space, "auto");
+ break;
+ case APST_classic:
+ strcpy(string_space, "classic");
+ break;
+ case APST_forced_classic:
+ strcpy(string_space, "forced_classic");
+ break;
+ case APST_airplay2:
+ strcpy(string_space, "airplay2");
+ break;
+ }
+ }
+}
+
+APST_t string_to_service_type(const char *parameter, const char *setting_name) {
+ APST_t response = APST_auto;
+ if (parameter != NULL) {
+ if (strcasecmp(parameter, "auto") == 0) {
+ response = APST_auto;
+ } else if (strcasecmp(parameter, "classic") == 0) {
+ response = APST_classic;
+ } else if (strcasecmp(parameter, "airplay1") == 0) {
+ response = APST_classic;
+ } else if (strcasecmp(parameter, "airplay2") == 0) {
+ response = APST_airplay2;
+ } else {
+ warn("The %s \"%s\" was ignored. It must be \"auto\", \"classic\" or \"airplay2\". (You can "
+ "use \"airplay1\" instead of \"classic\".)",
+ setting_name, parameter);
+ }
+#ifndef CONFIG_AIRPLAY_2
+ if (response == APST_airplay2) {
+ warn("This version of Shairport Sync supports does not support AirPlay 2. The %s \"%s\" "
+ "setting has been ignored.",
+ setting_name, parameter);
+ response = APST_auto; // reset to default
+ }
+#endif
+ }
+ /*
+ char service_type_string[32];
+ service_type_to_string(response, service_type_string);
+ debug(1, "config.service_type read from %s is: \"%s\".", setting_name, service_type_string);
+ */
+ return response;
+}
+
void command_set_volume(double volume) {
// this has a cancellation point if waiting is enabled
if (config.cmd_set_volume) {
@@ -1310,11 +1368,11 @@ void command_start(void) {
char **argV;
if (config.cmd_start_returns_output) {
- close(pipes[0]);
+ safe_socket_close(&pipes[0]);
if (dup2(pipes[1], 1) < 0) {
warn("Unable to reopen pipe as stdout for popen of start command");
debug(1, "dup2 finished with error %d", errno);
- close(pipes[1]);
+ safe_socket_close(&pipes[1]);
return;
}
}
@@ -1344,9 +1402,9 @@ void command_start(void) {
if (config.cmd_start_returns_output) {
static char buffer[256];
int len;
- close(pipes[1]);
+ safe_socket_close(&pipes[1]);
len = read(pipes[0], buffer, 255);
- close(pipes[0]);
+ safe_socket_close(&pipes[0]);
buffer[len] = '\0';
if (buffer[len - 1] == '\n')
buffer[len - 1] = '\0'; // strip trailing newlines
@@ -1705,45 +1763,6 @@ int try_to_open_pipe_for_writing(const char *pathname) {
return fdis;
}
-/* from
- * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
- */
-
-char *str_replace(const char *string, const char *substr, const char *replacement) {
- char *tok = NULL;
- char *newstr = NULL;
- char *oldstr = NULL;
- char *head = NULL;
-
- /* if either substr or replacement is NULL, duplicate string a let caller handle it */
- if (substr == NULL || replacement == NULL)
- return strdup(string);
- newstr = strdup(string);
- head = newstr;
- if (head) {
- while ((tok = strstr(head, substr))) {
- oldstr = newstr;
- newstr = malloc(strlen(oldstr) - strlen(substr) + strlen(replacement) + 1);
- /*failed to alloc mem, free old string and return NULL */
- if (newstr == NULL) {
- free(oldstr);
- return NULL;
- }
- memcpy(newstr, oldstr, tok - oldstr);
- memcpy(newstr + (tok - oldstr), replacement, strlen(replacement));
- memcpy(newstr + (tok - oldstr) + strlen(replacement), tok + strlen(substr),
- strlen(oldstr) - strlen(substr) - (tok - oldstr));
- memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement), 0, 1);
- /* move back head right after the last replacement */
- head = newstr + (tok - oldstr) + strlen(replacement);
- free(oldstr);
- }
- } else {
- die("failed to allocate memory in str_replace.");
- }
- return newstr;
-}
-
/* from http://burtleburtle.net/bob/rand/smallprng.html */
// this is not thread-safe, so we need a mutex on it to use it properly.
@@ -1826,7 +1845,8 @@ void sps_nanosleep(const time_t sec, const long nanosec) {
rem = req;
} while ((result == -1) && (errno == EINTR));
if (result == -1)
- debug(1, "Error in sps_nanosleep of %" PRIdMAX " sec and %ld nanoseconds: %d.", (intmax_t)sec, nanosec, errno);
+ debug(1, "Error in sps_nanosleep of %" PRIdMAX " sec and %ld nanoseconds: %d.", (intmax_t)sec,
+ nanosec, errno);
}
// Mac OS X doesn't have pthread_mutex_timedlock
@@ -1914,7 +1934,7 @@ int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const cha
if (strerror_r(r, errstr, sizeof(errstr)) == 0) {
debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, errstr, mutexname, dstring);
} else {
- debug(1, "error %d: unlocking mutex \"%s\" at \"%s\".", r, mutexname, dstring);
+ debug(1, "error %d: unlocking mutex \"%s\" at \"%s\".", r, mutexname, dstring);
}
}
pthread_setcancelstate(oldState, NULL);
@@ -1938,10 +1958,9 @@ void plist_cleanup(void *arg) {
#endif
void socket_cleanup(void *arg) {
- intptr_t fdp = (intptr_t)arg;
- int soc = fdp;
- debug(3, "socket_cleanup called for socket: %d.", soc);
- close(fdp);
+ int *p = (int *)arg;
+ debug(3, "socket_cleanup called for socket: %d.", *p);
+ safe_socket_close(p);
}
void cv_cleanup(void *arg) {
@@ -2349,12 +2368,8 @@ int get_device_id(uint8_t *id, int int_length) {
#ifdef AF_PACKET
if ((ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET)) {
struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr;
- if (
- ((ifa->ifa_flags & IFF_UP) != 0) &&
- ((ifa->ifa_flags & IFF_RUNNING) != 0) &&
- ((ifa->ifa_flags & IFF_LOOPBACK) == 0) &&
- (ifa->ifa_addr != 0)
- ) {
+ if (((ifa->ifa_flags & IFF_UP) != 0) && ((ifa->ifa_flags & IFF_RUNNING) != 0) &&
+ ((ifa->ifa_flags & IFF_LOOPBACK) == 0) && (ifa->ifa_addr != 0)) {
found = 1;
response = 0;
for (i = 0; ((i < s->sll_halen) && (i < int_length)); i++) {
@@ -2430,7 +2445,7 @@ int named_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
debug(1, "error creating thread \"%s\"", actual_name);
}
#ifndef COMPILE_FOR_OSX
- else {
+ if (response == 0) {
pthread_setname_np(*thread, actual_name);
}
#endif
@@ -2481,6 +2496,7 @@ int named_pthread_create_with_priority(pthread_t *thread, int priority,
}
// ret == 0 if creating and setting up the attributes was successful
if (ret == 0) {
+
ret = pthread_create(thread, &attr, start_routine, arg);
pthread_attr_destroy(&attr);
}
@@ -2489,7 +2505,7 @@ int named_pthread_create_with_priority(pthread_t *thread, int priority,
if (ret != 0) {
ret = pthread_create(thread, NULL, start_routine, arg);
if (failed_to_set_rt == 0) {
- inform("Can not set realtime properties of a thread.");
+ inform("Can not set realtime properties of thread \"%s\".", actual_name);
failed_to_set_rt = 1;
}
}
diff --git a/common.h b/common.h
index e18af0f9..3c523833 100644
--- a/common.h
+++ b/common.h
@@ -14,6 +14,14 @@ extern "C" {
#include "definitions.h"
#include "mdns.h"
+// service type to be offered
+typedef enum {
+ APST_auto = 0,
+ APST_classic, // must be classic / airplay 1
+ APST_forced_classic, // must be classic / airplay 1 because no nqptp
+ APST_airplay2, // must be airplay 2; error if no NQPTP
+} APST_t; // AirPlay Service Type
+
// struct sockaddr_in6 is bigger than struct sockaddr. derp
#ifdef AF_INET6
#define SOCKADDR struct sockaddr_storage
@@ -24,8 +32,10 @@ extern "C" {
#endif
#if defined(CONFIG_CONVOLUTION)
+// impulse response filter file status
typedef enum { ev_unchecked, ev_okay, ev_invalid } ir_file_evaluation;
+// impulse response filter file record
typedef struct {
unsigned int samplerate; // initialized to 0, will be filter frame rate
unsigned int channels;
@@ -147,12 +157,13 @@ typedef enum {
#define SPS_FORMAT_SET (((1 << (SPS_FORMAT_HIGHEST_NATIVE + 1)) - 1) - (1 << SPS_FORMAT_UNKNOWN))
#define SPS_RATE_SET (((1 << (SPS_RATE_HIGHEST + 1)) - 1) - (1 << SPS_RATE_UNKNOWN))
-// in SPS_CHANNEL_SET, bit 0 set means a channel set of no channels, bit 1 set means a channel set of 1 channel
-// and so on to bit 31 meaning a channel set of 31 channels. We want to consider all possible channel sets
-// apart from channel set 0.
+// in SPS_CHANNEL_SET, bit 0 set means a channel set of no channels, bit 1 set means a channel set
+// of 1 channel and so on to bit 31 meaning a channel set of 31 channels. We want to consider all
+// possible channel sets apart from channel set 0.
#define SPS_GREATEST_CHANNEL_COUNT 31 // should be 32 to be fully in line with ALSA limits
-#define SPS_CHANNEL_SET 0xFFFFFFFE // channel sets 31 to 1, but no channel set 0
-// #define SPS_CHANNEL_SET (((1 << (SPS_GREATEST_CHANNEL_COUNT + 1)) - 1) - (1 << 0)) // channels 1 to 31, not 0-based!
+#define SPS_CHANNEL_SET 0xFFFFFFFE // channel sets 31 to 1, but no channel set 0
+// #define SPS_CHANNEL_SET (((1 << (SPS_GREATEST_CHANNEL_COUNT + 1)) - 1) - (1 << 0)) // channels 1
+// to 31, not 0-based!
#ifndef CONFIG_AIRPLAY_2
#define SPS_FORMAT_NON_FFMPEG_SET SPS_FORMAT_SET
@@ -408,8 +419,11 @@ typedef struct {
uint8_t airplay_pk[32]; // public key
char *pk_string;
char *nqptp_shared_memory_interface_name; // client name for nqptp service
- int enable_HK_Access_Control; // true if the device is part of an Apple Home
+ int enable_HK_Access_Control; // true if the device is part of an Apple Home
#endif
+
+ APST_t service_type; // APST_auto, APST_classic, APST_forced_classic, APST_airplay2
+
int unfixable_error_reported; // only report once.
uint64_t eight_channel_layout; // non-zero means enabled and is a channel layout
@@ -460,11 +474,6 @@ void set_requested_connection_state_to_output(int v);
int try_to_open_pipe_for_writing(
const char *pathname); // open it without blocking if it's not hooked up
-/* from
- * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722
- */
-char *str_replace(const char *string, const char *substr, const char *replacement);
-
// based on http://burtleburtle.net/bob/rand/smallprng.html
void r64init(uint64_t seed);
@@ -562,6 +571,9 @@ unsigned int config_get_string_settings_as_string_array(config_setting_t *settin
const char ***result);
unsigned int config_get_int_settings_as_int_array(config_setting_t *setting, int **result);
+APST_t string_to_service_type(const char *parameter, const char *setting_name);
+void service_type_to_string(APST_t service_type, char *string_space);
+
void command_start(void);
void command_stop(void);
void command_execute(const char *command, const char *extra_argument, const int block);
diff --git a/configure.ac b/configure.ac
index e066b94f..06ecdeb6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -469,7 +469,7 @@ if test "x$with_airplay_2" = "xyes" ; then
])
AC_CHECK_LIB([uuid],[uuid_generate], [], [AC_MSG_ERROR([AirPlay 2 support requires the uuid library -- uuid-dev suggested])])
systemd_after_args="${systemd_after_args}${systemd_after_args:+ }nqptp.service"
- systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }nqptp.service"
+ systemd_wants_args="${systemd_wants_args}${systemd_wants_args:+ }nqptp.service"
fi
AM_CONDITIONAL([USE_AIRPLAY_2], [test "x$with_airplay_2" = "xyes"])
@@ -586,8 +586,13 @@ if test "x${systemd_requires_args}" != x ; then
systemd_requires_args="Requires=${systemd_requires_args}"
fi
+if test "x${systemd_wants_args}" != x ; then
+ systemd_wants_args="Wants=${systemd_wants_args}"
+fi
+
AC_SUBST([SYSTEMD_AFTER_ARGS], [${systemd_after_args}])
AC_SUBST([SYSTEMD_REQUIRES_ARGS], [${systemd_requires_args}])
+AC_SUBST([SYSTEMD_WANTS_ARGS], [${systemd_wants_args}])
# The man directory will not be recursively made unless you set the SUBDIR in Makefile.am.
# But leave this as is to enable a distclean to be done.
diff --git a/dacp.c b/dacp.c
index 2252482f..f8d90404 100644
--- a/dacp.c
+++ b/dacp.c
@@ -45,6 +45,7 @@
#include "metadata/hub.h"
#include "tinyhttp/http.h"
+#include "utilities/network_utilities.h"
typedef struct {
int players_connection_thread_index; // the connection thread index when a player thread is
@@ -88,7 +89,7 @@ void *response_realloc(__attribute__((unused)) void *opaque, void *ptr, int size
t = realloc(ptr, size);
}
if (t == NULL)
- debug(1, "response_realloc of size %d to ptr %" PRIxPTR " failed!", size, (uintptr_t) ptr);
+ debug(1, "response_realloc of size %d to ptr %" PRIxPTR " failed!", size, (uintptr_t)ptr);
}
return t;
}
@@ -117,8 +118,7 @@ void response_body(void *opaque, const char *data, int size) {
static void
response_header(__attribute__((unused)) void *opaque, __attribute__((unused)) const char *ckey,
__attribute__((unused)) int nkey, __attribute__((unused)) const char *cvalue,
- __attribute__((unused)) int nvalue) { /* example doesn't care about headers */
-}
+ __attribute__((unused)) int nvalue) { /* example doesn't care about headers */ }
static void response_code(void *opaque, int code) {
struct HttpResponse *response = (struct HttpResponse *)opaque;
@@ -150,12 +150,6 @@ void mutex_lock_cleanup(void *arg) {
debug(1, "Error releasing mutex.");
}
-void connect_cleanup(void *arg) {
- int *fd = (int *)arg;
- // debug(2, "dacp_send_command: close socket %d.",*fd);
- close(*fd);
-}
-
void http_cleanup(void *arg) {
// debug(1, "http cleanup called.");
struct http_roundtripper *rt = (struct http_roundtripper *)arg;
@@ -234,7 +228,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
// \"%s\".",errno,strerror(errno));
response.code = 497; // Can't establish a socket to the DACP server
} else {
- pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
+ pthread_cleanup_push(socket_cleanup, (void *)&sockfd);
// debug(2, "dacp_send_command: open socket %d.",sockfd);
// This is for limiting the time to be spent waiting for a response.
@@ -350,9 +344,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
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 will close the socket
+ // debug(1,"DACP socket closed.");
}
pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
// pthread_mutex_unlock(&dacp_conversation_lock);
@@ -1302,8 +1295,8 @@ int dacp_set_volume(int32_t vo) {
int32_t active_speakers = 0;
for (i = 0; i < speaker_count; i++) {
if (speaker_info[i].speaker_number == machine_number) {
- debug(2, "Our speaker number found: %" PRId64 " with relative volume %" PRId32 ".", machine_number,
- speaker_info[i].volume);
+ debug(2, "Our speaker number found: %" PRId64 " with relative volume %" PRId32 ".",
+ machine_number, speaker_info[i].volume);
}
if (speaker_info[i].active == 1) {
active_speakers++;
diff --git a/dbus-service.c b/dbus-service.c
index 781fed0e..4dbd1a6a 100644
--- a/dbus-service.c
+++ b/dbus-service.c
@@ -418,7 +418,7 @@ static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton,
static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton,
GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
-#ifdef CONFIG_DACP_CLIENT
+#ifdef CONFIG_DACP_CLIENT
send_simple_dacp_command("play");
#endif
shairport_sync_remote_control_complete_play(skeleton, invocation);
@@ -596,9 +596,9 @@ gboolean notify_convolution_enabled_callback(__attribute__((unused)) ShairportSy
#endif
#ifdef CONFIG_CONVOLUTION
-gboolean notify_convolution_maximum_length_in_seconds_callback(ShairportSync *skeleton,
- __attribute__((unused))
- gpointer user_data) {
+gboolean
+notify_convolution_maximum_length_in_seconds_callback(ShairportSync *skeleton,
+ __attribute__((unused)) gpointer user_data) {
gdouble th = shairport_sync_get_convolution_maximum_length_in_seconds(skeleton);
if ((th >= 0.0) && (th <= 15.0)) {
@@ -612,10 +612,8 @@ gboolean notify_convolution_maximum_length_in_seconds_callback(ShairportSync *sk
return TRUE;
}
#else
-gboolean notify_convolution_maximum_length_in_seconds_callback(__attribute__((unused))
- ShairportSync *skeleton,
- __attribute__((unused))
- gpointer user_data) {
+gboolean notify_convolution_maximum_length_in_seconds_callback(
+ __attribute__((unused)) ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) {
warn(">> Convolution support is not built in to this build of Shairport Sync.");
return TRUE;
}
@@ -643,16 +641,16 @@ gboolean notify_convolution_gain_callback(__attribute__((unused)) ShairportSync
}
#endif
#ifdef CONFIG_CONVOLUTION
-gboolean notify_convolution_impulse_response_files_callback(ShairportSync *skeleton,
- __attribute__((unused))
- gpointer user_data) {
+gboolean
+notify_convolution_impulse_response_files_callback(ShairportSync *skeleton,
+ __attribute__((unused)) gpointer user_data) {
char *th = (char *)shairport_sync_get_convolution_impulse_response_files(skeleton);
if (th != NULL) {
debug(1, ">> freeing current configuration impulse response filter files.");
free_ir_filenames(config.convolution_ir_files, config.convolution_ir_file_count);
config.convolution_ir_files = NULL;
config.convolution_ir_file_count = 0;
-
+
config.convolution_ir_files = parse_ir_filenames(th, &config.convolution_ir_file_count);
sanity_check_ir_files(1, config.convolution_ir_files, config.convolution_ir_file_count);
debug(1, ">> setting %d configuration impulse response filter%s",
@@ -662,10 +660,9 @@ gboolean notify_convolution_impulse_response_files_callback(ShairportSync *skele
return TRUE;
}
#else
-gboolean notify_convolution_impulse_response_files_callback(__attribute__((unused))
- ShairportSync *skeleton,
- __attribute__((unused))
- gpointer user_data) {
+gboolean
+notify_convolution_impulse_response_files_callback(__attribute__((unused)) ShairportSync *skeleton,
+ __attribute__((unused)) gpointer user_data) {
__attribute__((unused)) char *th =
(char *)shairport_sync_get_convolution_impulse_response_files(skeleton);
return TRUE;
@@ -716,7 +713,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton,
gdouble iv = shairport_sync_get_volume(skeleton);
if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) {
debug(2, ">> set volume to %7.4f.", iv);
- config.airplay_volume = iv;
+ config.airplay_volume = iv;
} else {
debug(1, ">> invalid volume: %f. Ignored.", iv);
shairport_sync_set_volume(skeleton, config.airplay_volume);
@@ -760,9 +757,10 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
-#ifdef CONFIG_AIRPLAY_2
+#ifdef CONFIG_AIRPLAY_2
if (strcasecmp(th, "ffmpeg") != 0) {
- warn(" This request, to set the decoder to \"%s\", is ignored. For AirPlay 2, the FFmpeg decoder is always used.",
+ warn(" This request, to set the decoder to \"%s\", is ignored. For AirPlay 2, the FFmpeg "
+ "decoder is always used.",
th);
}
#else
@@ -879,7 +877,8 @@ gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton,
return TRUE;
}
#else
-gboolean notify_shuffle_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton,
+gboolean notify_shuffle_callback(__attribute__((unused))
+ ShairportSyncAdvancedRemoteControl *skeleton,
__attribute__((unused)) gpointer user_data) {
return TRUE;
}
@@ -924,7 +923,8 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto
return TRUE;
}
#else
-gboolean notify_loop_status_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton,
+gboolean notify_loop_status_callback(__attribute__((unused))
+ ShairportSyncAdvancedRemoteControl *skeleton,
__attribute__((unused)) gpointer user_data) {
return TRUE;
}
@@ -971,16 +971,14 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
static gboolean on_handle_drop_session(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
- release_play_lock(NULL); // stop any current session and don't replace it
+ stop_play(); // stop any current session and don't replace it
shairport_sync_complete_drop_session(skeleton, invocation);
return TRUE;
}
-static gboolean on_handle_set_frame_position_update_interval(ShairportSync *skeleton,
- GDBusMethodInvocation *invocation,
- const gdouble seconds,
- __attribute__((unused))
- gpointer user_data) {
+static gboolean on_handle_set_frame_position_update_interval(
+ ShairportSync *skeleton, GDBusMethodInvocation *invocation, const gdouble seconds,
+ __attribute__((unused)) gpointer user_data) {
debug(1, ">> set frame position update interval to %.6f.", seconds);
config.metadata_progress_interval = seconds;
shairport_sync_complete_set_frame_position_update_interval(skeleton, invocation);
@@ -1062,10 +1060,10 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
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",
G_CALLBACK(on_handle_rewind), NULL);
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-toggle-mute",
diff --git a/definitions.h b/definitions.h
index 18a74d97..b27cba71 100644
--- a/definitions.h
+++ b/definitions.h
@@ -5,7 +5,6 @@
#include "config.h"
-
// for generating our own UUIDs
#define SHAIRPORT_SYNC_DEVICE_NAMESPACE "01d7c137-6316-455d-a52f-dfb529f26adf"
diff --git a/docker/Dockerfile b/docker/Dockerfile
index dcae96ca..106e07f5 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -106,6 +106,33 @@ RUN DESTDIR=install make install
WORKDIR /
##### SPS END #####
+# Shairport Sync Development Environment
+# This (large) image contains the custom-built FFmpeg library and NQPTP, Avahi, D-Bus, etc., Shairport Sync source and all tools.
+# When started, D-Bus, Avahi and NQPTP are set to run in the background.
+# To build it, here is a sample local build instruction:
+# $ docker build --build-arg SHAIRPORT_SYNC_BRANCH=development --build-arg NQPTP_BRANCH=main --target dev -t shairport-sync-dev -f docker/Dockerfile .
+# Sample local use (remember to save your changes):
+# $ docker run -it \
+# --cap-add=SYS_NICE --net host --device /dev/snd \
+# -v /etc/shairport-sync.conf:/etc/shairport-sync.conf \
+# shairport-sync-dev
+
+##### DEV #####
+FROM shairport-sync AS dev
+RUN apk add bash
+
+RUN addgroup shairport-sync && \
+ adduser -D shairport-sync -G shairport-sync && \
+ addgroup -g 29 docker_audio && \
+ addgroup shairport-sync docker_audio && \
+ addgroup shairport-sync audio && \
+ mkdir -p /run/dbus
+
+COPY --from=nqptp /nqptp/nqptp /usr/local/bin/nqptp
+COPY --chmod=755 ./docker/devrun.sh ./devrun.sh
+ENTRYPOINT ["./devrun.sh"]
+##### DEV END #####
+
##### STATIC FILES #####
FROM scratch AS files
diff --git a/docker/devrun.sh b/docker/devrun.sh
new file mode 100755
index 00000000..8d59a639
--- /dev/null
+++ b/docker/devrun.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+# exit if any command returns a non-zero result
+set -e
+echo "Shairport Sync Dev Startup ($(date))"
+if [ -z ${ENABLE_AVAHI+x} ] || [ $ENABLE_AVAHI -eq 1 ]; then
+ rm -rf /run/dbus/dbus.pid
+ rm -rf /run/avahi-daemon/pid
+ dbus-uuidgen --ensure
+ dbus-daemon --system
+ avahi-daemon --daemonize --no-chroot
+fi
+# Don't launch NQPTP if classic only
+SERVICE_TYPE=""
+for arg in "$@"; do
+ case "$arg" in
+ --service-type=classic|--service-type=airplay1)
+ SERVICE_TYPE="${arg#--service-type=}"
+ ;;
+ esac
+done
+if [ -z "$SERVICE_TYPE" ]; then
+ # not looking for Classic aka AirPlay 1 so start NQPTP for AirPlay 2
+ echo "Starting NQPTP ($(date))"
+ (/usr/local/bin/nqptp > /dev/null 2>&1) &
+fi
+while [ ! -f /var/run/avahi-daemon/pid ]; do
+ echo "Warning: avahi is not running, sleeping for 5 seconds before checking again"
+ sleep 5
+done
+# for PipeWire
+export XDG_RUNTIME_DIR=/tmp
+# for PulseAudio
+export PULSE_SERVER=unix:/tmp/pulseaudio.socket
+export PULSE_COOKIE=/tmp/pulseaudio.cookie
+echo "Finished startup tasks ($(date)), starting bash."
+exec /bin/bash
\ No newline at end of file
diff --git a/docker/run.sh b/docker/run.sh
index bb1b1da7..f02daa66 100755
--- a/docker/run.sh
+++ b/docker/run.sh
@@ -15,9 +15,23 @@ if [ -z ${ENABLE_AVAHI+x} ] || [ $ENABLE_AVAHI -eq 1 ]; then
avahi-daemon --daemonize --no-chroot
fi
-echo "Starting NQPTP ($(date))"
+# Don't launch NQPTP if it classic only
-(/usr/local/bin/nqptp > /dev/null 2>&1) &
+SERVICE_TYPE=""
+
+for arg in "$@"; do
+ case "$arg" in
+ --service-type=classic|--service-type=airplay1)
+ SERVICE_TYPE="${arg#--service-type=}"
+ ;;
+ esac
+done
+
+if [ -z "$SERVICE_TYPE" ]; then
+ # not looking for Classic aka AirPlay 1 so start NQPTP for AirPlay 2
+ echo "Starting NQPTP ($(date))"
+ (/usr/local/bin/nqptp > /dev/null 2>&1) &
+fi
while [ ! -f /var/run/avahi-daemon/pid ]; do
echo "Warning: avahi is not running, sleeping for 5 seconds before trying to start shairport-sync"
diff --git a/man/shairport-sync.1.xml b/man/shairport-sync.1.xml
index 44a3ff36..28f0d11b 100644
--- a/man/shairport-sync.1.xml
+++ b/man/shairport-sync.1.xml
@@ -39,7 +39,9 @@
[-a service-name | --name=service-name]
[-B command | --onstart=command]
[-c configurationfile | --configfile=configurationfile]
+ [--classic-airplay-only]
[-d | --daemon]
+ [--dbus-default-message-bus=bus]
[-E command | --onstop=command]
[-g | --get-cover-art]
[-j | --justDaemoniseNoPIDFile]
@@ -202,6 +204,15 @@
shairport-sync has been compiled with libdaemon support.
+
+
+
+