diff --git a/.github/workflows/check_ap2_systemd_basic.yml b/.github/workflows/check_ap2_systemd_basic.yml index 3287a4a6..b0762d25 100644 --- a/.github/workflows/check_ap2_systemd_basic.yml +++ b/.github/workflows/check_ap2_systemd_basic.yml @@ -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 diff --git a/.github/workflows/check_ap2_systemd_full.yml b/.github/workflows/check_ap2_systemd_full.yml index 89f80df9..b5fcf008 100644 --- a/.github/workflows/check_ap2_systemd_full.yml +++ b/.github/workflows/check_ap2_systemd_full.yml @@ -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 diff --git a/.github/workflows/check_ap2_systemd_full_build_folder.yml b/.github/workflows/check_ap2_systemd_full_build_folder.yml index a541aa72..1164ee37 100644 --- a/.github/workflows/check_ap2_systemd_full_build_folder.yml +++ b/.github/workflows/check_ap2_systemd_full_build_folder.yml @@ -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 diff --git a/.github/workflows/check_ap2_systemv_full.yml b/.github/workflows/check_ap2_systemv_full.yml index 136275c8..b3cb58a7 100644 --- a/.github/workflows/check_ap2_systemv_full.yml +++ b/.github/workflows/check_ap2_systemv_full.yml @@ -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 diff --git a/.github/workflows/check_classic_mac_basic.yml b/.github/workflows/check_classic_mac_basic.yml index a9412914..1449cbcd 100644 --- a/.github/workflows/check_classic_mac_basic.yml +++ b/.github/workflows/check_classic_mac_basic.yml @@ -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 diff --git a/.github/workflows/check_classic_systemd_basic.yml b/.github/workflows/check_classic_systemd_basic.yml index 6bf3ef98..d43dcebb 100644 --- a/.github/workflows/check_classic_systemd_basic.yml +++ b/.github/workflows/check_classic_systemd_basic.yml @@ -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 diff --git a/.github/workflows/check_classic_systemd_full.yml b/.github/workflows/check_classic_systemd_full.yml index 08070a40..9489f7df 100644 --- a/.github/workflows/check_classic_systemd_full.yml +++ b/.github/workflows/check_classic_systemd_full.yml @@ -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 diff --git a/.github/workflows/docker-on-push-tag-or-pr.yaml b/.github/workflows/docker-on-push-tag-or-pr.yaml index bdd01bf7..4e25a428 100644 --- a/.github/workflows/docker-on-push-tag-or-pr.yaml +++ b/.github/workflows/docker-on-push-tag-or-pr.yaml @@ -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: diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 820c8862..2b329ed4 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -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.' diff --git a/ADVANCED TOPICS/README.md b/ADVANCED TOPICS/README.md index 086af05b..4d96cb79 100644 --- a/ADVANCED TOPICS/README.md +++ b/ADVANCED TOPICS/README.md @@ -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). diff --git a/BUILD.md b/BUILD.md index a17727ed..dd10d4c9 100644 --- a/BUILD.md +++ b/BUILD.md @@ -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 ``` diff --git a/Makefile.am b/Makefile.am index 64763533..96bf0cb4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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)\" diff --git a/activity_monitor.c b/activity_monitor.c index 06fe4fee..368e8589 100644 --- a/activity_monitor.c +++ b/activity_monitor.c @@ -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 } diff --git a/ap2_buffered_audio_processor.c b/ap2_buffered_audio_processor.c index 47f89c5d..1bf18882 100644 --- a/ap2_buffered_audio_processor.c +++ b/ap2_buffered_audio_processor.c @@ -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) { diff --git a/ap2_event_message_handler.c b/ap2_event_message_handler.c index 94a84d0d..457416a8 100644 --- a/ap2_event_message_handler.c +++ b/ap2_event_message_handler.c @@ -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, diff --git a/ap2_event_receiver.c b/ap2_event_receiver.c index 29f23970..f7923a40 100644 --- a/ap2_event_receiver.c +++ b/ap2_event_receiver.c @@ -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); diff --git a/audio_alsa.c b/audio_alsa.c index 83ed20df..7571a375 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -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 diff --git a/audio_sndio.c b/audio_sndio.c index dd1d3bac..35637d03 100644 --- a/audio_sndio.c +++ b/audio_sndio.c @@ -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 { diff --git a/common.c b/common.c index c513077e..f041c226 100644 --- a/common.c +++ b/common.c @@ -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."); diff --git a/common.h b/common.h index 3c523833..772da08a 100644 --- a/common.h +++ b/common.h @@ -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 diff --git a/configure.ac b/configure.ac index 8cad1b17..6d206af3 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [5.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]) diff --git a/dacp.c b/dacp.c index f8d90404..e0347a52 100644 --- a/dacp.c +++ b/dacp.c @@ -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) || diff --git a/dbus-service.c b/dbus-service.c index 4dbd1a6a..397ac759 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -44,6 +44,7 @@ #include "metadata/hub.h" #include "dbus-service.h" +#include "utilities/exit.h" #ifdef CONFIG_CONVOLUTION #include @@ -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; } diff --git a/man/shairport-sync.1 b/man/shairport-sync.1 index d7d7336c..e9a0b87f 100644 --- a/man/shairport-sync.1 +++ b/man/shairport-sync.1 @@ -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. diff --git a/man/shairport-sync.1.xml b/man/shairport-sync.1.xml index 76170a5c..73e3f3e9 100644 --- a/man/shairport-sync.1.xml +++ b/man/shairport-sync.1.xml @@ -45,7 +45,6 @@ [-g | --get-cover-art] [-j | --justDaemoniseNoPIDFile] [--logOutputLevel] - [--log-to-syslog] [-L latency | --latency=latency] [-m backend | --mdns=backend] [-M | --metadata-enable] @@ -269,16 +268,6 @@ - - @@ -446,7 +435,7 @@ diff --git a/metadata/pc_queue.c b/metadata/pc_queue.c index 86bc9845..e216558c 100644 --- a/metadata/pc_queue.c +++ b/metadata/pc_queue.c @@ -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); diff --git a/mpris-service.c b/mpris-service.c index 06d506c2..71e254f3 100644 --- a/mpris-service.c +++ b/mpris-service.c @@ -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; } diff --git a/mqtt.c b/mqtt.c index 3acb8865..f9936cb9 100644 --- a/mqtt.c +++ b/mqtt.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -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 ", where 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') { diff --git a/player.c b/player.c index edc7f347..555c6a2e 100644 --- a/player.c +++ b/player.c @@ -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,20 +4626,18 @@ 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; 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)) || + // if any of these are true, we need to create a new convolver + (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..."); diff --git a/player.h b/player.h index 05c035f6..a5b2c27c 100644 --- a/player.h +++ b/player.h @@ -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 diff --git a/rtp.c b/rtp.c index 569c8fc3..7b7ee774 100644 --- a/rtp.c +++ b/rtp.c @@ -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; } diff --git a/rtsp.c b/rtsp.c index 9ae4a214..60a9929c 100644 --- a/rtsp.c +++ b/rtsp.c @@ -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) { diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index aa8d35e2..61898166 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -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 = ""; +// convolution_ir_files = ""; // 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. }; diff --git a/scripts/shairport-sync.freebsd b/scripts/shairport-sync.freebsd index 7f536084..0b74f950 100644 --- a/scripts/shairport-sync.freebsd +++ b/scripts/shairport-sync.freebsd @@ -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 diff --git a/scripts/shairport-sync.service.in b/scripts/shairport-sync.service.in index 190fc9de..dc24e2db 100644 --- a/scripts/shairport-sync.service.in +++ b/scripts/shairport-sync.service.in @@ -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 diff --git a/shairport.c b/shairport.c index f4164e16..3c3b0ab5 100644 --- a/shairport.c +++ b/shairport.c @@ -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 @@ -771,6 +773,17 @@ int parse_options(int argc, char **argv) { "inclusive.", 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)) { @@ -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; } diff --git a/utilities/buffered_read.c b/utilities/buffered_read.c index ac90c24a..2f5a07cd 100644 --- a/utilities/buffered_read.c +++ b/utilities/buffered_read.c @@ -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) { diff --git a/utilities/debug.c b/utilities/debug.c index 50ffd37c..ebc7ba8c 100644 --- a/utilities/debug.c +++ b/utilities/debug.c @@ -24,6 +24,7 @@ SOFTWARE. #include "debug.h" #include // for isprint() +#include // for usleep() #include #include #include @@ -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, ...) { diff --git a/utilities/debug.h b/utilities/debug.h index 8491ed74..751bbd26 100644 --- a/utilities/debug.h +++ b/utilities/debug.h @@ -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); diff --git a/utilities/exit.c b/utilities/exit.c new file mode 100644 index 00000000..9ba01afa --- /dev/null +++ b/utilities/exit.c @@ -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 // for sig_atomic_t +#include // for EXIT_SUCCESS +#include // for memset +#include // for usleep +#include + +#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 +} \ No newline at end of file diff --git a/utilities/exit.h b/utilities/exit.h new file mode 100644 index 00000000..359268fd --- /dev/null +++ b/utilities/exit.h @@ -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 + +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. diff --git a/utilities/network_utilities.c b/utilities/network_utilities.c index 096c31f9..51141310 100644 --- a/utilities/network_utilities.c +++ b/utilities/network_utilities.c @@ -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; } \ No newline at end of file diff --git a/utilities/rtsp_message_utilities.c b/utilities/rtsp_message_utilities.c index 015376fe..23ac6594 100644 --- a/utilities/rtsp_message_utilities.c +++ b/utilities/rtsp_message_utilities.c @@ -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) {