Merge updates from the development branch into the master branch as 4.2.1

This commit is contained in:
Mike Brady
2023-09-16 18:20:12 +01:00
32 changed files with 1135 additions and 774 deletions
@@ -3,6 +3,8 @@ name: Basic ALSA configuration for systemd, using a build folder.
on:
push:
branches: [ "development" ]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
build:
+3 -1
View File
@@ -3,7 +3,9 @@ name: Full configuration (but without apple-alac) for systemd.
on:
push:
branches: [ "development", "danger" ]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
build:
@@ -3,6 +3,8 @@ name: Full configuration (but without apple-alac) for systemd, using a build fol
on:
push:
branches: [ "development" ]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
build:
+3 -1
View File
@@ -3,7 +3,9 @@ name: Full configuration (but without apple-alac) for a System V system.
on:
push:
branches: [ "development" ]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
build:
@@ -3,7 +3,9 @@ name: Basic libao configuration for macOS with BREW -- classic only, because mac
on:
push:
branches: [ "development", "danger" ]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
build:
@@ -3,7 +3,9 @@ name: Basic ALSA classic configuration for systemd, using a build folder.
on:
push:
branches: [ "development" ]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
build:
@@ -3,6 +3,8 @@ name: Full classic configuration (but without apple-alac) for systemd, using a b
on:
push:
branches: [ "development", "danger" ]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
build:
@@ -4,20 +4,65 @@
# 'master' - rolling, rolling-classic
# 'development' - development, development-classic
name: Build and push docker (commit)
name: Build and push docker (push/pull request)
on:
push:
branches:
- master
- development
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
env:
DOCKER_PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
NQPTP_BRANCH: main
jobs:
main:
test-build-on-pull-request:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Set SHAIRPORT_SYNC_BRANCH env
run: |
SHAIRPORT_SYNC_BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "Current SHAIRPORT_SYNC_BRANCH set to ${SHAIRPORT_SYNC_BRANCH}"
echo "SHAIRPORT_SYNC_BRANCH=${SHAIRPORT_SYNC_BRANCH}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Build (classic)
uses: docker/build-push-action@v4.0.0
with:
context: ./
file: ./docker/classic/Dockerfile
push: false
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
- name: Build
uses: docker/build-push-action@v4.0.0
with:
context: ./
file: ./docker/Dockerfile
push: false
build-args: |
SHAIRPORT_SYNC_BRANCH=${{ env.SHAIRPORT_SYNC_BRANCH }}
NQPTP_BRANCH=${{ env.NQPTP_BRANCH }}
build-and-publish:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout
+1 -1
View File
@@ -5,7 +5,7 @@ Here is how to add a Shairport Sync device to the Apple Home application on iOS
Step 0: Speaker & TV access.
---
In the Home app > Home settings "Allow Speaker & TV access", select the option "Anyone On the Same Network".
In the Home app > Home settings "Allow Speaker & TV access", select the option "Anyone On the Same Network". If a "require password" option below the "anyone on same network" is available, make sure it is unchecked.
Step 1: Open the "Home" app
---
+5 -5
View File
@@ -38,14 +38,14 @@ endif
endif
endif
# include information generated by 'git describe --tags --dirty' if requested
# include information generated by 'git describe --tags --dirty --broken' if requested
if USE_GIT_VERSION
shairport_sync_SOURCES += gitversion.c
gitversion.h: .git/index
printf "// Do not edit!\n" > gitversion.h
printf "// This file is automatically generated by 'git describe --tags --dirty', if available.\n" >> gitversion.h
printf "// This file is automatically generated by 'git describe --tags --dirty --broken', if available.\n" >> gitversion.h
printf " char git_version_string[] = \"" >> gitversion.h
git describe --tags --dirty | tr -d '[[:space:]]' >> gitversion.h
git describe --tags --dirty --broken | tr -d '[[:space:]]' >> gitversion.h
printf "\";\n" >> gitversion.h
gitversion.c: gitversion.h
touch gitversion.c
@@ -262,12 +262,12 @@ endif # INSTALL_CONFIG_FILES
INSTALL_GROUP_TARGET = install-group-local
$(INSTALL_GROUP_TARGET):
getent group shairport-sync &>/dev/null || groupadd -r shairport-sync >/dev/null
getent group shairport-sync &>/dev/null || groupadd -r shairport-sync &>/dev/null
INSTALL_USER_TARGET = install-user-local
$(INSTALL_USER_TARGET): $(INSTALL_GROUP_TARGET)
getent passwd shairport-sync &> /dev/null || useradd -r -M -g shairport-sync -s /usr/sbin/nologin -G audio shairport-sync >/dev/null
getent passwd shairport-sync &>/dev/null || useradd -r -M -g shairport-sync -s /usr/sbin/nologin -G audio shairport-sync &>/dev/null
if INSTALL_SYSTEMV
+1
View File
@@ -14,6 +14,7 @@ Shairport Sync does not support AirPlay video or photo streaming.
* Runtime settings are documented [here](scripts/shairport-sync.conf).
* Build configuration options are detailed in [CONFIGURATION FLAGS.md](CONFIGURATION%20FLAGS.md).
* The `man` page, detailing command line options, is [here](https://htmlpreview.github.io/?https://github.com/mikebrady/shairport-sync/blob/master/man/shairport-sync.html).
* Some advanced topics and developed in [ADVANCED TOPICS](https://github.com/mikebrady/shairport-sync/tree/master/ADVANCED%20TOPICS).
# Features
* Outputs AirPlay audio to [ALSA](https://www.alsa-project.org/wiki/Main_Page), [sndio](http://www.sndio.org), [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/), [Jack Audio](http://jackaudio.org), to a unix pipe or to `STDOUT`. It also has experimental support for [PipeWire](https://pipewire.org) and limited support for [libao](https://xiph.org/ao/) and for [libsoundio](http://libsound.io).
+5 -1
View File
@@ -240,7 +240,11 @@ void activity_monitor_start() {
void activity_monitor_stop() {
if (activity_monitor_running) {
debug(3, "activity_monitor_stop start...");
debug(2, "activity_monitor_stop begin. state: %d, player_state: %d.", state, player_state);
if ((state == am_active) || (state == am_timing_out)) {
going_inactive(config.cmd_blocking);
state = am_inactive;
}
pthread_cancel(activity_monitor_thread);
pthread_join(activity_monitor_thread, NULL);
debug(2, "activity_monitor_stop complete");
+2 -1
View File
@@ -176,7 +176,8 @@ void handle_unfixable_error(int errorCode) {
command_execute(config.cmd_unfixable, messageString, 1);
} else {
die("An unrecoverable error, \"output_device_error_%d\", has been "
"detected. Doing an emergency exit, as no run_this_if_an_unfixable_error_is_detected program.",
"detected. Doing an emergency exit, as no run_this_if_an_unfixable_error_is_detected "
"program.",
errorCode);
}
}
+1
View File
@@ -131,6 +131,7 @@ static int init(int argc, char **argv) {
fmt.rate = 44100;
fmt.channels = 2;
fmt.byte_format = AO_FMT_NATIVE;
fmt.matrix = strdup("L,R");
}
return 0;
}
+2 -1
View File
@@ -298,7 +298,8 @@ static int jack_init(__attribute__((unused)) int argc, __attribute__((unused)) c
}
while (port_list[i++] != NULL) {
inform(
"Additional matching port %s found. Check that the connections are what you intended.");
"Additional matching port %s found. Check that the connections are what you intended.",
port_list[i - 1]);
}
jack_free(port_list);
}
+1 -1
View File
@@ -59,7 +59,7 @@ void stream_success_cb(pa_stream *stream, int success, void *userdata);
void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata);
int status_error_notifications = 0;
static void check_pa_stream_status(const pa_stream *p, const char *message) {
static void check_pa_stream_status(pa_stream *p, const char *message) {
if (status_error_notifications < 10) {
if (p == NULL) {
warn("%s No pulseaudio stream!", message);
+163 -30
View File
@@ -37,6 +37,7 @@
#include <fcntl.h>
#include <inttypes.h> // PRIdPTR
#include <libgen.h>
#include <math.h>
#include <memory.h>
#include <poll.h>
#include <popt.h>
@@ -71,11 +72,12 @@
#endif
#ifdef CONFIG_OPENSSL
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/aes.h> // needed for older AES stuff
#include <openssl/bio.h> // needed for BIO_new_mem_buf
#include <openssl/err.h> // needed for ERR_error_string, ERR_get_error
#include <openssl/evp.h> // needed for EVP_PKEY_CTX_new, EVP_PKEY_sign_init, EVP_PKEY_sign
#include <openssl/pem.h> // needed for PEM_read_bio_RSAPrivateKey, EVP_PKEY_CTX_set_rsa_padding
#include <openssl/rsa.h> // needed for EVP_PKEY_CTX_set_rsa_padding
#endif
#ifdef CONFIG_POLARSSL
@@ -808,25 +810,82 @@ static char super_secret_key[] =
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
RSA *rsa = NULL;
if (!rsa) {
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
BIO_free(bmem);
}
uint8_t *out = NULL;
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1); // 1.0.2
EVP_PKEY *rsaKey = PEM_read_bio_PrivateKey(bmem, NULL, NULL, NULL); // 1.0.2
BIO_free(bmem);
size_t ol = 0;
if (rsaKey != NULL) {
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(rsaKey, NULL); // 1.0.2
if (ctx != NULL) {
uint8_t *out = malloc(RSA_size(rsa));
switch (mode) {
case RSA_MODE_AUTH:
*outlen = RSA_private_encrypt(inlen, input, out, rsa, RSA_PKCS1_PADDING);
break;
case RSA_MODE_KEY:
*outlen = RSA_private_decrypt(inlen, input, out, rsa, RSA_PKCS1_OAEP_PADDING);
break;
default:
die("bad rsa mode");
switch (mode) {
case RSA_MODE_AUTH: {
if (EVP_PKEY_sign_init(ctx) > 0) { // 1.0.2
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0) { // 1.0.2
if (EVP_PKEY_sign(ctx, NULL, &ol, (const unsigned char *)input, inlen) > 0) { // 1.0.2
out = (unsigned char *)malloc(ol);
if (EVP_PKEY_sign(ctx, out, &ol, (const unsigned char *)input, inlen) > 0) { // 1.0.2
debug(3, "success with output length of %lu.", ol);
} else {
debug(1, "error 2 \"%s\" with EVP_PKEY_sign:",
ERR_error_string(ERR_get_error(), NULL));
}
} else {
debug(1,
"error 1 \"%s\" with EVP_PKEY_sign:", ERR_error_string(ERR_get_error(), NULL));
}
} else {
debug(1, "error \"%s\" with EVP_PKEY_CTX_set_rsa_padding:",
ERR_error_string(ERR_get_error(), NULL));
}
} else {
debug(1,
"error \"%s\" with EVP_PKEY_sign_init:", ERR_error_string(ERR_get_error(), NULL));
}
} break;
case RSA_MODE_KEY: {
if (EVP_PKEY_decrypt_init(ctx) > 0) {
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) > 0) {
/* Determine buffer length */
if (EVP_PKEY_decrypt(ctx, NULL, &ol, (const unsigned char *)input, inlen) > 0) {
out = OPENSSL_malloc(ol);
if (out != NULL) {
if (EVP_PKEY_decrypt(ctx, out, &ol, (const unsigned char *)input, inlen) > 0) {
debug(3, "decrypt success");
} else {
debug(1, "error \"%s\" with EVP_PKEY_decrypt:",
ERR_error_string(ERR_get_error(), NULL));
}
} else {
debug(1, "OPENSSL_malloc failed");
}
} else {
debug(1,
"error \"%s\" with EVP_PKEY_decrypt:", ERR_error_string(ERR_get_error(), NULL));
}
} else {
debug(1, "error \"%s\" with EVP_PKEY_CTX_set_rsa_padding:",
ERR_error_string(ERR_get_error(), NULL));
}
} else {
debug(1, "error \"%s\" with EVP_PKEY_decrypt_init:",
ERR_error_string(ERR_get_error(), NULL));
}
} break;
default:
debug(1, "Unknown mode");
break;
}
EVP_PKEY_CTX_free(ctx); // 1.0.2
} else {
printf("error \"%s\" with EVP_PKEY_CTX_new:\n", ERR_error_string(ERR_get_error(), NULL));
}
EVP_PKEY_free(rsaKey); // 1.0.2
} else {
printf("error \"%s\" with EVP_PKEY_new:\n", ERR_error_string(ERR_get_error(), NULL));
}
RSA_free(rsa);
*outlen = ol;
pthread_setcancelstate(oldState, NULL);
return out;
}
@@ -1133,7 +1192,25 @@ uint32_t uatoi(const char *nptr) {
return r;
}
// clang-format off
// Given an AirPlay volume (0 to -30) and the highest and lowest attenuations available in the mixer,
// the *vol2attn functions return anmattenuation depending on the AirPlay volume
// and the function's transfer function.
// Note that the max_db and min_db are given as dB*100
// clang-format on
double flat_vol2attn(double vol, long max_db, long min_db) {
// clang-format off
// This "flat" volume control profile has the property that a given change in the AirPlay volume
// always results in the same change in output dB. For example, if a change of AirPlay volume
// from 0 to -4 resulted in a 7 dB change, then a change in AirPlay volume from -20 to -24
// would also result in a 7 dB change.
// clang-format on
double vol_setting = min_db; // if all else fails, set this, for safety
if ((vol <= 0.0) && (vol >= -30.0)) {
@@ -1142,21 +1219,77 @@ double flat_vol2attn(double vol, long max_db, long min_db) {
// max_db);
} else if (vol != -144.0) {
debug(1,
"Linear volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.",
"flat_vol2attn volume request value %f is out of range: should be from 0.0 to -30.0 or "
"-144.0.",
vol);
}
return vol_setting;
}
// Given a volume (0 to -30) and high and low attenuations available in the mixer in dB, return an
// attenuation depending on the volume and the function's transfer function
// See http://tangentsoft.net/audio/atten.html for data on good attenuators.
// We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer
// functions referred to at the link above.
// Note that the max_db and min_db are given as dB*100
double dasl_tapered_vol2attn(double vol, long max_db, long min_db) {
// clang-format off
// The "dasl_tapered" volume control profile has the property that halving the AirPlay volume (the "vol" parameter)
// reduces the output level by 10 dB, which corresponds to roughly halving the perceived volume.
// For example, if the AirPlay volume goes from 0.0 to -15.0, the output level will decrease by 10 dB.
// Halving the AirPlay volume again, from -15 to -22.5, will decrease output by a further 10 dB.
// Reducing the AirPlay volume by half again, this time from -22.5 to -25.25 decreases the output by a further 10 dB,
// meaning that at AirPlay volume -25.25, the volume is decreased 30 dB.
// If the attenuation range of the mixer is restricted -- for example, if it is just 30 dB --
// the output level would reach its minimum before the AirPlay volume reached its minimum.
// This would result in part of the AirPlay volume control's range where
// changing the AirPlay volume would make no difference to the output level.
// In the example of an attenuator with a range of 00.dB to -30.0dB, this
// "dead zone" would be from AirPlay volume -30.0 to -25.25,
// i.e. about one sixth of its -30.0 to 0.0 travel.
// To work around this, the "flat" output level is used if it gives a
// higher output dB value than the calculation described above.
// If the device's attenuation range is over about 50 dB,
// the flat output level will hardly be needed at all.
// clang-format on
double vol_setting = min_db; // if all else fails, set this, for safety
if ((vol <= 0.0) && (vol >= -30.0)) {
double vol_pct = 1 - (vol / -30.0); // This will be in the range [0, 1]
if (vol_pct <= 0) {
return min_db;
}
double flat_setting = min_db + (max_db - min_db) * vol_pct;
vol_setting =
max_db + 1000 * log10(vol_pct) / log10(2); // This will be in the range [-inf, max_db]
if (vol_setting < flat_setting) {
debug(3,
"dasl_tapered_vol2attn returning a flat setting of %f for AirPlay volume %f instead of "
"a tapered setting of %f in a range from %f to %f.",
flat_setting, vol, vol_setting, 1.0 * min_db, 1.0 * max_db);
return flat_setting;
}
if (vol_setting > max_db) {
return max_db;
}
return vol_setting;
} else if (vol != -144.0) {
debug(1,
"dasl_tapered volume request value %f is out of range: should be from 0.0 to -30.0 or "
"-144.0.",
vol);
}
return vol_setting;
}
double vol2attn(double vol, long max_db, long min_db) {
// See http://tangentsoft.net/audio/atten.html for data on good attenuators.
// We want a smooth attenuation function, like, for example, the ALPS RK27 Potentiometer transfer
// functions referred to at the link above.
// We use a little coordinate geometry to build a transfer function from the volume passed in to
// the device's dynamic range. (See the diagram in the documents folder.) The x axis is the
// "volume in" which will be from -30 to 0. The y axis will be the "volume out" which will be from
@@ -1199,7 +1332,7 @@ double vol2attn(double vol, long max_db, long min_db) {
}
vol_setting += max_db;
} else if (vol != -144.0) {
debug(1, "Volume request value %f is out of range: should be from 0.0 to -30.0 or -144.0.",
debug(1, "vol2attn request value %f is out of range: should be from 0.0 to -30.0 or -144.0.",
vol);
vol_setting = min_db; // for safety, return the lowest setting...
} else {
+10 -1
View File
@@ -73,6 +73,7 @@ typedef enum {
typedef enum {
VCP_standard = 0,
VCP_flat,
VCP_dasl_tapered,
} volume_control_profile_type;
typedef enum {
@@ -387,9 +388,17 @@ char *base64_enc(uint8_t *input, int length);
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode);
// given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60
// dB), return an attenuation depending on a linear interpolation along along the range
// dB), return an attenuation depending on a linear interpolation along the range
double flat_vol2attn(double vol, long max_db, long min_db);
// The intention behind dasl_tapered is that a given percentage change in volume should result in
// the same percentage change in perceived loudness. For instance, doubling the volume level should
// result in doubling the perceived loudness. With the range of AirPlay volume being from -30 to 0,
// doubling the volume from -22.5 to -15 results in an increase of 10 dB. Similarly, doubling the
// volume from -15 to 0 results in an increase of 10 dB. For compatibility with mixers having a
// restricted attenuation range (e.g. 30 dB), "dasl_tapered" will switch to a flat profile at low
// AirPlay volumes.
double dasl_tapered_vol2attn(double vol, long max_db, long min_db);
// given a volume (0 to -30) and high and low attenuations in dB*100 (e.g. 0 to -6000 for 0 to -60
// dB), return an attenuation depending on the transfer function
double vol2attn(double vol, long max_db, long min_db);
+2 -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.2], [4265913+mikebrady@users.noreply.github.com])
AC_INIT([shairport-sync], [4.2.1], [4265913+mikebrady@users.noreply.github.com])
AM_INIT_AUTOMAKE([subdir-objects])
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
@@ -33,6 +33,7 @@ esac
# Checks for programs.
AC_PROG_CC
AC_PROG_CXX
AC_CHECK_TOOL(AR, ar, :)
AC_PROG_INSTALL
PKG_PROG_PKG_CONFIG([0.9.0])
+12 -14
View File
@@ -411,19 +411,17 @@ void set_dacp_server_information(rtsp_conn_info *conn) {
debug(2, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
dacp_server.ip_string, dacp_server.dacp_id);
// If the client is forked-daapd, then we always use revision number 1
// because otherwise the return read will hang in a "long poll" if there
// are no changes.
// This is different to other AirPlay clients
// which return immediately with a 403 code if there are no changes.
dacp_server.always_use_revision_number_1 = 0;
if (conn->UserAgent != NULL) {
char *p = strstr(conn->UserAgent, "forked-daapd");
if ((p != 0) &&
(p == conn->UserAgent)) { // must exist and be at the start of the UserAgent string
dacp_server.always_use_revision_number_1 = 1;
}
}
/*
"long polling" is not implemented by Shairport Sync, whereby by sending the client the
last-received revision number, the link will hang until a change occurs.
Instead, at present, Shairport Sync always uses a revision number of 1.
*/
// always use revision number 1
dacp_server.always_use_revision_number_1 = 1;
metadata_hub_modify_prolog();
int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
@@ -653,7 +651,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
char *response = NULL;
int32_t item_size;
char command[1024] = "";
if (always_use_revision_number_1 != 0) // for forked-daapd
if (always_use_revision_number_1 != 0) // see the "long polling" note above
revision_number = 1;
snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
revision_number);
+15 -8
View File
@@ -635,13 +635,14 @@ gboolean notify_volume_callback(ShairportSync *skeleton,
if (((iv >= -30.0) && (iv <= 0.0)) || (iv == -144.0)) {
debug(2, ">> setting volume to %7.4f.", iv);
lock_player();
if (playing_conn != NULL) {
player_volume(iv, playing_conn);
playing_conn->own_airplay_volume = iv;
playing_conn->own_airplay_volume_set = 1;
pthread_cleanup_debug_mutex_lock(&principal_conn_lock, 100000, 1);
if (principal_conn != NULL) {
player_volume(iv, principal_conn);
principal_conn->own_airplay_volume = iv;
principal_conn->own_airplay_volume_set = 1;
}
unlock_player();
pthread_cleanup_pop(1); // release the principal_conn lock
config.airplay_volume = iv;
config.last_access_to_volume_info_time = get_absolute_time_in_ns();
} else {
@@ -765,6 +766,8 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton,
config.volume_control_profile = VCP_standard;
else if (strcasecmp(th, "flat") == 0)
config.volume_control_profile = VCP_flat;
else if (strcasecmp(th, "dasl_tapered") == 0)
config.volume_control_profile = VCP_dasl_tapered;
else {
warn("Unrecognised Volume Control Profile: \"%s\".", th);
switch (config.volume_control_profile) {
@@ -774,6 +777,9 @@ gboolean notify_volume_control_profile_callback(ShairportSync *skeleton,
case VCP_flat:
shairport_sync_set_volume_control_profile(skeleton, "flat");
break;
case VCP_dasl_tapered:
shairport_sync_set_volume_control_profile(skeleton, "dasl_tapered");
break;
default:
debug(1, "This should never happen!");
shairport_sync_set_volume_control_profile(skeleton, "standard");
@@ -868,8 +874,6 @@ static gboolean on_handle_remote_command(ShairportSync *skeleton, GDBusMethodInv
static gboolean on_handle_drop_session(ShairportSync *skeleton, GDBusMethodInvocation *invocation,
__attribute__((unused)) gpointer user_data) {
if (playing_conn != NULL)
debug(1, ">> stopping current play session");
get_play_lock(NULL, 1); // stop any current session and don't replace it
shairport_sync_complete_drop_session(skeleton, invocation);
return TRUE;
@@ -1065,6 +1069,9 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name
if (config.volume_control_profile == VCP_standard)
shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "standard");
else if (config.volume_control_profile == VCP_dasl_tapered)
shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton),
"dasl_tapered");
else
shairport_sync_set_volume_control_profile(SHAIRPORT_SYNC(shairportSyncSkeleton), "flat");
+9 -5
View File
@@ -56,7 +56,7 @@
#endif
#ifdef CONFIG_OPENSSL
#include <openssl/md5.h>
#include <openssl/evp.h>
#endif
struct metadata_bundle metadata_store;
@@ -209,10 +209,14 @@ char *metadata_write_image_file(const char *buf, int len) {
// uint8_t ap_md5[16];
#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, buf, len);
MD5_Final(img_md5, &ctx);
EVP_MD_CTX *ctx;
unsigned int img_md5_len = EVP_MD_size(EVP_md5());
ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
EVP_DigestUpdate(ctx, buf, len);
EVP_DigestFinal_ex(ctx, img_md5, &img_md5_len);
EVP_MD_CTX_free(ctx);
#endif
#ifdef CONFIG_MBEDTLS
+17 -8
View File
@@ -1,6 +1,6 @@
/*
* This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp).
* Copyright (c) 2021--2022 Mike Brady.
* Copyright (c) 2021--2023 Mike Brady.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,7 +22,7 @@
#define NQPTP_INTERFACE_NAME "/nqptp"
#define NQPTP_SHM_STRUCTURES_VERSION 9
#define NQPTP_SHM_STRUCTURES_VERSION 10
#define NQPTP_CONTROL_PORT 9000
// The control port expects a UDP packet with the first character being a command letter
@@ -50,20 +50,29 @@
// When the clock is inactive, it can stop running. This causes the offset to decrease.
// NQPTP clock smoothing would treat this as a network delay, causing true sync to be lost.
// To avoid this, when the clock goes from inactive to active,
// NQPTP resets clock smoothing to the new offset.
// NQPTP resets clock smoothing to the new offset.
#include <inttypes.h>
#include <pthread.h>
struct shm_structure {
pthread_mutex_t shm_mutex; // for safely accessing the structure
uint16_t version; // check this is equal to NQPTP_SHM_STRUCTURES_VERSION
typedef struct {
uint64_t master_clock_id; // the current master clock
char master_clock_ip[64]; // where it's coming from
uint64_t local_time; // the time when the offset was calculated
uint64_t local_to_master_time_offset; // add this to the local time to get master clock time
uint64_t master_clock_start_time; // this is when the master clock became master
} shm_structure_set;
// The actual interface comprises a shared memory region of type struct shm_structure.
// This comprises two records of type shm_structure_set.
// The secondary record is written strictly after all writes to the main record are
// complete. This is ensured using the __sync_synchronize() construct.
// The reader should ensure that both copies match for a read to be valid.
// For safety, the secondary record should be read strictly after the first.
struct shm_structure {
uint16_t version; // check this is equal to NQPTP_SHM_STRUCTURES_VERSION
shm_structure_set main;
shm_structure_set secondary;
};
#endif
+108 -60
View File
@@ -57,7 +57,12 @@
#endif
#ifdef CONFIG_OPENSSL
#include <openssl/aes.h>
#include <openssl/aes.h> // needed for older AES stuff
#include <openssl/bio.h> // needed for BIO_new_mem_buf
#include <openssl/err.h> // needed for ERR_error_string, ERR_get_error
#include <openssl/evp.h> // needed for EVP_PKEY_CTX_new, EVP_PKEY_sign_init, EVP_PKEY_sign
#include <openssl/pem.h> // needed for PEM_read_bio_RSAPrivateKey, EVP_PKEY_CTX_set_rsa_padding
#include <openssl/rsa.h> // needed for EVP_PKEY_CTX_set_rsa_padding
#endif
#ifdef CONFIG_SOXR
@@ -96,7 +101,7 @@
#include "activity_monitor.h"
// m<ake the first audio packet deliberately early to bias the sync error of
// make the first audio packet deliberately early to bias the sync error of
// the very first packet, making the error more likely to be too early
// rather than too late. It it's too early,
// a delay exactly compensating for it can be sent just before the
@@ -190,6 +195,41 @@ void unencrypted_packet_decode(unsigned char *packet, int length, short *dest, i
}
}
#ifdef CONFIG_OPENSSL
// Thanks to
// https://stackoverflow.com/questions/27558625/how-do-i-use-aes-cbc-encrypt-128-openssl-properly-in-ubuntu
// for inspiration. Changed to a 128-bit key and no padding.
int openssl_aes_decrypt_cbc(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
unsigned char *iv, unsigned char *plaintext) {
EVP_CIPHER_CTX *ctx;
int len;
int plaintext_len = 0;
ctx = EVP_CIPHER_CTX_new();
if (ctx != NULL) {
if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv) == 1) {
EVP_CIPHER_CTX_set_padding(ctx, 0); // no padding -- always returns 1
// no need to allow space for padding in the output, as padding is disabled
if (EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len) == 1) {
plaintext_len = len;
if (EVP_DecryptFinal_ex(ctx, plaintext + len, &len) == 1) {
plaintext_len += len;
} else {
debug(1, "EVP_DecryptFinal_ex error \"%s\".", ERR_error_string(ERR_get_error(), NULL));
}
} else {
debug(1, "EVP_DecryptUpdate error \"%s\".", ERR_error_string(ERR_get_error(), NULL));
}
} else {
debug(1, "EVP_DecryptInit_ex error \"%s\".", ERR_error_string(ERR_get_error(), NULL));
}
EVP_CIPHER_CTX_free(ctx);
} else {
debug(1, "EVP_CIPHER_CTX_new error \"%s\".", ERR_error_string(ERR_get_error(), NULL));
}
return plaintext_len;
}
#endif
int audio_packet_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_conn_info *conn) {
// parameters: where the decoded stuff goes, its length in samples,
// the incoming packet, the length of the incoming packet in bytes
@@ -218,7 +258,7 @@ int audio_packet_decode(short *dest, int *destlen, uint8_t *buf, int len, rtsp_c
aes_crypt_cbc(&conn->dctx, AES_DECRYPT, aeslen, iv, buf, packet);
#endif
#ifdef CONFIG_OPENSSL
AES_cbc_encrypt(buf, packet, aeslen, &conn->aes, iv, AES_DECRYPT);
openssl_aes_decrypt_cbc(buf, aeslen, conn->stream.aeskey, iv, packet);
#endif
memcpy(packet + aeslen, buf + aeslen, len - aeslen);
unencrypted_packet_decode(packet, len, dest, &outsize, maximum_possible_outsize, conn);
@@ -390,7 +430,7 @@ static void free_audio_buffers(rtsp_conn_info *conn) {
free(conn->audio_buffer[i].data);
buffers_released++;
}
debug(1, "%" PRId64 " buffers allocated, %" PRId64 " buffers released.", buffers_allocated,
debug(2, "%" PRId64 " buffers allocated, %" PRId64 " buffers released.", buffers_allocated,
buffers_released);
}
@@ -1737,20 +1777,15 @@ double suggested_volume(rtsp_conn_info *conn) {
void player_thread_cleanup_handler(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
if (pthread_mutex_trylock(&playing_conn_lock) == 0) {
pthread_cleanup_push(mutex_unlock, &playing_conn_lock);
if (playing_conn == conn) {
if (config.output->stop) {
debug(3, "Connection %d: Stop the output backend.", conn->connection_number);
config.output->stop();
}
} else {
debug(1, "This is not the playing conn.");
if ((principal_conn == conn) && (conn != NULL)) {
if (config.output->stop) {
debug(2, "Connection %d: Stop the output backend.", conn->connection_number);
config.output->stop();
}
pthread_cleanup_pop(1); // unlock the mutex
} else {
debug(1, "Can not acquire play lock.");
if (conn != NULL)
debug(1, "Connection %d: this conn is not the principal_conn.", conn->connection_number);
}
int oldState;
@@ -1860,8 +1895,6 @@ void player_thread_cleanup_handler(void *arg) {
if (conn->stream.type == ast_apple_lossless)
terminate_decoders(conn);
// reset_anchor_info(conn);
// release_play_lock(conn);
conn->rtp_running = 0;
pthread_setcancelstate(oldState, NULL);
debug(2, "Connection %d: player terminated.", conn->connection_number);
@@ -1923,10 +1956,6 @@ void *player_thread_func(void *arg) {
memset(&conn->dctx, 0, sizeof(aes_context));
aes_setkey_dec(&conn->dctx, conn->stream.aeskey, 128);
#endif
#ifdef CONFIG_OPENSSL
AES_set_decrypt_key(conn->stream.aeskey, 128, &conn->aes);
#endif
}
conn->timestamp_epoch = 0; // indicate that the next timestamp will be the first one.
@@ -2029,14 +2058,14 @@ void *player_thread_func(void *arg) {
#define trend_interval 1003
int number_of_statistics, oldest_statistic, newest_statistic;
int at_least_one_frame_seen = 0;
int frames_seen_in_this_logging_interval = 0;
int at_least_one_frame_seen_this_session = 0;
int64_t tsum_of_sync_errors, tsum_of_corrections, tsum_of_insertions_and_deletions,
tsum_of_drifts;
int64_t previous_sync_error = 0, previous_correction = 0;
uint64_t minimum_dac_queue_size = UINT64_MAX;
int32_t minimum_buffer_occupancy = INT32_MAX;
int32_t maximum_buffer_occupancy = INT32_MIN;
uint64_t minimum_dac_queue_size;
int32_t minimum_buffer_occupancy;
int32_t maximum_buffer_occupancy;
#ifdef CONFIG_AIRPLAY_2
conn->ap2_audio_buffer_minimum_size = -1;
@@ -2445,7 +2474,7 @@ void *player_thread_func(void *arg) {
// now, go back as far as the total latency less, say, 100 ms, and check the presence of
// frames from then onwards
at_least_one_frame_seen = 1;
frames_seen_in_this_logging_interval++;
// This is the timing error for the next audio frame in the DAC, if applicable
int64_t sync_error = 0;
@@ -2472,10 +2501,12 @@ void *player_thread_func(void *arg) {
int16_t bo = conn->ab_write - conn->ab_read; // do this in 16 bits
conn->buffer_occupancy = bo; // 32 bits
if (conn->buffer_occupancy < minimum_buffer_occupancy)
if ((frames_seen_in_this_logging_interval == 1) ||
(conn->buffer_occupancy < minimum_buffer_occupancy))
minimum_buffer_occupancy = conn->buffer_occupancy;
if (conn->buffer_occupancy > maximum_buffer_occupancy)
if ((frames_seen_in_this_logging_interval == 1) ||
(conn->buffer_occupancy > maximum_buffer_occupancy))
maximum_buffer_occupancy = conn->buffer_occupancy;
// now, before outputting anything to the output device, check the stats
@@ -2564,7 +2595,7 @@ void *player_thread_func(void *arg) {
if (config.statistics_requested) {
if (at_least_one_frame_seen) {
if (frames_seen_in_this_logging_interval) {
do {
line_of_stats[0] = '\0';
statistics_column = 0;
@@ -2632,13 +2663,9 @@ void *player_thread_func(void *arg) {
inform("No frames received in the last sampling interval.");
}
}
minimum_dac_queue_size = UINT64_MAX; // hack reset
maximum_buffer_occupancy = INT32_MIN; // can't be less than this
minimum_buffer_occupancy = INT32_MAX; // can't be more than this
#ifdef CONFIG_AIRPLAY_2
conn->ap2_audio_buffer_minimum_size = -1;
#endif
at_least_one_frame_seen = 0;
}
// here, we want to check (a) if we are meant to do synchronisation,
@@ -2661,7 +2688,8 @@ void *player_thread_func(void *arg) {
current_delay =
0; // could get a negative value if there was underrun, but ignore it.
}
if (current_delay < minimum_dac_queue_size) {
if ((frames_seen_in_this_logging_interval == 1) ||
(current_delay < minimum_dac_queue_size)) {
minimum_dac_queue_size = current_delay; // update for display later
}
} else {
@@ -3205,6 +3233,13 @@ void *player_thread_func(void *arg) {
inframe->resend_time = 0;
inframe->initialisation_time = 0;
// if we've just printed out statistics, note that in the next interval
// we haven't seen any frames yet
if (play_number % print_interval == 0) {
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();
@@ -3390,6 +3425,9 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c
else if (config.volume_control_profile == VCP_flat)
scaled_attenuation =
flat_vol2attn(airplay_volume, max_db, min_db); // no cancellation points
else if (config.volume_control_profile == VCP_dasl_tapered)
scaled_attenuation =
dasl_tapered_vol2attn(airplay_volume, max_db, min_db); // no cancellation points
else
debug(1, "player_volume_without_notification: unrecognised volume control profile");
}
@@ -3586,14 +3624,21 @@ int player_prepare_to_play(rtsp_conn_info *conn) {
}
int player_play(rtsp_conn_info *conn) {
pthread_t *pt = malloc(sizeof(pthread_t));
if (pt == NULL)
die("Couldn't allocate space for pthread_t");
conn->player_thread = pt;
int rc = pthread_create(pt, NULL, player_thread_func, (void *)conn);
if (rc)
debug(1, "Error creating player_thread: %s", strerror(errno));
debug(2, "Connection %d: player_play.", conn->connection_number);
pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 1);
if (conn->player_thread == NULL) {
pthread_t *pt = malloc(sizeof(pthread_t));
if (pt == NULL)
die("Couldn't allocate space for pthread_t");
int rc = pthread_create(pt, NULL, player_thread_func, (void *)conn);
if (rc)
debug(1, "Connection %d: error creating player_thread: %s", conn->connection_number,
strerror(errno));
conn->player_thread = pt; // set _after_ creation of thread
} else {
debug(1, "Connection %d: player thread already exists.", conn->connection_number);
}
pthread_cleanup_pop(1); // release the player_create_delete_mutex
#ifdef CONFIG_METADATA
send_ssnc_metadata('pbeg', NULL, 0, 1); // contains cancellation points
#endif
@@ -3602,35 +3647,38 @@ int player_play(rtsp_conn_info *conn) {
int player_stop(rtsp_conn_info *conn) {
// note -- this may be called from another connection thread.
// int dl = debuglev;
// debuglev = 3;
debug(3, "player_stop");
if (conn->player_thread) {
#ifdef CONFIG_AIRPLAY_2
ptp_send_control_message_string("E"); // signify play is "E"nding
#endif
debug(2, "Connection %d: player_stop.", conn->connection_number);
int response = 0; // okay
pthread_cleanup_debug_mutex_lock(&conn->player_create_delete_mutex, 5000, 1);
pthread_t *pt = conn->player_thread;
if (pt) {
debug(3, "player_thread cancel...");
pthread_cancel(*conn->player_thread);
conn->player_thread = NULL; // cleared _before_ cancelling of thread
pthread_cancel(*pt);
debug(3, "player_thread join...");
if (pthread_join(*conn->player_thread, NULL) == -1) {
if (pthread_join(*pt, NULL) == -1) {
char errorstring[1024];
strerror_r(errno, (char *)errorstring, sizeof(errorstring));
debug(1, "Connection %d: error %d joining player thread: \"%s\".", conn->connection_number,
errno, (char *)errorstring);
} else {
debug(3, "player_thread joined.");
debug(2, "Connection %d: player_stop successful.", conn->connection_number);
}
free(conn->player_thread);
conn->player_thread = NULL;
free(pt);
response = 0; // deleted
} else {
debug(2, "Connection %d: no player thread.", conn->connection_number);
response = -1; // already deleted or never created...
}
pthread_cleanup_pop(1); // release the player_create_delete_mutex
if (response == 0) { // if the thread was just stopped and deleted...
#ifdef CONFIG_AIRPLAY_2
ptp_send_control_message_string("E"); // signify play is "E"nding
#endif
#ifdef CONFIG_METADATA
send_ssnc_metadata('pend', NULL, 0, 1); // contains cancellation points
#endif
// debuglev = dl;
command_stop();
return 0;
} else {
debug(3, "Connection %d: player thread already deleted.", conn->connection_number);
// debuglev = dl;
return -1;
}
return response;
}
+4 -7
View File
@@ -107,7 +107,8 @@ typedef enum {
unspecified_stream_category = 0,
ptp_stream,
ntp_stream,
remote_control_stream
remote_control_stream,
classic_airplay_stream
} airplay_stream_c; // "c" for category
#ifdef CONFIG_AIRPLAY_2
@@ -221,7 +222,7 @@ typedef struct {
int32_t last_seqno_read;
// mutexes and condition variables
pthread_cond_t flowcontrol;
pthread_mutex_t ab_mutex, flush_mutex, volume_control_mutex;
pthread_mutex_t ab_mutex, flush_mutex, volume_control_mutex, player_create_delete_mutex;
int fix_volume;
double own_airplay_volume;
@@ -246,10 +247,6 @@ typedef struct {
aes_context dctx;
#endif
#ifdef CONFIG_OPENSSL
AES_KEY aes;
#endif
int amountStuffed;
int32_t framesProcessedInThisEpoch;
@@ -418,7 +415,7 @@ typedef struct {
uint64_t dac_buffer_queue_minimum_length;
} rtsp_conn_info;
extern pthread_mutex_t playing_conn_lock;
extern pthread_mutex_t principal_conn_lock;
extern int statistics_row; // will be reset to zero when debug level changes or statistics enabled
void reset_buffer(rtsp_conn_info *conn);
+63 -16
View File
@@ -1,6 +1,6 @@
/*
* This file is part of Shairport Sync.
* Copyright (c) Mike Brady 2020 -- 2021
* Copyright (c) Mike Brady 2020 -- 2023
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -49,23 +49,70 @@ void *mapped_addr = NULL;
// returns a copy of the shared memory data from the nqptp
// shared memory interface, so long as it's open.
int get_nqptp_data(struct shm_structure *nqptp_data) {
// uint64_t tn = get_absolute_time_in_ns(); // if interested in timing the function...
struct shm_structure local_nqptp_data;
int response = -1; // presume the worst. Fix it on success
// the first part of the shared memory is a mutex lock, so use it to get
// exclusive access while copying
// We need to ensure that when we read the record, we are not reading it while it is partly
// updated and therefore inconsistent. To achieve this, we do the following:
// We ensure that the secondary record is written by NQPTP _strictly after_
// all writes to the main record are complete.
// Here we read two copies of the entire record, the second
// _strictly after_ all reads from the first are complete.
// (Strict write and read ordering is ensured using the __sync_synchronize() construct.)
// We then compare the main record in the first read to the
// secondary record in the second read.
// If they are equal, we can be sure we have not read a record that has been
// made inconsistent by an interrupted update.
if ((mapped_addr != MAP_FAILED) && (mapped_addr != NULL)) {
pthread_cleanup_debug_mutex_lock((pthread_mutex_t *)mapped_addr, 10000, 1);
memcpy(nqptp_data, (char *)mapped_addr, sizeof(struct shm_structure));
pthread_cleanup_pop(1); // release the mutex
response = 0;
int loop_count = 1;
do {
__sync_synchronize();
memcpy(nqptp_data, (char *)mapped_addr, sizeof(struct shm_structure));
__sync_synchronize();
// read again strictly after a full read -- this is to read the secondary strictly after the
// primary
memcpy(&local_nqptp_data, (char *)mapped_addr, sizeof(struct shm_structure));
// check that the main and secondary data sets match
if (memcmp(&nqptp_data->main, &local_nqptp_data.secondary, sizeof(shm_structure_set)) != 0) {
usleep(2); // microseconds
loop_count++;
}
} while (
(memcmp(&nqptp_data->main, &local_nqptp_data.secondary, sizeof(shm_structure_set)) != 0) &&
(loop_count < 100));
if (loop_count == 10) {
debug(1, "get_nqptp_data -- main and secondary records don't match after %d attempts!",
loop_count);
response = -1;
} else {
response = 0;
}
} else {
if (mapped_addr == NULL)
debug(1, "get_nqptp_data because the mapped_addr is NULL");
debug(1, "get_nqptp_data failed because the mapped_addr is NULL");
else if (mapped_addr == MAP_FAILED)
debug(1, "get_nqptp_data because the mapped_addr is MAP_FAILED");
debug(1, "get_nqptp_data failed because the mapped_addr is MAP_FAILED");
else
debug(1, "get_nqptp_data failed");
}
// int64_t et = get_absolute_time_in_ns() - tn;
// debug(1, "get_nqptp_data time: %.3f microseconds.", 0.001 * et);
return response;
}
int ptp_get_clock_version() {
int response = 0; // no version number information available
struct shm_structure nqptp_data;
if (get_nqptp_data(&nqptp_data) == 0) {
response = nqptp_data.version;
}
return response;
}
@@ -85,15 +132,15 @@ int ptp_get_clock_info(uint64_t *actual_clock_id, uint64_t *time_of_sample, uint
if (get_nqptp_data(&nqptp_data) == 0) {
if (nqptp_data.version == NQPTP_SHM_STRUCTURES_VERSION) {
// assuming a clock id can not be zero
if (nqptp_data.master_clock_id != 0) {
if (nqptp_data.main.master_clock_id != 0) {
if (actual_clock_id != NULL)
*actual_clock_id = nqptp_data.master_clock_id;
*actual_clock_id = nqptp_data.main.master_clock_id;
if (time_of_sample != NULL)
*time_of_sample = nqptp_data.local_time;
*time_of_sample = nqptp_data.main.local_time;
if (raw_offset != NULL)
*raw_offset = nqptp_data.local_to_master_time_offset;
*raw_offset = nqptp_data.main.local_to_master_time_offset;
if (mastership_start_time != NULL)
*mastership_start_time = nqptp_data.master_clock_start_time;
*mastership_start_time = nqptp_data.main.master_clock_start_time;
} else {
response = clock_no_master;
}
@@ -124,12 +171,12 @@ int ptp_shm_interface_open() {
if (strcmp(config.nqptp_shared_memory_interface_name, "") != 0) {
response = 0;
int shared_memory_file_descriptor =
shm_open(config.nqptp_shared_memory_interface_name, O_RDWR, 0);
shm_open(config.nqptp_shared_memory_interface_name, O_RDONLY, 0);
if (shared_memory_file_descriptor >= 0) {
mapped_addr =
// needs to be PROT_READ | PROT_WRITE to allow the mapped memory to be writable for the
// mutex to lock and unlock
mmap(NULL, sizeof(struct shm_structure), PROT_READ | PROT_WRITE, MAP_SHARED,
mmap(NULL, sizeof(struct shm_structure), PROT_READ, MAP_SHARED,
shared_memory_file_descriptor, 0);
if (mapped_addr == MAP_FAILED) {
response = -1;
+2 -1
View File
@@ -1,6 +1,6 @@
/*
* This file is part of Shairport Sync.
* Copyright (c) Mike Brady 2020 -- 2021
* Copyright (c) Mike Brady 2020 -- 2023
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
@@ -37,6 +37,7 @@ void ptp_send_control_message_string(const char *msg);
void ptp_shm_interface_init();
int ptp_shm_interface_open();
int ptp_get_clock_version();
int ptp_shm_interface_close();
#endif /* __PTP_UTILITIES_H */
+13
View File
@@ -2916,14 +2916,27 @@ void *rtp_buffered_audio_processor(void *arg) {
else if (ret < 0) {
debug(1, "error %d during decoding", ret);
} else {
#if LIBAVUTIL_VERSION_MAJOR >= 57
av_samples_alloc(&pcm_audio, &dst_linesize,
codec_context->ch_layout.nb_channels,
decoded_frame->nb_samples, av_format, 1);
#else
av_samples_alloc(&pcm_audio, &dst_linesize, codec_context->channels,
decoded_frame->nb_samples, av_format, 1);
#endif
// remember to free pcm_audio
ret = swr_convert(swr, &pcm_audio, decoded_frame->nb_samples,
(const uint8_t **)decoded_frame->extended_data,
decoded_frame->nb_samples);
#if LIBAVUTIL_VERSION_MAJOR >= 57
dst_bufsize = av_samples_get_buffer_size(
&dst_linesize, codec_context->ch_layout.nb_channels, ret, av_format,
1);
#else
dst_bufsize = av_samples_get_buffer_size(
&dst_linesize, codec_context->channels, ret, av_format, 1);
#endif
// debug(1,"generated %d bytes of PCM", dst_bufsize);
// copy the PCM audio into the PCM buffer.
// make sure it's big enough first
+560 -572
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,7 +3,7 @@
#include "player.h"
extern rtsp_conn_info *playing_conn;
extern rtsp_conn_info *principal_conn;
extern rtsp_conn_info **conns;
void *rtsp_listen_loop(__attribute((unused)) void *arg);
+7
View File
@@ -42,6 +42,13 @@ general =
// volume_control_profile = "standard" ; // use this advanced setting to specify how the airplay volume is transferred to the mixer volume.
// "standard" makes the volume change more quickly at lower volumes and slower at higher volumes.
// "flat" makes the volume change at the same rate at all volumes.
// "dasl_tapered" is similar to "standard" - it makes the volume change more quickly at lower volumes and slower at higher volumes.
// The intention behind dasl_tapered is that a given percentage change in volume should result in the same percentage change in
// perceived loudness. For instance, doubling the volume level should result in doubling the perceived loudness.
// With the range of AirPlay volume being from -30 to 0, doubling the volume from -22.5 to -15 results in an increase of 10 dB.
// Similarly, doubling the volume from -15 to 0 results in an increase of 10 dB.
// For compatibility with mixers having a restricted attenuation range (e.g. 30 dB), "dasl_tapered" will switch to a flat profile at low AirPlay volumes.
// volume_control_combined_hardware_priority = "no"; // when extending the volume range by combining the built-in software attenuator with the hardware mixer attenuator, set this to "yes" to reduce volume by using the hardware mixer first, then the built-in software attenuator.
// default_airplay_volume = -24.0; // this is the suggested volume after a reset or after the high_volume_threshold has been exceed and the high_volume_idle_timeout_in_minutes has passed
+66 -34
View File
@@ -61,6 +61,7 @@
#endif
#ifdef CONFIG_OPENSSL
#include <openssl/evp.h>
#include <openssl/md5.h>
#endif
@@ -904,9 +905,11 @@ int parse_options(int argc, char **argv) {
config.volume_control_profile = VCP_standard;
else if (strcasecmp(str, "flat") == 0)
config.volume_control_profile = VCP_flat;
else if (strcasecmp(str, "dasl_tapered") == 0)
config.volume_control_profile = VCP_dasl_tapered;
else
die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default) "
"or \"flat\"",
die("Invalid volume_control_profile choice \"%s\". It should be \"standard\" (default), "
"\"dasl_tapered\", or \"flat\"",
str);
}
@@ -1413,7 +1416,7 @@ int parse_options(int argc, char **argv) {
for (i = 5; i >= 0; i--) {
// In AirPlay 2 mode, the AP1 name prefix must be
// the same as the AirPlay 2 device id less the colons.
config.ap1_prefix[i] = temporary_airplay_id & 0xFF;
config.ap1_prefix[i] = temporary_airplay_id & 0xFF;
apids[i * 3 + 1] = hexchar[temporary_airplay_id & 0xF];
temporary_airplay_id = temporary_airplay_id >> 4;
apids[i * 3] = hexchar[temporary_airplay_id & 0xF];
@@ -1592,6 +1595,16 @@ void exit_function() {
#endif
*/
debug(2, "Stopping the activity monitor.");
activity_monitor_stop(0);
debug(2, "Stopping the activity monitor done.");
#ifdef CONFIG_DACP_CLIENT
debug(2, "Stopping DACP Monitor");
dacp_monitor_stop();
debug(2, "Stopping DACP Monitor Done");
#endif
#if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE)
/*
Actually, there is no stop_mpris_service() function.
@@ -1617,12 +1630,6 @@ void exit_function() {
}
#endif
#ifdef CONFIG_DACP_CLIENT
debug(2, "Stopping DACP Monitor");
dacp_monitor_stop();
debug(2, "Stopping DACP Monitor Done");
#endif
#ifdef CONFIG_METADATA_HUB
debug(2, "Stopping metadata hub");
metadata_hub_stop();
@@ -1634,9 +1641,6 @@ void exit_function() {
metadata_stop(); // close down the metadata pipe
debug(2, "Stopping metadata done");
#endif
debug(2, "Stopping the activity monitor.");
activity_monitor_stop(0);
debug(2, "Stopping the activity monitor done.");
if ((config.output) && (config.output->deinit)) {
debug(2, "Deinitialise the audio backend.");
@@ -2516,7 +2520,6 @@ int main(int argc, char **argv) {
soxr_time_check_thread_started = 1;
#endif
// In AirPlay 2 mode, the AP1 prefix is the same as the device ID less the colons
// In AirPlay 1 mode, the AP1 prefix is calculated by hashing the service name.
#ifndef CONFIG_AIRPLAY_2
@@ -2525,11 +2528,14 @@ int main(int argc, char **argv) {
// debug(1, "size of hw_addr is %u.", sizeof(config.hw_addr));
#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, config.service_name, strlen(config.service_name));
MD5_Update(&ctx, config.hw_addr, sizeof(config.hw_addr));
MD5_Final(ap_md5, &ctx);
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
EVP_DigestUpdate(mdctx, config.service_name, strlen(config.service_name));
EVP_DigestUpdate(mdctx, config.hw_addr, sizeof(config.hw_addr));
unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
EVP_DigestFinal_ex(mdctx, ap_md5, &md5_digest_len);
EVP_MD_CTX_free(mdctx);
#endif
#ifdef CONFIG_MBEDTLS
@@ -2592,25 +2598,51 @@ int main(int argc, char **argv) {
#endif
#ifdef CONFIG_AIRPLAY_2
ptp_send_control_message_string("T"); // get nqptp to create the named shm interface
int ptp_check_times = 0;
const int ptp_wait_interval_us = 5000;
// wait for up to ten seconds for NQPTP to come online
ptp_send_control_message_string(
"T"); // send this message to get nqptp to create the named shm interface
uint64_t nqptp_start_waiting_time = get_absolute_time_in_ns();
int continue_waiting = 0;
int response = 0;
int64_t time_spent_waiting = 0;
do {
ptp_send_control_message_string("T"); // get nqptp to create the named shm interface
usleep(ptp_wait_interval_us);
ptp_check_times++;
} while ((ptp_shm_interface_open() != 0) &&
(ptp_check_times < (10000000 / ptp_wait_interval_us)));
continue_waiting = 0;
response = ptp_shm_interface_open();
if ((response == -1) && (errno == ENOENT)) {
time_spent_waiting = get_absolute_time_in_ns() - nqptp_start_waiting_time;
if (time_spent_waiting < 10000000000L) {
continue_waiting = 1;
usleep(50000);
}
}
} while (continue_waiting != 0);
if (ptp_shm_interface_open() != 0) {
die("Can't access NQPTP! Is it installed and running?");
} else {
if (ptp_check_times == 1)
debug(1, "NQPTP is online.");
else
debug(1, "NQPTP is online after %u microseconds.", ptp_check_times * ptp_wait_interval_us);
if ((response == -1) && (errno == ENOENT)) {
die("Shairport Sync can not find the nqptp service on this system. Is nqptp installed and "
"running?");
} else if ((response == -1) && (errno == EACCES)) {
die("Shairport Sync must have read access to the nqptp shared memory file in /dev/shm/.");
} else if (response != 0) {
die("an error occurred accessing the nqptp service.");
}
int ptp_clock_version = ptp_get_clock_version();
if (ptp_clock_version == 0) {
die("The nqptp service on this system, which is required for Shairport Sync to operate, does "
"not seem to be initialised.");
} else if (ptp_clock_version < NQPTP_SHM_STRUCTURES_VERSION) {
die("The nqptp service (SMI Version %d) on this system is too old for this version of "
"Shairport Sync, which requires SMI Version %d. Please update.",
ptp_clock_version, NQPTP_SHM_STRUCTURES_VERSION);
} else if (ptp_clock_version > NQPTP_SHM_STRUCTURES_VERSION) {
die("This version of Shairport Sync (SMI Version %d) is too old for the version of nqptp (SMI "
"Version %d) on this system. Please update.",
NQPTP_SHM_STRUCTURES_VERSION, ptp_clock_version);
}
if (time_spent_waiting == 0)
debug(1, "NQPTP is online.");
else
debug(1, "NQPTP came online after %.3f milliseconds.", 0.000001 * time_spent_waiting);
#endif
#ifdef CONFIG_METADATA