This commit is contained in:
Mike Brady
2026-04-25 11:22:18 +01:00
13 changed files with 213 additions and 74 deletions
@@ -59,7 +59,7 @@ jobs:
fi
- name: Login to Docker Registry
uses: docker/login-action@v4.0.0
uses: docker/login-action@v4.1.0
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_REGISTRY_USER }}
@@ -73,7 +73,7 @@ jobs:
uses: docker/setup-buildx-action@v4.0.0
- name: Build and push ${{ matrix.name }}
uses: docker/build-push-action@v7.0.0
uses: docker/build-push-action@v7.1.0
env:
registry_and_name: ${{ secrets.DOCKER_REGISTRY }}/${{ secrets.DOCKER_IMAGE_NAME }}
with:
+41
View File
@@ -1,7 +1,37 @@
/*
* Convolver Thread Pool. This file is part of Shairport Sync
* Copyright (c) Mike Brady 2026
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "ConvolverThreadPool.h"
#include "FFTConvolver.h"
#include "config.h"
extern "C" void _debug(const char *filename, const int linenumber, int level, const char *format,
...);
#define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__)
ConvolverThreadPool::ConvolverThreadPool()
: _convolvers(), _threads(), _taskQueue(), _queueMutex(), _condition(), _completionCV(),
_stop(false), _activeTasks(0) {}
@@ -105,6 +135,16 @@ void ConvolverThreadPool::waitForAll() {
_completionCV.wait(lock, [this]() { return _taskQueue.empty() && _activeTasks == 0; });
}
void ConvolverThreadPool::clearState(size_t convolverId) {
// Do the replacement assertion check first, and then wait for all tasks to stop
if (convolverId < _convolvers.size()) {
waitForAll();
} else {
debug(1, "assert(convolverId < _convolvers.size()) failed, with convolverId: %u and _convolvers.size(): %u.", convolverId, _convolvers.size());
}
}
/* this is the old version
void ConvolverThreadPool::clearState(size_t convolverId) {
assert(convolverId < _convolvers.size());
@@ -113,6 +153,7 @@ void ConvolverThreadPool::clearState(size_t convolverId) {
// _convolvers[convolverId]->clearState();
}
*/
void ConvolverThreadPool::clearAllStates() {
// Make sure no tasks are running before clearing state
+27 -3
View File
@@ -1,4 +1,28 @@
/*
* Convolver. This file is part of Shairport Sync
* Copyright (c) Mike Brady 2026
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "convolver.h"
#include "ConvolverThreadPool.h"
@@ -22,8 +46,8 @@ void convolver_pool_init(size_t numThreads, size_t numConvolvers) {
if (!pool.init(numThreads, numConvolvers)) {
debug(1, "failed to initialize thread pool!");
} else {
debug(1, "thread pool initialized with %u threads and %u convolvers.", numThreads,
numConvolvers);
debug(1, "thread pool initialized with %u thread%s and %u convolver%s.", numThreads, numThreads == 1 ? "" : "s",
numConvolvers, numConvolvers == 1 ? "" : "s");
}
}
+33 -8
View File
@@ -235,6 +235,7 @@ void *rtp_buffered_audio_processor(void *arg) {
uint16_t data_len;
// here we read from the buffer that our thread has been reading
// debug(1,"read a block");
size_t bytes_remaining_in_buffer;
nread =
read_sized_block(buffered_audio, &data_len, sizeof(data_len), &bytes_remaining_in_buffer);
@@ -249,6 +250,7 @@ void *rtp_buffered_audio_processor(void *arg) {
// get the block itself
// debug(1,"buffered audio packet of size %u detected.", data_len - 2);
nread = read_sized_block(buffered_audio, packet, data_len - 2, &bytes_remaining_in_buffer);
// debug(1,"block read");
// diagnostic
if ((conn->ap2_audio_buffer_minimum_size < 0) ||
@@ -442,7 +444,7 @@ void *rtp_buffered_audio_processor(void *arg) {
// to decode it and pass it to the player
if (new_audio_block_needed == 0) {
// is there space in the player thread's buffer system?
size_t player_buffer_occupancy = get_audio_buffer_occupancy(conn);
// size_t player_buffer_occupancy = get_audio_buffer_occupancy(conn);
// debug(1,"player buffer size and occupancy: %u and %u", player_buffer_size,
// player_buffer_occupancy);
@@ -451,17 +453,41 @@ void *rtp_buffered_audio_processor(void *arg) {
// and send it to the player. Otherwise, keep the block and sleep for a while.
// calculate if there is room in the decoded audio buffer...
int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <= config.audio_decoded_buffer_desired_length;
// debug(1, "frames buffered: %f seconds, desired length: %f seconds.", (1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate, config.audio_decoded_buffer_desired_length);
// int audio_decoded_buffer_below_desired_length = ((1.0 * player_buffer_occupancy * conn->frames_per_packet) / conn->input_rate) <= config.audio_decoded_buffer_desired_length;
uint64_t buffer_should_be_time;
int have_valid_time = (frame_to_local_time(timestamp, &buffer_should_be_time, conn) == 0);
// calculate the lead time to make sure it's not too early...
int64_t lead_time = buffer_should_be_time - get_absolute_time_in_ns();
if ((play_enabled != 0) && (have_valid_time != 0) &&
(audio_decoded_buffer_below_desired_length != 0) &&
(lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))) {
// debug(1,"play_enabled: %d, have_valid_time: %d, audio_decoded_buffer_below_desired_length: %d, lead_time * 1E-9: %f, (config.audio_decoded_buffer_desired_length + 0.1): %f, player_buffer_occupancy: %zu",
// play_enabled, have_valid_time, audio_decoded_buffer_below_desired_length, lead_time * 1E-9, (config.audio_decoded_buffer_desired_length + 0.1), player_buffer_occupancy
// );
// A slight problem here is that counting the number of buffers may not be sufficient,
// because the actual device may be
// taking data in large quantities at a single time.
// So we just have to ensure that there
// is enough of a lead time maintained for sufficient audio to be available to prevent
// the device from under-running.
// If means that the Shairport Sync player might riun out of audio occasionally, but
// as long as the device has enough in its buffer, everything is fine.
// But it also means that Shairport Sync's buffers must be sufficient to hold all the
// entire lead-time's amount of audio in case the device has a zero-sized buffer.
if ((play_enabled != 0) && (have_valid_time != 0)
// (audio_decoded_buffer_below_desired_length != 0) &&
&& (lead_time * 1E-9 < (config.audio_decoded_buffer_desired_length + 0.1))
// && (audio_decoded_buffer_below_desired_length != 0)
) {
very_early_packets_signalled = 0; //reset very early packet warning signaller
@@ -639,8 +665,7 @@ void *rtp_buffered_audio_processor(void *arg) {
debug(1, "incoming frame suddenly (?) has a lead time of %f seconds, with a desired decoded buffer length of %f.", 1.0 * lead_time * 1E-9, config.audio_decoded_buffer_desired_length);
very_early_packets_signalled = 1;
}
usleep(20000); // wait for a while
usleep(((1000000 * conn->frames_per_packet) / conn->input_rate) * 2); // wait for approximately the length of two packets
}
}
}
+2 -2
View File
@@ -985,7 +985,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra
}
#ifdef CONFIG_FFMPEG
if (local_format == SPS_FORMAT_UNKNOWN) {
debug(3, "check for an next highest rate above %u with %u channels.", rate,
debug(3, "check for the next highest rate above %u with %u channels.", rate,
local_channels);
unsigned int rate_pointer = 0;
while ((rate_pointer < sizeof(rates) / sizeof(unsigned int)) &&
@@ -1003,7 +1003,7 @@ int32_t search_for_suitable_configuration(unsigned int channels, unsigned int ra
if (local_format == SPS_FORMAT_UNKNOWN) {
int rate_pointer = (int)(sizeof(rates) / sizeof(unsigned int) - 1);
debug(3, "check for an next lowest rate below %u with %u channels.", rate,
debug(3, "check for the next lowest rate below %u with %u channels.", rate,
local_channels);
while ((rate_pointer >= 0) && (local_format == SPS_FORMAT_UNKNOWN)) {
local_rate = rates[rate_pointer];
+35 -18
View File
@@ -1041,32 +1041,49 @@ uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
#endif
int config_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value) {
int response = config_lookup_string(cfg, path, value);
if (response == CONFIG_TRUE) {
if ((value != NULL) && ((*value == NULL) || (*value[0] == 0))) {
warn("The \"%s\" parameter is an empty string and has been ignored.", path);
response = CONFIG_FALSE;
int response = CONFIG_FALSE;
config_setting_t *s = config_lookup(cfg, path);
if (s != NULL) {
// the setting exists, but might not be a string
if (config_setting_type(s) == CONFIG_TYPE_STRING) {
if (value != NULL) {
*value = config_setting_get_string(s);
response = CONFIG_TRUE;
// the string might be empty...
if ((*value == NULL) || (*value[0] == 0)) {
warn("The \"%s\" parameter is an empty string and has been ignored.", path);
response = CONFIG_FALSE;
}
}
} else {
warn("the \"%s\" parameter is not a string, as required, and has been ignored.", path);
}
}
return response;
}
int config_set_lookup_bool(config_t *cfg, char *where, int *dst) {
const char *str = 0;
if (config_lookup_string(cfg, where, &str)) {
if (strcasecmp(str, "no") == 0) {
(*dst) = 0;
return 1;
} else if (strcasecmp(str, "yes") == 0) {
(*dst) = 1;
return 1;
int config_set_lookup_bool(config_t *cfg, const char *where, int *dst) {
const char *str = NULL;
int response = CONFIG_FALSE;
config_setting_t *s = config_lookup(cfg, where);
if (s != NULL) {
if (config_setting_type(s) == CONFIG_TYPE_STRING) {
str = config_setting_get_string(s);
if (strcasecmp(str, "no") == 0) {
(*dst) = 0;
response = CONFIG_TRUE;
} else if (strcasecmp(str, "yes") == 0) {
(*dst) = 1;
response = CONFIG_TRUE;
} else {
die("invalid boolean parameter \"%s\" option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
return 0;
}
} else {
die("Invalid %s option choice \"%s\". It should be \"yes\" or \"no\"", where, str);
return 0;
warn("the \"%s\" parameter is not a string with a value of \"yes\" or \"no\", as required, and has been ignored.", where);
}
} else {
return 0;
}
return response;
}
// remember to free the returned array of strings.
+1 -1
View File
@@ -561,7 +561,7 @@ extern int type_of_exit_cleanup; // normal, emergency, dbus requested...
extern uint64_t minimum_dac_queue_size;
int config_lookup_non_empty_string(const config_t *cfg, const char *path, const char **value);
int config_set_lookup_bool(config_t *cfg, char *where, int *dst);
int config_set_lookup_bool(config_t *cfg, const char *where, int *dst);
int check_string_or_list_setting(config_setting_t *setting, const char *item);
int check_int_or_list_setting(config_setting_t *setting, const int item);
+14 -17
View File
@@ -1,7 +1,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.50])
AC_INIT([shairport-sync], [5.0.2], [4265913+mikebrady@users.noreply.github.com])
AC_INIT([shairport-sync], [5.0.3], [4265913+mikebrady@users.noreply.github.com])
: ${CFLAGS="-O3"}
: ${CXXFLAGS="-O3"}
AM_INIT_AUTOMAKE([subdir-objects])
@@ -228,8 +228,8 @@ if test "x$with_avahi" = "xyes" ; then
AC_DEFINE([CONFIG_AVAHI], 1, [Include Avahi-based mDNS support.])
AC_CHECK_LIB([avahi-client], [avahi_client_new], , AC_MSG_ERROR(Avahi support requires the avahi-client library!))
AC_CHECK_LIB([avahi-common],[avahi_strerror], , AC_MSG_ERROR(Avahi support requires the avahi-common library!))
systemd_after_args="${systemd_after_args} avahi-daemon.service"
systemd_requires_args="${systemd_requires_args} avahi-daemon.service"
systemd_after_args="${systemd_after_args}${systemd_after_args:+ }avahi-daemon.service"
systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }avahi-daemon.service"
fi
AM_CONDITIONAL([USE_AVAHI], [test "x$with_avahi" = "xyes"])
@@ -412,6 +412,7 @@ AM_CONDITIONAL([USE_METADATA], [test "x$with_metadata" = "xyes"])
# Look for AirPlay 2 flag
AC_ARG_WITH(airplay-2, [AS_HELP_STRING([--with-airplay-2],[Build for AirPlay 2])])
if test "x$with_airplay_2" = "xyes" ; then
using_ffmpeg=true
AC_DEFINE([CONFIG_AIRPLAY_2], 1, [Build for AirPlay 2])
AC_CHECK_PROG(PLISTUTIL_CHECK,plistutil,yes)
AS_IF([test x"$PLISTUTIL_CHECK" != x"yes"], [AC_MSG_ERROR([plistutil can not be found. Please install plistutil for building for AirPlay 2. Maybe it's in the apt package libplist-utils.])])
@@ -443,6 +444,8 @@ if test "x$with_airplay_2" = "xyes" ; then
)
])
AC_CHECK_LIB([uuid],[uuid_generate], [], [AC_MSG_ERROR([AirPlay 2 support requires the uuid library -- uuid-dev suggested])])
systemd_after_args="${systemd_after_args}${systemd_after_args:+ }nqptp.service"
systemd_requires_args="${systemd_requires_args}${systemd_requires_args:+ }nqptp.service"
fi
AM_CONDITIONAL([USE_AIRPLAY_2], [test "x$with_airplay_2" = "xyes"])
@@ -455,7 +458,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
PKG_CHECK_MODULES(
[libavutil], [libavutil],
[CFLAGS="${libavutil_CFLAGS} ${CFLAGS}" LIBS="${libavutil_LIBS} ${LIBS}"],
[AC_MSG_ERROR(AirPlay 2 support requires libavutil -- libavutil-dev suggested)]
[AC_MSG_ERROR(FFmpeg requires libavutil -- libavutil-dev suggested)]
)
PKG_CHECK_MODULES(
[libavcodec], [libavcodec],
@@ -463,7 +466,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
[AC_CHECK_LIB(
[avcodec], [avcodec_find_decoder],
[],
[AC_MSG_ERROR(AirPlay 2 support requires libavcodec -- libavcodec-dev suggested)]
[AC_MSG_ERROR(FFmpeg requires libavcodec -- libavcodec-dev suggested)]
)]
)
PKG_CHECK_MODULES(
@@ -472,7 +475,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
[AC_CHECK_LIB(
[avformat], [avformat_new_stream],
[],
[AC_MSG_ERROR([AirPlay 2 support requires libavformat -- libavformat-dev suggested])]
[AC_MSG_ERROR([FFmpeg requires libavformat -- libavformat-dev suggested])]
)]
)
PKG_CHECK_MODULES(
@@ -481,16 +484,7 @@ if test "x$with_ffmpeg" = "xyes" -o "x$with_airplay_2" = "xyes" ; then
[AC_CHECK_LIB(
[swresample], [swr_convert],
[],
[AC_MSG_ERROR([AirPlay 2 support requires libswresample -- libswresample-dev suggested])]
)]
)
PKG_CHECK_MODULES(
[libuuid], [uuid],
[CFLAGS="${libuuid_CFLAGS} ${CFLAGS}" LIBS="${libuuid_LIBS} ${LIBS}"],
[AC_CHECK_LIB(
[uuid], [uuid_generate],
[],
[AC_MSG_ERROR([AirPlay 2 support requires the uuid library -- uuid-dev suggested])]
[AC_MSG_ERROR([FFmpeg requires libswresample -- libswresample-dev suggested])]
)]
)
else
@@ -505,8 +499,11 @@ AM_CONDITIONAL([USE_HAMMERTON], [test $using_ffmpeg = false])
# Look for Apple ALAC flag
AC_ARG_WITH(apple-alac,[AS_HELP_STRING([--with-apple-alac],[include support for the Apple ALAC decoder (deprecated)])])
if test "x${with_apple_alac}" = "xyes" ; then
if test "x${with_airplay_2}" = "xyes" ; then
AC_MSG_ERROR([the Apple ALAC Decoder (deprecated) can not be used in AirPlay 2.])
fi
if test $using_ffmpeg = true ; then
AC_MSG_RESULT([Note: the FFMPEG decoder, included in the build, supercedes the Apple ALAC decoder, which is deprecated. Consider omitting the Apple ALAC decoder.])
AC_MSG_WARN([the FFMPEG decoder, included in the build, supercedes the Apple ALAC Decoder, which is deprecated. Consider omitting the Apple ALAC Decoder.])
fi
AC_DEFINE([CONFIG_APPLE_ALAC], 1, [Include support for using the Apple ALAC Decoder (deprecated)])
if test "x${with_pkg_config}" = xyes ; then
+8 -1
View File
@@ -737,6 +737,12 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
__attribute__((unused)) gpointer user_data) {
char *th = (char *)shairport_sync_get_alacdecoder(skeleton);
#ifdef CONFIG_AIRPLAY_2
if (strcasecmp(th, "ffmpeg") != 0) {
warn(" This request, to set the decoder to \"%s\", is ignored. For AirPlay 2, the FFmpeg decoder is always used.",
th);
}
#else
if ((strcasecmp(th, "hammerton") == 0) &&
((config.decoders_supported & (1 << decoder_hammerton)) != 0))
config.decoder_in_use = 1 << decoder_hammerton;
@@ -747,12 +753,13 @@ gboolean notify_alacdecoder_callback(ShairportSync *skeleton,
((config.decoders_supported & (1 << decoder_ffmpeg_alac)) != 0))
config.decoder_in_use = 1 << decoder_ffmpeg_alac;
else {
warn("An unrecognised or unsupported ALAC decoder: \"%s\" was requested via D-Bus interface. "
warn("An unrecognised or unsupported decoder: \"%s\" was requested via D-Bus interface. "
"(Possibly "
"support for this decoder was not compiled "
"into this version of Shairport Sync.)",
th);
}
#endif
return TRUE;
}
+1 -1
View File
@@ -2687,7 +2687,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn, int resync_requested) {
// wait if the buffer is empty
if ((conn->ab_synced != 0) && (conn->ab_read == conn->ab_write)) { // the buffer is empty!
if (notified_buffer_empty == 0) {
debug(2, "Connection %d: Buffer Empty", conn->connection_number);
debug(4, "Connection %d: Buffer Empty", conn->connection_number);
notified_buffer_empty = 1;
// reset_input_flow_metrics(conn); // don't do a full flush parameters reset
// conn->initial_reference_time = 0;
+6 -3
View File
@@ -1513,9 +1513,12 @@ int frame_to_ptp_local_time(uint32_t timestamp, uint64_t *time, rtsp_conn_info *
int32_t frame_difference = timestamp - anchor_rtptime;
int64_t time_difference = frame_difference;
time_difference = time_difference * 1000000000;
if (conn->input_rate == 0)
die("conn->input_rate is zero!");
time_difference = time_difference / conn->input_rate;
if (conn->input_rate == 0) {
debug(1,"in a call to frame_to_ptp_local_time, conn->input_rate is zero!");
time_difference = 0;
} else {
time_difference = time_difference / conn->input_rate;
}
uint64_t ltime = anchor_local_time + time_difference;
*time = ltime;
result = 0;
+3
View File
@@ -2828,6 +2828,8 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
plist_array_append_item(addresses, plist_new_string(conn->self_ip_string));
// debug(1,"self ip: \"%s\"", conn->self_ip_string);
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
struct ifaddrs *addrs, *iap;
getifaddrs(&addrs);
for (iap = addrs; iap != NULL; iap = iap->ifa_next) {
@@ -2862,6 +2864,7 @@ void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp)
}
}
freeifaddrs(addrs);
pthread_setcancelstate(oldState, NULL);
// debug(1,"initial timing peer command: \"%s\".", timing_list_message);
// ptp_send_control_message_string(timing_list_message);
+40 -18
View File
@@ -195,13 +195,12 @@ int has_fltp_capable_aac_decoder(void) {
#endif
#ifdef CONFIG_SOXR
pthread_t soxr_time_check_thread;
int soxr_time_check_thread_started = 0;
pthread_t *soxr_time_check_thread = NULL;
void *soxr_time_check(__attribute__((unused)) void *arg) {
// this just checks how long it takes to process adding and subtracing a frame
// from a buffer at 44100
// #include <syscall.h>
// debug(1, "soxr_time_check PID %d", syscall(SYS_gettid));
// #include <syscall.h>
// debug(1, "soxr_time_check PID %ld", syscall(SYS_gettid));
const int buffer_length = 352;
int32_t inbuffer[buffer_length * 2];
@@ -221,7 +220,7 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
(uint64_t)1500000000 + soxr_start_time; // loop for a second and a half, max -- no need to be
// able to cancel it, do _don't even try_!
while (get_absolute_time_in_ns() < loop_until_time) {
pthread_testcancel();
number_of_iterations++;
for (i = 0; i < buffer_length; i++) {
double w = sin(i * (frequency + number_of_iterations * 2) * 2 * M_PI / 44100);
@@ -230,6 +229,9 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
inbuffer[i * 2 + 1] = wint;
}
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
soxr_io_spec_t io_spec;
io_spec.itype = SOXR_INT32_I;
io_spec.otype = SOXR_INT32_I;
@@ -256,6 +258,8 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
outbuffer, buffer_length - 1, &odone, // Output.
&io_spec, // Input, output and transfer spec.
NULL, NULL); // Default configuration.
pthread_setcancelstate(oldState, NULL);
}
int64_t soxr_execution_time =
@@ -271,8 +275,8 @@ void *soxr_time_check(__attribute__((unused)) void *arg) {
debug(1, "No soxr-timing iterations performed, so \"vernier\" iteration will be used.");
config.soxr_delay_index = 0; // used as a flag
}
debug(2, "soxr_delay: %d nanoseconds, soxr_delay_threshold: %d milliseconds.",
config.soxr_delay_index, config.soxr_delay_threshold / 1000000);
debug(2, "soxr_delay maximum permissible: %d milliseconds, actual: %f milliseconds.",
config.soxr_delay_threshold / 1000000, 1E-6 * config.soxr_delay_index);
if ((config.packet_stuffing == ST_soxr) &&
(config.soxr_delay_index > config.soxr_delay_threshold))
inform("Note: this device may be too slow for \"soxr\" interpolation. Consider choosing the "
@@ -1856,7 +1860,6 @@ void exit_function() {
(config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not
// actually daemonised at all
#endif
debug(2, "exit function called...");
/*
Actually, there is no terminate_mqtt() function.
#ifdef CONFIG_MQTT
@@ -1902,12 +1905,16 @@ void exit_function() {
}
#ifdef CONFIG_SOXR
// be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down
if (soxr_time_check_thread_started != 0) {
debug(2, "Waiting for SoXr timecheck to terminate...");
pthread_join(soxr_time_check_thread, NULL);
soxr_time_check_thread_started = 0;
debug(2, "Waiting for SoXr timecheck to terminate done");
{
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
if (soxr_time_check_thread != NULL) {
pthread_cancel(*soxr_time_check_thread);
pthread_join(*soxr_time_check_thread, NULL);
free(soxr_time_check_thread);
soxr_time_check_thread = NULL;
}
pthread_setcancelstate(oldState, NULL);
}
#endif
@@ -1994,7 +2001,7 @@ void exit_function() {
#else
mdns_unregister(); // once the dacp handler is done and all player threrads are done it should
// be safe
debug(1, "normal exit");
debug(2, "normal exit");
#endif
} else {
debug(1, "emergency exit");
@@ -2346,6 +2353,7 @@ int main(int argc, char **argv) {
openlog(NULL, 0, LOG_DAEMON);
#endif
type_of_exit_cleanup = TOE_normal; // what kind of exit cleanup needed
debug(1, "adding the exit function");
atexit(exit_function);
// get a device id -- the first non-local MAC address
@@ -2957,7 +2965,9 @@ int main(int argc, char **argv) {
config.output_channel_map[i] = strdup(channel_id);
debug(2, "output channel %d is \"%s\".", i, config.output_channel_map[i]);
} else {
warn("channel \"%s\" is not recognised -- output channel %d will be silent.",
warn("during channel mapping, \"%s\" was not recognised as a channel name -- as a result, output channel %d will be silent.",
channel_id, i);
config.output_channel_map[i] = strdup("--");
}
@@ -3153,8 +3163,20 @@ int main(int argc, char **argv) {
debug(option_print_level, "loudness reference level is %f", config.loudness_reference_volume_db);
#ifdef CONFIG_SOXR
named_pthread_create(&soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker");
soxr_time_check_thread_started = 1;
{
int oldState;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
soxr_time_check_thread = malloc(sizeof(pthread_t));
if (soxr_time_check_thread != NULL) {
named_pthread_create(soxr_time_check_thread, NULL, &soxr_time_check, NULL, "soxr_checker");
} else {
debug(1,"couldn't get memory to start the soxr_checker");
}
pthread_setcancelstate(oldState, NULL); // make this un-cancellable
}
#endif
#ifdef CONFIG_FFMPEG