This is version 5.2

This commit is contained in:
Mike Brady
2026-07-28 17:15:07 +01:00
43 changed files with 453 additions and 458 deletions
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Install Dependencies
run: sudo apt-get -y --no-install-recommends install libpipewire-0.3-dev libplist-utils xmltoman libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev libgcrypt-dev uuid-dev
- name: Configure
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Install Dependencies
run: sudo apt-get -y --no-install-recommends install libglib2.0-dev libplist-utils xmltoman libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libmosquitto-dev avahi-daemon libavahi-client-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev libgcrypt-dev
- name: Configure
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Install Dependencies
run: sudo apt-get -y --no-install-recommends install libpipewire-0.3-dev libplist-utils xmltoman libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev libgcrypt-dev
- name: Configure
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Install Dependencies
run: sudo apt-get -y --no-install-recommends install libpipewire-0.3-dev libplist-utils xmltoman libpopt-dev libdaemon-dev libconfig-dev libasound2-dev libao-dev libjack-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev
- name: Configure
@@ -11,7 +11,7 @@ jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Install Dependencies
run: |
brew install automake
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Install Dependencies
run: sudo apt-get -y --no-install-recommends install xmltoman libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
- name: Configure
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/checkout@v7.0.1
- name: Install Dependencies
run: sudo apt-get -y --no-install-recommends install xmltoman libpipewire-0.3-dev libpopt-dev libconfig-dev libasound2-dev libao-dev libjack-dev libglib2.0-dev libmosquitto-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev libavutil-dev libavcodec-dev libavformat-dev
- name: Configure
@@ -38,7 +38,7 @@ jobs:
NQPTP_BRANCH=${{ needs.docker-vars.outputs.nqptp_branch }}
steps:
- name: Checkout shairport sync repo
uses: actions/checkout@v7.0.0
uses: actions/checkout@v7.0.1
with:
fetch-depth: 0
@@ -59,7 +59,7 @@ jobs:
fi
- name: Login to Docker Registry
uses: docker/login-action@v4.2.0
uses: docker/login-action@v4.5.1
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_REGISTRY_USER }}
@@ -67,13 +67,13 @@ jobs:
if: needs.docker-vars.outputs.push_docker_image == 'true'
- name: Set up QEMU
uses: docker/setup-qemu-action@v4.1.0
uses: docker/setup-qemu-action@v4.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4.1.0
uses: docker/setup-buildx-action@v4.2.0
- name: Build and push ${{ matrix.name }}
uses: docker/build-push-action@v7.2.0
uses: docker/build-push-action@v7.3.0
env:
registry_and_name: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE_NAME }}
with:
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10.3.0
- uses: actions/stale@v10.4.0
with:
stale-issue-message: 'This issue has been inactive for 28 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
stale-pr-message: 'This PR has been inactive for 28 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.'
+1 -1
View File
@@ -14,4 +14,4 @@ The configuration file – which contains lots of documentation – is on your s
placed at `/etc/shairport-sync.conf.sample` (`/usr/local/etc/shairport-sync.conf.sample` on FreeBSD).
You can also view an online version [here](../scripts/shairport-sync.conf).
Build configuration flags are discussed [here](CONFIGURATION%20FLAGS.md).
Build configuration flags are discussed [here](../CONFIGURATION%20FLAGS.md).
+19 -5
View File
@@ -50,6 +50,7 @@ The configuration file is typically at `/etc/shairport-sync.conf` or `/usr/local
You should also remove any of the following service files that may be present:
* `/etc/systemd/system/shairport-sync.service`
* `/etc/systemd/user/shairport-sync.service`
* `/usr/lib/systemd/system/shairport-sync.service`
* `/lib/systemd/system/shairport-sync.service`
* `/lib/systemd/user/shairport-sync.service`
* `/etc/dbus-1/system.d/shairport-sync-dbus.conf`
@@ -59,6 +60,11 @@ You should also remove any of the following service files that may be present:
New service files will be installed if necessary at the `# make install` stage.
When you have finished removing the service files, perform the following command:
```
# systemctl daemon-reload
```
(In FreeBSD, there is no need to remove the file at `/usr/local/etc/rc.d/shairport-sync` – it's always replaced in the `make install` step.)
#### Reboot after Cleaning Up
If you removed any installations of Shairport Sync or any of its service or configuration files in the last three steps, you should reboot.
@@ -127,15 +133,13 @@ Enable and start the `avahi-daemon` service.
# systemctl start avahi-daemon
```
### FreeBSD
This is for FreeBSD 14.3. First, update everything:
This is for FreeBSD 15.1. First, update everything:
```
# freebsd-update fetch
# freebsd-update install
# pkg update
# pkg upgrade
```
Next, install the Avahi subsystem.
FYI, `avahi-app` is chosen because it doesn’t require X11. If you are using a GUI with your FreeBSD system, select `avahi` rather than `avahi-app`.
If you are using a GUI with your FreeBSD system, select `avahi` rather than `avahi-app`.
`nss_mdns` is included to allow FreeBSD to resolve mDNS-originated addresses – it's not actually needed by Shairport Sync. Thanks to [reidransom](https://gist.github.com/reidransom/6033227) for this.
```
# pkg install avahi-app nss_mdns
@@ -156,7 +160,7 @@ Next, install the packages that are needed for Shairport Sync and NQPTP. If you
If you are building Shairport Sync for AirPlay 2, install the following packages:
```
# pkg install git autotools pkgconf popt libconfig openssl alsa-utils libsoxr \
libplist libsodium ffmpeg libuuid vim
libplist libsodium libgcrypt ffmpeg libuuid vim
```
If you are building classic Shairport Sync, the list of packages is shorter:
@@ -181,12 +185,22 @@ Download Shairport Sync and configure, compile and install it. Before executing
* You can actually omit `--with-ffmpeg` when building classic Shairport Sync, but it is not recommended. While you'll save space (you can omit the FFMpeg libraries in the build and run-time environments), transcoding, e.g. from 44,100 to 48,000 frames per second, will not be available and less well-maintained and less secure decoders will be used.
* **Extra Features:** If you wish to add extra features, for example an extra audio backend, take a look at the [configuration flags](CONFIGURATION%20FLAGS.md). For this walkthrough, though, please do not change too much!
If you are building Shairport Sync for the first time, clone its git repository and move into it:
```
$ git clone https://github.com/mikebrady/shairport-sync.git
$ cd shairport-sync
```
Otherwise, if you have previously cloned the Shairport Sync git repository to the local directory `shairport-sync`, move into that directory and pull the updates:
```
$ cd shairport-sync
$ git pull
```
Next, perform the following steps:
```
$ autoreconf -fi # about 1.5 minutes on a Raspberry Pi B
$ ./configure --sysconfdir=/etc --with-alsa \
--with-soxr --with-avahi --with-ssl=openssl --with-systemd-startup --with-airplay-2
$ make clean # only necessary when updating, to remove outdated older object files
$ make # just over 7 minutes on a Raspberry Pi B
# make install
```
+2 -1
View File
@@ -31,7 +31,8 @@ shairport_sync_SOURCES = shairport.c bonjour_strings.c mdns.c common.c rtp.c \
player.c audio.c loudness.c activity_monitor.c \
rtsp.c \
utilities/debug.c utilities/network_utilities.c utilities/rtsp_message_utilities.c \
utilities/string_utilities.c
utilities/string_utilities.c \
utilities/exit.c
if BUILD_FOR_DARWIN
AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -Wno-deprecated-declarations -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
+1 -1
View File
@@ -131,7 +131,7 @@ void activity_monitor_signify_activity(int active) {
pthread_mutex_unlock(&activity_monitor_mutex);
}
// lock the mutex again to send a signal
pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 4);
pthread_mutex_lock_and_cleanup_push(&activity_monitor_mutex);
pthread_cond_signal(&activity_monitor_cv);
pthread_cleanup_pop(1); // release the mutex
}
+1 -2
View File
@@ -344,8 +344,7 @@ void *rtp_buffered_audio_processor(void *arg) {
}
if (finished == 0) {
pthread_cleanup_debug_mutex_lock(&conn->flush_mutex, 25000,
4); // 25 ms is a long time to wait!
pthread_mutex_lock_and_cleanup_push(&conn->flush_mutex);
if (blocks_read != 0) {
if (conn->ap2_immediate_flush_requested != 0) {
if (ap2_immediate_flush_requested == 0) {
+1 -1
View File
@@ -44,7 +44,7 @@ void decodeAndLogPlist(plist_t plist_to_log) {
// 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_mutex_lock(&conn->event_sender_mutex);
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,
+2 -2
View File
@@ -41,7 +41,7 @@ void ap2_event_receiver_cleanup_handler(void *arg) {
// this is here to ensure it's only performed once during a teardown of a ptp stream
send_ssnc_metadata('disc', conn->client_ip_string, strlen(conn->client_ip_string), 1);
#endif
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
pthread_mutex_lock(&conn->event_sender_mutex);
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
safe_socket_close(&conn->event_channel_fd);
pthread_cleanup_pop(1); // unlock the mutex
@@ -65,7 +65,7 @@ void *ap2_event_receiver(void *arg) {
memset(&remote_addr, 0, sizeof(remote_addr));
socklen_t addr_size = sizeof(remote_addr);
debug_mutex_lock(&conn->event_sender_mutex, 1000000, 4);
pthread_mutex_lock(&conn->event_sender_mutex);
pthread_cleanup_push(mutex_unlock, &conn->event_sender_mutex);
conn->event_channel_fd =
eintr_checked_accept(conn->event_socket, (struct sockaddr *)&remote_addr, &addr_size);
+29 -30
View File
@@ -1,7 +1,7 @@
/*
* libalsa output driver. This file is part of Shairport.
* Copyright (c) Muffinman, Skaman 2013
* Copyright (c) Mike Brady 2014--2025
* Copyright (c) Mike Brady 2014--2026
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -212,9 +212,10 @@ void handle_unfixable_error(int errorCode) {
if (config.cmd_unfixable) {
command_execute(config.cmd_unfixable, messageString, 1);
} else {
die("An unrecoverable error, \"output_device_error_%d\", has been "
"detected. Doing an emergency exit, as no run_this_if_an_unfixable_error_is_detected "
"program.",
pthread_mutex_unlock(&alsa_mutex); // release the alsa mutex to allow a clean exit
die("an unrecoverable error, \"output_device_error_%d\", has been "
"detected. Doing an emergency exit, as no \"run_this_if_an_unfixable_error_is_detected\" "
"handler has been provided.",
errorCode);
}
}
@@ -246,7 +247,7 @@ static int get_permissible_configuration_settings() {
snd_pcm_hw_params_alloca(&local_alsa_params);
snd_pcm_info_t *local_alsa_info;
snd_pcm_info_alloca(&local_alsa_info);
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
snd_pcm_t *temporary_alsa_handle = NULL;
ret = snd_pcm_open(&temporary_alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0);
if (ret == 0) {
@@ -1061,7 +1062,7 @@ static int prepare_mixer() {
// Now, start trying to initialise the alsa device with the settings
// obtained
pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
pthread_mutex_lock_and_cleanup_push(&alsa_mixer_mutex);
if (open_mixer() == 0) {
if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
0) {
@@ -1147,7 +1148,7 @@ static int prepare_mixer() {
if (response == 0)
response = close_mixer();
}
debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
pthread_mutex_unlock(&alsa_mixer_mutex); // release the mutex
pthread_cleanup_pop(0);
pthread_setcancelstate(oldState, NULL);
}
@@ -1593,7 +1594,7 @@ static int configure(int32_t requested_encoded_format, char **channel_map) {
int response = 0;
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
if (current_encoded_output_format != requested_encoded_format) {
if (current_encoded_output_format == 0)
debug(2, "alsa: setting output configuration to %s.",
@@ -1610,7 +1611,7 @@ static int configure(int32_t requested_encoded_format, char **channel_map) {
if ((response == 0) && (channel_map != NULL)) {
*channel_map = get_channel_map_str();
}
debug_mutex_unlock(&alsa_mutex, 0);
pthread_mutex_unlock(&alsa_mutex);
pthread_cleanup_pop(0);
pthread_setcancelstate(oldState, NULL);
if (response != 0)
@@ -1623,7 +1624,7 @@ static void deinit(void) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
debug(2, "audio_alsa deinit called.");
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
if (alsa_handle != NULL) {
debug(3, "alsa: closing the output device.");
do_close();
@@ -1647,7 +1648,7 @@ static int set_mute_state() {
int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 10000, 0);
pthread_mutex_lock_and_cleanup_push(&alsa_mixer_mutex);
if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) &&
(open_mixer() == 0)) {
response = 0; // okay if actually using the mute facility
@@ -1674,7 +1675,7 @@ static int set_mute_state() {
}
close_mixer();
}
debug_mutex_unlock(&alsa_mixer_mutex, 4); // release the mutex
pthread_mutex_unlock(&alsa_mixer_mutex); // release the mutex
pthread_cleanup_pop(0); // release the mutex
pthread_setcancelstate(oldState, NULL);
return response;
@@ -1910,11 +1911,11 @@ static int delay(long *the_delay) {
snd_pcm_state_t state;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
ret = delay_and_status(&state, &my_delay, NULL);
debug_mutex_unlock(&alsa_mutex, 0);
pthread_mutex_unlock(&alsa_mutex);
pthread_cleanup_pop(0);
pthread_setcancelstate(oldState, NULL);
@@ -1941,7 +1942,7 @@ static int stats(uint64_t *raw_measurement_time, uint64_t *corrected_measurement
snd_pcm_sframes_t my_delay = 0; // this initialisation is to silence a clang warning
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
if (alsa_handle == NULL) {
ret = alsa_handle_status;
@@ -1958,7 +1959,7 @@ static int stats(uint64_t *raw_measurement_time, uint64_t *corrected_measurement
frames_sent_break_occurred = 0; // reset it.
if (frames_sent_to_dac != NULL)
*frames_sent_to_dac = frames_sent_for_playing;
debug_mutex_unlock(&alsa_mutex, 0);
pthread_mutex_unlock(&alsa_mutex);
pthread_cleanup_pop(0);
pthread_setcancelstate(oldState, NULL);
uint64_t hd = my_delay; // note: snd_pcm_sframes_t is a long
@@ -1983,12 +1984,10 @@ static int do_play(void *buf, int samples) {
if (ret == 0) { // will be non-zero if an error or a stall
// just check the state of the DAC
if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) &&
(state != SND_PCM_STATE_XRUN)) {
debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state);
if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING)) {
debug(1, "alsa: DAC in unexpected state %s prior to writing.", snd_pcm_state_name(state));
}
if (state == SND_PCM_STATE_XRUN) {
debug(1, "alsa: DAC in SND_PCM_STATE_XRUN prior to writing.");
ret = snd_pcm_recover(alsa_handle, ret, 1);
}
@@ -2141,7 +2140,7 @@ static int play(void *buf, int samples, __attribute__((unused)) int sample_type,
int ret = 0;
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
if (alsa_backend_state == abm_disconnected) {
ret = do_open();
@@ -2163,13 +2162,13 @@ static int play(void *buf, int samples, __attribute__((unused)) int sample_type,
ret = do_play(buf, samples);
}
debug_mutex_unlock(&alsa_mutex, 0);
pthread_mutex_unlock(&alsa_mutex);
pthread_cleanup_pop(0); // release the mutex
return ret;
}
static void flush(void) {
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 4);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
if (alsa_backend_state != abm_disconnected) { // must be playing or connected...
// do nothing for a flush if config.keep_dac_busy is true
if (config.keep_dac_busy == 0) {
@@ -2178,19 +2177,19 @@ static void flush(void) {
} else {
debug(3, "alsa: flush() -- called on a disconnected alsa backend");
}
debug_mutex_unlock(&alsa_mutex, 4);
pthread_mutex_unlock(&alsa_mutex);
pthread_cleanup_pop(0); // release the mutex
}
static void stop(void) {
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 4);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
if (alsa_backend_state != abm_disconnected) { // must be playing or connected...
if (config.keep_dac_busy == 0) {
do_close();
}
} else
debug(3, "alsa: stop() -- called on a disconnected alsa backend");
debug_mutex_unlock(&alsa_mutex, 4);
pthread_mutex_unlock(&alsa_mutex);
pthread_cleanup_pop(0); // release the mutex
}
@@ -2200,7 +2199,7 @@ static void do_volume(double vol) { // caller is assumed to have the alsa_mutex
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
set_volume = vol;
pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 4);
pthread_mutex_lock_and_cleanup_push(&alsa_mixer_mutex);
if (volume_set_request && (open_mixer() == 0)) {
if (has_softvol) {
if (ctl && elem_id) {
@@ -2230,7 +2229,7 @@ static void do_volume(double vol) { // caller is assumed to have the alsa_mutex
volume_set_request = 0; // any external request that has been made is now satisfied
close_mixer();
}
debug_mutex_unlock(&alsa_mixer_mutex, 4);
pthread_mutex_unlock(&alsa_mixer_mutex);
pthread_cleanup_pop(0); // release the mutex
pthread_setcancelstate(oldState, NULL);
}
@@ -2303,7 +2302,7 @@ static void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg)
"alsa_buffer_monitor_thread_code sleep was %.6f sec but request was for %.6f sec. "
"Disabling standby may not work properly!",
sleep_time_actual_ns * 0.000000001, config.disable_standby_mode_silence_scan_interval);
pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0);
pthread_mutex_lock_and_cleanup_push(&alsa_mutex);
// check possible state transitions here
if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) {
// open the dac and move to abm_connected mode
@@ -2409,7 +2408,7 @@ static void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg)
}
}
}
debug_mutex_unlock(&alsa_mutex, 0);
pthread_mutex_unlock(&alsa_mutex);
pthread_cleanup_pop(0); // release the mutex
uint64_t tsb = get_absolute_time_in_ns();
usleep(sleep_time_us); // has a cancellation point in it
+28 -28
View File
@@ -39,7 +39,7 @@ static char channel_map_6[] = "FL FR BL BR FC LFE";
static pthread_mutex_t sndio_mutex = PTHREAD_MUTEX_INITIALIZER;
static int current_encoded_output_format;
static struct sio_hdl *hdl;
static struct sio_hdl *ohdl;
static const char *output_device_name;
static unsigned int output_device_driver_bufsiz; // parameters for opening the output device
static unsigned int output_device_driver_round; // parameters for opening the output device
@@ -51,7 +51,7 @@ static size_t written;
uint64_t time_of_last_onmove_cb;
int at_least_one_onmove_cb_seen;
struct sio_par par;
struct sio_par opar;
struct sndio_formats {
sps_format_t sps_format;
@@ -84,7 +84,7 @@ static uint16_t permissible_configurations[SPS_RATE_HIGHEST + 1][SPS_FORMAT_HIGH
static int get_permissible_configuration_settings() {
int ret = 0;
uint64_t hto = get_absolute_time_in_ns();
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
pthread_mutex_lock_and_cleanup_push(&sndio_mutex);
struct sio_hdl *hdl = sio_open(output_device_name, SIO_PLAY, 0);
if (hdl != NULL) {
struct sio_cap cap;
@@ -198,7 +198,7 @@ static int get_permissible_configuration_settings() {
proposed_par.rate = sps_rate_actual_rate(r);
proposed_par.pchan = c;
proposed_par.bits = format_info->bits;
proposed_par.bps = SIO_BPS(par.bits);
proposed_par.bps = SIO_BPS(opar.bits);
proposed_par.le = format_info->le;
proposed_par.sig = format_info->sig;
if (sio_setpar(hdl, &proposed_par) == 1) {
@@ -283,7 +283,7 @@ static int configure(int32_t requested_encoded_format, char **channel_map) {
if (is_running != 0) {
debug(1, "sndio: the output device is running while changing configuration");
if (sio_flush(hdl) != 1)
if (sio_flush(ohdl) != 1)
debug(1, "sndio: unable to flush");
written = played = is_running = 0;
time_of_last_onmove_cb = 0;
@@ -301,9 +301,9 @@ static int configure(int32_t requested_encoded_format, char **channel_map) {
par.sig = format_info->sig;
debug(3, "Requested %u/%u/%u/%u/%u (rate/bits/signed/le/channels)", par.rate, par.bits, par.sig,
par.le, par.pchan);
if (sio_setpar(hdl, &par) == 1) {
if (sio_setpar(ohdl, &par) == 1) {
struct sio_par apar;
if (sio_getpar(hdl, &apar) == 1) {
if (sio_getpar(ohdl, &apar) == 1) {
debug(3, "Got %u/%u/%u/%u/%u (rate/bits/signed/le/channels)", apar.rate, apar.bits,
apar.sig, apar.le, apar.pchan);
if ((apar.rate == par.rate) && (apar.pchan == par.pchan) && (apar.bits == par.bits) &&
@@ -439,23 +439,23 @@ static int init(int argc, char **argv) {
*/
get_permissible_configuration_settings();
debug(2, "sndio: output device name is \"%s\".", output_device_name);
hdl = sio_open(output_device_name, SIO_PLAY, 0);
if (!hdl)
ohdl = sio_open(output_device_name, SIO_PLAY, 0);
if (!ohdl)
die("sndio: cannot open audio device");
sio_onmove(hdl, onmove_cb, NULL);
sio_onmove(ohdl, onmove_cb, NULL);
// debug(1, "sndio: init done");
return 0;
}
static void deinit() {
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
if (hdl != NULL) {
pthread_mutex_lock_and_cleanup_push(&sndio_mutex);
if (ohdl != NULL) {
if (is_running != 0) {
sio_flush(hdl);
sio_flush(ohdl);
is_running = 0;
}
sio_close(hdl);
hdl = NULL;
sio_close(ohdl);
ohdl = NULL;
}
pthread_cleanup_pop(1); // unlock the mutex
}
@@ -465,10 +465,10 @@ static int play(void *buf, int frames, __attribute__((unused)) int sample_type,
__attribute__((unused)) uint64_t playtime) {
if (frames > 0) {
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
pthread_mutex_lock_and_cleanup_push(&sndio_mutex);
if (is_running == 0) {
if (hdl != NULL) {
if (sio_start(hdl) != 1)
if (ohdl != NULL) {
if (sio_start(ohdl) != 1)
debug(1, "sndio: unable to start");
is_running = 1;
written = played = 0;
@@ -478,7 +478,7 @@ static int play(void *buf, int frames, __attribute__((unused)) int sample_type,
debug(1, "sndio: output device is not open for play!");
}
}
written += sio_write(hdl, buf, frames * framesize);
written += sio_write(ohdl, buf, frames * framesize);
pthread_cleanup_pop(1); // unlock the mutex
}
return 0;
@@ -486,10 +486,10 @@ static int play(void *buf, int frames, __attribute__((unused)) int sample_type,
static void stop() {
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
if (hdl != NULL) {
pthread_mutex_lock_and_cleanup_push(&sndio_mutex);
if (ohdl != NULL) {
if (is_running != 0) {
if (sio_flush(hdl) != 1)
if (sio_flush(ohdl) != 1)
debug(1, "sndio: unable to stop");
written = played = is_running = 0;
} else {
@@ -509,7 +509,7 @@ int get_delay(long *delay) {
// calculate the difference in time between now and when the last callback occurred,
// and use it to estimate the frames that would have been output
uint64_t time_difference = get_absolute_time_in_ns() - time_of_last_onmove_cb;
uint64_t frame_difference = (time_difference * par.rate) / 1000000000;
uint64_t frame_difference = (time_difference * opar.rate) / 1000000000;
estimated_extra_frames_output = frame_difference;
// sanity check -- total estimate can not exceed frames written.
if ((estimated_extra_frames_output + played) > written / framesize) {
@@ -526,8 +526,8 @@ int get_delay(long *delay) {
static int delay(long *delay) {
int result = 0;
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
if (hdl != NULL) {
pthread_mutex_lock_and_cleanup_push(&sndio_mutex);
if (ohdl != NULL) {
if (is_running != 0) {
result = get_delay(delay);
} else {
@@ -543,10 +543,10 @@ static int delay(long *delay) {
}
static void flush() {
pthread_cleanup_debug_mutex_lock(&sndio_mutex, 1000, 1);
if (hdl != NULL) {
pthread_mutex_lock_and_cleanup_push(&sndio_mutex);
if (ohdl != NULL) {
if (is_running != 0) {
if (sio_flush(hdl) != 1)
if (sio_flush(ohdl) != 1)
debug(1, "sndio: unable to flush");
written = played = is_running = 0;
} else {
+8 -129
View File
@@ -131,11 +131,8 @@ void set_alsa_out_dev(char *);
#endif
config_t config_file_stuff;
int type_of_exit_cleanup;
uint64_t minimum_dac_queue_size;
pthread_mutex_t the_conn_lock = PTHREAD_MUTEX_INITIALIZER;
unsigned int sps_format_sample_size_array[] = {
0, // unknown
1, 1, // S8, U8
@@ -233,8 +230,10 @@ const char *short_format_description(int32_t encoded_format) {
}
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
static volatile int requested_connection_state_to_output = 1;
// static volatile int requested_connection_state_to_output = 1;
/*
// this stuff is to direct logging to syslog via libdaemon or directly
// alternatively you can direct it to stderr using a command line option
@@ -291,7 +290,7 @@ int create_log_file(const char *path) {
int flags = fcntl(fd, F_GETFL);
if (flags == -1) {
// strerror_r(errno, (char
//*)errorstring, sizeof(errorstring));
// *)errorstring, sizeof(errorstring));
// debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".",
// errno,
// (char *)errorstring, pathname);
@@ -337,33 +336,12 @@ void log_to_syslog() {
#else
sps_log = syslog;
#endif
}
*/
shairport_cfg config;
sigset_t pselect_sigset;
// note -- don't use this to shutdown from dbus -- see its own code in dbus-service.c
void sps_shutdown(type_of_exit_type shutdown_type) { // TOE_normal, TOE_emergency
type_of_exit_cleanup = shutdown_type;
if (type_of_exit_cleanup == TOE_emergency) {
debug(1, "emergency shutdown requested");
exit(EXIT_FAILURE);
} else {
debug(1, "normal shutdown requested");
exit(EXIT_SUCCESS);
}
}
int usleep_uncancellable(useconds_t usec) {
int response;
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
response = usleep(usec);
pthread_setcancelstate(oldState, NULL);
return response;
}
static uint16_t UDPPortIndex = 0;
void resetFreeUDPPort() {
@@ -524,9 +502,9 @@ uint16_t bind_UDP_port(int ip_family, const char *self_ip_address, uint32_t scop
return sport;
}
int get_requested_connection_state_to_output() { return requested_connection_state_to_output; }
// int get_requested_connection_state_to_output() { return requested_connection_state_to_output; }
void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; }
// void set_requested_connection_state_to_output(int v) { requested_connection_state_to_output = v; }
void getErrorText(char *destinationString, size_t destinationStringLength) {
#pragma GCC diagnostic push
@@ -1828,13 +1806,6 @@ uint64_t nctoh64(const uint8_t *p) {
return vl;
}
pthread_mutex_t barrier_mutex = PTHREAD_MUTEX_INITIALIZER;
void memory_barrier() {
pthread_mutex_lock(&barrier_mutex);
pthread_mutex_unlock(&barrier_mutex);
}
void sps_nanosleep(const time_t sec, const long nanosec) {
struct timespec req, rem;
int result;
@@ -1849,98 +1820,6 @@ void sps_nanosleep(const time_t sec, const long nanosec) {
nanosec, errno);
}
// Mac OS X doesn't have pthread_mutex_timedlock
// Also note that timing must be relative to CLOCK_REALTIME
/*
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
struct timespec timeoutTime;
uint64_t wait_until_time = dally_time * 1000; // to nanoseconds
uint64_t start_time = get_realtime_in_ns(); // this is from CLOCK_REALTIME
wait_until_time = wait_until_time + start_time;
uint64_t wait_until_sec = wait_until_time / 1000000000;
uint64_t wait_until_nsec = wait_until_time % 1000000000;
timeoutTime.tv_sec = wait_until_sec;
timeoutTime.tv_nsec = wait_until_nsec;
int r = pthread_mutex_timedlock(mutex, &timeoutTime);
pthread_setcancelstate(oldState, NULL);
return r;
}
#endif
#ifdef COMPILE_FOR_OSX
*/
int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time) {
// this would not be not pthread_cancellation safe because is contains a cancellation point
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
int time_to_wait = dally_time;
int r = pthread_mutex_trylock(mutex);
while ((r == EBUSY) && (time_to_wait > 0)) {
int st = time_to_wait;
if (st > 1000)
st = 1000;
sps_nanosleep(0, st * 1000); // this contains a cancellation point
time_to_wait -= st;
r = pthread_mutex_trylock(mutex);
}
pthread_setcancelstate(oldState, NULL);
return r;
}
// #endif
int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexname,
const char *filename, const int line, int debuglevel) {
if ((debuglevel > debug_level()) || (debuglevel == 0))
return pthread_mutex_lock(mutex);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
if (debuglevel != 0)
_debug(filename, line, 3, "mutex_lock \"%s\".", mutexname); // only if you really ask for it!
int result = sps_pthread_mutex_timedlock(mutex, dally_time);
if (result == ETIMEDOUT) {
_debug(
filename, line, debuglevel,
"mutex_lock \"%s\" failed to lock after %f ms -- now waiting unconditionally to lock it.",
mutexname, dally_time * 1E-3);
result = pthread_mutex_lock(mutex);
if (result == 0)
_debug(filename, line, debuglevel, " ...mutex_lock \"%s\" locked successfully.", mutexname);
else
_debug(filename, line, debuglevel, " ...mutex_lock \"%s\" exited with error code: %u",
mutexname, result);
}
pthread_setcancelstate(oldState, NULL);
return result;
}
int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexname, const char *filename,
const int line, int debuglevel) {
if ((debuglevel > debug_level()) || (debuglevel == 0))
return pthread_mutex_unlock(mutex);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
char dstring[1000];
char errstr[512];
memset(dstring, 0, sizeof(dstring));
snprintf(dstring, sizeof(dstring), "%s:%d", filename, line);
debug(debuglevel, "mutex_unlock \"%s\" at \"%s\".", mutexname, dstring);
int r = pthread_mutex_unlock(mutex);
if ((debuglevel != 0) && (r != 0)) {
if (strerror_r(r, errstr, sizeof(errstr)) == 0) {
debug(1, "error %d: \"%s\" unlocking mutex \"%s\" at \"%s\".", r, errstr, mutexname, dstring);
} else {
debug(1, "error %d: unlocking mutex \"%s\" at \"%s\".", r, mutexname, dstring);
}
}
pthread_setcancelstate(oldState, NULL);
return r;
}
void malloc_cleanup(void *arg) {
// the address of the malloc variable is passed in case a realloc is done as some time
// debug(1, "malloc cleanup called.");
+15 -58
View File
@@ -52,11 +52,6 @@ typedef enum {
} dbus_message_bus_t;
#endif
typedef enum {
TOE_normal,
TOE_emergency,
} type_of_exit_type;
#define sps_extra_code_output_stalled 32768
#define sps_extra_code_output_state_cannot_make_ready 32769
@@ -198,6 +193,8 @@ typedef struct {
double resend_control_check_interval_time; // wait this long between making requests
double resend_control_last_check_time; // if the packet is missing this close to the time of use,
// give up
int get_plist_metadata; // set to non-zero to get richer plist metadata
pthread_mutex_t lock;
config_t *cfg;
int endianness;
@@ -438,9 +435,6 @@ typedef struct {
unsigned int output_channel_map_size; // number of output channels
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
GMainLoop *glib_worker_loop;
// for clean quitting from a dbus interface quit request (from the DBus or MPRIS interfaces)
int quit_requested_from_glib_mainloop; // remember that it initialised to zero.
dbus_message_bus_t dbus_default_message_bus;
#if defined(CONFIG_DBUS_INTERFACE)
@@ -458,18 +452,20 @@ uint32_t nctohl(const uint8_t *p); // read 4 characters from *p and do ntohl on
uint16_t nctohs(const uint8_t *p); // read 2 characters from *p and do ntohs on them
uint64_t nctoh64(const uint8_t *p); // read 8 characters from *p to a uint64_t
void memory_barrier();
// void memory_barrier();
/*
void log_to_stderr(); // call this to direct logging to stderr;
void log_to_stdout(); // call this to direct logging to stdout;
void log_to_syslog(); // call this to direct logging to the system log;
void log_to_file(); // call this to direct logging to a file or (pre-existing) pipe;
*/
// true if Shairport Sync is supposed to be sending output to the output device, false otherwise
int get_requested_connection_state_to_output();
//int get_requested_connection_state_to_output();
void set_requested_connection_state_to_output(int v);
// void set_requested_connection_state_to_output(int v);
int try_to_open_pipe_for_writing(
const char *pathname); // open it without blocking if it's not hooked up
@@ -505,14 +501,6 @@ extern volatile int debuglev;
MADEID(once_flag_, __LINE__) = 1; \
}
// do X once, and then ignore repeated calls until they stop for more than one second
#define once_per_1_second_burst(X) \
static uint64_t MADEID(time_, __LINE__) = 0; \
int64_t MADEID(interval_, __LINE__) = get_absolute_time_in_ns() - MADEID(time_, __LINE__); \
if ((MADEID(time_, __LINE__) == 0) || (MADEID(interval_, __LINE__) > 1000000000L)) \
X; \
MADEID(time_, __LINE__) = get_absolute_time_in_ns()
void getErrorText(char *destinationString, size_t destinationStringLength);
uint8_t *base64_dec(char *input, int *outlen);
@@ -558,7 +546,6 @@ uint32_t uatoi(const char *nptr);
extern shairport_cfg config;
extern config_t config_file_stuff;
extern int type_of_exit_cleanup; // normal, emergency, dbus requested...
extern uint64_t minimum_dac_queue_size;
@@ -581,44 +568,11 @@ void command_set_volume(double volume);
int mkpath(const char *path, mode_t mode);
void sps_shutdown(type_of_exit_type shutdown_type); // TOE_normal, TOE_emergency, TOE_dbus
#define pthread_mutex_lock_and_cleanup_push(mu) \
if (pthread_mutex_lock(mu) == 0) \
pthread_cleanup_push(mutex_unlock, (void *)mu)
extern sigset_t pselect_sigset;
extern pthread_mutex_t the_conn_lock;
#define conn_lock(arg) \
pthread_mutex_lock(&the_conn_lock); \
arg; \
pthread_mutex_unlock(&the_conn_lock);
// wait for the specified time in microseconds -- it checks every 20 milliseconds
// int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
// const char *debugmessage, int debuglevel);
// wait for the specified time, checking every 20 milliseconds, and block if it can't acquire the
// lock
int _debug_mutex_lock(pthread_mutex_t *mutex, useconds_t dally_time, const char *mutexName,
const char *filename, const int line, int debuglevel);
#define debug_mutex_lock(mu, t, d) _debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d)
int _debug_mutex_unlock(pthread_mutex_t *mutex, const char *mutexName, const char *filename,
const int line, int debuglevel);
#define debug_mutex_unlock(mu, d) _debug_mutex_unlock(mu, #mu, __FILE__, __LINE__, d)
void pthread_cleanup_debug_mutex_unlock(void *arg);
#define pthread_cleanup_debug_mutex_lock(mu, t, d) \
if (_debug_mutex_lock(mu, t, #mu, __FILE__, __LINE__, d) == 0) \
pthread_cleanup_push(pthread_cleanup_debug_mutex_unlock, (void *)mu)
#define config_lock \
if (pthread_mutex_trylock(&config.lock) != 0) { \
debug(1, "config_lock: cannot acquire config.lock"); \
}
#define config_unlock pthread_mutex_unlock(&config.lock)
int do_pthread_setname(pthread_t *restrict thread, const char *format, ...);
@@ -640,7 +594,6 @@ char *get_version_string(); // mallocs a string space -- remember to free it aft
int64_t generate_zero_frames(char *outp, size_t number_of_frames, int with_dither,
int64_t random_number_in, uint32_t encoded_output_format);
void malloc_cleanup(void *arg);
int string_update_with_size(char **str, int *flag, char *s, size_t len);
@@ -652,10 +605,14 @@ int bind_socket_and_port(int type, int ip_family, const char *self_ip_address, u
uint16_t bind_UDP_port(int ip_family, const char *self_ip_address, uint32_t scope_id, int *sock);
// for pthread_push and pop
// careful with the difference between cleanup and unlock!
void malloc_cleanup(void *arg);
void socket_cleanup(void *arg);
void mutex_unlock(void *arg);
void rwlock_unlock(void *arg);
void mutex_cleanup(void *arg);
void rwlock_unlock(void *arg);
void cv_cleanup(void *arg);
void thread_cleanup(void *arg);
#ifdef CONFIG_AIRPLAY_2
+1 -1
View File
@@ -1,7 +1,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
AC_INIT([shairport-sync], [5.1], [4265913+mikebrady@users.noreply.github.com])
AC_INIT([shairport-sync], [5.2], [4265913+mikebrady@users.noreply.github.com])
: ${CFLAGS="-O3"}
: ${CXXFLAGS="-O3"}
AM_INIT_AUTOMAKE([subdir-objects])
+8 -12
View File
@@ -212,11 +212,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
uint64_t start_time = get_absolute_time_in_ns();
pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
// only do this one at a time -- not sure it is necessary, but better safe than sorry
// int mutex_reply = sps_pthread_mutex_timedlock(&dacp_conversation_lock, 2000000, command,
// 1);
int mutex_reply = debug_mutex_lock(&dacp_conversation_lock, 2000000, 1);
// int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
if (mutex_reply == 0) {
pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock);
@@ -390,10 +386,10 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) {
// as the conn's connection number
// this is to signify that the player has stopped, but only if another thread (with a different
// index) hasn't already taken over the dacp service
debug_mutex_lock(&dacp_server_information_lock, 500000, 4);
pthread_mutex_lock(&dacp_server_information_lock);
if (dacp_server.players_connection_thread_index == conn->connection_number)
dacp_server.players_connection_thread_index = 0;
debug_mutex_unlock(&dacp_server_information_lock, 4);
pthread_mutex_unlock(&dacp_server_information_lock);
}
// this will be running on the thread of its caller, not of the conversation thread...
@@ -403,7 +399,7 @@ void relinquish_dacp_server_information(rtsp_conn_info *conn) {
// Thus, we can keep the DACP port that might have previously been discovered
void set_dacp_server_information(rtsp_conn_info *conn) {
// debug(1, "set_dacp_server_information");
debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
pthread_mutex_lock(&dacp_server_information_lock);
dacp_server.players_connection_thread_index = conn->connection_number;
if ((conn->dacp_id == NULL) || (strcmp(conn->dacp_id, dacp_server.dacp_id) != 0)) {
@@ -464,11 +460,11 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id);
pthread_cond_signal(&dacp_server_information_cv);
debug_mutex_unlock(&dacp_server_information_lock, 3);
pthread_mutex_unlock(&dacp_server_information_lock);
}
void dacp_monitor_port_update_callback(const char *dacp_id, uint16_t port) {
debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
pthread_mutex_lock(&dacp_server_information_lock);
debug(3,
"dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port "
"number %d.",
@@ -493,7 +489,7 @@ void dacp_monitor_port_update_callback(const char *dacp_id, uint16_t port) {
}
}
pthread_cond_signal(&dacp_server_information_cv);
debug_mutex_unlock(&dacp_server_information_lock, 3);
pthread_mutex_unlock(&dacp_server_information_lock);
}
void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
@@ -510,7 +506,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
while (1) {
int result = 0;
int32_t the_volume;
pthread_cleanup_debug_mutex_lock(&dacp_server_information_lock, 500000, 4);
pthread_mutex_lock_and_cleanup_push(&dacp_server_information_lock);
if (dacp_server.scan_enable == 0) {
metadata_hub_modify_prolog();
int ch = (metadata_store.dacp_server_active != 0) ||
+2 -2
View File
@@ -44,6 +44,7 @@
#include "metadata/hub.h"
#include "dbus-service.h"
#include "utilities/exit.h"
#ifdef CONFIG_CONVOLUTION
#include <FFTConvolver/convolver.h>
@@ -934,9 +935,8 @@ static gboolean on_handle_quit(ShairportSync *skeleton, GDBusMethodInvocation *i
__attribute__((unused)) const gchar *command,
__attribute__((unused)) gpointer user_data) {
debug(1, ">> quit request...");
config.quit_requested_from_glib_mainloop = 1;
g_main_loop_quit(config.glib_worker_loop);
shairport_sync_complete_quit(skeleton, invocation);
exit_request(EXIT_SUCCESS);
return TRUE;
}
+3 -8
View File
@@ -2,7 +2,7 @@
.SH NAME
shairport-sync \- AirPlay and AirPlay 2 Audio Player
.SH SYNOPSIS
\fBshairport-sync [-djvw]\fB [-a \fB\fIservice-name\fB | --name=\fB\fIservice-name\fB]\fB [-B \fB\fIcommand\fB | --onstart=\fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB | --configfile=\fB\fIconfigurationfile\fB]\fB [-d | --daemon]\fB [--dbus-default-message-bus=\fB\fIbus\fB]\fB [-E \fB\fIcommand\fB | --onstop=\fB\fIcommand\fB]\fB [-g | --get-cover-art]\fB [-j | --justDaemoniseNoPIDFile]\fB [--logOutputLevel]\fB [--log-to-syslog]\fB [-L \fB\fIlatency\fB | --latency=\fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB | --mdns=\fB\fIbackend\fB]\fB [-M | --metadata-enable]\fB [-o \fB\fIbackend\fB | --output=\fB\fIbackend\fB]\fB [-p \fB\fIport\fB | --port=\fB\fIport\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB | --resync=\fB\fIthreshold\fB]\fB [--service-type=\fB\fItype\fB]\fB [--statistics]\fB [-S \fB\fImode\fB | --stuffing=\fB\fImode\fB]\fB [-t \fB\fItimeout\fB | --timeout=\fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-v | --verbose]\fB [-w | --wait-cmd]\fB [-- \fB\fIaudio_backend_options\fB]\fB
\fBshairport-sync [-djvw]\fB [-a \fB\fIservice-name\fB | --name=\fB\fIservice-name\fB]\fB [-B \fB\fIcommand\fB | --onstart=\fB\fIcommand\fB]\fB [-c \fB\fIconfigurationfile\fB | --configfile=\fB\fIconfigurationfile\fB]\fB [-d | --daemon]\fB [--dbus-default-message-bus=\fB\fIbus\fB]\fB [-E \fB\fIcommand\fB | --onstop=\fB\fIcommand\fB]\fB [-g | --get-cover-art]\fB [-j | --justDaemoniseNoPIDFile]\fB [--logOutputLevel]\fB [-L \fB\fIlatency\fB | --latency=\fB\fIlatency\fB]\fB [-m \fB\fIbackend\fB | --mdns=\fB\fIbackend\fB]\fB [-M | --metadata-enable]\fB [-o \fB\fIbackend\fB | --output=\fB\fIbackend\fB]\fB [-p \fB\fIport\fB | --port=\fB\fIport\fB]\fB [--password=\fB\fIsecret\fB]\fB [-r \fB\fIthreshold\fB | --resync=\fB\fIthreshold\fB]\fB [--service-type=\fB\fItype\fB]\fB [--statistics]\fB [-S \fB\fImode\fB | --stuffing=\fB\fImode\fB]\fB [-t \fB\fItimeout\fB | --timeout=\fB\fItimeout\fB]\fB [--tolerance=\fB\fIframes\fB]\fB [-v | --verbose]\fB [-w | --wait-cmd]\fB [-- \fB\fIaudio_backend_options\fB]\fB
shairport-sync -X | --displayConfig\fB
@@ -96,11 +96,6 @@ Kill the shairport-sync daemon and exit. (Requires that the daemon has written i
\fB--logOutputLevel\f1
Use this to log the volume level when the volume is changed. It may be useful if you are trying to determine a suitable value for the maximum volume level. Not available as a configuration file setting.
.TP
\fB--log-to-syslog\f1
Warnings, error messages and messages are sent, by default, to \fISTDERR\f1. Use this option to route these messages to the \fBsyslog\f1 instead. This is intended for use when Shairport Sync is operating as a daemon.
See also \fB--displayConfig\f1.
.TP
\fB-L | --latency=\f1\fIlatency\f1
Use this to set the \fIdefault latency\f1, in frames, for audio coming from an unidentified source or from an iTunes Version 9 or earlier source. The standard value for the \fIdefault latency\f1 is 88,200 frames, where there are 44,100 frames to the second.
@@ -131,7 +126,7 @@ Resynchronise if timings differ by more than \fIthreshold\f1 frames. If the outp
On an AirPlay-2-capable Shairport Sync, if you need offer only the older "classic" AirPlay (sometimes called AirPlay 1) service, set \fB--service-type=classic\f1. A potential use of this would be for Apple Music on Windows, which is not compatible with the AirPlay 2 service offered by Shairport Sync, but which is compatible with the classic-only service. To offer AirPlay 2 service only, set \fB--service-type=airplay2\f1 (note that this requires NQPTP). Set \fB--service-type=auto\f1 to allow Shairport Sync to choose AirPlay 2 service if NQPTP is present or classic AirPlay service if not, with "(Classic)" appended to the default service name.
.TP
\fB--statistics\f1
Print some performance information to \fISTDERR\f1, or to \fBsyslog\f1 if the \fB--log-to-syslog\f1 command line option is also chosen.
Print some performance information to \fISTDERR\f1.
.TP
\fB-S \f1\fImode\f1\fB | --stuffing=\f1\fImode\f1
Interpolate ("stuff") the audio stream using the \fImode\f1. "Stuffing" refers to the process of adding or removing frames of audio to or from the stream sent to the output device in order to keep it synchronised with the player. The \fBvernier\f1 mode is normally almost completely inaudible and suitable for low-powered devices. An alternative mode, \fBsoxr\f1, is less obtrusive but requires much more processing power. For this mode, support for \fBlibsoxr\f1, the SoX Resampler Library, must be selected when \fBshairport-sync\f1 is built. The \fBbasic\f1 mode is also suitable for low-powered device, but \fBvernier\f1 is better. The default setting, \fBauto\f1, allows Shairport Sync to choose \fBsoxr\f1 mode if the system is powerful enough.
@@ -148,7 +143,7 @@ Allow playback to be up to \fIframes\f1 out of exact synchronization before atte
Print version information and exit.
.TP
\fB-v | --verbose\f1
Print debug information to the \fISTDERR\f1, or to \fBsyslog\f1 if the \fB--log-to-syslog\f1 command line option is also chosen. Repeat up to three times (i.e. \fB-vv\f1 or \fB-vvv\f1) for more detail. You should use \fB-vvv\f1 very sparingly -- it is really noisy.
Print debug information to the \fISTDERR\f1. Repeat up to three times (i.e. \fB-vv\f1 or \fB-vvv\f1) for more detail. You should use \fB-vvv\f1 very sparingly -- it is really noisy.
.TP
\fB-w | --wait-cmd\f1
Wait for commands specified using \fB-B\f1 or \fB-E\f1 to complete before continuing execution.
+2 -13
View File
@@ -45,7 +45,6 @@
<opt>[-g | --get-cover-art]</opt>
<opt>[-j | --justDaemoniseNoPIDFile]</opt>
<opt>[--logOutputLevel]</opt>
<opt>[--log-to-syslog]</opt>
<opt>[-L </opt><arg>latency</arg><opt> | --latency=</opt><arg>latency</arg><opt>]</opt>
<opt>[-m </opt><arg>backend</arg><opt> | --mdns=</opt><arg>backend</arg><opt>]</opt>
<opt>[-M | --metadata-enable]</opt>
@@ -269,16 +268,6 @@
</optdesc>
</option>
<option>
<p><opt>--log-to-syslog</opt></p>
<optdesc><p>
Warnings, error messages and messages are sent, by default, to <file>STDERR</file>.
Use this option to route these messages to the <opt>syslog</opt> instead.
This is intended for use when Shairport Sync is operating as a daemon.
</p><p>See also <opt>--displayConfig</opt>.</p>
</optdesc>
</option>
<option>
<p><opt>-L | --latency=</opt><arg>latency</arg></p>
<optdesc><p>
@@ -381,7 +370,7 @@
<option>
<p><opt>--statistics</opt></p>
<optdesc><p>
Print some performance information to <file>STDERR</file>, or to <opt>syslog</opt> if the <opt>--log-to-syslog</opt> command line option is also chosen.
Print some performance information to <file>STDERR</file>.
</p></optdesc>
</option>
@@ -446,7 +435,7 @@
<option>
<p><opt>-v | --verbose</opt></p>
<optdesc><p>
Print debug information to the <file>STDERR</file>, or to <opt>syslog</opt> if the <opt>--log-to-syslog</opt> command line option is also chosen.
Print debug information to the <file>STDERR</file>.
Repeat up to three times (i.e. <opt>-vv</opt> or <opt>-vvv</opt>) for more detail. You should use <opt>-vvv</opt> very sparingly -- it is really noisy.
</p></optdesc>
</option>
+3 -3
View File
@@ -81,11 +81,11 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) {
int rc;
if (the_queue) {
if (block == 0) {
rc = debug_mutex_lock(&the_queue->pc_queue_lock, 10000, 4);
rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
if (rc == EBUSY)
return EBUSY;
} else
rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4);
rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
if (rc)
debug(1, "Error %d (\"%s\") locking for pc_queue_add_item. Block is %d.", rc, strerror(rc),
block);
@@ -137,7 +137,7 @@ 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) {
int rc;
if (the_queue) {
rc = debug_mutex_lock(&the_queue->pc_queue_lock, 50000, 4);
rc = pthread_mutex_lock(&the_queue->pc_queue_lock);
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);
+2 -2
View File
@@ -41,6 +41,7 @@
#include "metadata/hub.h"
#include "mpris-service.h"
#include "utilities/exit.h"
static guint ownerID = 0;
static GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM; // default is the dbus system message bus
@@ -234,9 +235,8 @@ void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)
static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
debug(1, ">> quit request...");
config.quit_requested_from_glib_mainloop = 1;
g_main_loop_quit(config.glib_worker_loop);
media_player2_complete_quit(skeleton, invocation);
exit_request(EXIT_SUCCESS);
return TRUE;
}
+28 -4
View File
@@ -1,3 +1,4 @@
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
@@ -70,7 +71,8 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
char *commands[] = {"command", "beginff", "beginrew", "mutetoggle",
"nextitem", "previtem", "pause", "playpause",
"play", "stop", "playresume", "shuffle_songs",
"volumedown", "volumeup", "disconnect", NULL};
"volumedown", "volumeup", "disconnect", "queue_next",
NULL};
int it = 0;
@@ -82,6 +84,25 @@ void on_message(__attribute__((unused)) struct mosquitto *mosq,
if (strcmp(commands[it], "disconnect") == 0) {
debug(2, "[MQTT]: Disconnect Command: %s\n", commands[it]);
stop_play(); // stop any current session and don't replace it
} else if (strcmp(commands[it], "queue_next") == 0) {
// payload is "queue_next <track_id>", where <track_id> is the hex track_id
// string as published by shairport-sync itself (see the "mper"/"track_id" handling
// above), e.g. "queue_next 1A2B3C4D5E6F7"
char *track_id = payload + strlen(commands[it]);
while (*track_id == ' ' || *track_id == '\t')
track_id++;
size_t track_id_len = strlen(track_id);
while (track_id_len > 0 && isspace((unsigned char)track_id[track_id_len - 1]))
track_id[--track_id_len] = '\0';
if (track_id_len == 0) {
warn("[MQTT]: queue_next command received with no track_id -- ignoring.");
} else {
char dacp_command[256];
snprintf(dacp_command, sizeof(dacp_command),
"cue?command=add&query='dmap.persistentid:0x%s'&mode=3", track_id);
debug(2, "[MQTT]: Queue Next Command: %s\n", dacp_command);
send_simple_dacp_command(dacp_command);
}
} else {
debug(2, "[MQTT]: DACP Command: %s\n", commands[it]);
#ifdef CONFIG_DACP_CLIENT
@@ -301,7 +322,6 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le
if (config.mqtt_publish_parsed) {
if (type == 'core') {
int32_t r;
uint64_t trackid;
char trackidstring[32];
switch (code) {
@@ -321,8 +341,12 @@ void mqtt_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t le
mqtt_publish("title", data, length);
break;
case 'mper':
trackid = *(uint64_t *)(data);
r = snprintf(trackidstring, sizeof(trackidstring), "%" PRIX64 "", trackid);
// publish the raw persistent-id bytes as hex, in the order received --
// no byte-order or integer conversion, so this can't reverse the value.
r = 0;
for (uint32_t i = 0; i < length && r < (int32_t)sizeof(trackidstring) - 2; i++)
r += snprintf(trackidstring + r, sizeof(trackidstring) - (size_t)r, "%02X",
(unsigned char)data[i]);
mqtt_publish("track_id", trackidstring, r);
}
} else if (type == 'ssnc') {
+55 -55
View File
@@ -4,7 +4,7 @@
* All rights reserved.
*
* Modifications for audio synchronisation, AirPlay 2
* and related work, copyright (c) Mike Brady 2014--2025
* and related work, copyright (c) Mike Brady 2014--2026
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -332,7 +332,7 @@ static void free_audio_buffers(rtsp_conn_info *conn) {
int first_possibly_missing_frame = -1;
void reset_buffer(rtsp_conn_info *conn) {
pthread_cleanup_debug_mutex_lock(&conn->ab_mutex, 30000, 0);
pthread_mutex_lock_and_cleanup_push(&conn->ab_mutex);
ab_resync(conn);
pthread_cleanup_pop(1);
#if CONFIG_FFMPEG
@@ -349,7 +349,7 @@ void reset_buffer(rtsp_conn_info *conn) {
size_t get_audio_buffer_occupancy(rtsp_conn_info *conn) {
size_t response = 0;
pthread_cleanup_debug_mutex_lock(&conn->ab_mutex, 30000, 0);
pthread_mutex_lock_and_cleanup_push(&conn->ab_mutex);
if (conn->ab_synced) {
int16_t occ =
conn->ab_write - conn->ab_read; // will be zero or positive if read and write are within
@@ -1412,7 +1412,7 @@ seq_t get_revised_seqno(rtsp_conn_info *conn, uint32_t timestamp) {
// go back through the buffers to find the first buffer following a buffer that predates
// the given timestamp, if any.
seq_t revised_seqno = conn->ab_write;
pthread_cleanup_debug_mutex_lock(&conn->ab_mutex, 30000, 0);
pthread_mutex_lock_and_cleanup_push(&conn->ab_mutex);
int older_seqno_found = 0;
while ((older_seqno_found == 0) && (revised_seqno != conn->ab_read)) {
revised_seqno--;
@@ -1478,18 +1478,18 @@ uint32_t player_put_packet(uint32_t ssrc, seq_t seqno, uint32_t actual_timestamp
// ignore a request to flush that has been made before the first packet...
if (conn->packet_count == 0) {
debug_mutex_lock(&conn->flush_mutex, 1000, 4);
pthread_mutex_lock(&conn->flush_mutex);
conn->flush_requested = 0;
conn->flush_rtp_timestamp = 0;
debug_mutex_unlock(&conn->flush_mutex, 4);
pthread_mutex_unlock(&conn->flush_mutex);
}
pthread_cleanup_debug_mutex_lock(&conn->ab_mutex, 30000, 0);
pthread_mutex_lock_and_cleanup_push(&conn->ab_mutex);
uint64_t time_now = get_absolute_time_in_ns();
conn->packet_count++;
conn->packet_count_since_flush++;
conn->time_of_last_audio_packet = time_now;
if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
// if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
abuf_t *abuf = 0;
if (!conn->ab_synced) {
conn->ab_write = seqno;
@@ -1795,7 +1795,7 @@ uint32_t player_put_packet(uint32_t ssrc, seq_t seqno, uint32_t actual_timestamp
if (number_of_missing_frames == 0)
first_possibly_missing_frame = conn->ab_write;
}
}
// } // remove this
pthread_cleanup_pop(1);
// debug_mutex_unlock(&conn->ab_mutex, 0);
return input_packets_used;
@@ -2044,7 +2044,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
abuf_t *curframe = NULL;
int notified_buffer_empty = 0; // diagnostic only
pthread_cleanup_debug_mutex_lock(&conn->ab_mutex, 30000, 0);
pthread_mutex_lock_and_cleanup_push(&conn->ab_mutex);
int wait;
long dac_delay = 0; // long because alsa returns a long
@@ -2059,6 +2059,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
// we must have timing information before we can do anything here
if ((have_timestamp_timing_information(conn)) && (conn->input_format_is_valid != 0)) {
/*
int rco = get_requested_connection_state_to_output();
if (conn->connection_state_to_output != rco) {
@@ -2066,23 +2067,23 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
// change happening
if (conn->connection_state_to_output == 0) { // going off
debug(2, "request flush because connection_state_to_output is off");
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
pthread_mutex_lock(&conn->flush_mutex);
conn->flush_requested = 1;
conn->flush_rtp_timestamp = 0;
debug_mutex_unlock(&conn->flush_mutex, 3);
pthread_mutex_unlock(&conn->flush_mutex);
}
}
*/
if (config.output->is_running)
if (config.output->is_running() != 0) { // if the back end isn't running for any reason
debug(2, "request flush because back end is not running");
debug_mutex_lock(&conn->flush_mutex, 1000, 0);
pthread_mutex_lock(&conn->flush_mutex);
conn->flush_requested = 1;
conn->flush_rtp_timestamp = 0;
debug_mutex_unlock(&conn->flush_mutex, 0);
pthread_mutex_unlock(&conn->flush_mutex);
}
pthread_cleanup_debug_mutex_lock(&conn->flush_mutex, 1000, 0);
pthread_mutex_lock_and_cleanup_push(&conn->flush_mutex);
if (conn->flush_requested == 1) {
if (conn->flush_output_flushed == 0) {
#if CONFIG_FFMPEG
@@ -3557,7 +3558,7 @@ void *player_thread_func(void *arg) {
}
conn->session_corrections = 0;
conn->connection_state_to_output = get_requested_connection_state_to_output();
// conn->connection_state_to_output = get_requested_connection_state_to_output();
int number_of_statistics, oldest_statistic, newest_statistic;
uint32_t frames_since_last_stats_logged = 0;
@@ -4625,8 +4626,9 @@ void *player_thread_func(void *arg) {
// Apply convolution
// First, have we got the right convolution setup?
static int convolver_error_notified = 0;
static int convolver_is_valid = 0;
static size_t current_convolver_block_size = 0;
// static size_t current_convolver_block_size = 0;
static unsigned int current_convolver_rate = 0;
static unsigned int current_convolver_channels = 0;
static double current_convolver_maximum_length_in_seconds = 0;
@@ -4634,11 +4636,8 @@ void *player_thread_func(void *arg) {
if (config.convolution_enabled) {
if (
// if any of these are true, we need to create a new convolver
// (conn->convolver_is_valid == 0) ||
(current_convolver_block_size != inframe->length) ||
(current_convolver_rate != conn->input_rate) ||
!((current_convolver_channels == 1) ||
(current_convolver_channels == conn->input_num_channels)) ||
(current_convolver_rate != RATE_FROM_ENCODED_FORMAT(config.current_output_configuration)) ||
(current_convolver_channels != CHANNELS_FROM_ENCODED_FORMAT(config.current_output_configuration)) ||
(current_convolver_maximum_length_in_seconds !=
config.convolution_max_length_in_seconds) ||
(config.convolution_ir_files_updated == 1)) {
@@ -4646,13 +4645,12 @@ void *player_thread_func(void *arg) {
// look for a convolution ir file with a matching rate and channel count
convolver_is_valid = 0; // declare any current convolver as invalid
current_convolver_block_size = inframe->length;
current_convolver_rate = conn->input_rate;
current_convolver_channels = conn->input_num_channels;
current_convolver_rate = RATE_FROM_ENCODED_FORMAT(config.current_output_configuration);
current_convolver_channels = CHANNELS_FROM_ENCODED_FORMAT(config.current_output_configuration);
current_convolver_maximum_length_in_seconds =
config.convolution_max_length_in_seconds;
config.convolution_ir_files_updated = 0;
debug(2, "try to initialise a %u/%u convolver.", current_convolver_rate,
debug(3, "looking for a %u/%u finite impulse response file.", current_convolver_rate,
current_convolver_channels);
char *convolver_file_found = NULL;
unsigned int ir = 0;
@@ -4667,55 +4665,58 @@ void *player_thread_func(void *arg) {
ir++;
}
}
// if no luck, try for a single-channel IR file
if (convolver_file_found == NULL) {
current_convolver_channels = 1;
ir = 0;
debug(3, "looking for a %u/1 finite impulse response file.", current_convolver_rate);
while ((ir < config.convolution_ir_file_count) &&
(convolver_file_found == NULL)) {
if ((config.convolution_ir_files[ir].samplerate ==
current_convolver_rate) &&
(config.convolution_ir_files[ir].channels ==
current_convolver_channels)) {
(config.convolution_ir_files[ir].channels == 1)) {
convolver_file_found = config.convolution_ir_files[ir].filename;
} else {
ir++;
}
}
if (convolver_file_found != NULL) {
debug(1, "The %u/1 finite impulse response file \"%s\" will be used for convolution.", current_convolver_rate,
convolver_file_found);
}
} else {
debug(1, "The %u/%u finite impulse response file \"%s\" will be used for convolution.", current_convolver_rate,
current_convolver_channels, convolver_file_found);
}
if (convolver_file_found != NULL) {
// we have an apparently suitable convolution ir file, so lets initialise
// a convolver
convolver_is_valid = convolver_init(
convolver_file_found, conn->input_num_channels,
config.convolution_max_length_in_seconds, inframe->length);
convolver_file_found, current_convolver_channels,
config.convolution_max_length_in_seconds, 1024); // power of 2 suggested for the block length
convolver_wait_for_all();
// if (convolver_is_valid)
// debug(1, "convolver_init for %u channels was successful.",
// conn->input_num_channels); convolver_is_valid = convolver_init(
// convolver_file_found, conn->input_num_channels,
// config.convolution_max_length_in_seconds, inframe->length);
if ((convolver_is_valid == 0) && (convolver_error_notified == 0)) {
debug(1, "can not initialise a %u/%u convolver from the \"%s\" finite impulse response file.", current_convolver_rate,
current_convolver_channels, convolver_file_found);
convolver_error_notified = 1;
}
} else if (convolver_error_notified == 0) {
debug(1, "Convolution is disabled because a suitable %u/%u or %u/1 finite impulse response file can not be found.", current_convolver_rate, current_convolver_channels, current_convolver_rate);
convolver_error_notified = 1;
}
if (convolver_is_valid == 0)
debug(1, "can not initialise a %u/%u convolver.", current_convolver_rate,
conn->input_num_channels);
else
debug(1, "convolver: \"%s\".", convolver_file_found);
}
if (convolver_is_valid != 0) {
for (j = 0; j < conn->input_num_channels; j++) {
// convolver_process(j, fbufs[j], inframe->length);
for (j = 0; j < current_convolver_channels; j++) {
convolver_process(j, fbufs[j], inframe->length);
}
convolver_wait_for_all();
convolver_error_notified = 0;
}
// apply convolution gain even if no convolution is done...
float gain = pow(10.0, config.convolution_gain / 20.0);
for (i = 0; i < inframe->length; ++i) {
for (j = 0; j < conn->input_num_channels; j++) {
for (j = 0; j < current_convolver_channels; j++) {
float output_level_db = 0.0;
if (fbufs[j][i] < 0.0)
output_level_db = 20 * log10(fbufs[j][i] / (float)INT32_MIN * 1.0);
@@ -4732,17 +4733,16 @@ void *player_thread_func(void *arg) {
}
}
}
#endif
if (conn->do_loudness) {
loudness_process_blocks((float *)fbufs, inframe->length,
conn->input_num_channels,
CHANNELS_FROM_ENCODED_FORMAT(config.current_output_configuration),
(float)conn->fix_volume / 65536);
}
// Interleave and convert back to int32_t
for (i = 0; i < inframe->length; i++) {
for (j = 0; j < conn->input_num_channels; j++) {
for (j = 0; j < CHANNELS_FROM_ENCODED_FORMAT(config.current_output_configuration); j++) {
tbuf32[conn->input_num_channels * i + j] = fbufs[j][i];
}
}
@@ -5017,7 +5017,7 @@ static void player_send_volume_metadata(uint8_t vol_mode_both, double airplay_vo
}
void player_volume_without_notification(double airplay_volume, rtsp_conn_info *conn) {
debug_mutex_lock(&conn->volume_control_mutex, 5000, 4);
pthread_mutex_lock(&conn->volume_control_mutex);
// first, see if we are hw only, sw only, both with hw attenuation on the top or both with sw
// attenuation on top
@@ -5235,7 +5235,7 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
// here, store the volume for possible use in the future
config.airplay_volume = airplay_volume;
conn->own_airplay_volume = airplay_volume;
debug_mutex_unlock(&conn->volume_control_mutex, 4);
pthread_mutex_unlock(&conn->volume_control_mutex);
}
void player_volume(double airplay_volume, rtsp_conn_info *conn) {
@@ -5246,11 +5246,11 @@ void player_volume(double airplay_volume, rtsp_conn_info *conn) {
void do_flush(uint32_t timestamp, rtsp_conn_info *conn) {
debug(3, "do_flush: flush to %u.", timestamp);
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
pthread_mutex_lock(&conn->flush_mutex);
conn->flush_requested = 1;
conn->flush_rtp_timestamp = timestamp; // flush all packets up to, but not including, this one.
reset_input_flow_metrics(conn);
debug_mutex_unlock(&conn->flush_mutex, 3);
pthread_mutex_unlock(&conn->flush_mutex);
}
void player_flush(uint32_t timestamp, rtsp_conn_info *conn) {
@@ -5280,7 +5280,7 @@ int player_play(rtsp_conn_info *conn) {
// access to the output device (i.e. knowing that it should not be in
// use by another program at this time).
pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 1);
pthread_mutex_lock_and_cleanup_push(&conn->player_create_delete_mutex);
if (conn->player_thread == NULL) {
pthread_t *pt = malloc(sizeof(pthread_t));
if (pt == NULL)
@@ -5307,7 +5307,7 @@ int player_stop(rtsp_conn_info *conn) {
// note -- this may be called from another connection thread.
debug(2, "Connection %d: player_stop.", conn->connection_number);
int response = 0; // okay
pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 4);
pthread_mutex_lock_and_cleanup_push(&conn->player_create_delete_mutex);
pthread_t *pt = conn->player_thread;
if (pt) {
debug(3, "player_thread cancel...");
+1 -1
View File
@@ -322,7 +322,7 @@ typedef struct {
alac_file *decoder_info;
uint64_t packet_count;
uint64_t packet_count_since_flush;
int connection_state_to_output;
// int connection_state_to_output;
uint64_t first_packet_time_to_play;
int64_t time_since_play_started; // nanoseconds
// stats
+8 -8
View File
@@ -487,7 +487,7 @@ void *rtp_control_receiver(void *arg) {
sync_rtp_timestamp = sync_rtp_timestamp - conn->latency;
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
pthread_mutex_lock(&conn->reference_time_mutex);
if (conn->initial_reference_time == 0) {
if (conn->packet_count_since_flush > 0) {
@@ -535,7 +535,7 @@ void *rtp_control_receiver(void *arg) {
conn->anchor_remote_info_is_valid = 1;
conn->latency_delayed_timestamp = rtp_timestamp_less_latency;
debug_mutex_unlock(&conn->reference_time_mutex, 0);
pthread_mutex_unlock(&conn->reference_time_mutex);
conn->reference_to_previous_time_difference =
remote_time_of_sync - old_remote_reference_time;
@@ -1111,11 +1111,11 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport
}
void reset_ntp_anchor_info(rtsp_conn_info *conn) {
debug_mutex_lock(&conn->reference_time_mutex, 1000, 1);
pthread_mutex_lock(&conn->reference_time_mutex);
conn->anchor_remote_info_is_valid = 0;
conn->anchor_rtptime = 0;
conn->anchor_time = 0;
debug_mutex_unlock(&conn->reference_time_mutex, 3);
pthread_mutex_unlock(&conn->reference_time_mutex);
}
int have_ntp_timing_information(rtsp_conn_info *conn) {
@@ -1132,7 +1132,7 @@ int frame_to_ntp_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *
// a zero result is good
if (conn->anchor_remote_info_is_valid == 0)
debug(1, "no anchor information");
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
pthread_mutex_lock(&conn->reference_time_mutex);
int result = -1;
if (conn->anchor_remote_info_is_valid != 0) {
uint64_t remote_time_of_timestamp;
@@ -1151,13 +1151,13 @@ int frame_to_ntp_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *
*time = remote_time_of_timestamp - local_to_remote_time_difference_now(conn);
result = 0;
}
debug_mutex_unlock(&conn->reference_time_mutex, 0);
pthread_mutex_unlock(&conn->reference_time_mutex);
return result;
}
int local_ntp_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn) {
// a zero result is good
debug_mutex_lock(&conn->reference_time_mutex, 1000, 0);
pthread_mutex_lock(&conn->reference_time_mutex);
int result = -1;
if (conn->anchor_remote_info_is_valid != 0) {
// first, get from [local] time to remote time.
@@ -1176,7 +1176,7 @@ int local_ntp_time_to_frame(uint64_t time, uint32_t *frame, rtsp_conn_info *conn
*frame = new_frame;
result = 0;
}
debug_mutex_unlock(&conn->reference_time_mutex, 0);
pthread_mutex_unlock(&conn->reference_time_mutex);
return result;
}
+13 -13
View File
@@ -148,7 +148,7 @@ int RTSP_connection_index = 1;
// keep track of the threads we have spawned so we can join() them
static int nconns = 0;
static void track_thread(rtsp_conn_info *conn) {
debug_mutex_lock(&conns_lock, 1000000, 4);
pthread_mutex_lock(&conns_lock);
// look for an empty slot first
int i = 0;
int found = 0;
@@ -170,7 +170,7 @@ static void track_thread(rtsp_conn_info *conn) {
die("could not reallocate memory for conns");
}
}
debug_mutex_unlock(&conns_lock, 4);
pthread_mutex_unlock(&conns_lock);
}
// note: connection numbers start at 1, so an except_this_one value of zero means "all threads"
@@ -178,7 +178,7 @@ void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_o
// if the stream category is unspecified_stream_category
// all categories are elegible for cancellation
// otherwise just the category itself
debug_mutex_lock(&conns_lock, 1000000, 3);
pthread_mutex_lock(&conns_lock);
int i;
for (i = 0; i < nconns; i++) {
if ((conns[i] != NULL) && (conns[i]->running != 0) &&
@@ -203,7 +203,7 @@ void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_o
conns[i] = NULL;
}
}
debug_mutex_unlock(&conns_lock, 3);
pthread_mutex_unlock(&conns_lock);
}
int old_connection_count = -1;
@@ -214,7 +214,7 @@ void cleanup_threads(void) {
int i;
int connection_count = 0;
// debug(2, "culling threads.");
debug_mutex_lock(&conns_lock, 1000000, 4);
pthread_mutex_lock(&conns_lock);
for (i = 0; i < nconns; i++) {
if ((conns[i] != NULL) && (conns[i]->running == 0)) {
debug(4, "found RTSP connection thread %d in a non-running state.",
@@ -230,7 +230,7 @@ void cleanup_threads(void) {
connection_count++;
}
}
debug_mutex_unlock(&conns_lock, 4);
pthread_mutex_unlock(&conns_lock);
if (old_connection_count != connection_count) {
if (connection_count == 0) {
@@ -247,7 +247,7 @@ void cleanup_threads(void) {
int terminate_conn(int connection_number) {
// this will look for a connection by number, cancel it, join it and delete it.
int found = 0;
debug_mutex_lock(&conns_lock, 1000000, 4);
pthread_mutex_lock(&conns_lock);
// look for an empty slot first
int i = 0;
while ((i < nconns) && (found == 0)) {
@@ -259,7 +259,7 @@ int terminate_conn(int connection_number) {
} else
i++;
}
debug_mutex_unlock(&conns_lock, 4);
pthread_mutex_unlock(&conns_lock);
return found;
}
@@ -1341,7 +1341,7 @@ void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
debug(4, "flushUntilTS is %" PRId64 ".", flushUntilTS);
}
debug_mutex_lock(&conn->flush_mutex, 1000, 4);
pthread_mutex_lock(&conn->flush_mutex);
if (flushFromValid == 0) {
// an immediate flush is requested
@@ -1387,7 +1387,7 @@ void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
}
}
debug_mutex_unlock(&conn->flush_mutex, 4);
pthread_mutex_unlock(&conn->flush_mutex);
plist_free(messagePlist);
}
@@ -1465,7 +1465,7 @@ void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
uint64_t rate;
plist_get_uint_val(item, &rate);
debug(3, "anchor rate 0x%016" PRIx64 ".", rate);
pthread_cleanup_debug_mutex_lock(&conn->flush_mutex, 1000, 4);
pthread_mutex_lock_and_cleanup_push(&conn->flush_mutex);
conn->ap2_rate = rate;
if ((rate & 1) != 0) {
ptp_send_control_message_string(
@@ -4168,14 +4168,14 @@ static void *rtsp_conversation_thread_func(void *pconn) {
// check to see if a conn has been zeroed
debug_mutex_lock(&conns_lock, 1000000, 4);
pthread_mutex_lock(&conns_lock);
int i;
for (i = 0; i < nconns; i++) {
if ((conns[i] != NULL) && (conns[i]->connection_number == 0)) {
debug(1, "conns[%d] has a Connection Number of 0!", i);
}
}
debug_mutex_unlock(&conns_lock, 4);
pthread_mutex_unlock(&conns_lock);
reply = rtsp_read_request(conn, &req);
if (reply == rtsp_read_request_response_ok) {
+15 -7
View File
@@ -21,7 +21,7 @@ general =
// The default is "auto", which means that the AirPlay 2 service will be provided if NQPTP is present and "classic" AirPlay will be provided otherwise, with "(Classic)" appended to the service name (see above).
// service_type = "auto"; // This can be "auto", "airplay2" or "classic".
// 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 if the processor is fast enough. Otherwise, "vernier" is selected.
// "vernier" recodes a packet of frames to a new packet containing more or fewer frames. This is recommended for low powered devices.
// "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.
// "basic" causes the simple removal or insertion of frames in a packet of frames. Not recommended.
@@ -106,13 +106,18 @@ sessioncontrol =
// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a program on here to deal with it.
// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared by Shairport Sync itself, hook a handler program on here to deal with it.
// An error code-string is passed as the last argument.
// Shairport Sync will always wait for this handler to complete.
// Shairport Sync will not exit automatically after executing this handler. To exit, the handler must do it itself, perhaps via a script with "/usr/bin/kill $PPID" as its last line.
// Many of these "unfixable" problems are caused by malfunctioning output devices, and sometimes it is necessary to restart the whole device to clear the problem.
// You could hook on a program to do this automatically, but beware -- the device may then power off and restart without warning!
// wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing
// allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source
// wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing,
// except the "run_this_if_an_unfixable_error_is_detected" handler, which will always wait for completion, irrespective of this setting.
// allow_session_interruption = "no"; // (Classic AirPlay only) Set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source
// session_timeout = 60; // wait for this number of seconds after a source disappears before terminating the session and becoming available again.
};
@@ -294,16 +299,19 @@ ao =
dsp =
{
//////////////////////////////////////////
// This convolution filter can be used with inpulse responses files
// This convolution filter can be used with finite impulse response (IR) files
// to apply almost any correction to the audio signal, like frequency and phase correction.
// For example you could measure (with a good microphone and a sweep-sine) the frequency response of your speakers + room,
// and apply a correction to get a flat response curve.
// Long impulse response files require lots of floating-point processing!
//
// Convolution is applied to each output channel at the output rate.
// Thus, your IR files must match the output rate and match the output channel count or are mono.
//////////////////////////////////////////
//
// convolution_enabled = "no"; // Set this to "yes" to activate the convolution filter.
// convolution_thread_pool_size = 1; // Number of CPU threads that can work on convolution at the same time.
// convolution_ir_files = "<comma-separated list of full path names to impulse response files, for sample rates of 44100 and 48000 -- stereo or mono only>";
// convolution_ir_files = "<comma-separated list of full path names to impulse response files, with sample rates that match output rates and with channel counts that match output channel counts or are mono>";
// convolution_gain = -4.0; // Static gain applied after convolution, between -60 and +18 dB. Useful for preventing clipping or amplifying low convolution output levels
// convolution_max_length_in_seconds = 1.0; // Truncate the input file to this length in order to save CPU.
//
@@ -383,7 +391,6 @@ mqtt =
diagnostics =
{
// disable_resend_requests = "no"; // set this to yes to stop Shairport Sync from requesting the retransmission of missing packets. Default is "no".
// log_output_to = "syslog"; // set this to "syslog" (default), "stderr" or "stdout" or a file or pipe path to specify were all logs, statistics and diagnostic messages are written to. If there's anything wrong with the file spec, output will be to "stderr".
// statistics = "no"; // set to "yes" to print statistics in the log
// log_verbosity = 0; // "0" means no debug verbosity, "3" is most verbose.
// log_show_file_and_line = "yes"; // set this to yes if you want the file and line number of the message source in the log file
@@ -391,4 +398,5 @@ diagnostics =
// log_show_time_since_last_message = "yes"; // set this to yes if you want the time since the last debug message in the debug message -- seconds down to nanoseconds
// drop_this_fraction_of_audio_packets = 0.0; // use this to simulate a noisy network where this fraction of UDP packets are lost in transmission. E.g. a value of 0.001 would mean an average of 0.1% of packets are lost, which is actually quite a high figure.
// retain_cover_art = "no"; // artwork is deleted when its corresponding track has been played. Set this to "yes" to retain all artwork permanently. Warning -- your directory might fill up.
// get_plist_metadata = "no"; // set this temporary setting to "yes" to get the new richer metadata stream.
};
+1 -1
View File
@@ -13,7 +13,7 @@ rcvar="shairport_sync_enable"
# This seems to be necessary in FreeBSD, as the startup system doesn't seem to like dashes ("-") in service names.
pidfile="/var/run/shairport-sync.pid"
apptodaemonise="/usr/local/bin/shairport-sync --log-to-syslog"
apptodaemonise="/usr/local/bin/shairport-sync"
command="/usr/sbin/daemon"
# -S log to syslog; -P store the supervisor PID; -u to specify the user to run the service under
+1 -1
View File
@@ -10,7 +10,7 @@ StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
ExecStart=@prefix@/bin/shairport-sync --log-to-syslog
ExecStart=@prefix@/bin/shairport-sync
User=shairport-sync
Group=shairport-sync
LimitRTPRIO=5
+69 -42
View File
@@ -83,6 +83,7 @@
#include "rtp.h"
#include "rtsp.h"
#include "utilities/string_utilities.h"
#include "utilities/exit.h"
#if defined(CONFIG_DACP_CLIENT)
#include "dacp.h"
@@ -140,6 +141,10 @@ int this_is_the_daemon_process = 0;
pthread_t rtsp_listener_thread;
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
GMainLoop *glib_worker_loop = NULL;
#endif
int killOption = 0;
int daemonisewith = 0;
int daemonisewithout = 0;
@@ -453,7 +458,7 @@ int parse_options(int argc, char **argv) {
break;
case 'u':
inform("Warning: the option -u is no longer needed and is deprecated. Debug and statistics "
"output to STDERR is now the default. Use \"--log-to-syslog\" to revert.");
"output to STDERR is now the default.");
break;
case 'D':
inform("Warning: the option -D or --disconnectFromOutput is deprecated.");
@@ -489,6 +494,7 @@ int parse_options(int argc, char **argv) {
}
}
if (c < -1) {
debug(1, "Oops");
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
}
@@ -503,17 +509,16 @@ int parse_options(int argc, char **argv) {
}
if (log_to_syslog_selected) {
// if this was the first command line argument, it'll already have been chosen
if (log_to_syslog_select_is_first_command_line_argument == 0) {
inform("Suggestion: make \"--log-to-syslog\" the first command line argument to ensure "
"messages go to the syslog right from the beginning.");
}
inform("the diagnostic \"log-to-syslog\" command_line_option is obsolete and is ignored. All logging is to STDERR, which is directed to the system log when Shairport Sync is running as a service.");
/*
#ifdef CONFIG_LIBDAEMON
log_to_default = 0; // a specific log output modality has been selected.
#endif
log_to_syslog();
*/
}
#ifdef CONFIG_LIBDAEMON
if ((daemonisewith) && (daemonisewithout))
die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected "
@@ -574,9 +579,6 @@ int parse_options(int argc, char **argv) {
#endif
config.firmware_version = strdup(PACKAGE_VERSION);
free(config.firmware_version);
config.firmware_version = strdup("5.2");
#ifdef CONFIG_METADATA
/* Get the metadata setting. */
config.metadata_enabled = 1; // if metadata support is included, then enable it by default
@@ -772,6 +774,17 @@ int parse_options(int argc, char **argv) {
value);
}
if (config_lookup_string(config.cfg, "diagnostics.get_plist_metadata", &str)) {
if (strcasecmp(str, "no") == 0)
config.get_plist_metadata = 0;
else if (strcasecmp(str, "yes") == 0)
config.get_plist_metadata = 1;
else
die("Invalid \"get_plist_metadata\" option choice \"%s\". It should be \"yes\" or "
"\"no\"",
str);
}
/* Get the verbosity setting. */
if (config_lookup_int(config.cfg, "diagnostics.log_verbosity", &value)) {
if ((value >= 0) && (value <= 3))
@@ -860,6 +873,7 @@ int parse_options(int argc, char **argv) {
/* Get the diagnostics output default. */
if (config_lookup_string(config.cfg, "diagnostics.log_output_to", &str)) {
/*
#ifdef CONFIG_LIBDAEMON
log_to_default = 0; // a specific log output modality has been selected.
#endif
@@ -874,7 +888,11 @@ int parse_options(int argc, char **argv) {
config.log_fd = -1;
log_to_file();
}
*/
warn("the diagnostic \"log_output_to\" setting is obsolete and is ignored. All logging is to STDERR, which is directed to the system log when Shairport Sync is running as a service.");
}
/* Get the ignore_volume_control setting. */
if (config_lookup_string(config.cfg, "general.ignore_volume_control", &str)) {
if (strcasecmp(str, "no") == 0)
@@ -1630,17 +1648,22 @@ int parse_options(int argc, char **argv) {
// 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
if (config.get_plist_metadata != 0) {
config.airplay_features |=
(uint64_t)1 << 50; // richer metadata in a binary plist, including more state information
} else {
// older metadata flags artwork, progress and text respectively
config.airplay_features |= (((uint64_t)1 << 16) | ((uint64_t)1 << 17));
if (config.get_coverart != 0)
config.airplay_features |= ((uint64_t)1 << 15);
}
}
#endif
// now generate the fex field
uint8_t fexbytes[8];
uint64_t temp = config.airplay_features;
debug(2, "airplay_features are %" PRIx64 ".", temp);
debug(4, "airplay_features are %" PRIx64 ".", temp);
for (i = 0; i < 8; i++) {
fexbytes[i] = temp & 0xff;
temp = temp >> 8;
@@ -1841,7 +1864,7 @@ GThread *glib_worker_thread = NULL;
gpointer glib_worker_thread_function(__attribute__((unused)) gpointer data) {
// use the default global-default main context
config.glib_worker_loop = g_main_loop_new(NULL, FALSE);
glib_worker_loop = g_main_loop_new(NULL, FALSE);
// debug(1, "glib worker thread started.");
@@ -1856,7 +1879,7 @@ gpointer glib_worker_thread_function(__attribute__((unused)) gpointer data) {
// debug(1, "g_main_loop_run start.");
g_main_loop_run(config.glib_worker_loop);
g_main_loop_run(glib_worker_loop);
// debug(1, "g_main_loop_run exit.");
@@ -1872,11 +1895,7 @@ gpointer glib_worker_thread_function(__attribute__((unused)) gpointer data) {
// debug(1, "stopped D-Bus service");
#endif
g_main_loop_unref(config.glib_worker_loop);
if (config.quit_requested_from_glib_mainloop != 0) {
debug(2, "glib_mainloop_thread_function asking for exit");
exit(EXIT_SUCCESS);
}
g_main_loop_unref(glib_worker_loop);
return NULL;
}
@@ -1893,17 +1912,15 @@ const char *pid_file_proc(void) {
}
#endif
void exit_rtsp_listener() {
debug(3, "exit_rtsp_listener begins");
if (type_of_exit_cleanup != TOE_emergency) {
pthread_cancel(rtsp_listener_thread);
pthread_join(rtsp_listener_thread, NULL); // not sure you need this
}
pthread_cancel(rtsp_listener_thread);
pthread_join(rtsp_listener_thread, NULL); // not sure you need this
debug(2, "exit_rtsp_listener ends");
}
void exit_function() {
if (type_of_exit_cleanup != TOE_emergency) {
// the following is to ensure that if libdaemon has been included
// that most of this code will be skipped when the parent process is exiting
// exec
@@ -1932,10 +1949,15 @@ void exit_function() {
#endif
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
if ((glib_worker_thread != NULL) && (config.quit_requested_from_glib_mainloop == 0)) {
g_main_loop_quit(config.glib_worker_loop);
debug(2, "GMainLoop stop requested");
}
if (glib_worker_loop != NULL) { // may not have been initialised
g_main_loop_quit(glib_worker_loop);
debug(2, "GMainLoop stop requested");
}
if (glib_worker_thread != NULL) {
g_thread_join(glib_worker_thread);
debug(1, "GLib worker thread joined");
}
#endif
#ifdef CONFIG_METADATA_HUB
@@ -2038,6 +2060,8 @@ void exit_function() {
#ifdef CONFIG_LIBDAEMON
if (this_is_the_daemon_process) { // this is the daemon that is exiting
mdns_unregister(); // once the dacp handler is done and all player threrads are done it should
// be safe
debug(1, "libdaemon daemon process exit");
} else {
if (config.daemonise)
@@ -2046,13 +2070,10 @@ void exit_function() {
debug(1, "normal exit");
}
#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 threads are done it should
// be safe
debug(2, "normal exit");
#endif
} else {
debug(1, "emergency exit");
}
}
// for removing zombie script processes
@@ -2069,12 +2090,12 @@ void handle_sigchld(__attribute__((unused)) int sig) {
// for clean exits
void intHandler(__attribute__((unused)) int k) {
debug(2, "exit on SIGINT");
exit(EXIT_SUCCESS);
exit_request(EXIT_SUCCESS);
}
void termHandler(__attribute__((unused)) int k) {
debug(2, "exit on SIGTERM");
exit(EXIT_SUCCESS);
exit_request(EXIT_SUCCESS);
}
void _display_config(const char *filename, const int linenumber, __attribute__((unused)) int argc,
@@ -2336,9 +2357,10 @@ const char *av_channel_layout_name(uint64_t channel_layout) {
#endif
int main(int argc, char **argv) {
exit_init(); // initialise the exit handler to give us a clean safe exit on request
// initialise debug messages stuff -- level 0, no elapsed time, relative time, file and line
// debug_init(int level, int show_elapsed_time, int show_relative_time, int show_file_and_line)
debug_init(0, 0, 1, 1);
debug_init(0, 0, 1, 1, exit_request);
memset(&config, 0, sizeof(config)); // also clears all strings, BTW
/* Check if we are called with -V or --version parameter */
if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) {
@@ -2381,13 +2403,15 @@ int main(int argc, char **argv) {
exit(EXIT_SUCCESS);
}
/* Check if we are called with -log-to-syslog */
/*
// Check if we are called with -log-to-syslog
if (argc >= 2 && (strcmp(argv[1], "--log-to-syslog") == 0)) {
log_to_syslog_select_is_first_command_line_argument = 1;
log_to_syslog();
} else {
log_to_stderr();
}
*/
pid = getpid();
config.log_fd = -1;
@@ -2398,7 +2422,6 @@ int main(int argc, char **argv) {
setlogmask(LOG_UPTO(LOG_DEBUG));
openlog(NULL, 0, LOG_DAEMON);
#endif
type_of_exit_cleanup = TOE_normal; // what kind of exit cleanup needed
debug(1, "adding the exit function");
atexit(exit_function);
@@ -2460,8 +2483,8 @@ int main(int argc, char **argv) {
config.packet_stuffing = ST_vernier; // you need to explicitly ask for "basic" (ST_basic)
#endif
set_requested_connection_state_to_output(
1); // we expect to be able to connect to the output device
// set_requested_connection_state_to_output(
// 1); // we expect to be able to connect to the output device
config.audio_backend_buffer_desired_length = 0.15; // seconds
config.audio_decoded_buffer_desired_length = 0.75; // seconds
config.udp_port_base = 6001;
@@ -3355,6 +3378,10 @@ int main(int argc, char **argv) {
// you'll see two threads named "listener" or whatever...
named_pthread_create(&rtsp_listener_thread, NULL, &rtsp_listen_loop, NULL, "listener");
atexit(exit_rtsp_listener);
pthread_join(rtsp_listener_thread, NULL);
// wait forever...
while (1) {
usleep(1000000);
}
return 0;
}
+3 -3
View File
@@ -35,7 +35,7 @@
ssize_t buffered_read(buffered_tcp_desc *descriptor, void *buf, size_t count,
size_t *bytes_remaining) {
ssize_t response = -1;
if (debug_mutex_lock(&descriptor->mutex, 50000, 4) != 0)
if (pthread_mutex_lock(&descriptor->mutex) != 0)
debug(1, "problem with mutex");
pthread_cleanup_push(mutex_unlock, (void *)&descriptor->mutex);
// wipe the slate dlean before reading...
@@ -116,7 +116,7 @@ void *buffered_tcp_reader(void *arg) {
do {
int have_time_to_sleep = 0;
if (debug_mutex_lock(&descriptor->mutex, 500000, 4) != 0)
if (pthread_mutex_lock(&descriptor->mutex) != 0)
debug(1, "problem with mutex");
pthread_cleanup_push(mutex_unlock, (void *)&descriptor->mutex);
while (descriptor->buffer_occupancy == descriptor->buffer_max_size) {
@@ -150,7 +150,7 @@ void *buffered_tcp_reader(void *arg) {
nread = recv(fd, descriptor->eoq, bytes_to_request, 0);
// debug(1, "Received %d bytes for a buffer size of %d bytes.",nread,
// descriptor->buffer_occupancy + nread);
if (debug_mutex_lock(&descriptor->mutex, 50000, 4) != 0)
if (pthread_mutex_lock(&descriptor->mutex) != 0)
debug(1, "problem with not empty mutex");
pthread_cleanup_push(mutex_unlock, (void *)&descriptor->mutex);
if (nread < 0) {
+13 -2
View File
@@ -24,6 +24,7 @@ SOFTWARE.
#include "debug.h"
#include <ctype.h> // for isprint()
#include <unistd.h> // for usleep()
#include <inttypes.h>
#include <pthread.h>
#include <stdarg.h>
@@ -41,6 +42,8 @@ int debugger_show_file_and_line = 1;
static uint64_t ns_time_at_startup = 0;
static uint64_t ns_time_at_last_debug_message;
void (*local_exit_requester)(const int exit_status) = NULL;
// always lock use this when accessing the ns_time_at_last_debug_message
static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -56,13 +59,14 @@ uint64_t debug_get_absolute_time_in_ns() {
return time_now_ns;
}
void debug_init(int level, int show_elapsed_time, int show_relative_time, int show_file_and_line) {
void debug_init(int level, int show_elapsed_time, int show_relative_time, int show_file_and_line, void (*exit_requester)(const int exit_status)) {
ns_time_at_startup = debug_get_absolute_time_in_ns();
ns_time_at_last_debug_message = ns_time_at_startup;
debuglev = level;
debugger_show_elapsed_time = show_elapsed_time;
debugger_show_relative_time = show_relative_time;
debugger_show_file_and_line = show_file_and_line;
local_exit_requester = exit_requester;
}
int debug_level() { return debuglev; };
@@ -143,7 +147,14 @@ void _die(const char *filename, const int linenumber, const char *format, ...) {
// syslog(LOG_ERR, "%s", b);
fprintf(stderr, "%s\n", b);
pthread_setcancelstate(oldState, NULL);
exit(EXIT_FAILURE);
if (local_exit_requester != NULL) {
local_exit_requester(EXIT_FAILURE);
// wait for the exit request to be honoured
usleep(1000000);
// if not, head for the hills
fprintf(stderr, "fatal error: exit cleanup could not be completed.\n");
_Exit(EXIT_FAILURE);
}
}
void _warn(const char *filename, const int linenumber, const char *format, ...) {
+2 -2
View File
@@ -1,7 +1,7 @@
/*
MIT License
Copyright (c) 2023--2025 Mike Brady 4265913+mikebrady@users.noreply.github.com
Copyright (c) 2023--2026 Mike Brady 4265913+mikebrady@users.noreply.github.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -38,7 +38,7 @@ SOFTWARE.
// level 0 is no messages, level 3 is most messages
EXTERNC void debug_init(int level, int show_elapsed_time, int show_relative_time,
int show_file_and_line);
int show_file_and_line, void (*exit_requester)(const int exit_status));
EXTERNC int debug_level();
EXTERNC void set_debug_level(int level);
+66
View File
@@ -0,0 +1,66 @@
/*
MIT License
Copyright (c) 2023--2026 Mike Brady 4265913+mikebrady@users.noreply.github.com
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.
*/
// exit_init() starts a thread what waits for the exit_request_flag to
// be set to non-zero.
// It then runs the standard exit(exit_status) to
// terminate the program, running all the atexit handlers first.
// exit_request() can be called from anywhere in the program
// including a SIG handler and
// the thread will take care of terminating the program cleanly.
// pass in EXIT_SUCCESS or EXIT_FAILURE in the request.
#include <signal.h> // for sig_atomic_t
#include <stdlib.h> // for EXIT_SUCCESS
#include <string.h> // for memset
#include <unistd.h> // for usleep
#include <pthread.h>
#include "exit.h"
#include "common.h"
volatile sig_atomic_t exit_request_flag = 0;
volatile sig_atomic_t exit_status = EXIT_SUCCESS;
pthread_t exit_manager_thread;
void *exit_manager(__attribute__((unused)) void *arg) {
while(exit_request_flag == 0) {
usleep(100000);
}
exit(exit_status);
return NULL;
}
void exit_init() {
memset(&exit_manager_thread, 0, sizeof(pthread_t));
named_pthread_create(&exit_manager_thread, NULL, &exit_manager, NULL, "exit_manager");
}
void exit_request(const int exit_status_requested) {
exit_status = exit_status_requested; // EXIT_SUCCESS or EXIT_FAILURE
exit_request_flag = 1; // ask for exit
}
+30
View File
@@ -0,0 +1,30 @@
/*
MIT License
Copyright (c) 2023--2026 Mike Brady 4265913+mikebrady@users.noreply.github.com
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.
*/
#pragma once
#include <stddef.h>
void exit_init();
void exit_request(const int exit_status); // call this to ask for the program to be exited.
// pass in EXIT_SUCCESS or EXIT_FAILURE.
+2 -2
View File
@@ -52,7 +52,7 @@ int _safe_socket_close(const char *filename, const int linenumber, int *sockfd)
int result = 0;
int oldstate;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
debug_mutex_lock(&safe_socket_lock, 1000000, 1);
pthread_mutex_lock(&safe_socket_lock);
if (*sockfd == 0) {
_debug(filename, linenumber, 1, "_safe_socket_close: socket is zero!");
}
@@ -64,7 +64,7 @@ int _safe_socket_close(const char *filename, const int linenumber, int *sockfd)
} else {
_debug(filename, linenumber, 1, "_safe_socket_close: socket already closed!");
}
debug_mutex_unlock(&safe_socket_lock, 4);
pthread_mutex_unlock(&safe_socket_lock);
pthread_setcancelstate(oldstate, NULL);
return result;
}
+4 -4
View File
@@ -45,7 +45,7 @@ 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);
int rc = pthread_mutex_lock(&reference_counter_lock);
if (rc)
debug(1, "Error %d locking reference counter lock", rc);
if (msg > (rtsp_message *)0x00010000) {
@@ -63,7 +63,7 @@ void msg_retain(rtsp_message *msg) {
rtsp_message *msg_init(void) {
// no thread cancellation points here
int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4);
int rc = pthread_mutex_lock(&reference_counter_lock);
if (rc)
debug(1, "Error %d locking reference counter lock", rc);
@@ -128,7 +128,7 @@ void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const
}
void msg_free(rtsp_message **msgh) {
debug_mutex_lock(&reference_counter_lock, 1000, 0);
pthread_mutex_lock(&reference_counter_lock);
if (*msgh > (rtsp_message *)0x00010000) {
rtsp_message *msg = *msgh;
msg->referenceCount--;
@@ -161,7 +161,7 @@ void msg_free(rtsp_message **msgh) {
"%" PRIxPTR ".",
(uintptr_t)*msgh);
}
debug_mutex_unlock(&reference_counter_lock, 0);
pthread_mutex_unlock(&reference_counter_lock);
}
int msg_handle_line(rtsp_message **pmsg, char *line) {