From 11052b789630295f259e8622bbf748c0b49da2fc Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:23:18 +0100 Subject: [PATCH] Lot of changes here. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. check_classic_mac_basic.yml — update to latest macOS-latest 2. docker-on-push-tag-or-pr — dependant updates 3. FFTConvolver — fix a compilation warning when the DEBUG flag is turned off 4. Reorganise some source files — e.g. move most of the metadata stuff into a subdirectory 5. Remove unused special-purpose AirPlay 2 RC event handler. 6. Reorganise source files and source code relating to the standard event handler. 7. Check configuration settings a little more carefully to offer more meaningful diagnostics. 8. Add password to AirPlay 2 and reorganise initialisation to set AirPlay 2 flags accordingly. Also, remove redundant airplay_pin. 9. Remove redundant debug facility definitions from common.h 10. Make it possible to omit all the dacp stuff from a build. 11. Make it possible to omit metadata multicast from a build. 12. Add AP2-native metadata by setting bit 50 of features to true. 13. Improve stability when pre-empting an existing play session. 14. Fix a few compiler warnings in the pair_ap library. 15. Ensure that the audio format is known before starting synchronisation. 16. Improve handle_configure to set/clear a new config setting. Doesn’t seem to do anything… 17. Experimentally turn off bit 46 of AirPlay 2 features — seems to stop failing pair-verify attempts. --- .github/workflows/check_classic_mac_basic.yml | 4 +- .../workflows/docker-on-push-tag-or-pr.yaml | 6 +- FFTConvolver/ConvolverThreadPool.cpp | 41 + FFTConvolver/convolver.cpp | 30 +- Makefile.am | 24 +- README.md | 2 +- RELEASENOTES-DEVELOPMENT.md | 44 +- activity_monitor.c | 7 +- ap2_buffered_audio_processor.c | 41 +- ap2_event_message_handler.c | 140 ++ ap2_event_message_handler.h | 14 + ap2_event_receiver.c | 180 +- ap2_rc_event_receiver.c | 161 -- ap2_rc_event_receiver.h | 8 - audio.c | 4 +- audio_alsa.c | 12 +- bonjour_strings.c | 2 +- common.c | 56 +- common.h | 17 +- configure.ac | 59 +- dacp.c | 6 +- dbus-service.c | 80 +- definitions.h | 4 + mdns_avahi.c | 6 +- metadata/core.c | 310 ++++ metadata/core.h | 39 + metadata_hub.c => metadata/hub.c | 65 +- metadata_hub.h => metadata/hub.h | 9 + metadata/multicast.c | 180 ++ metadata/multicast.h | 10 + metadata/pc_queue.c | 172 ++ metadata/pc_queue.h | 26 + metadata/pipe.c | 186 ++ metadata/pipe.h | 10 + mpris-service.c | 18 +- mqtt.c | 74 +- mqtt.h | 6 + org.gnome.ShairportSync.xml | 2 +- pair_ap/pair.c | 2 +- pair_ap/pair_fruit.c | 10 +- pair_ap/pair_homekit.c | 8 +- player.c | 182 +- player.h | 6 +- rtp.c | 9 +- rtsp.c | 1559 ++++------------- rtsp.h | 34 +- scripts/shairport-sync.conf | 2 +- shairport.c | 225 +-- utilities/debug.c | 121 +- utilities/generate_device_uuid.c | 48 + utilities/generate_device_uuid.h | 3 + utilities/generate_random_uuid.c | 47 + utilities/generate_random_uuid.h | 6 + utilities/network_utilities.c | 4 +- utilities/rtsp_message_utilities.c | 265 +++ utilities/rtsp_message_utilities.h | 33 + 56 files changed, 2672 insertions(+), 1947 deletions(-) create mode 100644 ap2_event_message_handler.c create mode 100644 ap2_event_message_handler.h delete mode 100644 ap2_rc_event_receiver.c delete mode 100644 ap2_rc_event_receiver.h create mode 100644 metadata/core.c create mode 100644 metadata/core.h rename metadata_hub.c => metadata/hub.c (91%) rename metadata_hub.h => metadata/hub.h (94%) create mode 100644 metadata/multicast.c create mode 100644 metadata/multicast.h create mode 100644 metadata/pc_queue.c create mode 100644 metadata/pc_queue.h create mode 100644 metadata/pipe.c create mode 100644 metadata/pipe.h create mode 100644 utilities/generate_device_uuid.c create mode 100644 utilities/generate_device_uuid.h create mode 100644 utilities/generate_random_uuid.c create mode 100644 utilities/generate_random_uuid.h create mode 100644 utilities/rtsp_message_utilities.c create mode 100644 utilities/rtsp_message_utilities.h diff --git a/.github/workflows/check_classic_mac_basic.yml b/.github/workflows/check_classic_mac_basic.yml index e0288d77..44f0def6 100644 --- a/.github/workflows/check_classic_mac_basic.yml +++ b/.github/workflows/check_classic_mac_basic.yml @@ -9,9 +9,7 @@ on: jobs: build: - - runs-on: macos-13 - + runs-on: macos-latest steps: - uses: actions/checkout@v6.0.2 - name: Install Dependencies diff --git a/.github/workflows/docker-on-push-tag-or-pr.yaml b/.github/workflows/docker-on-push-tag-or-pr.yaml index de8f857e..c3380f4e 100644 --- a/.github/workflows/docker-on-push-tag-or-pr.yaml +++ b/.github/workflows/docker-on-push-tag-or-pr.yaml @@ -59,7 +59,7 @@ jobs: fi - name: Login to Docker Registry - uses: docker/login-action@v3.7.0 + uses: docker/login-action@v4.1.0 with: registry: ${{ secrets.DOCKER_REGISTRY }} username: ${{ secrets.DOCKER_REGISTRY_USER }} @@ -67,13 +67,13 @@ jobs: if: needs.docker-vars.outputs.push_docker_image == 'true' - name: Set up QEMU - uses: docker/setup-qemu-action@v3.7.0 + uses: docker/setup-qemu-action@v4.0.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4.0.0 - name: Build and push ${{ matrix.name }} - uses: docker/build-push-action@v7.0.0 + uses: docker/build-push-action@v7.1.0 env: registry_and_name: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE_NAME }} with: diff --git a/FFTConvolver/ConvolverThreadPool.cpp b/FFTConvolver/ConvolverThreadPool.cpp index 8287417e..ef969572 100644 --- a/FFTConvolver/ConvolverThreadPool.cpp +++ b/FFTConvolver/ConvolverThreadPool.cpp @@ -1,7 +1,37 @@ +/* + * Convolver Thread Pool. This file is part of Shairport Sync + * Copyright (c) Mike Brady 2026 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + #include "ConvolverThreadPool.h" #include "FFTConvolver.h" #include "config.h" +extern "C" void _debug(const char *filename, const int linenumber, int level, const char *format, + ...); +#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__) + ConvolverThreadPool::ConvolverThreadPool() : _convolvers(), _threads(), _taskQueue(), _queueMutex(), _condition(), _completionCV(), _stop(false), _activeTasks(0) {} @@ -105,6 +135,16 @@ void ConvolverThreadPool::waitForAll() { _completionCV.wait(lock, [this]() { return _taskQueue.empty() && _activeTasks == 0; }); } +void ConvolverThreadPool::clearState(size_t convolverId) { + // Do the replacement assertion check first, and then wait for all tasks to stop + if (convolverId < _convolvers.size()) { + waitForAll(); + } else { + debug(1, "assert(convolverId < _convolvers.size()) failed, with convolverId: %u and _convolvers.size(): %u.", convolverId, _convolvers.size()); + } +} + +/* this is the old version void ConvolverThreadPool::clearState(size_t convolverId) { assert(convolverId < _convolvers.size()); @@ -113,6 +153,7 @@ void ConvolverThreadPool::clearState(size_t convolverId) { // _convolvers[convolverId]->clearState(); } +*/ void ConvolverThreadPool::clearAllStates() { // Make sure no tasks are running before clearing state diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp index 16eba015..c276d778 100644 --- a/FFTConvolver/convolver.cpp +++ b/FFTConvolver/convolver.cpp @@ -1,4 +1,28 @@ - +/* + * Convolver. This file is part of Shairport Sync + * Copyright (c) Mike Brady 2026 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #include "convolver.h" #include "ConvolverThreadPool.h" @@ -22,8 +46,8 @@ void convolver_pool_init(size_t numThreads, size_t numConvolvers) { if (!pool.init(numThreads, numConvolvers)) { debug(1, "failed to initialize thread pool!"); } else { - debug(1, "thread pool initialized with %u threads and %u convolvers.", numThreads, - numConvolvers); + debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads, numThreads == 1 ? "" : "s", + numConvolvers, numConvolvers == 1 ? "" : "s"); } } diff --git a/Makefile.am b/Makefile.am index 1d5bf176..b5c724d6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,7 +11,7 @@ homedir = @HOME@ man_MANS = $(top_srcdir)/man/shairport-sync.1 # endif -lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread +lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread --include=utilities/debug.h lib_tinyhttp_a_CFLAGS = -pthread lib_dbus_interface_a_CFLAGS = -pthread lib_mpris_interface_a_CFLAGS = -pthread @@ -27,7 +27,7 @@ noinst_LIBRARIES = # See below for the flags for the test client program -shairport_sync_SOURCES = shairport.c bonjour_strings.c rtsp.c mdns.c common.c rtp.c player.c audio.c loudness.c activity_monitor.c utilities/debug.c utilities/network_utilities.c +shairport_sync_SOURCES = shairport.c bonjour_strings.c rtsp.c mdns.c common.c rtp.c player.c audio.c loudness.c activity_monitor.c utilities/debug.c utilities/network_utilities.c utilities/rtsp_message_utilities.c if BUILD_FOR_DARWIN AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-deprecated-declarations -pthread -DSYSCONFDIR=\"$(sysconfdir)\" @@ -132,8 +132,20 @@ if USE_DNS_SD shairport_sync_SOURCES += mdns_dns_sd.c endif +if USE_METADATA +shairport_sync_SOURCES += metadata/pc_queue.c metadata/core.c +endif + +if USE_METADATA_PIPE +shairport_sync_SOURCES += metadata/pipe.c +endif + +if USE_METADATA_MULTICAST +shairport_sync_SOURCES += metadata/multicast.c +endif + if USE_METADATA_HUB -shairport_sync_SOURCES += metadata_hub.c +shairport_sync_SOURCES += metadata/hub.c endif if USE_MQTT @@ -148,7 +160,11 @@ lib_tinyhttp_a_SOURCES = tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c endif if USE_AIRPLAY_2 -shairport_sync_SOURCES += ap2_buffered_audio_processor.c ap2_event_receiver.c ap2_rc_event_receiver.c ptp-utilities.c utilities/buffered_read.c utilities/structured_buffer.c utilities/mod23.c plists/get_info_response.c +shairport_sync_SOURCES += ap2_buffered_audio_processor.c ap2_event_receiver.c \ + ap2_event_message_handler.c ptp-utilities.c \ + utilities/buffered_read.c utilities/structured_buffer.c utilities/mod23.c \ + utilities/generate_random_uuid.c utilities/generate_device_uuid.c \ + plists/get_info_response.c shairport_sync_LDADD += lib_pair_ap.a lib_pair_ap_a_SOURCES = pair_ap/pair.c pair_ap/pair_fruit.c pair_ap/pair_homekit.c pair_ap/pair-tlv.c noinst_LIBRARIES += lib_pair_ap.a diff --git a/README.md b/README.md index 2e74fd8c..f03b97df 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Shairport Sync does not support AirPlay video or photo streaming. * Next Steps and Advanced Topics are [here](ADVANCED%20TOPICS/README.md). * Runtime settings are documented [here](scripts/shairport-sync.conf). * Build configuration options are detailed in [CONFIGURATION FLAGS.md](CONFIGURATION%20FLAGS.md). -* The `man` page, detailing command line options, is [here](https://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/development/man/shairport-sync.html). +* The `man` page, detailing command line options, is [here](https://raw.githack.com/mikebrady/shairport-sync/development/man/shairport-sync.1.xml). * Some advanced topics and developed in [ADVANCED TOPICS](https://github.com/mikebrady/shairport-sync/tree/master/ADVANCED%20TOPICS). # Features diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index bf5e06e5..b12b9aa9 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,7 +1,49 @@ +Version 5.0.3-dev-4-g07ca1891 +== +**Bug Fix** +* Make code using `getifaddrs()` uncancellable to prevent a mysterious `Unexpected error 9 on netlink descriptor` error. Thanks to [microfx](https://github.com/microfx) for reporting the [issue](https://github.com/mikebrady/shairport-sync/issues/2184). + +Version 5.0.3-dev +== +**Small Changes** +* Add warnings if string or boolean configuration parameters were present but not strings. +* Improve behaviour when there is a large timing uncertainty/granularity and a large audio buffer. e.g. on some virtual machines. +* Make the SoxR delay checker thread behave better when being cancelled. +* Remove some debug messages, quieten some others, add a few. + +Version 5.0.1+-38-gf784e463 +== +**Build Update** +* The Apple ALAC Decoder can no longer be included in an AirPlay 2 build. The reason is that FFmpeg -- used instead -- deals comprehensively with ALAC, AAC, multichannel, mixdown and rate transcoding and is well maintained. + + The Apple ALAC Decoder is known to have security issues and is no longer maintained. This update prevents it from being included in Shairport Sync for AirPlay 2. + +Version 5.0.1+-36-g6729802d +== +**Update** +* The Shairport Sync `systemd` service file has been updated as follows: + * It uses POSIX shell parameter expansion functionality to prevent adding leading space in option values. + * It adds `nqptp.service` to the `After` and `Requires` options when building for Airplay 2. + + Many thanks to [mikelei8291](https://github.com/mikelei8291) for the [PR](https://github.com/mikebrady/shairport-sync/pull/2174). + +Version 5.0.1+-32-g20a87fef +== +**Build Bugfix** +* Fix a bug at the `.configure...` stage of a build of Classic Shairport Sync whereby the UUID library was incorrectly required if the FFmpeg library was being used. (The wording on a few associated warning messages was also fixed.) Thanks to [phexian](https://github.com/phexian) for the [report](https://github.com/mikebrady/shairport-sync/issues/2177). + +**Dependabot Update** +* A Dependabot update to a building tool was added. + +Version 5.0.1+-23-g7fb3506e +== +**Documentation Update** +* Fixed dead link to the online [`man`](https://raw.githack.com/mikebrady/shairport-sync/development/man/shairport-sync.1.xml) page. Thanks to [CornelisJ](https://github.com/CornelisJ) for the [report](https://github.com/mikebrady/shairport-sync/issues/2158). + Version 5.0.1+-13-ga14517d7 == **Change** -* When built for AirPlay 2, add pw=true to the Bonjour raop properties if the classic AirPlay password is set. This might help with Owntone compatibility. +* When built for AirPlay 2, add `pw=true` to the Bonjour raop properties if the classic AirPlay password is set. This might help with Owntone compatibility. Version 5.0.1+-11-g07546c35 == diff --git a/activity_monitor.c b/activity_monitor.c index bdd81c59..06fe4fee 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -40,7 +40,10 @@ #include "activity_monitor.h" #include "common.h" -#include "rtsp.h" + +#ifdef CONFIG_METADATA +#include "metadata/core.h" +#endif #ifdef CONFIG_DBUS_INTERFACE #include "dbus-service.h" @@ -128,7 +131,7 @@ void activity_monitor_signify_activity(int active) { pthread_mutex_unlock(&activity_monitor_mutex); } // lock the mutex again to send a signal - pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 4); pthread_cond_signal(&activity_monitor_cv); pthread_cleanup_pop(1); // release the mutex } diff --git a/ap2_buffered_audio_processor.c b/ap2_buffered_audio_processor.c index 30f3b00f..09581ded 100644 --- a/ap2_buffered_audio_processor.c +++ b/ap2_buffered_audio_processor.c @@ -235,6 +235,7 @@ void *rtp_buffered_audio_processor(void *arg) { uint16_t data_len; // here we read from the buffer that our thread has been reading + // debug(1,"read a block"); size_t bytes_remaining_in_buffer; nread = read_sized_block(buffered_audio, &data_len, sizeof(data_len), &bytes_remaining_in_buffer); @@ -249,6 +250,7 @@ void *rtp_buffered_audio_processor(void *arg) { // get the block itself // debug(1,"buffered audio packet of size %u detected.", data_len - 2); nread = read_sized_block(buffered_audio, packet, data_len - 2, &bytes_remaining_in_buffer); + // debug(1,"block read"); // diagnostic if ((conn->ap2_audio_buffer_minimum_size < 0) || @@ -442,7 +444,7 @@ void *rtp_buffered_audio_processor(void *arg) { // to decode it and pass it to the player if (new_audio_block_needed == 0) { // is there space in the player thread's buffer system? - size_t player_buffer_occupancy = get_audio_buffer_occupancy(conn); + // size_t player_buffer_occupancy = get_audio_buffer_occupancy(conn); // debug(1,"player buffer size and occupancy: %u and %u", player_buffer_size, // player_buffer_occupancy); @@ -451,17 +453,41 @@ void *rtp_buffered_audio_processor(void *arg) { // and send it to the player. Otherwise, keep the block and sleep for a while. // calculate if there is room in the decoded audio buffer... - int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <= config.audio_decoded_buffer_desired_length; + + // debug(1, "frames buffered: %f seconds, desired length: %f seconds.", (1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate, config.audio_decoded_buffer_desired_length); + + // int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <= config.audio_decoded_buffer_desired_length; uint64_t buffer_should_be_time; int have_valid_time = (frame_to_local_time(timestamp, &buffer_should_be_time, conn) == 0); // calculate the lead time to make sure it's not too early... int64_t lead_time = buffer_should_be_time - get_absolute_time_in_ns(); - - if ((play_enabled != 0) && (have_valid_time != 0) && - (audio_decoded_buffer_below_desired_length != 0) && - (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))) { + + + // debug(1,"play_enabled: %d, have_valid_time: %d, audio_decoded_buffer_below_desired_length: %d, lead_time * 1E-9: %f, (config.audio_decoded_buffer_desired_length + 0.1): %f, player_buffer_occupancy: %zu", + // play_enabled, have_valid_time, audio_decoded_buffer_below_desired_length, lead_time * 1E-9, (config.audio_decoded_buffer_desired_length + 0.1), player_buffer_occupancy + // ); + + // A slight problem here is that counting the number of buffers may not be sufficient, + // because the actual device may be + // taking data in large quantities at a single time. + + // So we just have to ensure that there + // is enough of a lead time maintained for sufficient audio to be available to prevent + // the device from under-running. + + // If means that the Shairport Sync player might riun out of audio occasionally, but + // as long as the device has enough in its buffer, everything is fine. + + // But it also means that Shairport Sync's buffers must be sufficient to hold all the + // entire lead-time's amount of audio in case the device has a zero-sized buffer. + + if ((play_enabled != 0) && (have_valid_time != 0) +// (audio_decoded_buffer_below_desired_length != 0) && + && (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1)) +// && (audio_decoded_buffer_below_desired_length != 0) + ) { very_early_packets_signalled = 0; //reset very early packet warning signaller @@ -639,8 +665,7 @@ void *rtp_buffered_audio_processor(void *arg) { debug(1, "incoming frame suddenly (?) has a lead time of %f seconds, with a desired decoded buffer length of %f.", 1.0 * lead_time * 1E-9, config.audio_decoded_buffer_desired_length); very_early_packets_signalled = 1; } - - usleep(20000); // wait for a while + usleep(((1000000 * conn->frames_per_packet) / conn->input_rate) * 2); // wait for approximately the length of two packets } } } diff --git a/ap2_event_message_handler.c b/ap2_event_message_handler.c new file mode 100644 index 00000000..7b03f436 --- /dev/null +++ b/ap2_event_message_handler.c @@ -0,0 +1,140 @@ +/* + * AirPlay 2 Event Port Message Handler. This file is part of Shairport Sync + * Copyright (c) Mike Brady 2025 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "ap2_event_message_handler.h" +#include "common.h" +#include "rtsp.h" +#include "utilities/structured_buffer.h" +#include "utilities/generate_random_uuid.h" + + +void decodeAndLogPlist(plist_t plist_to_log) { + if (plist_to_log != NULL) { + char *plist_as_string = plist_as_xml_text(plist_to_log); + if (plist_as_string != NULL) { + debug(3, "--\n%s\n--\n", plist_as_string); + free(plist_as_string); + } + } +} + +// will return -1 if there is an error or port is not open, 0 if the port was closed and a positive +// number if okay +ssize_t ap2_event_port_send_message(rtsp_conn_info *conn, char *data, size_t data_length) { + ssize_t result = -1; // assume a problem + debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4); + pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex); + if (conn->event_channel_fd != 0) { + result = write_encrypted(conn->event_channel_fd, &conn->ap2_pairing_context.event_cipher_bundle, + data, data_length); + if ((result != -1) && ((size_t)result == data_length)) { + debug(3, "Connection %d: Packet of %zu bytes successfully written on the Event Port.", + conn->connection_number, result); + uint8_t packet[4096]; + result = + read_encrypted(conn->event_channel_fd, &conn->ap2_pairing_context.event_cipher_bundle, + packet, sizeof(packet)); + debug(3, "Connection %d: Packet of %zu bytes successfully read on the Event Port.", + conn->connection_number, result); + if (result > 0) { + packet[result] = '\0'; + debug(3, "Connection %d: Packet Received on Event Port with contents: \n--\n%s\n--\n", + conn->connection_number, packet); + } else { + debug(2, "Connection %d: Event Port connection closed by client", conn->connection_number); + } + } else { + result = -1; // this covers a situation where the result is positive but not the same as the + // data_length + } + } else { + debug(1, "Connection %d: attempt to send a command to the event port over a closed socket", + conn->connection_number); + } + pthread_cleanup_pop(1); // unlock the mutex + return result; +} + +ssize_t ap2_event_port_post_command(rtsp_conn_info *conn, plist_t command) { + ssize_t result = 0; + decodeAndLogPlist(command); + structured_buffer *sbuf = sbuf_new(4096); + if (sbuf != NULL) { + pthread_cleanup_push(sbuf_cleanup, sbuf); + char *plistString = NULL; + uint32_t plistStringLength = 0; + + plist_to_bin(command, &plistString, &plistStringLength); + if (plistString != NULL) { + sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n", plistStringLength); + sbuf_printf(sbuf, "Content-Type: application/x-apple-binary-plist\r\n\r\n"); + sbuf_append(sbuf, plistString, plistStringLength); + free(plistString); // should be plist_to_bin_free, but it's not defined in older + // libraries + char *b = 0; + size_t l = 0; + sbuf_buf_and_length(sbuf, &b, &l); + result = ap2_event_port_send_message(conn, b, l); + debug(3, "Connection %d: POST /command sent on the event port. Result is %zd.", + conn->connection_number, result); + sbuf_clear(sbuf); + } + pthread_cleanup_pop(1); // delete the structured buffer + } + return result; +} + +ssize_t ap2_event_send_update_info(rtsp_conn_info *conn) { + // sends the updateInfo plist on the event port + ssize_t result = -1; + plist_t value_plist = generateInfoPlist(conn); + if (value_plist != NULL) { + void *txtData = NULL; + size_t txtDataLength = 0; + generateTxtDataValueInfo(conn, &txtData, &txtDataLength); + plist_dict_set_item(value_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength)); + free(txtData); + plist_t update_info_plist = plist_new_dict(); + if (update_info_plist != NULL) { + plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo")); + plist_dict_set_item(update_info_plist, "value", value_plist); + + char *plist_as_string = plist_as_xml_text(update_info_plist); + if (plist_as_string != NULL) { + debug(3, "update_info_plist is:\n--\n\"%s\"\n--\n", plist_as_string); + free(plist_as_string); + } + + result = ap2_event_port_post_command(conn, update_info_plist); + plist_free(update_info_plist); + } else { + debug(1, "Could not build an updateInfo plist"); + } + } else { + debug(1, "Could not build an updateInfo value plist"); + } + return result; +} diff --git a/ap2_event_message_handler.h b/ap2_event_message_handler.h new file mode 100644 index 00000000..fef68626 --- /dev/null +++ b/ap2_event_message_handler.h @@ -0,0 +1,14 @@ +#ifndef _AP2_EVENT_COMMAND_SENDER_H +#define _AP2_EVENT_COMMAND_SENDER_H + +#include +#include +#include "player.h" + +ssize_t ap2_event_send_unit_volume_notification(rtsp_conn_info *conn, double volume); +ssize_t ap2_event_send_modern_media_remote_command(rtsp_conn_info *conn, unsigned int command_number); +ssize_t ap2_event_send_update_info(rtsp_conn_info *conn); + +ssize_t ap2_event_send_dev_mule(); + +#endif // _AP2_EVENT_COMMAND_SENDER_H diff --git a/ap2_event_receiver.c b/ap2_event_receiver.c index 48d10d54..abff7c23 100644 --- a/ap2_event_receiver.c +++ b/ap2_event_receiver.c @@ -1,6 +1,6 @@ /* * Apple AirPlay 2 Event Receiver. This file is part of Shairport Sync. - * Copyright (c) Mike Brady 2014--2025 + * Copyright (c) Mike Brady 2014--2026 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -25,23 +25,27 @@ */ #include "ap2_event_receiver.h" -#include "bonjour_strings.h" +#include "ap2_event_message_handler.h" #include "common.h" #include "player.h" -#include "ptp-utilities.h" -#include "rtsp.h" #include "utilities/network_utilities.h" -#include "utilities/structured_buffer.h" + +#ifdef CONFIG_METADATA +#include "metadata/core.h" +#endif void ap2_event_receiver_cleanup_handler(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; // debug(1, "Connection %d: AP2 Event Receiver Cleanup start.", conn->connection_number); - // only update these things if you're (still) the principal conn - #ifdef CONFIG_METADATA // this is here to ensure it's only performed once during a teardown of a ptp stream send_ssnc_metadata('disc', conn->client_ip_string, strlen(conn->client_ip_string), 1); #endif + debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4); + pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex); + close(conn->event_channel_fd); + conn->event_channel_fd = 0; + pthread_cleanup_pop(1); // unlock the mutex if (conn->airplay_gid != NULL) { free(conn->airplay_gid); @@ -52,161 +56,43 @@ void ap2_event_receiver_cleanup_handler(void *arg) { free(conn->dacp_active_remote); conn->dacp_active_remote = NULL; } - if (conn->ap2_client_name) { - free(conn->ap2_client_name); - conn->ap2_client_name = NULL; - } - /* - pthread_rwlock_wrlock(&principal_conn_lock); // don't let the principal_conn be changed - pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); - if (principal_conn) - debug(1, "principal_conn: %d.", principal_conn->connection_number); - else - debug(1, "principal_conn: is NULL."); - if (principal_conn == conn) { - config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay - build_bonjour_strings(conn); - debug(1, "Connection %d: SETUP mdns_update on %s.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - mdns_update(NULL, secondary_txt_records); - principal_conn = NULL; - } - pthread_cleanup_pop(1); // release the principal_conn lock - */ debug(2, "Connection %d: AP2 Event Receiver Cleanup is complete.", conn->connection_number); } void *ap2_event_receiver(void *arg) { - // #include - // debug(1, "rtp_event_receiver PID %d", syscall(SYS_gettid)); rtsp_conn_info *conn = (rtsp_conn_info *)arg; debug(2, "Connection %d: AP2 Event Receiver started", conn->connection_number); - structured_buffer *sbuf = sbuf_new(4096); - if (sbuf != NULL) { - pthread_cleanup_push(sbuf_cleanup, sbuf); + SOCKADDR remote_addr; + memset(&remote_addr, 0, sizeof(remote_addr)); + socklen_t addr_size = sizeof(remote_addr); - /* - // only update these things if you're (still) the principal conn - pthread_rwlock_wrlock(&principal_conn_lock); // don't let the principal_conn be changed - pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); - if (principal_conn == conn) { - config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay - // config.airplay_statusflags |= 1 << 17; // ReceiverSessionIsActive - build_bonjour_strings(conn); - debug(2, "Connection %d: SETUP mdns_update on %s.", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - mdns_update(NULL, secondary_txt_records); - } - pthread_cleanup_pop(1); // release the principal_conn lock - */ - pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg); - - // listen(conn->event_socket, 5); // this is now done in the handle_setup_2 code - - uint8_t packet[4096]; - ssize_t nread; - SOCKADDR remote_addr; - memset(&remote_addr, 0, sizeof(remote_addr)); - socklen_t addr_size = sizeof(remote_addr); - - int fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size); + debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4); + pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex); + conn->event_channel_fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size); + pthread_cleanup_pop(1); // unlock the mutex + if (conn->event_channel_fd > 0) { debug(2, "Connection %d: ap2_event_receiver accepted a connection on socket %d and moved to a new " "socket %d.", - conn->connection_number, conn->event_socket, fd); - intptr_t pfd = fd; - pthread_cleanup_push(socket_cleanup, (void *)pfd); - int finished = 0; - do { - - plist_t value_plist = generateInfoPlist(conn); - if (value_plist != NULL) { - void *txtData = NULL; - size_t txtDataLength = 0; - generateTxtDataValueInfo(conn, &txtData, &txtDataLength); - plist_dict_set_item(value_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength)); - free(txtData); - plist_t update_info_plist = plist_new_dict(); - if (update_info_plist != NULL) { - plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo")); - plist_dict_set_item(update_info_plist, "value", value_plist); - char *plistString = NULL; - uint32_t plistStringLength = 0; - plist_to_bin(update_info_plist, &plistString, &plistStringLength); - if (plistString != NULL) { - char *plist_as_string = plist_as_xml_text(update_info_plist); - if (plist_as_string != NULL) { - debug(3, "Plist is: \"%s\".", plist_as_string); - free(plist_as_string); - } - sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n", - plistStringLength); - sbuf_printf(sbuf, "Content-Type: application/x-apple-binary-plist\r\n\r\n"); - sbuf_append(sbuf, plistString, plistStringLength); - - free(plistString); // should be plist_to_bin_free, but it's not defined in older - // libraries - char *b = 0; - size_t l = 0; - sbuf_buf_and_length(sbuf, &b, &l); - ssize_t wres = - write_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, b, l); - if ((wres == -1) || ((size_t)wres != l)) - debug(1, "Encrypted write error"); - - sbuf_clear(sbuf); - } else { - debug(1, "plist string not created!"); - } - plist_free(update_info_plist); - } else { - debug(1, "Could not build an updateInfo plist"); - } - // plist_free(value_plist); - } else { - debug(1, "Could not build an value plist"); - } - - while (finished == 0) { - nread = read_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, packet, - sizeof(packet)); - - // nread = recv(fd, packet, sizeof(packet), 0); - - if (nread < 0) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, - "Connection %d: error in ap2_event_receiver %d: \"%s\". Could not recv a packet.", - conn->connection_number, errno, errorstring); - // if ((config.diagnostic_drop_packet_fraction == 0.0) || - // (drand48() > config.diagnostic_drop_packet_fraction)) { - } else if (nread > 0) { - - // ssize_t plen = nread; - packet[nread] = '\0'; - debug(3, "Connection %d: Packet Received on Event Port with contents: \"%s\".", - conn->connection_number, packet); - } else { - debug(2, "Connection %d: Event Port connection closed by client", - conn->connection_number); - finished = 1; - } - } - - } while (finished == 0); - + conn->connection_number, conn->event_socket, conn->event_channel_fd); + pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg); + + ap2_event_send_update_info(conn); + + + while (1) { + usleep(1000000); + }; + debug(3, "Connection %d: AP2 Event Receiver RTP thread starting \"normal\" exit.", conn->connection_number); - pthread_cleanup_pop(1); // close the socket - pthread_cleanup_pop(1); // do the cleanup - pthread_cleanup_pop(1); // delete the structured buffer - debug(2, "Connection %d: AP2 Event Receiver RTP thread \"normal\" exit.", - conn->connection_number); } else { - debug(1, "Could not allocate a structured buffer!"); + debug(1, "Connection %d: could not accept an event socket", conn->connection_number); + conn->event_channel_fd = 0; } + debug(2, "Connection %d: AP2 Event Receiver RTP thread \"normal\" exit.", + conn->connection_number); conn->ap2_event_receiver_exited = 1; pthread_exit(NULL); } diff --git a/ap2_rc_event_receiver.c b/ap2_rc_event_receiver.c deleted file mode 100644 index 0fba8562..00000000 --- a/ap2_rc_event_receiver.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Apple AirPlay 2 Remote Control (RC) Event Receiver. This file is part of Shairport Sync. - * Copyright (c) Mike Brady 2014--2025 - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include "ap2_rc_event_receiver.h" -#include "common.h" -#include "player.h" -#include "rtsp.h" -#include "utilities/network_utilities.h" -#include "utilities/structured_buffer.h" - -void ap2_rc_event_receiver_cleanup_handler(void *arg) { - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - debug(2, "Connection %d: AP2 Event Receiver Cleanup.", conn->connection_number); -} - -void *ap2_rc_event_receiver(void *arg) { - // #include - // debug(1, "rtp_event_receiver PID %d", syscall(SYS_gettid)); - rtsp_conn_info *conn = (rtsp_conn_info *)arg; - if (conn->airplay_stream_category == remote_control_stream) - debug(2, "Connection %d (RC): AP2 RC Event Receiver started", conn->connection_number); - else - debug(2, "Connection %d: AP2 RC Event Receiver started", conn->connection_number); - - structured_buffer *sbuf = sbuf_new(4096); - if (sbuf != NULL) { - pthread_cleanup_push(sbuf_cleanup, sbuf); - - pthread_cleanup_push(ap2_rc_event_receiver_cleanup_handler, arg); - - // listen(conn->event_socket, 5); // this is now done in the handle_setup_2 code - - uint8_t packet[4096]; - ssize_t nread; - SOCKADDR remote_addr; - memset(&remote_addr, 0, sizeof(remote_addr)); - socklen_t addr_size = sizeof(remote_addr); - - int fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size); - debug(2, - "Connection %d: ap2_rc_event_receiver accepted a connection on socket %d and moved to a " - "new " - "socket %d.", - conn->connection_number, conn->event_socket, fd); - intptr_t pfd = fd; - pthread_cleanup_push(socket_cleanup, (void *)pfd); - int finished = 0; - do { - - plist_t value_plist = generateInfoPlist(conn); - if (value_plist != NULL) { - void *txtData = NULL; - size_t txtDataLength = 0; - generateTxtDataValueInfo(conn, &txtData, &txtDataLength); - plist_dict_set_item(value_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength)); - free(txtData); - plist_t update_info_plist = plist_new_dict(); - if (update_info_plist != NULL) { - plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo")); - plist_dict_set_item(update_info_plist, "value", value_plist); - char *plistString = NULL; - uint32_t plistStringLength = 0; - plist_to_bin(update_info_plist, &plistString, &plistStringLength); - if (plistString != NULL) { - char *plist_as_string = plist_as_xml_text(update_info_plist); - if (plist_as_string != NULL) { - debug(3, "Plist is: \"%s\".", plist_as_string); - free(plist_as_string); - } - sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n", - plistStringLength); - sbuf_printf(sbuf, "Content-Type: application/x-apple-binary-plist\r\n\r\n"); - sbuf_append(sbuf, plistString, plistStringLength); - - free(plistString); // should be plist_to_bin_free, but it's not defined in older - // libraries - char *b = 0; - size_t l = 0; - sbuf_buf_and_length(sbuf, &b, &l); - ssize_t wres = - write_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, b, l); - if ((wres == -1) || ((size_t)wres != l)) - debug(1, "Encrypted write error"); - - sbuf_clear(sbuf); - } else { - debug(1, "plist string not created!"); - } - plist_free(update_info_plist); - } else { - debug(1, "Could not build an updateInfo plist"); - } - // plist_free(value_plist); - } else { - debug(1, "Could not build an value plist"); - } - - while (finished == 0) { - nread = read_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, packet, - sizeof(packet)); - - // nread = recv(fd, packet, sizeof(packet), 0); - - if (nread < 0) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug( - 1, - "Connection %d: error in ap2_rc_event_receiver %d: \"%s\". Could not recv a packet.", - conn->connection_number, errno, errorstring); - // if ((config.diagnostic_drop_packet_fraction == 0.0) || - // (drand48() > config.diagnostic_drop_packet_fraction)) { - } else if (nread > 0) { - - // ssize_t plen = nread; - packet[nread] = '\0'; - debug(2, - "Connection %d: ap2_rc_event_receiver Packet Received on Event Port with contents: " - "\"%s\".", - conn->connection_number, packet); - } else { - debug(2, "Connection %d: ap2_rc_event_receiver Event Port connection closed by client", - conn->connection_number); - finished = 1; - } - } - - } while (finished == 0); - pthread_cleanup_pop(1); // close the socket - pthread_cleanup_pop(1); // do the cleanup - pthread_cleanup_pop(1); // delete the structured buffer - debug(2, "Connection %d: AP2 ap2_rc_event_receiver \"normal\" exit.", conn->connection_number); - } else { - debug(1, "Could not allocate a structured buffer!"); - } - conn->ap2_event_receiver_exited = 1; - pthread_exit(NULL); -} diff --git a/ap2_rc_event_receiver.h b/ap2_rc_event_receiver.h deleted file mode 100644 index 200e785b..00000000 --- a/ap2_rc_event_receiver.h +++ /dev/null @@ -1,8 +0,0 @@ -// RC means Remote Control - -#ifndef _AP2_RC_EVENT_RECEIVER_H -#define _AP2_RC_EVENT_RECEIVER_H - -void *ap2_rc_event_receiver(void *arg); - -#endif // _AP2_RC_EVENT_RECEIVER_H diff --git a/audio.c b/audio.c index 7c08de00..a703d660 100644 --- a/audio.c +++ b/audio.c @@ -985,7 +985,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra } #ifdef CONFIG_FFMPEG if (local_format == SPS_FORMAT_UNKNOWN) { - debug(3, "check for an next highest rate above %u with %u channels.", rate, + debug(3, "check for the next highest rate above %u with %u channels.", rate, local_channels); unsigned int rate_pointer = 0; while ((rate_pointer < sizeof(rates) / sizeof(unsigned int)) && @@ -1003,7 +1003,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra if (local_format == SPS_FORMAT_UNKNOWN) { int rate_pointer = (int)(sizeof(rates) / sizeof(unsigned int) - 1); - debug(3, "check for an next lowest rate below %u with %u channels.", rate, + debug(3, "check for the next lowest rate below %u with %u channels.", rate, local_channels); while ((rate_pointer >= 0) && (local_format == SPS_FORMAT_UNKNOWN)) { local_rate = rates[rate_pointer]; diff --git a/audio_alsa.c b/audio_alsa.c index b105457c..bf03dfc8 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -306,7 +306,7 @@ static int get_permissible_configuration_settings() { debug(3, "\"%s\" can handle %u channels.", alsa_out_dev, c); } else { // the device can't handle this number of channels - debug(3, "\"%s\" can not handle %u channels.", alsa_out_dev, c); + debug(4, "\"%s\" can not handle %u channels.", alsa_out_dev, c); config.channel_set &= ~(1 << c); // the alsa device can't accommodate this number of channels } @@ -332,7 +332,7 @@ static int get_permissible_configuration_settings() { debug(3, "\"%s\" can handle a rate of %u fps.", alsa_out_dev, sps_rate_actual_rate(r)); } else { - debug(3, "\"%s\" can not handle a rate of %u fps.", alsa_out_dev, + debug(4, "\"%s\" can not handle a rate of %u fps.", alsa_out_dev, sps_rate_actual_rate(r)); config.rate_set &= ~(1 << r); // the alsa device doesn't do this rate } @@ -357,7 +357,7 @@ static int get_permissible_configuration_settings() { debug(3, "\"%s\" can handle the %s format.", alsa_out_dev, sps_format_description_string(f)); } else { - debug(3, "\"%s\" can not handle the %s format.", alsa_out_dev, + debug(4, "\"%s\" can not handle the %s format.", alsa_out_dev, sps_format_description_string(f)); config.format_set &= ~(1 << f); // the alsa device doesn't do this format } @@ -2177,14 +2177,14 @@ static void flush(void) { } static void stop(void) { - pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1); + pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 4); if (alsa_backend_state != abm_disconnected) { // must be playing or connected... if (config.keep_dac_busy == 0) { do_close(); } } else debug(3, "alsa: stop() -- called on a disconnected alsa backend"); - debug_mutex_unlock(&alsa_mutex, 3); + debug_mutex_unlock(&alsa_mutex, 4); pthread_cleanup_pop(0); // release the mutex } @@ -2252,7 +2252,7 @@ static void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) // #include // debug(1, "alsa_buffer_monitor_thread_code PID %d", syscall(SYS_gettid)); // Wait until the output configuration has been set by the main program - debug(2, "alsa: alsa_buffer_monitor_thread_code started."); + debug(1, "alsa: alsa_buffer_monitor_thread_code started."); int frame_count = 0; int error_count = 0; int error_detected = 0; diff --git a/bonjour_strings.c b/bonjour_strings.c index 19d63e98..ff9c37a7 100644 --- a/bonjour_strings.c +++ b/bonjour_strings.c @@ -149,7 +149,7 @@ void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) { entry_number = 0; secondary_txt_records[entry_number++] = "acl=0"; - secondary_txt_records[entry_number++] = "btaddr=00:00:00:00:00:00"; + // secondary_txt_records[entry_number++] = "btaddr=00:00:00:00:00:00"; secondary_txt_records[entry_number++] = bnprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id); secondary_txt_records[entry_number++] = diff --git a/common.c b/common.c index e552af4c..e5961c98 100644 --- a/common.c +++ b/common.c @@ -1041,32 +1041,49 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) { #endif int config_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value) { - int response = config_lookup_string(cfg, path, value); - if (response == CONFIG_TRUE) { - if ((value != NULL) && ((*value == NULL) || (*value[0] == 0))) { - warn("The \"%s\" parameter is an empty string and has been ignored.", path); - response = CONFIG_FALSE; + int response = CONFIG_FALSE; + config_setting_t *s = config_lookup(cfg, path); + if (s != NULL) { + // the setting exists, but might not be a string + if (config_setting_type(s) == CONFIG_TYPE_STRING) { + if (value != NULL) { + *value = config_setting_get_string(s); + response = CONFIG_TRUE; + // the string might be empty... + if ((*value == NULL) || (*value[0] == 0)) { + warn("The \"%s\" parameter is an empty string and has been ignored.", path); + response = CONFIG_FALSE; + } + } + } else { + warn("the \"%s\" parameter is not a string, as required, and has been ignored.", path); } } return response; } -int config_set_lookup_bool(config_t *cfg, char *where, int *dst) { - const char *str = 0; - if (config_lookup_string(cfg, where, &str)) { - if (strcasecmp(str, "no") == 0) { - (*dst) = 0; - return 1; - } else if (strcasecmp(str, "yes") == 0) { - (*dst) = 1; - return 1; +int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) { + const char *str = NULL; + int response = CONFIG_FALSE; + config_setting_t *s = config_lookup(cfg, where); + if (s != NULL) { + if (config_setting_type(s) == CONFIG_TYPE_STRING) { + str = config_setting_get_string(s); + if (strcasecmp(str, "no") == 0) { + (*dst) = 0; + response = CONFIG_TRUE; + } else if (strcasecmp(str, "yes") == 0) { + (*dst) = 1; + response = CONFIG_TRUE; + } else { + die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"", where, str); + return 0; + } } else { - die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str); - return 0; + warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, and has been ignored.", where); } - } else { - return 0; } + return response; } // remember to free the returned array of strings. @@ -2322,6 +2339,8 @@ int get_device_id(uint8_t *id, int int_length) { int64_t time_to_wait; do { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable if (getifaddrs(&ifaddr) == 0) { t = id; int found = 0; @@ -2361,6 +2380,7 @@ int get_device_id(uint8_t *id, int int_length) { } freeifaddrs(ifaddr); } + pthread_setcancelstate(oldState, NULL); // wait a little time if we haven't got a response if (response != 0) { usleep(100000); diff --git a/common.h b/common.h index bf472829..a7a0604d 100644 --- a/common.h +++ b/common.h @@ -396,13 +396,13 @@ typedef struct { uint32_t airplay_statusflags; char *airplay_fex; // a base64-encoded version of the airplay_features in little-endian form char *airplay_device_id; // for the Bonjour advertisement and the GETINFO PList - char *airplay_pin; // non-NULL, 4 char PIN, if required for pairing char *airplay_pi; // UUID in the Bonjour advertisement and the GETINFO Plist char *airplay_pgid; // UUID in the txtAirPlay data sent on the event channel char *airplay_psi; // type 4 fixed UUID uint8_t airplay_pk[32]; // public key char *pk_string; char *nqptp_shared_memory_interface_name; // client name for nqptp service + int enable_HK_Access_Control; // true if the device is part of an Apple Home #endif int unfixable_error_reported; // only report once. @@ -478,19 +478,6 @@ uint16_t nextFreeUDPPort(); extern volatile int debuglev; -void _die(const char *filename, const int linenumber, const char *format, ...); -void _warn(const char *filename, const int linenumber, const char *format, ...); -void _inform(const char *filename, const int linenumber, const char *format, ...); -void _debug(const char *filename, const int linenumber, int level, const char *format, ...); -void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *buf, - size_t buf_len); - -#define die(...) _die(__FILE__, __LINE__, __VA_ARGS__) -#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__) -#define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__) -#define inform(...) _inform(__FILE__, __LINE__, __VA_ARGS__) -#define debug_print_buffer(...) _debug_print_buffer(__FILE__, __LINE__, __VA_ARGS__) - // Thanks to https://stackoverflow.com/a/1597129 for the inspiration for this identifier generation #define MAKEUNIQUEID2(x, y) x##y #define MADEID(x, y) MAKEUNIQUEID2(x, y) @@ -561,7 +548,7 @@ extern int type_of_exit_cleanup; // normal, emergency, dbus requested... extern uint64_t minimum_dac_queue_size; int config_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value); -int config_set_lookup_bool(config_t *cfg, char *where, int *dst); +int config_set_lookup_bool(config_t *cfg, const char *where, int *dst); int check_string_or_list_setting(config_setting_t *setting, const char *item); int check_int_or_list_setting(config_setting_t *setting, const int item); diff --git a/configure.ac b/configure.ac index 002639c0..dbf3901f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [5.0.1+], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [5.0.4-dev], [4265913+mikebrady@users.noreply.github.com]) : ${CFLAGS="-O3"} : ${CXXFLAGS="-O3"} AM_INIT_AUTOMAKE([subdir-objects]) @@ -219,6 +219,12 @@ fi # Look for metadata flag and resolve it further down the script AC_ARG_WITH(metadata,[AS_HELP_STRING([--with-metadata],[include support for a metadata feed])]) +# Look for metadata_pipe flag and resolve it further down the script +AC_ARG_WITH(metadata_pipe,[AS_HELP_STRING([--with-metadata-pipe],[include support for piping metadata out through a unix pipe])]) + +# Look for metadata_multicast flag and resolve it further down the script +AC_ARG_WITH(metadata_multicast,[AS_HELP_STRING([--with-metadata-multicast],[include support for metadata multicasting])]) + # What follows is a bit messy, because if the relevant library is requested, a compiler flag is defined, a file is included in the compilation # and the relevant link files are added. @@ -228,8 +234,8 @@ if test "x$with_avahi" = "xyes" ; then AC_DEFINE([CONFIG_AVAHI], 1, [Include Avahi-based mDNS support.]) AC_CHECK_LIB([avahi-client], [avahi_client_new], , AC_MSG_ERROR(Avahi support requires the avahi-client library!)) AC_CHECK_LIB([avahi-common],[avahi_strerror], , AC_MSG_ERROR(Avahi support requires the avahi-common library!)) - systemd_after_args="${systemd_after_args} avahi-daemon.service" - systemd_requires_args="${systemd_requires_args} avahi-daemon.service" + systemd_after_args="${systemd_after_args}${systemd_after_args:+ }avahi-daemon.service" + systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }avahi-daemon.service" fi AM_CONDITIONAL([USE_AVAHI], [test "x$with_avahi" = "xyes"]) @@ -404,14 +410,33 @@ fi AM_CONDITIONAL([USE_METADATA_HUB], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"]) AM_CONDITIONAL([USE_DACP_CLIENT], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"]) -if test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1" || test "x$with_metadata" = "xyes" ; then +if test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1" || test "x$with_metadata" = "xyes" || test "x$with_metadata_pipe" = "xyes" || test "x$with_metadata_multicast" = "xyes" ; then + REQUESTED_METADATA_SUPPORT=1 +fi + +if test "x$REQUESTED_METADATA_SUPPORT" = "x1" ; then AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.]) fi -AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"]) +AM_CONDITIONAL([USE_METADATA], [test "x$REQUESTED_METADATA_SUPPORT" = "x1"]) + +if test "x$with_metadata" = "xyes" ; then + DEFAULT_INCLUDE_PIPE_METADATA=1 +fi + +if test "x$DEFAULT_INCLUDE_PIPE_METADATA" = "x1" || test "x$with_metadata_pipe" = "xyes" ; then + AC_DEFINE([CONFIG_METADATA_PIPE], 1, [Needed by the compiler.]) +fi +AM_CONDITIONAL([USE_METADATA_PIPE], [test "x$DEFAULT_INCLUDE_PIPE_METADATA" = "x1" || test "x$with_metadata_pipe" = "xyes"]) + +if test "x$with_metadata_multicast" = "xyes" ; then + AC_DEFINE([CONFIG_METADATA_MULTICAST], 1, [Needed by the compiler.]) +fi +AM_CONDITIONAL([USE_METADATA_MULTICAST], [test "x$with_metadata_multicast" = "xyes"]) # Look for AirPlay 2 flag AC_ARG_WITH(airplay-2, [AS_HELP_STRING([--with-airplay-2],[Build for AirPlay 2])]) if test "x$with_airplay_2" = "xyes" ; then + using_ffmpeg=true AC_DEFINE([CONFIG_AIRPLAY_2], 1, [Build for AirPlay 2]) AC_CHECK_PROG(PLISTUTIL_CHECK,plistutil,yes) AS_IF([test x"$PLISTUTIL_CHECK" != x"yes"], [AC_MSG_ERROR([plistutil can not be found. Please install plistutil for building for AirPlay 2. Maybe it's in the apt package libplist-utils.])]) @@ -443,6 +468,8 @@ if test "x$with_airplay_2" = "xyes" ; then ) ]) AC_CHECK_LIB([uuid],[uuid_generate], [], [AC_MSG_ERROR([AirPlay 2 support requires the uuid library -- uuid-dev suggested])]) + systemd_after_args="${systemd_after_args}${systemd_after_args:+ }nqptp.service" + systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }nqptp.service" fi AM_CONDITIONAL([USE_AIRPLAY_2], [test "x$with_airplay_2" = "xyes"]) @@ -455,7 +482,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then PKG_CHECK_MODULES( [libavutil], [libavutil], [CFLAGS="${libavutil_CFLAGS} ${CFLAGS}" LIBS="${libavutil_LIBS} ${LIBS}"], - [AC_MSG_ERROR(AirPlay 2 support requires libavutil -- libavutil-dev suggested)] + [AC_MSG_ERROR(FFmpeg requires libavutil -- libavutil-dev suggested)] ) PKG_CHECK_MODULES( [libavcodec], [libavcodec], @@ -463,7 +490,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then [AC_CHECK_LIB( [avcodec], [avcodec_find_decoder], [], - [AC_MSG_ERROR(AirPlay 2 support requires libavcodec -- libavcodec-dev suggested)] + [AC_MSG_ERROR(FFmpeg requires libavcodec -- libavcodec-dev suggested)] )] ) PKG_CHECK_MODULES( @@ -472,7 +499,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then [AC_CHECK_LIB( [avformat], [avformat_new_stream], [], - [AC_MSG_ERROR([AirPlay 2 support requires libavformat -- libavformat-dev suggested])] + [AC_MSG_ERROR([FFmpeg requires libavformat -- libavformat-dev suggested])] )] ) PKG_CHECK_MODULES( @@ -481,16 +508,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then [AC_CHECK_LIB( [swresample], [swr_convert], [], - [AC_MSG_ERROR([AirPlay 2 support requires libswresample -- libswresample-dev suggested])] - )] - ) - PKG_CHECK_MODULES( - [libuuid], [uuid], - [CFLAGS="${libuuid_CFLAGS} ${CFLAGS}" LIBS="${libuuid_LIBS} ${LIBS}"], - [AC_CHECK_LIB( - [uuid], [uuid_generate], - [], - [AC_MSG_ERROR([AirPlay 2 support requires the uuid library -- uuid-dev suggested])] + [AC_MSG_ERROR([FFmpeg requires libswresample -- libswresample-dev suggested])] )] ) else @@ -505,8 +523,11 @@ AM_CONDITIONAL([USE_HAMMERTON], [test $using_ffmpeg = false]) # Look for Apple ALAC flag AC_ARG_WITH(apple-alac,[AS_HELP_STRING([--with-apple-alac],[include support for the Apple ALAC decoder (deprecated)])]) if test "x${with_apple_alac}" = "xyes" ; then + if test "x${with_airplay_2}" = "xyes" ; then + AC_MSG_ERROR([the Apple ALAC Decoder (deprecated) can not be used in AirPlay 2.]) + fi if test $using_ffmpeg = true ; then - AC_MSG_RESULT([Note: the FFMPEG decoder, included in the build, supercedes the Apple ALAC decoder, which is deprecated. Consider omitting the Apple ALAC decoder.]) + AC_MSG_WARN([the FFMPEG decoder, included in the build, supercedes the Apple ALAC Decoder, which is deprecated. Consider omitting the Apple ALAC Decoder.]) fi AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Include support for using the Apple ALAC Decoder (deprecated)]) if test "x${with_pkg_config}" = xyes ; then diff --git a/dacp.c b/dacp.c index aade558f..2252482f 100644 --- a/dacp.c +++ b/dacp.c @@ -43,7 +43,7 @@ #include #include -#include "metadata_hub.h" +#include "metadata/hub.h" #include "tinyhttp/http.h" typedef struct { @@ -397,10 +397,10 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) { // as the conn's connection number // this is to signify that the player has stopped, but only if another thread (with a different // index) hasn't already taken over the dacp service - debug_mutex_lock(&dacp_server_information_lock, 500000, 2); + debug_mutex_lock(&dacp_server_information_lock, 500000, 4); if (dacp_server.players_connection_thread_index == conn->connection_number) dacp_server.players_connection_thread_index = 0; - debug_mutex_unlock(&dacp_server_information_lock, 3); + debug_mutex_unlock(&dacp_server_information_lock, 4); } // this will be running on the thread of its caller, not of the conversation thread... diff --git a/dbus-service.c b/dbus-service.c index 15d02fa0..781fed0e 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -37,8 +37,11 @@ #include "rtp.h" +#ifdef CONFIG_DACP_CLIENT #include "dacp.h" -#include "metadata_hub.h" +#endif + +#include "metadata/hub.h" #include "dbus-service.h" @@ -335,7 +338,9 @@ static gboolean on_handle_set_volume(ShairportSyncAdvancedRemoteControl *skeleto GDBusMethodInvocation *invocation, const gint volume, __attribute__((unused)) gpointer user_data) { debug(2, "Set volume to %d.", volume); +#ifdef CONFIG_DACP_CLIENT dacp_set_volume(volume); +#endif shairport_sync_advanced_remote_control_complete_set_volume(skeleton, invocation); return TRUE; } @@ -343,7 +348,9 @@ static gboolean on_handle_set_volume(ShairportSyncAdvancedRemoteControl *skeleto static gboolean on_handle_fast_forward(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("beginff"); +#endif shairport_sync_remote_control_complete_fast_forward(skeleton, invocation); return TRUE; } @@ -351,7 +358,9 @@ static gboolean on_handle_fast_forward(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_rewind(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("beginrew"); +#endif shairport_sync_remote_control_complete_rewind(skeleton, invocation); return TRUE; } @@ -359,7 +368,9 @@ static gboolean on_handle_rewind(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_toggle_mute(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("mutetoggle"); +#endif shairport_sync_remote_control_complete_toggle_mute(skeleton, invocation); return TRUE; } @@ -367,7 +378,9 @@ static gboolean on_handle_toggle_mute(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_next(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("nextitem"); +#endif shairport_sync_remote_control_complete_next(skeleton, invocation); return TRUE; } @@ -375,7 +388,9 @@ static gboolean on_handle_next(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_previous(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("previtem"); +#endif shairport_sync_remote_control_complete_previous(skeleton, invocation); return TRUE; } @@ -383,7 +398,9 @@ static gboolean on_handle_previous(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_pause(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("pause"); +#endif shairport_sync_remote_control_complete_pause(skeleton, invocation); return TRUE; } @@ -391,7 +408,9 @@ static gboolean on_handle_pause(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("playpause"); +#endif shairport_sync_remote_control_complete_play_pause(skeleton, invocation); return TRUE; } @@ -399,7 +418,9 @@ static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("play"); +#endif shairport_sync_remote_control_complete_play(skeleton, invocation); return TRUE; } @@ -407,7 +428,9 @@ static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_stop(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("stop"); +#endif shairport_sync_remote_control_complete_stop(skeleton, invocation); return TRUE; } @@ -415,7 +438,9 @@ static gboolean on_handle_stop(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_resume(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("playresume"); +#endif shairport_sync_remote_control_complete_resume(skeleton, invocation); return TRUE; } @@ -423,7 +448,9 @@ static gboolean on_handle_resume(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_shuffle_songs(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("shuffle_songs"); +#endif shairport_sync_remote_control_complete_shuffle_songs(skeleton, invocation); return TRUE; } @@ -431,7 +458,9 @@ static gboolean on_handle_shuffle_songs(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_volume_up(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("volumeup"); +#endif shairport_sync_remote_control_complete_volume_up(skeleton, invocation); return TRUE; } @@ -439,7 +468,9 @@ static gboolean on_handle_volume_up(ShairportSyncRemoteControl *skeleton, static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("volumedown"); +#endif shairport_sync_remote_control_complete_volume_down(skeleton, invocation); return TRUE; } @@ -449,9 +480,11 @@ static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleto const gdouble volume, __attribute__((unused)) gpointer user_data) { debug(2, "Set airplay volume to %.6f.", volume); +#ifdef CONFIG_DACP_CLIENT char command[256] = ""; snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume); send_simple_dacp_command(command); +#endif shairport_sync_remote_control_complete_set_airplay_volume(skeleton, invocation); return TRUE; } @@ -683,17 +716,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton, gdouble iv = shairport_sync_get_volume(skeleton); if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) { debug(2, ">> set volume to %7.4f.", iv); - - pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed - pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); - - if (principal_conn != NULL) { - player_volume(iv, principal_conn); - principal_conn->own_airplay_volume = iv; - principal_conn->own_airplay_volume_set = 1; - } - pthread_cleanup_pop(1); // release the principal_conn lock - config.airplay_volume = iv; + config.airplay_volume = iv; } else { debug(1, ">> invalid volume: %f. Ignored.", iv); shairport_sync_set_volume(skeleton, config.airplay_volume); @@ -737,6 +760,12 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton, __attribute__((unused)) gpointer user_data) { char *th = (char *)shairport_sync_get_alacdecoder(skeleton); +#ifdef CONFIG_AIRPLAY_2 + if (strcasecmp(th, "ffmpeg") != 0) { + warn(" This request, to set the decoder to \"%s\", is ignored. For AirPlay 2, the FFmpeg decoder is always used.", + th); + } +#else if ((strcasecmp(th, "hammerton") == 0) && ((config.decoders_supported & (1 << decoder_hammerton)) != 0)) config.decoder_in_use = 1 << decoder_hammerton; @@ -747,12 +776,13 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton, ((config.decoders_supported & (1 << decoder_ffmpeg_alac)) != 0)) config.decoder_in_use = 1 << decoder_ffmpeg_alac; else { - warn("An unrecognised or unsupported ALAC decoder: \"%s\" was requested via D-Bus interface. " + warn("An unrecognised or unsupported decoder: \"%s\" was requested via D-Bus interface. " "(Possibly " "support for this decoder was not compiled " "into this version of Shairport Sync.)", th); } +#endif return TRUE; } @@ -838,6 +868,7 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton, return TRUE; } +#ifdef CONFIG_DACP_CLIENT gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton, __attribute__((unused)) gpointer user_data) { // debug(1,"notify_shuffle_callback called"); @@ -847,9 +878,17 @@ gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton, send_simple_dacp_command("setproperty?dacp.shufflestate=0"); return TRUE; } +#else +gboolean notify_shuffle_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton, + __attribute__((unused)) gpointer user_data) { + return TRUE; +} +#endif +#ifdef CONFIG_DACP_CLIENT gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleton, __attribute__((unused)) gpointer user_data) { + // debug(1,"notify_loop_status_callback called"); char *th = (char *)shairport_sync_advanced_remote_control_get_loop_status(skeleton); // enum volume_control_profile_type previous_volume_control_profile = @@ -884,6 +923,12 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto } return TRUE; } +#else +gboolean notify_loop_status_callback(__attribute__((unused)) ShairportSyncAdvancedRemoteControl *skeleton, + __attribute__((unused)) gpointer user_data) { + return TRUE; +} +#endif static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) const gchar *command, @@ -900,10 +945,12 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv __attribute__((unused)) gpointer user_data) { debug(1, "RemoteCommand with command \"%s\".", command); int reply = 0; + char *client_reply_hex = ""; +#ifdef CONFIG_DACP_CLIENT char *client_reply = NULL; ssize_t reply_size = 0; reply = dacp_send_command((const char *)command, &client_reply, &reply_size); - char *client_reply_hex = alloca(reply_size * 2 + 1); + client_reply_hex = alloca(reply_size * 2 + 1); if (client_reply_hex) { char *p = client_reply_hex; if (client_reply) { @@ -917,6 +964,7 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv } *p = '\0'; } +#endif shairport_sync_complete_remote_command(skeleton, invocation, reply, client_reply_hex); return TRUE; } @@ -958,7 +1006,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name shairportSyncAdvancedRemoteControlSkeleton = shairport_sync_advanced_remote_control_skeleton_new(); - g_dbus_interface_skeleton_export( G_DBUS_INTERFACE_SKELETON(shairportSyncAdvancedRemoteControlSkeleton), connection, "/org/gnome/ShairportSync", NULL); @@ -1015,9 +1062,10 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name g_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::file-and-line", G_CALLBACK(notify_file_and_line_callback), NULL); - + g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-fast-forward", G_CALLBACK(on_handle_fast_forward), NULL); + g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-rewind", G_CALLBACK(on_handle_rewind), NULL); g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-toggle-mute", diff --git a/definitions.h b/definitions.h index 3eeaf79c..18a74d97 100644 --- a/definitions.h +++ b/definitions.h @@ -5,6 +5,10 @@ #include "config.h" + +// for generating our own UUIDs +#define SHAIRPORT_SYNC_DEVICE_NAMESPACE "01d7c137-6316-455d-a52f-dfb529f26adf" + #if defined(__APPLE__) && defined(__MACH__) /* Apple OSX and iOS (Darwin). ------------------------------ */ #include diff --git a/mdns_avahi.c b/mdns_avahi.c index cf45ba23..fd0deedc 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -32,7 +32,11 @@ #include "common.h" #include "mdns.h" -#include "rtsp.h" + +#ifdef CONFIG_METADATA +#include "metadata/core.h" +#endif + #ifdef CONFIG_DACP_CLIENT #include "dacp.h" #endif diff --git a/metadata/core.c b/metadata/core.c new file mode 100644 index 00000000..d4537cf5 --- /dev/null +++ b/metadata/core.c @@ -0,0 +1,310 @@ +/* + * Core metadata handler. + * This file is part of Shairport Sync. + * Copyright (c) Mike Brady 2017--2026 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include "core.h" +#include "pc_queue.h" + +#include +#include +#include +#include +#include + +#ifdef CONFIG_METADATA_PIPE +#include "pipe.h" +#endif + +#ifdef CONFIG_METADATA_MULTICAST +#include "multicast.h" +#endif + +#ifdef CONFIG_METADATA_HUB +#include "hub.h" +#endif + +#ifdef CONFIG_MQTT +#include "mqtt.h" +#endif + + +// Metadata is not used by shairport-sync. +// Instead we send all metadata to a fifo pipe, so that other apps can listen to +// the pipe and use the metadata. + +// We use two 4-character codes to identify each piece of data and we send the +// data itself, if any, +// in base64 form. + +// The first 4-character code, called the "type", is either: +// 'core' for all the regular metadadata coming from iTunes, etc., or +// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync +// itself, such as +// start/end delimiters, etc. + +// For 'core' metadata, the second 4-character code is the 4-character metadata +// code coming from +// iTunes etc. +// For 'ssnc' metadata, the second 4-character code is used to distinguish the +// messages. + +// Cover art is not tagged in the same way as other metadata, it seems, so is +// sent as an 'ssnc' type +// metadata message with the code 'PICT' +// Here are the 'ssnc' codes defined so far: +// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the +// first few bytes to see +// which. +// 'abeg' -- active mode entered. No arguments +// 'aend' -- active mode exited. No arguments +// 'pbeg' -- play stream begin. No arguments +// 'pend' -- play stream end. No arguments +// 'pfls' -- play stream flush. The argument is an unsigned 32-bit +// frame number. It seems that all frames up to but not +// including this frame are to be flushed. +// +// 'prsm' -- play stream resume. No arguments. (deprecated) +// 'paus' -- buffered audio stream paused. No arguments. +// 'pres' -- buffered audio stream resumed. No arguments. +// 'pffr' -- the first frame of a play session has been received and has been validly +// timed. The argument is +// "/"." +// 'pdis' -- a discontinuity in the timestamps of incoming frames has been detected. +// timed. The argument is "/". +// discontinuity is the actual frame number less the expected frame number +// positive means a gap +// 'pvol' -- play volume. The volume is sent as a string -- +// "airplay_volume,volume,lowest_volume,highest_volume" +// volume, lowest_volume and highest_volume are given in dB. +// The "airplay_volume" is what's sent to the player, and is from +// 0.00 down to -30.00, +// with -144.00 meaning mute. +// This is linear on the volume control slider of iTunes or iOS +// AirPlay. +// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP +// timestamps for the start +// of the current play sequence, the current play point and the end of the +// play sequence. +// I guess the timestamps wrap at 2^32. +// 'mdst' -- a sequence of metadata is about to start; will have, as data, +// the rtptime associated with the metadata, if available +// 'mden' -- a sequence of metadata has ended; will have, as data, the +// rtptime associated with the metadata, if available +// 'pcst' -- a picture is about to be sent; will have, as data, the rtptime +// associated with the picture, if available +// 'pcen' -- a picture has been sent; will have, as data, the rtptime +// associated with the metadata, if available +// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session. +// Specifically, it's the "X-Apple-Client-Name" string +// 'snua' -- A "user agent" -- e.g. "iTunes/12..." -- has opened a play +// session. Specifically, it's the "User-Agent" string +// The next two two tokens are to facilitate remote control of the source. +// There is some information at http://nto.github.io/AirPlay.html about +// remote control of the source. +// +// 'daid' -- this is the source's DACP-ID (if it has one -- it's not +// guaranteed), useful if you want to remotely control the source. Use this +// string to identify the source's remote control on the network. +// 'acre' -- this is the source's Active-Remote token, necessary if you want +// to send commands to the source's remote control (if it has one). +// `clip` -- the payload is the IP number of the client, i.e. the sender of audio. +// Can be an IPv4 or an IPv6 number. In AirPlay 2 operation, it is sent as soon +// as the client has exclusive access to the player and after any existing +// play session has been interrupted and terminated. +// `conn` -- the payload is the IP number of the client, i.e. the sender of audio. +// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message. +// It is sent as soon as the client requests access to the player. +// If Shairport Sync is already playing, this message is sent before the current +// play session is stopped. +// `svip` -- the payload is the IP number of the server, i.e. the player itself. +// Can be an IPv4 or an IPv6 number. +// `svna` -- the payload is the service name of the player, i.e. the name by +// which it is seen in the AirPlay menu. +// `disc` -- the payload is the IP number of the client, i.e. the sender of audio. +// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message. +// It is sent when a client has been disconnected. +// `dapo` -- the payload is the port number (as text) on the server to which remote +// control commands should be sent. It is 3689 for iTunes but varies for iOS devices. +// `` + +int metadata_running = 0; + +void metadata_pack_cleanup_function(void *arg) { + // debug(1, "metadata_pack_cleanup_function called"); + metadata_package *pack = (metadata_package *)arg; + if (pack->carrier) + msg_free(&pack->carrier); // release the message + else if (pack->data) + free(pack->data); + // debug(1, "metadata_pack_cleanup_function exit"); +} + +void metadata_init(void) { + +#ifdef CONFIG_METADATA_PIPE + metadata_pipe_queue_init(); +#endif + +#ifdef CONFIG_METADATA_MULTICAST + metadata_multicast_queue_init(); +#endif + +#ifdef CONFIG_METADATA_HUB + metadata_hub_queue_init(); +#endif + +#ifdef CONFIG_MQTT + metadata_mqtt_queue_init(); +#endif + metadata_running = 1; +} + +void metadata_stop(void) { + if (metadata_running) { + debug(2, "metadata_stop called."); + +#ifdef CONFIG_MQTT + metadata_mqtt_queue_stop(); +#endif + +#ifdef CONFIG_METADATA_HUB + metadata_hub_queue_stop(); +#endif + +#ifdef CONFIG_METADATA_MULTICAST + metadata_multicast_queue_stop(); +#endif + +#ifdef CONFIG_METADATA_PIPE + metadata_pipe_queue_stop(); +#endif + + } +} + +int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block) { + + // clang-format off + // parameters: + // type, + // code, + // pointer to data or NULL, + // length of data or NULL, + // the rtsp_message or NULL, + + // the rtsp_message is sent for 'core' messages, because it contains the data + // and must not be freed until the data has been read. + // So, it is passed to send_metadata to be retained, sent to the thread where metadata + // is processed and released (and probably freed). + + // The rtsp_message is also sent for certain non-'core' messages. + + // The reading of the parameters is a bit complex: + // If the rtsp_message field is non-null, then it represents an rtsp_message + // and the data pointer is assumed to point to something within it. + // The reference counter of the rtsp_message is incremented here and + // should be decremented by the metadata handler when finished. + // If the reference count reduces to zero, the message will be freed. + + // If the rtsp_message is NULL, then if the pointer is non-null then the data it + // points to, of the length specified, is memcpy'd and passed to the metadata + // handler. The handler should free it when done. + + // If the rtsp_message is NULL and the pointer is also NULL, nothing further + // is done. + // clang-format on + + metadata_package pack; + pack.type = type; + pack.code = code; + pack.length = length; + pack.carrier = carrier; + pack.data = (char *)data; + if (pack.carrier) { + msg_retain(pack.carrier); + } else { + if (data) + pack.data = memdup(data, length); // only if it's not a null + } + + // debug(1, "send_metadata_to_queue %x/%x", type, code); + int rc = pc_queue_add_item(queue, &pack, block); + if (rc != 0) { + if (pack.carrier) { + if (rc == EWOULDBLOCK) + debug(2, + "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR ", " + "length %u, message %d.", + queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length, + pack.carrier->index_number); + msg_free(&pack.carrier); + } else { + if (rc == EWOULDBLOCK) + debug( + 2, + "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR ", length %u.", + queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length); + if (pack.data) + free(pack.data); + } + } + return rc; +} + +int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length, + rtsp_message *carrier, int block) { + int rc = 0; + if (config.metadata_enabled) { + +#ifdef CONFIG_METADATA_PIPE + rc = send_metadata_to_pipe_queue(type, code, data, length, carrier, block); +#endif + +#ifdef CONFIG_METADATA_MULTICAST + rc = + send_metadata_to_multicast_queue(type, code, data, length, carrier, block); +#endif + +#ifdef CONFIG_METADATA_HUB + rc = send_metadata_to_hub_queue(type, code, data, length, carrier, block); +#endif + +#ifdef CONFIG_MQTT + rc = send_metadata_to_mqtt_queue(type, code, data, length, carrier, block); +#endif + + } + return rc; +} + +int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length, + const int block) { + return send_metadata('ssnc', code, data, length, NULL, block); +} + diff --git a/metadata/core.h b/metadata/core.h new file mode 100644 index 00000000..85ed7646 --- /dev/null +++ b/metadata/core.h @@ -0,0 +1,39 @@ +#pragma once + +#include "common.h" +#include "config.h" +#include "../rtsp.h" +#include "pc_queue.h" +#include + +typedef struct { + uint32_t type; + uint32_t code; + char *data; + uint32_t length; + rtsp_message *carrier; +} metadata_package; + +void metadata_pack_cleanup_function(void *arg); + +// initialise and completely delete the metadata stuff + +void metadata_init(void); +void metadata_stop(void); + + +int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block); + +// sends metadata out to the metadata system. + +int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length, + rtsp_message *carrier, int block); + +// It is sent with the type 'ssnc' the given code, data and length +// The handler at the other end must know what to do with the data +// e.g. if it's malloced, to free it, etc. +// nothing is done automatically +int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length, + const int block); \ No newline at end of file diff --git a/metadata_hub.c b/metadata/hub.c similarity index 91% rename from metadata_hub.c rename to metadata/hub.c index db3bac70..fe2c2e14 100644 --- a/metadata_hub.c +++ b/metadata/hub.c @@ -5,7 +5,7 @@ * then you need a metadata hub, * where everything is stored * This file is part of Shairport Sync. - * Copyright (c) Mike Brady 2017--2025 + * Copyright (c) Mike Brady 2017--2026 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -44,7 +44,9 @@ #include "common.h" #include "dacp.h" -#include "metadata_hub.h" +#include "hub.h" +#include "pc_queue.h" +#include "core.h" #ifdef CONFIG_MBEDTLS #include @@ -59,6 +61,12 @@ #include #endif +// metadata queue definitions +pc_queue metadata_hub_queue; +#define metadata_hub_queue_size 500 +metadata_package metadata_hub_queue_items[metadata_hub_queue_size]; +pthread_t metadata_hub_thread; + struct metadata_bundle metadata_store; int metadata_hub_initialised = 0; @@ -710,3 +718,56 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin pthread_cleanup_pop(0); // don't remove the lock metadata_hub_modify_epilog(changed); } + +void metadata_hub_close(void) {} + +void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) { + // debug(2, "metadata_hub_thread_cleanup_function called"); + metadata_hub_close(); +} + +void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { + // #include + // debug(1, "metadata_hub_thread_function PID %d", syscall(SYS_gettid)); + metadata_package pack; + pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL); + while (1) { + pc_queue_get_item(&metadata_hub_queue, &pack); + pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); + if (pack.carrier) { + debug(4, " hub: type %x, code %x, length %u, message %d.", pack.type, + pack.code, pack.length, pack.carrier->index_number); + } else { + debug(4, " hub: type %x, code %x, length %u.", pack.type, pack.code, + pack.length); + } + metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length); + debug(4, " hub: done."); + pthread_cleanup_pop(1); + } + pthread_cleanup_pop(1); // will never happen + pthread_exit(NULL); +} + +void metadata_hub_queue_init() { + // create a pc_queue for the metadata hub + pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package), + metadata_hub_queue_size, "hub"); + if (named_pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL, + "metadata hub") != 0) + debug(1, "Failed to create metadata hub thread!"); +} + +void metadata_hub_queue_stop() { + // debug(2, "metadata stop hub thread."); + pthread_cancel(metadata_hub_thread); + pthread_join(metadata_hub_thread, NULL); + pc_queue_delete(&metadata_hub_queue); + // debug(2, "metadata stop hub done."); +} + +int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block) { + return send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block); +} diff --git a/metadata_hub.h b/metadata/hub.h similarity index 94% rename from metadata_hub.h rename to metadata/hub.h index aadd51e4..754f9c4a 100644 --- a/metadata_hub.h +++ b/metadata/hub.h @@ -1,6 +1,7 @@ #pragma once #include "common.h" #include "config.h" +#include "rtsp.h" #include #define number_of_watchers 2 @@ -189,3 +190,11 @@ void _metadata_hub_read_epilog(const char *filename, const int linenumber); #define metadata_hub_read_prolog(void) _metadata_hub_read_prolog(__FILE__, __LINE__) #define metadata_hub_read_epilog(void) _metadata_hub_modify_epilog(__FILE__, __LINE__) + +// metadata queue stuff +void metadata_hub_queue_init(); +void metadata_hub_queue_stop(); +int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block); + diff --git a/metadata/multicast.c b/metadata/multicast.c new file mode 100644 index 00000000..de4930fb --- /dev/null +++ b/metadata/multicast.c @@ -0,0 +1,180 @@ + #include "multicast.h" + #include "core.h" + #include "pc_queue.h" + + #include + #include + + +// A special sub-protocol is used for sending large data items over UDP +// If the payload exceeded 4 MB, it is chunked using the following format: +// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data. +// Notice that the number of items is different to the standard + +#define METADATA_SNDBUF (4 * 1024 * 1024) +static int metadata_sock = -1; +static struct sockaddr_in metadata_sockaddr; +static char *metadata_sockmsg; +pc_queue metadata_multicast_queue; +#define metadata_multicast_queue_size 500 +metadata_package metadata_multicast_queue_items[metadata_multicast_queue_size]; +pthread_t metadata_multicast_thread; + +int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block) { + return send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block); +} + +void metadata_create_multicast_socket(void) { + if (config.metadata_enabled == 0) + return; + + // Unlike metadata pipe, socket is opened once and stays open, + // so we can call it in create + if (config.metadata_sockaddr && config.metadata_sockport) { + metadata_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (metadata_sock < 0) { + debug(1, "Could not open metadata socket"); + } else { + int buffer_size = METADATA_SNDBUF; + setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)); + bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr)); + metadata_sockaddr.sin_family = AF_INET; + metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr); + metadata_sockaddr.sin_port = htons(config.metadata_sockport); + metadata_sockmsg = malloc(config.metadata_sockmsglength); + if (metadata_sockmsg) { + memset(metadata_sockmsg, 0, config.metadata_sockmsglength); + } else { + die("Could not malloc metadata multicast socket buffer"); + } + } + } +} + +void metadata_delete_multicast_socket(void) { + if (config.metadata_enabled == 0) + return; + if (metadata_sock != -1) { + shutdown(metadata_sock, SHUT_RDWR); // we want to immediately deallocate the buffer + close(metadata_sock); + } + if (metadata_sockmsg) + free(metadata_sockmsg); +} + +void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) { + // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code, + // length); + if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) { + char *ptr = metadata_sockmsg; + uint32_t v; + v = htonl(type); + memcpy(ptr, &v, 4); + ptr += 4; + v = htonl(code); + memcpy(ptr, &v, 4); + ptr += 4; + memcpy(ptr, data, length); + sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, + sizeof(metadata_sockaddr)); + } else if (metadata_sock >= 0) { + // send metadata in numbered chunks using the protocol: + // ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data) + + uint32_t chunk_ix = 0; + if (config.metadata_sockmsglength == 24) + die("A divide by zero almost occurred (config.metadata_sockmsglength = 24)."); + uint32_t chunk_total = length / (config.metadata_sockmsglength - 24); + if (chunk_total * (config.metadata_sockmsglength - 24) < length) { + chunk_total++; + } + uint32_t remaining = length; + uint32_t v; + char *data_crsr = data; + do { + char *ptr = metadata_sockmsg; + memcpy(ptr, "ssncchnk", 8); + ptr += 8; + v = htonl(chunk_ix); + memcpy(ptr, &v, 4); + ptr += 4; + v = htonl(chunk_total); + memcpy(ptr, &v, 4); + ptr += 4; + v = htonl(type); + memcpy(ptr, &v, 4); + ptr += 4; + v = htonl(code); + memcpy(ptr, &v, 4); + ptr += 4; + size_t datalen = remaining; + if (datalen > config.metadata_sockmsglength - 24) { + datalen = config.metadata_sockmsglength - 24; + } + memcpy(ptr, data_crsr, datalen); + data_crsr += datalen; + sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0, + (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr)); + chunk_ix++; + remaining -= datalen; + if (remaining == 0) + break; + } while (1); + } +} + +void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) { + // debug(2, "metadata_multicast_thread_cleanup_function called"); + metadata_delete_multicast_socket(); +} + +void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) { + // #include + // debug(1, "metadata_multicast_thread_function PID %d", syscall(SYS_gettid)); + metadata_create_multicast_socket(); + metadata_package pack; + pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL); + while (1) { + pc_queue_get_item(&metadata_multicast_queue, &pack); + pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); + if (config.metadata_enabled) { + if (pack.carrier) { + debug(4, + " multicast: type " + "%x, code %x, length %u, message %d.", + pack.type, pack.code, pack.length, pack.carrier->index_number); + } else { + debug(4, + " multicast: type " + "%x, code %x, length %u.", + pack.type, pack.code, pack.length); + } + metadata_multicast_process(pack.type, pack.code, pack.data, pack.length); + debug(4, + " multicast: done."); + } + pthread_cleanup_pop(1); + } + pthread_cleanup_pop(1); // will never happen + pthread_exit(NULL); +} + +void metadata_multicast_queue_init() { + // create a pc_queue for the metadata_multicast_queue + pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items, + sizeof(metadata_package), metadata_multicast_queue_size, "multicast"); + if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, + NULL, "metadata mcst") != 0) + debug(1, "Failed to create metadata multicast thread!"); +} + +void metadata_multicast_queue_stop() { + if (metadata_multicast_thread) { + pthread_cancel(metadata_multicast_thread); + pthread_join(metadata_multicast_thread, NULL); + pc_queue_delete(&metadata_multicast_queue); + // debug(2, "metadata stop multicast done."); + } +} diff --git a/metadata/multicast.h b/metadata/multicast.h new file mode 100644 index 00000000..0f35b1be --- /dev/null +++ b/metadata/multicast.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "rtsp.h" + +void metadata_multicast_queue_init(); +void metadata_multicast_queue_stop(); +int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block); \ No newline at end of file diff --git a/metadata/pc_queue.c b/metadata/pc_queue.c new file mode 100644 index 00000000..296ccbdc --- /dev/null +++ b/metadata/pc_queue.c @@ -0,0 +1,172 @@ +/* + * Producer--consumer queue and access methods. + * This file is part of Shairport Sync. + * Copyright (c) Mike Brady 2017--2026 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "pc_queue.h" + +#include +#include +#include + +void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items, + const char *name) { + if (name) + debug(4, "Creating metadata queue \"%s\".", name); + else + debug(1, "Creating an unnamed metadata queue."); + pthread_mutex_init(&the_queue->pc_queue_lock, NULL); + pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL); + pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL); + the_queue->item_size = item_size; + the_queue->items = items; + the_queue->count = 0; + the_queue->capacity = number_of_items; + the_queue->toq = 0; + the_queue->eoq = 0; + if (name == NULL) + the_queue->name = NULL; + else + the_queue->name = strdup(name); +} + +void pc_queue_delete(pc_queue *the_queue) { + if (the_queue->name) + debug(2, "Deleting metadata queue \"%s\".", the_queue->name); + else + debug(1, "Deleting an unnamed metadata queue."); + if (the_queue->name != NULL) + free(the_queue->name); + // debug(2, "destroying pc_queue_item_removed_signal"); + pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal); + // debug(2, "destroying pc_queue_item_added_signal"); + pthread_cond_destroy(&the_queue->pc_queue_item_added_signal); + // debug(2, "destroying pc_queue_lock"); + pthread_mutex_destroy(&the_queue->pc_queue_lock); + // debug(2, "destroying signals and locks done"); +} + +void pc_queue_cleanup_handler(void *arg) { + // debug(1, "pc_queue_cleanup_handler called."); + pc_queue *the_queue = (pc_queue *)arg; + int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); + if (rc) + debug(1, "Error unlocking for pc_queue_add_item or pc_queue_get_item."); +} + +int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { + int response = 0; + int rc; + if (the_queue) { + if (block == 0) { + rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 4); + if (rc == EBUSY) + return EBUSY; + } else + rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4); + if (rc) + debug(1, "Error %d (\"%s\") locking for pc_queue_add_item. Block is %d.", rc, strerror(rc), + block); + pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue); + // leave this out if you want this to return if the queue is already full + // irrespective of the block flag. + /* + while (the_queue->count == the_queue->capacity) { + rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, + &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed"); + } + */ + if (the_queue->count < the_queue->capacity) { + uint32_t i = the_queue->eoq; + void *p = the_queue->items + the_queue->item_size * i; + // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; + memcpy(p, the_stuff, the_queue->item_size); + + // update the pointer + i++; + if (i == the_queue->capacity) + // fold pointer if necessary + i = 0; + the_queue->eoq = i; + the_queue->count++; + // debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count, + // the_queue->capacity); + if (the_queue->count == the_queue->capacity) + debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name, + the_queue->count); + rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); + if (rc) + debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item", + the_queue->name); + } else { + response = EWOULDBLOCK; // a bit arbitrary, this. + debug(3, + "metadata queue \"%s\": is already full with %d items in it. Not adding this item to " + "the queue.", + the_queue->name, the_queue->count); + } + pthread_cleanup_pop(1); // unlock the queue lock. + } else { + debug(1, "Adding an item to a NULL queue"); + } + return response; +} + +int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { + int rc; + if (the_queue) { + rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4); + if (rc) + debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name); + pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue); + while (the_queue->count == 0) { + rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); + if (rc) + debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name); + } + uint32_t i = the_queue->toq; + // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq; + void *p = the_queue->items + the_queue->item_size * i; + memcpy(the_stuff, p, the_queue->item_size); + + // update the pointer + i++; + if (i == the_queue->capacity) + // fold pointer if necessary + i = 0; + the_queue->toq = i; + the_queue->count--; + debug(4, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count, + the_queue->capacity); + rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal); + if (rc) + debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name); + pthread_cleanup_pop(1); // unlock the queue lock. + } else { + debug(1, "Removing an item from a NULL queue"); + } + return 0; +} + diff --git a/metadata/pc_queue.h b/metadata/pc_queue.h new file mode 100644 index 00000000..c5afacc8 --- /dev/null +++ b/metadata/pc_queue.h @@ -0,0 +1,26 @@ + +#pragma once + +#include "common.h" +#include "config.h" +#include + +typedef struct { + pthread_mutex_t pc_queue_lock; + pthread_cond_t pc_queue_item_added_signal; + pthread_cond_t pc_queue_item_removed_signal; + char *name; + size_t item_size; // number of bytes in each item + uint32_t count; // number of items in the queue + uint32_t capacity; // maximum number of items + uint32_t toq; // first item to take + uint32_t eoq; // free space at end of queue + void *items; // a pointer to where the items are actually stored +} pc_queue; // producer-consumer queue + +void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items, + const char *name); + +void pc_queue_delete(pc_queue *the_queue); +int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block); +int pc_queue_get_item(pc_queue *the_queue, void *the_stuff); diff --git a/metadata/pipe.c b/metadata/pipe.c new file mode 100644 index 00000000..35128861 --- /dev/null +++ b/metadata/pipe.c @@ -0,0 +1,186 @@ + #include "pipe.h" + #include "core.h" + #include "pc_queue.h" + + #include + #include + #include + #include + +static int fd = -1; // the pipe file descriptor + +pc_queue metadata_queue; +#define metadata_queue_size 500 +metadata_package metadata_queue_items[metadata_queue_size]; +pthread_t metadata_thread; + +void metadata_open(void) { + if (config.metadata_enabled == 0) + return; + + size_t pl = strlen(config.metadata_pipename) + 1; + + char *path = malloc(pl + 1); + snprintf(path, pl + 1, "%s", config.metadata_pipename); + + fd = try_to_open_pipe_for_writing(path); + free(path); +} + +static void metadata_close(void) { + if (fd < 0) + return; + close(fd); + fd = -1; +} + +void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { + // debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length); + int ret = 0; + // readers may go away and come back + + if (fd < 0) + metadata_open(); + if (fd < 0) + return; + char thestring[1024]; + snprintf(thestring, 1024, "%x%x%u", type, code, + length); + // ret = non_blocking_write(fd, thestring, strlen(thestring)); + ret = write(fd, thestring, strlen(thestring)); + if (ret < 0) { + // debug(1,"metadata_process error %d exit 1",ret); + return; + } + if ((data != NULL) && (length > 0)) { + snprintf(thestring, 1024, "\n\n"); + // ret = non_blocking_write(fd, thestring, strlen(thestring)); + ret = write(fd, thestring, strlen(thestring)); + if (ret < 0) { + // debug(1,"metadata_process error %d exit 2",ret); + return; + } + // here, we write the data in base64 form using our nice base64 encoder + // but, we break it into lines of 76 output characters, except for the last + // one. + // thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time + size_t remaining_count = length; + char *remaining_data = data; + // size_t towrite_count; + char outbuf[76]; + while ((remaining_count) && (ret >= 0)) { + size_t towrite_count = remaining_count; + if (towrite_count > 57) + towrite_count = 57; + size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit + if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) == + NULL) + debug(1, "Error encoding base64 data."); + // debug(1,"Remaining count: %d ret: %d, outbuf_size: + // %d.",remaining_count,ret,outbuf_size); + // ret = non_blocking_write(fd, outbuf, outbuf_size); + ret = write(fd, outbuf, outbuf_size); + if (ret < 0) { + // debug(1,"metadata_process error %d exit 3",ret); + return; + } + remaining_data += towrite_count; + remaining_count -= towrite_count; + } + snprintf(thestring, 1024, ""); + // ret = non_blocking_write(fd, thestring, strlen(thestring)); + ret = write(fd, thestring, strlen(thestring)); + if (ret < 0) { + // debug(1,"metadata_process error %d exit 4",ret); + return; + } + } + snprintf(thestring, 1024, "\n"); + // ret = non_blocking_write(fd, thestring, strlen(thestring)); + ret = write(fd, thestring, strlen(thestring)); + if (ret < 0) { + // debug(1,"metadata_process error %d exit 5",ret); + return; + } +} + +void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) { + // debug(2, "metadata_thread_cleanup_function called"); + metadata_close(); +} + +void *metadata_thread_function(__attribute__((unused)) void *ignore) { + // #include + // debug(1, "metadata_thread_function PID %d", syscall(SYS_gettid)); + metadata_package pack; + pthread_cleanup_push(metadata_thread_cleanup_function, NULL); + while (1) { + pc_queue_get_item(&metadata_queue, &pack); + pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); + if (config.metadata_enabled) { + if (pack.carrier) { + debug(4, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code, + pack.length, pack.carrier->index_number); + } else { + debug(4, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length); + } + metadata_process(pack.type, pack.code, pack.data, pack.length); + debug(4, " pipe: done."); + } + pthread_cleanup_pop(1); + } + pthread_cleanup_pop(1); // will never happen + pthread_exit(NULL); +} + +void metadata_pipe_queue_init() { + // create the metadata pipe, if necessary + size_t pl = strlen(config.metadata_pipename) + 1; + char *path = malloc(pl + 1); + snprintf(path, pl + 1, "%s", config.metadata_pipename); + mode_t oldumask = umask(000); + if (mkfifo(path, 0666) && errno != EEXIST) + die("Could not create metadata pipe \"%s\".", path); + umask(oldumask); + debug(1, "metadata pipe name is \"%s\".", path); + + // try to open it + fd = try_to_open_pipe_for_writing(path); + // we check that it's not a "real" error. From the "man 2 open" page: + // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO + // open for reading." Which is okay. + if ((fd == -1) && (errno != ENXIO)) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, path); + warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, path); + } + free(path); + + // initialise the metadata queues first, otherwise the might be a race condition + // create a pc_queue for the metadata pipe + pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), + metadata_queue_size, "pipe"); + + if (named_pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL, + "metadata pipe") != 0) + debug(1, "Failed to create metadata thread!"); +} + +void metadata_pipe_queue_stop() { + if (metadata_thread) { + // debug(2, "metadata stop metadata_thread thread."); + pthread_cancel(metadata_thread); + pthread_join(metadata_thread, NULL); + pc_queue_delete(&metadata_queue); + // debug(2, "metadata_stop finished successfully."); + } +} + +int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block) { + return send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block); +} diff --git a/metadata/pipe.h b/metadata/pipe.h new file mode 100644 index 00000000..2a242983 --- /dev/null +++ b/metadata/pipe.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "rtsp.h" + +void metadata_pipe_queue_init(); +void metadata_pipe_queue_stop(); +int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block); \ No newline at end of file diff --git a/mpris-service.c b/mpris-service.c index b7877a98..06d506c2 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -35,9 +35,11 @@ #include "rtp.h" +#ifdef CONFIG_DACP_CLIENT #include "dacp.h" +#endif -#include "metadata_hub.h" +#include "metadata/hub.h" #include "mpris-service.h" static guint ownerID = 0; @@ -240,28 +242,36 @@ static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *in static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("nextitem"); +#endif media_player2_player_complete_next(skeleton, invocation); return TRUE; } static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("previtem"); +#endif media_player2_player_complete_previous(skeleton, invocation); return TRUE; } static gboolean on_handle_stop(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("stop"); +#endif media_player2_player_complete_stop(skeleton, invocation); return TRUE; } static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("pause"); +#endif media_player2_player_complete_pause(skeleton, invocation); return TRUE; } @@ -269,14 +279,18 @@ static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocat static gboolean on_handle_play_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("playpause"); +#endif media_player2_player_complete_play_pause(skeleton, invocation); return TRUE; } static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command("play"); +#endif media_player2_player_complete_play(skeleton, invocation); return TRUE; } @@ -284,11 +298,13 @@ static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocati static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation, const gdouble volume, __attribute__((unused)) gpointer user_data) { +#ifdef CONFIG_DACP_CLIENT double ap_volume = mpris_volume_to_airplay_volume(volume); debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume); char command[256] = ""; snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", ap_volume); send_simple_dacp_command(command); +#endif media_player2_player_complete_play(skeleton, invocation); return TRUE; } diff --git a/mqtt.c b/mqtt.c index 64cc1aa9..9035986b 100644 --- a/mqtt.c +++ b/mqtt.c @@ -11,10 +11,21 @@ #include "rtp.h" +#ifdef CONFIG_DACP_CLIENT #include "dacp.h" -#include "metadata_hub.h" +#endif +#include "metadata/hub.h" +#include "metadata/core.h" +#include "metadata/pc_queue.h" + #include "mqtt.h" #include +// this is for receiving metadata + +pc_queue metadata_mqtt_queue; +#define metadata_mqtt_queue_size 500 +metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size]; +pthread_t metadata_mqtt_thread; // this holds the mosquitto client struct mosquitto *global_mosq = NULL; @@ -71,7 +82,9 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq, release_play_lock(NULL); // stop any current session and don't replace it } else { debug(2, "[MQTT]: DACP Command: %s\n", commands[it]); +#ifdef CONFIG_DACP_CLIENT send_simple_dacp_command(commands[it]); +#endif } break; } @@ -443,3 +456,62 @@ int initialise_mqtt() { return 0; } + +// metadata handling stuff +void metadata_mqtt_close(void) {} + +void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) { + // debug(2, "metadata_mqtt_thread_cleanup_function called"); + metadata_mqtt_close(); + // debug(2, "metadata_mqtt_thread_cleanup_function done"); +} + +void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) { + // #include + // debug(1, "metadata_mqtt_thread_function PID %d", syscall(SYS_gettid)); + metadata_package pack; + pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL); + while (1) { + pc_queue_get_item(&metadata_mqtt_queue, &pack); + pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); + if (config.mqtt_enabled) { + if (pack.carrier) { + debug(3, + " mqtt: type %x, code %x, length %u, message " + "%d.", + pack.type, pack.code, pack.length, pack.carrier->index_number); + } else { + debug(3, " mqtt: type %x, code %x, length %u.", + pack.type, pack.code, pack.length); + } + mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length); + debug(3, " mqtt: done."); + } + + pthread_cleanup_pop(1); + } + pthread_cleanup_pop(1); // will never happen + pthread_exit(NULL); +} + +void metadata_mqtt_queue_init() { + // create a pc_queue for the MQTT handler + pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package), + metadata_mqtt_queue_size, "mqtt"); + if (named_pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL, + "metadata mqtt") != 0) + debug(1, "Failed to create metadata mqtt thread!"); +} +void metadata_mqtt_queue_stop() { + // debug(2, "metadata stop mqtt thread."); + pthread_cancel(metadata_mqtt_thread); + pthread_join(metadata_mqtt_thread, NULL); + pc_queue_delete(&metadata_mqtt_queue); + // debug(2, "metadata stop mqtt done."); +} +int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block) { + return send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block); +} + diff --git a/mqtt.h b/mqtt.h index 69ad75bd..f450b416 100644 --- a/mqtt.h +++ b/mqtt.h @@ -12,4 +12,10 @@ void on_connect(struct mosquitto *mosq, void *userdata, int rc); void on_disconnect(struct mosquitto *mosq, void *userdata, int rc); void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg); void _cb_log(struct mosquitto *mosq, void *userdata, int level, const char *str); + +void metadata_mqtt_queue_init(); +void metadata_mqtt_queue_stop(); +int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code, + const char *data, const uint32_t length, rtsp_message *carrier, + int block); #endif /* #ifndef MQTT_H */ diff --git a/org.gnome.ShairportSync.xml b/org.gnome.ShairportSync.xml index dd1673ac..d24787b2 100644 --- a/org.gnome.ShairportSync.xml +++ b/org.gnome.ShairportSync.xml @@ -40,7 +40,7 @@ - + diff --git a/pair_ap/pair.c b/pair_ap/pair.c index 966f45bc..80a48950 100644 --- a/pair_ap/pair.c +++ b/pair_ap/pair.c @@ -95,7 +95,7 @@ hash_init(enum hash_alg alg, HashCTX *c) } int -hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len) +hash_update(__attribute__((unused)) enum hash_alg alg, HashCTX *c, const void *data, size_t len) { #if CONFIG_OPENSSL switch (alg) diff --git a/pair_ap/pair_fruit.c b/pair_ap/pair_fruit.c index 5b81a56a..8faf04fa 100644 --- a/pair_ap/pair_fruit.c +++ b/pair_ap/pair_fruit.c @@ -593,7 +593,7 @@ encrypt_ctr(unsigned char *ciphertext, int ciphertext_len, /* -------------------------- IMPLEMENTATION -------------------------------- */ static int -client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id) +client_setup_new(struct pair_setup_context *handle, const char *pin, __attribute__((unused)) pair_cb add_cb, __attribute__((unused)) void *cb_arg, __attribute__((unused)) const char *device_id) { struct pair_client_setup_context *sctx = &handle->sctx.client; @@ -853,7 +853,7 @@ client_setup_result(struct pair_setup_context *handle) { struct pair_client_setup_context *sctx = &handle->sctx.client; char *ptr; - int i; + unsigned int i; // Last 32 bytes of the private key is the public key, so we don't need to // explicitly export that @@ -867,13 +867,13 @@ client_setup_result(struct pair_setup_context *handle) static int -client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id) +client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, __attribute__((unused)) pair_cb cb, __attribute__((unused)) void *cb_arg, const char *device_id) { struct pair_client_verify_context *vctx = &handle->vctx.client; char hex[] = { 0, 0, 0 }; size_t hexkey_len; const char *ptr; - int i; + unsigned int i; if (!is_initialized()) return -1; @@ -1024,7 +1024,7 @@ client_verify_response1(struct pair_verify_context *handle, const uint8_t *data, } static int -client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) +client_verify_response2(struct pair_verify_context *handle, __attribute__((unused)) const uint8_t *data, __attribute__((unused)) size_t data_len) { struct pair_client_verify_context *vctx = &handle->vctx.client; // TODO actually check response diff --git a/pair_ap/pair_homekit.c b/pair_ap/pair_homekit.c index baf94e43..525ee695 100644 --- a/pair_ap/pair_homekit.c +++ b/pair_ap/pair_homekit.c @@ -822,7 +822,7 @@ static void hexread(uint8_t *out, size_t out_len, const char *in) { char hex[] = { 0, 0, 0 }; - int i; + unsigned int i; for (i = 0; i < out_len; i++, in+=2) { @@ -1668,7 +1668,7 @@ static int client_setup_result(struct pair_setup_context *handle) { char *ptr; - int i; + unsigned int i; assert(sizeof(handle->result_str) >= 2 * sizeof(handle->result.client_private_key) + 2 * sizeof(handle->result.server_public_key) + 1); @@ -1686,7 +1686,7 @@ client_setup_result(struct pair_setup_context *handle) } static int -client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id) +client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, __attribute__((unused)) pair_cb cb, __attribute__((unused)) void *cb_arg, __attribute__((unused)) const char *device_id) { struct pair_client_verify_context *vctx = &handle->vctx.client; size_t hexkey_len; @@ -2888,7 +2888,7 @@ server_list_response(size_t *len, pair_list_cb cb, void *cb_arg) } static int -server_list(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len) +server_list(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, __attribute__((unused)) const uint8_t *in, __attribute__((unused)) size_t in_len) { // Skip reading the request, it just has state = 1 and pair method = // PairingMethodListPairings diff --git a/player.c b/player.c index e15de80e..2d8b9081 100644 --- a/player.c +++ b/player.c @@ -72,8 +72,12 @@ #include #endif +#ifdef CONFIG_METADATA +#include "metadata/core.h" +#endif + #ifdef CONFIG_METADATA_HUB -#include "metadata_hub.h" +#include "metadata/hub.h" #endif #ifdef CONFIG_DACP_CLIENT @@ -383,18 +387,6 @@ const char *get_category_string(airplay_stream_c cat) { #ifdef CONFIG_FFMPEG -static void avcodec_alloc_context3_cleanup_handler(void *arg) { - debug(3, "avcodec_alloc_context3_cleanup_handler"); - AVCodecContext *codec_context = arg; - av_free(codec_context); -} - -static void avcodec_open2_cleanup_handler(__attribute__((unused)) void *arg) { - debug(3, "avcodec_open2_cleanup_handler"); - // AVCodecContext *codec_context = arg; - // avcodec_close(codec_context); -} - static void swr_alloc_cleanup_handler(void *arg) { debug(3, "swr_alloc_cleanup_handler"); SwrContext **swr = arg; @@ -407,25 +399,9 @@ static void av_packet_alloc_cleanup_handler(void *arg) { av_packet_free(pkt); } -/* -static void av_frame_alloc_cleanup_handler(void *arg) { - debug(3, "av_frame_alloc_cleanup_handler"); - AVFrame **frame = arg; - av_frame_free(frame); -} -*/ - void clear_decoding_chain(rtsp_conn_info *conn) { if (conn->incoming_ssrc != 0) { - // debug_mutex_lock(&conn->ab_mutex, 30000, 0); - // ab_resync(conn); - // debug_mutex_unlock(&conn->ab_mutex, 0); - pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, conn->codec_context); - pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata); - pthread_cleanup_push(avcodec_open2_cleanup_handler, conn->codec_context); - pthread_cleanup_pop(1); // avcodec_open2_cleanup_handler - pthread_cleanup_pop(1); // deallocate the malloc - pthread_cleanup_pop(1); // avcodec_alloc_context3_cleanup_handler + avcodec_free_context(&conn->codec_context); conn->incoming_ssrc = SSRC_NONE; } } @@ -776,6 +752,9 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) { // get information about the output from the resampler int64_t resampler_output_format = 0; int resampler_channels_found = 0; + + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); #if LIBAVUTIL_VERSION_MAJOR >= 57 @@ -886,6 +865,8 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) { free(device_channels); } } + + pthread_setcancelstate(oldState, NULL); if (output_channel_map_faulty != 0) { once(inform("The output device's %u-channel map is incomplete or faulty: \"%s\".", @@ -1064,70 +1045,70 @@ void prepare_decoding_chain(rtsp_conn_info *conn, ssrc_t ssrc) { conn->codec = avcodec_find_decoder(AV_CODEC_ID_AAC); break; default: - die("Can't find a suitable codec for SSRC: %s", get_ssrc_name(ssrc)); + die("Connection %d: can't find a suitable codec for SSRC: %s", conn->connection_number, get_ssrc_name(ssrc)); break; } + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); + // Get a decoder-dependent codec context conn->codec_context = avcodec_alloc_context3(conn->codec); - if (conn->codec_context == NULL) { - debug(1, "Could not allocate a codec context!"); - } - // push a deallocator -- av_free(codec_context) - pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, conn->codec_context); - - // prepare to open the codec context with that codec - // but first, if it's the ALAC decoder, prepare a magic cookie - if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) { - alac_ffmpeg_magic_cookie *extradata = - malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it - if (extradata == NULL) - die("Could not allocate memory for a magic cookie."); - // creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?) - memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie)); - extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie)); - extradata->cookie_tag = htonl('alac'); - extradata->alac_config.frameLength = htonl(352); - if (ssrc == ALAC_48000_S24_2) { - extradata->alac_config.bitDepth = 24; // Seems to be S24 - extradata->alac_config.sampleRate = htonl(48000); + if (conn->codec_context != NULL) { + // push a deallocator -- av_free(codec_context) + // pthread_cleanup_push(avcodec_alloc_context3_cleanup_handler, &conn->codec_context); + + // prepare to open the codec context with that codec + // but first, if it's the ALAC decoder, prepare a magic cookie + if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) { + alac_ffmpeg_magic_cookie *extradata = + malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it + if (extradata == NULL) + die("connection %d: could not allocate memory for a magic cookie.", conn->connection_number); + // creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?) + memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie)); + extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie)); + extradata->cookie_tag = htonl('alac'); + extradata->alac_config.frameLength = htonl(352); + if (ssrc == ALAC_48000_S24_2) { + extradata->alac_config.bitDepth = 24; // Seems to be S24 + extradata->alac_config.sampleRate = htonl(48000); + } else { + extradata->alac_config.bitDepth = 16; // Seems to be S16 + extradata->alac_config.sampleRate = htonl(44100); + } + extradata->alac_config.pb = 40; + extradata->alac_config.mb = 10; + extradata->alac_config.kb = 14; + extradata->alac_config.numChannels = 2; + extradata->alac_config.maxRun = htons(255); + conn->codec_context->extradata = (uint8_t *)extradata; + conn->codec_context->extradata_size = sizeof(alac_ffmpeg_magic_cookie); } else { - extradata->alac_config.bitDepth = 16; // Seems to be S16 - extradata->alac_config.sampleRate = htonl(44100); + conn->codec_context->extradata = NULL; + } + // pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata); + // avcodec_free_context() will free extradata + + if (avcodec_open2(conn->codec_context, conn->codec, NULL) < 0) { + pthread_setcancelstate(oldState, NULL); + die("connection %d: could not initialise the codec context", conn->connection_number); } - extradata->alac_config.pb = 40; - extradata->alac_config.mb = 10; - extradata->alac_config.kb = 14; - extradata->alac_config.numChannels = 2; - extradata->alac_config.maxRun = htons(255); - conn->codec_context->extradata = (uint8_t *)extradata; - conn->codec_context->extradata_size = sizeof(alac_ffmpeg_magic_cookie); } else { - conn->codec_context->extradata = NULL; - } - pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata); - - if (avcodec_open2(conn->codec_context, conn->codec, NULL) < 0) { - die("Could not initialise the codec context"); - } - - // push a closer -- avcodec_close(codec_context); - pthread_cleanup_push(avcodec_open2_cleanup_handler, conn->codec_context); + die("connection %d: could not allocate a codec context!", conn->connection_number); + } + pthread_setcancelstate(oldState, NULL); conn->input_rate = get_ssrc_rate(ssrc); + debug(2, "Connection %d: set conn->input_rate: %u.", conn->connection_number, conn->input_rate); if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) { conn->frames_per_packet = 352; } else { conn->frames_per_packet = 1024; } - conn->codec_context->sample_rate = conn->input_rate; - conn->ffmpeg_decoding_chain_initialised = 1; - pthread_cleanup_pop(0); // successful exit -- don't run the avcodec_open2_cleanup_handler - pthread_cleanup_pop(0); // successful exit -- don't deallocate the malloc - pthread_cleanup_pop( - 0); // successful exit -- don't run the avcodec_alloc_context3_cleanup_handler + conn->input_format_is_valid = 1; } } } @@ -1142,6 +1123,9 @@ int64_t avframe_to_audio(rtsp_conn_info *conn, AVFrame *decoded_frame, uint8_t * uint8_t *pcm_audio = NULL; int dst_linesize; + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + int number_of_output_samples_expected = swr_get_out_samples(conn->swr, decoded_frame->nb_samples); debug(4, "A maximum of %d output samples expected for %d input samples.", @@ -1243,10 +1227,12 @@ int64_t avframe_to_audio(rtsp_conn_info *conn, AVFrame *decoded_frame, uint8_t * *decoded_audio_samples_count = 0; } av_freep(&pcm_audio); - return swr_get_delay( + int64_t response = swr_get_delay( conn->swr, RATE_FROM_ENCODED_FORMAT( config.current_output_configuration)); // number of frames left in the resampler + pthread_setcancelstate(oldState, NULL); + return response; } // take a block of incoming data and decode it. @@ -1254,8 +1240,11 @@ int64_t avframe_to_audio(rtsp_conn_info *conn, AVFrame *decoded_frame, uint8_t * // transcoded and maybe resampled later AVFrame *block_to_avframe(rtsp_conn_info *conn, uint8_t *incoming_data, size_t incoming_data_length) { + AVFrame *decoded_frame = NULL; if (incoming_data_length > 8) { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable AVPacket *pkt = av_packet_alloc(); if (pkt) { // push a deallocator -- av_packet_free(pkt); @@ -1314,6 +1303,7 @@ AVFrame *block_to_avframe(rtsp_conn_info *conn, uint8_t *incoming_data, } else { debug(1, "Can't allocate an AVPacket!"); } + pthread_setcancelstate(oldState, NULL); } return decoded_frame; } @@ -2032,7 +2022,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) { do { // debug(3, "buffer_get_frame is iterating"); // we must have timing information before we can do anything here - if (have_timestamp_timing_information(conn)) { + if ((have_timestamp_timing_information(conn)) && (conn->input_format_is_valid != 0)) { int rco = get_requested_connection_state_to_output(); @@ -2481,10 +2471,12 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) { RATE_FROM_ENCODED_FORMAT(config.current_output_configuration)) / 1000000000; int64_t exact_frame_gap = gross_frame_gap - dac_delay; - debug(3, + + + debug(4, "Exact frame gap: %" PRId64 - ". DAC delay: %ld. First packet timestamp: %u.", - exact_frame_gap, dac_delay, conn->first_packet_timestamp); + ". DAC delay: %ld. Total: %" PRId64 ". First packet timestamp: %u. ", + exact_frame_gap, dac_delay, gross_frame_gap, conn->first_packet_timestamp); // int64_t frames_needed_to_maintain_desired_buffer = // (int64_t)(config.audio_backend_buffer_desired_length * // config.current_output_configuration->rate) - @@ -2687,7 +2679,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) { // wait if the buffer is empty if ((conn->ab_synced != 0) && (conn->ab_read == conn->ab_write)) { // the buffer is empty! if (notified_buffer_empty == 0) { - debug(2, "Connection %d: Buffer Empty", conn->connection_number); + debug(4, "Connection %d: Buffer Empty", conn->connection_number); notified_buffer_empty = 1; // reset_input_flow_metrics(conn); // don't do a full flush parameters reset // conn->initial_reference_time = 0; @@ -2701,14 +2693,10 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) { wait = 1; // keep waiting until the timing information becomes available } if (wait) { - if (conn->frames_per_packet == 0) - debug(1, "frames_per_packet is zero!"); - - uint64_t time_to_wait_for_wakeup_ns = 20000000; // default if no input rate is set - if (conn->input_rate != 0) { - time_to_wait_for_wakeup_ns = - 1000000000 / conn->input_rate; // this is time period of one frame - time_to_wait_for_wakeup_ns *= 4 * conn->frames_per_packet; + uint64_t time_to_wait_for_wakeup_ns = 10000000; // default + if (conn->input_format_is_valid != 0) { + time_to_wait_for_wakeup_ns = 1000000000 / conn->input_rate; // this is time period of one frame + time_to_wait_for_wakeup_ns *= 4 * conn->frames_per_packet; // about 4 * 7 mS for 352 frames per second } #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD @@ -2719,12 +2707,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) { struct timespec time_of_wakeup; time_of_wakeup.tv_sec = sec; time_of_wakeup.tv_nsec = nsec; - + // debug(1, "wait for up to %f mS or for the next packet...", time_to_wait_for_wakeup_ns * 1E-6); int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex, &time_of_wakeup); // this is a pthread cancellation point if ((rc != 0) && (rc != ETIMEDOUT)) // if (rc) debug(3, "pthread_cond_timedwait returned error code %d.", rc); + // debug(1, "waited"); #endif #ifdef COMPILE_FOR_OSX uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000; @@ -2804,12 +2793,15 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) { debug(1, "error %d", ret); // We need to get those frames of silence out of the resampler // so we'll pass in an empty AVFrame to flush them through - AVFrame *avf = av_frame_alloc(); // empty frame + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); + AVFrame *avf = av_frame_alloc(); conn->frames_retained_in_the_resampler = avframe_to_audio(conn, avf, &pp, &pl, &number_of_output_frames); + av_frame_free(&avf); + pthread_setcancelstate(oldState, NULL); curframe->data = (short *)pp; curframe->length = number_of_output_frames; - av_frame_free(&avf); } } #ifndef CONFIG_AIRPLAY_2 @@ -5262,7 +5254,7 @@ int player_stop(rtsp_conn_info *conn) { // note -- this may be called from another connection thread. debug(2, "Connection %d: player_stop.", conn->connection_number); int response = 0; // okay - pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 1); + pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 4); pthread_t *pt = conn->player_thread; if (pt) { debug(3, "player_thread cancel..."); diff --git a/player.h b/player.h index 2f993c24..e2626a06 100644 --- a/player.h +++ b/player.h @@ -257,6 +257,7 @@ typedef struct { typedef struct { int connection_number; // for debug ID purposes, nothing else... int is_playing; // set true by player_play, set false by player_stop + int input_format_is_valid; // set when the input format is known and set in this structure unsigned int sync_samples_index; // for estimating the gap between the highest and lowest timing // error over the past n samples unsigned int sync_samples_count; // the array of samples is defined locally @@ -272,7 +273,7 @@ typedef struct { // otherwise int software_mute_enabled; // if we don't have a real mute that we can use int fd; - int authorized; // set if a password is required and has been supplied + int authorized; // set if a password is required and has been supplied or not required. Also always set in AirPlay 2 mode. char *auth_nonce; // the session nonce, if needed stream_cfg stream; SOCKADDR remote, local; @@ -412,6 +413,7 @@ typedef struct { // (will be unspecified if not build for AirPlay 2) #ifdef CONFIG_AIRPLAY_2 + plist_t sessionPlist; char *airplay_gid; // UUID in the Bonjour advertisement -- if NULL, the group UUID is the same as // the pi UUID airplay_t airplay_type; // are we using AirPlay 1 or AirPlay 2 protocol on this connection? @@ -423,6 +425,8 @@ typedef struct { pthread_t rtp_ap2_control_thread; pthread_t rtp_realtime_audio_thread; pthread_t rtp_buffered_audio_thread; + pthread_mutex_t event_sender_mutex; + int event_channel_fd; // zero if closed, controlled by event_sender_mutex int ap2_event_receiver_exited; diff --git a/rtp.c b/rtp.c index 79032ede..2c8f9915 100644 --- a/rtp.c +++ b/rtp.c @@ -1513,9 +1513,12 @@ int frame_to_ptp_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info * int32_t frame_difference = timestamp - anchor_rtptime; int64_t time_difference = frame_difference; time_difference = time_difference * 1000000000; - if (conn->input_rate == 0) - die("conn->input_rate is zero!"); - time_difference = time_difference / conn->input_rate; + if (conn->input_rate == 0) { + debug(1,"Connection %d: in a call to frame_to_ptp_local_time, conn->input_rate is zero!", conn->connection_number); + time_difference = 0; + } else { + time_difference = time_difference / conn->input_rate; + } uint64_t ltime = anchor_local_time + time_difference; *time = ltime; result = 0; diff --git a/rtsp.c b/rtsp.c index 7da2ffaf..0fbee800 100644 --- a/rtsp.c +++ b/rtsp.c @@ -3,7 +3,7 @@ * Copyright (c) James Laird 2013 * Modifications, including those associated with audio synchronization, multithreading and - * metadata handling copyright (c) Mike Brady 2014--2025 + * metadata handling copyright (c) Mike Brady 2014--2026 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -53,6 +53,7 @@ #include "activity_monitor.h" #include "config.h" +#include "utilities/rtsp_message_utilities.h" #ifdef CONFIG_OPENSSL #include @@ -74,12 +75,13 @@ #include "rtp.h" #include "rtsp.h" -#ifdef CONFIG_METADATA_HUB -#include "metadata_hub.h" +#ifdef CONFIG_METADATA +#include "metadata/pc_queue.h" +#include "metadata/core.h" #endif -#ifdef CONFIG_MQTT -#include "mqtt.h" +#ifdef CONFIG_METADATA_HUB +#include "metadata/hub.h" #endif #ifdef AF_INET6 @@ -89,11 +91,10 @@ #endif #ifdef CONFIG_AIRPLAY_2 +#include #include "ap2_buffered_audio_processor.h" #include "ap2_event_receiver.h" -#include "ap2_rc_event_receiver.h" #include "pair_ap/pair.h" -#include "plist/plist.h" #include "plists/get_info_response.h" #include "ptp-utilities.h" @@ -114,8 +115,6 @@ #include "mdns.h" #include "utilities/network_utilities.h" -#define METADATA_SNDBUF (4 * 1024 * 1024) - enum rtsp_read_request_response { rtsp_read_request_response_ok, rtsp_read_request_response_pending, @@ -129,8 +128,6 @@ enum rtsp_read_request_response { rtsp_conn_info *principal_conn; rtsp_conn_info **conns; -int metadata_running = 0; - // always lock this when accessing the principal conn value // use a read lock when consulting and holding it // use a write lock if you want to change it @@ -139,10 +136,6 @@ pthread_rwlock_t principal_conn_lock = PTHREAD_RWLOCK_INITIALIZER; // always lock this when accessing the list of connection threads pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER; -// every time we want to retain or release a reference count, lock it with this -// if a reference count is read as zero, it means the it's being deallocated. -static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; - // only one thread is allowed to use the player at once. // it monitors the request variable (at least when interrupted) // static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -151,236 +144,6 @@ static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; int RTSP_connection_index = 1; -#ifdef CONFIG_METADATA -typedef struct { - pthread_mutex_t pc_queue_lock; - pthread_cond_t pc_queue_item_added_signal; - pthread_cond_t pc_queue_item_removed_signal; - char *name; - size_t item_size; // number of bytes in each item - uint32_t count; // number of items in the queue - uint32_t capacity; // maximum number of items - uint32_t toq; // first item to take - uint32_t eoq; // free space at end of queue - void *items; // a pointer to where the items are actually stored -} pc_queue; // producer-consumer queue -#endif - -static int msg_indexes = 1; - -typedef struct { - int index_number; - uint32_t referenceCount; // we might start using this... - unsigned int nheaders; - char *name[16]; - char *value[16]; - - uint32_t contentlength; - char *content; - - // for requests - char method[16]; - char path[256]; - - // for responses - int respcode; -} rtsp_message; - -#ifdef CONFIG_AIRPLAY_2 - -int add_pstring_to_malloc(const char *s, void **allocation, size_t *size) { - int response = 0; - void *p = *allocation; - if (p == NULL) { - p = malloc(strlen(s) + 1); - if (p == NULL) { - debug(1, "error allocating memory"); - } else { - *allocation = p; - *size = *size + strlen(s) + 1; - uint8_t *b = (uint8_t *)p; - *b = strlen(s); - p = p + 1; - memcpy(p, s, strlen(s)); - response = 1; - } - } else { - p = realloc(p, *size + strlen(s) + 1); - if (p == NULL) { // assuming we never allocate a zero byte space - debug(1, "error reallocating memory"); - } else { - *allocation = p; - uint8_t *b = (uint8_t *)p + *size; - *b = strlen(s); - p = p + *size + 1; - memcpy(p, s, strlen(s)); - *size = *size + strlen(s) + 1; - response = 1; - } - } - return response; -} - -#endif - -#ifdef CONFIG_METADATA -typedef struct { - uint32_t type; - uint32_t code; - char *data; - uint32_t length; - rtsp_message *carrier; -} metadata_package; - -void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items, - const char *name) { - if (name) - debug(4, "Creating metadata queue \"%s\".", name); - else - debug(1, "Creating an unnamed metadata queue."); - pthread_mutex_init(&the_queue->pc_queue_lock, NULL); - pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL); - pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL); - the_queue->item_size = item_size; - the_queue->items = items; - the_queue->count = 0; - the_queue->capacity = number_of_items; - the_queue->toq = 0; - the_queue->eoq = 0; - if (name == NULL) - the_queue->name = NULL; - else - the_queue->name = strdup(name); -} - -void pc_queue_delete(pc_queue *the_queue) { - if (the_queue->name) - debug(2, "Deleting metadata queue \"%s\".", the_queue->name); - else - debug(1, "Deleting an unnamed metadata queue."); - if (the_queue->name != NULL) - free(the_queue->name); - // debug(2, "destroying pc_queue_item_removed_signal"); - pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal); - // debug(2, "destroying pc_queue_item_added_signal"); - pthread_cond_destroy(&the_queue->pc_queue_item_added_signal); - // debug(2, "destroying pc_queue_lock"); - pthread_mutex_destroy(&the_queue->pc_queue_lock); - // debug(2, "destroying signals and locks done"); -} - -int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length, - rtsp_message *carrier, int block); - -int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length, - const int block) { - return send_metadata('ssnc', code, data, length, NULL, block); -} - -void pc_queue_cleanup_handler(void *arg) { - // debug(1, "pc_queue_cleanup_handler called."); - pc_queue *the_queue = (pc_queue *)arg; - int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock); - if (rc) - debug(1, "Error unlocking for pc_queue_add_item or pc_queue_get_item."); -} - -int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { - int response = 0; - int rc; - if (the_queue) { - if (block == 0) { - rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 4); - if (rc == EBUSY) - return EBUSY; - } else - rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4); - if (rc) - debug(1, "Error %d (\"%s\") locking for pc_queue_add_item. Block is %d.", rc, strerror(rc), - block); - pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue); - // leave this out if you want this to return if the queue is already full - // irrespective of the block flag. - /* - while (the_queue->count == the_queue->capacity) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, - &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed"); - } - */ - if (the_queue->count < the_queue->capacity) { - uint32_t i = the_queue->eoq; - void *p = the_queue->items + the_queue->item_size * i; - // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; - memcpy(p, the_stuff, the_queue->item_size); - - // update the pointer - i++; - if (i == the_queue->capacity) - // fold pointer if necessary - i = 0; - the_queue->eoq = i; - the_queue->count++; - // debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count, - // the_queue->capacity); - if (the_queue->count == the_queue->capacity) - debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name, - the_queue->count); - rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); - if (rc) - debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item", - the_queue->name); - } else { - response = EWOULDBLOCK; // a bit arbitrary, this. - debug(3, - "metadata queue \"%s\": is already full with %d items in it. Not adding this item to " - "the queue.", - the_queue->name, the_queue->count); - } - pthread_cleanup_pop(1); // unlock the queue lock. - } else { - debug(1, "Adding an item to a NULL queue"); - } - return response; -} - -int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { - int rc; - if (the_queue) { - rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4); - if (rc) - debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name); - pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue); - while (the_queue->count == 0) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock); - if (rc) - debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name); - } - uint32_t i = the_queue->toq; - // void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq; - void *p = the_queue->items + the_queue->item_size * i; - memcpy(the_stuff, p, the_queue->item_size); - - // update the pointer - i++; - if (i == the_queue->capacity) - // fold pointer if necessary - i = 0; - the_queue->toq = i; - the_queue->count--; - debug(4, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count, - the_queue->capacity); - rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal); - if (rc) - debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name); - pthread_cleanup_pop(1); // unlock the queue lock. - } else { - debug(1, "Removing an item from a NULL queue"); - } - return 0; -} - -#endif - // The principal_conn variable points to the connection that // controls the mDNS status and flags and that is potentially // in control of the playing subsystem to output audio to a backend @@ -469,12 +232,13 @@ play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption) principal_conn->client_ip_string, principal_conn->client_rtsp_port, principal_conn->self_ip_string, principal_conn->self_rtsp_port); close(principal_conn->fd); - // principal_conn->fd = 0; + principal_conn->fd = 0; + usleep(1000000); } rtsp_conn_info *previous_principal_conn = principal_conn; principal_conn = conn; // make the conn the new principal_conn pthread_cancel(previous_principal_conn->thread); // cancel the previous one... - + usleep(1000000); if (principal_conn == NULL) { #ifdef CONFIG_AIRPLAY_2 config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay @@ -509,7 +273,7 @@ play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption) // keep track of the threads we have spawned so we can join() them static int nconns = 0; static void track_thread(rtsp_conn_info *conn) { - debug_mutex_lock(&conns_lock, 1000000, 3); + debug_mutex_lock(&conns_lock, 1000000, 4); // look for an empty slot first int i = 0; int found = 0; @@ -629,270 +393,6 @@ static char *nextline(char *in, int inbuf) { return out; } -void msg_retain(rtsp_message *msg) { - int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4); - if (rc) - debug(1, "Error %d locking reference counter lock", rc); - if (msg > (rtsp_message *)0x00010000) { - msg->referenceCount++; - debug(4, "msg_free increment reference counter message %d to %d.", msg->index_number, - msg->referenceCount); - // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); - rc = pthread_mutex_unlock(&reference_counter_lock); - if (rc) - debug(1, "Error %d unlocking reference counter lock", rc); - } else { - debug(1, "invalid rtsp_message pointer 0x%" PRIxPTR " passed to retain", (uintptr_t)msg); - } -} - -rtsp_message *msg_init(void) { - // no thread cancellation points here - int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4); - if (rc) - debug(1, "Error %d locking reference counter lock", rc); - - rtsp_message *msg = malloc(sizeof(rtsp_message)); - if (msg) { - memset(msg, 0, sizeof(rtsp_message)); - msg->referenceCount = 1; // from now on, any access to this must be protected with the lock - msg->index_number = msg_indexes++; - debug(4, "msg_init message %d", msg->index_number); - } else { - die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); - } - // debug(1,"msg_init -- create item %d.", msg->index_number); - - rc = pthread_mutex_unlock(&reference_counter_lock); - if (rc) - debug(1, "Error %d unlocking reference counter lock", rc); - - return msg; -} - -int msg_add_header(rtsp_message *msg, char *name, char *value) { - if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) { - warn("too many headers?!"); - return 1; - } - - msg->name[msg->nheaders] = strdup(name); - msg->value[msg->nheaders] = strdup(value); - msg->nheaders++; - - return 0; -} - -char *msg_get_header(rtsp_message *msg, char *name) { - unsigned int i; - for (i = 0; i < msg->nheaders; i++) - if (!strcasecmp(msg->name[i], name)) - return msg->value[i]; - return NULL; -} - -void _debug_print_msg_headers(const char *filename, const int linenumber, int level, - rtsp_message *msg) { - unsigned int i; - if (msg->respcode != 0) - _debug(filename, linenumber, level, " Response Code: %d.", msg->respcode); - for (i = 0; i < msg->nheaders; i++) { - _debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i], - msg->value[i]); - } -} - -/* -static void debug_print_msg_content(int level, rtsp_message *msg) { - if (msg->contentlength) { - char *obf = malloc(msg->contentlength * 2 + 1); - if (obf) { - char *obfp = obf; - int obfc; - for (obfc = 0; obfc < msg->contentlength; obfc++) { - snprintf(obfp, 3, "%02X", msg->content[obfc]); - obfp += 2; - }; - *obfp = 0; - debug(level, "Content (hex): \"%s\"", obf); - free(obf); - } else { - debug(level, "Can't allocate space for debug buffer"); - } - } else { - debug(level, "No content"); - } -} -*/ - -void msg_free(rtsp_message **msgh) { - debug_mutex_lock(&reference_counter_lock, 1000, 0); - if (*msgh > (rtsp_message *)0x00010000) { - rtsp_message *msg = *msgh; - msg->referenceCount--; - if (msg->referenceCount) - debug(4, "msg_free decrement reference counter message %d to %d", msg->index_number, - msg->referenceCount); - if (msg->referenceCount == 0) { - unsigned int i; - for (i = 0; i < msg->nheaders; i++) { - free(msg->name[i]); - free(msg->value[i]); - } - if (msg->content) - free(msg->content); - // debug(1,"msg_free item %d -- free.",msg->index_number); - uintptr_t index = (msg->index_number) & 0xFFFF; - if (index == 0) - index = 0x10000; // ensure it doesn't fold to zero. - *msgh = - (rtsp_message *)(index); // put a version of the index number of the freed message in here - debug(4, "msg_free freed message %d", msg->index_number); - free(msg); - } else { - // debug(1,"msg_free item %d -- decrement reference to - // %d.",msg->index_number,msg->referenceCount); - } - } else if (*msgh != NULL) { - debug(1, - "msg_free: error attempting to free an allocated but already-freed rtsp_message, number " - "%" PRIxPTR ".", - (uintptr_t)*msgh); - } - debug_mutex_unlock(&reference_counter_lock, 0); -} - -int msg_handle_line(rtsp_message **pmsg, char *line) { - rtsp_message *msg = *pmsg; - - if (!msg) { - msg = msg_init(); - *pmsg = msg; - char *sp, *p; - sp = NULL; // this is to quieten a compiler warning - - debug(4, "RTSP/HTTP Message Received: \"%s\".", line); - - p = strtok_r(line, " ", &sp); - if (!p) - goto fail; - strncpy(msg->method, p, sizeof(msg->method) - 1); - - p = strtok_r(NULL, " ", &sp); - if (!p) - goto fail; - strncpy(msg->path, p, sizeof(msg->path) - 1); - - p = strtok_r(NULL, " ", &sp); - if (!p) - goto fail; - if ((strcmp(p, "RTSP/1.0") != 0) && (strcmp(p, "HTTP/1.1") != 0)) { - debug(1, "Problem with Message: \"%s\"", p); - goto fail; - } - - return -1; - } - - if (strlen(line)) { - char *p; - p = strstr(line, ": "); - if (!p) { - warn("bad header: >>%s<<", line); - goto fail; - } - *p = 0; - p += 2; - msg_add_header(msg, line, p); - debug(4, " %s: %s.", line, p); - return -1; - } else { - char *cl = msg_get_header(msg, "Content-Length"); - if (cl) - return atoi(cl); - else - return 0; - } - -fail: - debug(1, "msg_handle_line fail"); - msg_free(pmsg); - *pmsg = NULL; - return 0; -} - -#ifdef CONFIG_AIRPLAY_2 - -int rtsp_message_contains_plist(rtsp_message *message) { - int reply = 0; // assume there is no plist in the message - if ((message->contentlength >= strlen("bplist00")) && - (strstr(message->content, "bplist00") == message->content)) - reply = 1; - return reply; -} - -plist_t plist_from_rtsp_content(rtsp_message *message) { - plist_t the_plist = NULL; - if (rtsp_message_contains_plist(message)) { - plist_from_memory(message->content, message->contentlength, &the_plist); - } - return the_plist; -} - -char *plist_as_xml_text(plist_t the_plist) { - // caller must free the returned character buffer - // convert it to xml format - uint32_t size; - char *plist_out = NULL; - plist_to_xml(the_plist, &plist_out, &size); - - // put it into a NUL-terminated string - char *reply = malloc(size + 1); - if (reply) { - memcpy(reply, plist_out, size); - reply[size] = '\0'; - } - if (plist_out) - free(plist_out); - return reply; -} - -// caller must free the returned character buffer -char *rtsp_plist_content(rtsp_message *message) { - char *reply = NULL; - // first, check if it has binary plist content - if (rtsp_message_contains_plist(message)) - reply = plist_as_xml_text(plist_from_rtsp_content(message)); - return reply; -} - -#endif - -void _debug_log_rtsp_message(const char *filename, const int linenumber, int level, char *prompt, - rtsp_message *message) { - if (level > debug_level()) - return; - if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list... - _debug(filename, linenumber, level, "%s", prompt); - _debug_print_msg_headers(filename, linenumber, level, message); -#ifdef CONFIG_AIRPLAY_2 - char *plist_content = rtsp_plist_content(message); - if (plist_content) { - _debug(filename, linenumber, level, " Content Plist (as XML):\n--\n%s--", plist_content); - free(plist_content); - } else -#endif - { - _debug(filename, linenumber, level, " No Content Plist. Content length: %d.", - message->contentlength); - } -} - -#define debug_log_rtsp_message(level, prompt, message) \ - _debug_log_rtsp_message(__FILE__, __LINE__, level, prompt, message) -#define debug_print_msg_headers(level, message) \ - _debug_print_msg_headers(__FILE__, __LINE__, level, message) - #ifdef CONFIG_AIRPLAY_2 static void buf_add(sized_buffer *buf, uint8_t *in, size_t in_len) { if (buf->length + in_len > buf->size) { @@ -1343,9 +843,9 @@ int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) { #ifdef CONFIG_AIRPLAY_2 void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req, rtsp_message *resp) { - debug(2, "Connection %d: RECORD on %s", conn->connection_number, + debug(2, "Connection %d: RECORD (AP2) on %s", conn->connection_number, get_category_string(conn->airplay_stream_category)); - debug_log_rtsp_message(3, "RECORD incoming message", req); + debug_log_rtsp_message_conn(conn, 2, "RECORD (AP2) incoming message", req); msg_add_header(resp, "Audio-Latency", "0"); resp->respcode = 200; } @@ -1400,6 +900,39 @@ void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) #ifdef CONFIG_AIRPLAY_2 +int add_pstring_to_malloc(const char *s, void **allocation, size_t *size) { + int response = 0; + void *p = *allocation; + if (p == NULL) { + p = malloc(strlen(s) + 1); + if (p == NULL) { + debug(1, "error allocating memory"); + } else { + *allocation = p; + *size = *size + strlen(s) + 1; + uint8_t *b = (uint8_t *)p; + *b = strlen(s); + p = p + 1; + memcpy(p, s, strlen(s)); + response = 1; + } + } else { + p = realloc(p, *size + strlen(s) + 1); + if (p == NULL) { // assuming we never allocate a zero byte space + debug(1, "error reallocating memory"); + } else { + *allocation = p; + uint8_t *b = (uint8_t *)p + *size; + *b = strlen(s); + p = p + *size + 1; + memcpy(p, s, strlen(s)); + *size = *size + strlen(s) + 1; + response = 1; + } + } + return response; +} + void generateTxtDataValueInfo(rtsp_conn_info *conn, void **response, size_t *responseLength) { void *qualifier_response_data = NULL; size_t qualifier_response_data_length = 0; @@ -1419,11 +952,20 @@ void generateTxtDataValueInfo(rtsp_conn_info *conn, void **response, size_t *res &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); +/* + uint64_t features_hi = 0x0001C340445D0A00L; + features_hi = (features_hi >> 32) & 0xffffffff; + uint64_t features_lo = 0x0001C340445D0A00L; + features_lo = features_lo & 0xffffffff; +*/ + + uint64_t features_hi = config.airplay_features; features_hi = (features_hi >> 32) & 0xffffffff; uint64_t features_lo = config.airplay_features; features_lo = features_lo & 0xffffffff; + if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "features=0x%" PRIX64 ",0x%" PRIX64 "", features_lo, features_hi), @@ -1453,7 +995,7 @@ void generateTxtDataValueInfo(rtsp_conn_info *conn, void **response, size_t *res gcgl = conn->groupContainsGroupLeader; // should have igl here; - if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "igl=%0"), + if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "igl=%d", 0), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); @@ -1519,6 +1061,8 @@ plist_t generateInfoPlist(rtsp_conn_info *conn) { // debug(1,"qualifier_response_data_length: %u.", qualifier_response_data_length); + plist_dict_set_item(response_plist, "psi", plist_new_string(config.airplay_psi)); + plist_dict_set_item(response_plist, "featuresEx", plist_new_string(config.airplay_fex)); plist_dict_set_item(response_plist, "features", plist_new_uint(config.airplay_features)); @@ -1526,9 +1070,7 @@ plist_t generateInfoPlist(rtsp_conn_info *conn) { plist_dict_set_item(response_plist, "deviceID", plist_new_string(config.airplay_device_id)); plist_dict_set_item(response_plist, "pi", plist_new_string(config.airplay_pi)); plist_dict_set_item(response_plist, "name", plist_new_string(config.service_name)); - char *vs = get_version_string(); plist_dict_set_item(response_plist, "model", plist_new_string(config.model)); - free(vs); plist_dict_set_item(response_plist, "pk", plist_new_data((const char *)config.airplay_pk, sizeof(config.airplay_pk))); char senderAddress[256]; @@ -1561,7 +1103,7 @@ plist_t generateInfoPlist(rtsp_conn_info *conn) { } plist_dict_set_item(supported_formats_plist, "bufferStream", plist_new_uint(bufferStreamFormats)); - debug(3, "bufferedStream formats: 0x%" PRIX64 ".", bufferStreamFormats); + debug(4, "bufferedStream formats: 0x%" PRIX64 ".", bufferStreamFormats); plist_dict_set_item(response_plist, "supportedFormats", supported_formats_plist); } } @@ -1967,7 +1509,7 @@ static void pairing_remove(struct pairings *pairing) { static int pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg __attribute__((unused))) { - debug(2, "pair-add cb for %s", device_id); + debug(1, "pair-add cb for %s", device_id); struct pairings *pairing = pairing_find(device_id); if (pairing) { @@ -1981,7 +1523,7 @@ static int pairing_add_cb(uint8_t public_key[32], const char *device_id, static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id, void *cb_arg __attribute__((unused))) { - debug(2, "pair-remove cb for %s", device_id); + debug(1, "pair-remove cb for %s", device_id); struct pairings *pairing = pairing_find(device_id); if (!pairing) { @@ -1995,7 +1537,7 @@ static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), con static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg __attribute__((unused))) { - debug(2, "pair-list cb"); + debug(1, "pair-list cb"); for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg); @@ -2004,7 +1546,15 @@ static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { - debug(2, "Connection %d: handle_pair_add", conn->connection_number); + + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); + if (hdr) { + if (conn->ap2_client_name) + free(conn->ap2_client_name); + conn->ap2_client_name = strdup(hdr); + } + debug(1, "Connection %d from \"%s\": handle_pair_add", conn->connection_number, conn->ap2_client_name); + debug_log_rtsp_message_conn(conn, 1, "pair-add request", req); uint8_t *body = NULL; size_t body_len = 0; int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL, @@ -2017,12 +1567,18 @@ void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message resp->content = (char *)body; // these will be freed when the data is sent resp->contentlength = body_len; msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(2, "pair-add response", resp); + debug_log_rtsp_message_conn(conn, 1, "pair-add response", resp); } void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { - debug(2, "Connection %d: handle_pair_list", conn->connection_number); + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); + if (hdr) { + if (conn->ap2_client_name) + free(conn->ap2_client_name); + conn->ap2_client_name = strdup(hdr); + } + debug(1, "Connection %d from \"%s\": handle_pair_list", conn->connection_number, conn->ap2_client_name); uint8_t *body = NULL; size_t body_len = 0; int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL, @@ -2035,12 +1591,19 @@ void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message resp->content = (char *)body; // these will be freed when the data is sent resp->contentlength = body_len; msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(2, "pair-list response", resp); + debug_log_rtsp_message_conn(conn, 1, "pair-list response", resp); } void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: handle_pair_remove", conn->connection_number); + + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); + if (hdr) { + if (conn->ap2_client_name) + free(conn->ap2_client_name); + conn->ap2_client_name = strdup(hdr); + } + debug(1, "Connection %d from \"%s\": handle_pair_remove", conn->connection_number, conn->ap2_client_name); uint8_t *body = NULL; size_t body_len = 0; int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL, @@ -2053,12 +1616,25 @@ void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_messa resp->content = (char *)body; // these will be freed when the data is sent resp->contentlength = body_len; msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(3, "pair-remove response", resp); + debug_log_rtsp_message_conn(conn, 1, "pair-remove response", resp); } void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { - debug(3, "Connection %d: handle_pair_verify Content-Length %d", conn->connection_number, - req->contentlength); + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); + if (hdr) { + if (conn->ap2_client_name) + free(conn->ap2_client_name); + conn->ap2_client_name = strdup(hdr); + } + // try to pick up the stages + uint8_t *b = (uint8_t *)req->content; + char mstage = '-'; + if ((req->contentlength >= 3) && (b[0] == 6) && (b[1] == 1) && (b[1] <= 9)) { + mstage = '0' + b[2]; + } + + debug(1, "Connection %d from \"%s\": handle_pair_verify, stage M%c, Content-Length %d", conn->connection_number, conn->ap2_client_name, mstage, req->contentlength); + debug_log_rtsp_message_conn(conn, 2, "pair-verify request", req); int ret; uint8_t *body = NULL; size_t body_len = 0; @@ -2105,24 +1681,45 @@ out: resp->contentlength = body_len; if (body) msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(3, "pair-verify response", resp); + debug_log_rtsp_message_conn(conn, 2, "pair-verify response", resp); } void handle_pair_pin_start(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); + if (hdr) { + if (conn->ap2_client_name) + free(conn->ap2_client_name); + conn->ap2_client_name = strdup(hdr); + } + debug(1, "Connection %d from \"%s\": handle_pair_pin_start, Content-Length %d", conn->connection_number, + conn->ap2_client_name, + req->contentlength); + debug_log_rtsp_message_conn(conn, 1, "handle_pair_pin_start", req); + uint8_t *body = NULL; size_t body_len = 0; - debug(2, "Connection %d: pair-pin-start Content-Length %d", conn->connection_number, - req->contentlength); - debug_log_rtsp_message(2, "handle_pair_pin_start", req); resp->content = (char *)body; // these will be freed when the data is sent resp->contentlength = body_len; if (body != NULL) msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(2, "pair-pin-start response", resp); + debug_log_rtsp_message(1, "pair-pin-start response", resp); } void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); + if (hdr) { + if (conn->ap2_client_name) + free(conn->ap2_client_name); + conn->ap2_client_name = strdup(hdr); + } + debug(2, "Connection %d from \"%s\": handle_pair_setup, Content-Length %d", conn->connection_number, + conn->ap2_client_name, + req->contentlength); + debug_log_rtsp_message_conn(conn, 2, "pair-setup request", req); + int ret; uint8_t *body = NULL; size_t body_len = 0; @@ -2131,7 +1728,7 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re debug_log_rtsp_message(3, "handle_pair_setup", req); if (!conn->ap2_pairing_context.setup_ctx) { - conn->ap2_pairing_context.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.airplay_pin, + conn->ap2_pairing_context.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.password, NULL, NULL, config.airplay_device_id); if (!conn->ap2_pairing_context.setup_ctx) { debug(1, "Error creating setup context"); @@ -2154,19 +1751,18 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re // don't activate yet, the response to this request is still plaintext conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 3, conn->pair_setup_result->shared_secret, - conn->pair_setup_result->shared_secret_len, - ""); // last argument is the (possible) dynamic salt suffix + conn->pair_setup_result->shared_secret_len, ""); // last argument is the (possible) dynamic salt suffix if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) { - debug(1, "Error setting up rtsp control channel ciphering\n"); + debug(1, "Error setting up rtsp control channel ciphering"); goto out; } conn->ap2_pairing_context.control_cipher_bundle.description = strdup("Control Stream"); + conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 4, conn->pair_setup_result->shared_secret, - conn->pair_setup_result->shared_secret_len, - ""); // last argument is the (possible) dynamic salt suffix + conn->pair_setup_result->shared_secret_len, ""); // last argument is the (possible) dynamic salt suffix if (!conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx) { - debug(1, "Error setting up rtsp control channel ciphering\n"); + debug(1, "Error setting up rtsp event channel ciphering"); goto out; } conn->ap2_pairing_context.event_cipher_bundle.description = strdup("Event Stream"); @@ -2177,12 +1773,23 @@ out: resp->contentlength = body_len; if (body) msg_add_header(resp, "Content-Type", "application/octet-stream"); - debug_log_rtsp_message(3, "pair-setup response", resp); + debug_log_rtsp_message_conn(conn, 2, "pair-setup response", resp); } void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + char *hdr = msg_get_header(req, "X-Apple-Client-Name"); + if (hdr) { + if (conn->ap2_client_name) + free(conn->ap2_client_name); + conn->ap2_client_name = strdup(hdr); + } + debug(2, "Connection %d from \"%s\": handle_fp_setup,", conn->connection_number, + conn->ap2_client_name); + debug_log_rtsp_message_conn(conn, 2, "fp-setup request", req); + + /* Fairplay magic */ static uint8_t server_fp_reply1[] = "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x00\x0f\x9f\x3f\x9e\x0a" @@ -2298,24 +1905,63 @@ void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message */ void handle_configure(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req __attribute__((unused)), rtsp_message *resp) { - uint8_t public_key[32]; - - pair_public_key_get(PAIR_SERVER_HOMEKIT, public_key, config.airplay_device_id); + debug_log_rtsp_message_conn(conn, 1, "POST /configure req:", req); + + int existingEnable_HK_Access_Control = config.enable_HK_Access_Control; + plist_t response_plist = plist_new_dict(); - plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi)); - plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1)); - plist_dict_set_item(response_plist, "PublicKey", - plist_new_data((const char *)public_key, sizeof(public_key))); - plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name)); - plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0)); - + // look for a configuration dictionary + plist_t messagePlist = plist_from_rtsp_content(req); + if (messagePlist != NULL) { + // look for the keyed dict "ConfigurationDictionary" + plist_t configurationDict = plist_dict_get_item(messagePlist, "ConfigurationDictionary"); + if (configurationDict != NULL) { + uint8_t enable_HK_Access_Control = 0; + plist_t enableItem = plist_dict_get_item(configurationDict,"Enable_HK_Access_Control"); + if (enableItem != NULL) { + plist_get_bool_val(enableItem, &enable_HK_Access_Control); + + if (enable_HK_Access_Control != 0) { + config.enable_HK_Access_Control = 1; + plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi)); + plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1)); + plist_dict_set_item(response_plist, "PublicKey", + plist_new_data((const char *)config.airplay_pk, sizeof(config.airplay_pk))); + plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name)); + plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0)); + } else { + config.enable_HK_Access_Control = 0; + // leave the response dict empty + } + } else { + debug(1, "no Enable_HK_Access_Control item in POST /configure ConfigurationDictionary"); + } + debug(1, "enable_HK_Access_Control is %u.", enable_HK_Access_Control); + } else { + debug(1, "no ConfigurationDictionary in POST /configure plist"); + } + plist_free(messagePlist); + } else { + debug(1, "no plist in POST /configure request"); + } + + if (config.enable_HK_Access_Control != 0) { + config.airplay_statusflags |= (1 << 10); // DeviceWasSetupForHKAccessControl + } else { + config.airplay_statusflags &= (0xffffffff - (1 << 10)); + } + + if (config.enable_HK_Access_Control != existingEnable_HK_Access_Control) { + build_bonjour_strings(principal_conn); + mdns_update(NULL, secondary_txt_records); + } plist_to_bin(response_plist, &resp->content, &resp->contentlength); plist_free(response_plist); msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); - debug_log_rtsp_message(2, "POST /configure response:", resp); + debug_log_rtsp_message_conn(conn, 1, "POST /configure response:", resp); } void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, @@ -2325,6 +1971,7 @@ void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message debug_log_rtsp_message(4, NULL, req); int is_playing = 0; + int connection_number = 0; int type = 0; double rate = 0.0; @@ -2334,6 +1981,7 @@ void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); if ((principal_conn != NULL) && (principal_conn->is_playing != 0)) { is_playing = 1; + connection_number = principal_conn->connection_number; type = principal_conn->type; rate = 1.0 * principal_conn->input_rate; } @@ -2343,9 +1991,9 @@ void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message if (is_playing != 0) { if ((type != 96) && (type != 103)) - debug(1, "Connection %d, feedback unexpected type: %u.", conn->connection_number, type); + debug(1, "Connection %d, feedback unexpected type: %u.", connection_number, type); if ((rate != 44100.0) && (rate != 48000.0)) - debug(2, "Connection %d, feedback unexpected rate: %f.", conn->connection_number, rate); + debug(2, "Connection %d, feedback unexpected rate: %f.", connection_number, rate); plist_t payload_plist = plist_new_dict(); plist_dict_set_item(payload_plist, "type", plist_new_uint(type)); plist_dict_set_item(payload_plist, "sr", plist_new_real(rate)); @@ -2372,6 +2020,11 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req, req->contentlength); debug_log_rtsp_message(3, NULL, req); if (rtsp_message_contains_plist(req)) { + // we are not going to load the plist here because we don't wamt + // to incur the memory and processing cost. So we'll just send it to the + // metadata handling code and it can be dealt with there. + send_metadata('ssnc', 'copl', req->content, req->contentlength, req, 1); // COmmand PList (and release 'req' afterwards) + /* plist_t command_dict = NULL; plist_from_memory(req->content, req->contentlength, &command_dict); if (command_dict != NULL) { @@ -2380,6 +2033,9 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req, if (item != NULL) { char *typeValue = NULL; plist_get_string_val(item, &typeValue); + debug(1, "Connection %d: POST /command plist type \"%s\" received.", + conn->connection_number, typeValue); + debug_log_rtsp_message(1, NULL, req); if ((typeValue != NULL) && (strcmp(typeValue, "updateMRSupportedCommands") == 0)) { item = plist_dict_get_item(command_dict, "params"); if (item != NULL) { @@ -2404,7 +2060,7 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req, if (subsidiary_plist) { char *printable_plist = plist_as_xml_text(subsidiary_plist); if (printable_plist) { - debug(3, "Connection %d:\n==\n%s\n==", conn->connection_number, printable_plist); + debug(4, "Connection %d:\n==\n%s\n==", conn->connection_number, printable_plist); free(printable_plist); } else { debug(1, "Can't print the plist!"); @@ -2426,10 +2082,6 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req, debug(1, "Connection %d: POST /command no params dict.", conn->connection_number); } resp->respcode = 200; - } else { - debug(1, "Connection %d: POST /command plist type \"%s\" received.", - conn->connection_number, typeValue); - debug_log_rtsp_message(2, NULL, req); } if (typeValue != NULL) free(typeValue); @@ -2441,6 +2093,7 @@ void handle_command(rtsp_conn_info *conn, rtsp_message *req, } else { debug(1, "Connection %d: POST /command plist cannot be inputted.", conn->connection_number); } + */ } else { debug(1, "Connection %d: POST /command contains no plist", conn->connection_number); } @@ -2564,7 +2217,7 @@ void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message #ifdef CONFIG_AIRPLAY_2 void handle_options_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { - debug_log_rtsp_message(2, "OPTIONS request", req); + debug_log_rtsp_message_conn(conn, 1, "OPTIONS request", req); debug(3, "Connection %d: OPTIONS", conn->connection_number); resp->respcode = 200; msg_add_header(resp, "Public", @@ -2578,10 +2231,11 @@ void handle_options_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { - debug(2, "Connection %d: TEARDOWN 2 %s.", conn->connection_number, + debug(2, "Connection %d from \"%s\": TEARDOWN 2 %s.", conn->connection_number, + conn->ap2_client_name, get_category_string(conn->airplay_stream_category)); debug_log_rtsp_message(2, "TEARDOWN 2: ", req); - + if (conn->player_thread) { debug(2, "TEARDOWN 2 is stopping a player thread before exiting..."); player_stop(conn); // this nulls the player_thread and cancels the threads... @@ -2650,23 +2304,24 @@ static void check_and_send_plist_metadata(plist_t messagePlist, const char *plis void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { int err; - debug(2, "Connection %d: SETUP (AirPlay 2) on %s", conn->connection_number, - get_category_string(conn->airplay_stream_category)); - debug_log_rtsp_message(2, "SETUP (AirPlay 2) incoming message", req); plist_t messagePlist = plist_from_rtsp_content(req); + + if (messagePlist != NULL) { + // if (conn->sessionPlist) + // plist_free(conn->sessionPlist); + conn->sessionPlist = messagePlist; + } plist_t setupResponsePlist = plist_new_dict(); resp->respcode = 501; // see if we can get a name for the client - char *clientNameString = NULL; plist_t nameItem = plist_dict_get_item(messagePlist, "name"); if (nameItem != NULL) { - plist_get_string_val(nameItem, &conn->ap2_client_name); // generates a malloced string - clientNameString = strdup(conn->ap2_client_name); - } else { - clientNameString = strdup(""); + if (conn->ap2_client_name) + free(conn->ap2_client_name); + plist_get_string_val(nameItem, &conn->ap2_client_name); // we need to free it! } // see if the incoming plist contains a "streams" array @@ -2688,7 +2343,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) if (strcmp(timingProtocolString, "PTP") == 0) { debug(2, "Connection %d: AP2 PTP connection from %s:%u (\"%s\") to self at %s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, - clientNameString, conn->self_ip_string, conn->self_rtsp_port); + conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port); conn->airplay_stream_category = ptp_stream; conn->timing_type = ts_ptp; @@ -2697,7 +2352,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) } else if (strcmp(timingProtocolString, "NTP") == 0) { debug(1, "Connection %d: SETUP: NTP setup from %s:%u (\"%s\") to self at %s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, - clientNameString, conn->self_ip_string, conn->self_rtsp_port); + conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port); conn->airplay_stream_category = ntp_stream; conn->timing_type = ts_ntp; do_pthread_setname(&conn->thread, "ap2_ntp_%d", conn->connection_number); @@ -2706,7 +2361,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) "Connection %d: SETUP: a \"None\" setup detected from %s:%u (\"%s\") to self at " "%s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, - clientNameString, conn->self_ip_string, conn->self_rtsp_port); + conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port); // now check to see if it's got the "isRemoteControlOnly" item and check it's true plist_t isRemoteControlOnly = plist_dict_get_item(messagePlist, "isRemoteControlOnly"); if (isRemoteControlOnly != NULL) { @@ -2717,7 +2372,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) "Connection %d: SETUP: Remote Control Only connection from %s:%u (\"%s\") to " "self at %s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, - clientNameString, conn->self_ip_string, conn->self_rtsp_port); + conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port); conn->airplay_stream_category = remote_control_stream; do_pthread_setname(&conn->thread, "ap2_rc_%d", conn->connection_number); } else { @@ -2733,6 +2388,11 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) conn->connection_number); } } + + debug(2, "Connection %d from \"%s\": Initial (i.e. no streams array) SETUP (AirPlay 2) on %s", conn->connection_number, + conn->ap2_client_name, + get_category_string(conn->airplay_stream_category)); + debug_log_rtsp_message_conn(conn, 2, "Initial (i.e. no streams array) SETUP (AirPlay 2) incoming message", req); // here, we know it's an initial setup and we know the kind of setup being requested // if it's a full service PTP stream, we get groupUUID, groupContainsGroupLeader and @@ -2828,6 +2488,9 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) plist_array_append_item(addresses, plist_new_string(conn->self_ip_string)); // debug(1,"self ip: \"%s\"", conn->self_ip_string); + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + struct ifaddrs *addrs, *iap; getifaddrs(&addrs); for (iap = addrs; iap != NULL; iap = iap->ifa_next) { @@ -2862,6 +2525,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) } } freeifaddrs(addrs); + pthread_setcancelstate(oldState, NULL); // debug(1,"initial timing peer command: \"%s\".", timing_list_message); // ptp_send_control_message_string(timing_list_message); @@ -2891,6 +2555,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) debug(2, "Connection %d: TCP PTP event port opened: %u.", conn->connection_number, conn->local_event_port); + if (conn->rtp_event_thread != NULL) debug(1, "previous rtp_event_thread allocation not freed, it seems."); conn->ap2_event_receiver_exited = 0; @@ -2962,7 +2627,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) conn->rtp_event_thread = malloc(sizeof(pthread_t)); if (conn->rtp_event_thread == NULL) die("Couldn't allocate space for pthread_t"); - named_pthread_create(conn->rtp_event_thread, NULL, &ap2_rc_event_receiver, (void *)conn, + named_pthread_create(conn->rtp_event_thread, NULL, &ap2_event_receiver, (void *)conn, "ap2_rc_evt_%d", conn->connection_number); plist_dict_set_item(setupResponsePlist, "eventPort", plist_new_uint(conn->local_event_port)); @@ -2993,9 +2658,12 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) warn("Unrecognised SETUP incoming message -- ignored."); } } else { - debug(2, "Connection %d: SETUP on %s. A \"streams\" array has been found", - conn->connection_number, get_category_string(conn->airplay_stream_category)); - debug_log_rtsp_message(3, "SETUP (AirPlay 2) SETUP with streams incoming message", req); + + debug(2, "Connection %d from \"%s\": Subsequent (i.e. with streams array) SETUP (AirPlay 2) on %s", conn->connection_number, + conn->ap2_client_name, + get_category_string(conn->airplay_stream_category)); + debug_log_rtsp_message_conn(conn, 2, "Subsequent (i.e. with streams array) SETUP (AirPlay 2) incoming message", req); + if (conn->airplay_stream_category == ptp_stream) { if (conn->player_thread) { @@ -3107,8 +2775,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) conn->type = item_value; switch (item_value) { case 96: { - debug(1, "Connection %d. AP2 Realtime ALAC/44100/S16/2 Stream.", conn->connection_number); - debug_log_rtsp_message(2, "Realtime Audio Stream SETUP incoming message", req); + debug(4, "Connection %d. AP2 Realtime Audio Stream SETUP.", conn->connection_number); + debug_log_rtsp_message(4, "AP2 Realtime Audio Stream SETUP incoming message:", req); conn->stream.type = ast_apple_lossless; conn->airplay_stream_type = realtime_stream; @@ -3117,12 +2785,20 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) if (item != NULL) { plist_get_uint_val(item, &item_value); // see https://emanuelecozzi.net/docs/airplay2/audio/ for values - debug(2, "Sample rate: %" PRId64 ".", item_value); conn->input_rate = item_value; + debug(4, "Set conn->input_rate: %u.", conn->input_rate); } else { - debug(1, "No sample rate (sr) property found in setup."); + debug(1, "Connection %d. No sample rate (sr) property found in setup.", conn->connection_number); + } + + item = plist_dict_get_item(stream0, "spf"); // samples per frame + if (item != NULL) { + plist_get_uint_val(item, &item_value); + conn->frames_per_packet = item_value; + debug(4, "Set conn->frames_per_packet: %u.", conn->frames_per_packet); + } else { + debug(1, "Connection %d. No samples per frame (spf) property found in setup.", conn->connection_number); } - conn->frames_per_packet = 352; // bind a new UDP port and get a socket conn->local_realtime_audio_port = 0; // any port @@ -3235,8 +2911,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) snprintf(salt_suffix, sizeof(salt_suffix), "%" PRIu64 "", seed); conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 5, conn->pair_setup_result->shared_secret, - conn->pair_setup_result->shared_secret_len, - salt_suffix); // last argument is the (possible) dynamic salt suffix + conn->pair_setup_result->shared_secret_len, salt_suffix); // last argument is the (possible) dynamic salt suffix if (conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx != NULL) { conn->ap2_pairing_context.data_cipher_bundle.description = strdup("DataStream"); // get a port to use as an data port @@ -3294,8 +2969,6 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); } plist_free(messagePlist); - if (clientNameString != NULL) - free(clientNameString); } #endif @@ -3463,11 +3136,11 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, debug(3, "Connection %d: request to set AirPlay Volume to: %f.", conn->connection_number, volume); // if we are playing, go ahead and change the volume -#ifdef CONFIG_DBUS_INTERFACE - if (dbus_service_is_running()) { - shairport_sync_set_volume(shairportSyncSkeleton, volume); - } else { -#endif +// #ifdef CONFIG_DBUS_INTERFACE +// if (dbus_service_is_running()) { +// shairport_sync_set_volume(shairportSyncSkeleton, volume); +// } else { +// #endif pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); if (principal_conn == conn) { @@ -3488,9 +3161,9 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, conn->own_airplay_volume_set = 1; } pthread_cleanup_pop(1); // release the principal_conn lock -#ifdef CONFIG_DBUS_INTERFACE - } -#endif +// #ifdef CONFIG_DBUS_INTERFACE +// } +// #endif } else if (strncmp(cp, "progress: ", strlen("progress: ")) == 0) { // this can be sent even when metadata is not solicited @@ -3509,663 +3182,6 @@ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, } #ifdef CONFIG_METADATA -// Metadata is not used by shairport-sync. -// Instead we send all metadata to a fifo pipe, so that other apps can listen to -// the pipe and use the metadata. - -// We use two 4-character codes to identify each piece of data and we send the -// data itself, if any, -// in base64 form. - -// The first 4-character code, called the "type", is either: -// 'core' for all the regular metadadata coming from iTunes, etc., or -// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync -// itself, such as -// start/end delimiters, etc. - -// For 'core' metadata, the second 4-character code is the 4-character metadata -// code coming from -// iTunes etc. -// For 'ssnc' metadata, the second 4-character code is used to distinguish the -// messages. - -// Cover art is not tagged in the same way as other metadata, it seems, so is -// sent as an 'ssnc' type -// metadata message with the code 'PICT' -// Here are the 'ssnc' codes defined so far: -// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the -// first few bytes to see -// which. -// 'abeg' -- active mode entered. No arguments -// 'aend' -- active mode exited. No arguments -// 'pbeg' -- play stream begin. No arguments -// 'pend' -- play stream end. No arguments -// 'pfls' -- play stream flush. The argument is an unsigned 32-bit -// frame number. It seems that all frames up to but not -// including this frame are to be flushed. -// -// 'prsm' -- play stream resume. No arguments. (deprecated) -// 'paus' -- buffered audio stream paused. No arguments. -// 'pres' -- buffered audio stream resumed. No arguments. -// 'pffr' -- the first frame of a play session has been received and has been validly -// timed. The argument is -// "/"." -// 'pdis' -- a discontinuity in the timestamps of incoming frames has been detected. -// timed. The argument is "/". -// discontinuity is the actual frame number less the expected frame number -// positive means a gap -// 'pvol' -- play volume. The volume is sent as a string -- -// "airplay_volume,volume,lowest_volume,highest_volume" -// volume, lowest_volume and highest_volume are given in dB. -// The "airplay_volume" is what's sent to the player, and is from -// 0.00 down to -30.00, -// with -144.00 meaning mute. -// This is linear on the volume control slider of iTunes or iOS -// AirPlay. -// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP -// timestamps for the start -// of the current play sequence, the current play point and the end of the -// play sequence. -// I guess the timestamps wrap at 2^32. -// 'mdst' -- a sequence of metadata is about to start; will have, as data, -// the rtptime associated with the metadata, if available -// 'mden' -- a sequence of metadata has ended; will have, as data, the -// rtptime associated with the metadata, if available -// 'pcst' -- a picture is about to be sent; will have, as data, the rtptime -// associated with the picture, if available -// 'pcen' -- a picture has been sent; will have, as data, the rtptime -// associated with the metadata, if available -// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session. -// Specifically, it's the "X-Apple-Client-Name" string -// 'snua' -- A "user agent" -- e.g. "iTunes/12..." -- has opened a play -// session. Specifically, it's the "User-Agent" string -// The next two two tokens are to facilitate remote control of the source. -// There is some information at http://nto.github.io/AirPlay.html about -// remote control of the source. -// -// 'daid' -- this is the source's DACP-ID (if it has one -- it's not -// guaranteed), useful if you want to remotely control the source. Use this -// string to identify the source's remote control on the network. -// 'acre' -- this is the source's Active-Remote token, necessary if you want -// to send commands to the source's remote control (if it has one). -// `clip` -- the payload is the IP number of the client, i.e. the sender of audio. -// Can be an IPv4 or an IPv6 number. In AirPlay 2 operation, it is sent as soon -// as the client has exclusive access to the player and after any existing -// play session has been interrupted and terminated. -// `conn` -- the payload is the IP number of the client, i.e. the sender of audio. -// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message. -// It is sent as soon as the client requests access to the player. -// If Shairport Sync is already playing, this message is sent before the current -// play session is stopped. -// `svip` -- the payload is the IP number of the server, i.e. the player itself. -// Can be an IPv4 or an IPv6 number. -// `svna` -- the payload is the service name of the player, i.e. the name by -// which it is seen in the AirPlay menu. -// `disc` -- the payload is the IP number of the client, i.e. the sender of audio. -// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message. -// It is sent when a client has been disconnected. -// `dapo` -- the payload is the port number (as text) on the server to which remote -// control commands should be sent. It is 3689 for iTunes but varies for iOS devices. -// `` - -// A special sub-protocol is used for sending large data items over UDP -// If the payload exceeded 4 MB, it is chunked using the following format: -// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data. -// Notice that the number of items is different to the standard - -static int fd = -1; -// static int dirty = 0; - -pc_queue metadata_queue; -#define metadata_queue_size 500 -metadata_package metadata_queue_items[metadata_queue_size]; -pthread_t metadata_thread; - -#ifdef CONFIG_METADATA_HUB -pc_queue metadata_hub_queue; -#define metadata_hub_queue_size 500 -metadata_package metadata_hub_queue_items[metadata_hub_queue_size]; -pthread_t metadata_hub_thread; -#endif - -#ifdef CONFIG_MQTT -pc_queue metadata_mqtt_queue; -#define metadata_mqtt_queue_size 500 -metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size]; -pthread_t metadata_mqtt_thread; -#endif - -static int metadata_sock = -1; -static struct sockaddr_in metadata_sockaddr; -static char *metadata_sockmsg; -pc_queue metadata_multicast_queue; -#define metadata_multicast_queue_size 500 -metadata_package metadata_multicast_queue_items[metadata_queue_size]; -pthread_t metadata_multicast_thread; - -void metadata_create_multicast_socket(void) { - if (config.metadata_enabled == 0) - return; - - // Unlike metadata pipe, socket is opened once and stays open, - // so we can call it in create - if (config.metadata_sockaddr && config.metadata_sockport) { - metadata_sock = socket(AF_INET, SOCK_DGRAM, 0); - if (metadata_sock < 0) { - debug(1, "Could not open metadata socket"); - } else { - int buffer_size = METADATA_SNDBUF; - setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)); - bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr)); - metadata_sockaddr.sin_family = AF_INET; - metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr); - metadata_sockaddr.sin_port = htons(config.metadata_sockport); - metadata_sockmsg = malloc(config.metadata_sockmsglength); - if (metadata_sockmsg) { - memset(metadata_sockmsg, 0, config.metadata_sockmsglength); - } else { - die("Could not malloc metadata multicast socket buffer"); - } - } - } -} - -void metadata_delete_multicast_socket(void) { - if (config.metadata_enabled == 0) - return; - if (metadata_sock != -1) { - shutdown(metadata_sock, SHUT_RDWR); // we want to immediately deallocate the buffer - close(metadata_sock); - } - if (metadata_sockmsg) - free(metadata_sockmsg); -} - -void metadata_open(void) { - if (config.metadata_enabled == 0) - return; - - size_t pl = strlen(config.metadata_pipename) + 1; - - char *path = malloc(pl + 1); - snprintf(path, pl + 1, "%s", config.metadata_pipename); - - fd = try_to_open_pipe_for_writing(path); - free(path); -} - -static void metadata_close(void) { - if (fd < 0) - return; - close(fd); - fd = -1; -} - -void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) { - // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code, - // length); - if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) { - char *ptr = metadata_sockmsg; - uint32_t v; - v = htonl(type); - memcpy(ptr, &v, 4); - ptr += 4; - v = htonl(code); - memcpy(ptr, &v, 4); - ptr += 4; - memcpy(ptr, data, length); - sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr, - sizeof(metadata_sockaddr)); - } else if (metadata_sock >= 0) { - // send metadata in numbered chunks using the protocol: - // ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data) - - uint32_t chunk_ix = 0; - if (config.metadata_sockmsglength == 24) - die("A divide by zero almost occurred (config.metadata_sockmsglength = 24)."); - uint32_t chunk_total = length / (config.metadata_sockmsglength - 24); - if (chunk_total * (config.metadata_sockmsglength - 24) < length) { - chunk_total++; - } - uint32_t remaining = length; - uint32_t v; - char *data_crsr = data; - do { - char *ptr = metadata_sockmsg; - memcpy(ptr, "ssncchnk", 8); - ptr += 8; - v = htonl(chunk_ix); - memcpy(ptr, &v, 4); - ptr += 4; - v = htonl(chunk_total); - memcpy(ptr, &v, 4); - ptr += 4; - v = htonl(type); - memcpy(ptr, &v, 4); - ptr += 4; - v = htonl(code); - memcpy(ptr, &v, 4); - ptr += 4; - size_t datalen = remaining; - if (datalen > config.metadata_sockmsglength - 24) { - datalen = config.metadata_sockmsglength - 24; - } - memcpy(ptr, data_crsr, datalen); - data_crsr += datalen; - sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0, - (struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr)); - chunk_ix++; - remaining -= datalen; - if (remaining == 0) - break; - } while (1); - } -} - -void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) { - // debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length); - int ret = 0; - // readers may go away and come back - - if (fd < 0) - metadata_open(); - if (fd < 0) - return; - char thestring[1024]; - snprintf(thestring, 1024, "%x%x%u", type, code, - length); - // ret = non_blocking_write(fd, thestring, strlen(thestring)); - ret = write(fd, thestring, strlen(thestring)); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 1",ret); - return; - } - if ((data != NULL) && (length > 0)) { - snprintf(thestring, 1024, "\n\n"); - // ret = non_blocking_write(fd, thestring, strlen(thestring)); - ret = write(fd, thestring, strlen(thestring)); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 2",ret); - return; - } - // here, we write the data in base64 form using our nice base64 encoder - // but, we break it into lines of 76 output characters, except for the last - // one. - // thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time - size_t remaining_count = length; - char *remaining_data = data; - // size_t towrite_count; - char outbuf[76]; - while ((remaining_count) && (ret >= 0)) { - size_t towrite_count = remaining_count; - if (towrite_count > 57) - towrite_count = 57; - size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit - if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) == - NULL) - debug(1, "Error encoding base64 data."); - // debug(1,"Remaining count: %d ret: %d, outbuf_size: - // %d.",remaining_count,ret,outbuf_size); - // ret = non_blocking_write(fd, outbuf, outbuf_size); - ret = write(fd, outbuf, outbuf_size); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 3",ret); - return; - } - remaining_data += towrite_count; - remaining_count -= towrite_count; - } - snprintf(thestring, 1024, ""); - // ret = non_blocking_write(fd, thestring, strlen(thestring)); - ret = write(fd, thestring, strlen(thestring)); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 4",ret); - return; - } - } - snprintf(thestring, 1024, "\n"); - // ret = non_blocking_write(fd, thestring, strlen(thestring)); - ret = write(fd, thestring, strlen(thestring)); - if (ret < 0) { - // debug(1,"metadata_process error %d exit 5",ret); - return; - } -} - -void metadata_pack_cleanup_function(void *arg) { - // debug(1, "metadata_pack_cleanup_function called"); - metadata_package *pack = (metadata_package *)arg; - if (pack->carrier) - msg_free(&pack->carrier); // release the message - else if (pack->data) - free(pack->data); - // debug(1, "metadata_pack_cleanup_function exit"); -} - -void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) { - // debug(2, "metadata_thread_cleanup_function called"); - metadata_close(); -} - -void *metadata_thread_function(__attribute__((unused)) void *ignore) { - // #include - // debug(1, "metadata_thread_function PID %d", syscall(SYS_gettid)); - metadata_create_multicast_socket(); - metadata_package pack; - pthread_cleanup_push(metadata_thread_cleanup_function, NULL); - while (1) { - pc_queue_get_item(&metadata_queue, &pack); - pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (config.metadata_enabled) { - if (pack.carrier) { - debug(4, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code, - pack.length, pack.carrier->index_number); - } else { - debug(4, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length); - } - metadata_process(pack.type, pack.code, pack.data, pack.length); - debug(4, " pipe: done."); - } - pthread_cleanup_pop(1); - } - pthread_cleanup_pop(1); // will never happen - pthread_exit(NULL); -} - -void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) { - // debug(2, "metadata_multicast_thread_cleanup_function called"); - metadata_delete_multicast_socket(); -} - -void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) { - // #include - // debug(1, "metadata_multicast_thread_function PID %d", syscall(SYS_gettid)); - metadata_create_multicast_socket(); - metadata_package pack; - pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL); - while (1) { - pc_queue_get_item(&metadata_multicast_queue, &pack); - pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (config.metadata_enabled) { - if (pack.carrier) { - debug(4, - " multicast: type " - "%x, code %x, length %u, message %d.", - pack.type, pack.code, pack.length, pack.carrier->index_number); - } else { - debug(4, - " multicast: type " - "%x, code %x, length %u.", - pack.type, pack.code, pack.length); - } - metadata_multicast_process(pack.type, pack.code, pack.data, pack.length); - debug(4, - " multicast: done."); - } - pthread_cleanup_pop(1); - } - pthread_cleanup_pop(1); // will never happen - pthread_exit(NULL); -} - -#ifdef CONFIG_METADATA_HUB -void metadata_hub_close(void) {} - -void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) { - // debug(2, "metadata_hub_thread_cleanup_function called"); - metadata_hub_close(); -} - -void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { - // #include - // debug(1, "metadata_hub_thread_function PID %d", syscall(SYS_gettid)); - metadata_package pack; - pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL); - while (1) { - pc_queue_get_item(&metadata_hub_queue, &pack); - pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (pack.carrier) { - debug(4, " hub: type %x, code %x, length %u, message %d.", pack.type, - pack.code, pack.length, pack.carrier->index_number); - } else { - debug(4, " hub: type %x, code %x, length %u.", pack.type, pack.code, - pack.length); - } - metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length); - debug(4, " hub: done."); - pthread_cleanup_pop(1); - } - pthread_cleanup_pop(1); // will never happen - pthread_exit(NULL); -} -#endif - -#ifdef CONFIG_MQTT -void metadata_mqtt_close(void) {} - -void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) { - // debug(2, "metadata_mqtt_thread_cleanup_function called"); - metadata_mqtt_close(); - // debug(2, "metadata_mqtt_thread_cleanup_function done"); -} - -void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) { - // #include - // debug(1, "metadata_mqtt_thread_function PID %d", syscall(SYS_gettid)); - metadata_package pack; - pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL); - while (1) { - pc_queue_get_item(&metadata_mqtt_queue, &pack); - pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (config.mqtt_enabled) { - if (pack.carrier) { - debug(3, - " mqtt: type %x, code %x, length %u, message " - "%d.", - pack.type, pack.code, pack.length, pack.carrier->index_number); - } else { - debug(3, " mqtt: type %x, code %x, length %u.", - pack.type, pack.code, pack.length); - } - mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length); - debug(3, " mqtt: done."); - } - - pthread_cleanup_pop(1); - } - pthread_cleanup_pop(1); // will never happen - pthread_exit(NULL); -} -#endif - -void metadata_init(void) { - if (config.metadata_enabled) { - // create the metadata pipe, if necessary - size_t pl = strlen(config.metadata_pipename) + 1; - char *path = malloc(pl + 1); - snprintf(path, pl + 1, "%s", config.metadata_pipename); - mode_t oldumask = umask(000); - if (mkfifo(path, 0666) && errno != EEXIST) - die("Could not create metadata pipe \"%s\".", path); - umask(oldumask); - debug(1, "metadata pipe name is \"%s\".", path); - - // try to open it - fd = try_to_open_pipe_for_writing(path); - // we check that it's not a "real" error. From the "man 2 open" page: - // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO - // open for reading." Which is okay. - if ((fd == -1) && (errno != ENXIO)) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - } - free(path); - - // initialise the metadata queues first, otherwise the might be a race condition - // create a pc_queue for the metadata pipe - pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package), - metadata_queue_size, "pipe"); - - if (named_pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL, - "metadata pipe") != 0) - debug(1, "Failed to create metadata thread!"); - - // create a pc_queue for the metadata_multicast_queue - pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items, - sizeof(metadata_package), metadata_multicast_queue_size, "multicast"); - if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, - NULL, "metadata mcst") != 0) - debug(1, "Failed to create metadata multicast thread!"); - } -#ifdef CONFIG_METADATA_HUB - // create a pc_queue for the metadata hub - pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package), - metadata_hub_queue_size, "hub"); - if (named_pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL, - "metadata hub") != 0) - debug(1, "Failed to create metadata hub thread!"); -#endif -#ifdef CONFIG_MQTT - // create a pc_queue for the MQTT handler - pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package), - metadata_mqtt_queue_size, "mqtt"); - if (named_pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL, - "metadata mqtt") != 0) - debug(1, "Failed to create metadata mqtt thread!"); -#endif - metadata_running = 1; -} - -void metadata_stop(void) { - if (metadata_running) { - debug(2, "metadata_stop called."); -#ifdef CONFIG_MQTT - // debug(2, "metadata stop mqtt thread."); - pthread_cancel(metadata_mqtt_thread); - pthread_join(metadata_mqtt_thread, NULL); - pc_queue_delete(&metadata_mqtt_queue); - // debug(2, "metadata stop mqtt done."); -#endif -#ifdef CONFIG_METADATA_HUB - // debug(2, "metadata stop hub thread."); - pthread_cancel(metadata_hub_thread); - pthread_join(metadata_hub_thread, NULL); - pc_queue_delete(&metadata_hub_queue); - // debug(2, "metadata stop hub done."); -#endif - if (config.metadata_enabled) { - // debug(2, "metadata stop multicast thread."); - if (metadata_multicast_thread) { - pthread_cancel(metadata_multicast_thread); - pthread_join(metadata_multicast_thread, NULL); - pc_queue_delete(&metadata_multicast_queue); - // debug(2, "metadata stop multicast done."); - } - if (metadata_thread) { - // debug(2, "metadata stop metadata_thread thread."); - pthread_cancel(metadata_thread); - pthread_join(metadata_thread, NULL); - pc_queue_delete(&metadata_queue); - // debug(2, "metadata_stop finished successfully."); - } - } - } -} - -int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code, - const char *data, const uint32_t length, rtsp_message *carrier, - int block) { - - // clang-format off - // parameters: - // type, - // code, - // pointer to data or NULL, - // length of data or NULL, - // the rtsp_message or NULL, - - // the rtsp_message is sent for 'core' messages, because it contains the data - // and must not be freed until the data has been read. - // So, it is passed to send_metadata to be retained, sent to the thread where metadata - // is processed and released (and probably freed). - - // The rtsp_message is also sent for certain non-'core' messages. - - // The reading of the parameters is a bit complex: - // If the rtsp_message field is non-null, then it represents an rtsp_message - // and the data pointer is assumed to point to something within it. - // The reference counter of the rtsp_message is incremented here and - // should be decremented by the metadata handler when finished. - // If the reference count reduces to zero, the message will be freed. - - // If the rtsp_message is NULL, then if the pointer is non-null then the data it - // points to, of the length specified, is memcpy'd and passed to the metadata - // handler. The handler should free it when done. - - // If the rtsp_message is NULL and the pointer is also NULL, nothing further - // is done. - // clang-format on - - metadata_package pack; - pack.type = type; - pack.code = code; - pack.length = length; - pack.carrier = carrier; - pack.data = (char *)data; - if (pack.carrier) { - msg_retain(pack.carrier); - } else { - if (data) - pack.data = memdup(data, length); // only if it's not a null - } - - // debug(1, "send_metadata_to_queue %x/%x", type, code); - int rc = pc_queue_add_item(queue, &pack, block); - if (rc != 0) { - if (pack.carrier) { - if (rc == EWOULDBLOCK) - debug(2, - "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR ", " - "length %u, message %d.", - queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length, - pack.carrier->index_number); - msg_free(&pack.carrier); - } else { - if (rc == EWOULDBLOCK) - debug( - 2, - "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR ", length %u.", - queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length); - if (pack.data) - free(pack.data); - } - } - return rc; -} - -int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length, - rtsp_message *carrier, int block) { - int rc = 0; - if (config.metadata_enabled) { - rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block); - rc = - send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block); - } - -#ifdef CONFIG_METADATA_HUB - rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block); -#endif - -#ifdef CONFIG_MQTT - rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block); -#endif - - return rc; -} static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req, @@ -4301,7 +3317,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m } else #endif if (!strncmp(ct, "text/parameters", 15)) { - // debug(2, "received parameters in SET_PARAMETER request."); + debug(3, "received parameters in SET_PARAMETER request."); handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress } else { debug(1, "Connection %d: received unknown Content-Type \"%s\" in SET_PARAMETER request.", @@ -4404,6 +3420,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag debug(2, "An uncompressed PCM stream has been detected."); conn->stream.type = ast_uncompressed; conn->frames_per_packet = 352; // number of audio frames per packet. + debug(2, "Set conn->input_rate: 44100"); conn->input_rate = 44100; conn->input_num_channels = 2; conn->input_bit_depth = 16; @@ -4510,6 +3527,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag // done later). conn->frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. conn->input_rate = conn->stream.fmtp[11]; + debug(2, "Set conn->input_rate: %u.", conn->input_rate); conn->input_num_channels = conn->stream.fmtp[7]; conn->input_bit_depth = conn->stream.fmtp[3]; conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); @@ -4540,7 +3558,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag debug(2, "AirPlay version %d detected.", conn->AirPlayVersion); } } - + conn->input_format_is_valid = 1; #ifdef CONFIG_METADATA send_metadata('ssnc', 'snua', hdr, strlen(hdr), req, 1); #endif @@ -4677,7 +3695,7 @@ static char *make_nonce(void) { return base64_enc(random, 8); // returns a pointer to malloc'ed memory } -static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { +static int rtsp_classic_airplay_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { if (!config.password) return 0; @@ -5011,6 +4029,12 @@ void rtsp_conversation_thread_cleanup_function(void *arg) { rc = pthread_mutex_destroy(&conn->flush_mutex); if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); +#ifdef CONFIG_AIRPLAY_2 + rc = pthread_mutex_destroy(&conn->event_sender_mutex); + if (rc) + debug(1, "Connection %d: error %d destroying event_sender_mutex.", conn->connection_number, + rc); +#endif debug(3, "Connection %d: Closed.", conn->connection_number); conn->running = 0; // for the garbage collector release_play_lock(conn); @@ -5047,6 +4071,12 @@ static void *rtsp_conversation_thread_func(void *pconn) { die("Connection %d: error %d initialising player_create_delete_mutex.", conn->connection_number, rc); + #ifdef CONFIG_AIRPLAY_2 + rc = pthread_mutex_init(&conn->event_sender_mutex, NULL); + if (rc) + die("Connection %d: error %d initialising event_sender_mutex.", conn->connection_number, rc); + #endif + // nothing before this is cancellable pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); @@ -5106,8 +4136,18 @@ static void *rtsp_conversation_thread_func(void *pconn) { msg_add_header(resp, "Server", "AirTunes/105.1"); #endif - if ((conn->authorized == 1) || (rtsp_auth(&conn->auth_nonce, req, resp)) == 0) { - conn->authorized = 1; // it must have been authorized or didn't need a password + + +// we are only concerned with classic_airplay_authorized if we are running in classic AirPlay mode +// either because the build is for classic AirPlay only or, if the build is for AirPlay 2, +// we happen to be running in classic AirPlay compatibility mode + + if ((conn->authorized == 1) || +#ifdef CONFIG_AIRPLAY_2 + (conn->airplay_type != ap_1) || +#endif + (rtsp_classic_airplay_auth(&conn->auth_nonce, req, resp) == 0)) { + conn->authorized = 1; // it must have been classic airplay and authorized or didn't need a password, or not classic AirPlay at all struct method_handler *mh; int method_selected = 0; for (mh = method_handlers; mh->method; mh++) { @@ -5167,7 +4207,8 @@ static void *rtsp_conversation_thread_func(void *pconn) { so_linger.l_linger = 0; err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); if (err) - debug(1, "Could not set the RTSP socket to abort due to a write error on closing."); + debug(1, "Connection %d: Could not set the RTSP socket to abort due to a write error on closing.", + conn->connection_number); conn->stop = 1; // if (debuglev >= 1) // debuglev = 3; // see what happens next diff --git a/rtsp.h b/rtsp.h index 39a40dc8..b873425f 100644 --- a/rtsp.h +++ b/rtsp.h @@ -3,6 +3,27 @@ #include "player.h" +typedef struct { + int index_number; + uint32_t referenceCount; // we might start using this... + unsigned int nheaders; + char *name[16]; + char *value[16]; + + uint32_t contentlength; + char *content; + + // for requests + char method[16]; + char path[256]; + + // for responses + int respcode; +} rtsp_message; + +void msg_retain(rtsp_message *msg); +void msg_free(rtsp_message **msgh); + extern pthread_rwlock_t principal_conn_lock; extern rtsp_conn_info *principal_conn; extern rtsp_conn_info **conns; @@ -27,19 +48,6 @@ play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption); // this will release the play lock only if the conn has it or if the conn is NULL void release_play_lock(rtsp_conn_info *conn); -// initialise and completely delete the metadata stuff - -void metadata_init(void); -void metadata_stop(void); - -// sends metadata out to the metadata pipe, if enabled. -// It is sent with the type 'ssnc' the given code, data and length -// The handler at the other end must know what to do with the data -// e.g. if it's malloced, to free it, etc. -// nothing is done automatically - -int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length, - const int block); #ifdef CONFIG_AIRPLAY_2 ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 88ee0e72..35f1d7a5 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -14,7 +14,7 @@ general = // %v for the version number, e.g. 3.0 and // %V for the full version string, e.g. 3.3-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc // Overall length can not exceed 50 characters. Example: "Shairport Sync %v on %H". -// password = "secret"; // (AirPlay 1 only) leave this commented out if you don't want to require a password +// password = "secret"; // leave this commented out if you don't want to require a password // The interpolation setting below controls how Shairport Sync adds or removes frames of audio to keep in sync. // "auto" (default) measures the processor's floating point speed and chooses "soxr" if available and it is fast enough. Otherwise, "vernier" is selected. // "soxr" uses the SoX library to recode a packet of frames to a new packet containing more or fewer frames. This needs a processor with fast floating point capability. diff --git a/shairport.c b/shairport.c index 448f17e6..695c1f79 100644 --- a/shairport.c +++ b/shairport.c @@ -54,6 +54,9 @@ #include #include #include +#include "utilities/generate_random_uuid.h" +#include "utilities/generate_device_uuid.h" + #endif #ifdef CONFIG_MBEDTLS @@ -84,8 +87,12 @@ #include "dacp.h" #endif +#if defined(CONFIG_METADATA) +#include "metadata/core.h" +#endif + #if defined(CONFIG_METADATA_HUB) -#include "metadata_hub.h" +#include "metadata/hub.h" #endif #ifdef CONFIG_DBUS_INTERFACE @@ -195,13 +202,12 @@ int has_fltp_capable_aac_decoder(void) { #endif #ifdef CONFIG_SOXR -pthread_t soxr_time_check_thread; -int soxr_time_check_thread_started = 0; +pthread_t *soxr_time_check_thread = NULL; void *soxr_time_check(__attribute__((unused)) void *arg) { // this just checks how long it takes to process adding and subtracing a frame // from a buffer at 44100 - // #include - // debug(1, "soxr_time_check PID %d", syscall(SYS_gettid)); + // #include + // debug(1, "soxr_time_check PID %ld", syscall(SYS_gettid)); const int buffer_length = 352; int32_t inbuffer[buffer_length * 2]; @@ -221,7 +227,7 @@ void *soxr_time_check(__attribute__((unused)) void *arg) { (uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be // able to cancel it, do _don't even try_! while (get_absolute_time_in_ns() < loop_until_time) { - + pthread_testcancel(); number_of_iterations++; for (i = 0; i < buffer_length; i++) { double w = sin(i * (frequency + number_of_iterations * 2) * 2 * M_PI / 44100); @@ -230,6 +236,9 @@ void *soxr_time_check(__attribute__((unused)) void *arg) { inbuffer[i * 2 + 1] = wint; } + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + soxr_io_spec_t io_spec; io_spec.itype = SOXR_INT32_I; io_spec.otype = SOXR_INT32_I; @@ -256,6 +265,8 @@ void *soxr_time_check(__attribute__((unused)) void *arg) { outbuffer, buffer_length - 1, &odone, // Output. &io_spec, // Input, output and transfer spec. NULL, NULL); // Default configuration. + + pthread_setcancelstate(oldState, NULL); } int64_t soxr_execution_time = @@ -271,8 +282,8 @@ void *soxr_time_check(__attribute__((unused)) void *arg) { debug(1, "No soxr-timing iterations performed, so \"vernier\" iteration will be used."); config.soxr_delay_index = 0; // used as a flag } - debug(2, "soxr_delay: %d nanoseconds, soxr_delay_threshold: %d milliseconds.", - config.soxr_delay_index, config.soxr_delay_threshold / 1000000); + debug(2, "soxr_delay maximum permissible: %d milliseconds, actual: %f milliseconds.", + config.soxr_delay_threshold / 1000000, 1E-6 * config.soxr_delay_index); if ((config.packet_stuffing == ST_soxr) && (config.soxr_delay_index > config.soxr_delay_threshold)) inform("Note: this device may be too slow for \"soxr\" interpolation. Consider choosing the " @@ -337,6 +348,7 @@ void usage(char *progname) { printf(" -t, --timeout=SECONDS Go back to idle mode from play mode after a break in communications of this many seconds (default 60). Set to 0 never to exit play mode.\n"); printf(" --tolerance=TOLERANCE [Deprecated] Allow a synchronization error of TOLERANCE frames (default 88) before trying to correct it.\n"); printf(" --logOutputLevel Log the output level setting -- a debugging option, useful for determining the optimum maximum volume.\n"); + #ifdef CONFIG_LIBDAEMON printf(" -d, --daemon Daemonise.\n"); printf(" -j, --justDaemoniseNoPIDFile Daemonise without a PID file.\n"); @@ -347,6 +359,10 @@ void usage(char *progname) { printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. --metadata-pipename=/tmp/%s-metadata.\n", config.appName); printf(" The default is /tmp/%s-metadata.\n", config.appName); printf(" -g, --get-coverart Include cover art in the metadata to be gathered and sent.\n"); +#endif +#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) + printf(" --dbus-default-message-bus=BUS Use the BUS specified for the native D-Bus and MPRIS interfaces.\n"); + printf(" BUS must be \"system\" (default) or \"session\".\n"); #endif printf(" --log-to-syslog Send debug and statistics information through syslog\n"); printf(" If used, this should be the first command line argument.\n"); @@ -535,6 +551,7 @@ int parse_options(int argc, char **argv) { // for unexpected circumstances config.model = strdup("ShairportSync"); + //config.model = strdup("AirPort10,115"); // config.model = strdup("AudioAccessory5,1"); // config.srcvers = strdup(PACKAGE_VERSION); @@ -553,6 +570,9 @@ int parse_options(int argc, char **argv) { #endif config.firmware_version = strdup(PACKAGE_VERSION); + free(config.firmware_version); + config.firmware_version = strdup("5.2"); + #ifdef CONFIG_METADATA /* Get the metadata setting. */ config.metadata_enabled = 1; // if metadata support is included, then enable it by default @@ -581,83 +601,6 @@ int parse_options(int argc, char **argv) { // not made active again (not used) #endif -#ifdef CONFIG_AIRPLAY_2 - // the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32 - // bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as - // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist: - // 496155702020608 this setting here is the source of both the plist features response and the - // mDNS string. - // note: 0x300401F4A00 works but with weird delays and stuff - // config.airplay_features = 0x1C340405FCA00; - uint64_t mask = - ((uint64_t)1 << 17) | ((uint64_t)1 << 16) | ((uint64_t)1 << 15) | ((uint64_t)1 << 50); - config.airplay_features = - // 0x114BD04A5FCA00 & (~mask); - 0x1C340405D4A00 & (~mask); // APX + Authentication4 (b14) with no metadata (see below) - - /* - config.airplay_features |= (uint64_t)1 << 21; // Audio Format 4 - - config.airplay_features |= (uint64_t)1 << 25; // Unknown - config.airplay_features |= (uint64_t)1 << 36; // Unknown - config.airplay_features |= (uint64_t)1 << 39; // Unknown - config.airplay_features |= (uint64_t)1 << 43; // Supports System Pairing - config.airplay_features |= (uint64_t)1 << 47; // Unknown - - // 0xB - config.airplay_features |= (uint64_t)1 << 63; // Unknown - config.airplay_features |= (uint64_t)1 << 61; // Unknown - config.airplay_features |= (uint64_t)1 << 60; // Unknown - // ...0xC - // // config.airplay_features |= (uint64_t)1 << 59; // Unknown - config.airplay_features |= (uint64_t)1 << 58; // Unknown - - // ...0x3 - config.airplay_features |= (uint64_t)1 << 53; // Unknown - config.airplay_features |= (uint64_t)1 << 52; // Unknown - */ - - // config.airplay_features |= ((uint64_t)1 << 58) | ((uint64_t)1 << 60) | ((uint64_t)1 << 58); - - // 60 seems to interfere with disconnecting from a group - - // Advertised with mDNS and returned with GET /info, see - // https://openairplay.github.io/airplay-spec/status_flags.html 0x4: Audio cable attached, no PIN - // required (transient pairing), 0x204: Audio cable attached, OneTimePairingRequired 0x604: Audio - // cable attached, OneTimePairingRequired, device was setup for Homekit access control - config.airplay_statusflags = 0; - config.airplay_statusflags |= 1 << 2; // Audio cable is attached - // config.airplay_statusflags |= 1 << 10; // DeviceWasSetupForHKAccessControl - // config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay - // config.airplay_statusflags |= 1 << 19; // Unknown. Seems to control whether individual volume - // controls are shown and whether the SPS devices shows when its active. - - // Set to NULL to work with transient pairing - config.airplay_pin = NULL; - - // use the MAC address placed in config.hw_addr to generate the default airplay_device_id - uint64_t temporary_airplay_id = nctoh64(config.hw_addr); - temporary_airplay_id = - temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8. - - // now generate a UUID - // from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c - // with thanks - uuid_t binuuid; - uuid_generate_random(binuuid); - - char *uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end - // Produces a UUID string at uuid consisting of lower-case letters - uuid_unparse_lower(binuuid, uuid); - config.airplay_pi = uuid; - - char *pgid_uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end - uuid_generate_random(binuuid); - uuid_unparse_lower(binuuid, pgid_uuid); - config.airplay_pgid = pgid_uuid; - -#endif - // config_setting_t *setting; const char *str = NULL; int value = 0; @@ -665,6 +608,11 @@ int parse_options(int argc, char **argv) { // debug(1, "Looking for the configuration file \"%s\".", config.configfile); + // use the MAC address placed in config.hw_addr to generate the default airplay_device_id + uint64_t temporary_airplay_id = nctoh64(config.hw_addr); + temporary_airplay_id = + temporary_airplay_id >> 16; // we only use the first 6 bytes but have imported 8. + config_init(&config_file_stuff); config_file_real_path = realpath(config.configfile, NULL); @@ -1465,7 +1413,6 @@ int parse_options(int argc, char **argv) { "avahi enabled, or disable remote control."); } #endif -#endif #ifdef CONFIG_AIRPLAY_2 long long aid; @@ -1481,6 +1428,8 @@ int parse_options(int argc, char **argv) { } #endif +#endif + } // now, do the command line options again, but this time do them fully -- it's a unix convention @@ -1600,7 +1549,10 @@ int parse_options(int argc, char **argv) { // a uuid_t and an md5 hash are both 128 bits, 16 bytes uuid_t result; memset(result, 0, sizeof(result)); - memcpy(result, config.ap1_prefix, sizeof(result)); + if (sizeof(config.ap1_prefix) < sizeof(result)) + memcpy(result, config.ap1_prefix, sizeof(config.ap1_prefix)); + else + memcpy(result, config.ap1_prefix, sizeof(result)); // OpenSSL is mandatory for AirPlay 2 anyway #ifdef CONFIG_OPENSSL @@ -1627,8 +1579,8 @@ int parse_options(int argc, char **argv) { // Produces a UUID string at uuid consisting of lower-case letters uuid_unparse_lower(result, psi_uuid); config.airplay_psi = psi_uuid; + debug(3, "size of pk is %zu.", sizeof(config.airplay_pk)); - pair_public_key_get(PAIR_SERVER_HOMEKIT, config.airplay_pk, config.airplay_device_id); char buf[128]; char *ptr = buf; @@ -1637,18 +1589,38 @@ int parse_options(int argc, char **argv) { ptr += sprintf(ptr, "%02x", config.airplay_pk[pk_index]); *ptr = '\0'; config.pk_string = strdup(buf); + + // the features code is a 64-bit number, but in the mDNS advertisement, the least significant 32 + // bit are given first for example, if the features number is 0x1C340405F4A00, it will be given as + // features=0x405F4A00,0x1C340 in the mDNS string, and in a signed decimal number in the plist: + // 496155702020608 this setting here is the source of both the plist features response and the + // mDNS string. + + config.airplay_features = + 0x00018340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no AP1 artwork (b15) + // 0x0001C340405C4A00; // no AP2 metadata (b50), no AP1 text (b17), no AP1 progress (b16), no AP1 artwork (b15) + // 0x0001C340445D0A00; + // config.airplay_features |= (1 << 26); // 0x0x4000000 + + // features=0x0001C340445D0A00 -- AirPort Express #ifdef CONFIG_METADATA // If we are asking for metadata, turn on the relevant bits - if (config.metadata_enabled != 0) { - config.airplay_features |= (uint64_t)1 << 16; // progress, 17 is text, 50 is in a binary plist - config.airplay_features |= (uint64_t)1 << 17; // text, 50 is in a binary plist - // config.airplay_features |= - // (uint64_t)1 << 50; // binary plist + + // If bit 50 is set, metadata is sent via plists in POST /command payloads. + // The data consists of textual information about what is playing and cover art + // It does not seem possible to turn off the cover art. - // If we are asking for artwork, turn on the relevant bit - if (config.get_coverart) - config.airplay_features |= (uint64_t)1 << 15; // artwork + // While bit 50 is set, no data comes through the "classic" way. That is + // no progress, text or picture data comes through in the way that + // it comes through in Classic AirPlay (aka AirPlay 1). + + // Although it is less flexible about what metadata is sent, bit 50 being set + // provides much more information, so should be the default for AirPlay 2 + + if (config.metadata_enabled != 0) { + config.airplay_features |= (uint64_t)1 << 50; // metadata in a binary plist, including more state information + // config.airplay_features |= ((uint64_t)1 << 15) | ((uint64_t)1 << 16) | ((uint64_t)1 << 17); // older metadata flags artwork, progress and text respectively } #endif @@ -1669,6 +1641,23 @@ int parse_options(int argc, char **argv) { if (padding) *padding = 0; debug(2, "airplay_fex is \"%s\"", config.airplay_fex); + + // now the status flags + // Advertised with mDNS and returned with GET /info, see + // https://openairplay.github.io/airplay-spec/status_flags.html + + config.airplay_statusflags = 0; + config.airplay_statusflags |= 1 << 2; // Audio cable is attached + if (config.password != NULL) { + config.airplay_statusflags |= 1 << 7; // Password required + } + // config.airplay_statusflags |= 1 << 10; // DeviceWasSetupForHKAccessControl + // config.airplay_statusflags |= 1 << 11; // DeviceSupportsRelay + // config.airplay_statusflags |= 1 << 19; // Unknown. Seems to control whether individual volume + // controls are shown and whether the SPS devices shows when its active. + + config.airplay_pi = generate_device_uuid(config.airplay_device_id); + config.airplay_pgid = generate_random_uuid(); #endif #ifdef CONFIG_LIBDAEMON @@ -1856,7 +1845,6 @@ void exit_function() { (config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not // actually daemonised at all #endif - debug(2, "exit function called..."); /* Actually, there is no terminate_mqtt() function. #ifdef CONFIG_MQTT @@ -1902,12 +1890,16 @@ void exit_function() { } #ifdef CONFIG_SOXR - // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down - if (soxr_time_check_thread_started != 0) { - debug(2, "Waiting for SoXr timecheck to terminate..."); - pthread_join(soxr_time_check_thread, NULL); - soxr_time_check_thread_started = 0; - debug(2, "Waiting for SoXr timecheck to terminate done"); + { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + if (soxr_time_check_thread != NULL) { + pthread_cancel(*soxr_time_check_thread); + pthread_join(*soxr_time_check_thread, NULL); + free(soxr_time_check_thread); + soxr_time_check_thread = NULL; + } + pthread_setcancelstate(oldState, NULL); } #endif @@ -1948,8 +1940,6 @@ void exit_function() { free(config.nqptp_shared_memory_interface_name); if (config.airplay_device_id) free(config.airplay_device_id); - if (config.airplay_pin) - free(config.airplay_pin); if (config.airplay_pi) free(config.airplay_pi); if (config.airplay_pgid) @@ -1994,7 +1984,7 @@ void exit_function() { #else mdns_unregister(); // once the dacp handler is done and all player threrads are done it should // be safe - debug(1, "normal exit"); + debug(2, "normal exit"); #endif } else { debug(1, "emergency exit"); @@ -2346,6 +2336,7 @@ int main(int argc, char **argv) { openlog(NULL, 0, LOG_DAEMON); #endif type_of_exit_cleanup = TOE_normal; // what kind of exit cleanup needed + debug(1, "adding the exit function"); atexit(exit_function); // get a device id -- the first non-local MAC address @@ -2957,7 +2948,9 @@ int main(int argc, char **argv) { config.output_channel_map[i] = strdup(channel_id); debug(2, "output channel %d is \"%s\".", i, config.output_channel_map[i]); } else { - warn("channel \"%s\" is not recognised -- output channel %d will be silent.", + + + warn("during channel mapping, \"%s\" was not recognised as a channel name -- as a result, output channel %d will be silent.", channel_id, i); config.output_channel_map[i] = strdup("--"); } @@ -3153,8 +3146,20 @@ int main(int argc, char **argv) { debug(option_print_level, "loudness reference level is %f", config.loudness_reference_volume_db); #ifdef CONFIG_SOXR - named_pthread_create(&soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker"); - soxr_time_check_thread_started = 1; + + { + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + soxr_time_check_thread = malloc(sizeof(pthread_t)); + if (soxr_time_check_thread != NULL) { + named_pthread_create(soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker"); + } else { + debug(1,"couldn't get memory to start the soxr_checker"); + } + pthread_setcancelstate(oldState, NULL); // make this un-cancellable + + } + #endif #ifdef CONFIG_FFMPEG @@ -3180,7 +3185,7 @@ int main(int argc, char **argv) { if (idx != 0) strncat(channel_map, " ", sizeof(channel_map) - 1); strncat(channel_map, chName, sizeof(channel_map) - 1 - strlen(channel_map)); - debug(3, "Channel %d: \"%s\"", idx, chName); + debug(4, "Channel %d: \"%s\"", idx, chName); } else { debug(1, "Insufficient space for the name of channel %d.", idx); } diff --git a/utilities/debug.c b/utilities/debug.c index ea3a9365..e99cf80c 100644 --- a/utilities/debug.c +++ b/utilities/debug.c @@ -23,6 +23,7 @@ SOFTWARE. */ #include "debug.h" +#include // for isprint() #include #include #include @@ -64,13 +65,9 @@ void debug_init(int level, int show_elapsed_time, int show_relative_time, int sh debugger_show_file_and_line = show_file_and_line; } -int debug_level() { - return debuglev; -}; +int debug_level() { return debuglev; }; -void set_debug_level(int level) { - debuglev = level; -} +void set_debug_level(int level) { debuglev = level; } void increase_debug_level() { if (debuglev < 3) @@ -82,28 +79,15 @@ void decrease_debug_level() { debuglev--; } -int get_show_elapsed_time() { - return debugger_show_elapsed_time; - -} -void set_show_elapsed_time(int setting) { - debugger_show_elapsed_time = setting; -} -int get_show_relative_timel() { - return debugger_show_relative_time; -} +int get_show_elapsed_time() { return debugger_show_elapsed_time; } +void set_show_elapsed_time(int setting) { debugger_show_elapsed_time = setting; } +int get_show_relative_timel() { return debugger_show_relative_time; } -void set_show_relative_time(int setting) { - debugger_show_relative_time = setting; -} +void set_show_relative_time(int setting) { debugger_show_relative_time = setting; } -int get_show_file_and_line() { - return debugger_show_file_and_line; -} +int get_show_file_and_line() { return debugger_show_file_and_line; } -void set_show_file_and_line(int setting) { - debugger_show_file_and_line = setting; -} +void set_show_file_and_line(int setting) { debugger_show_file_and_line = setting; } char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss, double tsl, const char *filename, const int linenumber, const char *prefix) { @@ -244,10 +228,94 @@ void _inform(const char *filename, const int linenumber, const char *format, ... pthread_setcancelstate(oldState, NULL); } + +void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *ibuf, + size_t buf_len) { + + if (level > debuglev) + return; + + char *vbuf = (char *)ibuf; // this to stop the compiler complaining about indexing through a void + unsigned int i; + unsigned int hexdump_cols = 16; + + // 0x123456: \n + const size_t buffer_size = 2 + 6 + 2 + hexdump_cols * 3 + hexdump_cols + 1; + char *buf = malloc(buffer_size); + if (buf) { + //char *bufp = buf; + // *buf = '\0'; + + // if (msg) + // printf("%s", msg); + + #define ADDR_PREFIX_LEN 10 // "0x000000: " + + // each complete row + for (i = 0; i < (buf_len / hexdump_cols); i++) { + char *bufp = buf; + *buf = '\0'; + snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06x ", (i * hexdump_cols) & 0xFFFFFF); + bufp += ADDR_PREFIX_LEN; + + unsigned int j; + for (j = 0; j < hexdump_cols; j++) { + snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[i * hexdump_cols + j]); + bufp += strlen("12 "); + } + + for (j = 0; j < hexdump_cols; j++) { + if (isprint(vbuf[i * hexdump_cols + j])) { + *bufp = 0xFF & vbuf[i * hexdump_cols + j]; + } else { + *bufp = '.'; + } + bufp++; + } + *bufp = '\0'; + _debug(thefilename, linenumber, level, "%s", buf); + } + + size_t remaining = buf_len % hexdump_cols; + if (remaining != 0) { + size_t starting_offset = buf_len - remaining; + char *bufp = buf; + *buf = '\0'; + snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06zx ", starting_offset & 0xFFFFFF); + bufp += ADDR_PREFIX_LEN; + + unsigned int j; + for (j = 0; j < hexdump_cols; j++) { + if (j < remaining) { + snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[starting_offset + j]); + } else { + snprintf(bufp, strlen(" ") + 1, " "); // fille with blanks + } + bufp += strlen("12 "); + } + + for (j = 0; j < remaining; j++) { + if (isprint(vbuf[i * hexdump_cols + j])) { + *bufp = 0xFF & vbuf[starting_offset + j]; + } else { + *bufp = '.'; + } + bufp++; + } + *bufp = '\0'; + _debug(thefilename, linenumber, level, "%s", buf); + } + free(buf); + } +} + + +/* void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf, size_t buf_len) { if (level > debuglev) return; + new_debug_print_buffer(thefilename, linenumber, level, vbuf, buf_len); char *buf = (char *)vbuf; char *obf = malloc(buf_len * 4 + 1); // to be on the safe side -- 4 characters on average for each byte @@ -274,4 +342,5 @@ void _debug_print_buffer(const char *thefilename, const int linenumber, int leve _debug(thefilename, linenumber, level, "%s", obf); free(obf); } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/utilities/generate_device_uuid.c b/utilities/generate_device_uuid.c new file mode 100644 index 00000000..51dcef4e --- /dev/null +++ b/utilities/generate_device_uuid.c @@ -0,0 +1,48 @@ +/* + * Generate a device-related UUID. This file is part of Shairport Sync + * Copyright (c) Mike Brady 2026 + + * Modifications, including those associated with audio synchronization, multithreading and + * metadata handling copyright (c) Mike Brady 2014--2025 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "generate_device_uuid.h" +#include "definitions.h" + +// user is responsible for deallocating returned string +char *generate_device_uuid(const char *device_id) { + uuid_t namespace_uuid; + uuid_t derived_uuid; + + uuid_parse(SHAIRPORT_SYNC_DEVICE_NAMESPACE, namespace_uuid); + uuid_generate_sha1(derived_uuid, namespace_uuid, device_id, strlen(device_id)); + + char *uuid = malloc(UUID_STR_LEN + 1); + uuid_unparse_lower(derived_uuid, uuid); + return uuid; +} \ No newline at end of file diff --git a/utilities/generate_device_uuid.h b/utilities/generate_device_uuid.h new file mode 100644 index 00000000..96a26952 --- /dev/null +++ b/utilities/generate_device_uuid.h @@ -0,0 +1,3 @@ +#pragma once + +char *generate_device_uuid(const char *device_id); \ No newline at end of file diff --git a/utilities/generate_random_uuid.c b/utilities/generate_random_uuid.c new file mode 100644 index 00000000..848345ff --- /dev/null +++ b/utilities/generate_random_uuid.c @@ -0,0 +1,47 @@ +/* + * Generate a random UUID. This file is part of Shairport Sync + * Copyright (c) Mike Brady 2026 + + * Modifications, including those associated with audio synchronization, multithreading and + * metadata handling copyright (c) Mike Brady 2014--2025 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +#include "generate_random_uuid.h" + +// user is responsible for deallocating returned string +char *generate_random_uuid() { + // generate a UUID + // from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c + // with thanks + uuid_t binuuid; + uuid_generate_random(binuuid); + + char *uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end + // Produces a UUID string at uuid consisting of lower-case letters + uuid_unparse_lower(binuuid, uuid); + return uuid; +} \ No newline at end of file diff --git a/utilities/generate_random_uuid.h b/utilities/generate_random_uuid.h new file mode 100644 index 00000000..80d47a25 --- /dev/null +++ b/utilities/generate_random_uuid.h @@ -0,0 +1,6 @@ +#ifndef _GENERATE_RANDOM_UUID_H +#define _GENERATE_RANDOM_UUID_H + +char *generate_random_uuid(); + +#endif // _GENERATE_RANDOM_UUID_H diff --git a/utilities/network_utilities.c b/utilities/network_utilities.c index 63882e4b..edb0741f 100644 --- a/utilities/network_utilities.c +++ b/utilities/network_utilities.c @@ -38,8 +38,8 @@ int eintr_checked_accept(int sockfd, struct sockaddr *addr, char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, - "error %d accept()ing a socketin ap2_event_receiver: \"%s\". (Note: error %d will be ignored.)", - errno, errorstring, EINTR); + "error %d accept()ing a socket %d: \"%s\". (Note: error %d will be ignored.)", + errno, sockfd, errorstring, EINTR); } } while((response == -1) && (errno == EINTR)); diff --git a/utilities/rtsp_message_utilities.c b/utilities/rtsp_message_utilities.c new file mode 100644 index 00000000..21393cd1 --- /dev/null +++ b/utilities/rtsp_message_utilities.c @@ -0,0 +1,265 @@ +#include +#include +#include +#include +#include "rtsp_message_utilities.h" +#include "../rtsp.h" +#include "../common.h" + +#ifdef HAVE_LIBPLIST_GE_2_3_0 +#define plist_from_memory(plist_data, length, plist) \ + plist_from_memory((plist_data), (length), (plist), NULL) +#endif + +// every time we want to retain or release a reference count, lock it with this +// if a reference count is read as zero, it means the it's being deallocated. +static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; +static int msg_indexes = 1; + +void msg_retain(rtsp_message *msg) { + int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4); + if (rc) + debug(1, "Error %d locking reference counter lock", rc); + if (msg > (rtsp_message *)0x00010000) { + msg->referenceCount++; + debug(4, "msg_free increment reference counter message %d to %d.", msg->index_number, + msg->referenceCount); + // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); + rc = pthread_mutex_unlock(&reference_counter_lock); + if (rc) + debug(1, "Error %d unlocking reference counter lock", rc); + } else { + debug(1, "invalid rtsp_message pointer 0x%" PRIxPTR " passed to retain", (uintptr_t)msg); + } +} + +rtsp_message *msg_init(void) { + // no thread cancellation points here + int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4); + if (rc) + debug(1, "Error %d locking reference counter lock", rc); + + rtsp_message *msg = malloc(sizeof(rtsp_message)); + if (msg) { + memset(msg, 0, sizeof(rtsp_message)); + msg->referenceCount = 1; // from now on, any access to this must be protected with the lock + msg->index_number = msg_indexes++; + debug(4, "msg_init message %d", msg->index_number); + } else { + die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); + } + // debug(1,"msg_init -- create item %d.", msg->index_number); + + rc = pthread_mutex_unlock(&reference_counter_lock); + if (rc) + debug(1, "Error %d unlocking reference counter lock", rc); + + return msg; +} + +int msg_add_header(rtsp_message *msg, char *name, char *value) { + if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) { + warn("too many headers?!"); + return 1; + } + + msg->name[msg->nheaders] = strdup(name); + msg->value[msg->nheaders] = strdup(value); + msg->nheaders++; + + return 0; +} + +char *msg_get_header(rtsp_message *msg, char *name) { + unsigned int i; + for (i = 0; i < msg->nheaders; i++) + if (!strcasecmp(msg->name[i], name)) + return msg->value[i]; + return NULL; +} + +void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, + rtsp_message *msg) { + unsigned int i; + if (conn != NULL) { + if (msg->respcode != 0) + _debug(filename, linenumber, level, "Connection: %d, Response Code: %d.", conn->connection_number, msg->respcode); + for (i = 0; i < msg->nheaders; i++) { + _debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"", conn->connection_number, msg->name[i], + msg->value[i]); + } + } else { + if (msg->respcode != 0) + _debug(filename, linenumber, level, " Response Code: %d.", msg->respcode); + for (i = 0; i < msg->nheaders; i++) { + _debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i], + msg->value[i]); + } + } +} + +void msg_free(rtsp_message **msgh) { + debug_mutex_lock(&reference_counter_lock, 1000, 0); + if (*msgh > (rtsp_message *)0x00010000) { + rtsp_message *msg = *msgh; + msg->referenceCount--; + if (msg->referenceCount) + debug(4, "msg_free decrement reference counter message %d to %d", msg->index_number, + msg->referenceCount); + if (msg->referenceCount == 0) { + unsigned int i; + for (i = 0; i < msg->nheaders; i++) { + free(msg->name[i]); + free(msg->value[i]); + } + if (msg->content) + free(msg->content); + // debug(1,"msg_free item %d -- free.",msg->index_number); + uintptr_t index = (msg->index_number) & 0xFFFF; + if (index == 0) + index = 0x10000; // ensure it doesn't fold to zero. + *msgh = + (rtsp_message *)(index); // put a version of the index number of the freed message in here + debug(4, "msg_free freed message %d", msg->index_number); + free(msg); + } else { + // debug(1,"msg_free item %d -- decrement reference to + // %d.",msg->index_number,msg->referenceCount); + } + } else if (*msgh != NULL) { + debug(1, + "msg_free: error attempting to free an allocated but already-freed rtsp_message, number " + "%" PRIxPTR ".", + (uintptr_t)*msgh); + } + debug_mutex_unlock(&reference_counter_lock, 0); +} + +int msg_handle_line(rtsp_message **pmsg, char *line) { + rtsp_message *msg = *pmsg; + + if (!msg) { + msg = msg_init(); + *pmsg = msg; + char *sp, *p; + sp = NULL; // this is to quieten a compiler warning + + debug(4, "RTSP/HTTP Message Received: \"%s\".", line); + + p = strtok_r(line, " ", &sp); + if (!p) + goto fail; + strncpy(msg->method, p, sizeof(msg->method) - 1); + + p = strtok_r(NULL, " ", &sp); + if (!p) + goto fail; + strncpy(msg->path, p, sizeof(msg->path) - 1); + + p = strtok_r(NULL, " ", &sp); + if (!p) + goto fail; + if ((strcmp(p, "RTSP/1.0") != 0) && (strcmp(p, "HTTP/1.1") != 0)) { + debug(1, "Problem with Message: \"%s\"", p); + goto fail; + } + + return -1; + } + + if (strlen(line)) { + char *p; + p = strstr(line, ": "); + if (!p) { + warn("bad header: >>%s<<", line); + goto fail; + } + *p = 0; + p += 2; + msg_add_header(msg, line, p); + debug(4, " %s: %s.", line, p); + return -1; + } else { + char *cl = msg_get_header(msg, "Content-Length"); + if (cl) + return atoi(cl); + else + return 0; + } + +fail: + debug(1, "msg_handle_line fail"); + msg_free(pmsg); + *pmsg = NULL; + return 0; +} + +#ifdef CONFIG_AIRPLAY_2 + +int rtsp_message_contains_plist(rtsp_message *message) { + int reply = 0; // assume there is no plist in the message + if ((message->contentlength >= strlen("bplist00")) && + (strstr(message->content, "bplist00") == message->content)) + reply = 1; + return reply; +} + +plist_t plist_from_rtsp_content(rtsp_message *message) { + plist_t the_plist = NULL; + if (rtsp_message_contains_plist(message)) { + plist_from_memory(message->content, message->contentlength, &the_plist); + } + return the_plist; +} + +char *plist_as_xml_text(plist_t the_plist) { + // caller must free the returned character buffer + // convert it to xml format + uint32_t size; + char *plist_out = NULL; + plist_to_xml(the_plist, &plist_out, &size); + + // put it into a NUL-terminated string + char *reply = malloc(size + 1); + if (reply) { + memcpy(reply, plist_out, size); + reply[size] = '\0'; + } + if (plist_out) + free(plist_out); + return reply; +} + +// caller must free the returned character buffer +char *rtsp_plist_content(rtsp_message *message) { + char *reply = NULL; + // first, check if it has binary plist content + if (rtsp_message_contains_plist(message)) + reply = plist_as_xml_text(plist_from_rtsp_content(message)); + return reply; +} + +#endif + +void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt, + rtsp_message *message) { + if (level > debug_level()) + return; + if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list... + _debug(filename, linenumber, level, "%s", prompt); + _debug_print_msg_headers(conn, filename, linenumber, level, message); +#ifdef CONFIG_AIRPLAY_2 + char *plist_content = rtsp_plist_content(message); + if (plist_content) { + _debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--", message->contentlength, plist_content); + free(plist_content); + } else +#endif + { + _debug(filename, linenumber, level, " No Content Plist. Content length: %u.", + message->contentlength); + if (message->contentlength > 0) { + _debug_print_buffer(filename, linenumber, level, message->content, message->contentlength); + } + } +} diff --git a/utilities/rtsp_message_utilities.h b/utilities/rtsp_message_utilities.h new file mode 100644 index 00000000..3a42c031 --- /dev/null +++ b/utilities/rtsp_message_utilities.h @@ -0,0 +1,33 @@ +#pragma once + +#include "player.h" +#include "rtsp.h" + +rtsp_message *msg_init(void); +int msg_handle_line(rtsp_message **pmsg, char *line); +int msg_add_header(rtsp_message *msg, char *name, char *value); +char *msg_get_header(rtsp_message *msg, char *name); + +void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt, + rtsp_message *message); + +#define debug_log_rtsp_message_conn(conn, level, prompt, message) \ + _debug_log_rtsp_message(conn, __FILE__, __LINE__, level, prompt, message) + +#define debug_log_rtsp_message(level, prompt, message) \ + _debug_log_rtsp_message(NULL, __FILE__, __LINE__, level, prompt, message) + + +void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, + rtsp_message *msg); +#define debug_print_msg_headers(level, message) \ + _debug_print_msg_headers(NULL, __FILE__, __LINE__, level, message) + +#define debug_print_msg_headers_conn(level, message) \ + _debug_print_msg_headers(conn, __FILE__, __LINE__, level, message) + + +#ifdef CONFIG_AIRPLAY_2 +int rtsp_message_contains_plist(rtsp_message *message); +plist_t plist_from_rtsp_content(rtsp_message *message); +#endif \ No newline at end of file