Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
Limit the maximum number of open file handles in Docker images.
Increase the level of optimisation (to -O3) while building the Shairport Sync application itself.
Enabled termination of a disconnected session in AirPlay 2 operation after a timeout, which was disabled by default.
Fixed a bug that prevented Shairport Sync from terminating cleanly when a fatal error that occurred while directly accessing the alsa output device.
Fixed a bug that prevented Shairport Sync from recovering cleanly if a player disconnected without warning. The problem was that the player thread would not respond to cancel request. Fixed by moving a pthreadtestcancel() call to the innermost loop.
Fixed compilation errors on old versions of Mac OS X by, reordering some of the files to be included in shairport.c.
Avoided using TCP_KEEPINTVL and TCP_KEEPCNT if they are not defined (they are not defined in older versions of Mac OS X).
Fixed a race condition with the metadata queues. The problem was that the queues were being initialised by threads launched by the main thread which, having started the threads, proceeded to use the queues. But if the threads were late in starting, the queues might not be initialised by the time the main thread tried to use them. Fixed by initialising the queues in the main thread.
Enable the ALSA backend to access mixers on a devices with a hdmi: prefix.
Update the help text for the ALSA backend to denote HDMI devices using the hdmi: prefix rather than hw:.
Manage thread cancellation state explicitly in audio_ao.c
CAR INSTALL guide: Simplify some of the wording. Change order and allow a few seconds before starting systemd-timesync service.
  • Loading branch information
