From 22c001cf1b4d6caf5c4304869dd733036050f166 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 7 Apr 2018 18:23:48 +0100 Subject: [PATCH 01/20] Update to Version 3.2 Release Candidate 1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ec18cc0..48c9d19b 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ What else? * 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 iOS. * Raw Audio — Shairport Sync can deliver raw PCM audio to standard output or to a pipe. This output is delivered synchronously with the source after the appropriate latency and is not interpolated or "stuffed" on its way through Shairport Sync. * Autotools and Libtool Support — the Shairport Sync build process uses GNU `autotools` and `libtool` to examine and configure the build environment — important for portability and for cross compilation. Previous versions of Shairport looked at the current system to determine which packages were available, instead of looking at the target system for what packages were available. -* Compilers on Linux, FreeBSD, OpenBSD. +* Compiles on Linux, FreeBSD, OpenBSD. * An MPRIS interface, partially complete and very functional. * A native D-Bus interface. From 5771470e5c13900065fe1d2be3ac46e4367bbafa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 7 Apr 2018 18:28:09 +0100 Subject: [PATCH 02/20] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d21e4b6c..62474db9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,4 @@ -Version 3.2drc1 +Version 3.2RC1 ==== **Enhancements** * Shairport Sync now offers an IPC interface via D-Bus. It provides an incomplete but functional MPRIS interface and also provides a native Shairport Sync interface. Both provide some metadata and some remote control. The native D-Bus interface permits remote control of the current AirPlay or iTunes client. It includes status information about whether the remote control connection is viable or not, i.e. whether it can still be used to control the client. A remote control connection to the audio client becomes valid when the client starts AirPlaying to Shairport Sync. The connections remains valid until the audio source deselects Shairport Sync for AirPlay, or until the client disappears, or until another client starts AirPlaying to Shairport Sync. After 15 minutes of inactivity, the remote contol connection will be dropped. From 7e69739bd2036df21bfd019acacee794808c9ea0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Mon, 9 Apr 2018 19:58:24 +0100 Subject: [PATCH 03/20] Clarify setting for Raspberry Pi --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48c9d19b..ec34beea 100644 --- a/README.md +++ b/README.md @@ -354,11 +354,20 @@ Note: Shairport Sync can take configuration settings from command line options. **Raspberry Pi** -The Raspberry Pi has a built-in audio DAC that is connected to the device's headphone jack. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line: +The Raspberry Pi Models A and B have a built-in audio DAC that is connected to the device's headphone jack. An updated audio driver has greatly improved the quality of the output – see [#525](https://github.com/mikebrady/shairport-sync/issues/525) for details. To activate the updated driver, add the line: ``` audio_pwm_mode=2 ``` -to `/boot/config.txt` and reboot. +to `/boot/config.txt`. To make Shairport Sync output to the built-in audio DAC and use its hardware mixer, in the `alsa` section of the configuration file, set the output device and mixer as follows: +``` +alsa = +{ + output_device = "hw:0"; // the name of the alsa output device. Use "alsamixer" or "aplay" to find out the names of devices, mixers, etc. + mixer_control_name = "PCM"; // the name of the mixer to use to adjust output volume. If not specified, volume in adjusted in software. + // ... other alsa settings +``` +(Remember to uncomment the lines by removing the `//` at the start of each.) When these changes have been made, reboot the machine. + Apart from a loud click when used for the first time after power-up, it is quite adequate for casual listening. A problem is that it declares itself to have a very large mixer volume control range – all the way from -102.38dB up to +4dB, a range of 106.38 dB. In reality, only the top 50 dB of it is in any way usable. To help get the most from the DAC, consider using the `volume_range_db` setting in the `general` group to instruct Shairport Sync to use the top of the DAC mixer's declared range. For example, if you set the `volume_range_db` figure to 50, the top 50 dB of the range will the used. With this setting on the Raspberry Pi, maximum volume will be +4dB and minimum volume will be -46dB, below which muting will occur. From a user's point of view, the effect of using this setting is to move the minimum usable volume all the way down to the bottom of the user's volume control, rather than have the minimum usable volume concentrated very close to the maximum volume. From 70d51b6722556083b813d9649e3e8e923a345f83 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 Apr 2018 10:41:44 +0100 Subject: [PATCH 04/20] RC2 has extra debug messages and handles situation where there is no DACP-ID or Active-Remote information --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index ca1842cb..59862489 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.2RC1], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.2RC2], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From fd7c2d7954c23a386c8f73cec67c3cc1e9d1d6cc Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 Apr 2018 20:05:21 +0100 Subject: [PATCH 05/20] Allow audio packets as well as resends on the control port. Add some debug messages. --- dacp.c | 2 +- player.c | 9 ++++++--- rtp.c | 48 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/dacp.c b/dacp.c index 63001113..4ecc2064 100644 --- a/dacp.c +++ b/dacp.c @@ -223,7 +223,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "Error %d setting receive timeout for DACP service.", errno); int ndata = recv(sockfd, buffer, sizeof(buffer), 0); - debug(3, "Received %d bytes: \"%s\".", ndata, buffer); + // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { debug(1, "dacp_send_command -- error receiving response for command \"%s\".", command); diff --git a/player.c b/player.c index 3552410a..4f652802 100644 --- a/player.c +++ b/player.c @@ -1173,7 +1173,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { if (!conn->ab_buffering) { // check once, after a short period of has elapsed, assuming 352 frames per packet - i = ((250 * 44100) / 352) / 1000; // approx 250 ms + i = ((125 * 44100) / 352) / 1000; // approx 125 ms if (i < seq_diff(conn->ab_read, conn->ab_write, conn->ab_read)) { seq_t next = seq_sum(conn->ab_read, i); abuf = conn->audio_buffer + BUFIDX(next); @@ -1686,7 +1686,7 @@ static void *player_thread_func(void *arg) { config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); } else if (frames_to_drop) { if (frames_to_drop > 3 * config.output_rate) - warn("Very large number of frames to drop: %" PRId64 ".",frames_to_drop); + warn("Very large number of frames to drop: %" PRId64 ".", frames_to_drop); debug(3, "%" PRId64 " frames to drop.", frames_to_drop); frames_to_drop -= inframe->length; if (frames_to_drop < 0) @@ -1888,7 +1888,10 @@ static void *player_thread_func(void *arg) { (!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) && (abs_sync_error > config.resyncthreshold * config.output_rate)) { if (abs_sync_error > 3 * config.output_rate) { - warn("Very large sync error: %" PRId64 " frames, with a delay of %" PRId64 "frames.",sync_error,delay); + warn("Very large sync error: %" PRId64 " frames, with delay: %" PRId64 + ", td_in_frames: %" PRId64 ", rt: %" PRId64 ", nt: %" PRId64 + ", current_delay: %" PRId64 ", frames.", + sync_error, delay, td_in_frames, rt, nt, current_delay); } sync_error_out_of_bounds++; } else { diff --git a/rtp.c b/rtp.c index 8168de14..f4020804 100644 --- a/rtp.c +++ b/rtp.c @@ -134,6 +134,7 @@ void *rtp_audio_receiver(void *arg) { if (type == 0x56) { pktp += 4; plen -= 4; + debug(1, "resent packet %u received in audio port.", ntohs(*(uint16_t *)(pktp + 2))); } seq_t seqno = ntohs(*(uint16_t *)(pktp + 2)); // increment last_seqno and see if it's the same as the incoming seqno @@ -344,27 +345,42 @@ void *rtp_control_receiver(void *arg) { } else { debug(1, "Sync packet received before we got a timing packet back."); } - } else if (packet[1] == 0xd6) { // resent audio data in the control path -- whaale only? - // debug(1, "Control Port -- Retransmitted Audio Data Packet received."); - pktp = packet + 4; - plen -= 4; - seq_t seqno = ntohs(*(uint16_t *)(pktp + 2)); + } else { + uint8_t type = packet[1] & ~0x80; + if ((type == 0x60) || + (type == 0x56)) { // audio data / resent audio data in the control path -- whaale only? + pktp = packet; - int64_t timestamp = monotonic_timestamp(ntohl(*(uint32_t *)(pktp + 4)), conn); + if (type == 0x56) { // resent audio data in the control path -- whaale only? + // debug(1, "Control Port -- Retransmitted Audio Data Packet received."); + pktp = packet + 4; + plen -= 4; + } - pktp += 12; - plen -= 12; + seq_t seqno = ntohs(*(uint16_t *)(pktp + 2)); + if (type == 0x56) + debug(1, "Resent audio packet %u received in the control port.", seqno); + else + debug(1, "Audio packet %u received in the control port.", seqno); - // check if packet contains enough content to be reasonable - if (plen >= 16) { - player_put_packet(seqno, timestamp, pktp, plen, conn); - continue; + int64_t timestamp = monotonic_timestamp(ntohl(*(uint32_t *)(pktp + 4)), conn); + + pktp += 12; + plen -= 12; + + // check if packet contains enough content to be reasonable + + if (plen >= 16) { + player_put_packet(seqno, timestamp, pktp, plen, conn); + continue; + } else { + debug(3, "Too-short retransmitted audio packet received in control port, ignored."); + } } else { - debug(3, "Too-short retransmitted audio packet received in control port, ignored."); + debug(1, "Control Port -- Unknown RTP packet of type 0x%02X length %d, ignored.", packet[1], + nread); } - } else - debug(1, "Control Port -- Unknown RTP packet of type 0x%02X length %d, ignored.", packet[1], - nread); + } } debug(3, "Control RTP thread interrupted. terminating."); From 89a6a7d29e4248cbe564d36e62472d370411c127 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 Apr 2018 20:13:53 +0100 Subject: [PATCH 06/20] Reformulate the conversion of seconds and nanoseconds into fixed point 64-bit time. --- common.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common.c b/common.c index 81e882ad..e942f1c0 100644 --- a/common.c +++ b/common.c @@ -794,8 +794,14 @@ uint64_t get_absolute_time_in_fp() { struct timespec tn; // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt clock_gettime(CLOCK_MONOTONIC, &tn); + uint64_t tnfpsec = tn.tv_sec; + uint64_t tnfpnsec = tn.tv_nsec; + tnfpsec = tnfpsec<<32; + tnfpnsec = tnfpnsec<<32; + tnfpnsec = tnfpnsec/1000000000; + time_now_fp = - ((uint64_t)tn.tv_sec << 32) + ((uint64_t)tn.tv_nsec << 32) / 1000000000; // types okay + tnfpsec + tnfpnsec; // types okay #endif #ifdef COMPILE_FOR_OSX uint64_t time_now_mach; From d392f75c1c37d2ea4a7757e935ea3ac48cabec95 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 Apr 2018 20:56:38 +0100 Subject: [PATCH 07/20] Add warnings to tell us if the time info might overflow, quieten some debug messages in rtp. --- common.c | 4 ++++ rtp.c | 19 +++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/common.c b/common.c index e942f1c0..e835ab67 100644 --- a/common.c +++ b/common.c @@ -795,7 +795,11 @@ uint64_t get_absolute_time_in_fp() { // can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt clock_gettime(CLOCK_MONOTONIC, &tn); uint64_t tnfpsec = tn.tv_sec; + if (tnfpsec>0x100000000) + warn("clock_gettime seconds overflow!"); uint64_t tnfpnsec = tn.tv_nsec; + if (tnfpnsec>0x100000000) + warn("clock_gettime nanoseconds seconds overflow!"); tnfpsec = tnfpsec<<32; tnfpnsec = tnfpnsec<<32; tnfpnsec = tnfpnsec/1000000000; diff --git a/rtp.c b/rtp.c index f4020804..e8dc5d7d 100644 --- a/rtp.c +++ b/rtp.c @@ -134,7 +134,7 @@ void *rtp_audio_receiver(void *arg) { if (type == 0x56) { pktp += 4; plen -= 4; - debug(1, "resent packet %u received in audio port.", ntohs(*(uint16_t *)(pktp + 2))); + debug(3,"resent packet %u received in audio port.",ntohs(*(uint16_t *)(pktp + 2))); } seq_t seqno = ntohs(*(uint16_t *)(pktp + 2)); // increment last_seqno and see if it's the same as the incoming seqno @@ -347,22 +347,21 @@ void *rtp_control_receiver(void *arg) { } } else { uint8_t type = packet[1] & ~0x80; - if ((type == 0x60) || - (type == 0x56)) { // audio data / resent audio data in the control path -- whaale only? + if ((type == 0x60) || (type == 0x56)) { // audio data / resent audio data in the control path -- whaale only? pktp = packet; - + if (type == 0x56) { // resent audio data in the control path -- whaale only? // debug(1, "Control Port -- Retransmitted Audio Data Packet received."); pktp = packet + 4; plen -= 4; - } - + } + seq_t seqno = ntohs(*(uint16_t *)(pktp + 2)); - if (type == 0x56) - debug(1, "Resent audio packet %u received in the control port.", seqno); + if (type==0x56) + debug(3,"Resent audio packet %u received in the control port.",seqno); else - debug(1, "Audio packet %u received in the control port.", seqno); - + debug(1,"Audio packet %u received in the control port.",seqno); + int64_t timestamp = monotonic_timestamp(ntohl(*(uint32_t *)(pktp + 4)), conn); pktp += 12; From ad450d745b5854cb3326bb52f3fb6968f9947a84 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 12 Apr 2018 08:52:47 +0100 Subject: [PATCH 08/20] Add an extra field to a buffer for checking purposes. --- player.c | 10 +++++++--- player.h | 3 ++- rtp.c | 10 ++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/player.c b/player.c index 4f652802..340b40d3 100644 --- a/player.c +++ b/player.c @@ -462,10 +462,11 @@ static void free_audio_buffers(rtsp_conn_info *conn) { free(conn->audio_buffer[i].data); } -void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, +void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info *conn) { // all timestamps are done at the output rate + // the "actual_timestamp" is the one that comes in the packet, and is carried over for debugging and checking only. int64_t ltimestamp = timestamp * conn->output_sample_ratio; @@ -518,6 +519,7 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i)); abuf->ready = 0; // to be sure, to be sure abuf->timestamp = 0; + abuf->given_timestamp = 0; abuf->sequence_number = 0; } // debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write); @@ -550,11 +552,13 @@ void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, abuf->ready = 1; abuf->length = datalen; abuf->timestamp = ltimestamp; + abuf->given_timestamp = actual_timestamp; abuf->sequence_number = seqno; } else { debug(1, "Bad audio packet detected and discarded."); abuf->ready = 0; abuf->timestamp = 0; + abuf->given_timestamp = 0; abuf->sequence_number = 0; } } @@ -1890,8 +1894,8 @@ static void *player_thread_func(void *arg) { if (abs_sync_error > 3 * config.output_rate) { warn("Very large sync error: %" PRId64 " frames, with delay: %" PRId64 ", td_in_frames: %" PRId64 ", rt: %" PRId64 ", nt: %" PRId64 - ", current_delay: %" PRId64 ", frames.", - sync_error, delay, td_in_frames, rt, nt, current_delay); + ", current_delay: %" PRId64 ", seqno: %u, given timestamp: %" PRIu32 ".", + sync_error, delay, td_in_frames, rt, nt, current_delay, inframe->sequence_number, inframe->given_timestamp); } sync_error_out_of_bounds++; } else { diff --git a/player.h b/player.h index 6f1a8b5b..56ea1f91 100644 --- a/player.h +++ b/player.h @@ -39,6 +39,7 @@ typedef struct audio_buffer_entry { // decoded audio packets int ready; int64_t timestamp; seq_t sequence_number; + uint32_t given_timestamp; // for debugging and checking signed short *data; int length; // the length of the decoded data } abuf_t; @@ -193,7 +194,7 @@ void player_stop(rtsp_conn_info *conn); void player_volume(double f, rtsp_conn_info *conn); void player_volume_without_notification(double f, rtsp_conn_info *conn); void player_flush(int64_t timestamp, rtsp_conn_info *conn); -void player_put_packet(seq_t seqno, int64_t timestamp, uint8_t *data, int len, +void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data, int len, rtsp_conn_info *conn); int64_t monotonic_timestamp(uint32_t timestamp, diff --git a/rtp.c b/rtp.c index e8dc5d7d..e4c83a1d 100644 --- a/rtp.c +++ b/rtp.c @@ -147,7 +147,8 @@ void *rtp_audio_receiver(void *arg) { // debug(3, "RTP: Packets out of sequence: expected: %d, got %d.", last_seqno, seqno); last_seqno = seqno; // reset warning... } - int64_t timestamp = monotonic_timestamp(ntohl(*(uint32_t *)(pktp + 4)), conn); + uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4)); + int64_t timestamp = monotonic_timestamp(actual_timestamp, conn); // if (packet[1]&0x10) // debug(1,"Audio packet Extension bit set."); @@ -157,7 +158,7 @@ void *rtp_audio_receiver(void *arg) { // check if packet contains enough content to be reasonable if (plen >= 16) { - player_put_packet(seqno, timestamp, pktp, plen, conn); + player_put_packet(seqno, actual_timestamp, timestamp, pktp, plen, conn); continue; } if (type == 0x56 && seqno == 0) { @@ -361,8 +362,9 @@ void *rtp_control_receiver(void *arg) { debug(3,"Resent audio packet %u received in the control port.",seqno); else debug(1,"Audio packet %u received in the control port.",seqno); + uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4)); - int64_t timestamp = monotonic_timestamp(ntohl(*(uint32_t *)(pktp + 4)), conn); + int64_t timestamp = monotonic_timestamp(actual_timestamp, conn); pktp += 12; plen -= 12; @@ -370,7 +372,7 @@ void *rtp_control_receiver(void *arg) { // check if packet contains enough content to be reasonable if (plen >= 16) { - player_put_packet(seqno, timestamp, pktp, plen, conn); + player_put_packet(seqno, actual_timestamp, timestamp, pktp, plen, conn); continue; } else { debug(3, "Too-short retransmitted audio packet received in control port, ignored."); From 146796dac6a4c33c24653097de73c92987cb2de3 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Apr 2018 14:06:46 +0100 Subject: [PATCH 09/20] incorporate 3.2d30 stuff -- lots of important bug fixes -- and include the extra diagnostic information from RC2 --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 59862489..810d868e 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.2RC2], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.2RC3], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From de33cf5ff3d326a5f237a8c20c85c88531d4e9fa Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Apr 2018 14:17:26 +0100 Subject: [PATCH 10/20] Update RELEASENOTES.md --- RELEASENOTES.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 62474db9..f05932fb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,16 @@ +Version 3.2RC3 +==== +**Enhancements** +* Lost UDP packet resending code improved for better performance on busy or congested networks. + +**Bug Fixes** +* 3.2RC3 removes fixes some important and long-standing bugs that could make Shairport Sync less resilient on noisy or poor quality networks. + +Version 3.2RC2 +==== +**Bug Fixes** +* 3.2RC2 makes Shairport Sync work with AirAudio, removing a crashing bug, and includes some more debugging code. + Version 3.2RC1 ==== **Enhancements** From deb9f4b86b2fe9c846ea096b200d405435cfea51 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Apr 2018 16:07:13 +0100 Subject: [PATCH 11/20] Add logic to skip an occasional outbound timing requester or to discard an occasional incoming timing packet, all to simulate a poor network. --- player.c | 6 +- rtp.c | 358 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 190 insertions(+), 174 deletions(-) diff --git a/player.c b/player.c index 75d0fbfc..60bd9282 100644 --- a/player.c +++ b/player.c @@ -527,9 +527,9 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp check_buf->resend_level = j; if (config.disable_resend_requests == 0) { rtp_request_resend(next, 1, conn); - // if (j>=3) - debug(1, "Resend request level #%d for packet %u in range %u to %u.", j, next, - conn->ab_read, conn->ab_write); + if (j >= 3) + debug(2, "Resend request level #%d for packet %u in range %u to %u.", j, next, + conn->ab_read, conn->ab_write); conn->resend_requests++; } } diff --git a/rtp.c b/rtp.c index 9ef7be2c..57f204e6 100644 --- a/rtp.c +++ b/rtp.c @@ -147,7 +147,7 @@ void *rtp_audio_receiver(void *arg) { last_seqno = seqno; // reset warning... } } else { - debug(1, "Audio Receiver -- Retransmitted Audio Data Packet %u received.", seqno); + debug(2, "Audio Receiver -- Retransmitted Audio Data Packet %u received.", seqno); } uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4)); @@ -372,7 +372,7 @@ void *rtp_control_receiver(void *arg) { pktp = packet + 4; plen -= 4; seq_t seqno = ntohs(*(uint16_t *)(pktp + 2)); - debug(1, "Control Receiver -- Retransmitted Audio Data Packet %u received.", seqno); + debug(2, "Control Receiver -- Retransmitted Audio Data Packet %u received.", seqno); uint32_t actual_timestamp = ntohl(*(uint32_t *)(pktp + 4)); int64_t timestamp = monotonic_timestamp(actual_timestamp, conn); @@ -391,7 +391,7 @@ void *rtp_control_receiver(void *arg) { debug(1, "Control Receiver -- Unknown RTP packet of type 0x%02X length %d, ignored.", packet[1], nread); } else { - debug(1, "Control Receiver -- dropping a packet to simulate a bad network."); + debug(3, "Control Receiver -- dropping a packet to simulate a bad network."); } } else { debug(1, "Control Receiver -- error receiving a packet."); @@ -457,12 +457,21 @@ void *rtp_timing_sender(void *arg) { if (conn->timing_sender_stop != 0) { break; } - if (sendto(conn->timing_socket, &req, sizeof(req), 0, - (struct sockaddr *)&conn->rtp_client_timing_socket, msgsize) == -1) { - char em[1024]; - strerror_r(errno, em, sizeof(em)); - debug(1, "Error %d using send-to to the timing socket: \"%s\".", errno, em); + + if ((config.diagnostic_drop_packet_fraction == 0.0) || + (drand48() > config.diagnostic_drop_packet_fraction)) { + + if (sendto(conn->timing_socket, &req, sizeof(req), 0, + (struct sockaddr *)&conn->rtp_client_timing_socket, msgsize) == -1) { + char em[1024]; + strerror_r(errno, em, sizeof(em)); + debug(1, "Error %d using send-to to the timing socket: \"%s\".", errno, em); + } + + } else { + debug(1, "Timing Sender -- dropping an outbound timing packet to simulate a bad network."); } + request_number++; // this is to deal with the possibility of missing a timing_sender_stop signal. @@ -522,194 +531,201 @@ void *rtp_timing_receiver(void *arg) { if (nread >= 0) { - // ssize_t plen = nread; - // debug(1,"Packet Received on Timing Port."); - if (packet[1] == 0xd3) { // timing reply - /* - char obf[4096]; - char *obfp = obf; - int obfc; - for (obfc=0;obfc config.diagnostic_drop_packet_fraction)) { - // arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000; - // departure_time = ((uint64_t)dtt.tv_sec<<32)+((uint64_t)dtt.tv_nsec<<32)/1000000000; + // ssize_t plen = nread; + // debug(1,"Packet Received on Timing Port."); + if (packet[1] == 0xd3) { // timing reply + /* + char obf[4096]; + char *obfp = obf; + int obfc; + for (obfc=0;obfcdeparture_time; + // arrival_time = ((uint64_t)att.tv_sec<<32)+((uint64_t)att.tv_nsec<<32)/1000000000; + // departure_time = ((uint64_t)dtt.tv_sec<<32)+((uint64_t)dtt.tv_nsec<<32)/1000000000; - uint64_t rtus = (return_time * 1000000) >> 32; + return_time = arrival_time - conn->departure_time; - if (rtus < 300000) { + uint64_t rtus = (return_time * 1000000) >> 32; - // debug(2,"Synchronisation ping return time is %f milliseconds.",(rtus*1.0)/1000); + if (rtus < 300000) { - // distant_receive_time = - // ((uint64_t)ntohl(*((uint32_t*)&packet[16])))<<32+ntohl(*((uint32_t*)&packet[20])); + // debug(2,"Synchronisation ping return time is %f milliseconds.",(rtus*1.0)/1000); - distant_receive_time = (uint64_t)nctohl(&packet[16]) << 32; - distant_receive_time += nctohl(&packet[20]); + // distant_receive_time = + // ((uint64_t)ntohl(*((uint32_t*)&packet[16])))<<32+ntohl(*((uint32_t*)&packet[20])); - // distant_transmit_time = - // ((uint64_t)ntohl(*((uint32_t*)&packet[24])))<<32+ntohl(*((uint32_t*)&packet[28])); + distant_receive_time = (uint64_t)nctohl(&packet[16]) << 32; + distant_receive_time += nctohl(&packet[20]); - distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32; - distant_transmit_time += nctohl(&packet[28]); + // distant_transmit_time = + // ((uint64_t)ntohl(*((uint32_t*)&packet[24])))<<32+ntohl(*((uint32_t*)&packet[28])); - // processing_time = distant_transmit_time - distant_receive_time; + distant_transmit_time = (uint64_t)nctohl(&packet[24]) << 32; + distant_transmit_time += nctohl(&packet[28]); - // debug(1,"Return trip time: %lluuS, remote processing time: - // %lluuS.",(return_time*1000000)>>32,(processing_time*1000000)>>32); + // processing_time = distant_transmit_time - distant_receive_time; - uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2; + // debug(1,"Return trip time: %lluuS, remote processing time: + // %lluuS.",(return_time*1000000)>>32,(processing_time*1000000)>>32); - unsigned int cc; - for (cc = time_ping_history - 1; cc > 0; cc--) { - conn->time_pings[cc] = conn->time_pings[cc - 1]; - conn->time_pings[cc].dispersion = - (conn->time_pings[cc].dispersion * 110) / - 100; // make the dispersions 'age' by this rational factor - } - // these are for diagnostics only -- not used - conn->time_pings[0].local_time = arrival_time; - conn->time_pings[0].remote_time = distant_transmit_time; + uint64_t local_time_by_remote_clock = distant_transmit_time + return_time / 2; - conn->time_pings[0].local_to_remote_difference = - local_time_by_remote_clock - arrival_time; - conn->time_pings[0].dispersion = return_time; - if (conn->time_ping_count < time_ping_history) - conn->time_ping_count++; - - uint64_t local_time_chosen = arrival_time; - - // uint64_t remote_time_chosen = distant_transmit_time; - // now pick the timestamp with the lowest dispersion - uint64_t l2rtd = conn->time_pings[0].local_to_remote_difference; - uint64_t tld = conn->time_pings[0].dispersion; - // chosen = 0; - for (cc = 1; cc < conn->time_ping_count; cc++) - if (conn->time_pings[cc].dispersion < tld) { - l2rtd = conn->time_pings[cc].local_to_remote_difference; - // chosen = cc; - tld = conn->time_pings[cc].dispersion; - local_time_chosen = conn->time_pings[cc].local_time; - // remote_time_chosen = conn->time_pings[cc].remote_time; + unsigned int cc; + for (cc = time_ping_history - 1; cc > 0; cc--) { + conn->time_pings[cc] = conn->time_pings[cc - 1]; + conn->time_pings[cc].dispersion = + (conn->time_pings[cc].dispersion * 110) / + 100; // make the dispersions 'age' by this rational factor } - // int64_t ji; + // these are for diagnostics only -- not used + conn->time_pings[0].local_time = arrival_time; + conn->time_pings[0].remote_time = distant_transmit_time; - if (conn->time_ping_count > 1) { - if (l2rtd > conn->local_to_remote_time_difference) { - local_to_remote_time_jitters = - local_to_remote_time_jitters + l2rtd - conn->local_to_remote_time_difference; - // ji = l2rtd - conn->local_to_remote_time_difference; + conn->time_pings[0].local_to_remote_difference = + local_time_by_remote_clock - arrival_time; + conn->time_pings[0].dispersion = return_time; + if (conn->time_ping_count < time_ping_history) + conn->time_ping_count++; + + uint64_t local_time_chosen = arrival_time; + + // uint64_t remote_time_chosen = distant_transmit_time; + // now pick the timestamp with the lowest dispersion + uint64_t l2rtd = conn->time_pings[0].local_to_remote_difference; + uint64_t tld = conn->time_pings[0].dispersion; + // chosen = 0; + for (cc = 1; cc < conn->time_ping_count; cc++) + if (conn->time_pings[cc].dispersion < tld) { + l2rtd = conn->time_pings[cc].local_to_remote_difference; + // chosen = cc; + tld = conn->time_pings[cc].dispersion; + local_time_chosen = conn->time_pings[cc].local_time; + // remote_time_chosen = conn->time_pings[cc].remote_time; + } + // int64_t ji; + + if (conn->time_ping_count > 1) { + if (l2rtd > conn->local_to_remote_time_difference) { + local_to_remote_time_jitters = + local_to_remote_time_jitters + l2rtd - conn->local_to_remote_time_difference; + // ji = l2rtd - conn->local_to_remote_time_difference; + } else { + local_to_remote_time_jitters = + local_to_remote_time_jitters + conn->local_to_remote_time_difference - l2rtd; + // ji = -(conn->local_to_remote_time_difference - l2rtd); + } + local_to_remote_time_jitters_count += 1; + } + // uncomment below to print jitter between client's clock and oour clock + // int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time + // difference + // with dispersion of %lld us with delta of %lld us",rtus,ji); + + conn->local_to_remote_time_difference = l2rtd; + if (first_local_to_remote_time_difference == 0) { + first_local_to_remote_time_difference = conn->local_to_remote_time_difference; + // first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); + } + + // int64_t clock_drift; + // int64_t clock_drift_in_usec; + // double clock_drift_ppm = 0.0; + if (first_local_time == 0) { + first_local_time = local_time_chosen; + // first_remote_time = remote_time_chosen; + // clock_drift = 0; } else { - local_to_remote_time_jitters = - local_to_remote_time_jitters + conn->local_to_remote_time_difference - l2rtd; - // ji = -(conn->local_to_remote_time_difference - l2rtd); + // uint64_t local_time_change = local_time_chosen - first_local_time; + // uint64_t remote_time_change = remote_time_chosen - first_remote_time; + + /* + if (remote_time_change >= local_time_change) + clock_drift = remote_time_change - local_time_change; + else + clock_drift = -(local_time_change - remote_time_change); + */ + /* + if (clock_drift >= 0) + clock_drift_in_usec = (clock_drift * 1000000) >> 32; + else + clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32); + */ + + // clock_drift_ppm = (1.0 * clock_drift_in_usec) / (local_time_change >> 32); } - local_to_remote_time_jitters_count += 1; - } - // uncomment below to print jitter between client's clock and oour clock - // int64_t rtus = (tld*1000000)>>32; ji = (ji*1000000)>>32; debug(1,"Choosing time - // difference - // with dispersion of %lld us with delta of %lld us",rtus,ji); - conn->local_to_remote_time_difference = l2rtd; - if (first_local_to_remote_time_difference == 0) { - first_local_to_remote_time_difference = conn->local_to_remote_time_difference; - // first_local_to_remote_time_difference_time = get_absolute_time_in_fp(); - } + int64_t source_drift_usec; + if (conn->play_segment_reference_frame != 0) { + int64_t reference_timestamp; + uint64_t reference_timestamp_time, remote_reference_timestamp_time; + get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, + &remote_reference_timestamp_time, conn); + uint64_t frame_difference = 0; + if (reference_timestamp >= conn->play_segment_reference_frame) + frame_difference = + (uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame; + else // rollover + frame_difference = (uint64_t)reference_timestamp + 0x100000000 - + (uint64_t)conn->play_segment_reference_frame; + uint64_t frame_time_difference_calculated = + (((uint64_t)frame_difference << 32) / 44100); + uint64_t frame_time_difference_actual = + remote_reference_timestamp_time - + conn->play_segment_reference_frame_remote_time; // this is all done by reference + // to + // the + // sources' system clock + // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec + // actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, + // (frame_time_difference_actual*1000000)>>32); + if (frame_time_difference_calculated >= + frame_time_difference_actual) // i.e. if the time it should have taken to send the + // packets is greater than the actual time difference + // measured on the source clock + // then the source DAC's clock is running fast relative to the source system clock + source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual; + else + // otherwise the source DAC's clock is running slow relative to the source system + // clock + source_drift_usec = + -(frame_time_difference_actual - frame_time_difference_calculated); + } else + source_drift_usec = 0; + source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds - // int64_t clock_drift; - // int64_t clock_drift_in_usec; - // double clock_drift_ppm = 0.0; - if (first_local_time == 0) { - first_local_time = local_time_chosen; - // first_remote_time = remote_time_chosen; - // clock_drift = 0; + // long current_delay = 0; + // if (config.output->delay) { + // config.output->delay(¤t_delay); + //} + // Useful for troubleshooting: + // debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec + // %10.1lld\treturn_time_in_usec + // %10.1llu", + // clock_drift_ppm, + // chosen, + //(session_corrections*1000000)/44100, + // current_delay, + // source_drift_usec, + // buffer_occupancy, + //(return_time*1000000)>>32); } else { - // uint64_t local_time_change = local_time_chosen - first_local_time; - // uint64_t remote_time_change = remote_time_chosen - first_remote_time; - - /* - if (remote_time_change >= local_time_change) - clock_drift = remote_time_change - local_time_change; - else - clock_drift = -(local_time_change - remote_time_change); - */ - /* - if (clock_drift >= 0) - clock_drift_in_usec = (clock_drift * 1000000) >> 32; - else - clock_drift_in_usec = -(((-clock_drift) * 1000000) >> 32); - */ - - // clock_drift_ppm = (1.0 * clock_drift_in_usec) / (local_time_change >> 32); + debug(1, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.", + rtus); } - - int64_t source_drift_usec; - if (conn->play_segment_reference_frame != 0) { - int64_t reference_timestamp; - uint64_t reference_timestamp_time, remote_reference_timestamp_time; - get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, - &remote_reference_timestamp_time, conn); - uint64_t frame_difference = 0; - if (reference_timestamp >= conn->play_segment_reference_frame) - frame_difference = - (uint64_t)reference_timestamp - (uint64_t)conn->play_segment_reference_frame; - else // rollover - frame_difference = (uint64_t)reference_timestamp + 0x100000000 - - (uint64_t)conn->play_segment_reference_frame; - uint64_t frame_time_difference_calculated = - (((uint64_t)frame_difference << 32) / 44100); - uint64_t frame_time_difference_actual = - remote_reference_timestamp_time - - conn->play_segment_reference_frame_remote_time; // this is all done by reference to - // the - // sources' system clock - // debug(1,"%llu frames since play started, %llu usec calculated, %llu usec - // actual",frame_difference, (frame_time_difference_calculated*1000000)>>32, - // (frame_time_difference_actual*1000000)>>32); - if (frame_time_difference_calculated >= - frame_time_difference_actual) // i.e. if the time it should have taken to send the - // packets is greater than the actual time difference - // measured on the source clock - // then the source DAC's clock is running fast relative to the source system clock - source_drift_usec = frame_time_difference_calculated - frame_time_difference_actual; - else - // otherwise the source DAC's clock is running slow relative to the source system - // clock - source_drift_usec = - -(frame_time_difference_actual - frame_time_difference_calculated); - } else - source_drift_usec = 0; - source_drift_usec = (source_drift_usec * 1000000) >> 32; // turn it to microseconds - - // long current_delay = 0; - // if (config.output->delay) { - // config.output->delay(¤t_delay); - //} - // Useful for troubleshooting: - // debug(1, "clock_drift_ppm %f\tchosen %5d\tsource_drift_usec - // %10.1lld\treturn_time_in_usec - // %10.1llu", - // clock_drift_ppm, - // chosen, - //(session_corrections*1000000)/44100, - // current_delay, - // source_drift_usec, - // buffer_occupancy, - //(return_time*1000000)>>32); } else { - debug(1, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.", - rtus); + debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); } } else { - debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread); + debug(1, "Timing Receiver -- dropping a packet to simulate a bad network."); } } else { debug(1, "Timing receiver -- error receiving a packet."); From 74a0bc5341797ee79a4b54df5d0fb4c068d26a3c Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Apr 2018 17:38:26 +0100 Subject: [PATCH 12/20] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f05932fb..f79940c4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,7 +15,7 @@ Version 3.2RC1 ==== **Enhancements** * Shairport Sync now offers an IPC interface via D-Bus. It provides an incomplete but functional MPRIS interface and also provides a native Shairport Sync interface. Both provide some metadata and some remote control. The native D-Bus interface permits remote control of the current AirPlay or iTunes client. It includes status information about whether the remote control connection is viable or not, i.e. whether it can still be used to control the client. A remote control connection to the audio client becomes valid when the client starts AirPlaying to Shairport Sync. The connections remains valid until the audio source deselects Shairport Sync for AirPlay, or until the client disappears, or until another client starts AirPlaying to Shairport Sync. After 15 minutes of inactivity, the remote contol connection will be dropped. -* OpenBSD compatibility. Shairport Sync now compiles on OpenBSD - please consult the OpenBSD note for details. +* OpenBSD compatibility. Shairport Sync now compiles on OpenBSD -- please consult the OpenBSD note for details. * A new `general` option `volume_control_profile`, for advanced use only, with two options: `"standard"` which uses the standard volume control profile -- this has a higher transfer profile at low volumes and a lower transfer profile at high volumes -- or `"flat"` which uses a uniform transfer profile to linearly scale the output mixer's dB according to the AirPlay volume. * Some DACs have a feature that the lowest permissible "attenuation" value that the built-in hardware mixer can be set to is not an attenuation value at all – it is in fact a command to mute the output completely. Shairport Sync has always checked for this feature, basically in order to ignore it when getting the true range of attenuation values offered by the mixer. However, with this enhancement, Shairport Sync can actually use this feature to mute the output where appropriate. From 2fad189e0b8296004f1f196db8f7e5941982c916 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Apr 2018 18:19:59 +0100 Subject: [PATCH 13/20] Update README.md --- README.md | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ec34beea..539370e0 100644 --- a/README.md +++ b/README.md @@ -530,14 +530,34 @@ sync error in milliseconds, net correction in ppm, corrections in ppm, total pac ``` This will be followed by the statistics themselves at regular intervals, for example: ``` - -1.3, 25.9, 25.9, 3758, 0, 0, 0, 0, 4444, 263, 270 - -2.0, 96.0, 96.0, 7516, 0, 0, 0, 0, 5357, 260, 267 - -2.0, 96.0, 96.0, 11274, 0, 0, 0, 0, 4956, 262, 268 - -2.0, 94.5, 94.5, 15032, 0, 0, 0, 0, 5430, 263, 267 - -2.0, 99.8, 99.8, 18790, 0, 0, 0, 0, 5047, 261, 268 - -2.0, 91.5, 91.5, 22548, 0, 0, 0, 0, 5258, 260, 267 - -2.0, 90.7, 90.7, 26306, 0, 0, 0, 0, 5279, 262, 267 - -2.0, 96.0, 96.0, 30064, 0, 0, 0, 0, 5551, 260, 266 + -0.7, 0.0, 0.0, 1003, 0, 0, 0, 0, 6085, 222, 230 + -0.8, 0.0, 0.0, 2006, 0, 0, 0, 0, 6087, 222, 230 + -1.1, 0.0, 0.0, 3009, 0, 0, 0, 0, 6078, 223, 230 + -1.1, 0.0, 0.0, 4012, 0, 0, 0, 0, 6078, 222, 230 + -1.1, 0.0, 0.0, 5015, 0, 0, 0, 0, 6071, 223, 230 + -1.1, 0.0, 0.0, 6018, 0, 0, 0, 0, 6081, 223, 230 + -1.0, 0.0, 0.0, 7021, 0, 0, 0, 0, 6078, 223, 230 + -0.8, 0.0, 0.0, 8024, 0, 0, 0, 0, 6085, 223, 230 + -0.3, 0.0, 0.0, 9027, 0, 0, 0, 0, 6111, 224, 230 + -0.4, 0.0, 0.0, 10030, 0, 0, 0, 0, 6104, 223, 230 + -0.8, 0.0, 0.0, 11033, 0, 0, 0, 0, 6096, 223, 230 + -0.5, 0.0, 0.0, 12036, 0, 0, 0, 0, 6094, 224, 230 + -0.7, 0.0, 0.0, 13039, 0, 0, 0, 0, 6095, 222, 230 + -0.7, 0.0, 0.0, 14042, 0, 0, 0, 0, 6094, 222, 230 + -0.2, 0.0, 0.0, 15045, 0, 0, 0, 0, 6111, 223, 230 + -0.6, 0.0, 0.0, 16048, 0, 2, 0, 2, 6090, 210, 230 + -0.6, 0.0, 0.0, 17051, 0, 2, 0, 2, 6097, 220, 230 + -0.5, 0.0, 0.0, 18054, 0, 2, 0, 2, 6097, 224, 230 + -0.4, 0.0, 0.0, 19057, 0, 2, 0, 2, 6098, 222, 230 + -0.4, 0.0, 0.0, 20060, 0, 2, 0, 2, 6107, 223, 230 + -0.4, 0.0, 0.0, 21063, 0, 2, 0, 2, 6111, 222, 230 + -0.4, 0.0, 0.0, 22066, 0, 3, 0, 3, 6108, 210, 230 + -0.3, 0.0, 0.0, 23069, 0, 4, 0, 4, 6106, 214, 245 + -0.4, 0.0, 0.0, 24072, 0, 4, 0, 4, 6104, 232, 245 + -0.3, 0.0, 0.0, 25075, 0, 4, 0, 4, 6069, 232, 246 + -0.3, 0.0, 0.0, 26078, 0, 4, 0, 4, 6110, 232, 245 + -0.3, 0.0, 0.0, 27081, 0, 4, 0, 4, 6088, 231, 245 + -0.2, 0.0, 0.0, 28084, 0, 4, 0, 4, 6090, 232, 245 ``` "Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. @@ -546,7 +566,7 @@ This will be followed by the statistics themselves at regular intervals, for exa "Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. -For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an Ethernet-connected iMac driving a WiFi-connected Raspberry Pi 3 using an IQaudIO Pi-DigiAMP+. +For reference, a drift of one second per day is approximately 11.57 ppm. Left uncorrected, even a drift this small between two audio outputs will be audible after a short time. The above sample is from an iPhone 6 to Shairport Sync running on a WiFi-connected Raspberry Pi 3 using a cheap no-name USB DAC. It's not unusual to have resend requests, late packets and even missing packets if some part of the connection to the Shairport Sync device is over WiFi. Late packets can sometimes be asked for and received multiple times. Sometimes late packets are sent and arrive too late, but have already been sent and received in time, so weren't needed anyway... From 3722c1e371dccc70d49e3f108657ccbadbd62593 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Apr 2018 18:20:50 +0100 Subject: [PATCH 14/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 539370e0..c5dbb93d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Shairport Sync is an AirPlay audio player – it plays audio streamed from iTune Audio played by a Shairport Sync-powered device stays synchronised with the source and hence with similar devices playing the same source. In this way, synchronised multi-room audio is possible for suitable players. (Hence the name Shairport Sync, BTW.) -Shairport Sync runs on Linux and FreeBSD. It does not support AirPlay video or photo streaming. +Shairport Sync runs on Linux, FreeBSD and OpenBSD. It does not support AirPlay video or photo streaming. This is the stable "master" branch. Changes and updates are incorporated into this branch relatively slowly. To access the development version, where all the latest changes are made first, please switch to the "development" branch. From 8cd4da93a2faa0d1a36faea90eccb23e574d5d6d Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Apr 2018 18:37:34 +0100 Subject: [PATCH 15/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5dbb93d..12b1dc9a 100644 --- a/README.md +++ b/README.md @@ -560,7 +560,7 @@ This will be followed by the statistics themselves at regular intervals, for exa -0.2, 0.0, 0.0, 28084, 0, 4, 0, 4, 6090, 232, 245 ``` -"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 1.3 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. +"Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 0.7 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. "Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm. From f79aa84ba1fd516ddc0c6922ad83b128afe87ce9 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sat, 14 Apr 2018 21:58:39 +0100 Subject: [PATCH 16/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12b1dc9a..2d663e8f 100644 --- a/README.md +++ b/README.md @@ -562,7 +562,7 @@ This will be followed by the statistics themselves at regular intervals, for exa "Sync error in milliseconds" is the average deviation from exact synchronisation. The first line of the example above indicates that the output is on average 0.7 milliseconds behind exact synchronisation. Sync is allowed to drift by the `general` `drift_tolerance_in_seconds` setting — (± 0.002 seconds) by default — before a correction will be made. -"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. The first line of the example above indicates that the output device is consuming frames 25.9 ppm faster than the source is generating them. The subsequent lines indicate that the stable actual difference in the clocks is around 95 ppm. +"Net correction in ppm" is actually the net sum of corrections — the number of frame insertions less the number of frame deletions — given as a moving average in parts per million. After an initial settling period, it represents the drift — the divergence between the rate at which frames are generated at the source and the rate at which the output device consumes them. In this case, the drift is negligible, but it can routinely be up to 150 ppm, especially with older machines. "Corrections in ppm" is the number of frame insertions plus the number of frame deletions (i.e. the total number of corrections), given as a moving average in parts per million. The closer this is to the net corrections, the fewer "unnecessary" corrections that are being made. Third party programs tend to have much larger levels of corrections. From 2fc9ca74baca5f07528e05babc2e50faeb2ddefd Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 19 Apr 2018 10:17:08 +0100 Subject: [PATCH 17/20] Send resend requests via the control channel rather than the audio channel. This makes it compatilbe iwht AirAudio and conformant with the unofficial guide to the airplay protocol. Brown bag time. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 810d868e..7e410172 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.2RC3], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.2RC4], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 919f0d8959cce853563e80f3579765eeab66c579 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 19 Apr 2018 10:24:52 +0100 Subject: [PATCH 18/20] Update RELEASENOTES.md --- RELEASENOTES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f79940c4..429fb31b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,8 @@ +Version 3.2RC4 +==== +**Bug Fixes** +* 3.2RC4 sends resend requests (correctly) in the control channel now rather than (incorrectly) over the audio channel as before. This makes it compatible with AirAudio and also makes it conform to the Unofficial AirPlay protocol. This bug went unnoticed for so long because other AirPlay sources accept resend requests in the audio channel. Thanks to [funtax](https://github.com/funtax) and to [Janusz Kowalczyk](https://github.com/kowalcj0) for bringing the issue to the fore. + Version 3.2RC3 ==== **Enhancements** From d35451c17eb8900b2cc3701db3103061f3d987d0 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 19 Apr 2018 10:26:56 +0100 Subject: [PATCH 19/20] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 429fb31b..c9c3060b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,7 @@ Version 3.2RC4 ==== **Bug Fixes** -* 3.2RC4 sends resend requests (correctly) in the control channel now rather than (incorrectly) over the audio channel as before. This makes it compatible with AirAudio and also makes it conform to the Unofficial AirPlay protocol. This bug went unnoticed for so long because other AirPlay sources accept resend requests in the audio channel. Thanks to [funtax](https://github.com/funtax) and to [Janusz Kowalczyk](https://github.com/kowalcj0) for bringing the issue to the fore. +* 3.2RC4 sends resend requests (correctly) in the control channel now rather than (incorrectly) over the audio channel as before. This makes it compatible with AirAudio and also makes it conform to the [Unofficial AirPlay Protocol Specification](https://nto.github.io/AirPlay.html). This bug went unnoticed for so long because other AirPlay sources accept resend requests in the audio channel. Thanks to [funtax](https://github.com/funtax) and to [Janusz Kowalczyk](https://github.com/kowalcj0) for bringing the issue to the fore. Version 3.2RC3 ==== From 43ea75c89f118665cda692ddc7c00e61db1d9ff5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 19 Apr 2018 13:28:56 +0100 Subject: [PATCH 20/20] Update RELEASENOTES.md --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c9c3060b..bd9f285b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,7 @@ Version 3.2RC4 ==== **Bug Fixes** -* 3.2RC4 sends resend requests (correctly) in the control channel now rather than (incorrectly) over the audio channel as before. This makes it compatible with AirAudio and also makes it conform to the [Unofficial AirPlay Protocol Specification](https://nto.github.io/AirPlay.html). This bug went unnoticed for so long because other AirPlay sources accept resend requests in the audio channel. Thanks to [funtax](https://github.com/funtax) and to [Janusz Kowalczyk](https://github.com/kowalcj0) for bringing the issue to the fore. +* 3.2RC4 now sends resend requests (correctly) in the control channel rather than (incorrectly) over the audio channel as before. This makes it compatible with AirAudio and also makes it conform to the [Unofficial AirPlay Protocol Specification](https://nto.github.io/AirPlay.html). This bug went unnoticed for so long because other AirPlay sources accept resend requests in the audio channel. Thanks to [funtax](https://github.com/funtax) and to [Janusz Kowalczyk](https://github.com/kowalcj0) for bringing the issue to the fore. Version 3.2RC3 ====