Merge branch 'development'

Fix two memory leaks and start using `libgcrypt` again to avoid a memory leak in `libcrypto`.
Fix a memory allocation error.
Close output_device when AP2 Streamed Audio Pauses or Stops
Close output_device when active period ends and `disable_standby_mode` is set to `"auto"`.
Documentation updates.
This commit is contained in:
Mike Brady
2022-10-09 10:09:32 +01:00
9 changed files with 159 additions and 117 deletions
+3 -2
View File
@@ -23,16 +23,17 @@ for timing and synchronisation in AirPlay 2. NQPTP must run as `root` and must h
Lossless and High Definition Lossless material is transcoded to AAC before it reaches Shairport Sync.
## 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.
AirPlay 2 support needs a slightly more powerful CPU for decoding and synchronisation and more memory for bigger buffers and larger libraries. A system with the power of 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.
* Ports 319 and 320 must be free to use (i.e. they must not be in use by another service such as a PTP service) and must not be blocked by a firewall.
* 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.
* Shairport Sync will not run in AirPlay 2 mode 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](TROUBLESHOOTING.md#aac-decoder-issues-airplay-2-only) to help you find out if your system has it.
* An audio output, for example an ALSA device (or `sndio` in FreeBSD). The device must be capable of running at 44,100 frames per second. You can use an `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.
- Multiple instances of the AirPlay 2 version of Shairport Sync can not be hosted on the same system. It seems that AirPlay 2 clients are confused by having multiple AirPlay 2 players at the same IP addresses.
## Guides
* A building guide is available at [BUILD.md](BUILD.md).
+4 -3
View File
@@ -39,7 +39,7 @@ Okay, now let's get the tools and libraries for building and installing Shairpor
# apt upgrade # this is optional but recommended
# apt install --no-install-recommends build-essential git xmltoman autoconf automake libtool \
libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev \
libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev xxd
libplist-dev libsodium-dev libavutil-dev libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev xxd
```
If you are building classic Shairport Sync, the list of packages is shorter:
```
@@ -54,7 +54,7 @@ For AirPlay 2 operation, _before you install the libraries_, please ensure the y
# yum update
# yum install make automake gcc gcc-c++ \
git xmltoman autoconf automake avahi-devel libconfig-devel openssl-devel popt-devel soxr-devel \
ffmpeg ffmpeg-devel libplist-devel libsodium-devel libuuid-devel vim-common \
ffmpeg ffmpeg-devel libplist-devel libsodium-devel libgcrypt-dev libuuid-devel vim-common \
alsa-lib-devel
```
If you are building classic Shairport Sync, the list of packages is shorter:
@@ -121,7 +121,7 @@ Download, install, enable and start NQPTP from [here](https://github.com/mikebra
### Shairport Sync
#### Build and Install
Download Shairport Sync, configure, compile and install it. Before executing the commands, please note the following:
Download Shairport Sync, check out the `development` branch and configure, compile and install it. Before executing the commands, please note the following:
* If building for FreeBSD, replace `--with-systemd` with `--with-os=freebsd --with-freebsd-service`.
* Omit the `--with-airplay-2` from the `./configure` options if you are building classic Shairport Sync.
@@ -130,6 +130,7 @@ Download Shairport Sync, configure, compile and install it. Before executing the
```
$ git clone https://github.com/mikebrady/shairport-sync.git
$ cd shairport-sync
$ git checkout development
$ autoreconf -fi
$ ./configure --sysconfdir=/etc --with-alsa \
--with-soxr --with-avahi --with-ssl=openssl --with-systemd --with-airplay-2
+1 -1
View File
@@ -1,7 +1,7 @@
SUBDIRS = man
ARFLAGS = cr
lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_OPENSSL -pthread
lib_pair_ap_a_CFLAGS = -Wall -g -DCONFIG_GCRYPT -pthread
lib_tinyhttp_a_CFLAGS = -pthread
lib_dbus_interface_a_CFLAGS = -pthread
lib_mpris_interface_a_CFLAGS = -pthread
+13 -2
View File
@@ -116,6 +116,15 @@ You have installed Shairport Sync successfully, the daemon is running, you can s
You have to allow connections to your Shairport Sync device from remote devices. To do so, after re-enabling UFW (see last step of the previous section), enter the following commands in shell:
**Classic AirPlay:**
```
sudo ufw allow 3689/tcp
sudo ufw allow 5353
sudo ufw allow 5000/tcp
sudo ufw allow 6000:6009/udp
```
**AirPlay 2:**
```
sudo ufw allow 319:320/udp
sudo ufw allow 3689/tcp
@@ -123,9 +132,11 @@ sudo ufw allow 5353
sudo ufw allow 5000/tcp
sudo ufw allow 7000/tcp
sudo ufw allow 6000:6009/udp
sudo ufw allow 35000:65535/udp
sudo ufw allow 32768:60999/udp
sudo ufw allow 32768:60999/tcp
```
Ports 319 and 320 need to be opened for AirPlay 2 only.
The range `32768:60999` represents the [ephemeral port range](https://en.m.wikipedia.org/wiki/Ephemeral_port) which seems to be standardised on those values.
You can check UFW config by typing `sudo ufw status` in shell. Please make sure that UFW is active, especially if you have deactivated it previously for testing purposes. Check out the [ufw man pages](http://manpages.ubuntu.com/manpages/man8/ufw.8.html) for more.
+2 -12
View File
@@ -56,8 +56,7 @@ pthread_mutex_t activity_monitor_mutex;
pthread_cond_t activity_monitor_cv;
void going_active(int block) {
// debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" :
// "out");
// debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : "out");
if (config.cmd_active_start)
command_execute(config.cmd_active_start, "", block);
#ifdef CONFIG_METADATA
@@ -71,17 +70,12 @@ void going_active(int block) {
#endif
if (config.disable_standby_mode == disable_standby_auto) {
#ifdef CONFIG_DBUS_INTERFACE
if (dbus_service_is_running())
shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
#endif
config.keep_dac_busy = 1;
}
}
void going_inactive(int block) {
// debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" :
// "out");
// debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : "out");
if (config.cmd_active_stop)
command_execute(config.cmd_active_stop, "", block);
#ifdef CONFIG_METADATA
@@ -95,10 +89,6 @@ void going_inactive(int block) {
#endif
if (config.disable_standby_mode == disable_standby_auto) {
#ifdef CONFIG_DBUS_INTERFACE
if (dbus_service_is_running())
shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
#endif
config.keep_dac_busy = 0;
}
}
+1 -1
View File
@@ -58,7 +58,7 @@ static int play(__attribute__((unused)) void *buf, __attribute__((unused)) int s
// sample code using some of the extra information
if (sample_type == play_samples_are_timed) {
int64_t lead_time = playtime - get_absolute_time_in_ns();
debug(1, "leadtime for frame %u is %" PRId64 " nanoseconds, i.e. %f seconds.", timestamp,
debug(2, "leadtime for frame %u is %" PRId64 " nanoseconds, i.e. %f seconds.", timestamp,
lead_time, 0.000000001 * lead_time);
}
+6 -1
View File
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
AC_INIT([shairport-sync], [4.1-rc0], [4265913+mikebrady@users.noreply.github.com])
AC_INIT([shairport-sync], [4.1-dev], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE([subdir-objects])
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
@@ -412,6 +412,11 @@ if test "x$with_airplay_2" = "xyes" ; then
])
])
PKG_CHECK_MODULES([libsodium], [libsodium],[CFLAGS="${libsodium_CFLAGS} ${CFLAGS}" LIBS="${libsodium_LIBS} ${LIBS}"],[AC_MSG_ERROR(AirPlay 2 support requires libsodium -- libsodium-dev suggested)])
PKG_CHECK_MODULES([libgcrypt], [libgcrypt],[CFLAGS="${libgcrypt_CFLAGS} ${CFLAGS}" LIBS="${libgcrypt_LIBS} ${LIBS}"],[
AC_CHECK_LIB([gcrypt], [gcry_control], [], [
AC_MSG_ERROR([Airplay 2 support requires libgcrypt -- libgcrypt-dev suggested])
])
])
PKG_CHECK_MODULES([libavutil], [libavutil],[CFLAGS="${libavutil_CFLAGS} ${CFLAGS}" LIBS="${libavutil_LIBS} ${LIBS}"],[
AC_MSG_ERROR(AirPlay 2 support requires libavutil -- libavutil-dev suggested)])
PKG_CHECK_MODULES([libavcodec], [libavcodec],[CFLAGS="${libavcodec_CFLAGS} ${CFLAGS}" LIBS="${libavcodec_LIBS} ${LIBS}"],[
+105 -92
View File
@@ -1830,92 +1830,95 @@ void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
uint64_t flushUntilTS = 0;
int flushFromValid = 0;
plist_t messagePlist = plist_from_rtsp_content(req);
plist_t item = plist_dict_get_item(messagePlist, "flushFromSeq");
if (item == NULL) {
debug(2, "Can't find a flushFromSeq");
} else {
flushFromValid = 1;
plist_get_uint_val(item, &flushFromSeq);
debug(2, "flushFromSeq is %" PRId64 ".", flushFromSeq);
}
if (messagePlist != NULL) {
plist_t item = plist_dict_get_item(messagePlist, "flushFromSeq");
if (item == NULL) {
debug(2, "Can't find a flushFromSeq");
} else {
flushFromValid = 1;
plist_get_uint_val(item, &flushFromSeq);
debug(2, "flushFromSeq is %" PRId64 ".", flushFromSeq);
}
item = plist_dict_get_item(messagePlist, "flushFromTS");
if (item == NULL) {
if (flushFromValid != 0)
debug(1, "flushFromSeq without flushFromTS!");
else
debug(2, "Can't find a flushFromTS");
} else {
plist_get_uint_val(item, &flushFromTS);
item = plist_dict_get_item(messagePlist, "flushFromTS");
if (item == NULL) {
if (flushFromValid != 0)
debug(1, "flushFromSeq without flushFromTS!");
else
debug(2, "Can't find a flushFromTS");
} else {
plist_get_uint_val(item, &flushFromTS);
if (flushFromValid == 0)
debug(1, "flushFromTS without flushFromSeq!");
debug(2, "flushFromTS is %" PRId64 ".", flushFromTS);
}
item = plist_dict_get_item(messagePlist, "flushUntilSeq");
if (item == NULL) {
debug(1, "Can't find the flushUntilSeq");
} else {
plist_get_uint_val(item, &flushUntilSeq);
debug(2, "flushUntilSeq is %" PRId64 ".", flushUntilSeq);
}
item = plist_dict_get_item(messagePlist, "flushUntilTS");
if (item == NULL) {
debug(1, "Can't find the flushUntilTS");
} else {
plist_get_uint_val(item, &flushUntilTS);
debug(2, "flushUntilTS is %" PRId64 ".", flushUntilTS);
}
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
// a flush with from... components will not be followed by a setanchor (i.e. a play)
// if it's a flush that will be followed by a setanchor (i.e. a play) then stop play now.
if (flushFromValid == 0)
debug(1, "flushFromTS without flushFromSeq!");
debug(2, "flushFromTS is %" PRId64 ".", flushFromTS);
conn->ap2_play_enabled = 0;
// add the exact request as made to the linked list (not used for anything but diagnostics now)
// int flushNow = 0;
// if (flushFromValid == 0)
// flushNow = 1;
// add_flush_request(flushNow, flushFromSeq, flushFromTS, flushUntilSeq, flushUntilTS, conn);
// now, if it's an immediate flush, replace the existing request, if any
// but it if's a deferred flush and there is an existing deferred request,
// only update the flushUntil stuff -- that seems to preserve
// the intended semantics
// so, always replace these
conn->ap2_flush_until_sequence_number = flushUntilSeq;
conn->ap2_flush_until_rtp_timestamp = flushUntilTS;
if ((conn->ap2_flush_requested != 0) && (conn->ap2_flush_from_valid != 0) &&
(flushFromValid != 0)) {
// if there is a request already, and it's a deferred request, and the current request is also
// deferred... do nothing! -- leave the starting point in place. Yeah, yeah, we know de Morgan's
// Law, but this seems clearer
} else {
conn->ap2_flush_from_sequence_number = flushFromSeq;
conn->ap2_flush_from_rtp_timestamp = flushFromTS;
}
conn->ap2_flush_from_valid = flushFromValid;
conn->ap2_flush_requested = 1;
// reflect the possibly updated flush request
// add_flush_request(flushNow, conn->ap2_flush_from_sequence_number,
// conn->ap2_flush_from_rtp_timestamp, conn->ap2_flush_until_sequence_number,
// conn->ap2_flush_until_rtp_timestamp, conn);
debug_mutex_unlock(&conn->flush_mutex, 3);
if (flushFromValid)
debug(2, "Deferred Flush Requested");
else
debug(2, "Immediate Flush Requested");
plist_free(messagePlist);
// display_all_flush_requests(conn);
}
item = plist_dict_get_item(messagePlist, "flushUntilSeq");
if (item == NULL) {
debug(1, "Can't find the flushUntilSeq");
} else {
plist_get_uint_val(item, &flushUntilSeq);
debug(2, "flushUntilSeq is %" PRId64 ".", flushUntilSeq);
}
item = plist_dict_get_item(messagePlist, "flushUntilTS");
if (item == NULL) {
debug(1, "Can't find the flushUntilTS");
} else {
plist_get_uint_val(item, &flushUntilTS);
debug(2, "flushUntilTS is %" PRId64 ".", flushUntilTS);
}
debug_mutex_lock(&conn->flush_mutex, 1000, 1);
// a flush with from... components will not be followed by a setanchor (i.e. a play)
// if it's a flush that will be followed by a setanchor (i.e. a play) then stop play now.
if (flushFromValid == 0)
conn->ap2_play_enabled = 0;
// add the exact request as made to the linked list (not used for anything but diagnostics now)
// int flushNow = 0;
// if (flushFromValid == 0)
// flushNow = 1;
// add_flush_request(flushNow, flushFromSeq, flushFromTS, flushUntilSeq, flushUntilTS, conn);
// now, if it's an immediate flush, replace the existing request, if any
// but it if's a deferred flush and there is an existing deferred request,
// only update the flushUntil stuff -- that seems to preserve
// the intended semantics
// so, always replace these
conn->ap2_flush_until_sequence_number = flushUntilSeq;
conn->ap2_flush_until_rtp_timestamp = flushUntilTS;
if ((conn->ap2_flush_requested != 0) && (conn->ap2_flush_from_valid != 0) &&
(flushFromValid != 0)) {
// if there is a request already, and it's a deferred request, and the current request is also
// deferred... do nothing! -- leave the starting point in place. Yeah, yeah, we know de Morgan's
// Law, but this seems clearer
} else {
conn->ap2_flush_from_sequence_number = flushFromSeq;
conn->ap2_flush_from_rtp_timestamp = flushFromTS;
}
conn->ap2_flush_from_valid = flushFromValid;
conn->ap2_flush_requested = 1;
// reflect the possibly updated flush request
// add_flush_request(flushNow, conn->ap2_flush_from_sequence_number,
// conn->ap2_flush_from_rtp_timestamp, conn->ap2_flush_until_sequence_number,
// conn->ap2_flush_until_rtp_timestamp, conn);
debug_mutex_unlock(&conn->flush_mutex, 3);
if (flushFromValid)
debug(2, "Deferred Flush Requested");
else
debug(2, "Immediate Flush Requested");
// display_all_flush_requests(conn);
resp->respcode = 200;
}
@@ -2003,6 +2006,10 @@ void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
activity_monitor_signify_activity(0);
conn->ap2_play_enabled = 0;
reset_anchor_info(conn);
if (config.output->stop) {
debug(2, "Connection %d: Stop the output backend.", conn->connection_number);
config.output->stop();
}
}
pthread_cleanup_pop(1); // unlock the conn->flush_mutex
}
@@ -2218,7 +2225,7 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re
uint8_t *body = NULL;
size_t body_len = 0;
struct pair_result *result;
debug(2, "Connection %d: pair-setup Content-Length %d", conn->connection_number,
debug(2, "Connection %d: handle_pair-setup Content-Length %d", conn->connection_number,
req->contentlength);
if (!conn->ap2_pairing_context.setup_ctx) {
@@ -2819,6 +2826,7 @@ static void check_and_send_plist_metadata(plist_t messagePlist, const char *plis
plist_get_string_val(item, &value);
if (value != NULL) {
send_metadata('ssnc', metadata_code, value, strlen(value), NULL, 0);
free(value);
}
}
}
@@ -4169,19 +4177,22 @@ void metadata_stop(void) {
int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char *data,
uint32_t length, rtsp_message *carrier, int block) {
// parameters: type, code, pointer to data or NULL, length of data or NULL,
// the rtsp_message or
// NULL
// clang-format off
// parameters:
// type,
// code,
// pointer to data or NULL,
// length of data or NULL,
// the rtsp_message or NULL,
// the rtsp_message is sent for 'core' messages, because it contains the data
// and must not be
// freed until the data has been read. So, it is passed to send_metadata to be
// retained,
// sent to the thread where metadata is processed and released (and probably
// freed).
// and must not be freed until the data has been read.
// So, it is passed to send_metadata to be retained, sent to the thread where metadata
// is processed and released (and probably freed).
// The rtsp_message is also sent for certain non-'core' messages.
// The reading of the parameters is a bit complex
// The reading of the parameters is a bit complex:
// If the rtsp_message field is non-null, then it represents an rtsp_message
// and the data pointer is assumed to point to something within it.
// The reference counter of the rtsp_message is incremented here and
@@ -4191,8 +4202,10 @@ int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char *
// If the rtsp_message is NULL, then if the pointer is non-null then the data it
// points to, of the length specified, is memcpy'd and passed to the metadata
// handler. The handler should free it when done.
// If the rtsp_message is NULL and the pointer is also NULL, nothing further
// is done.
// clang-format on
metadata_package pack;
pack.type = type;
+24 -3
View File
@@ -45,6 +45,7 @@
#ifdef CONFIG_AIRPLAY_2
#include "ptp-utilities.h"
#include <gcrypt.h>
#include <libavcodec/avcodec.h>
#include <sodium.h>
#include <uuid/uuid.h>
@@ -570,7 +571,7 @@ int parse_options(int argc, char **argv) {
uuid_t binuuid;
uuid_generate_random(binuuid);
char *uuid = malloc(UUID_STR_LEN);
char *uuid = malloc(UUID_STR_LEN + 1); // leave space for the NUL at the end
// Produces a UUID string at uuid consisting of lower-case letters
uuid_unparse_lower(binuuid, uuid);
config.airplay_pi = uuid;
@@ -1307,7 +1308,6 @@ int parse_options(int argc, char **argv) {
// now, do the command line options again, but this time do them fully -- it's a unix convention
// that command line
// arguments have precedence over configuration file settings.
optind = argc;
for (j = 0; j < argc; j++)
if (strcmp(argv[j], "--") == 0)
@@ -1652,7 +1652,6 @@ void exit_function() {
}
}
#endif
if (config.cfg)
config_destroy(config.cfg);
if (config.appName)
@@ -2103,6 +2102,28 @@ int main(int argc, char **argv) {
debug(1, "libsodium initialised.");
}
// this code is based on
// https://www.gnupg.org/documentation/manuals/gcrypt/Initializing-the-library.html
/* Version check should be the very first call because it
makes sure that important subsystems are initialized.
#define NEED_LIBGCRYPT_VERSION to the minimum required version. */
#define NEED_LIBGCRYPT_VERSION "1.5.4"
if (!gcry_check_version(NEED_LIBGCRYPT_VERSION)) {
die("libgcrypt is too old (need %s, have %s).", NEED_LIBGCRYPT_VERSION,
gcry_check_version(NULL));
}
/* Disable secure memory. */
gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
/* ... If required, other initialization goes here. */
/* Tell Libgcrypt that initialization has completed. */
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
#endif
/* Mess around with the latency options */