mikebrady committed Jul 6, 2024
2 parents 28ae1da + 8941cae commit 6ab7942
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 168 deletions.
19 changes: 12 additions & 7 deletions CAR INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,40 +191,45 @@ if test $MODE = RUN ; then
else
# If script execution gets in here, it starts services needed for normal operation.
/bin/systemctl start systemd-timesyncd || :
/bin/systemctl start dhcpcd || /bin/systemctl start NetworkManager || :
/bin/sleep 2 # may be necessary while the network becomes available
/bin/systemctl start systemd-timesyncd || :
fi
exit 0 # normal exit here
```

#### Disable Unused Services
#### Disable Unused Services - Optional
These optional steps have been tested on a Raspberry Pi only -- they have not been tested on other systems.
Some services are not necessary for this setup and can be disabled as follows:
```
# systemctl disable keyboard-setup
# systemctl disable triggerhappy
# systemctl disable dphys-swapfile
```
#### Optional: Read-only mode – Raspberry Pi Specific
This optional step is applicable to a Raspberry Pi only. Run `sudo raspi-config` and then choose `Performance Options` > `Overlay Filesystem` and choose to enable the overlay filesystem, and to set the boot partition to be write-protected. (The idea here is that this offers more protection against files being corrupted by the sudden removal of power.)

### Final Steps

#### Disable Unused Services - Mandatory
You now need to disable some services; that is, you need to stop them starting automatically on power-up. This is because they either interfere with the system's operation in WiFi Access Point mode, or because they won't work when the system isn't connected to the Internet. Only one of the `NetworkManager` and the `dhcpcd` service will be present in your system, but it's no harm to try to disable both.
```
# systemctl disable dhcpcd
# systemctl disable NetworkManager
# systemctl disable wpa_supplicant
# systemctl disable systemd-timesyncd
```
Lastly, note that the WiFi credentials you used initially to connect to your network (e.g. your home network) will have been stored in the system in plain text. This is convenient for when you want to reconnect to update (see later), but if you prefer to delete them, they will be in `/etc/wpa_supplicant/wpa_supplicant.conf`
Lastly, note that the WiFi credentials you used initially to connect to your network (e.g. your home network) will have been stored in the system in plain text. This is convenient for when you want to reconnect to update (see later), but if you prefer to delete them, they will be in `/etc/wpa_supplicant/wpa_supplicant.conf`.

#### Optional: Read-only mode – Raspberry Pi Specific
This optional step is applicable to a Raspberry Pi only. Run `sudo raspi-config` and then choose `Performance Options` > `Overlay Filesystem` and choose to enable the overlay filesystem, and to set the boot partition to be write-protected. (The idea here is that this offers more protection against files being corrupted by the sudden removal of power.)

### Final Step
When you are finished, carefully power down the machine before unplugging it from power:
```
# poweroff
```
Note: doing a `reboot` here doesn't seem to work properly -- it really does seem necessary to power off.

### Ready
Install the Raspberry Pi in your car. It should be powered from a source that is switched off when you leave the car, otherwise the slight current drain will eventually flatten the car's battery.

Expand All @@ -241,7 +246,7 @@ However, if you're *upgrading* the operating system to e.g. from Bullseye to Boo
If it's a Raspberry Pi and you have optionally enabled the read-only mode, you must take the device out of Read-only mode:
Run `sudo raspi-config` and then choose `Performance Options` > `Overlay Filesystem` and choose to disable the overlay filesystem and to set the boot partition not to be write-protected. This is so that changes can be written to the file system; you can make the filesystem read-only again later. Save the changes and reboot the system.
#### Undo Optimisations
If you have disabled any of the services listed in the [Disable Unused Services](#disable-unused-services) section, you should re-enable them. (But *do not* re-eneable `NetworkManager`, `dhcpcd`, `wpa_supplicant` or `systemd-timesyncd` -- they are handled specially by the startup script.)
If you have disabled any of the services listed in the [Disable Unused Services - Optional](#disable-unused-services---optional) section, you should re-enable them. (But *do not* re-eneable `NetworkManager`, `dhcpcd`, `wpa_supplicant` or `systemd-timesyncd` -- they are handled specially by the startup script.)
#### Perform Legacy Updates
Over time, the arrangements by which the system is prepared for operation has changed to make it easier to revert to normal operation when necessary for maintenance, updates, etc. A small number of the old settings need to be changed to bring them up to date with the present arrangements. Once the required changes have been made, your system will be ready for the update process detailed below. Here are those legacy changes you need to make, just once:

Expand Down
88 changes: 79 additions & 9 deletions audio_alsa.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* libalsa output driver. This file is part of Shairport.
* Copyright (c) Muffinman, Skaman 2013
* Copyright (c) Mike Brady 2014 -- 2022
* Copyright (c) Mike Brady 2014 -- 2024
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
Expand Down Expand Up @@ -146,6 +146,7 @@ long alsa_mix_minv, alsa_mix_maxv;
long alsa_mix_mindb, alsa_mix_maxdb;

char *alsa_out_dev = "default";
char *hw_alsa_out_dev = NULL;
char *alsa_mix_dev = NULL;
char *alsa_mix_ctrl = NULL;
int alsa_mix_index = 0;
Expand Down Expand Up @@ -279,19 +280,78 @@ uint64_t frames_sent_for_playing;
// frames_sent_for_playing (which Shairport Sync might hold) would be invalid.
int frames_sent_break_occurred;

// if a device name ends in ",DEV=0", drop it. Then if it also begins with "CARD=", drop that too.
static void simplify_and_printf_mutable_device_name(char *device_name) {
if (strstr(device_name, ",DEV=0") == device_name + strlen(device_name) - strlen(",DEV=0")) {
char *shortened_device_name = str_replace(device_name, ",DEV=0", "");
char *simplified_device_name = str_replace(shortened_device_name, "CARD=", "");
printf(" \"%s\"\n", simplified_device_name);
free(simplified_device_name);
free(shortened_device_name);
} else {
printf(" \"%s\"\n", device_name);
}
}

static void help(void) {

printf(" -d output-device set the output device, default is \"default\".\n"
" -c mixer-control set the mixer control name, default is to use no mixer.\n"
" -m mixer-device set the mixer device, default is the output device.\n"
" -i mixer-index set the mixer index, default is 0.\n");
int r = system("if [ -d /proc/asound ] ; then echo \" hardware output devices:\" ; ls -al "
"/proc/asound/ 2>/dev/null | grep '\\->' | tr -s ' ' | cut -d ' ' -f 9 | while "
"read line; do echo \" \\\"hw:$line\\\"\" ; done ; fi");
if (r != 0)
debug(2, "error %d executing a script to list alsa hardware device names", r);
// look for devices with a name prefix of hw: or hdmi:
int card_number = -1;
snd_card_next(&card_number);

if (card_number < 0) {
printf(" no hardware output devices found.\n");
}

int at_least_one_device_found = 0;
while (card_number >= 0) {
void **hints;
char *hdmi_str = NULL;
char *hw_str = NULL;
if (snd_device_name_hint(card_number, "pcm", &hints) == 0) {
void **device_on_card_hints = hints;
while (*device_on_card_hints != NULL) {
char *device_on_card_name = snd_device_name_get_hint(*device_on_card_hints, "NAME");
if ((strstr(device_on_card_name, "hw:") == device_on_card_name) && (hw_str == NULL))
hw_str = strdup(device_on_card_name);
if ((strstr(device_on_card_name, "hdmi:") == device_on_card_name) && (hdmi_str == NULL))
hdmi_str = strdup(device_on_card_name);
free(device_on_card_name);
device_on_card_hints++;
}
snd_device_name_free_hint(hints);
if ((hdmi_str != NULL) || (hw_str != NULL)) {
if (at_least_one_device_found == 0) {
printf(" hardware output devices:\n");
at_least_one_device_found = 1;
}
}
if (hdmi_str != NULL) {
simplify_and_printf_mutable_device_name(hdmi_str);
} else if (hw_str != NULL) {
simplify_and_printf_mutable_device_name(hw_str);
}
if (hdmi_str != NULL)
free(hdmi_str);
if (hw_str != NULL)
free(hw_str);
}
snd_card_next(&card_number);
}
if (at_least_one_device_found == 0)
printf(" no hardware output devices found.\n");
}

void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; } // ugh -- not static!
void set_alsa_out_dev(char *dev) {
alsa_out_dev = dev;
if (hw_alsa_out_dev != NULL)
free(hw_alsa_out_dev);
hw_alsa_out_dev = str_replace(alsa_out_dev, "hdmi:", "hw:");
} // ugh -- not static!

// assuming pthread cancellation is disabled
// returns zero of all is okay, a Unx error code if there's a problem
Expand Down Expand Up @@ -902,7 +962,7 @@ static int prepare_mixer() {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable

if (alsa_mix_dev == NULL)
alsa_mix_dev = alsa_out_dev;
alsa_mix_dev = hw_alsa_out_dev;

// Now, start trying to initialise the alsa device with the settings
// obtained
Expand Down Expand Up @@ -1355,11 +1415,19 @@ static int init(int argc, char **argv) {
}

debug(1, "alsa: output device name is \"%s\".", alsa_out_dev);



// now, we need a version of the alsa_out_dev that substitutes "hw:" for "hdmi" if it's
// there. It seems hw:1 would be a valid devcie name where hdmi:1 would not

if (alsa_out_dev != NULL)
hw_alsa_out_dev = str_replace(alsa_out_dev, "hdmi:", "hw:");

// so, now, if the option to keep the DAC running has been selected, start a
// thread to monitor the
// length of the queue
// if the queue gets too short, stuff it with silence


pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL);

Expand All @@ -1376,6 +1444,8 @@ static void deinit(void) {
debug(3, "Join buffer monitor thread.");
pthread_join(alsa_buffer_monitor_thread, NULL);
pthread_setcancelstate(oldState, NULL);
if (hw_alsa_out_dev != NULL)
free(hw_alsa_out_dev);
}

static int set_mute_state() {
Expand Down
9 changes: 9 additions & 0 deletions audio_ao.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ static void help(void) {
}

static int init(int argc, char **argv) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
ao_initialize();
driver = ao_default_driver_id();
if (driver == -1) {
Expand Down Expand Up @@ -133,14 +135,18 @@ static int init(int argc, char **argv) {
fmt.byte_format = AO_FMT_NATIVE;
fmt.matrix = strdup("L,R");
}
pthread_setcancelstate(oldState, NULL);
return 0;
}

static void deinit(void) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
if (dev != NULL)
ao_close(dev);
dev = NULL;
ao_shutdown();
pthread_setcancelstate(oldState, NULL);
}

static void start(__attribute__((unused)) int sample_rate,
Expand All @@ -166,10 +172,13 @@ static int play(void *buf, int samples, __attribute__((unused)) int sample_type,

static void stop(void) {
// debug(1,"libao stop");
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
if (dev != NULL) {
ao_close(dev);
dev = NULL;
}
pthread_setcancelstate(oldState, NULL);
}

audio_output audio_ao = {.name = "ao",
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.50])
AC_INIT([shairport-sync], [4.3.3], [[email protected]])
AC_INIT([shairport-sync], [4.3.4], [[email protected]])
AM_INIT_AUTOMAKE([subdir-objects])
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ COPY . .
RUN git checkout "$SHAIRPORT_SYNC_BRANCH"
WORKDIR /shairport-sync/build
RUN autoreconf -i ../
RUN ../configure --sysconfdir=/etc --with-alsa --with-pa --with-soxr --with-avahi --with-ssl=openssl \
RUN CFLAGS="-O3" CXXFLAGS="-O3" ../configure --sysconfdir=/etc --with-alsa --with-pa --with-soxr --with-avahi --with-ssl=openssl \
--with-airplay-2 --with-metadata --with-dummy --with-pipe --with-dbus-interface \
--with-stdout --with-mpris-interface --with-mqtt-client \
--with-apple-alac --with-convolution
Expand Down
2 changes: 1 addition & 1 deletion docker/classic/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ COPY . .
RUN git checkout "$SHAIRPORT_SYNC_BRANCH"
WORKDIR /shairport-sync/build
RUN autoreconf -i ../
RUN ../configure --sysconfdir=/etc --with-alsa --with-pa --with-soxr --with-avahi --with-ssl=mbedtls \
RUN CFLAGS="-O3" CXXFLAGS="-O3" ../configure --sysconfdir=/etc --with-alsa --with-pa --with-soxr --with-avahi --with-ssl=mbedtls \
--with-metadata --with-dummy --with-pipe --with-dbus-interface \
--with-stdout --with-mpris-interface --with-mqtt-client \
--with-apple-alac --with-convolution
Expand Down
4 changes: 4 additions & 0 deletions docker/classic/etc/s6-overlay/s6-rc.d/02-dbus/run
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#!/command/with-contenv sh

# Set the limit to the same value Docker has been using in earlier version.
ulimit -n 1048576

echo "Starting dbus"
exec s6-notifyoncheck dbus-daemon --system --nofork --nopidfile
4 changes: 4 additions & 0 deletions docker/etc/s6-overlay/s6-rc.d/02-dbus/run
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#!/command/with-contenv sh

# Set the limit to the same value Docker has been using in earlier version.
ulimit -n 1048576

echo "Starting dbus"
exec s6-notifyoncheck dbus-daemon --system --nofork --nopidfile
14 changes: 2 additions & 12 deletions player.c
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
pthread_cleanup_push(buffer_get_frame_cleanup_handler,
(void *)conn); // undo what's been done so far
do {

pthread_testcancel(); // even if no packets are coming in...
// get the time
local_time_now = get_absolute_time_in_ns(); // type okay
// debug(3, "buffer_get_frame is iterating");
Expand Down Expand Up @@ -2269,9 +2269,7 @@ void *player_thread_func(void *arg) {
if (conn->input_bytes_per_frame == 0)
debug(1, "conn->input_bytes_per_frame is zero!");

pthread_testcancel(); // allow a pthread_cancel request to take effect.
abuf_t *inframe = buffer_get_frame(conn); // this has cancellation point(s), but it's not
// guaranteed that they'll always be executed
abuf_t *inframe = buffer_get_frame(conn); // this has a (needed!) deliberate cancellation point in it.
uint64_t local_time_now = get_absolute_time_in_ns(); // types okay
config.last_access_to_volume_info_time =
local_time_now; // ensure volume info remains seen as valid
Expand Down Expand Up @@ -3233,14 +3231,6 @@ void *player_thread_func(void *arg) {
frames_seen_in_this_logging_interval = 0;
}

// update the watchdog
if ((config.dont_check_timeout == 0) && (config.timeout != 0)) {
uint64_t time_now = get_absolute_time_in_ns();
debug_mutex_lock(&conn->watchdog_mutex, 1000, 0);
conn->watchdog_bark_time = time_now;
debug_mutex_unlock(&conn->watchdog_mutex, 0);
}

// debug(1,"Sync error %lld frames. Amount to stuff %d." ,sync_error,amount_to_stuff);

// new stats calculation. We want a running average of sync error, drift, adjustment,
Expand Down
6 changes: 1 addition & 5 deletions player.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,11 @@ typedef struct {
SOCKADDR remote, local;
volatile int stop;
volatile int running;
volatile uint64_t watchdog_bark_time;
volatile int watchdog_barks; // number of times the watchdog has timed out and done something

uint64_t playstart;
uint64_t connection_start_time; // the time the device is selected, which could be a long time
// before a play
pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread,
player_watchdog_thread;
pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread;

// buffers to delete on exit
int32_t *tbuf;
Expand Down Expand Up @@ -380,7 +377,6 @@ typedef struct {
// request in flight at the same time

pthread_mutex_t reference_time_mutex;
pthread_mutex_t watchdog_mutex;

double local_to_remote_time_gradient; // if no drift, this would be exactly 1.0; likely it's
// slightly above or below.
Expand Down
Loading

0 comments on commit 6ab7942

Please sign in to comment.