From 02ea0ea6c0bed1b049d60bd5e8830c6541e67082 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:15:41 +0200 Subject: [PATCH 01/17] Update release notes for version 5.3-dev-3 [skip ci] Added release notes for version 5.3-dev-3-gcac178a6, including a bug fix related to libdaemon. --- RELEASENOTES-DEVELOPMENT.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 7bf449d1..33bf8810 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,10 @@ +Version 5.3-dev-3-gcac178a6 +== +This is essentially Version 5.2.1. + +**Bug Fix** +* Fixed a bug if `libdaemon` is included in the configuration. The bug was a reference to `log_to_syslog()`, which is obsolete and has been removed. Thanks to [Daeho Ro](https://github.com/daeho-ro) for the [PR](https://github.com/mikebrady/shairport-sync/pull/2243). + Version 5.3-dev == This is essentially Version 5.2, which is essentially Version 5.2-dev-46. From 63ad9f7e94403205c293e798bedcfa5fcc47cc60 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:33:44 +0100 Subject: [PATCH 02/17] quieten a debug message. --- shairport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shairport.c b/shairport.c index d64538dd..8489832c 100644 --- a/shairport.c +++ b/shairport.c @@ -1955,7 +1955,7 @@ void exit_function() { } if (glib_worker_thread != NULL) { g_thread_join(glib_worker_thread); - debug(1, "GLib worker thread joined"); + debug(2, "GLib worker thread joined"); } #endif From 4c4f18d2856a1f972be8f5c0abe29e05651e3a18 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:36:20 +0100 Subject: [PATCH 03/17] Check more carefully if an exact rate and format match should be searched for. If it fails, instead of failing completely, look for the best format at the same rate. --- audio.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/audio.c b/audio.c index 90ca8fac..707da922 100644 --- a/audio.c +++ b/audio.c @@ -959,21 +959,30 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra local_rate = rate; // begin with the requested rate debug(3, "check for an exact multiple of %u with %u channels.", rate, local_channels); while ((local_rate <= 384000) && (local_format == SPS_FORMAT_UNKNOWN)) { + // first, if we in a circumstance where no further audio processing, + // e.g. volume adjustment or convolution is required, check_ + // for an exact rate and bit depth match with the same or more channels if ( - // clang-format off + // clang-format off // check for the exact format only under these conditions, otherwise look for the best (config.ignore_volume_control != 0) && (config.volume_max_db_set == 0) && +#ifdef CONFIG_CONVOLUTION + (config.convolution_enabled == 0) && +#endif + (config.loudness_enabled == 0) && (local_rate == rate) && (local_channels >= channels) && (config.playback_mode != ST_mono) - // clang-format on + // clang-format on ) { - // debug(1, "check exact"); + // debug(1, "first, check exact rate and format"); local_format = check_configuration_with_formats( local_channels, local_rate, (sps_format_t)format, check_configuration); - } else { - // debug(1, "check best"); + } + + if (local_format == SPS_FORMAT_UNKNOWN) { + // debug(1, "check best, with any format"); local_format = check_configuration_with_formats(local_channels, local_rate, SPS_FORMAT_S32, check_configuration); } From 7f8a23b51867cde212e9a8d19ad4a336284255a4 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:37:08 +0100 Subject: [PATCH 04/17] Remove redundant flags, add one new one and remove some commented out junk. --- common.h | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/common.h b/common.h index 772da08a..11a10fc0 100644 --- a/common.h +++ b/common.h @@ -213,6 +213,7 @@ typedef struct { char *pa_sink; // the name (or id) of the sink that Shairport Sync will play on. #endif + #ifdef CONFIG_PIPEWIRE char *pw_application_name; // the name under which Shairport Sync shows up as an "Application" in // the Sound Preferences in most desktop Linuxes. @@ -221,6 +222,7 @@ typedef struct { char *pw_node_name; // defaults to the application's name, usually "shairport-sync". char *pw_sink_target; // leave this unset if you don't want to change the sink_target. #endif + #ifdef CONFIG_METADATA int metadata_enabled; char *metadata_pipename; @@ -230,6 +232,7 @@ typedef struct { int get_coverart; double metadata_progress_interval; // 0 means no progress reports #endif + #ifdef CONFIG_MQTT int mqtt_enabled; char *mqtt_hostname; @@ -250,6 +253,7 @@ typedef struct { char *mqtt_autodiscovery_prefix; char *mqtt_empty_payload_substitute; #endif + uint8_t ap1_prefix[6]; uint8_t hw_addr[8]; // only needs 6 but 8 is handy when converting this to a number int port; @@ -302,8 +306,6 @@ typedef struct { // to be enabled under the auto setting int decoders_supported; int decoder_in_use; - // char *logfile; - // char *errfile; char *configfile; char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by // default “_raop._tcp.” for AirPlay 1. @@ -348,17 +350,11 @@ typedef struct { #ifdef CONFIG_CONVOLUTION int convolution_enabled; - unsigned int convolution_rate; // 0 means the convolver has never been initialised, so ignore - // convolver_valid. - // but if this is the same as the current rate and convolver_valid is false, it means that an - // attempt to initialise the convolver has failed. - size_t convolution_block_size; unsigned int convolution_ir_file_count; ir_file_info_t *convolution_ir_files; // NULL or an array of information about all the impulse // response files loaded int convolution_ir_files_updated; // set to true if the convolution_ir_files are changed. Cleared // when the convolver has been initialised - int convolver_valid; // set to true if the convolver can be initialised unsigned int convolution_threads; // number of threads in the convolver thread pool float convolution_gain; double convolution_max_length_in_seconds; @@ -385,6 +381,7 @@ typedef struct { int scan_max_inactive_count; // number of scans to do before stopping if not made active again // (about 15 minutes worth) #endif + int disable_resend_requests; // set this to stop resend request being made for missing packets double diagnostic_drop_packet_fraction; // pseudo randomly drop this fraction of packets, for // debugging. Currently audio packets only... @@ -395,10 +392,11 @@ typedef struct { int jack_soxr_resample_quality; #endif #endif + void *gradients; // a linked list of the clock gradients discovered for all DACP IDs // can't use IP numbers as they might be given to different devices // can't get hold of MAC addresses. - // can't define the nvll linked list struct here + // can't define the null linked list struct here char *firmware_version; // use these in information requests char *model; @@ -440,6 +438,7 @@ typedef struct { #if defined(CONFIG_DBUS_INTERFACE) dbus_message_bus_t dbus_service_bus_type; #endif + #if defined(CONFIG_MPRIS_INTERFACE) dbus_message_bus_t mpris_service_bus_type; #endif @@ -452,21 +451,6 @@ 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 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(); - -// 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 From 5ca9ade96377142bfa552f9e4d9731bd08dec767 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 2 Aug 2026 10:20:44 +0100 Subject: [PATCH 05/17] Fix bug in audio output rate and format matching [skip ci] Fixed a bug related to output rate and format matching in Shairport Sync. The fix ensures SPS looks for another format at the same rate if an exact match is not found. --- RELEASENOTES-DEVELOPMENT.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 33bf8810..42bb559e 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,20 @@ +Version 5.3-dev-7-g7f8a23b5 +== +**Bug Fix** +* Fixed a subtle bug thanks to [nicolas314](https://github.com/nicolas314)'s [report](https://github.com/mikebrady/shairport-sync/issues/2244). + + The bug would occur only in specific circumstances when automatically choosing a suitable output rate and format to match the input. Rather ironically, it arose from an attempt to maximise audio fidelity, as follows: + + If it can be determined from the settings that Shairport Sync ("SPS") does not have to modify the audio, e.g. by changing volume, then SPS will look for the output format with the exact same rate and format as the input, so that audio passes through SPS without alteration of any kind. + + Unfortunately, if it failed to find an exact match, SPS would start searching at a _different output rate_. + + The bug only showed up when it could be determined that no modification of the audio was to be performed. So the bug only occured if `ignore_volume_control` was set to `yes` and the output device could not exactly match the input format. + + The bug fix means that if SPS fails to find an exact format match, it will now look for another format _at the same rate_ before searching at other rates. + + In addition, to be sure that no further audio processing is needed, SPS will check to ensure that neither convolution not loudness are enabled. + Version 5.3-dev-3-gcac178a6 == This is essentially Version 5.2.1. From 43abfd63d2856cce2ea9c94e4faead947fedece5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 10:02:41 +0000 Subject: [PATCH 06/17] Bump docker/login-action from 4.5.1 to 4.6.0 Bumps [docker/login-action](https://github.com/docker/login-action) from 4.5.1 to 4.6.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v4.5.1...v4.6.0) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 4.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-on-push-tag-or-pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-on-push-tag-or-pr.yaml b/.github/workflows/docker-on-push-tag-or-pr.yaml index 4e25a428..34011ee5 100644 --- a/.github/workflows/docker-on-push-tag-or-pr.yaml +++ b/.github/workflows/docker-on-push-tag-or-pr.yaml @@ -59,7 +59,7 @@ jobs: fi - name: Login to Docker Registry - uses: docker/login-action@v4.5.1 + uses: docker/login-action@v4.6.0 with: registry: ${{ secrets.DOCKER_REGISTRY }} username: ${{ secrets.DOCKER_REGISTRY_USER }} From 31550e5be324410380f650fb7f7847abb367417f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 10:02:58 +0000 Subject: [PATCH 07/17] Bump actions/stale from 10.4.0 to 11.0.0 Bumps [actions/stale](https://github.com/actions/stale) from 10.4.0 to 11.0.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v10.4.0...v11.0.0) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 11.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 2b329ed4..04495c77 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.4.0 + - uses: actions/stale@v11.0.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.' From 9960f9c4820efc47750894da725b4ffebd13c43f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:53:06 +0100 Subject: [PATCH 08/17] Clarify bug fix process in release notes [skip ci] Updated the release notes to clarify the bug fix process and ensure proper checks before audio processing. --- RELEASENOTES-DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 42bb559e..d9aee0b3 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -13,7 +13,7 @@ Version 5.3-dev-7-g7f8a23b5 The bug fix means that if SPS fails to find an exact format match, it will now look for another format _at the same rate_ before searching at other rates. - In addition, to be sure that no further audio processing is needed, SPS will check to ensure that neither convolution not loudness are enabled. + In addition, to be sure that no further audio processing is needed, SPS checks to ensure that neither convolution not loudness are enabled. Version 5.3-dev-3-gcac178a6 == From 214eba2e7a9c080223a172dbd6a9c6f74e2f5b3f Mon Sep 17 00:00:00 2001 From: Kel Modderman Date: Sun, 9 Aug 2026 18:27:20 +1000 Subject: [PATCH 09/17] Read the plist from stdin without an explicit - xxd reads standard input when given no file operand, so the - is redundant. It also breaks builds that provide xxd through a partial reimplementation such as OpenWrt's scripts/xxdi.pl, which treats - as a file name and exits with xxdi.pl: Unable to open -: No such file or directory xml_plist_codegen.sh does not check for that failure, so the generated plists/get_info_response.c ends up with an empty byte array and AirPlay 2 answers GET /info with nothing. --- xml_plist_codegen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xml_plist_codegen.sh b/xml_plist_codegen.sh index 18ffdfb7..3c8f5ad6 100644 --- a/xml_plist_codegen.sh +++ b/xml_plist_codegen.sh @@ -23,6 +23,6 @@ printf "// Please do not edit this file!\n" > $2/$BASE_FILENAME.c printf "// This file was automatically generated from $INPUT_FILE_NAME.\n\n" >> $2/$BASE_FILENAME.c printf "#include \"$BASE_FILENAME.h\"\n\n" >> $2/$BASE_FILENAME.c printf "unsigned char ${BASE_FILENAME}_plist[] = {\n" >> $2/$BASE_FILENAME.c -plistutil -i $1 | xxd -i - >> $2/$BASE_FILENAME.c +plistutil -i $1 | xxd -i >> $2/$BASE_FILENAME.c printf " };\n\n" >> $2/$BASE_FILENAME.c printf "size_t ${BASE_FILENAME}_plist_len = sizeof(${BASE_FILENAME}_plist);\n" >> $2/$BASE_FILENAME.c From 427061fb48973a3113045185d41eba0007b7d945 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 9 Aug 2026 17:20:35 +0100 Subject: [PATCH 10/17] Update release notes for version 5.3-dev-15 [skip ci] Updated release notes to reflect enhancements and bug fixes in version 5.3-dev-15-gd0422952, including removal of unnecessary `-` for `xxd` and clarification of bug fix details. --- RELEASENOTES-DEVELOPMENT.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index d9aee0b3..0619948e 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,17 +1,22 @@ +Version 5.3-dev-15-gd0422952 +== +**Enhancement** +* Remove the explicit `-` for `xxd` to read from `STDIN`. It's not necessary, and removing it makes it easier to package Shairport Sync on other build systems, such as [OpenWrt](https://openwrt.org/start). Thanks to [kel-mo](https://github.com/kel-mo) for the [PR](https://github.com/mikebrady/shairport-sync/pull/2247). + Version 5.3-dev-7-g7f8a23b5 == **Bug Fix** * Fixed a subtle bug thanks to [nicolas314](https://github.com/nicolas314)'s [report](https://github.com/mikebrady/shairport-sync/issues/2244). - The bug would occur only in specific circumstances when automatically choosing a suitable output rate and format to match the input. Rather ironically, it arose from an attempt to maximise audio fidelity, as follows: + The bug would occur only in specific circumstances when searching for a suitable output rate and format to match the input. Rather ironically, it arose from an attempt to maximise audio fidelity, as follows: - If it can be determined from the settings that Shairport Sync ("SPS") does not have to modify the audio, e.g. by changing volume, then SPS will look for the output format with the exact same rate and format as the input, so that audio passes through SPS without alteration of any kind. + * If it can be determined from the settings that Shairport Sync ("SPS") does not have to modify the audio, e.g. by changing volume, then SPS will look for an output format with the exact same rate and format as the input, so that audio passes through SPS without alteration of any kind. - Unfortunately, if it failed to find an exact match, SPS would start searching at a _different output rate_. + * Unfortunately, due to the bug, if it failed to find an exact match, SPS would start searching at a _different output rate_. - The bug only showed up when it could be determined that no modification of the audio was to be performed. So the bug only occured if `ignore_volume_control` was set to `yes` and the output device could not exactly match the input format. + * The bug only showed up when it could be determined that no modification of the audio was to be performed. So the bug only occured if `ignore_volume_control` was set to `yes` and the output device could not exactly match the input format. - The bug fix means that if SPS fails to find an exact format match, it will now look for another format _at the same rate_ before searching at other rates. + The fix means that if SPS fails to find an exact format match, it will now look for another format _at the same rate_ before searching at other rates. In addition, to be sure that no further audio processing is needed, SPS checks to ensure that neither convolution not loudness are enabled. From 4df014537270db60a91c8b25cbd81d13e26d15a1 Mon Sep 17 00:00:00 2001 From: prhodey <76759868+prhodey@users.noreply.github.com> Date: Wed, 19 Aug 2026 20:10:50 -0400 Subject: [PATCH 11/17] Defer buffered AirPlay 2 latency offset conversion --- rtp.c | 7 +++++-- rtsp.c | 7 ++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rtp.c b/rtp.c index 7b7ee774..bcd84149 100644 --- a/rtp.c +++ b/rtp.c @@ -1488,8 +1488,11 @@ int get_ptp_anchor_local_time_info(rtsp_conn_info *conn, uint32_t *anchorRTP, } if (conn->last_anchor_info_is_valid != 0) { - if (anchorRTP != NULL) - *anchorRTP = conn->last_anchor_rtptime; + if (anchorRTP != NULL) { + // Use the current rate in case the stream format has changed. + int32_t added_latency = (int32_t)(config.audio_backend_latency_offset * conn->input_rate); + *anchorRTP = conn->last_anchor_rtptime - added_latency; + } if (anchorLocalTime != NULL) *anchorLocalTime = conn->last_anchor_local_time; } diff --git a/rtsp.c b/rtsp.c index 60a9929c..91ea19ef 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1453,11 +1453,8 @@ void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message // debug(1, "anchor rtpTime is %" PRId64 ".", rtpTime); uint32_t anchorRTPTime = rtpTime; - int32_t added_latency = (int32_t)(config.audio_backend_latency_offset * conn->input_rate); - // debug(1,"anchorRTPTime: %" PRIu32 ", added latency: %" PRId32 ".", anchorRTPTime, - // added_latency); - set_ptp_anchor_info(conn, conn->networkTimeTimelineID, anchorRTPTime - added_latency, - anchorTimeNanoseconds); + // Store the raw anchor; apply the latency offset when it is used. + set_ptp_anchor_info(conn, conn->networkTimeTimelineID, anchorRTPTime, anchorTimeNanoseconds); } item = plist_dict_get_item(messagePlist, "rate"); From b69e01cdf9d4e1577c5fe4bc1738404d27b10159 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:55:30 +0100 Subject: [PATCH 12/17] Correct the treatment of audio backend latency offset in Realtime streams. Fix a warning if the offset is too negative and that would require frames to be played before they could possibly arrive. --- rtp.c | 71 ++++++++++++++++++++++------------------------------------- 1 file changed, 26 insertions(+), 45 deletions(-) diff --git a/rtp.c b/rtp.c index bcd84149..ec718964 100644 --- a/rtp.c +++ b/rtp.c @@ -1619,6 +1619,7 @@ int32_t decipher_player_put_packet(uint8_t *ciphered_audio_alt, ssize_t nread, } void *rtp_ap2_control_receiver(void *arg) { + const int32_t ap2_realttime_stream_latency_fudge_factor = 11025; // seems to bring everything into sync // #include // debug(1, "rtp_ap2_control_receiver PID %d", syscall(SYS_gettid)); pthread_cleanup_push(rtp_ap2_control_handler_cleanup_handler, arg); @@ -1677,22 +1678,6 @@ void *rtp_ap2_control_receiver(void *arg) { switch (packet[1]) { case 215: // code 215, effectively an anchoring announcement { - // struct timespec tnr; - // clock_gettime(CLOCK_REALTIME, &tnr); - // uint64_t local_realtime_now = timespec_to_ns(&tnr); - - /* - char obf[4096]; - char *obfp = obf; - int obfc; - for (obfc=0;obfcinput_rate); - // the actual latency is the notified latency plus the fixed latency + the added - // latency + // this updates the anchor information contained in the packet + int32_t stream_specified_latency = frame_2 - frame_1; // this is the latency expected + if (stream_specified_latency != 77175) + debug(1, "Stream-specified latency is %d frames. Normally it is 77175.", stream_specified_latency); + int32_t net_source_latency = stream_specified_latency + ap2_realttime_stream_latency_fudge_factor; - int32_t net_latency = - notified_latency + 11035 + - added_latency; // this is the latency between incoming frames and the DAC - net_latency = net_latency - (int32_t)(config.audio_backend_buffer_desired_length * + // Now to accommodate a backend buffer of the desired length. + // Note that it's in input-rate frames, not output-rate frames! + + net_source_latency = net_source_latency - (int32_t)(config.audio_backend_buffer_desired_length * conn->input_rate); - // debug(1, "Net latency is %d frames.", net_latency); + + // Now we want to check the user-specified latency offset. + // We want to warn the user if they have asked for a negative latency that is too great -- + // one that would require packets to arrive before they actually do, + // (which is about two seconds before they are to be played). + + int32_t net_latency = net_source_latency + (int32_t)(config.audio_backend_latency_offset * conn->input_rate); + if (net_latency <= 0) { if (conn->latency_warning_issued == 0) { - warn("The stream latency (%f seconds) it too short to accommodate an offset of " - "%f " - "seconds and a backend buffer of %f seconds.", - ((notified_latency + 11035) * 1.0) / conn->input_rate, + warn("The stream latency (%g seconds) is too short to accommodate an audio backend latency offset of " + "%g seconds and a backend buffer of %g seconds. The audio_backend_latency_offset has been set to zero.", + ((stream_specified_latency + ap2_realttime_stream_latency_fudge_factor) * 1.0) / conn->input_rate, config.audio_backend_latency_offset, config.audio_backend_buffer_desired_length); - warn("(FYI the stream latency needed would be %f seconds.)", - config.audio_backend_buffer_desired_length - - config.audio_backend_latency_offset); + config.audio_backend_latency_offset = 0.0; + net_latency = net_source_latency; conn->latency_warning_issued = 1; } - conn->latency = notified_latency + 11035; - } else { - conn->latency = notified_latency + 11035 + added_latency; } - - set_ptp_anchor_info(conn, clock_id, frame_1 - 11035 - added_latency, - remote_packet_time_ns); + conn->latency = net_latency; // this is the time window within which packets can be accepted without being too late + set_ptp_anchor_info(conn, clock_id, frame_1 - ap2_realttime_stream_latency_fudge_factor, remote_packet_time_ns); if (conn->anchor_clock != clock_id) { debug(2, "Connection %d: Change Anchor Clock: %" PRIx64 ".", conn->connection_number, clock_id); From c4e17b60ef5c5fc0a25b531f6bb0d2a5f626d91a Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:13:42 +0100 Subject: [PATCH 13/17] Update release notes for version 5.3-dev-19 [skip ci] Added a bug fix for audio backend latency offset handling in AirPlay 2 operation, including background information and acknowledgments. --- RELEASENOTES-DEVELOPMENT.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 0619948e..431c5111 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,14 @@ +Version 5.3-dev-19-gb69e01cd +== +**Bug Fix** +* Fixed a bug whereby the audio backend latency offset was mishandled in AirPlay 2 operation. + + Background: the `audio_backend_latency_offset_in_seconds` in the `general` section of the configuration file allows you to [delay or advance the output](ADVANCED%20TOPICS/AdjustingSync.md#adjusting-synchronisation-with-shairport-sync) by a fixed amount relative to the exact synchronisation time. It can be used, for example, to delay the audio slightly so that it synchronises with audio coming from another device that is delayed by internal processing. + + The bug was that the latency offset value was being used before the frame rate of the incoming audio was properly set, resulting in an incorrect calculation of the number of frames to offset. + + Many thanks to [prhodey](https://github.com/prhodey) for discovering and investigating the problem and for a [PR](https://github.com/mikebrady/shairport-sync/pull/2253) that fixes the problem for Buffered Audio streams. An adjustment has also been made to the treatment of Realtime streams. + Version 5.3-dev-15-gd0422952 == **Enhancement** From 6966a74c82115aee3d522e7f0b37b4b3e927bcf9 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:47:37 +0100 Subject: [PATCH 14/17] If the output rate is different to the input rate, look for the deepest output bit depth, to give the transcoder the chance to minimise transcoding inaccuracies. --- audio.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio.c b/audio.c index 707da922..c1d27348 100644 --- a/audio.c +++ b/audio.c @@ -994,7 +994,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra } #ifdef CONFIG_FFMPEG if (local_format == SPS_FORMAT_UNKNOWN) { - debug(3, "check for the next highest rate above %u with %u channels.", rate, + debug(3, "check for the next highest rate above %u with %u channels and the best format.", rate, local_channels); unsigned int rate_pointer = 0; while ((rate_pointer < sizeof(rates) / sizeof(unsigned int)) && @@ -1002,7 +1002,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra local_rate = rates[rate_pointer]; if (local_rate > rate) { local_format = check_configuration_with_formats( - local_channels, local_rate, (sps_format_t)format, check_configuration); + local_channels, local_rate, SPS_FORMAT_S32, check_configuration); } if (local_format == SPS_FORMAT_UNKNOWN) { rate_pointer++; @@ -1012,13 +1012,13 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra if (local_format == SPS_FORMAT_UNKNOWN) { int rate_pointer = (int)(sizeof(rates) / sizeof(unsigned int) - 1); - debug(3, "check for the next lowest rate below %u with %u channels.", rate, + debug(3, "check for the next lowest rate below %u with %u channels and the best format.", rate, local_channels); while ((rate_pointer >= 0) && (local_format == SPS_FORMAT_UNKNOWN)) { local_rate = rates[rate_pointer]; if (local_rate < rate) { local_format = check_configuration_with_formats( - local_channels, local_rate, (sps_format_t)format, check_configuration); + local_channels, local_rate, SPS_FORMAT_S32, check_configuration); } if (local_format == SPS_FORMAT_UNKNOWN) { rate_pointer--; From 6cdd0eb7948596311e224b35660e3b667fdfa1e9 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:51:25 +0100 Subject: [PATCH 15/17] Update release notes for version 5.3-dev-21 [skip ci] Added enhancement regarding output rate and bit depth for transcoding. --- RELEASENOTES-DEVELOPMENT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 431c5111..7960eee0 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,8 @@ +Version 5.3-dev-21-g6966a74c +== +**Enhancement** +* If the output rate is different to the input rate (e.g. 48,000 vs 44,100), look for the deepest output bit depth in order that the transcoder has the maximum dynamic range available so that it can minimise transcoding inaccuracies. + Version 5.3-dev-19-gb69e01cd == **Bug Fix** From 3ba7dfea6fa0dbe258b32b31827891eacc2b5979 Mon Sep 17 00:00:00 2001 From: DMITRY BATIN <47609764+demosspro@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:56:47 +0300 Subject: [PATCH 16/17] Update README.md Old link not working. Add a valid link to MPRIS spec. - [v ] Documentation update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce15aedf..7fa49b1e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Shairport Sync does not support AirPlay video or photo streaming. * Metadata — Shairport Sync can deliver metadata supplied by the source, such as Album Name, Artist Name, Cover Art, etc. through a pipe or UDP socket to a recipient application program — see https://github.com/mikebrady/shairport-sync-metadata-reader for a sample recipient. Sources that supply metadata include iTunes and the Music app in macOS and iOS. * An interface to [MQTT](https://en.wikipedia.org/wiki/MQTT), a popular protocol for Inter Process Communication, Machine-to-Machine, Internet of Things and Home Automation projects. The interface provides access to metadata and artwork, with remote control for Classic AirPlay clients. * Digital Signal Processing facilities – please see the [DSP Wiki Page Guide](https://github.com/mikebrady/shairport-sync/wiki/Digital-Signal-Processing-with-Shairport-Sync). (Thanks to [Yann Pomarède](https://github.com/yannpom) for the code and to [Paul Wieland](https://github.com/PaulWieland) for the guide.) -* An [MPRIS](https://specifications.freedesktop.org/mpris-spec/2.2/)-like interface, partially complete and very functional, including access to metadata and artwork, and partial remote control for Classic AirPlay clients. +* An [MPRIS](https://specifications.freedesktop.org/mpris/latest/)-like interface, partially complete and very functional, including access to metadata and artwork, and partial remote control for Classic AirPlay clients. * A native D-Bus interface, including access to metadata, artwork and system settings. Remote control is also available for Classic AirPlay clients. * Better Volume Control — Shairport Sync offers finer control at very top and very bottom of the volume range. See http://tangentsoft.net/audio/atten.html for a good discussion of audio "attenuators", upon which volume control in Shairport Sync is modelled. See also the diagram of the volume transfer function in the documents folder. In addition, Shairport Sync can offer an extended volume control range on devices with a restricted range. * Flexible output rates, formats and channels with built-in transcoding. From 6e10eca88488df629b6ea9e84ecbb3b74366bc7c Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:05:57 +0100 Subject: [PATCH 17/17] Update MPRIS specification reference in documentation [skip ci] Updated README reference to the latest MPRIS specification version, acknowledging Dmitry Batin for the contribution. --- RELEASENOTES-DEVELOPMENT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 7960eee0..1ea7101a 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,8 @@ +Version 5.3-dev-24-g5e170a67 +== +**Documentation Update** +* Update the README reference to the MPRIS specification to the [latest](https://specifications.freedesktop.org/mpris/latest) version. Thanks to [Dmitry Batin](https://github.com/demosspro) for the [PR](https://github.com/mikebrady/shairport-sync/pull/2255). + Version 5.3-dev-21-g6966a74c == **Enhancement**