diff --git a/.github/workflows/docker-on-push-tag-or-pr.yaml b/.github/workflows/docker-on-push-tag-or-pr.yaml index 11129949..c3380f4e 100644 --- a/.github/workflows/docker-on-push-tag-or-pr.yaml +++ b/.github/workflows/docker-on-push-tag-or-pr.yaml @@ -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: diff --git a/FFTConvolver/ConvolverThreadPool.cpp b/FFTConvolver/ConvolverThreadPool.cpp index 8287417e..ef969572 100644 --- a/FFTConvolver/ConvolverThreadPool.cpp +++ b/FFTConvolver/ConvolverThreadPool.cpp @@ -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 diff --git a/FFTConvolver/convolver.cpp b/FFTConvolver/convolver.cpp index 16eba015..c276d778 100644 --- a/FFTConvolver/convolver.cpp +++ b/FFTConvolver/convolver.cpp @@ -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"); } } diff --git a/ap2_buffered_audio_processor.c b/ap2_buffered_audio_processor.c index 30f3b00f..09581ded 100644 --- a/ap2_buffered_audio_processor.c +++ b/ap2_buffered_audio_processor.c @@ -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 } } } diff --git a/audio.c b/audio.c index 7c08de00..a703d660 100644 --- a/audio.c +++ b/audio.c @@ -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]; diff --git a/common.c b/common.c index e552af4c..5b3f8feb 100644 --- a/common.c +++ b/common.c @@ -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. diff --git a/common.h b/common.h index bf472829..7e35e6ab 100644 --- a/common.h +++ b/common.h @@ -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); diff --git a/configure.ac b/configure.ac index f354846b..501b6073 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/dbus-service.c b/dbus-service.c index 15d02fa0..c6b63251 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -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; } diff --git a/player.c b/player.c index e15de80e..e6c93f8c 100644 --- a/player.c +++ b/player.c @@ -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; diff --git a/rtp.c b/rtp.c index 79032ede..02e7c167 100644 --- a/rtp.c +++ b/rtp.c @@ -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; diff --git a/rtsp.c b/rtsp.c index 7da2ffaf..68ad798c 100644 --- a/rtsp.c +++ b/rtsp.c @@ -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); diff --git a/shairport.c b/shairport.c index 448f17e6..d58f2743 100644 --- a/shairport.c +++ b/shairport.c @@ -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 - // debug(1, "soxr_time_check PID %d", syscall(SYS_gettid)); + // #include + // 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