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:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,7 +453,10 @@ 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);
|
||||
@@ -459,9 +464,30 @@ void *rtp_buffered_audio_processor(void *arg) {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
+26
-140
@@ -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);
|
||||
|
||||
/*
|
||||
// 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 {
|
||||
conn->connection_number, conn->event_socket, conn->event_channel_fd);
|
||||
pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg);
|
||||
|
||||
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);
|
||||
ap2_event_send_update_info(conn);
|
||||
|
||||
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);
|
||||
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
|
||||
} else {
|
||||
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);
|
||||
} else {
|
||||
debug(1, "Could not allocate a structured buffer!");
|
||||
}
|
||||
conn->ap2_event_receiver_exited = 1;
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Apple AirPlay 2 Remote Control (RC) Event Receiver. This file is part of Shairport Sync.
|
||||
* Copyright (c) Mike Brady 2014--2025
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ap2_rc_event_receiver.h"
|
||||
#include "common.h"
|
||||
#include "player.h"
|
||||
#include "rtsp.h"
|
||||
#include "utilities/network_utilities.h"
|
||||
#include "utilities/structured_buffer.h"
|
||||
|
||||
void ap2_rc_event_receiver_cleanup_handler(void *arg) {
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
debug(2, "Connection %d: AP2 Event Receiver Cleanup.", conn->connection_number);
|
||||
}
|
||||
|
||||
void *ap2_rc_event_receiver(void *arg) {
|
||||
// #include <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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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++] =
|
||||
|
||||
@@ -1041,33 +1041,50 @@ 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))) {
|
||||
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)) {
|
||||
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;
|
||||
return 1;
|
||||
response = CONFIG_TRUE;
|
||||
} else if (strcasecmp(str, "yes") == 0) {
|
||||
(*dst) = 1;
|
||||
return 1;
|
||||
response = CONFIG_TRUE;
|
||||
} else {
|
||||
die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
|
||||
die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, and has been ignored.", where);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// remember to free the returned array of strings.
|
||||
// you don't need to free the strings themselves -- they belong to libconfig.
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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...
|
||||
|
||||
+62
-14
@@ -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,16 +716,6 @@ 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;
|
||||
} else {
|
||||
debug(1, ">> invalid volume: %f. Ignored.", iv);
|
||||
@@ -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);
|
||||
@@ -1018,6 +1065,7 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
|
||||
|
||||
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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -777,6 +753,9 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
||||
int64_t resampler_output_format = 0;
|
||||
int resampler_channels_found = 0;
|
||||
|
||||
int oldState;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 57
|
||||
|
||||
AVChannelLayout output_channel_layout = {0};
|
||||
@@ -887,6 +866,8 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
||||
}
|
||||
}
|
||||
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
|
||||
if (output_channel_map_faulty != 0) {
|
||||
once(inform("The output device's %u-channel map is incomplete or faulty: \"%s\".",
|
||||
CHANNELS_FROM_ENCODED_FORMAT(config.current_output_configuration),
|
||||
@@ -1064,17 +1045,18 @@ 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!");
|
||||
}
|
||||
if (conn->codec_context != NULL) {
|
||||
// push a deallocator -- av_free(codec_context)
|
||||
pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, conn->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
|
||||
@@ -1082,7 +1064,7 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
|
||||
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.");
|
||||
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));
|
||||
@@ -1105,29 +1087,28 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) {
|
||||
} else {
|
||||
conn->codec_context->extradata = NULL;
|
||||
}
|
||||
pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata);
|
||||
// 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) {
|
||||
die("Could not initialise the codec context");
|
||||
pthread_setcancelstate(oldState, NULL);
|
||||
die("connection %d: could not initialise the codec context", conn->connection_number);
|
||||
}
|
||||
|
||||
// push a closer -- avcodec_close(codec_context);
|
||||
pthread_cleanup_push(avcodec_open2_cleanup_handler, conn->codec_context);
|
||||
} else {
|
||||
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...");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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!");
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
+113
-108
@@ -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));
|
||||
// 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,6 +1549,9 @@ 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));
|
||||
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
|
||||
@@ -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));
|
||||
|
||||
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;
|
||||
@@ -1638,17 +1590,37 @@ int parse_options(int argc, char **argv) {
|
||||
*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 we are asking for artwork, turn on the relevant bit
|
||||
if (config.get_coverart)
|
||||
config.airplay_features |= (uint64_t)1 << 15; // artwork
|
||||
// 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.
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
+94
-25
@@ -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;
|
||||
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_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
|
||||
@@ -275,3 +343,4 @@ void _debug_print_buffer(const char *thefilename, const int linenumber, int leve
|
||||
free(obf);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
char *generate_device_uuid(const char *device_id);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef _GENERATE_RANDOM_UUID_H
|
||||
#define _GENERATE_RANDOM_UUID_H
|
||||
|
||||
char *generate_random_uuid();
|
||||
|
||||
#endif // _GENERATE_RANDOM_UUID_H
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user