Merge updates from the development branch into the master branch as 4.2.1
This commit is contained in:
@@ -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,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,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:
|
||||
|
||||
+47
-2
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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])
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user