From 35ee02ec73772427a45159d9af1980c90d686f35 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Sun, 8 Jul 2018 13:00:47 +0100 Subject: [PATCH 1/7] Create CAR SETUP ALTERNATIVE APPROACH.md --- CAR SETUP ALTERNATIVE APPROACH.md | 92 +++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 CAR SETUP ALTERNATIVE APPROACH.md diff --git a/CAR SETUP ALTERNATIVE APPROACH.md b/CAR SETUP ALTERNATIVE APPROACH.md new file mode 100644 index 00000000..69e0325b --- /dev/null +++ b/CAR SETUP ALTERNATIVE APPROACH.md @@ -0,0 +1,92 @@ +# From [FnasBas](https://github.com/FnasBas) +This is based on [this comment](https://github.com/mikebrady/shairport-sync/issues/482#issuecomment-402543454). + +Here's a starting point. I hope it is useful to someone :) + +# Alternative car setup approach +This is offered in the hopes it will be useful to someone. The background is that, besides all the advantages of WiFi/AirPlay over Bluetooth, +when travelling my phone is my only internet connection, and I want to be able to share it with other devices. +Like an iPad or laptop. I may also want to be able to send audio to the car Shairport Sync service from my other devices while +simultaneously having internet access on them, via the phone. + +So the basic idea is just to turn the table on which device acts as the Access Point for the WiFi. Which works perfectly well. +The prerequisite is that your carrier/contract allows tethering, as otherwise you won't be able to have your phone act as a WiFi Access Point. +I also think this is a bit simpler to set up as you don't need to set up `hostapd` and a DHCP server on the Pi. +The drawback is that you need to do a gesture and tap a button on the phone to make its network discoverable when entering your car. + +## Phone configuration +Enable tethering on your iPhone with a secure passphrase. + +## Raspberry Pi configuration +Set up Shairport Sync according to your needs and instructions found elsewhere. + +### Network +Configure your Raspberry Pi with only one WiFi network. Through experimentation I found that if I was in the vicinity of my home network, +the Pi would not reliably choose the iPhone's WiFi network even though it was prioritized over the home network. +And it seems `wpa-supplicant` in general is a bit difficult to get to roam between different WiFi networks once it's decided on one. + +Example of `/etc/wpa\_supplicant/wpa\_supplicant.conf` + +``` +ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev +update_config=1 +country=SE +network={ + priority=3 + ssid="My unique clever wifi network name" + psk="My very secure wifi key" +} +``` + +### Read only root filesystem +By configuring your Pi to have a read only root filesystem, you need not ever worry about an improper shutdown of the OS +causing the Pi to fail to boot. You will also be sure that the Pi always boots to a predetermined state. +And you will eleminate wear on the SD card completely except for when modifying the system. +This will result in a very reliable AirPlay service. + +These instructions are a bit old but a good starting point: (https://openenergymonitor.org/forum-archive/node/5331.html) + +In addition to what's laid out there I've also: + +* Added the `/boot` fs to be mounted read-only and modified the rpi-rw/ro scripts and `/etc/fstab` accordingly +* in `/etc/systemd/journald.conf`, set `storage=volatile` +* As the `systemd` `ntp` client is hard-coded to use a specific path for its book keeping, I've disabled that and installed `ntpd` +and set the drift file to `/tmp/drift` +* Your DHCP client will complain that it can't write its lease to a file. You can work around this by setting it to write its lease +to a file in `/tmp` or disregard it as it will work fine in any case. + +In my case I do not use a car stereo but go straight Pi>Dac>Power amplifier. So I've also set up EQ with `alsa`, and a relay module to a +GPIO pin and some scripts that run on session start/stop that turns the power amplifier on and off accordingly. +But this could turn in to "How I set up my car audio" rather than offering an alternative to [what's currently suggested +for a car installation](https://github.com/mikebrady/shairport-sync/blob/master/CAR%20INSTALL.md). +So I stop. ;) + +## Using it +Make your phone's network discoverable. In iOS 11 there's a shortcut for this – swipe up and hold down on the connectivity section. +There's a button to turn on Internet Sharing. +In my experiments the Pi connects almost instantaneously to this network and a few moments later the AirPlay service offered by Shairport Sync +will become visible. + +## Changing and Maintaining it +Make your phone network discoverable. The Pi connects. Connect your laptop to the same network. +The iPhone does not appear to resolve host names from one tethered device to another so you need to `ssh` to the Pi's IP-address. +There's a number of ways to find the Pi's IP-address: + +* You can check your laptop's IP and try subtracting or adding from the last octet +* Use a `zeroconf/avahi` browser tool and locate the AirPlay service and find the IP address that way +* Use `nmap` or other similar utility to scan for the Pi + +`ssh` to the Pi +``` +rpi-rw +change stuff +rpi-ro +``` + +### Further thinking +Other Apple devices can wake the WiFi Access Point on the iPhone. My conclusion is that this must be accomplished via Bluetooth, +but Apple doesn't seem to be open spec on this subject as all I've been able to find is that certain devices, when paired with Bluetooth, +connected to iCloud etc. etc. will offer this functionality. +Being able to reverse-engineer what Bluetooth communication is actually done in order to turn on the WiFi Access Point on the phone +would mitigate the drawback of this method, as the Paspberry Pi could be constantly looking for the phone via Bluetooth and +enabling Internet Sharing that way. From e4d2bf0033f8016e0e059e498cd3a7bd89165802 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 10 Jul 2018 13:29:03 +0100 Subject: [PATCH 2/7] 3.2d66 corresponds to 3.2, with some extra features, e.g. mqtt --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 977d8e74..8cf7825e 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.2d65], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.2d66], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 7e0f6acb55b83b24682bc2984ff64341c485eaf4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 Jul 2018 20:49:31 +0100 Subject: [PATCH 3/7] Tidy up some avahi code and some player code, but bug not fixed --- mdns_avahi.c | 144 +++++++++++++++++++++++++++------------------------ player.c | 19 +++++-- 2 files changed, 90 insertions(+), 73 deletions(-) diff --git a/mdns_avahi.c b/mdns_avahi.c index 0a02a720..28aa9eca 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -84,33 +84,34 @@ static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIn /* Called whenever a service has been resolved successfully or timed out */ switch (event) { - case AVAHI_RESOLVER_FAILURE: - debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name, - type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); - break; - case AVAHI_RESOLVER_FOUND: { - // char a[AVAHI_ADDRESS_STR_MAX], *t; - // debug(1, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain); - char *dacpid = strstr(name, "iTunes_Ctrl_"); - if (dacpid) { - dacpid += strlen("iTunes_Ctrl_"); - if (strcmp(dacpid, dbs->dacp_id) == 0) { - debug(3, "Client's DACP port: %u.", port); -#ifdef HAVE_DACP_CLIENT - dacp_monitor_port_update_callback(dacpid, port); -#endif -#ifdef CONFIG_METADATA - char portstring[20]; - memset(portstring, 0, sizeof(portstring)); - snprintf(portstring, sizeof(portstring), "%u", port); - send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0); -#endif + case AVAHI_RESOLVER_FAILURE: + debug(2, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s.", name, + type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + break; + case AVAHI_RESOLVER_FOUND: { + // char a[AVAHI_ADDRESS_STR_MAX], *t; + // debug(1, "Resolve callback: Service '%s' of type '%s' in domain '%s':", name, type, domain); + char *dacpid = strstr(name, "iTunes_Ctrl_"); + if (dacpid) { + dacpid += strlen("iTunes_Ctrl_"); + if (strcmp(dacpid, dbs->dacp_id) == 0) { + debug(3, "Client's DACP port: %u.", port); + #ifdef HAVE_DACP_CLIENT + dacp_monitor_port_update_callback(dacpid, port); + #endif + #ifdef CONFIG_METADATA + char portstring[20]; + memset(portstring, 0, sizeof(portstring)); + snprintf(portstring, sizeof(portstring), "%u", port); + send_ssnc_metadata('dapo', strdup(portstring), strlen(portstring), 0); + #endif + } + } else { + debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!"); } - } else { - debug(1, "Resolve callback: Can't see a DACP string in a DACP Record!"); } } - } + // debug(1,"service resolver freed by resolve_callback"); check_avahi_response(1, avahi_service_resolver_free(r)); } static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, @@ -163,47 +164,47 @@ static void register_service(AvahiClient *c); static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { switch (state) { - case AVAHI_ENTRY_GROUP_ESTABLISHED: - /* The entry group has been established successfully */ - debug(1, "avahi: service '%s' successfully added.", service_name); - break; + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + debug(1, "avahi: service '%s' successfully added.", service_name); + break; - case AVAHI_ENTRY_GROUP_COLLISION: { - char *n; + case AVAHI_ENTRY_GROUP_COLLISION: { + char *n; - /* A service name collision with a remote service - * happened. Let's pick a new name */ - debug(1, "avahi name collision -- look for another"); - n = avahi_alternative_service_name(service_name); - if (service_name) - avahi_free(service_name); - else - debug(1, "avahi attempt to free a NULL service name"); - service_name = n; + /* A service name collision with a remote service + * happened. Let's pick a new name */ + debug(1, "avahi name collision -- look for another"); + n = avahi_alternative_service_name(service_name); + if (service_name) + avahi_free(service_name); + else + debug(1, "avahi attempt to free a NULL service name"); + service_name = n; - debug(2, "avahi: service name collision, renaming service to '%s'", service_name); + debug(2, "avahi: service name collision, renaming service to '%s'", service_name); - /* And recreate the services */ - register_service(avahi_entry_group_get_client(g)); - break; - } + /* And recreate the services */ + register_service(avahi_entry_group_get_client(g)); + break; + } - case AVAHI_ENTRY_GROUP_FAILURE: - debug(1, "avahi: entry group failure: %s", - avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); - break; + case AVAHI_ENTRY_GROUP_FAILURE: + debug(1, "avahi: entry group failure: %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + break; - case AVAHI_ENTRY_GROUP_UNCOMMITED: - debug(2, "avahi: service '%s' group is not yet committed.", service_name); - break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + debug(2, "avahi: service '%s' group is not yet committed.", service_name); + break; - case AVAHI_ENTRY_GROUP_REGISTERING: - debug(2, "avahi: service '%s' group is registering.", service_name); - break; + case AVAHI_ENTRY_GROUP_REGISTERING: + debug(2, "avahi: service '%s' group is registering.", service_name); + break; - default: - debug(1, "avahi: unhandled egroup state: %d", state); - break; + default: + debug(1, "avahi: unhandled egroup state: %d", state); + break; } } @@ -458,28 +459,35 @@ void *avahi_dacp_monitor(char *dacp_id) { free((char *)dbs); return NULL; } - debug(3, "DACP monitor successfully started"); + debug(3, "Avahi DACP monitor successfully started"); return (void *)dbs; } void avahi_dacp_dont_monitor(void *userdata) { + debug(3,"avahi_dacp_dont_monitor"); if (userdata) { dacp_browser_struct *dbs = (dacp_browser_struct *)userdata; // stop and dispose of everything - if ((dbs)->service_poll) + /*if (dbs->service_poll) avahi_threaded_poll_stop((dbs)->service_poll); - if ((dbs)->service_browser) - avahi_service_browser_free((dbs)->service_browser); - if ((dbs)->service_client) - avahi_client_free((dbs)->service_client); - if ((dbs)->service_poll) - avahi_threaded_poll_free((dbs)->service_poll); + */ + if (dbs->service_poll) { + avahi_threaded_poll_stop(dbs->service_poll); + avahi_threaded_poll_lock(dbs->service_poll); + if (dbs->service_browser) + avahi_service_browser_free(dbs->service_browser); + if (dbs->service_client) + avahi_client_free(dbs->service_client); + avahi_threaded_poll_unlock(dbs->service_poll); + avahi_threaded_poll_free(dbs->service_poll); + } free(dbs->dacp_id); - free((char *)(dbs)); - debug(3, "DACP monitor successfully stopped"); + free(userdata); + debug(3, "Avahi DACP monitor successfully stopped"); } else { - debug(1, "DACP Monitor is not running."); + debug(1, "Avahi DACP Monitor is not running."); } + debug(3,"avahi_dacp_dont_monitor exit"); } mdns_backend mdns_avahi = {.name = "avahi", diff --git a/player.c b/player.c index f713dce8..f91faa5c 100644 --- a/player.c +++ b/player.c @@ -1399,11 +1399,12 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng } // keep the last (dpm) samples, to mitigate the Gibbs phenomenon - op = scratchBuffer + (length + tstuff - gpm) * sizeof(int32_t); - ip = inptr + (length - gpm) * sizeof(int32_t); + op = scratchBuffer + (length + tstuff - gpm) * sizeof(int32_t) * 2; + ip = inptr + (length - gpm) * sizeof(int32_t) * 2; + for (i = 0; i < gpm; i++) { *op++ = *ip++; - *op++ = *ip++; + *op++ = *ip++; } // now, do the volume, dither and formatting processing @@ -1671,7 +1672,10 @@ static void *player_thread_func(void *arg) { // this is only used for compatability, if dacp stuff isn't enabled. // start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the // port number - conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); + if (conn->dapo_private_storage) + debug(1,"DACP monitor already initialised?"); + else + conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); #endif conn->framesProcessedInThisEpoch = 0; @@ -2335,7 +2339,12 @@ static void *player_thread_func(void *arg) { #ifndef HAVE_DACP_CLIENT // stop watching for DACP port number stuff // this is only used for compatability, if dacp stuff isn't enabled. - mdns_dacp_dont_monitor(conn->dapo_private_storage); + if (conn->dapo_private_storage) { + mdns_dacp_dont_monitor(conn->dapo_private_storage); + conn->dapo_private_storage = NULL; + } else { + debug(2,"DACP Monitor already stopped"); + } #endif debug(3, "Connection %d: stopping output device.", conn->connection_number); From 65807a8b415b0ad7c62aa4f9315ae82ff9ac6bd5 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 Jul 2018 21:00:23 +0100 Subject: [PATCH 4/7] Fix a bug that caused a crash when soxr was enabled and play stopped. Also may have caused other apparently random crashes --- player.c | 138 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 72 insertions(+), 66 deletions(-) diff --git a/player.c b/player.c index f91faa5c..3c60e24d 100644 --- a/player.c +++ b/player.c @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -316,16 +315,18 @@ static int alac_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_co } if (outsize > toutsize) { - debug(2, "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " - "truncated, but buffer overflow possible! Encrypted = %d.", + debug(2, + "Output from alac_decode larger (%d bytes, not frames) than expected (%d bytes) -- " + "truncated, but buffer overflow possible! Encrypted = %d.", outsize, toutsize, conn->stream.encrypted); reply = -1; // output packet is the wrong size } *destlen = outsize / conn->input_bytes_per_frame; if ((outsize % conn->input_bytes_per_frame) != 0) - debug(1, "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " - "and the audio frame size (%d).", + debug(1, + "Number of audio frames (%d) does not correspond exactly to the number of bytes (%d) " + "and the audio frame size (%d).", *destlen, outsize, conn->input_bytes_per_frame); return reply; } @@ -607,9 +608,10 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp int j; for (j = 1; j <= number_of_resend_attempts; j++) { // check j times, after a short period of has elapsed, assuming 352 frames per packet - // the higher the step_exponent, the less it will try. 1 means it will try very hard. 2.0 seems good. + // the higher the step_exponent, the less it will try. 1 means it will try very + // hard. 2.0 seems good. float step_exponent = 2.0; - int back_step = (int)(resend_interval * pow(j,step_exponent)); + int back_step = (int)(resend_interval * pow(j, step_exponent)); int k; for (k = -1; k <= 1; k++) { if ((back_step + k) < @@ -623,10 +625,11 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp j)) { // prevent multiple requests from the same level of lookback check_buf->resend_level = j; if (config.disable_resend_requests == 0) { - if (((int)(resend_interval * pow(j+1,step_exponent)) + k) >= + if (((int)(resend_interval * pow(j + 1, step_exponent)) + k) >= seq_diff(conn->ab_read, conn->ab_write, conn->ab_read)) - debug(3, "Last-ditch (#%d) resend request for packet %u in range %u to %u. " - "Looking back %d packets.", + debug(3, + "Last-ditch (#%d) resend request for packet %u in range %u to %u. " + "Looking back %d packets.", j, next, conn->ab_read, conn->ab_write, back_step + k); debug_mutex_unlock(&conn->ab_mutex, 3); rtp_request_resend(next, 1, conn); @@ -671,10 +674,8 @@ static inline void process_sample(int32_t sample, char **outp, enum sps_format_t 32; // Do not apply volume as it has already been done with the Loudness DSP filter } else { int64_t hyper_volume = (int64_t)volume << 16; - hyper_sample = - hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need to - // dither it down to its - // target resolution + hyper_sample = hyper_sample * hyper_volume; // this is 64 bit bit multiplication -- we may need + // to dither it down to its target resolution } // next, do dither, if necessary @@ -819,8 +820,9 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // if (conn->packet_count>500) { //for testing -- about 4 seconds of play first if ((local_time_now > conn->time_of_last_audio_packet) && (local_time_now - conn->time_of_last_audio_packet >= ct << 32)) { - debug(1, "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" " - "from RTSP conversation %d.", + debug(1, + "As Yeats almost said, \"Too long a silence / can make a stone of the heart\" " + "from RTSP conversation %d.", conn->connection_number); conn->stop = 1; pthread_kill(conn->thread, SIGUSR1); @@ -897,9 +899,8 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { notified_buffer_empty = 0; // at least one buffer now -- diagnostic only. if (conn->ab_buffering) { // if we are getting packets but not yet forwarding them to the // player - int have_sent_prefiller_silence = - 1; // set true when we have sent some silent frames to the - // DAC + int have_sent_prefiller_silence = 1; // set true when we have sent some silent frames to + // the DAC int64_t reference_timestamp; uint64_t reference_timestamp_time, remote_reference_timestamp_time; get_reference_timestamp_stuff(&reference_timestamp, &reference_timestamp_time, @@ -1351,9 +1352,9 @@ static int stuff_buffer_basic_32(int32_t *inptr, int length, enum sps_format_t l // (d) outputs the result in the approprate format // formats accepted so far include U8, S8, S16, S24, S24_3LE, S24_3BE and S32 -static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length, - enum sps_format_t l_output_format, char *outptr, int stuff, - int dither, rtsp_conn_info *conn) { +int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int length, + enum sps_format_t l_output_format, char *outptr, int stuff, int dither, + rtsp_conn_info *conn) { if (scratchBuffer == NULL) { die("soxr scratchBuffer not initialised."); } @@ -1365,6 +1366,7 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng if (tstuff) { // debug(1,"Stuff %d.",stuff); + soxr_io_spec_t io_spec; io_spec.itype = SOXR_INT32_I; io_spec.otype = SOXR_INT32_I; @@ -1374,17 +1376,17 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng size_t odone; - soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, /* Rates and # of chans. */ - inptr, length, NULL, /* Input. */ - scratchBuffer, length + tstuff, &odone, /* Output. */ - &io_spec, /* Input, output and transfer spec. */ - NULL, NULL); /* Default configuration.*/ + soxr_error_t error = soxr_oneshot(length, length + tstuff, 2, // Rates and # of chans. + inptr, length, NULL, // Input. + scratchBuffer, length + tstuff, &odone, // Output. + &io_spec, // Input, output and transfer spec. + NULL, NULL); // Default configuration. if (error) die("soxr error: %s\n", "error: %s\n", soxr_strerror(error)); - if (odone > length + 1) - die("odone = %d!\n", odone); + if (odone > (size_t)(length + 1)) + die("odone = %u!\n", odone); int i; int32_t *ip, *op; @@ -1399,12 +1401,13 @@ static int stuff_buffer_soxr_32(int32_t *inptr, int32_t *scratchBuffer, int leng } // keep the last (dpm) samples, to mitigate the Gibbs phenomenon - op = scratchBuffer + (length + tstuff - gpm) * sizeof(int32_t) * 2; - ip = inptr + (length - gpm) * sizeof(int32_t) * 2; + // pointer arithmetic, baby -- it's da bomb. + op = scratchBuffer + (length + tstuff - gpm) * 2; + ip = inptr + (length - gpm) * 2; for (i = 0; i < gpm; i++) { *op++ = *ip++; - *op++ = *ip++; + *op++ = *ip++; } // now, do the volume, dither and formatting processing @@ -1436,7 +1439,7 @@ typedef struct stats { // statistics for running averages int64_t sync_error, correction, drift; } stats_t; -static void *player_thread_func(void *arg) { +void *player_thread_func(void *arg) { // note, the thread will be started up with the player_thread_lock locked. You must release it // quickly. @@ -1508,10 +1511,9 @@ static void *player_thread_func(void *arg) { } conn->timestamp_epoch = 0; // indicate that the next timestamp will be the first one. - conn->maximum_timestamp_interval = - conn->input_rate * 60; // actually there shouldn't be more than about 13 - // seconds of a gap between successive rtptimes, at - // worst + conn->maximum_timestamp_interval = conn->input_rate * 60; // actually there shouldn't be more than + // about 13 seconds of a gap between + // successive rtptimes, at worst conn->output_sample_ratio = config.output_rate / conn->input_rate; @@ -1637,15 +1639,18 @@ static void *player_thread_func(void *arg) { // if ((input_rate!=config.output_rate) || (input_bit_depth!=output_bit_depth)) { // debug(1,"Define tbuf of length // %d.",output_bytes_per_frame*(max_frames_per_packet*output_sample_ratio+max_frame_size_change)); - tbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + tbuf = malloc( + sizeof(int32_t) * 2 * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (tbuf == NULL) die("Failed to allocate memory for the transition buffer."); + sbuf = 0; // initialise this, because soxr stuffing might be chosen later - sbuf = malloc(sizeof(int32_t) * 2 * (conn->max_frames_per_packet * conn->output_sample_ratio + - conn->max_frame_size_change)); + sbuf = malloc( + sizeof(int32_t) * 2 * + (conn->max_frames_per_packet * conn->output_sample_ratio + conn->max_frame_size_change)); if (sbuf == NULL) debug(1, "Failed to allocate memory for the sbuf buffer."); @@ -1658,9 +1663,8 @@ static void *player_thread_func(void *arg) { die("Failed to allocate memory for an output buffer."); conn->first_packet_timestamp = 0; conn->missing_packets = conn->late_packets = conn->too_late_packets = conn->resend_requests = 0; - conn->flush_rtp_timestamp = - 0; // it seems this number has a special significance -- it seems to be used - // as a null operand, so we'll use it like that too + conn->flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to + // be used as a null operand, so we'll use it like that too int sync_error_out_of_bounds = 0; // number of times in a row that there's been a serious sync error @@ -1673,7 +1677,7 @@ static void *player_thread_func(void *arg) { // start an mdns/zeroconf thread to look for DACP messages containing our DACP_ID and getting the // port number if (conn->dapo_private_storage) - debug(1,"DACP monitor already initialised?"); + debug(1, "DACP monitor already initialised?"); else conn->dapo_private_storage = mdns_dacp_monitor(conn->dacp_id); #endif @@ -1748,8 +1752,9 @@ static void *player_thread_func(void *arg) { } else { // the player may change the contents of the buffer, so it has to be zeroed each time; // might as well malloc and freee it locally - memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * - conn->output_sample_ratio); + memset(silence, 0, + conn->output_bytes_per_frame * conn->max_frames_per_packet * + conn->output_sample_ratio); config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } @@ -1769,8 +1774,9 @@ static void *player_thread_func(void *arg) { } else { // the player may change the contents of the buffer, so it has to be zeroed each time; // might as well malloc and freee it locally - memset(silence, 0, conn->output_bytes_per_frame * conn->max_frames_per_packet * - conn->output_sample_ratio); + memset(silence, 0, + conn->output_bytes_per_frame * conn->max_frames_per_packet * + conn->output_sample_ratio); config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio); free(silence); } @@ -1917,8 +1923,9 @@ static void *player_thread_func(void *arg) { SUCCESSOR(conn->last_seqno_read); // int32_t from seq_t, i.e. uint16_t, so okay. if (inframe->sequence_number != conn->last_seqno_read) { // seq_t, ei.e. uint16_t and int32_t, so okay - debug(2, "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " - "and ab_write: %u.", + debug(2, + "Player: packets out of sequence: expected: %u, got: %u, with ab_read: %u " + "and ab_write: %u.", conn->last_seqno_read, inframe->sequence_number, conn->ab_read, conn->ab_write); conn->last_seqno_read = inframe->sequence_number; // reset warning... } @@ -2069,7 +2076,7 @@ static void *player_thread_func(void *arg) { #ifdef CONFIG_CONVOLUTION || config.convolution #endif - ) { + ) { int32_t *tbuf32 = (int32_t *)tbuf; float fbuf_l[inbuflength]; float fbuf_r[inbuflength]; @@ -2271,10 +2278,10 @@ static void *player_thread_func(void *arg) { "%*d", /* max buffer occupancy */ 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ - 1000 * moving_average_sync_error / config.output_rate, - 10, moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), - 10, moving_average_insertions_plus_deletions * 1000000 / - (352 * conn->output_sample_ratio), + 1000 * moving_average_sync_error / config.output_rate, 10, + moving_average_correction * 1000000 / (352 * conn->output_sample_ratio), 10, + moving_average_insertions_plus_deletions * 1000000 / + (352 * conn->output_sample_ratio), 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, conn->resend_requests, 7, minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, maximum_buffer_occupancy); @@ -2290,11 +2297,10 @@ static void *player_thread_func(void *arg) { "%*d", /* max buffer occupancy */ 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ - 1000 * moving_average_sync_error / config.output_rate, - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 7, - minimum_dac_queue_size, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy); + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, + conn->resend_requests, 7, minimum_dac_queue_size, 5, + minimum_buffer_occupancy, 5, maximum_buffer_occupancy); } } else { inform(" %*.1f," /* Sync error in milliseconds */ @@ -2307,10 +2313,10 @@ static void *player_thread_func(void *arg) { "%*d", /* max buffer occupancy */ 9, /* should be 10, but there's an explicit space at the start to ensure alignment */ - 1000 * moving_average_sync_error / config.output_rate, - 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, - conn->too_late_packets, 7, conn->resend_requests, 5, - minimum_buffer_occupancy, 5, maximum_buffer_occupancy); + 1000 * moving_average_sync_error / config.output_rate, 12, play_number, 7, + conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, 7, + conn->resend_requests, 5, minimum_buffer_occupancy, 5, + maximum_buffer_occupancy); } } else { inform("No frames received in the last sampling interval."); @@ -2343,7 +2349,7 @@ static void *player_thread_func(void *arg) { mdns_dacp_dont_monitor(conn->dapo_private_storage); conn->dapo_private_storage = NULL; } else { - debug(2,"DACP Monitor already stopped"); + debug(2, "DACP Monitor already stopped"); } #endif From b7e070e5306bf83bddd0b95e07e862f96c949ec4 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 Jul 2018 21:04:31 +0100 Subject: [PATCH 5/7] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 8cf7825e..f047fc15 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.2d66], [mikebrady@eircom.net]) +AC_INIT([shairport-sync], [3.2d67], [mikebrady@eircom.net]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 65de2ddefc1099ee978ffdae59a69186483ed8ac Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Wed, 11 Jul 2018 21:08:50 +0100 Subject: [PATCH 6/7] Update RELEASENOTES.md --- RELEASENOTES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a5495253..b22fd16a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,12 @@ +Version 3.2d67 +==== +**Bug Fix** +* Fix a bug causing a crash when `soxr` interpolation was enabled and a play session was stopped. This bug overwrote incorrect memory locations, so depending on what it overwrote, may have been responsible for other inexplicable crashes. + +Version 3.2.d66 +==== +* This is equivalent to 3.2. + Version 3.2.d65 ==== **Modification** From b21a53d87969e045c34efe97fb4814c0df4bc219 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Thu, 12 Jul 2018 12:10:45 +0100 Subject: [PATCH 7/7] Update CAR SETUP ALTERNATIVE APPROACH.md --- CAR SETUP ALTERNATIVE APPROACH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CAR SETUP ALTERNATIVE APPROACH.md b/CAR SETUP ALTERNATIVE APPROACH.md index 69e0325b..bd128af4 100644 --- a/CAR SETUP ALTERNATIVE APPROACH.md +++ b/CAR SETUP ALTERNATIVE APPROACH.md @@ -25,7 +25,7 @@ Configure your Raspberry Pi with only one WiFi network. Through experimentation the Pi would not reliably choose the iPhone's WiFi network even though it was prioritized over the home network. And it seems `wpa-supplicant` in general is a bit difficult to get to roam between different WiFi networks once it's decided on one. -Example of `/etc/wpa\_supplicant/wpa\_supplicant.conf` +Example of `/etc/wpa_supplicant/wpa_supplicant.conf` ``` ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev