From e72acd799212f4681c005479b5fee980f0fc0c15 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Thu, 23 Jun 2022 10:37:59 -0700 Subject: [PATCH 1/5] audio_pw.c: use pthread_setcancelstate to protect the mainloop locking --- audio_pw.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/audio_pw.c b/audio_pw.c index 52e6a882..b9315cff 100644 --- a/audio_pw.c +++ b/audio_pw.c @@ -178,6 +178,8 @@ static void deinit() { pw_properties_free(data.props); data.props = NULL; } + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); } static int init(__attribute__((unused)) int argc, __attribute__((unused)) char **argv) { @@ -230,6 +232,7 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * die("pw: pw_properties_new() failed: %m"); } + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pw_thread_loop_lock(data.mainloop); if (pw_thread_loop_start(data.mainloop) != 0) { @@ -256,6 +259,7 @@ static int init(__attribute__((unused)) int argc, __attribute__((unused)) char * data.sync = pw_core_sync(data.core, 0, data.sync); pw_thread_loop_unlock(data.mainloop); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); return 0; } @@ -336,6 +340,8 @@ static const char *spa_format_to_str(enum spa_audio_format audio_format) { static void start(int sample_rate, int sample_format) { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + pw_thread_loop_lock(data.mainloop); const struct spa_pod *params[1]; @@ -410,22 +416,28 @@ static void start(int sample_rate, int sample_format) { } pw_thread_loop_unlock(data.mainloop); + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); } static void stop() { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pw_thread_loop_lock(data.mainloop); pw_stream_flush(data.stream, true); pw_thread_loop_unlock(data.mainloop); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); } static void flush() { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pw_thread_loop_lock(data.mainloop); pw_stream_flush(data.stream, false); pw_thread_loop_unlock(data.mainloop); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); } static int play(void *buf, int samples) { @@ -433,6 +445,7 @@ static int play(void *buf, int samples) { struct spa_buffer *spa_buffer; struct spa_data *spa_data; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pw_thread_loop_lock(data.mainloop); if (pw_stream_get_state(data.stream, NULL) == PW_STREAM_STATE_PAUSED) @@ -468,6 +481,8 @@ static int play(void *buf, int samples) { pw_thread_loop_unlock(data.mainloop); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + return 0; } From 8f7f85ffb1cf04b9d131aa311e0f41600ead8b6a Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Thu, 23 Jun 2022 10:52:03 -0700 Subject: [PATCH 2/5] audio_pw.c: use pw_thread_loop_timed_wait_full to avoid lockups --- audio_pw.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/audio_pw.c b/audio_pw.c index b9315cff..016df41d 100644 --- a/audio_pw.c +++ b/audio_pw.c @@ -35,6 +35,10 @@ #include +#define PW_TIMEOUT_S 5 +#define SECONDS_TO_NANOSECONDS 1000000000L +#define PW_TIMEOUT_NS (PW_TIMEOUT_S * SECONDS_TO_NANOSECONDS) + struct pw_data { struct pw_thread_loop *mainloop; struct pw_context *context; @@ -412,7 +416,15 @@ static void start(int sample_rate, int sample_format) { if (stream_state == PW_STREAM_STATE_PAUSED) break; - pw_thread_loop_wait(data.mainloop); + struct timespec abstime; + + pw_thread_loop_get_time(data.mainloop, &abstime, PW_TIMEOUT_NS); + + ret = pw_thread_loop_timed_wait_full(data.mainloop, &abstime); + if (ret == -ETIMEDOUT) { + deinit(); + die("pw: pw_thread_loop_timed_wait_full timed out: %s", strerror(ret)); + } } pw_thread_loop_unlock(data.mainloop); @@ -444,6 +456,7 @@ static int play(void *buf, int samples) { struct pw_buffer *pw_buffer; struct spa_buffer *spa_buffer; struct spa_data *spa_data; + int ret; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pw_thread_loop_lock(data.mainloop); @@ -456,7 +469,16 @@ static int play(void *buf, int samples) { if (pw_buffer) break; - pw_thread_loop_wait(data.mainloop); + struct timespec abstime; + + pw_thread_loop_get_time(data.mainloop, &abstime, PW_TIMEOUT_NS); + + ret = pw_thread_loop_timed_wait_full(data.mainloop, &abstime); + if (ret == -ETIMEDOUT) { + pw_thread_loop_unlock(data.mainloop); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + return ret; + } } spa_buffer = pw_buffer->buffer; From 7f5eb3a8a748eb0d5763e4327fc6316ba3c1250d Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 27 Jun 2022 10:17:36 +0100 Subject: [PATCH 3/5] Update AIRPLAY2.md --- AIRPLAY2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AIRPLAY2.md b/AIRPLAY2.md index 4e2900d8..b7825ca3 100644 --- a/AIRPLAY2.md +++ b/AIRPLAY2.md @@ -23,6 +23,7 @@ AirPlay 2 support needs a slightly more powerful CPU for decoding and synchronis Here are some guidelines: * Full access, including `root` privileges, to a system at least as powerful as a Raspberry Pi 2 or a Raspberry Pi Zero 2 W. * A fully up-to-date Linux or FreeBSD. This is important, as some of the libraries must be the latest available. +* Shairport Sync will not run on a Mac because NQPTP, on which it relies, needs ports 319 and 320, which are already used by macOS. * A version of the [ffmpeg](https://www.ffmpeg.org) library with an AAC decoder capable of decoding Floating Planar -- `fltp` -- material. There is a guide [here](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md#aac-decoder-issues-airplay-2-only) to help you find out if your system has it. (At the time of writing, Debian, Ubuntu, Raspberry Pi OS and FreeBSD all include this decoder. Fedora 36 does not.) * An audio output, for example an `alsa` device (or `sndio` in FreeBSD). You can use an application called [`sps-alsa-explore`](https://github.com/mikebrady/sps-alsa-explore) to test the suitability of hardware `alsa` audio devices on your device. Other backends continue to work as with "classic" Shairport Sync. From 994edf81c3910113b1321439144197ef4aba2e4b Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:35:06 +0100 Subject: [PATCH 4/5] Update FREEBSD.md --- FREEBSD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FREEBSD.md b/FREEBSD.md index 54d88823..e25fc810 100644 --- a/FREEBSD.md +++ b/FREEBSD.md @@ -4,7 +4,7 @@ Shairport Sync runs natively on FreeBSD using the `sndio` back end, thanks to th [`sndio`](http://www.sndio.org) is *"a small audio and MIDI framework part of the OpenBSD project and ported to FreeBSD, Linux and NetBSD"* developed by Alexandre Ratchov (see also [this paper](http://www.openbsd.org/papers/asiabsdcon2010_sndio.pdf) for more details). -The build instructions here build Shaiport Sync to output to the `sndio` and/or the `ALSA` sound systems. `ALSA` is the [Advanced Linux Sound Architecture](https://www.alsa-project.org/wiki/Main_Page), so it is not "native" to FreeBSD, but it has been ported to some architectures under FreeBSD. +The build instructions here build Shairport Sync to output to the `sndio` and/or the `ALSA` sound systems. `ALSA` is the [Advanced Linux Sound Architecture](https://www.alsa-project.org/wiki/Main_Page), so it is not "native" to FreeBSD, but it has been ported to some architectures under FreeBSD. Build instructions are different from previous versions of Shairport Sync and may change further. For now, leave the settings in the configuration file at default except as noted below. From c21ca295a8c09f1d352725f194e186f4d42a1a2e Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:48:07 +0100 Subject: [PATCH 5/5] Update AIRPLAY2.md --- AIRPLAY2.md | 44 ++++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/AIRPLAY2.md b/AIRPLAY2.md index b7825ca3..5dbfa0ac 100644 --- a/AIRPLAY2.md +++ b/AIRPLAY2.md @@ -1,49 +1,41 @@ -AirPlay 2 -=== +# AirPlay 2 Shairport Sync offers AirPlay 2 support for audio sources on iOS devices, Macs, HomePod minis and Apple TVs. -### Features and Limitations -#### What Works +## What Works - AirPlay 2 audio for iOS, HomePod mini, AppleTV and Mac players. -- Devices running Shairport Sync in AirPlay 2 mode can be [added as speakers](https://github.com/mikebrady/shairport-sync/blob/development/ADDINGTOHOME.md) to the Home app. + * Two types of audio are received by Shairport Sync – "Realtime" streams of CD quality ALAC (like "classic" AirPlay) and "Buffered Audio" streams of AAC stereo at 44,100 frames per second. The selection of stream type is made by the player. Realtime streams generally have a latency of about two seconds. Buffered Audio streams typically have a latency of half a second or less. + * Audio is synchronised with other AirPlay 2 devices. + * Shairport Sync offers "classic" AirPlay compatibility for situations where iTunes on macOS or macOS Music plays to multiple speakers and where one of more of them is compatible with AirPlay only. -#### What Does Not Work -- No Remote Control -- No AirPlay 2 for Windows iTunes +- Devices running Shairport Sync in AirPlay 2 mode can be [added](https://github.com/mikebrady/shairport-sync/blob/development/ADDINGTOHOME.md) to the Home app. -### General +## What Does Not Work +- No AirPlay 2 for Windows iTunes. +- Remote control facilities are not implemented. + +## General Shairport Sync uses a companion application called [`nqptp`](https://github.com/mikebrady/nqptp) ("Not Quite PTP") for timing and synchronisation in AirPlay 2. `nqptp` must run as `root` and must have exclusive access to ports `319` and `320`. Lossless and High Definition Lossless material is transcoded to AAC before it reaches Shairport Sync. -AirPlay 2 -- What You Need ---- +## What You Need AirPlay 2 support needs a slightly more powerful CPU for decoding and synchronisation and more memory for bigger buffers and larger libraries. A Raspberry Pi 2 or Raspberry Pi Zero 2 W or better is recommended. Here are some guidelines: * Full access, including `root` privileges, to a system at least as powerful as a Raspberry Pi 2 or a Raspberry Pi Zero 2 W. -* A fully up-to-date Linux or FreeBSD. This is important, as some of the libraries must be the latest available. +* An up-to-date Linux or FreeBSD. This is important, as some of the libraries must be the latest available. * Shairport Sync will not run on a Mac because NQPTP, on which it relies, needs ports 319 and 320, which are already used by macOS. * A version of the [ffmpeg](https://www.ffmpeg.org) library with an AAC decoder capable of decoding Floating Planar -- `fltp` -- material. There is a guide [here](https://github.com/mikebrady/shairport-sync/blob/development/TROUBLESHOOTING.md#aac-decoder-issues-airplay-2-only) to help you find out if your system has it. (At the time of writing, Debian, Ubuntu, Raspberry Pi OS and FreeBSD all include this decoder. Fedora 36 does not.) * An audio output, for example an `alsa` device (or `sndio` in FreeBSD). You can use an application called [`sps-alsa-explore`](https://github.com/mikebrady/sps-alsa-explore) to test the suitability of hardware `alsa` audio devices on your device. Other backends continue to work as with "classic" Shairport Sync. -Guides ---- +## Guides * To build Shairport Sync for AirPlay 2 on Linux, please follow the guide at [BUILDFORAP2.md](https://github.com/mikebrady/shairport-sync/blob/development/BUILDFORAP2.md). * A guide for building on FreeBSD is available [here](https://github.com/mikebrady/shairport-sync/blob/development/FREEBSD.md). -AirPlay 2 -- More About What Works ---- -* Two types of audio are received by Shairport Sync -- "Realtime" streams of CD quality ALAC (like "classic" AirPlay) and "Buffered Audio" streams of AAC stereo at 44,100 frames per second. The selection of stream type is made by the player. Realtime streams generally have a latency of about two seconds. Buffered Audio streams typically have a latency of half a second or less. -* Audio is synchronised with other AirPlay 2 devices, including AirPlay 2 devices that have their own master clocks. -* Shairport Sync offers "classic" AirPlay compatibility for situations where iTunes on macOS or macOS Music plays to multiple speakers and where one of more of them is compatible with AirPlay only. - -Note ----- +## Note The functionality offered by Shairport Sync is the result of study and analysis of the AirPlay and AirPlay 2 protocols by many people over the years. These protocols have not been officially published, and there is no assurance that Shairport Sync will continue to work with AirPlay in future. -Acknowledgements ----- +## Acknowledgements Huge thanks are due to a number of individuals who made direct and valuable contributions to Shairport Sync: 1. [ejurgensen](https://github.com/ejurgensen) contributed ideas and code. The `pair_ap` submodule and related code in `rtsp.c` is theirs. 2. [JD Smith](https://github.com/jdtsmith) contributed ideas, comments and suggestions along with extensive and thorough testing. @@ -51,7 +43,3 @@ Huge thanks are due to a number of individuals who made direct and valuable cont 4. [ckdo](https://github.com/ckdo) for their pathfinding work on AirPlay 2, especially the HomeKit pairing. Much of Shairport Sync's AirPlay 2 functionality is based on ideas developed at the [openairplay airplay2-receiver]( https://github.com/openairplay/airplay2-receiver) repository. It is a pleasure to acknowledge the work of the contributors there. - -Finally ----- -AirPlay 2 support is experimental.