Lot of changes here.

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.
This commit is contained in:
Mike Brady
2026-04-26 10:23:18 +01:00
parent e2bb272d93
commit 11052b7896
56 changed files with 2672 additions and 1947 deletions
@@ -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
@@ -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:
+41
View File
@@ -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
+27 -3
View File
@@ -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");
}
}
+20 -4
View File
@@ -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
+1 -1
View File
@@ -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
+43 -1
View File
@@ -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
==
+5 -2
View File
@@ -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
}
+33 -8
View File
@@ -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
}
}
}
+140
View File
@@ -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;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef _AP2_EVENT_COMMAND_SENDER_H
#define _AP2_EVENT_COMMAND_SENDER_H
#include <stddef.h>
#include <sys/types.h>
#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
+33 -147
View File
@@ -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 <syscall.h>
// 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);
}
-161
View File
@@ -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 <syscall.h>
// 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);
}
-8
View File
@@ -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
+2 -2
View File
@@ -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];
+6 -6
View File
@@ -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 <syscall.h>
// 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;
+1 -1
View File
@@ -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++] =
+38 -18
View File
@@ -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);
+2 -15
View File
@@ -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);
+40 -19
View File
@@ -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
+3 -3
View File
@@ -43,7 +43,7 @@
#include <time.h>
#include <unistd.h>
#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...
+64 -16
View File
@@ -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",
+4
View File
@@ -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 <TargetConditionals.h>
+5 -1
View File
@@ -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
+310
View File
@@ -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 <stdlib.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#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
// "<frame_number>/<time_it_should_be_played_in_64_bit_nanoseconds>"."
// 'pdis' -- a discontinuity in the timestamps of incoming frames has been detected.
// timed. The argument is "<frame_number>/<discontinuity>".
// 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);
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include "common.h"
#include "config.h"
#include "../rtsp.h"
#include "pc_queue.h"
#include <pthread.h>
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);
+63 -2
View File
@@ -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 <mbedtls/md5.h>
@@ -59,6 +61,12 @@
#include <openssl/evp.h>
#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 <syscall.h>
// 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);
}
+9
View File
@@ -1,6 +1,7 @@
#pragma once
#include "common.h"
#include "config.h"
#include "rtsp.h"
#include <pthread.h>
#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);
+180
View File
@@ -0,0 +1,180 @@
#include "multicast.h"
#include "core.h"
#include "pc_queue.h"
#include <stdlib.h>
#include <string.h>
// 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 <syscall.h>
// 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.");
}
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <inttypes.h>
#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);
+172
View File
@@ -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 <stdlib.h>
#include <string.h>
#include <errno.h>
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;
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "common.h"
#include "config.h"
#include <pthread.h>
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);
+186
View File
@@ -0,0 +1,186 @@
#include "pipe.h"
#include "core.h"
#include "pc_queue.h"
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
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, "<item><type>%x</type><code>%x</code><length>%u</length>", 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<data encoding=\"base64\">\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, "</data>");
// 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, "</item>\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 <syscall.h>
// 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);
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <inttypes.h>
#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);
+17 -1
View File
@@ -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;
}
+73 -1
View File
@@ -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 <mosquitto.h>
// 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 <syscall.h>
// 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);
}
+6
View File
@@ -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 */
+1 -1
View File
@@ -40,7 +40,7 @@
<property name="ElapsedTime" type="b" access="readwrite" />
<property name="DeltaTime" type="b" access="readwrite" />
<property name="FileAndLine" type="b" access="readwrite" />
</interface>
</interface>
<interface name="org.gnome.ShairportSync.RemoteControl">
<method name='FastForward'/>
<method name='Rewind'/>
+1 -1
View File
@@ -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)
+5 -5
View File
@@ -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
+4 -4
View File
@@ -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
+87 -95
View File
@@ -72,8 +72,12 @@
#include <FFTConvolver/convolver.h>
#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...");
+5 -1
View File
@@ -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;
+6 -3
View File
@@ -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;
+300 -1259
View File
File diff suppressed because it is too large Load Diff
+21 -13
View File
@@ -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);
+1 -1
View File
@@ -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.
+115 -110
View File
@@ -54,6 +54,9 @@
#include <libavcodec/avcodec.h>
#include <sodium.h>
#include <uuid/uuid.h>
#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 <syscall.h>
// debug(1, "soxr_time_check PID %d", syscall(SYS_gettid));
// #include <syscall.h>
// 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);
}
+95 -26
View File
@@ -23,6 +23,7 @@ SOFTWARE.
*/
#include "debug.h"
#include <ctype.h> // for isprint()
#include <inttypes.h>
#include <pthread.h>
#include <stdarg.h>
@@ -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: <hexdump_cols * 3> <hexdump_cols>\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);
}
}
}
*/
+48
View File
@@ -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 <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#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;
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
char *generate_device_uuid(const char *device_id);
+47
View File
@@ -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 <stdlib.h>
#include <uuid/uuid.h>
#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;
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef _GENERATE_RANDOM_UUID_H
#define _GENERATE_RANDOM_UUID_H
char *generate_random_uuid();
#endif // _GENERATE_RANDOM_UUID_H
+2 -2
View File
@@ -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));
+265
View File
@@ -0,0 +1,265 @@
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <plist/plist.h>
#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);
}
}
}
+33
View File
@@ -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