Lot of changes here.
1. check_classic_mac_basic.yml — update to latest macOS-latest 2. docker-on-push-tag-or-pr — dependant updates 3. FFTConvolver — fix a compilation warning when the DEBUG flag is turned off 4. Reorganise some source files — e.g. move most of the metadata stuff into a subdirectory 5. Remove unused special-purpose AirPlay 2 RC event handler. 6. Reorganise source files and source code relating to the standard event handler. 7. Check configuration settings a little more carefully to offer more meaningful diagnostics. 8. Add password to AirPlay 2 and reorganise initialisation to set AirPlay 2 flags accordingly. Also, remove redundant airplay_pin. 9. Remove redundant debug facility definitions from common.h 10. Make it possible to omit all the dacp stuff from a build. 11. Make it possible to omit metadata multicast from a build. 12. Add AP2-native metadata by setting bit 50 of features to true. 13. Improve stability when pre-empting an existing play session. 14. Fix a few compiler warnings in the pair_ap library. 15. Ensure that the audio format is known before starting synchronisation. 16. Improve handle_configure to set/clear a new config setting. Doesn’t seem to do anything… 17. Experimentally turn off bit 46 of AirPlay 2 features — seems to stop failing pair-verify attempts.
This commit is contained in:
@@ -9,9 +9,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
runs-on: macos-latest
|
||||||
runs-on: macos-13
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6.0.2
|
- uses: actions/checkout@v6.0.2
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Login to Docker Registry
|
- name: Login to Docker Registry
|
||||||
uses: docker/login-action@v3.7.0
|
uses: docker/login-action@v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||||
username: ${{ secrets.DOCKER_REGISTRY_USER }}
|
username: ${{ secrets.DOCKER_REGISTRY_USER }}
|
||||||
@@ -67,13 +67,13 @@ jobs:
|
|||||||
if: needs.docker-vars.outputs.push_docker_image == 'true'
|
if: needs.docker-vars.outputs.push_docker_image == 'true'
|
||||||
|
|
||||||
- name: Set up QEMU
|
- 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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4.0.0
|
uses: docker/setup-buildx-action@v4.0.0
|
||||||
|
|
||||||
- name: Build and push ${{ matrix.name }}
|
- name: Build and push ${{ matrix.name }}
|
||||||
uses: docker/build-push-action@v7.0.0
|
uses: docker/build-push-action@v7.1.0
|
||||||
env:
|
env:
|
||||||
registry_and_name: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE_NAME }}
|
registry_and_name: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE_NAME }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,7 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Convolver Thread Pool. This file is part of Shairport Sync
|
||||||
|
* Copyright (c) Mike Brady 2026
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "ConvolverThreadPool.h"
|
#include "ConvolverThreadPool.h"
|
||||||
#include "FFTConvolver.h"
|
#include "FFTConvolver.h"
|
||||||
#include "config.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()
|
ConvolverThreadPool::ConvolverThreadPool()
|
||||||
: _convolvers(), _threads(), _taskQueue(), _queueMutex(), _condition(), _completionCV(),
|
: _convolvers(), _threads(), _taskQueue(), _queueMutex(), _condition(), _completionCV(),
|
||||||
_stop(false), _activeTasks(0) {}
|
_stop(false), _activeTasks(0) {}
|
||||||
@@ -105,6 +135,16 @@ void ConvolverThreadPool::waitForAll() {
|
|||||||
_completionCV.wait(lock, [this]() { return _taskQueue.empty() && _activeTasks == 0; });
|
_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) {
|
void ConvolverThreadPool::clearState(size_t convolverId) {
|
||||||
assert(convolverId < _convolvers.size());
|
assert(convolverId < _convolvers.size());
|
||||||
|
|
||||||
@@ -113,6 +153,7 @@ void ConvolverThreadPool::clearState(size_t convolverId) {
|
|||||||
|
|
||||||
// _convolvers[convolverId]->clearState();
|
// _convolvers[convolverId]->clearState();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
void ConvolverThreadPool::clearAllStates() {
|
void ConvolverThreadPool::clearAllStates() {
|
||||||
// Make sure no tasks are running before clearing state
|
// Make sure no tasks are running before clearing state
|
||||||
|
|||||||
@@ -1,4 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Convolver. This file is part of Shairport Sync
|
||||||
|
* Copyright (c) Mike Brady 2026
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "convolver.h"
|
#include "convolver.h"
|
||||||
#include "ConvolverThreadPool.h"
|
#include "ConvolverThreadPool.h"
|
||||||
@@ -22,8 +46,8 @@ void convolver_pool_init(size_t numThreads, size_t numConvolvers) {
|
|||||||
if (!pool.init(numThreads, numConvolvers)) {
|
if (!pool.init(numThreads, numConvolvers)) {
|
||||||
debug(1, "failed to initialize thread pool!");
|
debug(1, "failed to initialize thread pool!");
|
||||||
} else {
|
} else {
|
||||||
debug(1, "thread pool initialized with %u threads and %u convolvers.", numThreads,
|
debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads, numThreads == 1 ? "" : "s",
|
||||||
numConvolvers);
|
numConvolvers, numConvolvers == 1 ? "" : "s");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-4
@@ -11,7 +11,7 @@ homedir = @HOME@
|
|||||||
man_MANS = $(top_srcdir)/man/shairport-sync.1
|
man_MANS = $(top_srcdir)/man/shairport-sync.1
|
||||||
# endif
|
# 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_tinyhttp_a_CFLAGS = -pthread
|
||||||
lib_dbus_interface_a_CFLAGS = -pthread
|
lib_dbus_interface_a_CFLAGS = -pthread
|
||||||
lib_mpris_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
|
# 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
|
if BUILD_FOR_DARWIN
|
||||||
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-deprecated-declarations -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
|
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
|
shairport_sync_SOURCES += mdns_dns_sd.c
|
||||||
endif
|
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
|
if USE_METADATA_HUB
|
||||||
shairport_sync_SOURCES += metadata_hub.c
|
shairport_sync_SOURCES += metadata/hub.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if USE_MQTT
|
if USE_MQTT
|
||||||
@@ -148,7 +160,11 @@ lib_tinyhttp_a_SOURCES = tinyhttp/chunk.c tinyhttp/header.c tinyhttp/http.c
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if USE_AIRPLAY_2
|
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
|
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
|
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
|
noinst_LIBRARIES += lib_pair_ap.a
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Shairport Sync does not support AirPlay video or photo streaming.
|
|||||||
* Next Steps and Advanced Topics are [here](ADVANCED%20TOPICS/README.md).
|
* Next Steps and Advanced Topics are [here](ADVANCED%20TOPICS/README.md).
|
||||||
* Runtime settings are documented [here](scripts/shairport-sync.conf).
|
* Runtime settings are documented [here](scripts/shairport-sync.conf).
|
||||||
* Build configuration options are detailed in [CONFIGURATION FLAGS.md](CONFIGURATION%20FLAGS.md).
|
* 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).
|
* Some advanced topics and developed in [ADVANCED TOPICS](https://github.com/mikebrady/shairport-sync/tree/master/ADVANCED%20TOPICS).
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|||||||
@@ -1,7 +1,49 @@
|
|||||||
|
Version 5.0.3-dev-4-g07ca1891
|
||||||
|
==
|
||||||
|
**Bug Fix**
|
||||||
|
* Make code using `getifaddrs()` uncancellable to prevent a mysterious `Unexpected error 9 on netlink descriptor` error. Thanks to [microfx](https://github.com/microfx) for reporting the [issue](https://github.com/mikebrady/shairport-sync/issues/2184).
|
||||||
|
|
||||||
|
Version 5.0.3-dev
|
||||||
|
==
|
||||||
|
**Small Changes**
|
||||||
|
* Add warnings if string or boolean configuration parameters were present but not strings.
|
||||||
|
* Improve behaviour when there is a large timing uncertainty/granularity and a large audio buffer. e.g. on some virtual machines.
|
||||||
|
* Make the SoxR delay checker thread behave better when being cancelled.
|
||||||
|
* Remove some debug messages, quieten some others, add a few.
|
||||||
|
|
||||||
|
Version 5.0.1+-38-gf784e463
|
||||||
|
==
|
||||||
|
**Build Update**
|
||||||
|
* The Apple ALAC Decoder can no longer be included in an AirPlay 2 build. The reason is that FFmpeg -- used instead -- deals comprehensively with ALAC, AAC, multichannel, mixdown and rate transcoding and is well maintained.
|
||||||
|
|
||||||
|
The Apple ALAC Decoder is known to have security issues and is no longer maintained. This update prevents it from being included in Shairport Sync for AirPlay 2.
|
||||||
|
|
||||||
|
Version 5.0.1+-36-g6729802d
|
||||||
|
==
|
||||||
|
**Update**
|
||||||
|
* The Shairport Sync `systemd` service file has been updated as follows:
|
||||||
|
* It uses POSIX shell parameter expansion functionality to prevent adding leading space in option values.
|
||||||
|
* It adds `nqptp.service` to the `After` and `Requires` options when building for Airplay 2.
|
||||||
|
|
||||||
|
Many thanks to [mikelei8291](https://github.com/mikelei8291) for the [PR](https://github.com/mikebrady/shairport-sync/pull/2174).
|
||||||
|
|
||||||
|
Version 5.0.1+-32-g20a87fef
|
||||||
|
==
|
||||||
|
**Build Bugfix**
|
||||||
|
* Fix a bug at the `.configure...` stage of a build of Classic Shairport Sync whereby the UUID library was incorrectly required if the FFmpeg library was being used. (The wording on a few associated warning messages was also fixed.) Thanks to [phexian](https://github.com/phexian) for the [report](https://github.com/mikebrady/shairport-sync/issues/2177).
|
||||||
|
|
||||||
|
**Dependabot Update**
|
||||||
|
* A Dependabot update to a building tool was added.
|
||||||
|
|
||||||
|
Version 5.0.1+-23-g7fb3506e
|
||||||
|
==
|
||||||
|
**Documentation Update**
|
||||||
|
* Fixed dead link to the online [`man`](https://raw.githack.com/mikebrady/shairport-sync/development/man/shairport-sync.1.xml) page. Thanks to [CornelisJ](https://github.com/CornelisJ) for the [report](https://github.com/mikebrady/shairport-sync/issues/2158).
|
||||||
|
|
||||||
Version 5.0.1+-13-ga14517d7
|
Version 5.0.1+-13-ga14517d7
|
||||||
==
|
==
|
||||||
**Change**
|
**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
|
Version 5.0.1+-11-g07546c35
|
||||||
==
|
==
|
||||||
|
|||||||
+5
-2
@@ -40,7 +40,10 @@
|
|||||||
|
|
||||||
#include "activity_monitor.h"
|
#include "activity_monitor.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "rtsp.h"
|
|
||||||
|
#ifdef CONFIG_METADATA
|
||||||
|
#include "metadata/core.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_DBUS_INTERFACE
|
#ifdef CONFIG_DBUS_INTERFACE
|
||||||
#include "dbus-service.h"
|
#include "dbus-service.h"
|
||||||
@@ -128,7 +131,7 @@ void activity_monitor_signify_activity(int active) {
|
|||||||
pthread_mutex_unlock(&activity_monitor_mutex);
|
pthread_mutex_unlock(&activity_monitor_mutex);
|
||||||
}
|
}
|
||||||
// lock the mutex again to send a signal
|
// 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_cond_signal(&activity_monitor_cv);
|
||||||
pthread_cleanup_pop(1); // release the mutex
|
pthread_cleanup_pop(1); // release the mutex
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,6 +235,7 @@ void *rtp_buffered_audio_processor(void *arg) {
|
|||||||
uint16_t data_len;
|
uint16_t data_len;
|
||||||
// here we read from the buffer that our thread has been reading
|
// here we read from the buffer that our thread has been reading
|
||||||
|
|
||||||
|
// debug(1,"read a block");
|
||||||
size_t bytes_remaining_in_buffer;
|
size_t bytes_remaining_in_buffer;
|
||||||
nread =
|
nread =
|
||||||
read_sized_block(buffered_audio, &data_len, sizeof(data_len), &bytes_remaining_in_buffer);
|
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
|
// get the block itself
|
||||||
// debug(1,"buffered audio packet of size %u detected.", data_len - 2);
|
// 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);
|
nread = read_sized_block(buffered_audio, packet, data_len - 2, &bytes_remaining_in_buffer);
|
||||||
|
// debug(1,"block read");
|
||||||
|
|
||||||
// diagnostic
|
// diagnostic
|
||||||
if ((conn->ap2_audio_buffer_minimum_size < 0) ||
|
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
|
// to decode it and pass it to the player
|
||||||
if (new_audio_block_needed == 0) {
|
if (new_audio_block_needed == 0) {
|
||||||
// is there space in the player thread's buffer system?
|
// 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,
|
// debug(1,"player buffer size and occupancy: %u and %u", player_buffer_size,
|
||||||
// player_buffer_occupancy);
|
// 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.
|
// 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...
|
// 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;
|
uint64_t buffer_should_be_time;
|
||||||
|
|
||||||
int have_valid_time = (frame_to_local_time(timestamp, &buffer_should_be_time, conn) == 0);
|
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...
|
// 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();
|
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) &&
|
// 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",
|
||||||
(lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))) {
|
// 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
|
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);
|
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;
|
very_early_packets_signalled = 1;
|
||||||
}
|
}
|
||||||
|
usleep(((1000000 * conn->frames_per_packet) / conn->input_rate) * 2); // wait for approximately the length of two packets
|
||||||
usleep(20000); // wait for a while
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* AirPlay 2 Event Port Message Handler. This file is part of Shairport Sync
|
||||||
|
* Copyright (c) Mike Brady 2025
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ap2_event_message_handler.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "rtsp.h"
|
||||||
|
#include "utilities/structured_buffer.h"
|
||||||
|
#include "utilities/generate_random_uuid.h"
|
||||||
|
|
||||||
|
|
||||||
|
void decodeAndLogPlist(plist_t plist_to_log) {
|
||||||
|
if (plist_to_log != NULL) {
|
||||||
|
char *plist_as_string = plist_as_xml_text(plist_to_log);
|
||||||
|
if (plist_as_string != NULL) {
|
||||||
|
debug(3, "--\n%s\n--\n", plist_as_string);
|
||||||
|
free(plist_as_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// will return -1 if there is an error or port is not open, 0 if the port was closed and a positive
|
||||||
|
// number if okay
|
||||||
|
ssize_t ap2_event_port_send_message(rtsp_conn_info *conn, char *data, size_t data_length) {
|
||||||
|
ssize_t result = -1; // assume a problem
|
||||||
|
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
|
||||||
|
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
|
||||||
|
if (conn->event_channel_fd != 0) {
|
||||||
|
result = write_encrypted(conn->event_channel_fd, &conn->ap2_pairing_context.event_cipher_bundle,
|
||||||
|
data, data_length);
|
||||||
|
if ((result != -1) && ((size_t)result == data_length)) {
|
||||||
|
debug(3, "Connection %d: Packet of %zu bytes successfully written on the Event Port.",
|
||||||
|
conn->connection_number, result);
|
||||||
|
uint8_t packet[4096];
|
||||||
|
result =
|
||||||
|
read_encrypted(conn->event_channel_fd, &conn->ap2_pairing_context.event_cipher_bundle,
|
||||||
|
packet, sizeof(packet));
|
||||||
|
debug(3, "Connection %d: Packet of %zu bytes successfully read on the Event Port.",
|
||||||
|
conn->connection_number, result);
|
||||||
|
if (result > 0) {
|
||||||
|
packet[result] = '\0';
|
||||||
|
debug(3, "Connection %d: Packet Received on Event Port with contents: \n--\n%s\n--\n",
|
||||||
|
conn->connection_number, packet);
|
||||||
|
} else {
|
||||||
|
debug(2, "Connection %d: Event Port connection closed by client", conn->connection_number);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = -1; // this covers a situation where the result is positive but not the same as the
|
||||||
|
// data_length
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug(1, "Connection %d: attempt to send a command to the event port over a closed socket",
|
||||||
|
conn->connection_number);
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1); // unlock the mutex
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t ap2_event_port_post_command(rtsp_conn_info *conn, plist_t command) {
|
||||||
|
ssize_t result = 0;
|
||||||
|
decodeAndLogPlist(command);
|
||||||
|
structured_buffer *sbuf = sbuf_new(4096);
|
||||||
|
if (sbuf != NULL) {
|
||||||
|
pthread_cleanup_push(sbuf_cleanup, sbuf);
|
||||||
|
char *plistString = NULL;
|
||||||
|
uint32_t plistStringLength = 0;
|
||||||
|
|
||||||
|
plist_to_bin(command, &plistString, &plistStringLength);
|
||||||
|
if (plistString != NULL) {
|
||||||
|
sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n", plistStringLength);
|
||||||
|
sbuf_printf(sbuf, "Content-Type: application/x-apple-binary-plist\r\n\r\n");
|
||||||
|
sbuf_append(sbuf, plistString, plistStringLength);
|
||||||
|
free(plistString); // should be plist_to_bin_free, but it's not defined in older
|
||||||
|
// libraries
|
||||||
|
char *b = 0;
|
||||||
|
size_t l = 0;
|
||||||
|
sbuf_buf_and_length(sbuf, &b, &l);
|
||||||
|
result = ap2_event_port_send_message(conn, b, l);
|
||||||
|
debug(3, "Connection %d: POST /command sent on the event port. Result is %zd.",
|
||||||
|
conn->connection_number, result);
|
||||||
|
sbuf_clear(sbuf);
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1); // delete the structured buffer
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t ap2_event_send_update_info(rtsp_conn_info *conn) {
|
||||||
|
// sends the updateInfo plist on the event port
|
||||||
|
ssize_t result = -1;
|
||||||
|
plist_t value_plist = generateInfoPlist(conn);
|
||||||
|
if (value_plist != NULL) {
|
||||||
|
void *txtData = NULL;
|
||||||
|
size_t txtDataLength = 0;
|
||||||
|
generateTxtDataValueInfo(conn, &txtData, &txtDataLength);
|
||||||
|
plist_dict_set_item(value_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength));
|
||||||
|
free(txtData);
|
||||||
|
plist_t update_info_plist = plist_new_dict();
|
||||||
|
if (update_info_plist != NULL) {
|
||||||
|
plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo"));
|
||||||
|
plist_dict_set_item(update_info_plist, "value", value_plist);
|
||||||
|
|
||||||
|
char *plist_as_string = plist_as_xml_text(update_info_plist);
|
||||||
|
if (plist_as_string != NULL) {
|
||||||
|
debug(3, "update_info_plist is:\n--\n\"%s\"\n--\n", plist_as_string);
|
||||||
|
free(plist_as_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = ap2_event_port_post_command(conn, update_info_plist);
|
||||||
|
plist_free(update_info_plist);
|
||||||
|
} else {
|
||||||
|
debug(1, "Could not build an updateInfo plist");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug(1, "Could not build an updateInfo value plist");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef _AP2_EVENT_COMMAND_SENDER_H
|
||||||
|
#define _AP2_EVENT_COMMAND_SENDER_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include "player.h"
|
||||||
|
|
||||||
|
ssize_t ap2_event_send_unit_volume_notification(rtsp_conn_info *conn, double volume);
|
||||||
|
ssize_t ap2_event_send_modern_media_remote_command(rtsp_conn_info *conn, unsigned int command_number);
|
||||||
|
ssize_t ap2_event_send_update_info(rtsp_conn_info *conn);
|
||||||
|
|
||||||
|
ssize_t ap2_event_send_dev_mule();
|
||||||
|
|
||||||
|
#endif // _AP2_EVENT_COMMAND_SENDER_H
|
||||||
+33
-147
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Apple AirPlay 2 Event Receiver. This file is part of Shairport Sync.
|
* 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.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person
|
* Permission is hereby granted, free of charge, to any person
|
||||||
@@ -25,23 +25,27 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ap2_event_receiver.h"
|
#include "ap2_event_receiver.h"
|
||||||
#include "bonjour_strings.h"
|
#include "ap2_event_message_handler.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "ptp-utilities.h"
|
|
||||||
#include "rtsp.h"
|
|
||||||
#include "utilities/network_utilities.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) {
|
void ap2_event_receiver_cleanup_handler(void *arg) {
|
||||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||||
// debug(1, "Connection %d: AP2 Event Receiver Cleanup start.", conn->connection_number);
|
// 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
|
#ifdef CONFIG_METADATA
|
||||||
// this is here to ensure it's only performed once during a teardown of a ptp stream
|
// 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);
|
send_ssnc_metadata('disc', conn->client_ip_string, strlen(conn->client_ip_string), 1);
|
||||||
#endif
|
#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) {
|
if (conn->airplay_gid != NULL) {
|
||||||
free(conn->airplay_gid);
|
free(conn->airplay_gid);
|
||||||
@@ -52,161 +56,43 @@ void ap2_event_receiver_cleanup_handler(void *arg) {
|
|||||||
free(conn->dacp_active_remote);
|
free(conn->dacp_active_remote);
|
||||||
conn->dacp_active_remote = NULL;
|
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);
|
debug(2, "Connection %d: AP2 Event Receiver Cleanup is complete.", conn->connection_number);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *ap2_event_receiver(void *arg) {
|
void *ap2_event_receiver(void *arg) {
|
||||||
// #include <syscall.h>
|
|
||||||
// debug(1, "rtp_event_receiver PID %d", syscall(SYS_gettid));
|
|
||||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||||
debug(2, "Connection %d: AP2 Event Receiver started", conn->connection_number);
|
debug(2, "Connection %d: AP2 Event Receiver started", conn->connection_number);
|
||||||
structured_buffer *sbuf = sbuf_new(4096);
|
SOCKADDR remote_addr;
|
||||||
if (sbuf != NULL) {
|
memset(&remote_addr, 0, sizeof(remote_addr));
|
||||||
pthread_cleanup_push(sbuf_cleanup, sbuf);
|
socklen_t addr_size = sizeof(remote_addr);
|
||||||
|
|
||||||
/*
|
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
|
||||||
// only update these things if you're (still) the principal conn
|
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
|
||||||
pthread_rwlock_wrlock(&principal_conn_lock); // don't let the principal_conn be changed
|
conn->event_channel_fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size);
|
||||||
pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
|
pthread_cleanup_pop(1); // unlock the mutex
|
||||||
if (principal_conn == conn) {
|
if (conn->event_channel_fd > 0) {
|
||||||
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(2,
|
debug(2,
|
||||||
"Connection %d: ap2_event_receiver accepted a connection on socket %d and moved to a new "
|
"Connection %d: ap2_event_receiver accepted a connection on socket %d and moved to a new "
|
||||||
"socket %d.",
|
"socket %d.",
|
||||||
conn->connection_number, conn->event_socket, fd);
|
conn->connection_number, conn->event_socket, conn->event_channel_fd);
|
||||||
intptr_t pfd = fd;
|
pthread_cleanup_push(ap2_event_receiver_cleanup_handler, arg);
|
||||||
pthread_cleanup_push(socket_cleanup, (void *)pfd);
|
|
||||||
int finished = 0;
|
ap2_event_send_update_info(conn);
|
||||||
do {
|
|
||||||
|
|
||||||
plist_t value_plist = generateInfoPlist(conn);
|
while (1) {
|
||||||
if (value_plist != NULL) {
|
usleep(1000000);
|
||||||
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);
|
|
||||||
|
|
||||||
debug(3, "Connection %d: AP2 Event Receiver RTP thread starting \"normal\" exit.",
|
debug(3, "Connection %d: AP2 Event Receiver RTP thread starting \"normal\" exit.",
|
||||||
conn->connection_number);
|
conn->connection_number);
|
||||||
pthread_cleanup_pop(1); // close the socket
|
|
||||||
|
|
||||||
pthread_cleanup_pop(1); // do the cleanup
|
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 {
|
} 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;
|
conn->ap2_event_receiver_exited = 1;
|
||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
/*
|
|
||||||
* Apple AirPlay 2 Remote Control (RC) Event Receiver. This file is part of Shairport Sync.
|
|
||||||
* Copyright (c) Mike Brady 2014--2025
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation
|
|
||||||
* files (the "Software"), to deal in the Software without
|
|
||||||
* restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
||||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ap2_rc_event_receiver.h"
|
|
||||||
#include "common.h"
|
|
||||||
#include "player.h"
|
|
||||||
#include "rtsp.h"
|
|
||||||
#include "utilities/network_utilities.h"
|
|
||||||
#include "utilities/structured_buffer.h"
|
|
||||||
|
|
||||||
void ap2_rc_event_receiver_cleanup_handler(void *arg) {
|
|
||||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
|
||||||
debug(2, "Connection %d: AP2 Event Receiver Cleanup.", conn->connection_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *ap2_rc_event_receiver(void *arg) {
|
|
||||||
// #include <syscall.h>
|
|
||||||
// debug(1, "rtp_event_receiver PID %d", syscall(SYS_gettid));
|
|
||||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
|
||||||
if (conn->airplay_stream_category == remote_control_stream)
|
|
||||||
debug(2, "Connection %d (RC): AP2 RC Event Receiver started", conn->connection_number);
|
|
||||||
else
|
|
||||||
debug(2, "Connection %d: AP2 RC Event Receiver started", conn->connection_number);
|
|
||||||
|
|
||||||
structured_buffer *sbuf = sbuf_new(4096);
|
|
||||||
if (sbuf != NULL) {
|
|
||||||
pthread_cleanup_push(sbuf_cleanup, sbuf);
|
|
||||||
|
|
||||||
pthread_cleanup_push(ap2_rc_event_receiver_cleanup_handler, arg);
|
|
||||||
|
|
||||||
// listen(conn->event_socket, 5); // this is now done in the handle_setup_2 code
|
|
||||||
|
|
||||||
uint8_t packet[4096];
|
|
||||||
ssize_t nread;
|
|
||||||
SOCKADDR remote_addr;
|
|
||||||
memset(&remote_addr, 0, sizeof(remote_addr));
|
|
||||||
socklen_t addr_size = sizeof(remote_addr);
|
|
||||||
|
|
||||||
int fd = eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size);
|
|
||||||
debug(2,
|
|
||||||
"Connection %d: ap2_rc_event_receiver accepted a connection on socket %d and moved to a "
|
|
||||||
"new "
|
|
||||||
"socket %d.",
|
|
||||||
conn->connection_number, conn->event_socket, fd);
|
|
||||||
intptr_t pfd = fd;
|
|
||||||
pthread_cleanup_push(socket_cleanup, (void *)pfd);
|
|
||||||
int finished = 0;
|
|
||||||
do {
|
|
||||||
|
|
||||||
plist_t value_plist = generateInfoPlist(conn);
|
|
||||||
if (value_plist != NULL) {
|
|
||||||
void *txtData = NULL;
|
|
||||||
size_t txtDataLength = 0;
|
|
||||||
generateTxtDataValueInfo(conn, &txtData, &txtDataLength);
|
|
||||||
plist_dict_set_item(value_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength));
|
|
||||||
free(txtData);
|
|
||||||
plist_t update_info_plist = plist_new_dict();
|
|
||||||
if (update_info_plist != NULL) {
|
|
||||||
plist_dict_set_item(update_info_plist, "type", plist_new_string("updateInfo"));
|
|
||||||
plist_dict_set_item(update_info_plist, "value", value_plist);
|
|
||||||
char *plistString = NULL;
|
|
||||||
uint32_t plistStringLength = 0;
|
|
||||||
plist_to_bin(update_info_plist, &plistString, &plistStringLength);
|
|
||||||
if (plistString != NULL) {
|
|
||||||
char *plist_as_string = plist_as_xml_text(update_info_plist);
|
|
||||||
if (plist_as_string != NULL) {
|
|
||||||
debug(3, "Plist is: \"%s\".", plist_as_string);
|
|
||||||
free(plist_as_string);
|
|
||||||
}
|
|
||||||
sbuf_printf(sbuf, "POST /command RTSP/1.0\r\nContent-Length: %u\r\n",
|
|
||||||
plistStringLength);
|
|
||||||
sbuf_printf(sbuf, "Content-Type: application/x-apple-binary-plist\r\n\r\n");
|
|
||||||
sbuf_append(sbuf, plistString, plistStringLength);
|
|
||||||
|
|
||||||
free(plistString); // should be plist_to_bin_free, but it's not defined in older
|
|
||||||
// libraries
|
|
||||||
char *b = 0;
|
|
||||||
size_t l = 0;
|
|
||||||
sbuf_buf_and_length(sbuf, &b, &l);
|
|
||||||
ssize_t wres =
|
|
||||||
write_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, b, l);
|
|
||||||
if ((wres == -1) || ((size_t)wres != l))
|
|
||||||
debug(1, "Encrypted write error");
|
|
||||||
|
|
||||||
sbuf_clear(sbuf);
|
|
||||||
} else {
|
|
||||||
debug(1, "plist string not created!");
|
|
||||||
}
|
|
||||||
plist_free(update_info_plist);
|
|
||||||
} else {
|
|
||||||
debug(1, "Could not build an updateInfo plist");
|
|
||||||
}
|
|
||||||
// plist_free(value_plist);
|
|
||||||
} else {
|
|
||||||
debug(1, "Could not build an value plist");
|
|
||||||
}
|
|
||||||
|
|
||||||
while (finished == 0) {
|
|
||||||
nread = read_encrypted(fd, &conn->ap2_pairing_context.event_cipher_bundle, packet,
|
|
||||||
sizeof(packet));
|
|
||||||
|
|
||||||
// nread = recv(fd, packet, sizeof(packet), 0);
|
|
||||||
|
|
||||||
if (nread < 0) {
|
|
||||||
char errorstring[1024];
|
|
||||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
|
||||||
debug(
|
|
||||||
1,
|
|
||||||
"Connection %d: error in ap2_rc_event_receiver %d: \"%s\". Could not recv a packet.",
|
|
||||||
conn->connection_number, errno, errorstring);
|
|
||||||
// if ((config.diagnostic_drop_packet_fraction == 0.0) ||
|
|
||||||
// (drand48() > config.diagnostic_drop_packet_fraction)) {
|
|
||||||
} else if (nread > 0) {
|
|
||||||
|
|
||||||
// ssize_t plen = nread;
|
|
||||||
packet[nread] = '\0';
|
|
||||||
debug(2,
|
|
||||||
"Connection %d: ap2_rc_event_receiver Packet Received on Event Port with contents: "
|
|
||||||
"\"%s\".",
|
|
||||||
conn->connection_number, packet);
|
|
||||||
} else {
|
|
||||||
debug(2, "Connection %d: ap2_rc_event_receiver Event Port connection closed by client",
|
|
||||||
conn->connection_number);
|
|
||||||
finished = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} while (finished == 0);
|
|
||||||
pthread_cleanup_pop(1); // close the socket
|
|
||||||
pthread_cleanup_pop(1); // do the cleanup
|
|
||||||
pthread_cleanup_pop(1); // delete the structured buffer
|
|
||||||
debug(2, "Connection %d: AP2 ap2_rc_event_receiver \"normal\" exit.", conn->connection_number);
|
|
||||||
} else {
|
|
||||||
debug(1, "Could not allocate a structured buffer!");
|
|
||||||
}
|
|
||||||
conn->ap2_event_receiver_exited = 1;
|
|
||||||
pthread_exit(NULL);
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// RC means Remote Control
|
|
||||||
|
|
||||||
#ifndef _AP2_RC_EVENT_RECEIVER_H
|
|
||||||
#define _AP2_RC_EVENT_RECEIVER_H
|
|
||||||
|
|
||||||
void *ap2_rc_event_receiver(void *arg);
|
|
||||||
|
|
||||||
#endif // _AP2_RC_EVENT_RECEIVER_H
|
|
||||||
@@ -985,7 +985,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra
|
|||||||
}
|
}
|
||||||
#ifdef CONFIG_FFMPEG
|
#ifdef CONFIG_FFMPEG
|
||||||
if (local_format == SPS_FORMAT_UNKNOWN) {
|
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);
|
local_channels);
|
||||||
unsigned int rate_pointer = 0;
|
unsigned int rate_pointer = 0;
|
||||||
while ((rate_pointer < sizeof(rates) / sizeof(unsigned int)) &&
|
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) {
|
if (local_format == SPS_FORMAT_UNKNOWN) {
|
||||||
int rate_pointer = (int)(sizeof(rates) / sizeof(unsigned int) - 1);
|
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);
|
local_channels);
|
||||||
while ((rate_pointer >= 0) && (local_format == SPS_FORMAT_UNKNOWN)) {
|
while ((rate_pointer >= 0) && (local_format == SPS_FORMAT_UNKNOWN)) {
|
||||||
local_rate = rates[rate_pointer];
|
local_rate = rates[rate_pointer];
|
||||||
|
|||||||
+6
-6
@@ -306,7 +306,7 @@ static int get_permissible_configuration_settings() {
|
|||||||
debug(3, "\"%s\" can handle %u channels.", alsa_out_dev, c);
|
debug(3, "\"%s\" can handle %u channels.", alsa_out_dev, c);
|
||||||
} else {
|
} else {
|
||||||
// the device can't handle this number of channels
|
// 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 &=
|
config.channel_set &=
|
||||||
~(1 << c); // the alsa device can't accommodate this number of channels
|
~(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,
|
debug(3, "\"%s\" can handle a rate of %u fps.", alsa_out_dev,
|
||||||
sps_rate_actual_rate(r));
|
sps_rate_actual_rate(r));
|
||||||
} else {
|
} 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));
|
sps_rate_actual_rate(r));
|
||||||
config.rate_set &= ~(1 << r); // the alsa device doesn't do this rate
|
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,
|
debug(3, "\"%s\" can handle the %s format.", alsa_out_dev,
|
||||||
sps_format_description_string(f));
|
sps_format_description_string(f));
|
||||||
} else {
|
} 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));
|
sps_format_description_string(f));
|
||||||
config.format_set &= ~(1 << f); // the alsa device doesn't do this format
|
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) {
|
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 (alsa_backend_state != abm_disconnected) { // must be playing or connected...
|
||||||
if (config.keep_dac_busy == 0) {
|
if (config.keep_dac_busy == 0) {
|
||||||
do_close();
|
do_close();
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
debug(3, "alsa: stop() -- called on a disconnected alsa backend");
|
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
|
pthread_cleanup_pop(0); // release the mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2252,7 +2252,7 @@ static void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg)
|
|||||||
// #include <syscall.h>
|
// #include <syscall.h>
|
||||||
// debug(1, "alsa_buffer_monitor_thread_code PID %d", syscall(SYS_gettid));
|
// debug(1, "alsa_buffer_monitor_thread_code PID %d", syscall(SYS_gettid));
|
||||||
// Wait until the output configuration has been set by the main program
|
// 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 frame_count = 0;
|
||||||
int error_count = 0;
|
int error_count = 0;
|
||||||
int error_detected = 0;
|
int error_detected = 0;
|
||||||
|
|||||||
+1
-1
@@ -149,7 +149,7 @@ void build_bonjour_strings(__attribute((unused)) rtsp_conn_info *conn) {
|
|||||||
entry_number = 0;
|
entry_number = 0;
|
||||||
|
|
||||||
secondary_txt_records[entry_number++] = "acl=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++] =
|
secondary_txt_records[entry_number++] =
|
||||||
bnprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
|
bnprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
|
||||||
secondary_txt_records[entry_number++] =
|
secondary_txt_records[entry_number++] =
|
||||||
|
|||||||
@@ -1041,32 +1041,49 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int config_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value) {
|
int config_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value) {
|
||||||
int response = config_lookup_string(cfg, path, value);
|
int response = CONFIG_FALSE;
|
||||||
if (response == CONFIG_TRUE) {
|
config_setting_t *s = config_lookup(cfg, path);
|
||||||
if ((value != NULL) && ((*value == NULL) || (*value[0] == 0))) {
|
if (s != NULL) {
|
||||||
warn("The \"%s\" parameter is an empty string and has been ignored.", path);
|
// the setting exists, but might not be a string
|
||||||
response = CONFIG_FALSE;
|
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;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
const char *str = 0;
|
const char *str = NULL;
|
||||||
if (config_lookup_string(cfg, where, &str)) {
|
int response = CONFIG_FALSE;
|
||||||
if (strcasecmp(str, "no") == 0) {
|
config_setting_t *s = config_lookup(cfg, where);
|
||||||
(*dst) = 0;
|
if (s != NULL) {
|
||||||
return 1;
|
if (config_setting_type(s) == CONFIG_TYPE_STRING) {
|
||||||
} else if (strcasecmp(str, "yes") == 0) {
|
str = config_setting_get_string(s);
|
||||||
(*dst) = 1;
|
if (strcasecmp(str, "no") == 0) {
|
||||||
return 1;
|
(*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 {
|
} else {
|
||||||
die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
|
warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, and has been ignored.", where);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember to free the returned array of strings.
|
// 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;
|
int64_t time_to_wait;
|
||||||
do {
|
do {
|
||||||
|
int oldState;
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
|
||||||
if (getifaddrs(&ifaddr) == 0) {
|
if (getifaddrs(&ifaddr) == 0) {
|
||||||
t = id;
|
t = id;
|
||||||
int found = 0;
|
int found = 0;
|
||||||
@@ -2361,6 +2380,7 @@ int get_device_id(uint8_t *id, int int_length) {
|
|||||||
}
|
}
|
||||||
freeifaddrs(ifaddr);
|
freeifaddrs(ifaddr);
|
||||||
}
|
}
|
||||||
|
pthread_setcancelstate(oldState, NULL);
|
||||||
// wait a little time if we haven't got a response
|
// wait a little time if we haven't got a response
|
||||||
if (response != 0) {
|
if (response != 0) {
|
||||||
usleep(100000);
|
usleep(100000);
|
||||||
|
|||||||
@@ -396,13 +396,13 @@ typedef struct {
|
|||||||
uint32_t airplay_statusflags;
|
uint32_t airplay_statusflags;
|
||||||
char *airplay_fex; // a base64-encoded version of the airplay_features in little-endian form
|
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_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_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_pgid; // UUID in the txtAirPlay data sent on the event channel
|
||||||
char *airplay_psi; // type 4 fixed UUID
|
char *airplay_psi; // type 4 fixed UUID
|
||||||
uint8_t airplay_pk[32]; // public key
|
uint8_t airplay_pk[32]; // public key
|
||||||
char *pk_string;
|
char *pk_string;
|
||||||
char *nqptp_shared_memory_interface_name; // client name for nqptp service
|
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
|
#endif
|
||||||
int unfixable_error_reported; // only report once.
|
int unfixable_error_reported; // only report once.
|
||||||
|
|
||||||
@@ -478,19 +478,6 @@ uint16_t nextFreeUDPPort();
|
|||||||
|
|
||||||
extern volatile int debuglev;
|
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
|
// Thanks to https://stackoverflow.com/a/1597129 for the inspiration for this identifier generation
|
||||||
#define MAKEUNIQUEID2(x, y) x##y
|
#define MAKEUNIQUEID2(x, y) x##y
|
||||||
#define MADEID(x, y) MAKEUNIQUEID2(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;
|
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_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_string_or_list_setting(config_setting_t *setting, const char *item);
|
||||||
int check_int_or_list_setting(config_setting_t *setting, const int item);
|
int check_int_or_list_setting(config_setting_t *setting, const int item);
|
||||||
|
|
||||||
|
|||||||
+40
-19
@@ -1,7 +1,7 @@
|
|||||||
# Process this file with autoconf to produce a configure script.
|
# Process this file with autoconf to produce a configure script.
|
||||||
|
|
||||||
AC_PREREQ([2.50])
|
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"}
|
: ${CFLAGS="-O3"}
|
||||||
: ${CXXFLAGS="-O3"}
|
: ${CXXFLAGS="-O3"}
|
||||||
AM_INIT_AUTOMAKE([subdir-objects])
|
AM_INIT_AUTOMAKE([subdir-objects])
|
||||||
@@ -219,6 +219,12 @@ fi
|
|||||||
# Look for metadata flag and resolve it further down the script
|
# 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])])
|
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
|
# 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.
|
# 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_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-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!))
|
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_after_args="${systemd_after_args}${systemd_after_args:+ }avahi-daemon.service"
|
||||||
systemd_requires_args="${systemd_requires_args} avahi-daemon.service"
|
systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }avahi-daemon.service"
|
||||||
fi
|
fi
|
||||||
AM_CONDITIONAL([USE_AVAHI], [test "x$with_avahi" = "xyes"])
|
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_METADATA_HUB], [test "x$REQUESTED_EXTENDED_METADATA_SUPPORT" = "x1"])
|
||||||
AM_CONDITIONAL([USE_DACP_CLIENT], [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.])
|
AC_DEFINE([CONFIG_METADATA], 1, [Needed by the compiler.])
|
||||||
fi
|
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
|
# Look for AirPlay 2 flag
|
||||||
AC_ARG_WITH(airplay-2, [AS_HELP_STRING([--with-airplay-2],[Build for AirPlay 2])])
|
AC_ARG_WITH(airplay-2, [AS_HELP_STRING([--with-airplay-2],[Build for AirPlay 2])])
|
||||||
if test "x$with_airplay_2" = "xyes" ; then
|
if test "x$with_airplay_2" = "xyes" ; then
|
||||||
|
using_ffmpeg=true
|
||||||
AC_DEFINE([CONFIG_AIRPLAY_2], 1, [Build for AirPlay 2])
|
AC_DEFINE([CONFIG_AIRPLAY_2], 1, [Build for AirPlay 2])
|
||||||
AC_CHECK_PROG(PLISTUTIL_CHECK,plistutil,yes)
|
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.])])
|
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])])
|
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
|
fi
|
||||||
AM_CONDITIONAL([USE_AIRPLAY_2], [test "x$with_airplay_2" = "xyes"])
|
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(
|
PKG_CHECK_MODULES(
|
||||||
[libavutil], [libavutil],
|
[libavutil], [libavutil],
|
||||||
[CFLAGS="${libavutil_CFLAGS} ${CFLAGS}" LIBS="${libavutil_LIBS} ${LIBS}"],
|
[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(
|
PKG_CHECK_MODULES(
|
||||||
[libavcodec], [libavcodec],
|
[libavcodec], [libavcodec],
|
||||||
@@ -463,7 +490,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
|
|||||||
[AC_CHECK_LIB(
|
[AC_CHECK_LIB(
|
||||||
[avcodec], [avcodec_find_decoder],
|
[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(
|
PKG_CHECK_MODULES(
|
||||||
@@ -472,7 +499,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
|
|||||||
[AC_CHECK_LIB(
|
[AC_CHECK_LIB(
|
||||||
[avformat], [avformat_new_stream],
|
[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(
|
PKG_CHECK_MODULES(
|
||||||
@@ -481,16 +508,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
|
|||||||
[AC_CHECK_LIB(
|
[AC_CHECK_LIB(
|
||||||
[swresample], [swr_convert],
|
[swresample], [swr_convert],
|
||||||
[],
|
[],
|
||||||
[AC_MSG_ERROR([AirPlay 2 support requires libswresample -- libswresample-dev suggested])]
|
[AC_MSG_ERROR([FFmpeg 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])]
|
|
||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
@@ -505,8 +523,11 @@ AM_CONDITIONAL([USE_HAMMERTON], [test $using_ffmpeg = false])
|
|||||||
# Look for Apple ALAC flag
|
# Look for Apple ALAC flag
|
||||||
AC_ARG_WITH(apple-alac,[AS_HELP_STRING([--with-apple-alac],[include support for the Apple ALAC decoder (deprecated)])])
|
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_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
|
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
|
fi
|
||||||
AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Include support for using the Apple ALAC Decoder (deprecated)])
|
AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Include support for using the Apple ALAC Decoder (deprecated)])
|
||||||
if test "x${with_pkg_config}" = xyes ; then
|
if test "x${with_pkg_config}" = xyes ; then
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "metadata_hub.h"
|
#include "metadata/hub.h"
|
||||||
#include "tinyhttp/http.h"
|
#include "tinyhttp/http.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -397,10 +397,10 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) {
|
|||||||
// as the conn's connection number
|
// as the conn's connection number
|
||||||
// this is to signify that the player has stopped, but only if another thread (with a different
|
// 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
|
// 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)
|
if (dacp_server.players_connection_thread_index == conn->connection_number)
|
||||||
dacp_server.players_connection_thread_index = 0;
|
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...
|
// this will be running on the thread of its caller, not of the conversation thread...
|
||||||
|
|||||||
+64
-16
@@ -37,8 +37,11 @@
|
|||||||
|
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
#include "metadata_hub.h"
|
#endif
|
||||||
|
|
||||||
|
#include "metadata/hub.h"
|
||||||
|
|
||||||
#include "dbus-service.h"
|
#include "dbus-service.h"
|
||||||
|
|
||||||
@@ -335,7 +338,9 @@ static gboolean on_handle_set_volume(ShairportSyncAdvancedRemoteControl *skeleto
|
|||||||
GDBusMethodInvocation *invocation, const gint volume,
|
GDBusMethodInvocation *invocation, const gint volume,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
debug(2, "Set volume to %d.", volume);
|
debug(2, "Set volume to %d.", volume);
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
dacp_set_volume(volume);
|
dacp_set_volume(volume);
|
||||||
|
#endif
|
||||||
shairport_sync_advanced_remote_control_complete_set_volume(skeleton, invocation);
|
shairport_sync_advanced_remote_control_complete_set_volume(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -343,7 +348,9 @@ static gboolean on_handle_set_volume(ShairportSyncAdvancedRemoteControl *skeleto
|
|||||||
static gboolean on_handle_fast_forward(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_fast_forward(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("beginff");
|
send_simple_dacp_command("beginff");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_fast_forward(skeleton, invocation);
|
shairport_sync_remote_control_complete_fast_forward(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -351,7 +358,9 @@ static gboolean on_handle_fast_forward(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_rewind(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_rewind(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("beginrew");
|
send_simple_dacp_command("beginrew");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_rewind(skeleton, invocation);
|
shairport_sync_remote_control_complete_rewind(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -359,7 +368,9 @@ static gboolean on_handle_rewind(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_toggle_mute(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_toggle_mute(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("mutetoggle");
|
send_simple_dacp_command("mutetoggle");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_toggle_mute(skeleton, invocation);
|
shairport_sync_remote_control_complete_toggle_mute(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -367,7 +378,9 @@ static gboolean on_handle_toggle_mute(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_next(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_next(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("nextitem");
|
send_simple_dacp_command("nextitem");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_next(skeleton, invocation);
|
shairport_sync_remote_control_complete_next(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -375,7 +388,9 @@ static gboolean on_handle_next(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_previous(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_previous(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("previtem");
|
send_simple_dacp_command("previtem");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_previous(skeleton, invocation);
|
shairport_sync_remote_control_complete_previous(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -383,7 +398,9 @@ static gboolean on_handle_previous(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_pause(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_pause(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("pause");
|
send_simple_dacp_command("pause");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_pause(skeleton, invocation);
|
shairport_sync_remote_control_complete_pause(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -391,7 +408,9 @@ static gboolean on_handle_pause(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("playpause");
|
send_simple_dacp_command("playpause");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_play_pause(skeleton, invocation);
|
shairport_sync_remote_control_complete_play_pause(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -399,7 +418,9 @@ static gboolean on_handle_play_pause(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("play");
|
send_simple_dacp_command("play");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_play(skeleton, invocation);
|
shairport_sync_remote_control_complete_play(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -407,7 +428,9 @@ static gboolean on_handle_play(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_stop(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_stop(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("stop");
|
send_simple_dacp_command("stop");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_stop(skeleton, invocation);
|
shairport_sync_remote_control_complete_stop(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -415,7 +438,9 @@ static gboolean on_handle_stop(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_resume(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_resume(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("playresume");
|
send_simple_dacp_command("playresume");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_resume(skeleton, invocation);
|
shairport_sync_remote_control_complete_resume(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -423,7 +448,9 @@ static gboolean on_handle_resume(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_shuffle_songs(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_shuffle_songs(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("shuffle_songs");
|
send_simple_dacp_command("shuffle_songs");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_shuffle_songs(skeleton, invocation);
|
shairport_sync_remote_control_complete_shuffle_songs(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -431,7 +458,9 @@ static gboolean on_handle_shuffle_songs(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_volume_up(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_volume_up(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("volumeup");
|
send_simple_dacp_command("volumeup");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_volume_up(skeleton, invocation);
|
shairport_sync_remote_control_complete_volume_up(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -439,7 +468,9 @@ static gboolean on_handle_volume_up(ShairportSyncRemoteControl *skeleton,
|
|||||||
static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
|
static gboolean on_handle_volume_down(ShairportSyncRemoteControl *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("volumedown");
|
send_simple_dacp_command("volumedown");
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_volume_down(skeleton, invocation);
|
shairport_sync_remote_control_complete_volume_down(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -449,9 +480,11 @@ static gboolean on_handle_set_airplay_volume(ShairportSyncRemoteControl *skeleto
|
|||||||
const gdouble volume,
|
const gdouble volume,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
debug(2, "Set airplay volume to %.6f.", volume);
|
debug(2, "Set airplay volume to %.6f.", volume);
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
char command[256] = "";
|
char command[256] = "";
|
||||||
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume);
|
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", volume);
|
||||||
send_simple_dacp_command(command);
|
send_simple_dacp_command(command);
|
||||||
|
#endif
|
||||||
shairport_sync_remote_control_complete_set_airplay_volume(skeleton, invocation);
|
shairport_sync_remote_control_complete_set_airplay_volume(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -683,17 +716,7 @@ gboolean notify_volume_callback(ShairportSync *skeleton,
|
|||||||
gdouble iv = shairport_sync_get_volume(skeleton);
|
gdouble iv = shairport_sync_get_volume(skeleton);
|
||||||
if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) {
|
if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) {
|
||||||
debug(2, ">> set volume to %7.4f.", iv);
|
debug(2, ">> set volume to %7.4f.", iv);
|
||||||
|
config.airplay_volume = iv;
|
||||||
pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed
|
|
||||||
pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock);
|
|
||||||
|
|
||||||
if (principal_conn != NULL) {
|
|
||||||
player_volume(iv, principal_conn);
|
|
||||||
principal_conn->own_airplay_volume = iv;
|
|
||||||
principal_conn->own_airplay_volume_set = 1;
|
|
||||||
}
|
|
||||||
pthread_cleanup_pop(1); // release the principal_conn lock
|
|
||||||
config.airplay_volume = iv;
|
|
||||||
} else {
|
} else {
|
||||||
debug(1, ">> invalid volume: %f. Ignored.", iv);
|
debug(1, ">> invalid volume: %f. Ignored.", iv);
|
||||||
shairport_sync_set_volume(skeleton, config.airplay_volume);
|
shairport_sync_set_volume(skeleton, config.airplay_volume);
|
||||||
@@ -737,6 +760,12 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
|
|||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
|
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) &&
|
if ((strcasecmp(th, "hammerton") == 0) &&
|
||||||
((config.decoders_supported & (1 << decoder_hammerton)) != 0))
|
((config.decoders_supported & (1 << decoder_hammerton)) != 0))
|
||||||
config.decoder_in_use = 1 << decoder_hammerton;
|
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.decoders_supported & (1 << decoder_ffmpeg_alac)) != 0))
|
||||||
config.decoder_in_use = 1 << decoder_ffmpeg_alac;
|
config.decoder_in_use = 1 << decoder_ffmpeg_alac;
|
||||||
else {
|
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 "
|
"(Possibly "
|
||||||
"support for this decoder was not compiled "
|
"support for this decoder was not compiled "
|
||||||
"into this version of Shairport Sync.)",
|
"into this version of Shairport Sync.)",
|
||||||
th);
|
th);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -838,6 +868,7 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton,
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton,
|
gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
// debug(1,"notify_shuffle_callback called");
|
// debug(1,"notify_shuffle_callback called");
|
||||||
@@ -847,9 +878,17 @@ gboolean notify_shuffle_callback(ShairportSyncAdvancedRemoteControl *skeleton,
|
|||||||
send_simple_dacp_command("setproperty?dacp.shufflestate=0");
|
send_simple_dacp_command("setproperty?dacp.shufflestate=0");
|
||||||
return TRUE;
|
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,
|
gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleton,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
|
||||||
// debug(1,"notify_loop_status_callback called");
|
// debug(1,"notify_loop_status_callback called");
|
||||||
char *th = (char *)shairport_sync_advanced_remote_control_get_loop_status(skeleton);
|
char *th = (char *)shairport_sync_advanced_remote_control_get_loop_status(skeleton);
|
||||||
// enum volume_control_profile_type previous_volume_control_profile =
|
// enum volume_control_profile_type previous_volume_control_profile =
|
||||||
@@ -884,6 +923,12 @@ gboolean notify_loop_status_callback(ShairportSyncAdvancedRemoteControl *skeleto
|
|||||||
}
|
}
|
||||||
return TRUE;
|
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,
|
static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) const gchar *command,
|
__attribute__((unused)) const gchar *command,
|
||||||
@@ -900,10 +945,12 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
|
|||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
debug(1, "RemoteCommand with command \"%s\".", command);
|
debug(1, "RemoteCommand with command \"%s\".", command);
|
||||||
int reply = 0;
|
int reply = 0;
|
||||||
|
char *client_reply_hex = "";
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
char *client_reply = NULL;
|
char *client_reply = NULL;
|
||||||
ssize_t reply_size = 0;
|
ssize_t reply_size = 0;
|
||||||
reply = dacp_send_command((const char *)command, &client_reply, &reply_size);
|
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) {
|
if (client_reply_hex) {
|
||||||
char *p = client_reply_hex;
|
char *p = client_reply_hex;
|
||||||
if (client_reply) {
|
if (client_reply) {
|
||||||
@@ -917,6 +964,7 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
|
|||||||
}
|
}
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
shairport_sync_complete_remote_command(skeleton, invocation, reply, client_reply_hex);
|
shairport_sync_complete_remote_command(skeleton, invocation, reply, client_reply_hex);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -958,7 +1006,6 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
|
|||||||
|
|
||||||
shairportSyncAdvancedRemoteControlSkeleton =
|
shairportSyncAdvancedRemoteControlSkeleton =
|
||||||
shairport_sync_advanced_remote_control_skeleton_new();
|
shairport_sync_advanced_remote_control_skeleton_new();
|
||||||
|
|
||||||
g_dbus_interface_skeleton_export(
|
g_dbus_interface_skeleton_export(
|
||||||
G_DBUS_INTERFACE_SKELETON(shairportSyncAdvancedRemoteControlSkeleton), connection,
|
G_DBUS_INTERFACE_SKELETON(shairportSyncAdvancedRemoteControlSkeleton), connection,
|
||||||
"/org/gnome/ShairportSync", NULL);
|
"/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_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::file-and-line",
|
||||||
G_CALLBACK(notify_file_and_line_callback), NULL);
|
G_CALLBACK(notify_file_and_line_callback), NULL);
|
||||||
|
|
||||||
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-fast-forward",
|
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-fast-forward",
|
||||||
G_CALLBACK(on_handle_fast_forward), NULL);
|
G_CALLBACK(on_handle_fast_forward), NULL);
|
||||||
|
|
||||||
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-rewind",
|
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-rewind",
|
||||||
G_CALLBACK(on_handle_rewind), NULL);
|
G_CALLBACK(on_handle_rewind), NULL);
|
||||||
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-toggle-mute",
|
g_signal_connect(shairportSyncRemoteControlSkeleton, "handle-toggle-mute",
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
// for generating our own UUIDs
|
||||||
|
#define SHAIRPORT_SYNC_DEVICE_NAMESPACE "01d7c137-6316-455d-a52f-dfb529f26adf"
|
||||||
|
|
||||||
#if defined(__APPLE__) && defined(__MACH__)
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
/* Apple OSX and iOS (Darwin). ------------------------------ */
|
/* Apple OSX and iOS (Darwin). ------------------------------ */
|
||||||
#include <TargetConditionals.h>
|
#include <TargetConditionals.h>
|
||||||
|
|||||||
+5
-1
@@ -32,7 +32,11 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "mdns.h"
|
#include "mdns.h"
|
||||||
#include "rtsp.h"
|
|
||||||
|
#ifdef CONFIG_METADATA
|
||||||
|
#include "metadata/core.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_DACP_CLIENT
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+310
@@ -0,0 +1,310 @@
|
|||||||
|
/*
|
||||||
|
* Core metadata handler.
|
||||||
|
* This file is part of Shairport Sync.
|
||||||
|
* Copyright (c) Mike Brady 2017--2026
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
#include "pc_queue.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_PIPE
|
||||||
|
#include "pipe.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_MULTICAST
|
||||||
|
#include "multicast.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_HUB
|
||||||
|
#include "hub.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_MQTT
|
||||||
|
#include "mqtt.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Metadata is not used by shairport-sync.
|
||||||
|
// Instead we send all metadata to a fifo pipe, so that other apps can listen to
|
||||||
|
// the pipe and use the metadata.
|
||||||
|
|
||||||
|
// We use two 4-character codes to identify each piece of data and we send the
|
||||||
|
// data itself, if any,
|
||||||
|
// in base64 form.
|
||||||
|
|
||||||
|
// The first 4-character code, called the "type", is either:
|
||||||
|
// 'core' for all the regular metadadata coming from iTunes, etc., or
|
||||||
|
// 'ssnc' (for 'shairport-sync') for all metadata coming from Shairport Sync
|
||||||
|
// itself, such as
|
||||||
|
// start/end delimiters, etc.
|
||||||
|
|
||||||
|
// For 'core' metadata, the second 4-character code is the 4-character metadata
|
||||||
|
// code coming from
|
||||||
|
// iTunes etc.
|
||||||
|
// For 'ssnc' metadata, the second 4-character code is used to distinguish the
|
||||||
|
// messages.
|
||||||
|
|
||||||
|
// Cover art is not tagged in the same way as other metadata, it seems, so is
|
||||||
|
// sent as an 'ssnc' type
|
||||||
|
// metadata message with the code 'PICT'
|
||||||
|
// Here are the 'ssnc' codes defined so far:
|
||||||
|
// 'PICT' -- the payload is a picture, either a JPEG or a PNG. Check the
|
||||||
|
// first few bytes to see
|
||||||
|
// which.
|
||||||
|
// 'abeg' -- active mode entered. No arguments
|
||||||
|
// 'aend' -- active mode exited. No arguments
|
||||||
|
// 'pbeg' -- play stream begin. No arguments
|
||||||
|
// 'pend' -- play stream end. No arguments
|
||||||
|
// 'pfls' -- play stream flush. The argument is an unsigned 32-bit
|
||||||
|
// frame number. It seems that all frames up to but not
|
||||||
|
// including this frame are to be flushed.
|
||||||
|
//
|
||||||
|
// 'prsm' -- play stream resume. No arguments. (deprecated)
|
||||||
|
// 'paus' -- buffered audio stream paused. No arguments.
|
||||||
|
// 'pres' -- buffered audio stream resumed. No arguments.
|
||||||
|
// 'pffr' -- the first frame of a play session has been received and has been validly
|
||||||
|
// timed. The argument is
|
||||||
|
// "<frame_number>/<time_it_should_be_played_in_64_bit_nanoseconds>"."
|
||||||
|
// 'pdis' -- a discontinuity in the timestamps of incoming frames has been detected.
|
||||||
|
// timed. The argument is "<frame_number>/<discontinuity>".
|
||||||
|
// discontinuity is the actual frame number less the expected frame number
|
||||||
|
// positive means a gap
|
||||||
|
// 'pvol' -- play volume. The volume is sent as a string --
|
||||||
|
// "airplay_volume,volume,lowest_volume,highest_volume"
|
||||||
|
// volume, lowest_volume and highest_volume are given in dB.
|
||||||
|
// The "airplay_volume" is what's sent to the player, and is from
|
||||||
|
// 0.00 down to -30.00,
|
||||||
|
// with -144.00 meaning mute.
|
||||||
|
// This is linear on the volume control slider of iTunes or iOS
|
||||||
|
// AirPlay.
|
||||||
|
// 'prgr' -- progress -- this is metadata from AirPlay consisting of RTP
|
||||||
|
// timestamps for the start
|
||||||
|
// of the current play sequence, the current play point and the end of the
|
||||||
|
// play sequence.
|
||||||
|
// I guess the timestamps wrap at 2^32.
|
||||||
|
// 'mdst' -- a sequence of metadata is about to start; will have, as data,
|
||||||
|
// the rtptime associated with the metadata, if available
|
||||||
|
// 'mden' -- a sequence of metadata has ended; will have, as data, the
|
||||||
|
// rtptime associated with the metadata, if available
|
||||||
|
// 'pcst' -- a picture is about to be sent; will have, as data, the rtptime
|
||||||
|
// associated with the picture, if available
|
||||||
|
// 'pcen' -- a picture has been sent; will have, as data, the rtptime
|
||||||
|
// associated with the metadata, if available
|
||||||
|
// 'snam' -- A device -- e.g. "Joe's iPhone" -- has opened a play session.
|
||||||
|
// Specifically, it's the "X-Apple-Client-Name" string
|
||||||
|
// 'snua' -- A "user agent" -- e.g. "iTunes/12..." -- has opened a play
|
||||||
|
// session. Specifically, it's the "User-Agent" string
|
||||||
|
// The next two two tokens are to facilitate remote control of the source.
|
||||||
|
// There is some information at http://nto.github.io/AirPlay.html about
|
||||||
|
// remote control of the source.
|
||||||
|
//
|
||||||
|
// 'daid' -- this is the source's DACP-ID (if it has one -- it's not
|
||||||
|
// guaranteed), useful if you want to remotely control the source. Use this
|
||||||
|
// string to identify the source's remote control on the network.
|
||||||
|
// 'acre' -- this is the source's Active-Remote token, necessary if you want
|
||||||
|
// to send commands to the source's remote control (if it has one).
|
||||||
|
// `clip` -- the payload is the IP number of the client, i.e. the sender of audio.
|
||||||
|
// Can be an IPv4 or an IPv6 number. In AirPlay 2 operation, it is sent as soon
|
||||||
|
// as the client has exclusive access to the player and after any existing
|
||||||
|
// play session has been interrupted and terminated.
|
||||||
|
// `conn` -- the payload is the IP number of the client, i.e. the sender of audio.
|
||||||
|
// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message.
|
||||||
|
// It is sent as soon as the client requests access to the player.
|
||||||
|
// If Shairport Sync is already playing, this message is sent before the current
|
||||||
|
// play session is stopped.
|
||||||
|
// `svip` -- the payload is the IP number of the server, i.e. the player itself.
|
||||||
|
// Can be an IPv4 or an IPv6 number.
|
||||||
|
// `svna` -- the payload is the service name of the player, i.e. the name by
|
||||||
|
// which it is seen in the AirPlay menu.
|
||||||
|
// `disc` -- the payload is the IP number of the client, i.e. the sender of audio.
|
||||||
|
// Can be an IPv4 or an IPv6 number. This is an AirPlay-2-only message.
|
||||||
|
// It is sent when a client has been disconnected.
|
||||||
|
// `dapo` -- the payload is the port number (as text) on the server to which remote
|
||||||
|
// control commands should be sent. It is 3689 for iTunes but varies for iOS devices.
|
||||||
|
// ``
|
||||||
|
|
||||||
|
int metadata_running = 0;
|
||||||
|
|
||||||
|
void metadata_pack_cleanup_function(void *arg) {
|
||||||
|
// debug(1, "metadata_pack_cleanup_function called");
|
||||||
|
metadata_package *pack = (metadata_package *)arg;
|
||||||
|
if (pack->carrier)
|
||||||
|
msg_free(&pack->carrier); // release the message
|
||||||
|
else if (pack->data)
|
||||||
|
free(pack->data);
|
||||||
|
// debug(1, "metadata_pack_cleanup_function exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_init(void) {
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_PIPE
|
||||||
|
metadata_pipe_queue_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_MULTICAST
|
||||||
|
metadata_multicast_queue_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_HUB
|
||||||
|
metadata_hub_queue_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_MQTT
|
||||||
|
metadata_mqtt_queue_init();
|
||||||
|
#endif
|
||||||
|
metadata_running = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_stop(void) {
|
||||||
|
if (metadata_running) {
|
||||||
|
debug(2, "metadata_stop called.");
|
||||||
|
|
||||||
|
#ifdef CONFIG_MQTT
|
||||||
|
metadata_mqtt_queue_stop();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_HUB
|
||||||
|
metadata_hub_queue_stop();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_MULTICAST
|
||||||
|
metadata_multicast_queue_stop();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_PIPE
|
||||||
|
metadata_pipe_queue_stop();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block) {
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
// parameters:
|
||||||
|
// type,
|
||||||
|
// code,
|
||||||
|
// pointer to data or NULL,
|
||||||
|
// length of data or NULL,
|
||||||
|
// the rtsp_message or NULL,
|
||||||
|
|
||||||
|
// the rtsp_message is sent for 'core' messages, because it contains the data
|
||||||
|
// and must not be freed until the data has been read.
|
||||||
|
// So, it is passed to send_metadata to be retained, sent to the thread where metadata
|
||||||
|
// is processed and released (and probably freed).
|
||||||
|
|
||||||
|
// The rtsp_message is also sent for certain non-'core' messages.
|
||||||
|
|
||||||
|
// The reading of the parameters is a bit complex:
|
||||||
|
// If the rtsp_message field is non-null, then it represents an rtsp_message
|
||||||
|
// and the data pointer is assumed to point to something within it.
|
||||||
|
// The reference counter of the rtsp_message is incremented here and
|
||||||
|
// should be decremented by the metadata handler when finished.
|
||||||
|
// If the reference count reduces to zero, the message will be freed.
|
||||||
|
|
||||||
|
// If the rtsp_message is NULL, then if the pointer is non-null then the data it
|
||||||
|
// points to, of the length specified, is memcpy'd and passed to the metadata
|
||||||
|
// handler. The handler should free it when done.
|
||||||
|
|
||||||
|
// If the rtsp_message is NULL and the pointer is also NULL, nothing further
|
||||||
|
// is done.
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
metadata_package pack;
|
||||||
|
pack.type = type;
|
||||||
|
pack.code = code;
|
||||||
|
pack.length = length;
|
||||||
|
pack.carrier = carrier;
|
||||||
|
pack.data = (char *)data;
|
||||||
|
if (pack.carrier) {
|
||||||
|
msg_retain(pack.carrier);
|
||||||
|
} else {
|
||||||
|
if (data)
|
||||||
|
pack.data = memdup(data, length); // only if it's not a null
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug(1, "send_metadata_to_queue %x/%x", type, code);
|
||||||
|
int rc = pc_queue_add_item(queue, &pack, block);
|
||||||
|
if (rc != 0) {
|
||||||
|
if (pack.carrier) {
|
||||||
|
if (rc == EWOULDBLOCK)
|
||||||
|
debug(2,
|
||||||
|
"metadata queue \"%s\" full, dropping message item: type %x, code %x, data %" PRIxPTR ", "
|
||||||
|
"length %u, message %d.",
|
||||||
|
queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length,
|
||||||
|
pack.carrier->index_number);
|
||||||
|
msg_free(&pack.carrier);
|
||||||
|
} else {
|
||||||
|
if (rc == EWOULDBLOCK)
|
||||||
|
debug(
|
||||||
|
2,
|
||||||
|
"metadata queue \"%s\" full, dropping data item: type %x, code %x, data %" PRIxPTR ", length %u.",
|
||||||
|
queue->name, pack.type, pack.code, (uintptr_t)pack.data, pack.length);
|
||||||
|
if (pack.data)
|
||||||
|
free(pack.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length,
|
||||||
|
rtsp_message *carrier, int block) {
|
||||||
|
int rc = 0;
|
||||||
|
if (config.metadata_enabled) {
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_PIPE
|
||||||
|
rc = send_metadata_to_pipe_queue(type, code, data, length, carrier, block);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_MULTICAST
|
||||||
|
rc =
|
||||||
|
send_metadata_to_multicast_queue(type, code, data, length, carrier, block);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA_HUB
|
||||||
|
rc = send_metadata_to_hub_queue(type, code, data, length, carrier, block);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_MQTT
|
||||||
|
rc = send_metadata_to_mqtt_queue(type, code, data, length, carrier, block);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length,
|
||||||
|
const int block) {
|
||||||
|
return send_metadata('ssnc', code, data, length, NULL, block);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "../rtsp.h"
|
||||||
|
#include "pc_queue.h"
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t code;
|
||||||
|
char *data;
|
||||||
|
uint32_t length;
|
||||||
|
rtsp_message *carrier;
|
||||||
|
} metadata_package;
|
||||||
|
|
||||||
|
void metadata_pack_cleanup_function(void *arg);
|
||||||
|
|
||||||
|
// initialise and completely delete the metadata stuff
|
||||||
|
|
||||||
|
void metadata_init(void);
|
||||||
|
void metadata_stop(void);
|
||||||
|
|
||||||
|
|
||||||
|
int send_metadata_to_queue(pc_queue *queue, const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block);
|
||||||
|
|
||||||
|
// sends metadata out to the metadata system.
|
||||||
|
|
||||||
|
int send_metadata(const uint32_t type, const uint32_t code, const char *data, const uint32_t length,
|
||||||
|
rtsp_message *carrier, int block);
|
||||||
|
|
||||||
|
// It is sent with the type 'ssnc' the given code, data and length
|
||||||
|
// The handler at the other end must know what to do with the data
|
||||||
|
// e.g. if it's malloced, to free it, etc.
|
||||||
|
// nothing is done automatically
|
||||||
|
int send_ssnc_metadata(const uint32_t code, const char *data, const uint32_t length,
|
||||||
|
const int block);
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
* then you need a metadata hub,
|
* then you need a metadata hub,
|
||||||
* where everything is stored
|
* where everything is stored
|
||||||
* This file is part of Shairport Sync.
|
* This file is part of Shairport Sync.
|
||||||
* Copyright (c) Mike Brady 2017--2025
|
* Copyright (c) Mike Brady 2017--2026
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person
|
* Permission is hereby granted, free of charge, to any person
|
||||||
@@ -44,7 +44,9 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
#include "metadata_hub.h"
|
#include "hub.h"
|
||||||
|
#include "pc_queue.h"
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
#ifdef CONFIG_MBEDTLS
|
#ifdef CONFIG_MBEDTLS
|
||||||
#include <mbedtls/md5.h>
|
#include <mbedtls/md5.h>
|
||||||
@@ -59,6 +61,12 @@
|
|||||||
#include <openssl/evp.h>
|
#include <openssl/evp.h>
|
||||||
#endif
|
#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;
|
struct metadata_bundle metadata_store;
|
||||||
|
|
||||||
int metadata_hub_initialised = 0;
|
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
|
pthread_cleanup_pop(0); // don't remove the lock
|
||||||
metadata_hub_modify_epilog(changed);
|
metadata_hub_modify_epilog(changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void metadata_hub_close(void) {}
|
||||||
|
|
||||||
|
void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) {
|
||||||
|
// debug(2, "metadata_hub_thread_cleanup_function called");
|
||||||
|
metadata_hub_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) {
|
||||||
|
// #include <syscall.h>
|
||||||
|
// debug(1, "metadata_hub_thread_function PID %d", syscall(SYS_gettid));
|
||||||
|
metadata_package pack;
|
||||||
|
pthread_cleanup_push(metadata_hub_thread_cleanup_function, NULL);
|
||||||
|
while (1) {
|
||||||
|
pc_queue_get_item(&metadata_hub_queue, &pack);
|
||||||
|
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
|
||||||
|
if (pack.carrier) {
|
||||||
|
debug(4, " hub: type %x, code %x, length %u, message %d.", pack.type,
|
||||||
|
pack.code, pack.length, pack.carrier->index_number);
|
||||||
|
} else {
|
||||||
|
debug(4, " hub: type %x, code %x, length %u.", pack.type, pack.code,
|
||||||
|
pack.length);
|
||||||
|
}
|
||||||
|
metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length);
|
||||||
|
debug(4, " hub: done.");
|
||||||
|
pthread_cleanup_pop(1);
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1); // will never happen
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_hub_queue_init() {
|
||||||
|
// create a pc_queue for the metadata hub
|
||||||
|
pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package),
|
||||||
|
metadata_hub_queue_size, "hub");
|
||||||
|
if (named_pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL,
|
||||||
|
"metadata hub") != 0)
|
||||||
|
debug(1, "Failed to create metadata hub thread!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_hub_queue_stop() {
|
||||||
|
// debug(2, "metadata stop hub thread.");
|
||||||
|
pthread_cancel(metadata_hub_thread);
|
||||||
|
pthread_join(metadata_hub_thread, NULL);
|
||||||
|
pc_queue_delete(&metadata_hub_queue);
|
||||||
|
// debug(2, "metadata stop hub done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block) {
|
||||||
|
return send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "rtsp.h"
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
#define number_of_watchers 2
|
#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_prolog(void) _metadata_hub_read_prolog(__FILE__, __LINE__)
|
||||||
#define metadata_hub_read_epilog(void) _metadata_hub_modify_epilog(__FILE__, __LINE__)
|
#define metadata_hub_read_epilog(void) _metadata_hub_modify_epilog(__FILE__, __LINE__)
|
||||||
|
|
||||||
|
// metadata queue stuff
|
||||||
|
void metadata_hub_queue_init();
|
||||||
|
void metadata_hub_queue_stop();
|
||||||
|
int send_metadata_to_hub_queue(const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block);
|
||||||
|
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
#include "multicast.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "pc_queue.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
// A special sub-protocol is used for sending large data items over UDP
|
||||||
|
// If the payload exceeded 4 MB, it is chunked using the following format:
|
||||||
|
// "ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data.
|
||||||
|
// Notice that the number of items is different to the standard
|
||||||
|
|
||||||
|
#define METADATA_SNDBUF (4 * 1024 * 1024)
|
||||||
|
static int metadata_sock = -1;
|
||||||
|
static struct sockaddr_in metadata_sockaddr;
|
||||||
|
static char *metadata_sockmsg;
|
||||||
|
pc_queue metadata_multicast_queue;
|
||||||
|
#define metadata_multicast_queue_size 500
|
||||||
|
metadata_package metadata_multicast_queue_items[metadata_multicast_queue_size];
|
||||||
|
pthread_t metadata_multicast_thread;
|
||||||
|
|
||||||
|
int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block) {
|
||||||
|
return send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_create_multicast_socket(void) {
|
||||||
|
if (config.metadata_enabled == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Unlike metadata pipe, socket is opened once and stays open,
|
||||||
|
// so we can call it in create
|
||||||
|
if (config.metadata_sockaddr && config.metadata_sockport) {
|
||||||
|
metadata_sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (metadata_sock < 0) {
|
||||||
|
debug(1, "Could not open metadata socket");
|
||||||
|
} else {
|
||||||
|
int buffer_size = METADATA_SNDBUF;
|
||||||
|
setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size));
|
||||||
|
bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr));
|
||||||
|
metadata_sockaddr.sin_family = AF_INET;
|
||||||
|
metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr);
|
||||||
|
metadata_sockaddr.sin_port = htons(config.metadata_sockport);
|
||||||
|
metadata_sockmsg = malloc(config.metadata_sockmsglength);
|
||||||
|
if (metadata_sockmsg) {
|
||||||
|
memset(metadata_sockmsg, 0, config.metadata_sockmsglength);
|
||||||
|
} else {
|
||||||
|
die("Could not malloc metadata multicast socket buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_delete_multicast_socket(void) {
|
||||||
|
if (config.metadata_enabled == 0)
|
||||||
|
return;
|
||||||
|
if (metadata_sock != -1) {
|
||||||
|
shutdown(metadata_sock, SHUT_RDWR); // we want to immediately deallocate the buffer
|
||||||
|
close(metadata_sock);
|
||||||
|
}
|
||||||
|
if (metadata_sockmsg)
|
||||||
|
free(metadata_sockmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
|
||||||
|
// debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code,
|
||||||
|
// length);
|
||||||
|
if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) {
|
||||||
|
char *ptr = metadata_sockmsg;
|
||||||
|
uint32_t v;
|
||||||
|
v = htonl(type);
|
||||||
|
memcpy(ptr, &v, 4);
|
||||||
|
ptr += 4;
|
||||||
|
v = htonl(code);
|
||||||
|
memcpy(ptr, &v, 4);
|
||||||
|
ptr += 4;
|
||||||
|
memcpy(ptr, data, length);
|
||||||
|
sendto(metadata_sock, metadata_sockmsg, length + 8, 0, (struct sockaddr *)&metadata_sockaddr,
|
||||||
|
sizeof(metadata_sockaddr));
|
||||||
|
} else if (metadata_sock >= 0) {
|
||||||
|
// send metadata in numbered chunks using the protocol:
|
||||||
|
// ("ssnc", "chnk", packet_ix, packet_counts, packet_tag, packet_type, chunked_data)
|
||||||
|
|
||||||
|
uint32_t chunk_ix = 0;
|
||||||
|
if (config.metadata_sockmsglength == 24)
|
||||||
|
die("A divide by zero almost occurred (config.metadata_sockmsglength = 24).");
|
||||||
|
uint32_t chunk_total = length / (config.metadata_sockmsglength - 24);
|
||||||
|
if (chunk_total * (config.metadata_sockmsglength - 24) < length) {
|
||||||
|
chunk_total++;
|
||||||
|
}
|
||||||
|
uint32_t remaining = length;
|
||||||
|
uint32_t v;
|
||||||
|
char *data_crsr = data;
|
||||||
|
do {
|
||||||
|
char *ptr = metadata_sockmsg;
|
||||||
|
memcpy(ptr, "ssncchnk", 8);
|
||||||
|
ptr += 8;
|
||||||
|
v = htonl(chunk_ix);
|
||||||
|
memcpy(ptr, &v, 4);
|
||||||
|
ptr += 4;
|
||||||
|
v = htonl(chunk_total);
|
||||||
|
memcpy(ptr, &v, 4);
|
||||||
|
ptr += 4;
|
||||||
|
v = htonl(type);
|
||||||
|
memcpy(ptr, &v, 4);
|
||||||
|
ptr += 4;
|
||||||
|
v = htonl(code);
|
||||||
|
memcpy(ptr, &v, 4);
|
||||||
|
ptr += 4;
|
||||||
|
size_t datalen = remaining;
|
||||||
|
if (datalen > config.metadata_sockmsglength - 24) {
|
||||||
|
datalen = config.metadata_sockmsglength - 24;
|
||||||
|
}
|
||||||
|
memcpy(ptr, data_crsr, datalen);
|
||||||
|
data_crsr += datalen;
|
||||||
|
sendto(metadata_sock, metadata_sockmsg, datalen + 24, 0,
|
||||||
|
(struct sockaddr *)&metadata_sockaddr, sizeof(metadata_sockaddr));
|
||||||
|
chunk_ix++;
|
||||||
|
remaining -= datalen;
|
||||||
|
if (remaining == 0)
|
||||||
|
break;
|
||||||
|
} while (1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *arg) {
|
||||||
|
// debug(2, "metadata_multicast_thread_cleanup_function called");
|
||||||
|
metadata_delete_multicast_socket();
|
||||||
|
}
|
||||||
|
|
||||||
|
void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) {
|
||||||
|
// #include <syscall.h>
|
||||||
|
// debug(1, "metadata_multicast_thread_function PID %d", syscall(SYS_gettid));
|
||||||
|
metadata_create_multicast_socket();
|
||||||
|
metadata_package pack;
|
||||||
|
pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL);
|
||||||
|
while (1) {
|
||||||
|
pc_queue_get_item(&metadata_multicast_queue, &pack);
|
||||||
|
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
|
||||||
|
if (config.metadata_enabled) {
|
||||||
|
if (pack.carrier) {
|
||||||
|
debug(4,
|
||||||
|
" multicast: type "
|
||||||
|
"%x, code %x, length %u, message %d.",
|
||||||
|
pack.type, pack.code, pack.length, pack.carrier->index_number);
|
||||||
|
} else {
|
||||||
|
debug(4,
|
||||||
|
" multicast: type "
|
||||||
|
"%x, code %x, length %u.",
|
||||||
|
pack.type, pack.code, pack.length);
|
||||||
|
}
|
||||||
|
metadata_multicast_process(pack.type, pack.code, pack.data, pack.length);
|
||||||
|
debug(4,
|
||||||
|
" multicast: done.");
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1);
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1); // will never happen
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_multicast_queue_init() {
|
||||||
|
// create a pc_queue for the metadata_multicast_queue
|
||||||
|
pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items,
|
||||||
|
sizeof(metadata_package), metadata_multicast_queue_size, "multicast");
|
||||||
|
if (named_pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function,
|
||||||
|
NULL, "metadata mcst") != 0)
|
||||||
|
debug(1, "Failed to create metadata multicast thread!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_multicast_queue_stop() {
|
||||||
|
if (metadata_multicast_thread) {
|
||||||
|
pthread_cancel(metadata_multicast_thread);
|
||||||
|
pthread_join(metadata_multicast_thread, NULL);
|
||||||
|
pc_queue_delete(&metadata_multicast_queue);
|
||||||
|
// debug(2, "metadata stop multicast done.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "rtsp.h"
|
||||||
|
|
||||||
|
void metadata_multicast_queue_init();
|
||||||
|
void metadata_multicast_queue_stop();
|
||||||
|
int send_metadata_to_multicast_queue(const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block);
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Producer--consumer queue and access methods.
|
||||||
|
* This file is part of Shairport Sync.
|
||||||
|
* Copyright (c) Mike Brady 2017--2026
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pc_queue.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
|
||||||
|
const char *name) {
|
||||||
|
if (name)
|
||||||
|
debug(4, "Creating metadata queue \"%s\".", name);
|
||||||
|
else
|
||||||
|
debug(1, "Creating an unnamed metadata queue.");
|
||||||
|
pthread_mutex_init(&the_queue->pc_queue_lock, NULL);
|
||||||
|
pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL);
|
||||||
|
pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL);
|
||||||
|
the_queue->item_size = item_size;
|
||||||
|
the_queue->items = items;
|
||||||
|
the_queue->count = 0;
|
||||||
|
the_queue->capacity = number_of_items;
|
||||||
|
the_queue->toq = 0;
|
||||||
|
the_queue->eoq = 0;
|
||||||
|
if (name == NULL)
|
||||||
|
the_queue->name = NULL;
|
||||||
|
else
|
||||||
|
the_queue->name = strdup(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pc_queue_delete(pc_queue *the_queue) {
|
||||||
|
if (the_queue->name)
|
||||||
|
debug(2, "Deleting metadata queue \"%s\".", the_queue->name);
|
||||||
|
else
|
||||||
|
debug(1, "Deleting an unnamed metadata queue.");
|
||||||
|
if (the_queue->name != NULL)
|
||||||
|
free(the_queue->name);
|
||||||
|
// debug(2, "destroying pc_queue_item_removed_signal");
|
||||||
|
pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal);
|
||||||
|
// debug(2, "destroying pc_queue_item_added_signal");
|
||||||
|
pthread_cond_destroy(&the_queue->pc_queue_item_added_signal);
|
||||||
|
// debug(2, "destroying pc_queue_lock");
|
||||||
|
pthread_mutex_destroy(&the_queue->pc_queue_lock);
|
||||||
|
// debug(2, "destroying signals and locks done");
|
||||||
|
}
|
||||||
|
|
||||||
|
void pc_queue_cleanup_handler(void *arg) {
|
||||||
|
// debug(1, "pc_queue_cleanup_handler called.");
|
||||||
|
pc_queue *the_queue = (pc_queue *)arg;
|
||||||
|
int rc = pthread_mutex_unlock(&the_queue->pc_queue_lock);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "Error unlocking for pc_queue_add_item or pc_queue_get_item.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
|
||||||
|
int response = 0;
|
||||||
|
int rc;
|
||||||
|
if (the_queue) {
|
||||||
|
if (block == 0) {
|
||||||
|
rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 4);
|
||||||
|
if (rc == EBUSY)
|
||||||
|
return EBUSY;
|
||||||
|
} else
|
||||||
|
rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "Error %d (\"%s\") locking for pc_queue_add_item. Block is %d.", rc, strerror(rc),
|
||||||
|
block);
|
||||||
|
pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
|
||||||
|
// leave this out if you want this to return if the queue is already full
|
||||||
|
// irrespective of the block flag.
|
||||||
|
/*
|
||||||
|
while (the_queue->count == the_queue->capacity) {
|
||||||
|
rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal,
|
||||||
|
&the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (the_queue->count < the_queue->capacity) {
|
||||||
|
uint32_t i = the_queue->eoq;
|
||||||
|
void *p = the_queue->items + the_queue->item_size * i;
|
||||||
|
// void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq;
|
||||||
|
memcpy(p, the_stuff, the_queue->item_size);
|
||||||
|
|
||||||
|
// update the pointer
|
||||||
|
i++;
|
||||||
|
if (i == the_queue->capacity)
|
||||||
|
// fold pointer if necessary
|
||||||
|
i = 0;
|
||||||
|
the_queue->eoq = i;
|
||||||
|
the_queue->count++;
|
||||||
|
// debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count,
|
||||||
|
// the_queue->capacity);
|
||||||
|
if (the_queue->count == the_queue->capacity)
|
||||||
|
debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name,
|
||||||
|
the_queue->count);
|
||||||
|
rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item",
|
||||||
|
the_queue->name);
|
||||||
|
} else {
|
||||||
|
response = EWOULDBLOCK; // a bit arbitrary, this.
|
||||||
|
debug(3,
|
||||||
|
"metadata queue \"%s\": is already full with %d items in it. Not adding this item to "
|
||||||
|
"the queue.",
|
||||||
|
the_queue->name, the_queue->count);
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1); // unlock the queue lock.
|
||||||
|
} else {
|
||||||
|
debug(1, "Adding an item to a NULL queue");
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) {
|
||||||
|
int rc;
|
||||||
|
if (the_queue) {
|
||||||
|
rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "metadata queue \"%s\": error locking for pc_queue_get_item", the_queue->name);
|
||||||
|
pthread_cleanup_push(pc_queue_cleanup_handler, (void *)the_queue);
|
||||||
|
while (the_queue->count == 0) {
|
||||||
|
rc = pthread_cond_wait(&the_queue->pc_queue_item_added_signal, &the_queue->pc_queue_lock);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "metadata queue \"%s\": error waiting for item to be added", the_queue->name);
|
||||||
|
}
|
||||||
|
uint32_t i = the_queue->toq;
|
||||||
|
// void * p = &the_queue->qbase + the_queue->item_size*the_queue->toq;
|
||||||
|
void *p = the_queue->items + the_queue->item_size * i;
|
||||||
|
memcpy(the_stuff, p, the_queue->item_size);
|
||||||
|
|
||||||
|
// update the pointer
|
||||||
|
i++;
|
||||||
|
if (i == the_queue->capacity)
|
||||||
|
// fold pointer if necessary
|
||||||
|
i = 0;
|
||||||
|
the_queue->toq = i;
|
||||||
|
the_queue->count--;
|
||||||
|
debug(4, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count,
|
||||||
|
the_queue->capacity);
|
||||||
|
rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name);
|
||||||
|
pthread_cleanup_pop(1); // unlock the queue lock.
|
||||||
|
} else {
|
||||||
|
debug(1, "Removing an item from a NULL queue");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
pthread_mutex_t pc_queue_lock;
|
||||||
|
pthread_cond_t pc_queue_item_added_signal;
|
||||||
|
pthread_cond_t pc_queue_item_removed_signal;
|
||||||
|
char *name;
|
||||||
|
size_t item_size; // number of bytes in each item
|
||||||
|
uint32_t count; // number of items in the queue
|
||||||
|
uint32_t capacity; // maximum number of items
|
||||||
|
uint32_t toq; // first item to take
|
||||||
|
uint32_t eoq; // free space at end of queue
|
||||||
|
void *items; // a pointer to where the items are actually stored
|
||||||
|
} pc_queue; // producer-consumer queue
|
||||||
|
|
||||||
|
void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items,
|
||||||
|
const char *name);
|
||||||
|
|
||||||
|
void pc_queue_delete(pc_queue *the_queue);
|
||||||
|
int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block);
|
||||||
|
int pc_queue_get_item(pc_queue *the_queue, void *the_stuff);
|
||||||
+186
@@ -0,0 +1,186 @@
|
|||||||
|
#include "pipe.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "pc_queue.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
static int fd = -1; // the pipe file descriptor
|
||||||
|
|
||||||
|
pc_queue metadata_queue;
|
||||||
|
#define metadata_queue_size 500
|
||||||
|
metadata_package metadata_queue_items[metadata_queue_size];
|
||||||
|
pthread_t metadata_thread;
|
||||||
|
|
||||||
|
void metadata_open(void) {
|
||||||
|
if (config.metadata_enabled == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t pl = strlen(config.metadata_pipename) + 1;
|
||||||
|
|
||||||
|
char *path = malloc(pl + 1);
|
||||||
|
snprintf(path, pl + 1, "%s", config.metadata_pipename);
|
||||||
|
|
||||||
|
fd = try_to_open_pipe_for_writing(path);
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void metadata_close(void) {
|
||||||
|
if (fd < 0)
|
||||||
|
return;
|
||||||
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) {
|
||||||
|
// debug(1, "Process metadata with type %x, code %x and length %u.", type, code, length);
|
||||||
|
int ret = 0;
|
||||||
|
// readers may go away and come back
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
metadata_open();
|
||||||
|
if (fd < 0)
|
||||||
|
return;
|
||||||
|
char thestring[1024];
|
||||||
|
snprintf(thestring, 1024, "<item><type>%x</type><code>%x</code><length>%u</length>", type, code,
|
||||||
|
length);
|
||||||
|
// ret = non_blocking_write(fd, thestring, strlen(thestring));
|
||||||
|
ret = write(fd, thestring, strlen(thestring));
|
||||||
|
if (ret < 0) {
|
||||||
|
// debug(1,"metadata_process error %d exit 1",ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((data != NULL) && (length > 0)) {
|
||||||
|
snprintf(thestring, 1024, "\n<data encoding=\"base64\">\n");
|
||||||
|
// ret = non_blocking_write(fd, thestring, strlen(thestring));
|
||||||
|
ret = write(fd, thestring, strlen(thestring));
|
||||||
|
if (ret < 0) {
|
||||||
|
// debug(1,"metadata_process error %d exit 2",ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// here, we write the data in base64 form using our nice base64 encoder
|
||||||
|
// but, we break it into lines of 76 output characters, except for the last
|
||||||
|
// one.
|
||||||
|
// thus, we send groups of (76/4)*3 = 57 bytes to the encoder at a time
|
||||||
|
size_t remaining_count = length;
|
||||||
|
char *remaining_data = data;
|
||||||
|
// size_t towrite_count;
|
||||||
|
char outbuf[76];
|
||||||
|
while ((remaining_count) && (ret >= 0)) {
|
||||||
|
size_t towrite_count = remaining_count;
|
||||||
|
if (towrite_count > 57)
|
||||||
|
towrite_count = 57;
|
||||||
|
size_t outbuf_size = 76; // size of output buffer on entry, length of result on exit
|
||||||
|
if (base64_encode_so((unsigned char *)remaining_data, towrite_count, outbuf, &outbuf_size) ==
|
||||||
|
NULL)
|
||||||
|
debug(1, "Error encoding base64 data.");
|
||||||
|
// debug(1,"Remaining count: %d ret: %d, outbuf_size:
|
||||||
|
// %d.",remaining_count,ret,outbuf_size);
|
||||||
|
// ret = non_blocking_write(fd, outbuf, outbuf_size);
|
||||||
|
ret = write(fd, outbuf, outbuf_size);
|
||||||
|
if (ret < 0) {
|
||||||
|
// debug(1,"metadata_process error %d exit 3",ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
remaining_data += towrite_count;
|
||||||
|
remaining_count -= towrite_count;
|
||||||
|
}
|
||||||
|
snprintf(thestring, 1024, "</data>");
|
||||||
|
// ret = non_blocking_write(fd, thestring, strlen(thestring));
|
||||||
|
ret = write(fd, thestring, strlen(thestring));
|
||||||
|
if (ret < 0) {
|
||||||
|
// debug(1,"metadata_process error %d exit 4",ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
snprintf(thestring, 1024, "</item>\n");
|
||||||
|
// ret = non_blocking_write(fd, thestring, strlen(thestring));
|
||||||
|
ret = write(fd, thestring, strlen(thestring));
|
||||||
|
if (ret < 0) {
|
||||||
|
// debug(1,"metadata_process error %d exit 5",ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_thread_cleanup_function(__attribute__((unused)) void *arg) {
|
||||||
|
// debug(2, "metadata_thread_cleanup_function called");
|
||||||
|
metadata_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void *metadata_thread_function(__attribute__((unused)) void *ignore) {
|
||||||
|
// #include <syscall.h>
|
||||||
|
// debug(1, "metadata_thread_function PID %d", syscall(SYS_gettid));
|
||||||
|
metadata_package pack;
|
||||||
|
pthread_cleanup_push(metadata_thread_cleanup_function, NULL);
|
||||||
|
while (1) {
|
||||||
|
pc_queue_get_item(&metadata_queue, &pack);
|
||||||
|
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
|
||||||
|
if (config.metadata_enabled) {
|
||||||
|
if (pack.carrier) {
|
||||||
|
debug(4, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code,
|
||||||
|
pack.length, pack.carrier->index_number);
|
||||||
|
} else {
|
||||||
|
debug(4, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length);
|
||||||
|
}
|
||||||
|
metadata_process(pack.type, pack.code, pack.data, pack.length);
|
||||||
|
debug(4, " pipe: done.");
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1);
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1); // will never happen
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_pipe_queue_init() {
|
||||||
|
// create the metadata pipe, if necessary
|
||||||
|
size_t pl = strlen(config.metadata_pipename) + 1;
|
||||||
|
char *path = malloc(pl + 1);
|
||||||
|
snprintf(path, pl + 1, "%s", config.metadata_pipename);
|
||||||
|
mode_t oldumask = umask(000);
|
||||||
|
if (mkfifo(path, 0666) && errno != EEXIST)
|
||||||
|
die("Could not create metadata pipe \"%s\".", path);
|
||||||
|
umask(oldumask);
|
||||||
|
debug(1, "metadata pipe name is \"%s\".", path);
|
||||||
|
|
||||||
|
// try to open it
|
||||||
|
fd = try_to_open_pipe_for_writing(path);
|
||||||
|
// we check that it's not a "real" error. From the "man 2 open" page:
|
||||||
|
// "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO
|
||||||
|
// open for reading." Which is okay.
|
||||||
|
if ((fd == -1) && (errno != ENXIO)) {
|
||||||
|
char errorstring[1024];
|
||||||
|
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||||
|
debug(1, "metadata_init -- error %d (\"%s\") opening pipe: \"%s\".", errno,
|
||||||
|
(char *)errorstring, path);
|
||||||
|
warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno,
|
||||||
|
(char *)errorstring, path);
|
||||||
|
}
|
||||||
|
free(path);
|
||||||
|
|
||||||
|
// initialise the metadata queues first, otherwise the might be a race condition
|
||||||
|
// create a pc_queue for the metadata pipe
|
||||||
|
pc_queue_init(&metadata_queue, (char *)&metadata_queue_items, sizeof(metadata_package),
|
||||||
|
metadata_queue_size, "pipe");
|
||||||
|
|
||||||
|
if (named_pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL,
|
||||||
|
"metadata pipe") != 0)
|
||||||
|
debug(1, "Failed to create metadata thread!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_pipe_queue_stop() {
|
||||||
|
if (metadata_thread) {
|
||||||
|
// debug(2, "metadata stop metadata_thread thread.");
|
||||||
|
pthread_cancel(metadata_thread);
|
||||||
|
pthread_join(metadata_thread, NULL);
|
||||||
|
pc_queue_delete(&metadata_queue);
|
||||||
|
// debug(2, "metadata_stop finished successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block) {
|
||||||
|
return send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "rtsp.h"
|
||||||
|
|
||||||
|
void metadata_pipe_queue_init();
|
||||||
|
void metadata_pipe_queue_stop();
|
||||||
|
int send_metadata_to_pipe_queue(const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block);
|
||||||
+17
-1
@@ -35,9 +35,11 @@
|
|||||||
|
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "metadata_hub.h"
|
#include "metadata/hub.h"
|
||||||
#include "mpris-service.h"
|
#include "mpris-service.h"
|
||||||
|
|
||||||
static guint ownerID = 0;
|
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,
|
static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("nextitem");
|
send_simple_dacp_command("nextitem");
|
||||||
|
#endif
|
||||||
media_player2_player_complete_next(skeleton, invocation);
|
media_player2_player_complete_next(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("previtem");
|
send_simple_dacp_command("previtem");
|
||||||
|
#endif
|
||||||
media_player2_player_complete_previous(skeleton, invocation);
|
media_player2_player_complete_previous(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean on_handle_stop(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
static gboolean on_handle_stop(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("stop");
|
send_simple_dacp_command("stop");
|
||||||
|
#endif
|
||||||
media_player2_player_complete_stop(skeleton, invocation);
|
media_player2_player_complete_stop(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("pause");
|
send_simple_dacp_command("pause");
|
||||||
|
#endif
|
||||||
media_player2_player_complete_pause(skeleton, invocation);
|
media_player2_player_complete_pause(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -269,14 +279,18 @@ static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocat
|
|||||||
static gboolean on_handle_play_pause(MediaPlayer2Player *skeleton,
|
static gboolean on_handle_play_pause(MediaPlayer2Player *skeleton,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("playpause");
|
send_simple_dacp_command("playpause");
|
||||||
|
#endif
|
||||||
media_player2_player_complete_play_pause(skeleton, invocation);
|
media_player2_player_complete_play_pause(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command("play");
|
send_simple_dacp_command("play");
|
||||||
|
#endif
|
||||||
media_player2_player_complete_play(skeleton, invocation);
|
media_player2_player_complete_play(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -284,11 +298,13 @@ static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocati
|
|||||||
static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton,
|
static gboolean on_handle_set_volume(MediaPlayer2Player *skeleton,
|
||||||
GDBusMethodInvocation *invocation, const gdouble volume,
|
GDBusMethodInvocation *invocation, const gdouble volume,
|
||||||
__attribute__((unused)) gpointer user_data) {
|
__attribute__((unused)) gpointer user_data) {
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
double ap_volume = mpris_volume_to_airplay_volume(volume);
|
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);
|
debug(2, "Set mpris volume to %.6f, i.e. airplay volume to %.6f.", volume, ap_volume);
|
||||||
char command[256] = "";
|
char command[256] = "";
|
||||||
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", ap_volume);
|
snprintf(command, sizeof(command), "setproperty?dmcp.device-volume=%.6f", ap_volume);
|
||||||
send_simple_dacp_command(command);
|
send_simple_dacp_command(command);
|
||||||
|
#endif
|
||||||
media_player2_player_complete_play(skeleton, invocation);
|
media_player2_player_complete_play(skeleton, invocation);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,21 @@
|
|||||||
|
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
#include "dacp.h"
|
#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 "mqtt.h"
|
||||||
#include <mosquitto.h>
|
#include <mosquitto.h>
|
||||||
|
// this is for receiving metadata
|
||||||
|
|
||||||
|
pc_queue metadata_mqtt_queue;
|
||||||
|
#define metadata_mqtt_queue_size 500
|
||||||
|
metadata_package metadata_mqtt_queue_items[metadata_mqtt_queue_size];
|
||||||
|
pthread_t metadata_mqtt_thread;
|
||||||
|
|
||||||
// this holds the mosquitto client
|
// this holds the mosquitto client
|
||||||
struct mosquitto *global_mosq = NULL;
|
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
|
release_play_lock(NULL); // stop any current session and don't replace it
|
||||||
} else {
|
} else {
|
||||||
debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
|
debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
|
||||||
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
send_simple_dacp_command(commands[it]);
|
send_simple_dacp_command(commands[it]);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -443,3 +456,62 @@ int initialise_mqtt() {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// metadata handling stuff
|
||||||
|
void metadata_mqtt_close(void) {}
|
||||||
|
|
||||||
|
void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) {
|
||||||
|
// debug(2, "metadata_mqtt_thread_cleanup_function called");
|
||||||
|
metadata_mqtt_close();
|
||||||
|
// debug(2, "metadata_mqtt_thread_cleanup_function done");
|
||||||
|
}
|
||||||
|
|
||||||
|
void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) {
|
||||||
|
// #include <syscall.h>
|
||||||
|
// debug(1, "metadata_mqtt_thread_function PID %d", syscall(SYS_gettid));
|
||||||
|
metadata_package pack;
|
||||||
|
pthread_cleanup_push(metadata_mqtt_thread_cleanup_function, NULL);
|
||||||
|
while (1) {
|
||||||
|
pc_queue_get_item(&metadata_mqtt_queue, &pack);
|
||||||
|
pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack);
|
||||||
|
if (config.mqtt_enabled) {
|
||||||
|
if (pack.carrier) {
|
||||||
|
debug(3,
|
||||||
|
" mqtt: type %x, code %x, length %u, message "
|
||||||
|
"%d.",
|
||||||
|
pack.type, pack.code, pack.length, pack.carrier->index_number);
|
||||||
|
} else {
|
||||||
|
debug(3, " mqtt: type %x, code %x, length %u.",
|
||||||
|
pack.type, pack.code, pack.length);
|
||||||
|
}
|
||||||
|
mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length);
|
||||||
|
debug(3, " mqtt: done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_cleanup_pop(1);
|
||||||
|
}
|
||||||
|
pthread_cleanup_pop(1); // will never happen
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void metadata_mqtt_queue_init() {
|
||||||
|
// create a pc_queue for the MQTT handler
|
||||||
|
pc_queue_init(&metadata_mqtt_queue, (char *)&metadata_mqtt_queue_items, sizeof(metadata_package),
|
||||||
|
metadata_mqtt_queue_size, "mqtt");
|
||||||
|
if (named_pthread_create(&metadata_mqtt_thread, NULL, metadata_mqtt_thread_function, NULL,
|
||||||
|
"metadata mqtt") != 0)
|
||||||
|
debug(1, "Failed to create metadata mqtt thread!");
|
||||||
|
}
|
||||||
|
void metadata_mqtt_queue_stop() {
|
||||||
|
// debug(2, "metadata stop mqtt thread.");
|
||||||
|
pthread_cancel(metadata_mqtt_thread);
|
||||||
|
pthread_join(metadata_mqtt_thread, NULL);
|
||||||
|
pc_queue_delete(&metadata_mqtt_queue);
|
||||||
|
// debug(2, "metadata stop mqtt done.");
|
||||||
|
}
|
||||||
|
int send_metadata_to_mqtt_queue(const uint32_t type, const uint32_t code,
|
||||||
|
const char *data, const uint32_t length, rtsp_message *carrier,
|
||||||
|
int block) {
|
||||||
|
return send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,10 @@ void on_connect(struct mosquitto *mosq, void *userdata, int rc);
|
|||||||
void on_disconnect(struct mosquitto *mosq, void *userdata, int rc);
|
void on_disconnect(struct mosquitto *mosq, void *userdata, int rc);
|
||||||
void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg);
|
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 _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 */
|
#endif /* #ifndef MQTT_H */
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<property name="ElapsedTime" type="b" access="readwrite" />
|
<property name="ElapsedTime" type="b" access="readwrite" />
|
||||||
<property name="DeltaTime" type="b" access="readwrite" />
|
<property name="DeltaTime" type="b" access="readwrite" />
|
||||||
<property name="FileAndLine" type="b" access="readwrite" />
|
<property name="FileAndLine" type="b" access="readwrite" />
|
||||||
</interface>
|
</interface>
|
||||||
<interface name="org.gnome.ShairportSync.RemoteControl">
|
<interface name="org.gnome.ShairportSync.RemoteControl">
|
||||||
<method name='FastForward'/>
|
<method name='FastForward'/>
|
||||||
<method name='Rewind'/>
|
<method name='Rewind'/>
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ hash_init(enum hash_alg alg, HashCTX *c)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
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
|
#if CONFIG_OPENSSL
|
||||||
switch (alg)
|
switch (alg)
|
||||||
|
|||||||
@@ -593,7 +593,7 @@ encrypt_ctr(unsigned char *ciphertext, int ciphertext_len,
|
|||||||
/* -------------------------- IMPLEMENTATION -------------------------------- */
|
/* -------------------------- IMPLEMENTATION -------------------------------- */
|
||||||
|
|
||||||
static int
|
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;
|
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;
|
struct pair_client_setup_context *sctx = &handle->sctx.client;
|
||||||
char *ptr;
|
char *ptr;
|
||||||
int i;
|
unsigned int i;
|
||||||
|
|
||||||
// Last 32 bytes of the private key is the public key, so we don't need to
|
// Last 32 bytes of the private key is the public key, so we don't need to
|
||||||
// explicitly export that
|
// explicitly export that
|
||||||
@@ -867,13 +867,13 @@ client_setup_result(struct pair_setup_context *handle)
|
|||||||
|
|
||||||
|
|
||||||
static int
|
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;
|
struct pair_client_verify_context *vctx = &handle->vctx.client;
|
||||||
char hex[] = { 0, 0, 0 };
|
char hex[] = { 0, 0, 0 };
|
||||||
size_t hexkey_len;
|
size_t hexkey_len;
|
||||||
const char *ptr;
|
const char *ptr;
|
||||||
int i;
|
unsigned int i;
|
||||||
|
|
||||||
if (!is_initialized())
|
if (!is_initialized())
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1024,7 +1024,7 @@ client_verify_response1(struct pair_verify_context *handle, const uint8_t *data,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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;
|
struct pair_client_verify_context *vctx = &handle->vctx.client;
|
||||||
// TODO actually check response
|
// TODO actually check response
|
||||||
|
|||||||
@@ -822,7 +822,7 @@ static void
|
|||||||
hexread(uint8_t *out, size_t out_len, const char *in)
|
hexread(uint8_t *out, size_t out_len, const char *in)
|
||||||
{
|
{
|
||||||
char hex[] = { 0, 0, 0 };
|
char hex[] = { 0, 0, 0 };
|
||||||
int i;
|
unsigned int i;
|
||||||
|
|
||||||
for (i = 0; i < out_len; i++, in+=2)
|
for (i = 0; i < out_len; i++, in+=2)
|
||||||
{
|
{
|
||||||
@@ -1668,7 +1668,7 @@ static int
|
|||||||
client_setup_result(struct pair_setup_context *handle)
|
client_setup_result(struct pair_setup_context *handle)
|
||||||
{
|
{
|
||||||
char *ptr;
|
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);
|
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
|
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;
|
struct pair_client_verify_context *vctx = &handle->vctx.client;
|
||||||
size_t hexkey_len;
|
size_t hexkey_len;
|
||||||
@@ -2888,7 +2888,7 @@ server_list_response(size_t *len, pair_list_cb cb, void *cb_arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
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 =
|
// Skip reading the request, it just has state = 1 and pair method =
|
||||||
// PairingMethodListPairings
|
// PairingMethodListPairings
|
||||||
|
|||||||
@@ -72,8 +72,12 @@
|
|||||||
#include <FFTConvolver/convolver.h>
|
#include <FFTConvolver/convolver.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_METADATA
|
||||||
|
#include "metadata/core.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA_HUB
|
#ifdef CONFIG_METADATA_HUB
|
||||||
#include "metadata_hub.h"
|
#include "metadata/hub.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_DACP_CLIENT
|
#ifdef CONFIG_DACP_CLIENT
|
||||||
@@ -383,18 +387,6 @@ const char *get_category_string(airplay_stream_c cat) {
|
|||||||
|
|
||||||
#ifdef CONFIG_FFMPEG
|
#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) {
|
static void swr_alloc_cleanup_handler(void *arg) {
|
||||||
debug(3, "swr_alloc_cleanup_handler");
|
debug(3, "swr_alloc_cleanup_handler");
|
||||||
SwrContext **swr = arg;
|
SwrContext **swr = arg;
|
||||||
@@ -407,25 +399,9 @@ static void av_packet_alloc_cleanup_handler(void *arg) {
|
|||||||
av_packet_free(pkt);
|
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) {
|
void clear_decoding_chain(rtsp_conn_info *conn) {
|
||||||
if (conn->incoming_ssrc != 0) {
|
if (conn->incoming_ssrc != 0) {
|
||||||
// debug_mutex_lock(&conn->ab_mutex, 30000, 0);
|
avcodec_free_context(&conn->codec_context);
|
||||||
// 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
|
|
||||||
conn->incoming_ssrc = SSRC_NONE;
|
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
|
// get information about the output from the resampler
|
||||||
int64_t resampler_output_format = 0;
|
int64_t resampler_output_format = 0;
|
||||||
int resampler_channels_found = 0;
|
int resampler_channels_found = 0;
|
||||||
|
|
||||||
|
int oldState;
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||||
|
|
||||||
#if LIBAVUTIL_VERSION_MAJOR >= 57
|
#if LIBAVUTIL_VERSION_MAJOR >= 57
|
||||||
|
|
||||||
@@ -886,6 +865,8 @@ int setup_software_resampler(rtsp_conn_info *conn, ssrc_t ssrc) {
|
|||||||
free(device_channels);
|
free(device_channels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_setcancelstate(oldState, NULL);
|
||||||
|
|
||||||
if (output_channel_map_faulty != 0) {
|
if (output_channel_map_faulty != 0) {
|
||||||
once(inform("The output device's %u-channel map is incomplete or faulty: \"%s\".",
|
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);
|
conn->codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
|
||||||
break;
|
break;
|
||||||
default:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int oldState;
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
|
||||||
|
|
||||||
// Get a decoder-dependent codec context
|
// Get a decoder-dependent codec context
|
||||||
conn->codec_context = avcodec_alloc_context3(conn->codec);
|
conn->codec_context = avcodec_alloc_context3(conn->codec);
|
||||||
if (conn->codec_context == NULL) {
|
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);
|
||||||
// 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
|
||||||
// prepare to open the codec context with that codec
|
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
|
||||||
// but first, if it's the ALAC decoder, prepare a magic cookie
|
alac_ffmpeg_magic_cookie *extradata =
|
||||||
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
|
malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it
|
||||||
alac_ffmpeg_magic_cookie *extradata =
|
if (extradata == NULL)
|
||||||
malloc(sizeof(alac_ffmpeg_magic_cookie)); // might not use it
|
die("connection %d: could not allocate memory for a magic cookie.", conn->connection_number);
|
||||||
if (extradata == NULL)
|
// creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?)
|
||||||
die("Could not allocate memory for a magic cookie.");
|
memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie));
|
||||||
// creata a magic cookie preceded by the 12-byte "atom" (?) expected by FFMPEG (?)
|
extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie));
|
||||||
memset(extradata, 0, sizeof(alac_ffmpeg_magic_cookie));
|
extradata->cookie_tag = htonl('alac');
|
||||||
extradata->cookie_size = htonl(sizeof(alac_ffmpeg_magic_cookie));
|
extradata->alac_config.frameLength = htonl(352);
|
||||||
extradata->cookie_tag = htonl('alac');
|
if (ssrc == ALAC_48000_S24_2) {
|
||||||
extradata->alac_config.frameLength = htonl(352);
|
extradata->alac_config.bitDepth = 24; // Seems to be S24
|
||||||
if (ssrc == ALAC_48000_S24_2) {
|
extradata->alac_config.sampleRate = htonl(48000);
|
||||||
extradata->alac_config.bitDepth = 24; // Seems to be S24
|
} else {
|
||||||
extradata->alac_config.sampleRate = htonl(48000);
|
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 {
|
} else {
|
||||||
extradata->alac_config.bitDepth = 16; // Seems to be S16
|
conn->codec_context->extradata = NULL;
|
||||||
extradata->alac_config.sampleRate = htonl(44100);
|
}
|
||||||
|
// 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 {
|
} else {
|
||||||
conn->codec_context->extradata = NULL;
|
die("connection %d: could not allocate a codec context!", conn->connection_number);
|
||||||
}
|
}
|
||||||
pthread_cleanup_push(malloc_cleanup, &conn->codec_context->extradata);
|
pthread_setcancelstate(oldState, NULL);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
conn->input_rate = get_ssrc_rate(ssrc);
|
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)) {
|
if ((ssrc == ALAC_48000_S24_2) || (ssrc == ALAC_44100_S16_2)) {
|
||||||
conn->frames_per_packet = 352;
|
conn->frames_per_packet = 352;
|
||||||
} else {
|
} else {
|
||||||
conn->frames_per_packet = 1024;
|
conn->frames_per_packet = 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn->codec_context->sample_rate = conn->input_rate;
|
conn->codec_context->sample_rate = conn->input_rate;
|
||||||
|
|
||||||
conn->ffmpeg_decoding_chain_initialised = 1;
|
conn->ffmpeg_decoding_chain_initialised = 1;
|
||||||
pthread_cleanup_pop(0); // successful exit -- don't run the avcodec_open2_cleanup_handler
|
conn->input_format_is_valid = 1;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1142,6 +1123,9 @@ int64_t avframe_to_audio(rtsp_conn_info *conn, AVFrame *decoded_frame, uint8_t *
|
|||||||
uint8_t *pcm_audio = NULL;
|
uint8_t *pcm_audio = NULL;
|
||||||
int dst_linesize;
|
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);
|
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.",
|
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;
|
*decoded_audio_samples_count = 0;
|
||||||
}
|
}
|
||||||
av_freep(&pcm_audio);
|
av_freep(&pcm_audio);
|
||||||
return swr_get_delay(
|
int64_t response = swr_get_delay(
|
||||||
conn->swr,
|
conn->swr,
|
||||||
RATE_FROM_ENCODED_FORMAT(
|
RATE_FROM_ENCODED_FORMAT(
|
||||||
config.current_output_configuration)); // number of frames left in the resampler
|
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.
|
// 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
|
// transcoded and maybe resampled later
|
||||||
AVFrame *block_to_avframe(rtsp_conn_info *conn, uint8_t *incoming_data,
|
AVFrame *block_to_avframe(rtsp_conn_info *conn, uint8_t *incoming_data,
|
||||||
size_t incoming_data_length) {
|
size_t incoming_data_length) {
|
||||||
|
|
||||||
AVFrame *decoded_frame = NULL;
|
AVFrame *decoded_frame = NULL;
|
||||||
if (incoming_data_length > 8) {
|
if (incoming_data_length > 8) {
|
||||||
|
int oldState;
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
|
||||||
AVPacket *pkt = av_packet_alloc();
|
AVPacket *pkt = av_packet_alloc();
|
||||||
if (pkt) {
|
if (pkt) {
|
||||||
// push a deallocator -- av_packet_free(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 {
|
} else {
|
||||||
debug(1, "Can't allocate an AVPacket!");
|
debug(1, "Can't allocate an AVPacket!");
|
||||||
}
|
}
|
||||||
|
pthread_setcancelstate(oldState, NULL);
|
||||||
}
|
}
|
||||||
return decoded_frame;
|
return decoded_frame;
|
||||||
}
|
}
|
||||||
@@ -2032,7 +2022,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
|
|||||||
do {
|
do {
|
||||||
// debug(3, "buffer_get_frame is iterating");
|
// debug(3, "buffer_get_frame is iterating");
|
||||||
// we must have timing information before we can do anything here
|
// 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();
|
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)) /
|
RATE_FROM_ENCODED_FORMAT(config.current_output_configuration)) /
|
||||||
1000000000;
|
1000000000;
|
||||||
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
|
int64_t exact_frame_gap = gross_frame_gap - dac_delay;
|
||||||
debug(3,
|
|
||||||
|
|
||||||
|
debug(4,
|
||||||
"Exact frame gap: %" PRId64
|
"Exact frame gap: %" PRId64
|
||||||
". DAC delay: %ld. First packet timestamp: %u.",
|
". DAC delay: %ld. Total: %" PRId64 ". First packet timestamp: %u. ",
|
||||||
exact_frame_gap, dac_delay, conn->first_packet_timestamp);
|
exact_frame_gap, dac_delay, gross_frame_gap, conn->first_packet_timestamp);
|
||||||
// int64_t frames_needed_to_maintain_desired_buffer =
|
// int64_t frames_needed_to_maintain_desired_buffer =
|
||||||
// (int64_t)(config.audio_backend_buffer_desired_length *
|
// (int64_t)(config.audio_backend_buffer_desired_length *
|
||||||
// config.current_output_configuration->rate) -
|
// 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
|
// wait if the buffer is empty
|
||||||
if ((conn->ab_synced != 0) && (conn->ab_read == conn->ab_write)) { // the buffer is empty!
|
if ((conn->ab_synced != 0) && (conn->ab_read == conn->ab_write)) { // the buffer is empty!
|
||||||
if (notified_buffer_empty == 0) {
|
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;
|
notified_buffer_empty = 1;
|
||||||
// reset_input_flow_metrics(conn); // don't do a full flush parameters reset
|
// reset_input_flow_metrics(conn); // don't do a full flush parameters reset
|
||||||
// conn->initial_reference_time = 0;
|
// 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
|
wait = 1; // keep waiting until the timing information becomes available
|
||||||
}
|
}
|
||||||
if (wait) {
|
if (wait) {
|
||||||
if (conn->frames_per_packet == 0)
|
uint64_t time_to_wait_for_wakeup_ns = 10000000; // default
|
||||||
debug(1, "frames_per_packet is zero!");
|
if (conn->input_format_is_valid != 0) {
|
||||||
|
time_to_wait_for_wakeup_ns = 1000000000 / conn->input_rate; // this is time period of one frame
|
||||||
uint64_t time_to_wait_for_wakeup_ns = 20000000; // default if no input rate is set
|
time_to_wait_for_wakeup_ns *= 4 * conn->frames_per_packet; // about 4 * 7 mS for 352 frames per second
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
|
#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;
|
struct timespec time_of_wakeup;
|
||||||
time_of_wakeup.tv_sec = sec;
|
time_of_wakeup.tv_sec = sec;
|
||||||
time_of_wakeup.tv_nsec = nsec;
|
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,
|
int rc = pthread_cond_timedwait(&conn->flowcontrol, &conn->ab_mutex,
|
||||||
&time_of_wakeup); // this is a pthread cancellation point
|
&time_of_wakeup); // this is a pthread cancellation point
|
||||||
if ((rc != 0) && (rc != ETIMEDOUT))
|
if ((rc != 0) && (rc != ETIMEDOUT))
|
||||||
// if (rc)
|
// if (rc)
|
||||||
debug(3, "pthread_cond_timedwait returned error code %d.", rc);
|
debug(3, "pthread_cond_timedwait returned error code %d.", rc);
|
||||||
|
// debug(1, "waited");
|
||||||
#endif
|
#endif
|
||||||
#ifdef COMPILE_FOR_OSX
|
#ifdef COMPILE_FOR_OSX
|
||||||
uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
|
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);
|
debug(1, "error %d", ret);
|
||||||
// We need to get those frames of silence out of the resampler
|
// We need to get those frames of silence out of the resampler
|
||||||
// so we'll pass in an empty AVFrame to flush them through
|
// 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 =
|
conn->frames_retained_in_the_resampler =
|
||||||
avframe_to_audio(conn, avf, &pp, &pl, &number_of_output_frames);
|
avframe_to_audio(conn, avf, &pp, &pl, &number_of_output_frames);
|
||||||
|
av_frame_free(&avf);
|
||||||
|
pthread_setcancelstate(oldState, NULL);
|
||||||
curframe->data = (short *)pp;
|
curframe->data = (short *)pp;
|
||||||
curframe->length = number_of_output_frames;
|
curframe->length = number_of_output_frames;
|
||||||
av_frame_free(&avf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifndef CONFIG_AIRPLAY_2
|
#ifndef CONFIG_AIRPLAY_2
|
||||||
@@ -5262,7 +5254,7 @@ int player_stop(rtsp_conn_info *conn) {
|
|||||||
// note -- this may be called from another connection thread.
|
// note -- this may be called from another connection thread.
|
||||||
debug(2, "Connection %d: player_stop.", conn->connection_number);
|
debug(2, "Connection %d: player_stop.", conn->connection_number);
|
||||||
int response = 0; // okay
|
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;
|
pthread_t *pt = conn->player_thread;
|
||||||
if (pt) {
|
if (pt) {
|
||||||
debug(3, "player_thread cancel...");
|
debug(3, "player_thread cancel...");
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ typedef struct {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
int connection_number; // for debug ID purposes, nothing else...
|
int connection_number; // for debug ID purposes, nothing else...
|
||||||
int is_playing; // set true by player_play, set false by player_stop
|
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
|
unsigned int sync_samples_index; // for estimating the gap between the highest and lowest timing
|
||||||
// error over the past n samples
|
// error over the past n samples
|
||||||
unsigned int sync_samples_count; // the array of samples is defined locally
|
unsigned int sync_samples_count; // the array of samples is defined locally
|
||||||
@@ -272,7 +273,7 @@ typedef struct {
|
|||||||
// otherwise
|
// otherwise
|
||||||
int software_mute_enabled; // if we don't have a real mute that we can use
|
int software_mute_enabled; // if we don't have a real mute that we can use
|
||||||
int fd;
|
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
|
char *auth_nonce; // the session nonce, if needed
|
||||||
stream_cfg stream;
|
stream_cfg stream;
|
||||||
SOCKADDR remote, local;
|
SOCKADDR remote, local;
|
||||||
@@ -412,6 +413,7 @@ typedef struct {
|
|||||||
// (will be unspecified if not build for AirPlay 2)
|
// (will be unspecified if not build for AirPlay 2)
|
||||||
|
|
||||||
#ifdef CONFIG_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
|
char *airplay_gid; // UUID in the Bonjour advertisement -- if NULL, the group UUID is the same as
|
||||||
// the pi UUID
|
// the pi UUID
|
||||||
airplay_t airplay_type; // are we using AirPlay 1 or AirPlay 2 protocol on this connection?
|
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_ap2_control_thread;
|
||||||
pthread_t rtp_realtime_audio_thread;
|
pthread_t rtp_realtime_audio_thread;
|
||||||
pthread_t rtp_buffered_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;
|
int ap2_event_receiver_exited;
|
||||||
|
|
||||||
|
|||||||
@@ -1513,9 +1513,12 @@ int frame_to_ptp_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *
|
|||||||
int32_t frame_difference = timestamp - anchor_rtptime;
|
int32_t frame_difference = timestamp - anchor_rtptime;
|
||||||
int64_t time_difference = frame_difference;
|
int64_t time_difference = frame_difference;
|
||||||
time_difference = time_difference * 1000000000;
|
time_difference = time_difference * 1000000000;
|
||||||
if (conn->input_rate == 0)
|
if (conn->input_rate == 0) {
|
||||||
die("conn->input_rate is zero!");
|
debug(1,"Connection %d: in a call to frame_to_ptp_local_time, conn->input_rate is zero!", conn->connection_number);
|
||||||
time_difference = time_difference / conn->input_rate;
|
time_difference = 0;
|
||||||
|
} else {
|
||||||
|
time_difference = time_difference / conn->input_rate;
|
||||||
|
}
|
||||||
uint64_t ltime = anchor_local_time + time_difference;
|
uint64_t ltime = anchor_local_time + time_difference;
|
||||||
*time = ltime;
|
*time = ltime;
|
||||||
result = 0;
|
result = 0;
|
||||||
|
|||||||
@@ -3,6 +3,27 @@
|
|||||||
|
|
||||||
#include "player.h"
|
#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 pthread_rwlock_t principal_conn_lock;
|
||||||
extern rtsp_conn_info *principal_conn;
|
extern rtsp_conn_info *principal_conn;
|
||||||
extern rtsp_conn_info **conns;
|
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
|
// 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);
|
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
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count);
|
ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ general =
|
|||||||
// %v for the version number, e.g. 3.0 and
|
// %v for the version number, e.g. 3.0 and
|
||||||
// %V for the full version string, e.g. 3.3-OpenSSL-Avahi-ALSA-soxr-metadata-sysconfdir:/etc
|
// %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".
|
// 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.
|
// 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.
|
// "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.
|
// "soxr" uses the SoX library to recode a packet of frames to a new packet containing more or fewer frames. This needs a processor with fast floating point capability.
|
||||||
|
|||||||
+115
-110
@@ -54,6 +54,9 @@
|
|||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
#include <uuid/uuid.h>
|
#include <uuid/uuid.h>
|
||||||
|
#include "utilities/generate_random_uuid.h"
|
||||||
|
#include "utilities/generate_device_uuid.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_MBEDTLS
|
#ifdef CONFIG_MBEDTLS
|
||||||
@@ -84,8 +87,12 @@
|
|||||||
#include "dacp.h"
|
#include "dacp.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_METADATA)
|
||||||
|
#include "metadata/core.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(CONFIG_METADATA_HUB)
|
#if defined(CONFIG_METADATA_HUB)
|
||||||
#include "metadata_hub.h"
|
#include "metadata/hub.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_DBUS_INTERFACE
|
#ifdef CONFIG_DBUS_INTERFACE
|
||||||
@@ -195,13 +202,12 @@ int has_fltp_capable_aac_decoder(void) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_SOXR
|
#ifdef CONFIG_SOXR
|
||||||
pthread_t soxr_time_check_thread;
|
pthread_t *soxr_time_check_thread = NULL;
|
||||||
int soxr_time_check_thread_started = 0;
|
|
||||||
void *soxr_time_check(__attribute__((unused)) void *arg) {
|
void *soxr_time_check(__attribute__((unused)) void *arg) {
|
||||||
// this just checks how long it takes to process adding and subtracing a frame
|
// this just checks how long it takes to process adding and subtracing a frame
|
||||||
// from a buffer at 44100
|
// from a buffer at 44100
|
||||||
// #include <syscall.h>
|
// #include <syscall.h>
|
||||||
// debug(1, "soxr_time_check PID %d", syscall(SYS_gettid));
|
// debug(1, "soxr_time_check PID %ld", syscall(SYS_gettid));
|
||||||
|
|
||||||
const int buffer_length = 352;
|
const int buffer_length = 352;
|
||||||
int32_t inbuffer[buffer_length * 2];
|
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
|
(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_!
|
// able to cancel it, do _don't even try_!
|
||||||
while (get_absolute_time_in_ns() < loop_until_time) {
|
while (get_absolute_time_in_ns() < loop_until_time) {
|
||||||
|
pthread_testcancel();
|
||||||
number_of_iterations++;
|
number_of_iterations++;
|
||||||
for (i = 0; i < buffer_length; i++) {
|
for (i = 0; i < buffer_length; i++) {
|
||||||
double w = sin(i * (frequency + number_of_iterations * 2) * 2 * M_PI / 44100);
|
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;
|
inbuffer[i * 2 + 1] = wint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int oldState;
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
|
||||||
|
|
||||||
soxr_io_spec_t io_spec;
|
soxr_io_spec_t io_spec;
|
||||||
io_spec.itype = SOXR_INT32_I;
|
io_spec.itype = SOXR_INT32_I;
|
||||||
io_spec.otype = 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.
|
outbuffer, buffer_length - 1, &odone, // Output.
|
||||||
&io_spec, // Input, output and transfer spec.
|
&io_spec, // Input, output and transfer spec.
|
||||||
NULL, NULL); // Default configuration.
|
NULL, NULL); // Default configuration.
|
||||||
|
|
||||||
|
pthread_setcancelstate(oldState, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t soxr_execution_time =
|
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.");
|
debug(1, "No soxr-timing iterations performed, so \"vernier\" iteration will be used.");
|
||||||
config.soxr_delay_index = 0; // used as a flag
|
config.soxr_delay_index = 0; // used as a flag
|
||||||
}
|
}
|
||||||
debug(2, "soxr_delay: %d nanoseconds, soxr_delay_threshold: %d milliseconds.",
|
debug(2, "soxr_delay maximum permissible: %d milliseconds, actual: %f milliseconds.",
|
||||||
config.soxr_delay_index, config.soxr_delay_threshold / 1000000);
|
config.soxr_delay_threshold / 1000000, 1E-6 * config.soxr_delay_index);
|
||||||
if ((config.packet_stuffing == ST_soxr) &&
|
if ((config.packet_stuffing == ST_soxr) &&
|
||||||
(config.soxr_delay_index > config.soxr_delay_threshold))
|
(config.soxr_delay_index > config.soxr_delay_threshold))
|
||||||
inform("Note: this device may be too slow for \"soxr\" interpolation. Consider choosing the "
|
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(" -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(" --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");
|
printf(" --logOutputLevel Log the output level setting -- a debugging option, useful for determining the optimum maximum volume.\n");
|
||||||
|
|
||||||
#ifdef CONFIG_LIBDAEMON
|
#ifdef CONFIG_LIBDAEMON
|
||||||
printf(" -d, --daemon Daemonise.\n");
|
printf(" -d, --daemon Daemonise.\n");
|
||||||
printf(" -j, --justDaemoniseNoPIDFile Daemonise without a PID file.\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(" --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(" 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");
|
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
|
#endif
|
||||||
printf(" --log-to-syslog Send debug and statistics information through syslog\n");
|
printf(" --log-to-syslog Send debug and statistics information through syslog\n");
|
||||||
printf(" If used, this should be the first command line argument.\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
|
// for unexpected circumstances
|
||||||
|
|
||||||
config.model = strdup("ShairportSync");
|
config.model = strdup("ShairportSync");
|
||||||
|
//config.model = strdup("AirPort10,115");
|
||||||
// config.model = strdup("AudioAccessory5,1");
|
// config.model = strdup("AudioAccessory5,1");
|
||||||
|
|
||||||
// config.srcvers = strdup(PACKAGE_VERSION);
|
// config.srcvers = strdup(PACKAGE_VERSION);
|
||||||
@@ -553,6 +570,9 @@ int parse_options(int argc, char **argv) {
|
|||||||
#endif
|
#endif
|
||||||
config.firmware_version = strdup(PACKAGE_VERSION);
|
config.firmware_version = strdup(PACKAGE_VERSION);
|
||||||
|
|
||||||
|
free(config.firmware_version);
|
||||||
|
config.firmware_version = strdup("5.2");
|
||||||
|
|
||||||
#ifdef CONFIG_METADATA
|
#ifdef CONFIG_METADATA
|
||||||
/* Get the metadata setting. */
|
/* Get the metadata setting. */
|
||||||
config.metadata_enabled = 1; // if metadata support is included, then enable it by default
|
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)
|
// not made active again (not used)
|
||||||
#endif
|
#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;
|
// config_setting_t *setting;
|
||||||
const char *str = NULL;
|
const char *str = NULL;
|
||||||
int value = 0;
|
int value = 0;
|
||||||
@@ -665,6 +608,11 @@ int parse_options(int argc, char **argv) {
|
|||||||
|
|
||||||
// debug(1, "Looking for the configuration file \"%s\".", config.configfile);
|
// 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_init(&config_file_stuff);
|
||||||
|
|
||||||
config_file_real_path = realpath(config.configfile, NULL);
|
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.");
|
"avahi enabled, or disable remote control.");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_AIRPLAY_2
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
long long aid;
|
long long aid;
|
||||||
@@ -1481,6 +1428,8 @@ int parse_options(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now, do the command line options again, but this time do them fully -- it's a unix convention
|
// 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
|
// a uuid_t and an md5 hash are both 128 bits, 16 bytes
|
||||||
uuid_t result;
|
uuid_t result;
|
||||||
memset(result, 0, sizeof(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
|
// OpenSSL is mandatory for AirPlay 2 anyway
|
||||||
#ifdef CONFIG_OPENSSL
|
#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
|
// Produces a UUID string at uuid consisting of lower-case letters
|
||||||
uuid_unparse_lower(result, psi_uuid);
|
uuid_unparse_lower(result, psi_uuid);
|
||||||
config.airplay_psi = psi_uuid;
|
config.airplay_psi = psi_uuid;
|
||||||
|
|
||||||
debug(3, "size of pk is %zu.", sizeof(config.airplay_pk));
|
debug(3, "size of pk is %zu.", sizeof(config.airplay_pk));
|
||||||
|
|
||||||
pair_public_key_get(PAIR_SERVER_HOMEKIT, config.airplay_pk, config.airplay_device_id);
|
pair_public_key_get(PAIR_SERVER_HOMEKIT, config.airplay_pk, config.airplay_device_id);
|
||||||
char buf[128];
|
char buf[128];
|
||||||
char *ptr = buf;
|
char *ptr = buf;
|
||||||
@@ -1637,18 +1589,38 @@ int parse_options(int argc, char **argv) {
|
|||||||
ptr += sprintf(ptr, "%02x", config.airplay_pk[pk_index]);
|
ptr += sprintf(ptr, "%02x", config.airplay_pk[pk_index]);
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
config.pk_string = strdup(buf);
|
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
|
#ifdef CONFIG_METADATA
|
||||||
// If we are asking for metadata, turn on the relevant bits
|
// 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
|
// If bit 50 is set, metadata is sent via plists in POST /command payloads.
|
||||||
config.airplay_features |= (uint64_t)1 << 17; // text, 50 is in a binary plist
|
// The data consists of textual information about what is playing and cover art
|
||||||
// config.airplay_features |=
|
// It does not seem possible to turn off the cover art.
|
||||||
// (uint64_t)1 << 50; // binary plist
|
|
||||||
|
|
||||||
// If we are asking for artwork, turn on the relevant bit
|
// While bit 50 is set, no data comes through the "classic" way. That is
|
||||||
if (config.get_coverart)
|
// no progress, text or picture data comes through in the way that
|
||||||
config.airplay_features |= (uint64_t)1 << 15; // artwork
|
// 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
|
#endif
|
||||||
|
|
||||||
@@ -1669,6 +1641,23 @@ int parse_options(int argc, char **argv) {
|
|||||||
if (padding)
|
if (padding)
|
||||||
*padding = 0;
|
*padding = 0;
|
||||||
debug(2, "airplay_fex is \"%s\"", config.airplay_fex);
|
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
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_LIBDAEMON
|
#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
|
(config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not
|
||||||
// actually daemonised at all
|
// actually daemonised at all
|
||||||
#endif
|
#endif
|
||||||
debug(2, "exit function called...");
|
|
||||||
/*
|
/*
|
||||||
Actually, there is no terminate_mqtt() function.
|
Actually, there is no terminate_mqtt() function.
|
||||||
#ifdef CONFIG_MQTT
|
#ifdef CONFIG_MQTT
|
||||||
@@ -1902,12 +1890,16 @@ void exit_function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_SOXR
|
#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) {
|
int oldState;
|
||||||
debug(2, "Waiting for SoXr timecheck to terminate...");
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
|
||||||
pthread_join(soxr_time_check_thread, NULL);
|
if (soxr_time_check_thread != NULL) {
|
||||||
soxr_time_check_thread_started = 0;
|
pthread_cancel(*soxr_time_check_thread);
|
||||||
debug(2, "Waiting for SoXr timecheck to terminate done");
|
pthread_join(*soxr_time_check_thread, NULL);
|
||||||
|
free(soxr_time_check_thread);
|
||||||
|
soxr_time_check_thread = NULL;
|
||||||
|
}
|
||||||
|
pthread_setcancelstate(oldState, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1948,8 +1940,6 @@ void exit_function() {
|
|||||||
free(config.nqptp_shared_memory_interface_name);
|
free(config.nqptp_shared_memory_interface_name);
|
||||||
if (config.airplay_device_id)
|
if (config.airplay_device_id)
|
||||||
free(config.airplay_device_id);
|
free(config.airplay_device_id);
|
||||||
if (config.airplay_pin)
|
|
||||||
free(config.airplay_pin);
|
|
||||||
if (config.airplay_pi)
|
if (config.airplay_pi)
|
||||||
free(config.airplay_pi);
|
free(config.airplay_pi);
|
||||||
if (config.airplay_pgid)
|
if (config.airplay_pgid)
|
||||||
@@ -1994,7 +1984,7 @@ void exit_function() {
|
|||||||
#else
|
#else
|
||||||
mdns_unregister(); // once the dacp handler is done and all player threrads are done it should
|
mdns_unregister(); // once the dacp handler is done and all player threrads are done it should
|
||||||
// be safe
|
// be safe
|
||||||
debug(1, "normal exit");
|
debug(2, "normal exit");
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
debug(1, "emergency exit");
|
debug(1, "emergency exit");
|
||||||
@@ -2346,6 +2336,7 @@ int main(int argc, char **argv) {
|
|||||||
openlog(NULL, 0, LOG_DAEMON);
|
openlog(NULL, 0, LOG_DAEMON);
|
||||||
#endif
|
#endif
|
||||||
type_of_exit_cleanup = TOE_normal; // what kind of exit cleanup needed
|
type_of_exit_cleanup = TOE_normal; // what kind of exit cleanup needed
|
||||||
|
debug(1, "adding the exit function");
|
||||||
atexit(exit_function);
|
atexit(exit_function);
|
||||||
|
|
||||||
// get a device id -- the first non-local MAC address
|
// 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);
|
config.output_channel_map[i] = strdup(channel_id);
|
||||||
debug(2, "output channel %d is \"%s\".", i, config.output_channel_map[i]);
|
debug(2, "output channel %d is \"%s\".", i, config.output_channel_map[i]);
|
||||||
} else {
|
} 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);
|
channel_id, i);
|
||||||
config.output_channel_map[i] = strdup("--");
|
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);
|
debug(option_print_level, "loudness reference level is %f", config.loudness_reference_volume_db);
|
||||||
|
|
||||||
#ifdef CONFIG_SOXR
|
#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
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_FFMPEG
|
#ifdef CONFIG_FFMPEG
|
||||||
@@ -3180,7 +3185,7 @@ int main(int argc, char **argv) {
|
|||||||
if (idx != 0)
|
if (idx != 0)
|
||||||
strncat(channel_map, " ", sizeof(channel_map) - 1);
|
strncat(channel_map, " ", sizeof(channel_map) - 1);
|
||||||
strncat(channel_map, chName, sizeof(channel_map) - 1 - strlen(channel_map));
|
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 {
|
} else {
|
||||||
debug(1, "Insufficient space for the name of channel %d.", idx);
|
debug(1, "Insufficient space for the name of channel %d.", idx);
|
||||||
}
|
}
|
||||||
|
|||||||
+95
-26
@@ -23,6 +23,7 @@ SOFTWARE.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include <ctype.h> // for isprint()
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
@@ -64,13 +65,9 @@ void debug_init(int level, int show_elapsed_time, int show_relative_time, int sh
|
|||||||
debugger_show_file_and_line = show_file_and_line;
|
debugger_show_file_and_line = show_file_and_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
int debug_level() {
|
int debug_level() { return debuglev; };
|
||||||
return debuglev;
|
|
||||||
};
|
|
||||||
|
|
||||||
void set_debug_level(int level) {
|
void set_debug_level(int level) { debuglev = level; }
|
||||||
debuglev = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
void increase_debug_level() {
|
void increase_debug_level() {
|
||||||
if (debuglev < 3)
|
if (debuglev < 3)
|
||||||
@@ -82,28 +79,15 @@ void decrease_debug_level() {
|
|||||||
debuglev--;
|
debuglev--;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_show_elapsed_time() {
|
int get_show_elapsed_time() { return debugger_show_elapsed_time; }
|
||||||
return debugger_show_elapsed_time;
|
void set_show_elapsed_time(int setting) { debugger_show_elapsed_time = setting; }
|
||||||
|
int get_show_relative_timel() { return debugger_show_relative_time; }
|
||||||
}
|
|
||||||
void set_show_elapsed_time(int setting) {
|
|
||||||
debugger_show_elapsed_time = setting;
|
|
||||||
}
|
|
||||||
int get_show_relative_timel() {
|
|
||||||
return debugger_show_relative_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_show_relative_time(int setting) {
|
void set_show_relative_time(int setting) { debugger_show_relative_time = setting; }
|
||||||
debugger_show_relative_time = setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_show_file_and_line() {
|
int get_show_file_and_line() { return debugger_show_file_and_line; }
|
||||||
return debugger_show_file_and_line;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_show_file_and_line(int setting) {
|
void set_show_file_and_line(int setting) { debugger_show_file_and_line = setting; }
|
||||||
debugger_show_file_and_line = setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss, double tsl,
|
char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss, double tsl,
|
||||||
const char *filename, const int linenumber, const char *prefix) {
|
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);
|
pthread_setcancelstate(oldState, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *ibuf,
|
||||||
|
size_t buf_len) {
|
||||||
|
|
||||||
|
if (level > debuglev)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char *vbuf = (char *)ibuf; // this to stop the compiler complaining about indexing through a void
|
||||||
|
unsigned int i;
|
||||||
|
unsigned int hexdump_cols = 16;
|
||||||
|
|
||||||
|
// 0x123456: <hexdump_cols * 3> <hexdump_cols>\n
|
||||||
|
const size_t buffer_size = 2 + 6 + 2 + hexdump_cols * 3 + hexdump_cols + 1;
|
||||||
|
char *buf = malloc(buffer_size);
|
||||||
|
if (buf) {
|
||||||
|
//char *bufp = buf;
|
||||||
|
// *buf = '\0';
|
||||||
|
|
||||||
|
// if (msg)
|
||||||
|
// printf("%s", msg);
|
||||||
|
|
||||||
|
#define ADDR_PREFIX_LEN 10 // "0x000000: "
|
||||||
|
|
||||||
|
// each complete row
|
||||||
|
for (i = 0; i < (buf_len / hexdump_cols); i++) {
|
||||||
|
char *bufp = buf;
|
||||||
|
*buf = '\0';
|
||||||
|
snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06x ", (i * hexdump_cols) & 0xFFFFFF);
|
||||||
|
bufp += ADDR_PREFIX_LEN;
|
||||||
|
|
||||||
|
unsigned int j;
|
||||||
|
for (j = 0; j < hexdump_cols; j++) {
|
||||||
|
snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[i * hexdump_cols + j]);
|
||||||
|
bufp += strlen("12 ");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < hexdump_cols; j++) {
|
||||||
|
if (isprint(vbuf[i * hexdump_cols + j])) {
|
||||||
|
*bufp = 0xFF & vbuf[i * hexdump_cols + j];
|
||||||
|
} else {
|
||||||
|
*bufp = '.';
|
||||||
|
}
|
||||||
|
bufp++;
|
||||||
|
}
|
||||||
|
*bufp = '\0';
|
||||||
|
_debug(thefilename, linenumber, level, "%s", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t remaining = buf_len % hexdump_cols;
|
||||||
|
if (remaining != 0) {
|
||||||
|
size_t starting_offset = buf_len - remaining;
|
||||||
|
char *bufp = buf;
|
||||||
|
*buf = '\0';
|
||||||
|
snprintf(bufp, ADDR_PREFIX_LEN + 1,"0x%06zx ", starting_offset & 0xFFFFFF);
|
||||||
|
bufp += ADDR_PREFIX_LEN;
|
||||||
|
|
||||||
|
unsigned int j;
|
||||||
|
for (j = 0; j < hexdump_cols; j++) {
|
||||||
|
if (j < remaining) {
|
||||||
|
snprintf(bufp, strlen("12 ") + 1, "%02x ", 0xFF & vbuf[starting_offset + j]);
|
||||||
|
} else {
|
||||||
|
snprintf(bufp, strlen(" ") + 1, " "); // fille with blanks
|
||||||
|
}
|
||||||
|
bufp += strlen("12 ");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < remaining; j++) {
|
||||||
|
if (isprint(vbuf[i * hexdump_cols + j])) {
|
||||||
|
*bufp = 0xFF & vbuf[starting_offset + j];
|
||||||
|
} else {
|
||||||
|
*bufp = '.';
|
||||||
|
}
|
||||||
|
bufp++;
|
||||||
|
}
|
||||||
|
*bufp = '\0';
|
||||||
|
_debug(thefilename, linenumber, level, "%s", buf);
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf,
|
void _debug_print_buffer(const char *thefilename, const int linenumber, int level, void *vbuf,
|
||||||
size_t buf_len) {
|
size_t buf_len) {
|
||||||
if (level > debuglev)
|
if (level > debuglev)
|
||||||
return;
|
return;
|
||||||
|
new_debug_print_buffer(thefilename, linenumber, level, vbuf, buf_len);
|
||||||
char *buf = (char *)vbuf;
|
char *buf = (char *)vbuf;
|
||||||
char *obf =
|
char *obf =
|
||||||
malloc(buf_len * 4 + 1); // to be on the safe side -- 4 characters on average for each byte
|
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);
|
_debug(thefilename, linenumber, level, "%s", obf);
|
||||||
free(obf);
|
free(obf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Generate a device-related UUID. This file is part of Shairport Sync
|
||||||
|
* Copyright (c) Mike Brady 2026
|
||||||
|
|
||||||
|
* Modifications, including those associated with audio synchronization, multithreading and
|
||||||
|
* metadata handling copyright (c) Mike Brady 2014--2025
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <uuid/uuid.h>
|
||||||
|
|
||||||
|
#include "generate_device_uuid.h"
|
||||||
|
#include "definitions.h"
|
||||||
|
|
||||||
|
// user is responsible for deallocating returned string
|
||||||
|
char *generate_device_uuid(const char *device_id) {
|
||||||
|
uuid_t namespace_uuid;
|
||||||
|
uuid_t derived_uuid;
|
||||||
|
|
||||||
|
uuid_parse(SHAIRPORT_SYNC_DEVICE_NAMESPACE, namespace_uuid);
|
||||||
|
uuid_generate_sha1(derived_uuid, namespace_uuid, device_id, strlen(device_id));
|
||||||
|
|
||||||
|
char *uuid = malloc(UUID_STR_LEN + 1);
|
||||||
|
uuid_unparse_lower(derived_uuid, uuid);
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
char *generate_device_uuid(const char *device_id);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Generate a random UUID. This file is part of Shairport Sync
|
||||||
|
* Copyright (c) Mike Brady 2026
|
||||||
|
|
||||||
|
* Modifications, including those associated with audio synchronization, multithreading and
|
||||||
|
* metadata handling copyright (c) Mike Brady 2014--2025
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation
|
||||||
|
* files (the "Software"), to deal in the Software without
|
||||||
|
* restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <uuid/uuid.h>
|
||||||
|
|
||||||
|
#include "generate_random_uuid.h"
|
||||||
|
|
||||||
|
// user is responsible for deallocating returned string
|
||||||
|
char *generate_random_uuid() {
|
||||||
|
// generate a UUID
|
||||||
|
// from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c
|
||||||
|
// with thanks
|
||||||
|
uuid_t binuuid;
|
||||||
|
uuid_generate_random(binuuid);
|
||||||
|
|
||||||
|
char *uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end
|
||||||
|
// Produces a UUID string at uuid consisting of lower-case letters
|
||||||
|
uuid_unparse_lower(binuuid, uuid);
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef _GENERATE_RANDOM_UUID_H
|
||||||
|
#define _GENERATE_RANDOM_UUID_H
|
||||||
|
|
||||||
|
char *generate_random_uuid();
|
||||||
|
|
||||||
|
#endif // _GENERATE_RANDOM_UUID_H
|
||||||
@@ -38,8 +38,8 @@ int eintr_checked_accept(int sockfd, struct sockaddr *addr,
|
|||||||
char errorstring[1024];
|
char errorstring[1024];
|
||||||
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
|
||||||
debug(1,
|
debug(1,
|
||||||
"error %d accept()ing a socketin ap2_event_receiver: \"%s\". (Note: error %d will be ignored.)",
|
"error %d accept()ing a socket %d: \"%s\". (Note: error %d will be ignored.)",
|
||||||
errno, errorstring, EINTR);
|
errno, sockfd, errorstring, EINTR);
|
||||||
}
|
}
|
||||||
|
|
||||||
} while((response == -1) && (errno == EINTR));
|
} while((response == -1) && (errno == EINTR));
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <plist/plist.h>
|
||||||
|
#include "rtsp_message_utilities.h"
|
||||||
|
#include "../rtsp.h"
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBPLIST_GE_2_3_0
|
||||||
|
#define plist_from_memory(plist_data, length, plist) \
|
||||||
|
plist_from_memory((plist_data), (length), (plist), NULL)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// every time we want to retain or release a reference count, lock it with this
|
||||||
|
// if a reference count is read as zero, it means the it's being deallocated.
|
||||||
|
static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static int msg_indexes = 1;
|
||||||
|
|
||||||
|
void msg_retain(rtsp_message *msg) {
|
||||||
|
int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "Error %d locking reference counter lock", rc);
|
||||||
|
if (msg > (rtsp_message *)0x00010000) {
|
||||||
|
msg->referenceCount++;
|
||||||
|
debug(4, "msg_free increment reference counter message %d to %d.", msg->index_number,
|
||||||
|
msg->referenceCount);
|
||||||
|
// debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount);
|
||||||
|
rc = pthread_mutex_unlock(&reference_counter_lock);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "Error %d unlocking reference counter lock", rc);
|
||||||
|
} else {
|
||||||
|
debug(1, "invalid rtsp_message pointer 0x%" PRIxPTR " passed to retain", (uintptr_t)msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rtsp_message *msg_init(void) {
|
||||||
|
// no thread cancellation points here
|
||||||
|
int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "Error %d locking reference counter lock", rc);
|
||||||
|
|
||||||
|
rtsp_message *msg = malloc(sizeof(rtsp_message));
|
||||||
|
if (msg) {
|
||||||
|
memset(msg, 0, sizeof(rtsp_message));
|
||||||
|
msg->referenceCount = 1; // from now on, any access to this must be protected with the lock
|
||||||
|
msg->index_number = msg_indexes++;
|
||||||
|
debug(4, "msg_init message %d", msg->index_number);
|
||||||
|
} else {
|
||||||
|
die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes);
|
||||||
|
}
|
||||||
|
// debug(1,"msg_init -- create item %d.", msg->index_number);
|
||||||
|
|
||||||
|
rc = pthread_mutex_unlock(&reference_counter_lock);
|
||||||
|
if (rc)
|
||||||
|
debug(1, "Error %d unlocking reference counter lock", rc);
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
int msg_add_header(rtsp_message *msg, char *name, char *value) {
|
||||||
|
if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) {
|
||||||
|
warn("too many headers?!");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->name[msg->nheaders] = strdup(name);
|
||||||
|
msg->value[msg->nheaders] = strdup(value);
|
||||||
|
msg->nheaders++;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *msg_get_header(rtsp_message *msg, char *name) {
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; i < msg->nheaders; i++)
|
||||||
|
if (!strcasecmp(msg->name[i], name))
|
||||||
|
return msg->value[i];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
|
||||||
|
rtsp_message *msg) {
|
||||||
|
unsigned int i;
|
||||||
|
if (conn != NULL) {
|
||||||
|
if (msg->respcode != 0)
|
||||||
|
_debug(filename, linenumber, level, "Connection: %d, Response Code: %d.", conn->connection_number, msg->respcode);
|
||||||
|
for (i = 0; i < msg->nheaders; i++) {
|
||||||
|
_debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"", conn->connection_number, msg->name[i],
|
||||||
|
msg->value[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (msg->respcode != 0)
|
||||||
|
_debug(filename, linenumber, level, " Response Code: %d.", msg->respcode);
|
||||||
|
for (i = 0; i < msg->nheaders; i++) {
|
||||||
|
_debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i],
|
||||||
|
msg->value[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void msg_free(rtsp_message **msgh) {
|
||||||
|
debug_mutex_lock(&reference_counter_lock, 1000, 0);
|
||||||
|
if (*msgh > (rtsp_message *)0x00010000) {
|
||||||
|
rtsp_message *msg = *msgh;
|
||||||
|
msg->referenceCount--;
|
||||||
|
if (msg->referenceCount)
|
||||||
|
debug(4, "msg_free decrement reference counter message %d to %d", msg->index_number,
|
||||||
|
msg->referenceCount);
|
||||||
|
if (msg->referenceCount == 0) {
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; i < msg->nheaders; i++) {
|
||||||
|
free(msg->name[i]);
|
||||||
|
free(msg->value[i]);
|
||||||
|
}
|
||||||
|
if (msg->content)
|
||||||
|
free(msg->content);
|
||||||
|
// debug(1,"msg_free item %d -- free.",msg->index_number);
|
||||||
|
uintptr_t index = (msg->index_number) & 0xFFFF;
|
||||||
|
if (index == 0)
|
||||||
|
index = 0x10000; // ensure it doesn't fold to zero.
|
||||||
|
*msgh =
|
||||||
|
(rtsp_message *)(index); // put a version of the index number of the freed message in here
|
||||||
|
debug(4, "msg_free freed message %d", msg->index_number);
|
||||||
|
free(msg);
|
||||||
|
} else {
|
||||||
|
// debug(1,"msg_free item %d -- decrement reference to
|
||||||
|
// %d.",msg->index_number,msg->referenceCount);
|
||||||
|
}
|
||||||
|
} else if (*msgh != NULL) {
|
||||||
|
debug(1,
|
||||||
|
"msg_free: error attempting to free an allocated but already-freed rtsp_message, number "
|
||||||
|
"%" PRIxPTR ".",
|
||||||
|
(uintptr_t)*msgh);
|
||||||
|
}
|
||||||
|
debug_mutex_unlock(&reference_counter_lock, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int msg_handle_line(rtsp_message **pmsg, char *line) {
|
||||||
|
rtsp_message *msg = *pmsg;
|
||||||
|
|
||||||
|
if (!msg) {
|
||||||
|
msg = msg_init();
|
||||||
|
*pmsg = msg;
|
||||||
|
char *sp, *p;
|
||||||
|
sp = NULL; // this is to quieten a compiler warning
|
||||||
|
|
||||||
|
debug(4, "RTSP/HTTP Message Received: \"%s\".", line);
|
||||||
|
|
||||||
|
p = strtok_r(line, " ", &sp);
|
||||||
|
if (!p)
|
||||||
|
goto fail;
|
||||||
|
strncpy(msg->method, p, sizeof(msg->method) - 1);
|
||||||
|
|
||||||
|
p = strtok_r(NULL, " ", &sp);
|
||||||
|
if (!p)
|
||||||
|
goto fail;
|
||||||
|
strncpy(msg->path, p, sizeof(msg->path) - 1);
|
||||||
|
|
||||||
|
p = strtok_r(NULL, " ", &sp);
|
||||||
|
if (!p)
|
||||||
|
goto fail;
|
||||||
|
if ((strcmp(p, "RTSP/1.0") != 0) && (strcmp(p, "HTTP/1.1") != 0)) {
|
||||||
|
debug(1, "Problem with Message: \"%s\"", p);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(line)) {
|
||||||
|
char *p;
|
||||||
|
p = strstr(line, ": ");
|
||||||
|
if (!p) {
|
||||||
|
warn("bad header: >>%s<<", line);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
*p = 0;
|
||||||
|
p += 2;
|
||||||
|
msg_add_header(msg, line, p);
|
||||||
|
debug(4, " %s: %s.", line, p);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
char *cl = msg_get_header(msg, "Content-Length");
|
||||||
|
if (cl)
|
||||||
|
return atoi(cl);
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail:
|
||||||
|
debug(1, "msg_handle_line fail");
|
||||||
|
msg_free(pmsg);
|
||||||
|
*pmsg = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
|
||||||
|
int rtsp_message_contains_plist(rtsp_message *message) {
|
||||||
|
int reply = 0; // assume there is no plist in the message
|
||||||
|
if ((message->contentlength >= strlen("bplist00")) &&
|
||||||
|
(strstr(message->content, "bplist00") == message->content))
|
||||||
|
reply = 1;
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
plist_t plist_from_rtsp_content(rtsp_message *message) {
|
||||||
|
plist_t the_plist = NULL;
|
||||||
|
if (rtsp_message_contains_plist(message)) {
|
||||||
|
plist_from_memory(message->content, message->contentlength, &the_plist);
|
||||||
|
}
|
||||||
|
return the_plist;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *plist_as_xml_text(plist_t the_plist) {
|
||||||
|
// caller must free the returned character buffer
|
||||||
|
// convert it to xml format
|
||||||
|
uint32_t size;
|
||||||
|
char *plist_out = NULL;
|
||||||
|
plist_to_xml(the_plist, &plist_out, &size);
|
||||||
|
|
||||||
|
// put it into a NUL-terminated string
|
||||||
|
char *reply = malloc(size + 1);
|
||||||
|
if (reply) {
|
||||||
|
memcpy(reply, plist_out, size);
|
||||||
|
reply[size] = '\0';
|
||||||
|
}
|
||||||
|
if (plist_out)
|
||||||
|
free(plist_out);
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller must free the returned character buffer
|
||||||
|
char *rtsp_plist_content(rtsp_message *message) {
|
||||||
|
char *reply = NULL;
|
||||||
|
// first, check if it has binary plist content
|
||||||
|
if (rtsp_message_contains_plist(message))
|
||||||
|
reply = plist_as_xml_text(plist_from_rtsp_content(message));
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
|
||||||
|
rtsp_message *message) {
|
||||||
|
if (level > debug_level())
|
||||||
|
return;
|
||||||
|
if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list...
|
||||||
|
_debug(filename, linenumber, level, "%s", prompt);
|
||||||
|
_debug_print_msg_headers(conn, filename, linenumber, level, message);
|
||||||
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
char *plist_content = rtsp_plist_content(message);
|
||||||
|
if (plist_content) {
|
||||||
|
_debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--", message->contentlength, plist_content);
|
||||||
|
free(plist_content);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
_debug(filename, linenumber, level, " No Content Plist. Content length: %u.",
|
||||||
|
message->contentlength);
|
||||||
|
if (message->contentlength > 0) {
|
||||||
|
_debug_print_buffer(filename, linenumber, level, message->content, message->contentlength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "player.h"
|
||||||
|
#include "rtsp.h"
|
||||||
|
|
||||||
|
rtsp_message *msg_init(void);
|
||||||
|
int msg_handle_line(rtsp_message **pmsg, char *line);
|
||||||
|
int msg_add_header(rtsp_message *msg, char *name, char *value);
|
||||||
|
char *msg_get_header(rtsp_message *msg, char *name);
|
||||||
|
|
||||||
|
void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt,
|
||||||
|
rtsp_message *message);
|
||||||
|
|
||||||
|
#define debug_log_rtsp_message_conn(conn, level, prompt, message) \
|
||||||
|
_debug_log_rtsp_message(conn, __FILE__, __LINE__, level, prompt, message)
|
||||||
|
|
||||||
|
#define debug_log_rtsp_message(level, prompt, message) \
|
||||||
|
_debug_log_rtsp_message(NULL, __FILE__, __LINE__, level, prompt, message)
|
||||||
|
|
||||||
|
|
||||||
|
void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level,
|
||||||
|
rtsp_message *msg);
|
||||||
|
#define debug_print_msg_headers(level, message) \
|
||||||
|
_debug_print_msg_headers(NULL, __FILE__, __LINE__, level, message)
|
||||||
|
|
||||||
|
#define debug_print_msg_headers_conn(level, message) \
|
||||||
|
_debug_print_msg_headers(conn, __FILE__, __LINE__, level, message)
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef CONFIG_AIRPLAY_2
|
||||||
|
int rtsp_message_contains_plist(rtsp_message *message);
|
||||||
|
plist_t plist_from_rtsp_content(rtsp_message *message);
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user