From 77a5cf1e80c05065b210467752c9b506bee87caf Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 13 Jul 2020 10:47:10 +0100 Subject: [PATCH 01/36] Ensure metadata and cover art are enabled if metadata support is included at compilation. Reword some misleading error messages, set convolution defaults even if no configuration file is found. --- dacp.c | 6 ++++-- metadata_hub.c | 4 ++-- shairport.c | 19 +++++++++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/dacp.c b/dacp.c index c6b9968f..a5d0d659 100644 --- a/dacp.c +++ b/dacp.c @@ -154,7 +154,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"dacp_send_command: command is: \"%s\".",command); if (dacp_server.port == 0) { - debug(3, "No DACP port specified yet"); + // debug(3, "No DACP port specified yet"); result = 490; // no port specified } else { @@ -504,11 +504,13 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) { (metadata_store.advanced_dacp_server_active != 0); metadata_store.dacp_server_active = 0; metadata_store.advanced_dacp_server_active = 0; + /* debug(3, "setting metadata_store.dacp_server_active and " "metadata_store.advanced_dacp_server_active to 0 with an update " "flag value of %d", ch); + */ metadata_hub_modify_epilog(ch); uint64_t time_to_wait_for_wakeup_ns = @@ -1249,7 +1251,7 @@ int dacp_get_volume(int32_t *the_actual_volume) { } else { debug(1, "Unexpected return code %d from dacp_get_speaker_list.", http_response); } - } else if (http_response != 400) { + } else if ((http_response != 400) && (http_response != 490)) { debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response); } if (the_actual_volume) { diff --git a/metadata_hub.c b/metadata_hub.c index 190895c0..7b20ae18 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -148,7 +148,7 @@ void _metadata_hub_modify_prolog(const char *filename, const int linenumber) { } last_metadata_hub_modify_prolog_file = strdup(filename); last_metadata_hub_modify_prolog_line = linenumber; - debug(3, "Metadata_hub write lock acquired."); + // debug(3, "Metadata_hub write lock acquired."); } metadata_hub_re_lock_access_is_delayed = 0; } @@ -169,7 +169,7 @@ void _metadata_hub_modify_epilog(int modified, const char *filename, const int l } } pthread_rwlock_unlock(&metadata_hub_re_lock); - debug(3, "Metadata_hub write lock unlocked."); + // debug(3, "Metadata_hub write lock unlocked."); } /* diff --git a/shairport.c b/shairport.c index 14e50725..975b71fc 100644 --- a/shairport.c +++ b/shairport.c @@ -266,7 +266,7 @@ void usage(char *progname) { printf(" --metadata-pipename=PIPE send metadata to PIPE, e.g. " "--metadata-pipename=/tmp/shairport-sync-metadata.\n"); printf(" The default is /tmp/shairport-sync-metadata.\n"); - printf(" --get-coverart send cover art through the metadata pipe.\n"); + printf(" -g, --get-coverart send cover art through the metadata pipe.\n"); #endif printf(" -u, --use-stderr log messages through STDERR rather than the system log.\n"); printf("\n"); @@ -418,6 +418,17 @@ int parse_options(int argc, char **argv) { // seconds then we can add an offset of 5.17 seconds and still leave a second's worth of buffers // for unexpected circumstances +#ifdef CONFIG_METADATA + /* Get the metadata setting. */ + config.metadata_enabled = 1; // if metadata support is included, then enable it by default + config.get_coverart = 1; // if metadata support is included, then enable it by default +#endif + +#ifdef CONFIG_CONVOLUTION + config.convolution_max_length = 8192; +#endif + config.loudness_reference_volume_db = -20; + #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; config.scan_interval_when_active = @@ -846,7 +857,6 @@ int parse_options(int argc, char **argv) { #ifdef CONFIG_METADATA /* Get the metadata setting. */ - config.metadata_enabled = 1; // if metadata support is included, then enable it by default if (config_lookup_string(config.cfg, "metadata.enabled", &str)) { if (strcasecmp(str, "no") == 0) config.metadata_enabled = 0; @@ -856,7 +866,6 @@ int parse_options(int argc, char **argv) { die("Invalid metadata enabled option choice \"%s\". It should be \"yes\" or \"no\""); } - config.get_coverart = 1; // if metadata support is included, then enable it by default if (config_lookup_string(config.cfg, "metadata.include_cover_art", &str)) { if (strcasecmp(str, "no") == 0) config.get_coverart = 0; @@ -988,7 +997,6 @@ int parse_options(int argc, char **argv) { dvalue); } - config.convolution_max_length = 8192; if (config_lookup_int(config.cfg, "dsp.convolution_max_length", &value)) { config.convolution_max_length = value; @@ -1015,7 +1023,6 @@ int parse_options(int argc, char **argv) { die("Invalid dsp.convolution. It should be \"yes\" or \"no\""); } - config.loudness_reference_volume_db = -20; if (config_lookup_float(config.cfg, "dsp.loudness_reference_volume_db", &dvalue)) { config.loudness_reference_volume_db = dvalue; if (dvalue > 0 || dvalue < -100) @@ -1168,7 +1175,7 @@ int parse_options(int argc, char **argv) { break; case 'g': if (config.metadata_enabled == 0) - die("If you want to get cover art, you must also select the --metadata-pipename option."); + die("If you want to get cover art, ensure metadata_enabled is true."); break; #endif case 'S': From 18616a97cdb56b0114d3dadc5fa6e93d598fd9df Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 13 Jul 2020 10:57:52 +0100 Subject: [PATCH 02/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 4814b7db..1c7aa8a4 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,5 +1,19 @@ +Version 3.3.7d14 +==== +**Enhancements** +* Fixed a situation where a misleading error message was given if a configuration file cound not be located and `get_coverart` was selected as a command line option. Apart from a misleading message, `get_coverart` and `enable_metadata` should have been enabled automatically where metadata support is included in the build configuration. + +Version 3.3.7d13 +==== +This is 3.3.7rc1 + +**Enhancements** +* Fixed another bug, related to the bug fixed in 3.3.7d4, where leading zeros were not removed from the DACP ID. Thanks again to [julianc1969](https://github.com/juliandc1969). + Version 3.3.7d12 ==== +This is 3.3.7rc0 + **Enhancements** * Added the the string `-alac` to the the version string in '-V' if Apple ALAC decoder support is included. From 9a390afcd69b34433a1d9a6924cf6a7ec415e101 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 13 Jul 2020 10:59:00 +0100 Subject: [PATCH 03/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 1c7aa8a4..7b131129 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,7 +1,7 @@ Version 3.3.7d14 ==== **Enhancements** -* Fixed a situation where a misleading error message was given if a configuration file cound not be located and `get_coverart` was selected as a command line option. Apart from a misleading message, `get_coverart` and `enable_metadata` should have been enabled automatically where metadata support is included in the build configuration. +* Fixed a situation where a misleading error message was given if a configuration file cound not be located and `get_coverart` was selected as a command line option. Apart from a misleading message, `get_coverart` and `enable_metadata` should have been enabled automatically where metadata support is included in the build configuration. Thanks to [Craig Fletcher](https://github.com/leakypixel) for the bug report. Version 3.3.7d13 ==== From d3365f8bdcae1963d6eabf993adc90b1223920ee Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 13 Jul 2020 11:52:52 +0100 Subject: [PATCH 04/36] Quieten a debug message --- dacp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dacp.c b/dacp.c index a5d0d659..a25bf9e4 100644 --- a/dacp.c +++ b/dacp.c @@ -1249,7 +1249,7 @@ int dacp_get_volume(int32_t *the_actual_volume) { // metadata_store.speaker_volume = actual_volume; // metadata_hub_modify_epilog(1); } else { - debug(1, "Unexpected return code %d from dacp_get_speaker_list.", http_response); + debug(2, "Unexpected return code %d from dacp_get_speaker_list.", http_response); } } else if ((http_response != 400) && (http_response != 490)) { debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response); From 69d3b8d4a9ec7782f6d10b351116ce1064034790 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 13 Jul 2020 11:53:54 +0100 Subject: [PATCH 05/36] Ensure metadata and cover art are enabled if metadata support is included at compilation. Reword some misleading error messages, set convolution defaults even if no configuration file is found. Quieten a few debug messages. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 182d3303..215148cd 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d13], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.7d14], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 06c80713bb4bdc97eda1f1d62859a2dc3dc61a17 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 27 Jul 2020 09:40:05 +0100 Subject: [PATCH 06/36] Open metadata and audio pipes with 666 permissions. --- audio_pipe.c | 2 +- rtsp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_pipe.c b/audio_pipe.c index 6dedc464..45e0f060 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -121,7 +121,7 @@ static int init(int argc, char **argv) { // here, create the pipe mode_t oldumask = umask(000); - if (mkfifo(pipename, 0644) && errno != EEXIST) + if (mkfifo(pipename, 0666) && errno != EEXIST) die("Could not create audio pipe \"%s\"", pipename); umask(oldumask); diff --git a/rtsp.c b/rtsp.c index 2febacd0..1f1cb9f9 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1572,7 +1572,7 @@ void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { snprintf(path, pl + 1, "%s", config.metadata_pipename); mode_t oldumask = umask(000); - if (mkfifo(path, 0644) && errno != EEXIST) + if (mkfifo(path, 0666) && errno != EEXIST) die("Could not create metadata pipe \"%s\".", path); umask(oldumask); debug(1, "metadata pipe name is \"%s\".", path); From 7e2b8561218d0c71670684909f4c8bb8a946644d Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 27 Jul 2020 09:41:33 +0100 Subject: [PATCH 07/36] Open metadata and audio pipes with 666 permissions. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 215148cd..67d0a31d 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d14], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.7d15], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From b6cb19be176c763bd8cf4a741d5ba393683392d1 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 27 Jul 2020 09:46:07 +0100 Subject: [PATCH 08/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 7b131129..49829939 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,8 @@ +Version 3.3.7d15 +==== +**Enhancements** +* Changed permissions when creating the metadata pipe (and the audio pipe in the `pipe` backend) to `rw-rw-rw-` for compatibility with `snapserver`. + Version 3.3.7d14 ==== **Enhancements** From 65391a6916c1ce72d90d56bbc7da3982ae6551b5 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 13 Aug 2020 17:44:35 +0100 Subject: [PATCH 09/36] Potentially fix a flaw in resyncing. A flush is set up but not triggered. --- player.c | 1 + 1 file changed, 1 insertion(+) diff --git a/player.c b/player.c index dcef6c4e..d725ed9c 100644 --- a/player.c +++ b/player.c @@ -2320,6 +2320,7 @@ void *player_thread_func(void *arg) { uint32_t frames_to_drop_sized = local_frames_to_drop; debug_mutex_lock(&conn->flush_mutex, 1000, 1); + conn->flush_requested = 1; conn->flush_rtp_timestamp = inframe->given_timestamp + frames_to_drop_sized; // flush all packets up to (and including?) this From 42ea538a6279d4e295cf7c99f79a8e7197aad286 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 13 Aug 2020 19:35:31 +0100 Subject: [PATCH 10/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 49829939..db018d0d 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,8 @@ +Version 3.3.7d16 +==== +**Bug Fix** +* Fix potential inability to restore synchrnoisation quickly in certain situations. + Version 3.3.7d15 ==== **Enhancements** From ad1015788a9af5eb85b6dfb0ff9ca2446c28a020 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 13 Aug 2020 19:38:15 +0100 Subject: [PATCH 11/36] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 67d0a31d..9f64310f 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d15], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.7d17], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 5ddb80e0db201eedfbe0a80217283ba86b984955 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 13 Aug 2020 19:38:40 +0100 Subject: [PATCH 12/36] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 9f64310f..72fe895c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d17], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.7d16], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 919e4d6ac1bf7fcbe8d2b57cab9f413aced69f2f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 13 Aug 2020 20:41:30 +0100 Subject: [PATCH 13/36] A gentler flush process for resyncing. --- player.c | 119 +++++++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/player.c b/player.c index d725ed9c..edca685f 100644 --- a/player.c +++ b/player.c @@ -950,71 +950,71 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug(2, "flush request: flush output device."); } conn->flush_output_flushed = 1; - // now check to see it the flush request is for frames in the buffer or not - // if the first_packet_timestamp is zero, don't check - int flush_needed = 0; - int drop_request = 0; - if (conn->flush_rtp_timestamp == 0) { - debug(1, "flush request: flush frame 0 -- flush assumed to be needed."); - flush_needed = 1; - drop_request = 1; - } else { - if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) { - abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read); - abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1); - if ((firstPacket != NULL) && (firstPacket->ready)) { - // discard flushes more than 10 seconds into the future -- they are probably bogus - uint32_t first_frame_in_buffer = firstPacket->given_timestamp; - int32_t offset_from_first_frame = (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer); - if (offset_from_first_frame > (int)conn->input_rate * 10) { - debug(1, "flush request: sanity check -- flush frame %u is too far into the future from the first frame %u -- discarded.", conn->flush_rtp_timestamp, first_frame_in_buffer); - drop_request = 1; - } else { - if ((lastPacket != NULL) && (lastPacket->ready)) { - // we have enough information to check if the flush is needed or can be discarded - uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1; - // now we have to work out if the flush frame is in the buffer - // if it is later than the end of the buffer, flush everything and keep the request active. - // if it is in the buffer, we need to flush part of the buffer. Actually we flush the entire buffer and drop the request. - // if it is before the buffer, no flush is needed. Drop the request. - if (offset_from_first_frame > 0) { - int32_t offset_to_last_frame = (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp); - if (offset_to_last_frame >= 0) { - debug(2,"flush request: flush frame %u active -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); - drop_request = 1; - flush_needed = 1; - } else { - debug(2,"flush request: flush frame %u pending -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); - flush_needed = 1; - } + } + // now check to see it the flush request is for frames in the buffer or not + // if the first_packet_timestamp is zero, don't check + int flush_needed = 0; + int drop_request = 0; + if ((conn->flush_requested == 1) && (conn->flush_rtp_timestamp == 0)) { + debug(1, "flush request: flush frame 0 -- flush assumed to be needed."); + flush_needed = 1; + drop_request = 1; + } else if (conn->flush_rtp_timestamp != 0) { + if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) { + abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read); + abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1); + if ((firstPacket != NULL) && (firstPacket->ready)) { + // discard flushes more than 10 seconds into the future -- they are probably bogus + uint32_t first_frame_in_buffer = firstPacket->given_timestamp; + int32_t offset_from_first_frame = (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer); + if (offset_from_first_frame > (int)conn->input_rate * 10) { + debug(1, "flush request: sanity check -- flush frame %u is too far into the future from the first frame %u -- discarded.", conn->flush_rtp_timestamp, first_frame_in_buffer); + drop_request = 1; + } else { + if ((lastPacket != NULL) && (lastPacket->ready)) { + // we have enough information to check if the flush is needed or can be discarded + uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1; + // now we have to work out if the flush frame is in the buffer + // if it is later than the end of the buffer, flush everything and keep the request active. + // if it is in the buffer, we need to flush part of the buffer. Actually we flush the entire buffer and drop the request. + // if it is before the buffer, no flush is needed. Drop the request. + if (offset_from_first_frame > 0) { + int32_t offset_to_last_frame = (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp); + if (offset_to_last_frame >= 0) { + debug(2,"flush request: flush frame %u active -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); + drop_request = 1; + flush_needed = 1; } else { - debug(2,"flush request: flush frame %u expired -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); - drop_request = 1; + debug(2,"flush request: flush frame %u pending -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); + flush_needed = 1; } + } else { + debug(2,"flush request: flush frame %u expired -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); + drop_request = 1; } } } - } else { - debug(3, "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: %u, ab_write: %u", conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write); - // leave flush request pending and don't do a buffer flush, because there isn't one } - } - if (flush_needed) { - debug(2, "flush request: flush done."); - ab_resync(conn); // no cancellation points - conn->first_packet_timestamp = 0; - conn->first_packet_time_to_play = 0; - conn->time_since_play_started = 0; - have_sent_prefiller_silence = 0; - dac_delay = 0; + } else { + debug(3, "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: %u, ab_write: %u", conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write); + // leave flush request pending and don't do a buffer flush, because there isn't one } - if (drop_request) { - debug(2, "flush request: request dropped."); - conn->flush_requested = 0; - conn->flush_rtp_timestamp = 0; - conn->flush_output_flushed = 0; - } - } + } + if (flush_needed) { + debug(2, "flush request: flush done."); + ab_resync(conn); // no cancellation points + conn->first_packet_timestamp = 0; + conn->first_packet_time_to_play = 0; + conn->time_since_play_started = 0; + have_sent_prefiller_silence = 0; + dac_delay = 0; + } + if (drop_request) { + debug(2, "flush request: request dropped."); + conn->flush_requested = 0; + conn->flush_rtp_timestamp = 0; + conn->flush_output_flushed = 0; + } debug_mutex_unlock(&conn->flush_mutex, 0); if (conn->ab_synced) { curframe = conn->audio_buffer + BUFIDX(conn->ab_read); @@ -1997,7 +1997,7 @@ void *player_thread_func(void *arg) { // debug(3, "Play frame %d.", play_number); conn->play_number_after_flush++; if (inframe->given_timestamp == 0) { - debug(1, + debug(2, "Player has supplied a silent frame, (possibly frame %u) for play number %d, " "status 0x%X after %u resend requests.", SUCCESSOR(conn->last_seqno_read), play_number, inframe->status, @@ -2320,7 +2320,6 @@ void *player_thread_func(void *arg) { uint32_t frames_to_drop_sized = local_frames_to_drop; debug_mutex_lock(&conn->flush_mutex, 1000, 1); - conn->flush_requested = 1; conn->flush_rtp_timestamp = inframe->given_timestamp + frames_to_drop_sized; // flush all packets up to (and including?) this From 41868430eb65a79b5a651e16c82a208ab8a760e7 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 14 Aug 2020 13:27:34 +0100 Subject: [PATCH 14/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index db018d0d..4ec57618 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,7 +1,7 @@ Version 3.3.7d16 ==== **Bug Fix** -* Fix potential inability to restore synchrnoisation quickly in certain situations. +* Fix potential inability to restore synchronisation quickly in certain situations. Version 3.3.7d15 ==== From aa38f7820c58a67cdac08063792f0665f506dc75 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 2 Sep 2020 17:27:36 +0100 Subject: [PATCH 15/36] Add the word 'jack' to the version string if Jack Audi support is compiled in. --- common.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common.c b/common.c index 39c4d62e..dbb4bad0 100644 --- a/common.c +++ b/common.c @@ -1460,6 +1460,9 @@ char *get_version_string() { #ifdef CONFIG_SNDIO strcat(version_string, "-sndio"); #endif +#ifdef CONFIG_JACK + strcat(version_string, "-jack"); +#endif #ifdef CONFIG_AO strcat(version_string, "-ao"); #endif From 08f0878055a5ad530cba343240f0706cc34cc927 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 2 Sep 2020 17:28:02 +0100 Subject: [PATCH 16/36] Add the word 'jack' to the version string if Jack Audi support is compiled in. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 72fe895c..9f64310f 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d16], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.7d17], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 2c2ac1be9344f2ab2210bb425fc601215427b02f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 2 Sep 2020 17:31:47 +0100 Subject: [PATCH 17/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 4ec57618..8abde680 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,8 @@ +Version 3.3.7d17 +==== +**Bug Fix** +* Include the word `jack` in the version string if support for [Jack Audio](https://jackaudio.org) is included. + Version 3.3.7d16 ==== **Bug Fix** From ca56287254776bb5e3838258da57784d70132ab1 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 3 Sep 2020 09:53:13 +0100 Subject: [PATCH 18/36] Make the first output backend in the list of backends the default and make its name the default output_name. Clang-format everything --- audio.c | 2 +- audio.h | 2 +- audio_pipe.c | 32 +- common.c | 178 ++++----- common.h | 21 +- metadata_hub.c | 44 ++- metadata_hub.h | 7 +- player.c | 994 +++++++++++++++++++++++++------------------------ player.h | 4 +- rtp.c | 99 ++--- rtsp.c | 346 +++++++++-------- shairport.c | 207 +++++----- 12 files changed, 1016 insertions(+), 920 deletions(-) diff --git a/audio.c b/audio.c index 23b1760d..0fe7b04f 100644 --- a/audio.c +++ b/audio.c @@ -89,7 +89,7 @@ static audio_output *outputs[] = { #endif NULL}; -audio_output *audio_get_output(char *name) { +audio_output *audio_get_output(const char *name) { audio_output **out; // default to the first diff --git a/audio.h b/audio.h index 856f184b..3413250c 100644 --- a/audio.h +++ b/audio.h @@ -54,7 +54,7 @@ typedef struct { } audio_output; -audio_output *audio_get_output(char *name); +audio_output *audio_get_output(const char *name); void audio_ls_outputs(void); void parse_general_audio_options(void); diff --git a/audio_pipe.c b/audio_pipe.c index 45e0f060..05c98623 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -51,33 +51,34 @@ static void start(__attribute__((unused)) int sample_rate, // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO // open for reading." - fd = try_to_open_pipe_for_writing(pipename); - // we check that it's not a "real" error. From the "man 2 open" page: - // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO - // open for reading." Which is okay. - if ((fd == -1) && (errno != ENXIO)) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "audio_pipe start -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, pipename); - warn("can not open audio pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, pipename); - } + fd = try_to_open_pipe_for_writing(pipename); + // we check that it's not a "real" error. From the "man 2 open" page: + // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO + // open for reading." Which is okay. + if ((fd == -1) && (errno != ENXIO)) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "audio_pipe start -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, pipename); + warn("can not open audio pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, pipename); + } } static int play(void *buf, int samples) { // if the file is not open, try to open it. char errorstring[1024]; if (fd == -1) { - fd = try_to_open_pipe_for_writing(pipename); + fd = try_to_open_pipe_for_writing(pipename); } // if it's got a reader, write to it. if (fd > 0) { - //int rc = non_blocking_write(fd, buf, samples * 4); + // int rc = non_blocking_write(fd, buf, samples * 4); int rc = write(fd, buf, samples * 4); if ((rc < 0) && (errno != EPIPE)) { strerror_r(errno, (char *)errorstring, 1024); - debug(1, "audio_pip play: error %d writing to the pipe named \"%s\": \"%s\".", errno, pipename, errorstring); + debug(1, "audio_pip play: error %d writing to the pipe named \"%s\": \"%s\".", errno, + pipename, errorstring); } } return 0; @@ -85,7 +86,6 @@ static int play(void *buf, int samples) { static void stop(void) { // Don't close the pipe just because a play session has stopped. - } static int init(int argc, char **argv) { diff --git a/common.c b/common.c index dbb4bad0..c1deea32 100644 --- a/common.c +++ b/common.c @@ -29,6 +29,8 @@ #include "common.h" #include #include +#include +#include #include #include #include @@ -40,8 +42,6 @@ #include #include #include -#include -#include #ifdef COMPILE_FOR_OSX #include @@ -141,64 +141,66 @@ void do_sps_log_to_stdout(__attribute__((unused)) int prio, const char *t, ...) fprintf(stdout, "%s\n", s); } -int create_log_file(const char* path) { - int fd = -1; - if (path != NULL) { - char *dirc = strdup(path); - if (dirc) { - char *dname = dirname(dirc); - // create the directory, if necessary - int result = 0; - if (dname) { - char *pdir = realpath(dname, NULL); // will return a NULL if the directory doesn't exist - if (pdir == NULL) { - mode_t oldumask = umask(000); - result = mkpath(dname, 0777); - umask(oldumask); - } else { - free(pdir); - } - if ((result == 0) || (result == -EEXIST)) { - // now open the file - fd = open(path, O_WRONLY | O_NONBLOCK | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if ((fd == -1) && (errno == EEXIST)) - fd = open(path, O_WRONLY | O_APPEND | O_NONBLOCK); +int create_log_file(const char *path) { + int fd = -1; + if (path != NULL) { + char *dirc = strdup(path); + if (dirc) { + char *dname = dirname(dirc); + // create the directory, if necessary + int result = 0; + if (dname) { + char *pdir = realpath(dname, NULL); // will return a NULL if the directory doesn't exist + if (pdir == NULL) { + mode_t oldumask = umask(000); + result = mkpath(dname, 0777); + umask(oldumask); + } else { + free(pdir); + } + if ((result == 0) || (result == -EEXIST)) { + // now open the file + fd = open(path, O_WRONLY | O_NONBLOCK | O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if ((fd == -1) && (errno == EEXIST)) + fd = open(path, O_WRONLY | O_APPEND | O_NONBLOCK); - if (fd >= 0) { - // now we switch to blocking mode - int flags = fcntl(fd, F_GETFL); - if (flags == -1) { -// strerror_r(errno, (char *)errorstring, sizeof(errorstring)); -// debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno, -// (char *)errorstring, pathname); - } else { - flags = fcntl(fd, F_SETFL,flags & ~O_NONBLOCK); -// if (flags == -1) { -// strerror_r(errno, (char *)errorstring, sizeof(errorstring)); -// debug(1, "create_log_file -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno, -// (char *)errorstring, pathname); - } - } - } - } - free(dirc); - } - } - return fd; + if (fd >= 0) { + // now we switch to blocking mode + int flags = fcntl(fd, F_GETFL); + if (flags == -1) { + // strerror_r(errno, (char + //*)errorstring, sizeof(errorstring)); debug(1, "create_log_file -- error %d (\"%s\") + //getting flags of pipe: \"%s\".", errno, (char *)errorstring, pathname); + } else { + flags = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + // if (flags == -1) { + // strerror_r(errno, + //(char *)errorstring, sizeof(errorstring)); debug(1, "create_log_file -- error %d + //(\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno, (char *)errorstring, + //pathname); + } + } + } + } + free(dirc); + } + } + return fd; } void do_sps_log_to_fd(__attribute__((unused)) int prio, const char *t, ...) { - char s[1024]; - va_list args; - va_start(args, t); - vsnprintf(s, sizeof(s), t, args); - va_end(args); - if (config.log_fd == -1) - config.log_fd = create_log_file(config.log_file_path); - if (config.log_fd >= 0) { - dprintf(config.log_fd, "%s\n", s); + char s[1024]; + va_list args; + va_start(args, t); + vsnprintf(s, sizeof(s), t, args); + va_end(args); + if (config.log_fd == -1) + config.log_fd = create_log_file(config.log_file_path); + if (config.log_fd >= 0) { + dprintf(config.log_fd, "%s\n", s); } else if (errno != ENXIO) { // maybe there is a pipe there but not hooked up - fprintf(stderr, "%s\n", s); + fprintf(stderr, "%s\n", s); } } @@ -207,9 +209,9 @@ void log_to_stdout() { sps_log = do_sps_log_to_stdout; } void log_to_file() { sps_log = do_sps_log_to_fd; } void log_to_syslog() { #ifdef CONFIG_LIBDAEMON - sps_log = daemon_log; + sps_log = daemon_log; #else - sps_log = syslog; + sps_log = syslog; #endif } @@ -309,8 +311,8 @@ void _die(const char *filename, const int linenumber, const char *format, ...) { 1.0 * time_since_last_debug_message / 1000000000, filename, linenumber, " *fatal error: "); } else { - strncpy(b, "fatal error: ", sizeof(b)); - s = b+strlen(b); + strncpy(b, "fatal error: ", sizeof(b)); + s = b + strlen(b); } va_list args; va_start(args, format); @@ -339,8 +341,8 @@ void _warn(const char *filename, const int linenumber, const char *format, ...) 1.0 * time_since_last_debug_message / 1000000000, filename, linenumber, " *warning: "); } else { - strncpy(b, "warning: ", sizeof(b)); - s = b+strlen(b); + strncpy(b, "warning: ", sizeof(b)); + s = b + strlen(b); } va_list args; va_start(args, format); @@ -1134,12 +1136,12 @@ uint64_t get_absolute_time_in_ns() { return time_now_ns; } -int try_to_open_pipe_for_writing(const char* pathname) { - // tries to open the pipe in non-blocking mode first. - // if it succeeds, it sets it to blocking. - // if not, it returns -1. +int try_to_open_pipe_for_writing(const char *pathname) { + // tries to open the pipe in non-blocking mode first. + // if it succeeds, it sets it to blocking. + // if not, it returns -1. - int fdis = open(pathname, O_WRONLY | O_NONBLOCK); // open it in non blocking mode first + int fdis = open(pathname, O_WRONLY | O_NONBLOCK); // open it in non blocking mode first // we check that it's not a "real" error. From the "man 2 open" page: // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO @@ -1147,24 +1149,24 @@ int try_to_open_pipe_for_writing(const char* pathname) { // This is checked by the caller. if (fdis >= 0) { - // now we switch to blocking mode - int flags = fcntl(fdis, F_GETFL); - if (flags == -1) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "try_to_open_pipe -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno, - (char *)errorstring, pathname); - } else { - flags = fcntl(fdis, F_SETFL,flags & ~O_NONBLOCK); - if (flags == -1) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "try_to_open_pipe -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno, - (char *)errorstring, pathname); - } - } + // now we switch to blocking mode + int flags = fcntl(fdis, F_GETFL); + if (flags == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "try_to_open_pipe -- error %d (\"%s\") getting flags of pipe: \"%s\".", errno, + (char *)errorstring, pathname); + } else { + flags = fcntl(fdis, F_SETFL, flags & ~O_NONBLOCK); + if (flags == -1) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "try_to_open_pipe -- error %d (\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno, + (char *)errorstring, pathname); + } + } } - return fdis; + return fdis; } /* from @@ -1710,11 +1712,11 @@ int string_update_with_size(char **str, int *flag, char *s, size_t len) { } // from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks -void* memdup(const void* mem, size_t size) { - void* out = malloc(size); +void *memdup(const void *mem, size_t size) { + void *out = malloc(size); - if(out != NULL) - memcpy(out, mem, size); + if (out != NULL) + memcpy(out, mem, size); - return out; + return out; } diff --git a/common.h b/common.h index 1f5d1c87..68666ac9 100644 --- a/common.h +++ b/common.h @@ -182,8 +182,8 @@ typedef struct { char *pidfile; #endif - int log_fd; // file descriptor of the file or pipe to log stuff to. - char *log_file_path; // path to file or pipe to log to, if any + int log_fd; // file descriptor of the file or pipe to log stuff to. + char *log_file_path; // path to file or pipe to log to, if any int logOutputLevel; // log output level int debugger_show_elapsed_time; // in the debug message, display the time since startup int debugger_show_relative_time; // in the debug message, display the time since the last one @@ -285,10 +285,10 @@ typedef struct { int jack_soxr_resample_quality; #endif #endif - void *gradients; // a linked list of the clock gradients discovered for all DACP IDs - // can't use IP numbers as they might be given to different devices - // can't get hold of MAC addresses. - // can't define the nvll linked list struct here + void *gradients; // a linked list of the clock gradients discovered for all DACP IDs + // can't use IP numbers as they might be given to different devices + // can't get hold of MAC addresses. + // can't define the nvll linked list struct here } shairport_cfg; // accessors to config for multi-thread access @@ -303,9 +303,7 @@ void memory_barrier(); void log_to_stderr(); // call this to direct logging to stderr; void log_to_stdout(); // call this to direct logging to stdout; void log_to_syslog(); // call this to direct logging to the system log; -void log_to_file(); // call this to direct logging to a file or (pre-existing) pipe; - - +void log_to_file(); // call this to direct logging to a file or (pre-existing) pipe; // true if Shairport Sync is supposed to be sending output to the output device, false otherwise @@ -313,7 +311,8 @@ int get_requested_connection_state_to_output(); void set_requested_connection_state_to_output(int v); -int try_to_open_pipe_for_writing(const char* pathname); // open it without blocking if it's not hooked up +int try_to_open_pipe_for_writing( + const char *pathname); // open it without blocking if it's not hooked up /* from * http://coding.debuntu.org/c-implementing-str_replace-replace-all-occurrences-substring#comment-722 @@ -446,6 +445,6 @@ void malloc_cleanup(void *arg); int string_update_with_size(char **str, int *flag, char *s, size_t len); // from https://stackoverflow.com/questions/13663617/memdup-function-in-c, with thanks -void* memdup(const void* mem, size_t size); +void *memdup(const void *mem, size_t size); #endif // _COMMON_H diff --git a/metadata_hub.c b/metadata_hub.c index 7b20ae18..a899574a 100644 --- a/metadata_hub.c +++ b/metadata_hub.c @@ -136,18 +136,20 @@ void _metadata_hub_modify_prolog(const char *filename, const int linenumber) { // debug(1, "locking metadata hub for writing"); if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) { if (last_metadata_hub_modify_prolog_file) - debug(2, "Metadata_hub write lock at \"%s:%d\" is already taken at \"%s:%d\" -- must wait.", filename, linenumber, last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line); + debug(2, "Metadata_hub write lock at \"%s:%d\" is already taken at \"%s:%d\" -- must wait.", + filename, linenumber, last_metadata_hub_modify_prolog_file, + last_metadata_hub_modify_prolog_line); else - debug(2, "Metadata_hub write lock is already taken by unknown -- must wait."); + debug(2, "Metadata_hub write lock is already taken by unknown -- must wait."); metadata_hub_re_lock_access_is_delayed = 0; pthread_rwlock_wrlock(&metadata_hub_re_lock); debug(2, "Okay -- acquired the metadata_hub write lock at \"%s:%d\".", filename, linenumber); } else { - if (last_metadata_hub_modify_prolog_file) { - free(last_metadata_hub_modify_prolog_file); - } - last_metadata_hub_modify_prolog_file = strdup(filename); - last_metadata_hub_modify_prolog_line = linenumber; + if (last_metadata_hub_modify_prolog_file) { + free(last_metadata_hub_modify_prolog_file); + } + last_metadata_hub_modify_prolog_file = strdup(filename); + last_metadata_hub_modify_prolog_line = linenumber; // debug(3, "Metadata_hub write lock acquired."); } metadata_hub_re_lock_access_is_delayed = 0; @@ -160,13 +162,16 @@ void _metadata_hub_modify_epilog(int modified, const char *filename, const int l run_metadata_watchers(); } if (metadata_hub_re_lock_access_is_delayed) { - if (last_metadata_hub_modify_prolog_file) { - debug(1, "Metadata_hub write lock taken at \"%s:%d\" is freed at \"%s:%d\".", last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line, filename, linenumber); - free(last_metadata_hub_modify_prolog_file); - last_metadata_hub_modify_prolog_file = NULL; - } else { - debug(1, "Metadata_hub write lock taken at an unknown place is freed at \"%s:%d\".", filename, linenumber); - } + if (last_metadata_hub_modify_prolog_file) { + debug(1, "Metadata_hub write lock taken at \"%s:%d\" is freed at \"%s:%d\".", + last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line, filename, + linenumber); + free(last_metadata_hub_modify_prolog_file); + last_metadata_hub_modify_prolog_file = NULL; + } else { + debug(1, "Metadata_hub write lock taken at an unknown place is freed at \"%s:%d\".", filename, + linenumber); + } } pthread_rwlock_unlock(&metadata_hub_re_lock); // debug(3, "Metadata_hub write lock unlocked."); @@ -506,10 +511,11 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin debug(2, "MH Picture received, length %u bytes.", length); char uri[2048]; - if ((length > 16) && (strcmp(config.cover_art_cache_dir,"")!=0)) { // if it's okay to write the file - // make this uncancellable - int oldState; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable + if ((length > 16) && + (strcmp(config.cover_art_cache_dir, "") != 0)) { // if it's okay to write the file + // make this uncancellable + int oldState; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable char *pathname = metadata_write_image_file(data, length); snprintf(uri, sizeof(uri), "file://%s", pathname); free(pathname); @@ -524,7 +530,7 @@ void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uin changed = 1; else changed = 0; -// pthread_cleanup_pop(0); // don't remove the lock -- it'll have been done + // pthread_cleanup_pop(0); // don't remove the lock -- it'll have been done break; case 'clip': cs = strndup(data, length); diff --git a/metadata_hub.h b/metadata_hub.h index 760a848b..bdbbf321 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -149,7 +149,9 @@ void metadata_hub_release_track_artwork(void); // these functions lock and unlock the read-write mutex on the metadata hub and run the watchers // afterwards void _metadata_hub_modify_prolog(const char *filename, const int linenumber); -void _metadata_hub_modify_epilog(int modified, const char *filename, const int linenumber); // set to true if modifications occurred, 0 otherwise +void _metadata_hub_modify_epilog( + int modified, const char *filename, + const int linenumber); // set to true if modifications occurred, 0 otherwise /* // these are for safe reading @@ -158,7 +160,8 @@ void _metadata_hub_read_epilog(const char *filename, const int linenumber); */ #define metadata_hub_modify_prolog(void) _metadata_hub_modify_prolog(__FILE__, __LINE__) -#define metadata_hub_modify_epilog(modified) _metadata_hub_modify_epilog(modified, __FILE__, __LINE__) +#define metadata_hub_modify_epilog(modified) \ + _metadata_hub_modify_epilog(modified, __FILE__, __LINE__) #define metadata_hub_read_prolog(void) _metadata_hub_read_prolog(__FILE__, __LINE__) #define metadata_hub_read_epilog(void) _metadata_hub_modify_epilog(__FILE__, __LINE__) diff --git a/player.c b/player.c index edca685f..78912e3b 100644 --- a/player.c +++ b/player.c @@ -143,7 +143,6 @@ static void ab_resync(rtsp_conn_info *conn) { conn->ab_buffering = 1; } - // given starting and ending points as unsigned 16-bit integers running modulo 2^16, returns the // position of x in the interval in *pos // returns true if x is actually within the buffer @@ -174,25 +173,21 @@ int position_in_modulo_uint16_t_buffer(uint16_t x, uint16_t start, uint16_t end, return response; } -static inline seq_t SUCCESSOR(seq_t x) { - return x + 1; -} +static inline seq_t SUCCESSOR(seq_t x) { return x + 1; } // a minus b int16_t seq_diff(seq_t a, seq_t b) { - int16_t response; - seq_t diff = a - b; - seq_t invdiff = b - a; - if (diff < invdiff) - response = diff; - else - response = -invdiff; + int16_t response; + seq_t diff = a - b; + seq_t invdiff = b - a; + if (diff < invdiff) + response = diff; + else + response = -invdiff; return response; } -static inline seq_t seq_sum(seq_t a, seq_t b) { - return a + b; -} +static inline seq_t seq_sum(seq_t a, seq_t b) { return a + b; } // This orders u and v by picking the smaller of the two modulo differences // in unsigned modulo arithmetic and setting the sign of the result accordingly. @@ -215,16 +210,15 @@ static inline seq_t seq_sum(seq_t a, seq_t b) { // If that ever happens and there is a real ambiguity in the application, // the modulo chosen is too small. - int64_t int64_mod_difference(const uint64_t u, const uint64_t v, const uint64_t modulo) { - int64_t response; - uint64_t diff = (u - v) % modulo; - uint64_t invdiff = (v - u) % modulo; - if (diff < invdiff) - response = diff; - else - response = -invdiff; - return response; + int64_t response; + uint64_t diff = (u - v) % modulo; + uint64_t invdiff = (v - u) % modulo; + if (diff < invdiff) + response = diff; + else + response = -invdiff; + return response; } void reset_input_flow_metrics(rtsp_conn_info *conn) { @@ -466,172 +460,173 @@ void player_put_packet(seq_t seqno, uint32_t actual_timestamp, uint8_t *data, in conn->packet_count_since_flush++; conn->time_of_last_audio_packet = time_now; if (conn->connection_state_to_output) { // if we are supposed to be processing these packets - abuf_t *abuf = 0; - if (!conn->ab_synced) { - // if this is the first packet... - debug(3, "syncing to seqno %u.", seqno); - conn->ab_write = seqno; - conn->ab_read = seqno; - conn->ab_synced = 1; - } - int16_t write_point_gap = seq_diff(seqno,conn->ab_write); // this is the difference between - // the incoming packet number and the packet number that was expected. - if (write_point_gap == 0) { // if this is the expected packet (which could be the first packet...) - if (conn->input_frame_rate_starting_point_is_valid == 0) { - if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) { - conn->frames_inward_measurement_start_time = time_now; - conn->frames_inward_frames_received_at_measurement_start_time = actual_timestamp; - conn->input_frame_rate_starting_point_is_valid = 1; // valid now - } - } - conn->frames_inward_measurement_time = time_now; - conn->frames_inward_frames_received_at_measurement_time = actual_timestamp; - abuf = conn->audio_buffer + BUFIDX(seqno); - conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space - } else if (write_point_gap > 0) { // newer than expected - // initialise the frames in between - int i; - for (i = 0; i < write_point_gap; i++) { - abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i)); - abuf->ready = 0; // to be sure, to be sure - abuf->resend_request_number = 0; - abuf->initialisation_time = - time_now; // this represents when the packet was noticed to be missing - abuf->status = 1 << 0; // signifying missing - abuf->resend_time = 0; - abuf->given_timestamp = 0; - abuf->sequence_number = 0; - } - abuf = conn->audio_buffer + BUFIDX(seqno); - conn->ab_write = SUCCESSOR(seqno); - } else if (seq_diff(seqno,conn->ab_read) > 0) { // older than expected but still not too late - conn->late_packets++; - abuf = conn->audio_buffer + BUFIDX(seqno); - } else { // too late. - conn->too_late_packets++; - } + abuf_t *abuf = 0; + if (!conn->ab_synced) { + // if this is the first packet... + debug(3, "syncing to seqno %u.", seqno); + conn->ab_write = seqno; + conn->ab_read = seqno; + conn->ab_synced = 1; + } + int16_t write_point_gap = seq_diff(seqno, conn->ab_write); // this is the difference between + // the incoming packet number and the packet number that was expected. + if (write_point_gap == + 0) { // if this is the expected packet (which could be the first packet...) + if (conn->input_frame_rate_starting_point_is_valid == 0) { + if ((conn->packet_count_since_flush >= 500) && (conn->packet_count_since_flush <= 510)) { + conn->frames_inward_measurement_start_time = time_now; + conn->frames_inward_frames_received_at_measurement_start_time = actual_timestamp; + conn->input_frame_rate_starting_point_is_valid = 1; // valid now + } + } + conn->frames_inward_measurement_time = time_now; + conn->frames_inward_frames_received_at_measurement_time = actual_timestamp; + abuf = conn->audio_buffer + BUFIDX(seqno); + conn->ab_write = SUCCESSOR(seqno); // move the write pointer to the next free space + } else if (write_point_gap > 0) { // newer than expected + // initialise the frames in between + int i; + for (i = 0; i < write_point_gap; i++) { + abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i)); + abuf->ready = 0; // to be sure, to be sure + abuf->resend_request_number = 0; + abuf->initialisation_time = + time_now; // this represents when the packet was noticed to be missing + abuf->status = 1 << 0; // signifying missing + abuf->resend_time = 0; + abuf->given_timestamp = 0; + abuf->sequence_number = 0; + } + abuf = conn->audio_buffer + BUFIDX(seqno); + conn->ab_write = SUCCESSOR(seqno); + } else if (seq_diff(seqno, conn->ab_read) > 0) { // older than expected but still not too late + conn->late_packets++; + abuf = conn->audio_buffer + BUFIDX(seqno); + } else { // too late. + conn->too_late_packets++; + } - if (abuf) { - int datalen = conn->max_frames_per_packet; - abuf->initialisation_time = time_now; - abuf->resend_time = 0; - if (audio_packet_decode(abuf->data, &datalen, data, len, conn) == 0) { - abuf->ready = 1; - abuf->status = 0; // signifying that it was received - abuf->length = datalen; - abuf->given_timestamp = actual_timestamp; - abuf->sequence_number = seqno; - } else { - debug(1, "Bad audio packet detected and discarded."); - abuf->ready = 0; - abuf->status = 1 << 1; // bad packet, discarded - abuf->resend_request_number = 0; - abuf->given_timestamp = 0; - abuf->sequence_number = 0; - } - } + if (abuf) { + int datalen = conn->max_frames_per_packet; + abuf->initialisation_time = time_now; + abuf->resend_time = 0; + if (audio_packet_decode(abuf->data, &datalen, data, len, conn) == 0) { + abuf->ready = 1; + abuf->status = 0; // signifying that it was received + abuf->length = datalen; + abuf->given_timestamp = actual_timestamp; + abuf->sequence_number = seqno; + } else { + debug(1, "Bad audio packet detected and discarded."); + abuf->ready = 0; + abuf->status = 1 << 1; // bad packet, discarded + abuf->resend_request_number = 0; + abuf->given_timestamp = 0; + abuf->sequence_number = 0; + } + } - int rc = pthread_cond_signal(&conn->flowcontrol); - if (rc) - debug(1, "Error signalling flowcontrol."); + int rc = pthread_cond_signal(&conn->flowcontrol); + if (rc) + debug(1, "Error signalling flowcontrol."); - // resend checks - { - uint64_t minimum_wait_time = - (uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000); - uint64_t resend_repeat_interval = - (uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000); - uint64_t minimum_remaining_time = (uint64_t)( - (config.resend_control_last_check_time + config.audio_backend_buffer_desired_length) * - (uint64_t)1000000000); - uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000); - latency_time = latency_time / (uint64_t)conn->input_rate; + // resend checks + { + uint64_t minimum_wait_time = + (uint64_t)(config.resend_control_first_check_time * (uint64_t)1000000000); + uint64_t resend_repeat_interval = + (uint64_t)(config.resend_control_check_interval_time * (uint64_t)1000000000); + uint64_t minimum_remaining_time = (uint64_t)( + (config.resend_control_last_check_time + config.audio_backend_buffer_desired_length) * + (uint64_t)1000000000); + uint64_t latency_time = (uint64_t)(conn->latency * (uint64_t)1000000000); + latency_time = latency_time / (uint64_t)conn->input_rate; - int x; // this is the first frame to be checked - // if we detected a first empty frame before and if it's still in the buffer! - if ((first_possibly_missing_frame >= 0) && - (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read, - conn->ab_write, NULL))) { - x = first_possibly_missing_frame; - } else { - x = conn->ab_read; - } + int x; // this is the first frame to be checked + // if we detected a first empty frame before and if it's still in the buffer! + if ((first_possibly_missing_frame >= 0) && + (position_in_modulo_uint16_t_buffer(first_possibly_missing_frame, conn->ab_read, + conn->ab_write, NULL))) { + x = first_possibly_missing_frame; + } else { + x = conn->ab_read; + } - first_possibly_missing_frame = -1; // has not been set + first_possibly_missing_frame = -1; // has not been set - int missing_frame_run_count = 0; - int start_of_missing_frame_run = -1; - int number_of_missing_frames = 0; - while (x != conn->ab_write) { - abuf_t *check_buf = conn->audio_buffer + BUFIDX(x); - if (!check_buf->ready) { - if (first_possibly_missing_frame < 0) - first_possibly_missing_frame = x; - number_of_missing_frames++; - // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%" - // PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x, - // check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time); - int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) || - ((check_buf->initialisation_time - (time_now - latency_time)) < - minimum_remaining_time)); - int too_early = ((time_now - check_buf->initialisation_time) < minimum_wait_time); - int too_soon_after_last_request = - ((check_buf->resend_time != 0) && - ((time_now - check_buf->resend_time) < - resend_repeat_interval)); // time_now can never be less than the time_tag + int missing_frame_run_count = 0; + int start_of_missing_frame_run = -1; + int number_of_missing_frames = 0; + while (x != conn->ab_write) { + abuf_t *check_buf = conn->audio_buffer + BUFIDX(x); + if (!check_buf->ready) { + if (first_possibly_missing_frame < 0) + first_possibly_missing_frame = x; + number_of_missing_frames++; + // debug(1, "frame %u's initialisation_time is 0x%" PRIx64 ", latency_time is 0x%" + // PRIx64 ", time_now is 0x%" PRIx64 ", minimum_remaining_time is 0x%" PRIx64 ".", x, + // check_buf->initialisation_time, latency_time, time_now, minimum_remaining_time); + int too_late = ((check_buf->initialisation_time < (time_now - latency_time)) || + ((check_buf->initialisation_time - (time_now - latency_time)) < + minimum_remaining_time)); + int too_early = ((time_now - check_buf->initialisation_time) < minimum_wait_time); + int too_soon_after_last_request = + ((check_buf->resend_time != 0) && + ((time_now - check_buf->resend_time) < + resend_repeat_interval)); // time_now can never be less than the time_tag - if (too_late) - check_buf->status |= 1 << 2; // too late - else - check_buf->status &= 0xFF - (1 << 2); // not too late - if (too_early) - check_buf->status |= 1 << 3; // too early - else - check_buf->status &= 0xFF - (1 << 3); // not too early - if (too_soon_after_last_request) - check_buf->status |= 1 << 4; // too soon after last request - else - check_buf->status &= 0xFF - (1 << 4); // not too soon after last request + if (too_late) + check_buf->status |= 1 << 2; // too late + else + check_buf->status &= 0xFF - (1 << 2); // not too late + if (too_early) + check_buf->status |= 1 << 3; // too early + else + check_buf->status &= 0xFF - (1 << 3); // not too early + if (too_soon_after_last_request) + check_buf->status |= 1 << 4; // too soon after last request + else + check_buf->status &= 0xFF - (1 << 4); // not too soon after last request - if ((!too_soon_after_last_request) && (!too_late) && (!too_early)) { - if (start_of_missing_frame_run == -1) { - start_of_missing_frame_run = x; - missing_frame_run_count = 1; - } else { - missing_frame_run_count++; - } - check_buf->resend_time = time_now; // setting the time to now because we are - // definitely going to take action - check_buf->resend_request_number++; - debug(3, "Frame %d is missing with ab_read of %u and ab_write of %u.", x, - conn->ab_read, conn->ab_write); - } - // if (too_late) { - // debug(1,"too late to get missing frame %u.", x); - // } - } - // if (number_of_missing_frames != 0) - // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame - // = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame); - x = (x + 1) & 0xffff; - if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) { - // send a resend request - if (missing_frame_run_count > 1) - debug(3, "request resend of %d packets starting at seqno %u.", - missing_frame_run_count, start_of_missing_frame_run); - if (config.disable_resend_requests == 0) { - debug_mutex_unlock(&conn->ab_mutex, 3); - rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn); - debug_mutex_lock(&conn->ab_mutex, 20000, 1); - conn->resend_requests++; - } - start_of_missing_frame_run = -1; - missing_frame_run_count = 0; - } - } - if (number_of_missing_frames == 0) - first_possibly_missing_frame = conn->ab_write; - } + if ((!too_soon_after_last_request) && (!too_late) && (!too_early)) { + if (start_of_missing_frame_run == -1) { + start_of_missing_frame_run = x; + missing_frame_run_count = 1; + } else { + missing_frame_run_count++; + } + check_buf->resend_time = time_now; // setting the time to now because we are + // definitely going to take action + check_buf->resend_request_number++; + debug(3, "Frame %d is missing with ab_read of %u and ab_write of %u.", x, conn->ab_read, + conn->ab_write); + } + // if (too_late) { + // debug(1,"too late to get missing frame %u.", x); + // } + } + // if (number_of_missing_frames != 0) + // debug(1,"check with x = %u, ab_read = %u, ab_write = %u, first_possibly_missing_frame + // = %d.", x, conn->ab_read, conn->ab_write, first_possibly_missing_frame); + x = (x + 1) & 0xffff; + if (((check_buf->ready) || (x == conn->ab_write)) && (missing_frame_run_count > 0)) { + // send a resend request + if (missing_frame_run_count > 1) + debug(3, "request resend of %d packets starting at seqno %u.", missing_frame_run_count, + start_of_missing_frame_run); + if (config.disable_resend_requests == 0) { + debug_mutex_unlock(&conn->ab_mutex, 3); + rtp_request_resend(start_of_missing_frame_run, missing_frame_run_count, conn); + debug_mutex_lock(&conn->ab_mutex, 20000, 1); + conn->resend_requests++; + } + start_of_missing_frame_run = -1; + missing_frame_run_count = 0; + } + } + if (number_of_missing_frames == 0) + first_possibly_missing_frame = conn->ab_write; + } } debug_mutex_unlock(&conn->ab_mutex, 0); } @@ -944,77 +939,98 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { debug_mutex_lock(&conn->flush_mutex, 1000, 0); if (conn->flush_requested == 1) { - if (conn->flush_output_flushed == 0) - if (config.output->flush) { - config.output->flush(); // no cancellation points - debug(2, "flush request: flush output device."); + if (conn->flush_output_flushed == 0) + if (config.output->flush) { + config.output->flush(); // no cancellation points + debug(2, "flush request: flush output device."); } conn->flush_output_flushed = 1; } - // now check to see it the flush request is for frames in the buffer or not - // if the first_packet_timestamp is zero, don't check - int flush_needed = 0; - int drop_request = 0; - if ((conn->flush_requested == 1) && (conn->flush_rtp_timestamp == 0)) { - debug(1, "flush request: flush frame 0 -- flush assumed to be needed."); - flush_needed = 1; - drop_request = 1; - } else if (conn->flush_rtp_timestamp != 0) { - if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) { - abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read); - abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1); - if ((firstPacket != NULL) && (firstPacket->ready)) { - // discard flushes more than 10 seconds into the future -- they are probably bogus - uint32_t first_frame_in_buffer = firstPacket->given_timestamp; - int32_t offset_from_first_frame = (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer); - if (offset_from_first_frame > (int)conn->input_rate * 10) { - debug(1, "flush request: sanity check -- flush frame %u is too far into the future from the first frame %u -- discarded.", conn->flush_rtp_timestamp, first_frame_in_buffer); - drop_request = 1; - } else { - if ((lastPacket != NULL) && (lastPacket->ready)) { - // we have enough information to check if the flush is needed or can be discarded - uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1; - // now we have to work out if the flush frame is in the buffer - // if it is later than the end of the buffer, flush everything and keep the request active. - // if it is in the buffer, we need to flush part of the buffer. Actually we flush the entire buffer and drop the request. - // if it is before the buffer, no flush is needed. Drop the request. - if (offset_from_first_frame > 0) { - int32_t offset_to_last_frame = (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp); - if (offset_to_last_frame >= 0) { - debug(2,"flush request: flush frame %u active -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); - drop_request = 1; - flush_needed = 1; - } else { - debug(2,"flush request: flush frame %u pending -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); - flush_needed = 1; - } - } else { - debug(2,"flush request: flush frame %u expired -- buffer contains %u frames, from %u to %u", conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, first_frame_in_buffer, last_frame_in_buffer); - drop_request = 1; - } - } - } - } - } else { - debug(3, "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: %u, ab_write: %u", conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write); - // leave flush request pending and don't do a buffer flush, because there isn't one - } - } - if (flush_needed) { - debug(2, "flush request: flush done."); - ab_resync(conn); // no cancellation points - conn->first_packet_timestamp = 0; - conn->first_packet_time_to_play = 0; - conn->time_since_play_started = 0; - have_sent_prefiller_silence = 0; - dac_delay = 0; - } - if (drop_request) { - debug(2, "flush request: request dropped."); - conn->flush_requested = 0; - conn->flush_rtp_timestamp = 0; - conn->flush_output_flushed = 0; - } + // now check to see it the flush request is for frames in the buffer or not + // if the first_packet_timestamp is zero, don't check + int flush_needed = 0; + int drop_request = 0; + if ((conn->flush_requested == 1) && (conn->flush_rtp_timestamp == 0)) { + debug(1, "flush request: flush frame 0 -- flush assumed to be needed."); + flush_needed = 1; + drop_request = 1; + } else if (conn->flush_rtp_timestamp != 0) { + if ((conn->ab_synced) && ((conn->ab_write - conn->ab_read) > 0)) { + abuf_t *firstPacket = conn->audio_buffer + BUFIDX(conn->ab_read); + abuf_t *lastPacket = conn->audio_buffer + BUFIDX(conn->ab_write - 1); + if ((firstPacket != NULL) && (firstPacket->ready)) { + // discard flushes more than 10 seconds into the future -- they are probably bogus + uint32_t first_frame_in_buffer = firstPacket->given_timestamp; + int32_t offset_from_first_frame = + (int32_t)(conn->flush_rtp_timestamp - first_frame_in_buffer); + if (offset_from_first_frame > (int)conn->input_rate * 10) { + debug(1, + "flush request: sanity check -- flush frame %u is too far into the future from " + "the first frame %u -- discarded.", + conn->flush_rtp_timestamp, first_frame_in_buffer); + drop_request = 1; + } else { + if ((lastPacket != NULL) && (lastPacket->ready)) { + // we have enough information to check if the flush is needed or can be discarded + uint32_t last_frame_in_buffer = lastPacket->given_timestamp + lastPacket->length - 1; + // now we have to work out if the flush frame is in the buffer + // if it is later than the end of the buffer, flush everything and keep the request + // active. if it is in the buffer, we need to flush part of the buffer. Actually we + // flush the entire buffer and drop the request. if it is before the buffer, no flush + // is needed. Drop the request. + if (offset_from_first_frame > 0) { + int32_t offset_to_last_frame = + (int32_t)(last_frame_in_buffer - conn->flush_rtp_timestamp); + if (offset_to_last_frame >= 0) { + debug(2, + "flush request: flush frame %u active -- buffer contains %u frames, from " + "%u to %u", + conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, + first_frame_in_buffer, last_frame_in_buffer); + drop_request = 1; + flush_needed = 1; + } else { + debug(2, + "flush request: flush frame %u pending -- buffer contains %u frames, from " + "%u to %u", + conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, + first_frame_in_buffer, last_frame_in_buffer); + flush_needed = 1; + } + } else { + debug(2, + "flush request: flush frame %u expired -- buffer contains %u frames, from %u " + "to %u", + conn->flush_rtp_timestamp, last_frame_in_buffer - first_frame_in_buffer + 1, + first_frame_in_buffer, last_frame_in_buffer); + drop_request = 1; + } + } + } + } + } else { + debug(3, + "flush request: flush frame %u -- buffer not synced or empty: synced: %d, ab_read: " + "%u, ab_write: %u", + conn->flush_rtp_timestamp, conn->ab_synced, conn->ab_read, conn->ab_write); + // leave flush request pending and don't do a buffer flush, because there isn't one + } + } + if (flush_needed) { + debug(2, "flush request: flush done."); + ab_resync(conn); // no cancellation points + conn->first_packet_timestamp = 0; + conn->first_packet_time_to_play = 0; + conn->time_since_play_started = 0; + have_sent_prefiller_silence = 0; + dac_delay = 0; + } + if (drop_request) { + debug(2, "flush request: request dropped."); + conn->flush_requested = 0; + conn->flush_rtp_timestamp = 0; + conn->flush_output_flushed = 0; + } debug_mutex_unlock(&conn->flush_mutex, 0); if (conn->ab_synced) { curframe = conn->audio_buffer + BUFIDX(conn->ab_read); @@ -1037,7 +1053,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } } - if ((curframe) && (curframe->ready)) { notified_buffer_empty = 0; // at least one buffer now -- diagnostic only. if (conn->ab_buffering) { // if we are getting packets but not yet forwarding them to the @@ -1058,7 +1073,6 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { // Here, calculate when we should start playing. We need to know when to allow the // packets to be sent to the player. - // every second or so, we get a reference on when a particular packet should be // played. @@ -1093,8 +1107,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { &should_be_time, conn); conn->first_packet_time_to_play = should_be_time; - debug(2,"first_packet_time set for frame %u.", conn->first_packet_timestamp); - + debug(2, "first_packet_time set for frame %u.", conn->first_packet_timestamp); if (local_time_now > conn->first_packet_time_to_play) { uint64_t lateness = local_time_now - conn->first_packet_time_to_play; @@ -1105,172 +1118,180 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) { } } - - if (conn->first_packet_time_to_play != 0) { - // Now that we know the timing of the first packet... - if (config.output->delay) { - // and that the output device is capable of synchronization... + // Now that we know the timing of the first packet... + if (config.output->delay) { + // and that the output device is capable of synchronization... - // We may send packets of - // silence from now until the time the first audio packet should be sent - // and then we will send the first packet, which will be followed by - // the subsequent packets. - // here, we figure out whether and what silence to send. + // We may send packets of + // silence from now until the time the first audio packet should be sent + // and then we will send the first packet, which will be followed by + // the subsequent packets. + // here, we figure out whether and what silence to send. - uint64_t should_be_time; - uint32_t effective_latency = conn->latency; + uint64_t should_be_time; + uint32_t effective_latency = conn->latency; - switch (get_and_check_effective_latency(conn, &effective_latency, - config.audio_backend_latency_offset)) { - case -1: - if (conn->unachievable_audio_backend_latency_offset_notified == 0) { - warn("Negative latency! A latency of %d frames requested by the player, when " - "combined with an audio_backend_latency_offset of %f seconds, would make the " - "overall latency negative. The audio_backend_latency_offset setting is " - "ignored.", - conn->latency, config.audio_backend_latency_offset); - config.audio_backend_latency_offset = 0; // set it to zero - conn->unachievable_audio_backend_latency_offset_notified = 1; - }; - break; - case 1: - if (conn->unachievable_audio_backend_latency_offset_notified == 0) { - warn("An audio_backend_latency_offset of %f seconds may exceed the frame buffering " - "capacity -- the setting is ignored.", - config.audio_backend_latency_offset); - config.audio_backend_latency_offset = 0; // set it to zero; - conn->unachievable_audio_backend_latency_offset_notified = 1; - }; - break; - default: - break; - } + switch (get_and_check_effective_latency(conn, &effective_latency, + config.audio_backend_latency_offset)) { + case -1: + if (conn->unachievable_audio_backend_latency_offset_notified == 0) { + warn( + "Negative latency! A latency of %d frames requested by the player, when " + "combined with an audio_backend_latency_offset of %f seconds, would make the " + "overall latency negative. The audio_backend_latency_offset setting is " + "ignored.", + conn->latency, config.audio_backend_latency_offset); + config.audio_backend_latency_offset = 0; // set it to zero + conn->unachievable_audio_backend_latency_offset_notified = 1; + }; + break; + case 1: + if (conn->unachievable_audio_backend_latency_offset_notified == 0) { + warn("An audio_backend_latency_offset of %f seconds may exceed the frame " + "buffering " + "capacity -- the setting is ignored.", + config.audio_backend_latency_offset); + config.audio_backend_latency_offset = 0; // set it to zero; + conn->unachievable_audio_backend_latency_offset_notified = 1; + }; + break; + default: + break; + } - // readjust first packet time to play - frame_to_local_time(conn->first_packet_timestamp + - effective_latency, // this will go modulo 2^32 - &should_be_time, conn); + // readjust first packet time to play + frame_to_local_time(conn->first_packet_timestamp + + effective_latency, // this will go modulo 2^32 + &should_be_time, conn); - int64_t change_in_should_be_time = (int64_t)(should_be_time - conn->first_packet_time_to_play); + int64_t change_in_should_be_time = + (int64_t)(should_be_time - conn->first_packet_time_to_play); - if (fabs(0.000001*change_in_should_be_time) > 0.001) // the clock drift estimation might be nudging the estimate, and we can ignore this unless if's more than a microsecond - debug(2,"Change in estimated first_packet_time: %8.4f milliseconds.", 0.000001*change_in_should_be_time); + if (fabs(0.000001 * change_in_should_be_time) > + 0.001) // the clock drift estimation might be nudging the estimate, and we can + // ignore this unless if's more than a microsecond + debug(2, "Change in estimated first_packet_time: %8.4f milliseconds.", + 0.000001 * change_in_should_be_time); - conn->first_packet_time_to_play = should_be_time; + conn->first_packet_time_to_play = should_be_time; - if (local_time_now > conn->first_packet_time_to_play) { - uint64_t lateness = local_time_now - conn->first_packet_time_to_play; - debug(2, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness); - conn->ab_buffering = 0; - } else { - // do some calculations - int64_t lead_time = conn->first_packet_time_to_play - local_time_now; - if ((config.audio_backend_silent_lead_in_time_auto == 1) || - (lead_time <= - (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) { - // debug(1, "Lead time: %" PRId64 " nanoseconds.", lead_time); - int resp = 0; - dac_delay = 0; - if (have_sent_prefiller_silence != 0) - resp = config.output->delay(&dac_delay); // we know the output device must have a delay function - if (resp == 0) { - int64_t gross_frame_gap = - ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) / - 1000000000; - int64_t exact_frame_gap = gross_frame_gap - dac_delay; - int64_t frames_needed_to_maintain_desired_buffer = - (int64_t)(config.audio_backend_buffer_desired_length * config.output_rate) - - dac_delay; - // below, remember that exact_frame_gap and - // frames_needed_to_maintain_desired_buffer could both be negative - int64_t fs = frames_needed_to_maintain_desired_buffer; + if (local_time_now > conn->first_packet_time_to_play) { + uint64_t lateness = local_time_now - conn->first_packet_time_to_play; + debug(2, "Gone past starting time by %" PRIu64 " nanoseconds.", lateness); + conn->ab_buffering = 0; + } else { + // do some calculations + int64_t lead_time = conn->first_packet_time_to_play - local_time_now; + if ((config.audio_backend_silent_lead_in_time_auto == 1) || + (lead_time <= + (int64_t)(config.audio_backend_silent_lead_in_time * (int64_t)1000000000))) { + // debug(1, "Lead time: %" PRId64 " nanoseconds.", lead_time); + int resp = 0; + dac_delay = 0; + if (have_sent_prefiller_silence != 0) + resp = config.output->delay( + &dac_delay); // we know the output device must have a delay function + if (resp == 0) { + int64_t gross_frame_gap = + ((conn->first_packet_time_to_play - local_time_now) * config.output_rate) / + 1000000000; + int64_t exact_frame_gap = gross_frame_gap - dac_delay; + int64_t frames_needed_to_maintain_desired_buffer = + (int64_t)(config.audio_backend_buffer_desired_length * config.output_rate) - + dac_delay; + // below, remember that exact_frame_gap and + // frames_needed_to_maintain_desired_buffer could both be negative + int64_t fs = frames_needed_to_maintain_desired_buffer; - // if there isn't enough time to have the desired buffer size - if (exact_frame_gap <= frames_needed_to_maintain_desired_buffer) { - fs = conn->max_frames_per_packet * 2; - } + // if there isn't enough time to have the desired buffer size + if (exact_frame_gap <= frames_needed_to_maintain_desired_buffer) { + fs = conn->max_frames_per_packet * 2; + } - // if we are very close to the end of buffering, i.e. within two frame-lengths, - // add the remaining silence needed and end buffering - if (exact_frame_gap <= conn->max_frames_per_packet * 2) { - fs = exact_frame_gap; - if (fs > first_frame_early_bias) - fs = fs - first_frame_early_bias; // deliberately make the first packet a tiny bit early so that the player may compensate for it at the last minute - conn->ab_buffering = 0; - } - void *silence; - if (fs > 0) { - silence = malloc(conn->output_bytes_per_frame * fs); - if (silence == NULL) - debug(1, "Failed to allocate %d byte silence buffer.", fs); - else { - // generate frames of silence with dither if necessary - conn->previous_random_number = - generate_zero_frames(silence, fs, config.output_format, - conn->enable_dither, conn->previous_random_number); - config.output->play(silence, fs); - // debug(1, "Sent %" PRId64 " frames of silence", fs); - free(silence); - have_sent_prefiller_silence = 1; - } - } - } else { + // if we are very close to the end of buffering, i.e. within two frame-lengths, + // add the remaining silence needed and end buffering + if (exact_frame_gap <= conn->max_frames_per_packet * 2) { + fs = exact_frame_gap; + if (fs > first_frame_early_bias) + fs = fs - first_frame_early_bias; // deliberately make the first packet a + // tiny bit early so that the player may + // compensate for it at the last minute + conn->ab_buffering = 0; + } + void *silence; + if (fs > 0) { + silence = malloc(conn->output_bytes_per_frame * fs); + if (silence == NULL) + debug(1, "Failed to allocate %d byte silence buffer.", fs); + else { + // generate frames of silence with dither if necessary + conn->previous_random_number = + generate_zero_frames(silence, fs, config.output_format, + conn->enable_dither, conn->previous_random_number); + config.output->play(silence, fs); + // debug(1, "Sent %" PRId64 " frames of silence", fs); + free(silence); + have_sent_prefiller_silence = 1; + } + } + } else { - if (resp == sps_extra_code_output_stalled) { - if (conn->unfixable_error_reported == 0) { - conn->unfixable_error_reported = 1; - if (config.cmd_unfixable) { - command_execute(config.cmd_unfixable, "output_device_stalled", 1); - } else { - warn("an unrecoverable error, \"output_device_stalled\", has been " - "detected.", - conn->connection_number); - } - } - } else { - debug(2, "Unexpected response to getting dac delay: %d.", resp); - } - } - } - } - } else { - // if the output device doesn't have a delay, we simply send the lead-in - int64_t lead_time = conn->first_packet_time_to_play - local_time_now; // negative if we are late - void *silence; - int64_t frame_gap = (lead_time * config.output_rate) / 1000000000; - // debug(1,"%d frames needed.",frame_gap); - while (frame_gap > 0) { - ssize_t fs = config.output_rate / 10; - if (fs > frame_gap) - fs = frame_gap; + if (resp == sps_extra_code_output_stalled) { + if (conn->unfixable_error_reported == 0) { + conn->unfixable_error_reported = 1; + if (config.cmd_unfixable) { + command_execute(config.cmd_unfixable, "output_device_stalled", 1); + } else { + warn("an unrecoverable error, \"output_device_stalled\", has been " + "detected.", + conn->connection_number); + } + } + } else { + debug(2, "Unexpected response to getting dac delay: %d.", resp); + } + } + } + } + } else { + // if the output device doesn't have a delay, we simply send the lead-in + int64_t lead_time = + conn->first_packet_time_to_play - local_time_now; // negative if we are late + void *silence; + int64_t frame_gap = (lead_time * config.output_rate) / 1000000000; + // debug(1,"%d frames needed.",frame_gap); + while (frame_gap > 0) { + ssize_t fs = config.output_rate / 10; + if (fs > frame_gap) + fs = frame_gap; - silence = malloc(conn->output_bytes_per_frame * fs); - if (silence == NULL) - debug(1, "Failed to allocate %d frame silence buffer.", fs); - else { - // debug(1, "No delay function -- outputting %d frames of silence.", fs); - conn->previous_random_number = - generate_zero_frames(silence, fs, config.output_format, - conn->enable_dither, conn->previous_random_number); - config.output->play(silence, fs); - free(silence); - } - frame_gap -= fs; - } - conn->ab_buffering = 0; - } - } -#ifdef CONFIG_METADATA - if (conn->ab_buffering == 0) { - debug(2, "prsm"); - send_ssnc_metadata('prsm', NULL, 0, - 0); // "resume", but don't wait if the queue is locked - } -#endif + silence = malloc(conn->output_bytes_per_frame * fs); + if (silence == NULL) + debug(1, "Failed to allocate %d frame silence buffer.", fs); + else { + // debug(1, "No delay function -- outputting %d frames of silence.", fs); + conn->previous_random_number = + generate_zero_frames(silence, fs, config.output_format, conn->enable_dither, + conn->previous_random_number); + config.output->play(silence, fs); + free(silence); + } + frame_gap -= fs; + } + conn->ab_buffering = 0; + } } +#ifdef CONFIG_METADATA + if (conn->ab_buffering == 0) { + debug(2, "prsm"); + send_ssnc_metadata('prsm', NULL, 0, + 0); // "resume", but don't wait if the queue is locked + } +#endif } } + } // Here, we work out whether to release a packet or wait // We release a packet when the time is right. @@ -1665,8 +1686,8 @@ void player_thread_cleanup_handler(void *arg) { } if (conn->statistics) { - free(conn->statistics); - conn->statistics = NULL; + free(conn->statistics); + conn->statistics = NULL; } free_audio_buffers(conn); if (conn->stream.type == ast_apple_lossless) @@ -1690,8 +1711,8 @@ void *player_thread_func(void *arg) { conn->first_packet_timestamp = 0; conn->flush_requested = 0; conn->flush_output_flushed = 0; // only send a flush command to the output device once - conn->flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to - // be used as a null operand, so we'll use it like that too + conn->flush_rtp_timestamp = 0; // it seems this number has a special significance -- it seems to + // be used as a null operand, so we'll use it like that too conn->fix_volume = 0x10000; if (conn->latency == 0) { @@ -1902,9 +1923,9 @@ void *player_thread_func(void *arg) { int sync_error_out_of_bounds = 0; // number of times in a row that there's been a serious sync error - conn->statistics = malloc(sizeof(stats_t)*trend_interval); + conn->statistics = malloc(sizeof(stats_t) * trend_interval); if (conn->statistics == NULL) - die("Failed to allocate a statistics buffer"); + die("Failed to allocate a statistics buffer"); conn->framesProcessedInThisEpoch = 0; conn->framesGeneratedInThisEpoch = 0; @@ -2002,7 +2023,8 @@ void *player_thread_func(void *arg) { "status 0x%X after %u resend requests.", SUCCESSOR(conn->last_seqno_read), play_number, inframe->status, inframe->resend_request_number); - conn->last_seqno_read = SUCCESSOR(conn->last_seqno_read); // manage the packet out of sequence minder + conn->last_seqno_read = + SUCCESSOR(conn->last_seqno_read); // manage the packet out of sequence minder void *silence = malloc(conn->output_bytes_per_frame * conn->max_frames_per_packet * conn->output_sample_ratio); @@ -2112,8 +2134,7 @@ void *player_thread_func(void *arg) { die("Shairport Sync only supports 16 bit input"); } - - at_least_one_frame_seen = 1; + at_least_one_frame_seen = 1; // We have a frame of data. We need to see if we want to add or remove a frame from it to // keep in sync. @@ -2166,8 +2187,7 @@ void *player_thread_func(void *arg) { } } - conn->buffer_occupancy = - seq_diff(conn->ab_write, conn->ab_read); // int32_t from int16_t + conn->buffer_occupancy = seq_diff(conn->ab_write, conn->ab_read); // int32_t from int16_t if (conn->buffer_occupancy < minimum_buffer_occupancy) minimum_buffer_occupancy = conn->buffer_occupancy; @@ -2175,9 +2195,6 @@ void *player_thread_func(void *arg) { if (conn->buffer_occupancy > maximum_buffer_occupancy) maximum_buffer_occupancy = conn->buffer_occupancy; - - - // here, we want to check (a) if we are meant to do synchronisation, // (b) if we have a delay procedure, (c) if we can get the delay. @@ -2191,7 +2208,7 @@ void *player_thread_func(void *arg) { if (resp == 0) { // no error current_delay = l_delay; if (l_delay >= 0) - current_delay = l_delay; + current_delay = l_delay; else { debug(2, "Underrun of %ld frames reported, but ignored.", l_delay); current_delay = @@ -2229,15 +2246,16 @@ void *player_thread_func(void *arg) { local_time_to_frame(local_time_now, &should_be_frame_32, conn); // int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio; - int64_t delay = int64_mod_difference(should_be_frame_32 * conn->output_sample_ratio, nt - current_delay, UINT32_MAX * conn->output_sample_ratio); + int64_t delay = + int64_mod_difference(should_be_frame_32 * conn->output_sample_ratio, + nt - current_delay, UINT32_MAX * conn->output_sample_ratio); - //int64_t delay = should_be_frame - (nt - current_delay); // all int64_t + // int64_t delay = should_be_frame - (nt - current_delay); // all int64_t // the original frame numbers are unsigned 32-bit integers that roll over modulo 2^32 - // hence these delay figures will be unsigned numbers that roll over modulo 2^32 * conn->output_sample_ratio; - // therefore, calculating the delay must be done in the light of possible rollover - - + // hence these delay figures will be unsigned numbers that roll over modulo 2^32 * + // conn->output_sample_ratio; therefore, calculating the delay must be done in the light + // of possible rollover sync_error = delay - ((int64_t)conn->latency * conn->output_sample_ratio + @@ -2245,38 +2263,51 @@ void *player_thread_func(void *arg) { config.output_rate)); // int64_t from int64_t - int32_t, so okay if (at_least_one_frame_seen_this_session == 0) { - at_least_one_frame_seen_this_session = 1; + at_least_one_frame_seen_this_session = 1; - // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", sync_error); + // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", + // sync_error); - // this is a sneaky attempt to make a final adjustment to the timing of the first packet + // this is a sneaky attempt to make a final adjustment to the timing of the first + // packet - // the very first packet generally has a first_frame_early_bias subtracted from its timing - // to make it more likely that it will be early than late, - // making it possible to compensate for it be adding a few frames of silence. + // the very first packet generally has a first_frame_early_bias subtracted from its + // timing to make it more likely that it will be early than late, making it possible + // to compensate for it be adding a few frames of silence. - // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", sync_error); + // debug(2,"first frame real sync error (positive --> late): %" PRId64 " frames.", + // sync_error); - // remove the bias when reporting the error to make it the true error + // remove the bias when reporting the error to make it the true error - debug(2,"first frame sync error (positive --> late): %" PRId64 " frames, %.3f mS at %d frames per second output.", sync_error+first_frame_early_bias, (1000.0*(sync_error+first_frame_early_bias))/config.output_rate, config.output_rate); + debug(2, + "first frame sync error (positive --> late): %" PRId64 + " frames, %.3f mS at %d frames per second output.", + sync_error + first_frame_early_bias, + (1000.0 * (sync_error + first_frame_early_bias)) / config.output_rate, + config.output_rate); - // if the packet is early, add the frames needed to put it in sync. - if (sync_error < 0) { - size_t final_adjustment_length_sized = -sync_error; - char *final_adjustment_silence = malloc(conn->output_bytes_per_frame * final_adjustment_length_sized); + // if the packet is early, add the frames needed to put it in sync. + if (sync_error < 0) { + size_t final_adjustment_length_sized = -sync_error; + char *final_adjustment_silence = + malloc(conn->output_bytes_per_frame * final_adjustment_length_sized); if (final_adjustment_silence) { - conn->previous_random_number = - generate_zero_frames(final_adjustment_silence, final_adjustment_length_sized, config.output_format, - conn->enable_dither, conn->previous_random_number); + conn->previous_random_number = generate_zero_frames( + final_adjustment_silence, final_adjustment_length_sized, config.output_format, + conn->enable_dither, conn->previous_random_number); int final_adjustment = -sync_error; final_adjustment = final_adjustment - first_frame_early_bias; - debug(2, "final sync adjustment: %" PRId64 " silent frames added with a bias of %" PRId64 " frames.", -sync_error, first_frame_early_bias); + debug(2, + "final sync adjustment: %" PRId64 + " silent frames added with a bias of %" PRId64 " frames.", + -sync_error, first_frame_early_bias); config.output->play(final_adjustment_silence, final_adjustment_length_sized); free(final_adjustment_silence); } else { - warn("Failed to allocate memory for a final_adjustment_silence buffer of %d frames for a " + warn("Failed to allocate memory for a final_adjustment_silence buffer of %d " + "frames for a " "sync error of %d frames.", final_adjustment_length_sized, sync_error); } @@ -2560,30 +2591,30 @@ void *player_thread_func(void *arg) { // if there is no delay procedure, then we should be sending the packet // to the output at the time determined by // the packet's time to play + requested latency + requested offset. -/* - // This is just for checking during development + /* + // This is just for checking during + development - uint32_t should_be_frame_32; - local_time_to_frame(local_time_now, &should_be_frame_32, conn); - // int64_t should_be_frame = ((int64_t)should_be_frame_32) * conn->output_sample_ratio; + uint32_t should_be_frame_32; + local_time_to_frame(local_time_now, &should_be_frame_32, conn); + // int64_t should_be_frame = ((int64_t)should_be_frame_32) * + conn->output_sample_ratio; - int32_t ilatency = (int32_t)((config.audio_backend_latency_offset - config.audio_backend_buffer_desired_length) * conn->input_rate) + conn->latency; - if (ilatency < 0) - debug(1,"incorrect latency %d.", ilatency); + int32_t ilatency = (int32_t)((config.audio_backend_latency_offset - + config.audio_backend_buffer_desired_length) * conn->input_rate) + conn->latency; if + (ilatency < 0) debug(1,"incorrect latency %d.", ilatency); - int32_t idelay = (int32_t)(should_be_frame_32 - inframe->given_timestamp); + int32_t idelay = (int32_t)(should_be_frame_32 - inframe->given_timestamp); - idelay = idelay - ilatency; + idelay = idelay - ilatency; - debug(2,"delay is %d input frames.", idelay); -*/ + debug(2,"delay is %d input frames.", idelay); + */ // if this is the first frame, see if it's close to when it's supposed to be // release, which will be its time plus latency and any offset_time if (at_least_one_frame_seen_this_session == 0) { - at_least_one_frame_seen_this_session = 1; - - + at_least_one_frame_seen_this_session = 1; } play_samples = @@ -2779,11 +2810,10 @@ void *player_thread_func(void *arg) { "%*.2f," /* source actual (average) frame rate */ "%*.2f," /* source clock drift */ "%*d", /* source clock drift sample count */ - 12, play_number, - 7, conn->missing_packets, 7, conn->late_packets, 7, conn->too_late_packets, - 7, conn->resend_requests, 5, minimum_buffer_occupancy, 5, - maximum_buffer_occupancy, 11, conn->remote_frame_rate, 11, - conn->input_frame_rate, 10, + 12, play_number, 7, conn->missing_packets, 7, conn->late_packets, 7, + conn->too_late_packets, 7, conn->resend_requests, 5, + minimum_buffer_occupancy, 5, maximum_buffer_occupancy, 11, + conn->remote_frame_rate, 11, conn->input_frame_rate, 10, (conn->local_to_remote_time_gradient - 1.0) * 1000000, 6, conn->local_to_remote_time_gradient_sample_count); } @@ -2791,7 +2821,7 @@ void *player_thread_func(void *arg) { inform("No frames received in the last sampling interval."); } } - minimum_dac_queue_size = UINT64_MAX; // hack reset + 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 at_least_one_frame_seen = 0; @@ -2997,17 +3027,17 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c // here, send the 'pvol' metadata message when the airplay volume information // is being used by shairport sync to control the output volume char dv[128]; - memset(dv, 0, 128); - if (volume_mode == vol_both) { - // normalise the maximum output to the hardware device's max output - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, - (scaled_attenuation - max_db + hw_max_db) / 100.0, - (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0); - } else { - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0, - min_db / 100.0, max_db / 100.0); - } - send_ssnc_metadata('pvol', dv, strlen(dv), 1); + memset(dv, 0, 128); + if (volume_mode == vol_both) { + // normalise the maximum output to the hardware device's max output + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, + (scaled_attenuation - max_db + hw_max_db) / 100.0, + (min_db - max_db + hw_max_db) / 100.0, (max_db - max_db + hw_max_db) / 100.0); + } else { + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, scaled_attenuation / 100.0, + min_db / 100.0, max_db / 100.0); + } + send_ssnc_metadata('pvol', dv, strlen(dv), 1); #endif if (config.output->mute) @@ -3026,10 +3056,10 @@ void player_volume_without_notification(double airplay_volume, rtsp_conn_info *c else { // here, send the 'pvol' metadata message when the airplay volume information // is being used by shairport sync to control the output volume - char dv[128]; - memset(dv, 0, 128); - snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0); - send_ssnc_metadata('pvol', dv, strlen(dv), 1); + char dv[128]; + memset(dv, 0, 128); + snprintf(dv, 127, "%.2f,%.2f,%.2f,%.2f", airplay_volume, 0.0, 0.0, 0.0); + send_ssnc_metadata('pvol', dv, strlen(dv), 1); } #endif @@ -3057,10 +3087,10 @@ void player_flush(uint32_t timestamp, rtsp_conn_info *conn) { debug(3, "player_flush"); do_flush(timestamp, conn); #ifdef CONFIG_METADATA - debug(2, "pfls"); - char numbuf[32]; - snprintf(numbuf, sizeof(numbuf),"%u",timestamp); - send_ssnc_metadata('pfls', numbuf, strlen(numbuf), 1); // contains cancellation points + debug(2, "pfls"); + char numbuf[32]; + snprintf(numbuf, sizeof(numbuf), "%u", timestamp); + send_ssnc_metadata('pfls', numbuf, strlen(numbuf), 1); // contains cancellation points #endif } diff --git a/player.h b/player.h index f6b86cd4..2b0c2c2e 100644 --- a/player.h +++ b/player.h @@ -25,7 +25,9 @@ #include "audio.h" #define time_ping_history_power_of_two 7 -#define time_ping_history (1 << time_ping_history_power_of_two) // 2^7 is 128. At 1 per three seconds, approximately six minutes of records +#define time_ping_history \ + (1 << time_ping_history_power_of_two) // 2^7 is 128. At 1 per three seconds, approximately six + // minutes of records typedef struct time_ping_record { uint64_t dispersion; diff --git a/rtp.c b/rtp.c index e56c5454..c558645e 100644 --- a/rtp.c +++ b/rtp.c @@ -46,9 +46,9 @@ #include struct Nvll { - char* name; - double value; - struct Nvll *next; + char *name; + double value; + struct Nvll *next; }; typedef struct Nvll nvll; @@ -263,8 +263,8 @@ void *rtp_control_receiver(void *arg) { obfp += 2; }; *obfp = 0; - - + + // get raw timestamp information // I think that a good way to understand these timestamps is that // (1) the rtlt below is the timestamp of the frame that should be playing at the @@ -275,19 +275,19 @@ void *rtp_control_receiver(void *arg) { // Thus, (3) the latency can be calculated by subtracting the second from the // first. // There must be more to it -- there something missing. - + // In addition, it seems that if the value of the short represented by the second // pair of bytes in the packet is 7 // then an extra time lag is expected to be added, presumably by // the AirPort Express. - + // Best guess is that this delay is 11,025 frames. - + uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency uint32_t rt = nctohl(&packet[16]); // raw timestamp - + uint32_t fl = nctohs(&packet[2]); // - + debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and %u, giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt); //debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 " @@ -527,22 +527,25 @@ void rtp_timing_receiver_cleanup_handler(void *arg) { // walk down the list of DACP / gradient pairs, if any nvll *gradients = config.gradients; if (conn->dacp_id) - while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string,gradients->name) != 0)) - gradients = gradients->next; + while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string, gradients->name) != 0)) + gradients = gradients->next; - // if gradients comes out of this non-null, it is pointing to the DACP and it's last-known gradient + // if gradients comes out of this non-null, it is pointing to the DACP and it's last-known + // gradient if (gradients) { - gradients->value = conn->local_to_remote_time_gradient; - // debug(1,"Updating a drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, gradients->name); + gradients->value = conn->local_to_remote_time_gradient; + // debug(1,"Updating a drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient + // - 1.0)*1000000, gradients->name); } else { - nvll *new_entry = (nvll*)malloc(sizeof(nvll)); - if (new_entry) { - new_entry->name = strdup((const char *)&conn->client_ip_string); - new_entry->value = conn->local_to_remote_time_gradient; - new_entry->next = config.gradients; - config.gradients = new_entry; - // debug(1,"Setting a new drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, new_entry->name); - } + nvll *new_entry = (nvll *)malloc(sizeof(nvll)); + if (new_entry) { + new_entry->name = strdup((const char *)&conn->client_ip_string); + new_entry->value = conn->local_to_remote_time_gradient; + new_entry->next = config.gradients; + config.gradients = new_entry; + // debug(1,"Setting a new drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient + // - 1.0)*1000000, new_entry->name); + } } debug(3, "Cancel Timing Requester."); @@ -574,13 +577,14 @@ void *rtp_timing_receiver(void *arg) { conn->local_to_remote_time_gradient = 1.0; // initial value. // walk down the list of DACP / gradient pairs, if any nvll *gradients = config.gradients; - while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string,gradients->name) != 0)) - gradients = gradients->next; + while ((gradients) && (strcasecmp((const char *)&conn->client_ip_string, gradients->name) != 0)) + gradients = gradients->next; // if gradients comes out of this non-null, it is pointing to the IP and it's last-known gradient if (gradients) { - conn->local_to_remote_time_gradient = gradients->value; - // debug(1,"Using a stored drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient - 1.0)*1000000, gradients->name); + conn->local_to_remote_time_gradient = gradients->value; + // debug(1,"Using a stored drift of %.2f ppm for \"%s\".", (conn->local_to_remote_time_gradient + // - 1.0)*1000000, gradients->name); } // calculate diffusion factor @@ -591,13 +595,12 @@ void *rtp_timing_receiver(void *arg) { // be the nth root of diffusion_expansion_factor // where n is the number of elements in the array - const double diffusion_expansion_factor = 10; - double log_of_multiplier = log10(diffusion_expansion_factor)/time_ping_history; - double multiplier = pow(10,log_of_multiplier); + const double diffusion_expansion_factor = 10; + double log_of_multiplier = log10(diffusion_expansion_factor) / time_ping_history; + double multiplier = pow(10, log_of_multiplier); uint64_t dispersion_factor = (uint64_t)(multiplier * 100); // debug(1,"dispersion factor is %" PRIu64 ".", dispersion_factor); - // uint64_t first_local_to_remote_time_difference_time; // uint64_t l2rtd = 0; int sequence_number = 0; @@ -621,7 +624,8 @@ void *rtp_timing_receiver(void *arg) { if (packet[1] == 0xd3) { // timing reply return_time = arrival_time - conn->departure_time; - debug(3,"clock synchronisation request: return time is %8.3f milliseconds.",0.000001*return_time); + debug(3, "clock synchronisation request: return time is %8.3f milliseconds.", + 0.000001 * return_time); if (return_time < 200000000) { // must be less than 0.2 seconds // distant_receive_time = @@ -671,11 +675,11 @@ void *rtp_timing_receiver(void *arg) { // conn->time_pings[cc].dispersion * pow(2.14, // 1.0/conn->time_ping_count); if (conn->time_pings[cc].dispersion > UINT64_MAX / dispersion_factor) - debug(1,"dispersion factor is too large at %" PRIu64 "."); + debug(1, "dispersion factor is too large at %" PRIu64 "."); else - conn->time_pings[cc].dispersion = - (conn->time_pings[cc].dispersion * dispersion_factor) / - 100; // make the dispersions 'age' by this rational factor + conn->time_pings[cc].dispersion = + (conn->time_pings[cc].dispersion * dispersion_factor) / + 100; // make the dispersions 'age' by this rational factor } // these are used for doing a least squares calculation to get the drift conn->time_pings[0].local_time = arrival_time; @@ -752,7 +756,8 @@ void *rtp_timing_receiver(void *arg) { if ((conn->time_pings[cc].chosen) && (conn->time_pings[cc].sequence_number > (settling_time / 3))) { // wait for a approximate settling time - // have to scale them down so that the sum, possibly over every term in the array, doesn't overflow + // have to scale them down so that the sum, possibly over + // every term in the array, doesn't overflow y_bar += (conn->time_pings[cc].remote_time >> time_ping_history_power_of_two); x_bar += (conn->time_pings[cc].local_time >> time_ping_history_power_of_two); sample_count++; @@ -762,8 +767,6 @@ void *rtp_timing_receiver(void *arg) { y_bar = y_bar / sample_count; x_bar = x_bar / sample_count; - - int64_t xid, yid; double mtl, mbl; mtl = 0; @@ -791,19 +794,21 @@ void *rtp_timing_receiver(void *arg) { conn->local_to_remote_time_gradient = mtl / mbl; else { // conn->local_to_remote_time_gradient = 1.0; - debug(1,"mbl is zero. Drift remains at %.2f ppm.", (conn->local_to_remote_time_gradient - 1.0)*1000000); + debug(1, "mbl is zero. Drift remains at %.2f ppm.", + (conn->local_to_remote_time_gradient - 1.0) * 1000000); } - // scale the numbers back up - uint64_t ybf = y_bar << time_ping_history_power_of_two; - uint64_t xbf = x_bar << time_ping_history_power_of_two; + // scale the numbers back up + uint64_t ybf = y_bar << time_ping_history_power_of_two; + uint64_t xbf = x_bar << time_ping_history_power_of_two; - conn->local_to_remote_time_difference = - ybf - xbf; // make this the new local-to-remote-time-difference - conn->local_to_remote_time_difference_measurement_time = xbf; + conn->local_to_remote_time_difference = + ybf - xbf; // make this the new local-to-remote-time-difference + conn->local_to_remote_time_difference_measurement_time = xbf; } else { - debug(3,"not enough samples to estimate drift -- remaining at %.2f ppm.", (conn->local_to_remote_time_gradient - 1.0)*1000000); + debug(3, "not enough samples to estimate drift -- remaining at %.2f ppm.", + (conn->local_to_remote_time_gradient - 1.0) * 1000000); // conn->local_to_remote_time_gradient = 1.0; } // debug(1,"local to remote time gradient is %12.2f ppm, based on %d diff --git a/rtsp.c b/rtsp.c index 1f1cb9f9..af9e9706 100644 --- a/rtsp.c +++ b/rtsp.c @@ -115,7 +115,7 @@ typedef struct { pthread_cond_t pc_queue_item_added_signal; pthread_cond_t pc_queue_item_removed_signal; char *name; - size_t item_size; // number of bytes in each item + size_t item_size; // number of bytes in each item uint32_t count; // number of items in the queue uint32_t capacity; // maximum number of items uint32_t toq; // first item to take @@ -152,11 +152,12 @@ typedef struct { rtsp_message *carrier; } metadata_package; -void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items, const char* name) { - if (name) - debug(2, "Creating metadata queue \"%s\".", name); - else - debug(1, "Creating an unnamed metadata queue."); +void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t number_of_items, + const char *name) { + if (name) + debug(2, "Creating metadata queue \"%s\".", name); + else + debug(1, "Creating an unnamed metadata queue."); pthread_mutex_init(&the_queue->pc_queue_lock, NULL); pthread_cond_init(&the_queue->pc_queue_item_added_signal, NULL); pthread_cond_init(&the_queue->pc_queue_item_removed_signal, NULL); @@ -167,25 +168,25 @@ void pc_queue_init(pc_queue *the_queue, char *items, size_t item_size, uint32_t the_queue->toq = 0; the_queue->eoq = 0; if (name == NULL) - the_queue->name = NULL; + the_queue->name = NULL; else - the_queue->name = strdup(name); + the_queue->name = strdup(name); } void pc_queue_delete(pc_queue *the_queue) { - if (the_queue->name) - debug(2, "Deleting metadata queue \"%s\".", the_queue->name); - else - debug(1, "Deleting an unnamed metadata queue."); - if (the_queue->name != NULL) - free(the_queue->name); - // debug(2, "destroying pc_queue_item_removed_signal"); + if (the_queue->name) + debug(2, "Deleting metadata queue \"%s\".", the_queue->name); + else + debug(1, "Deleting an unnamed metadata queue."); + if (the_queue->name != NULL) + free(the_queue->name); + // debug(2, "destroying pc_queue_item_removed_signal"); pthread_cond_destroy(&the_queue->pc_queue_item_removed_signal); - // debug(2, "destroying pc_queue_item_added_signal"); + // debug(2, "destroying pc_queue_item_added_signal"); pthread_cond_destroy(&the_queue->pc_queue_item_added_signal); - // debug(2, "destroying pc_queue_lock"); + // debug(2, "destroying pc_queue_lock"); pthread_mutex_destroy(&the_queue->pc_queue_lock); - // debug(2, "destroying signals and locks done"); + // debug(2, "destroying signals and locks done"); } int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, @@ -204,7 +205,7 @@ void pc_queue_cleanup_handler(void *arg) { } int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { - int response = 0; + int response = 0; int rc; if (the_queue) { if (block == 0) { @@ -219,34 +220,39 @@ int pc_queue_add_item(pc_queue *the_queue, const void *the_stuff, int block) { // leave this out if you want this to return if the queue is already full // irrespective of the block flag. /* - while (the_queue->count == the_queue->capacity) { - rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, &the_queue->pc_queue_lock); - if (rc) - debug(1, "Error waiting for item to be removed"); - } - */ + while (the_queue->count == the_queue->capacity) { + rc = pthread_cond_wait(&the_queue->pc_queue_item_removed_signal, + &the_queue->pc_queue_lock); if (rc) debug(1, "Error waiting for item to be removed"); + } + */ if (the_queue->count < the_queue->capacity) { - uint32_t i = the_queue->eoq; - void *p = the_queue->items + the_queue->item_size * i; - // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; - memcpy(p, the_stuff, the_queue->item_size); + uint32_t i = the_queue->eoq; + void *p = the_queue->items + the_queue->item_size * i; + // void * p = &the_queue->qbase + the_queue->item_size*the_queue->eoq; + memcpy(p, the_stuff, the_queue->item_size); - // update the pointer - i++; - if (i == the_queue->capacity) - // fold pointer if necessary - i = 0; - the_queue->eoq = i; - the_queue->count++; - //debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count, the_queue->capacity); - if (the_queue->count == the_queue->capacity) - debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name, the_queue->count); - rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); - if (rc) - debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item", the_queue->name); + // update the pointer + i++; + if (i == the_queue->capacity) + // fold pointer if necessary + i = 0; + the_queue->eoq = i; + the_queue->count++; + // debug(2,"metadata queue+ \"%s\" %d/%d.", the_queue->name, the_queue->count, + // the_queue->capacity); + if (the_queue->count == the_queue->capacity) + debug(3, "metadata queue \"%s\": is now full with %d items in it!", the_queue->name, + the_queue->count); + rc = pthread_cond_signal(&the_queue->pc_queue_item_added_signal); + if (rc) + debug(1, "metadata queue \"%s\": error signalling after pc_queue_add_item", + the_queue->name); } else { - response = EWOULDBLOCK; // a bit arbitrary, this. - debug(3,"metadata queue \"%s\": is already full with %d items in it. Not adding this item to the queue.", the_queue->name, the_queue->count); + response = EWOULDBLOCK; // a bit arbitrary, this. + debug(3, + "metadata queue \"%s\": is already full with %d items in it. Not adding this item to " + "the queue.", + the_queue->name, the_queue->count); } pthread_cleanup_pop(1); // unlock the queue lock. } else { @@ -279,7 +285,8 @@ int pc_queue_get_item(pc_queue *the_queue, void *the_stuff) { i = 0; the_queue->toq = i; the_queue->count--; - debug(3,"metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count, the_queue->capacity); + debug(3, "metadata queue- \"%s\" %d/%d.", the_queue->name, the_queue->count, + the_queue->capacity); rc = pthread_cond_signal(&the_queue->pc_queue_item_removed_signal); if (rc) debug(1, "metadata queue \"%s\": error signalling after pc_queue_get_item", the_queue->name); @@ -317,15 +324,17 @@ void *player_watchdog_thread_code(void *arg) { uint64_t last_watchdog_bark_time = conn->watchdog_bark_time; debug_mutex_unlock(&conn->watchdog_mutex, 0); if (last_watchdog_bark_time != 0) { - uint64_t time_since_last_bark = (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000; + uint64_t time_since_last_bark = + (get_absolute_time_in_ns() - last_watchdog_bark_time) / 1000000000; uint64_t ct = config.timeout; // go from int to 64-bit int if (time_since_last_bark >= ct) { conn->watchdog_barks++; if (conn->watchdog_barks == 1) { // debuglev = 3; // tell us everything. - debug(1, "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " - "of the heart\".", + debug(1, + "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " + "of the heart\".", conn->connection_number); conn->stop = 1; pthread_cancel(conn->thread); @@ -446,7 +455,8 @@ void msg_retain(rtsp_message *msg) { debug(1, "Error %d locking reference counter lock"); if (msg > (rtsp_message *)0x00010000) { msg->referenceCount++; - debug(3,"msg_free increment reference counter message %d to %d.", msg->index_number, msg->referenceCount); + debug(3, "msg_free increment reference counter message %d to %d.", msg->index_number, + msg->referenceCount); // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) @@ -462,7 +472,7 @@ rtsp_message *msg_init(void) { memset(msg, 0, sizeof(rtsp_message)); msg->referenceCount = 1; // from now on, any access to this must be protected with the lock msg->index_number = msg_indexes++; - debug(3,"msg_init message %d", msg->index_number); + debug(3, "msg_init message %d", msg->index_number); } else { die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); } @@ -526,8 +536,9 @@ void msg_free(rtsp_message **msgh) { if (*msgh > (rtsp_message *)0x00010000) { rtsp_message *msg = *msgh; msg->referenceCount--; - if (msg->referenceCount) - debug(3,"msg_free decrement reference counter message %d to %d", msg->index_number, msg->referenceCount); + if (msg->referenceCount) + debug(3, "msg_free decrement reference counter message %d to %d", msg->index_number, + msg->referenceCount); if (msg->referenceCount == 0) { unsigned int i; for (i = 0; i < msg->nheaders; i++) { @@ -542,7 +553,7 @@ void msg_free(rtsp_message **msgh) { index = 0x10000; // ensure it doesn't fold to zero. *msgh = (rtsp_message *)(index); // put a version of the index number of the freed message in here - debug(3,"msg_free freed message %d", msg->index_number); + debug(3, "msg_free freed message %d", msg->index_number); free(msg); } else { // debug(1,"msg_free item %d -- decrement reference to @@ -607,7 +618,7 @@ int msg_handle_line(rtsp_message **pmsg, char *line) { } fail: - debug(3,"msg_handle_line fail"); + debug(3, "msg_handle_line fail"); msg_free(pmsg); *pmsg = NULL; return 0; @@ -650,7 +661,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes if (errno == EINTR) continue; if (errno == EAGAIN) { - debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!", conn->connection_number); + debug(1, "Connection %d: getting Error 11 -- EAGAIN from a blocking read!", + conn->connection_number); continue; } if (errno != ECONNRESET) { @@ -663,15 +675,15 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes goto shutdown; } -/* // this outputs the message received - { - void *pt = malloc(nread+1); - memset(pt, 0, nread+1); - memcpy(pt, buf + inbuf, nread); - debug(1, "Incoming string on port: \"%s\"",pt); - free(pt); - } -*/ + /* // this outputs the message received + { + void *pt = malloc(nread+1); + memset(pt, 0, nread+1); + memcpy(pt, buf + inbuf, nread); + debug(1, "Incoming string on port: \"%s\"",pt); + free(pt); + } + */ inbuf += nread; @@ -680,7 +692,8 @@ enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_mes msg_size = msg_handle_line(the_packet, buf); if (!(*the_packet)) { - debug(1,"Connection %d: rtsp_read_request can't find an RTSP header.", conn->connection_number); + debug(1, "Connection %d: rtsp_read_request can't find an RTSP header.", + conn->connection_number); reply = rtsp_read_request_response_bad_packet; goto shutdown; } @@ -887,9 +900,10 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message * rtsp_message *resp) { debug(3, "Connection %d: OPTIONS", conn->connection_number); resp->respcode = 200; - msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " - "PAUSE, FLUSH, TEARDOWN, " - "OPTIONS, GET_PARAMETER, SET_PARAMETER"); + msg_add_header(resp, "Public", + "ANNOUNCE, SETUP, RECORD, " + "PAUSE, FLUSH, TEARDOWN, " + "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, @@ -929,17 +943,17 @@ void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 } } -// debug(1,"RTSP Flush Requested: %u.",rtptime); + // debug(1,"RTSP Flush Requested: %u.",rtptime); -// the following is now done better by the player_flush routine as a 'pfls' -/* -#ifdef CONFIG_METADATA - if (p) - send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1); - else - send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0); -#endif -*/ + // the following is now done better by the player_flush routine as a 'pfls' + /* + #ifdef CONFIG_METADATA + if (p) + send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1); + else + send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0); + #endif + */ player_flush(rtptime, conn); // will not crash even it there is no player thread. resp->respcode = 200; @@ -1035,8 +1049,9 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { msg_add_header(resp, "Session", "1"); resp->respcode = 200; // it all worked out okay - debug(1, "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: " - "%d, Timing: %d and Audio: %d.", + debug(1, + "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: " + "%d, Timing: %d and Audio: %d.", conn->connection_number, conn->dacp_id, &conn->client_ip_string, &conn->self_ip_string, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); @@ -1265,8 +1280,6 @@ char *base64_encode_so(const unsigned char *data, size_t input_length, char *enc static int fd = -1; // static int dirty = 0; - - pc_queue metadata_queue; #define metadata_queue_size 500 metadata_package metadata_queue_items[metadata_queue_size]; @@ -1294,7 +1307,6 @@ pc_queue metadata_multicast_queue; metadata_package metadata_multicast_queue_items[metadata_queue_size]; pthread_t metadata_multicast_thread; - void metadata_create_multicast_socket(void) { if (config.metadata_enabled == 0) return; @@ -1332,7 +1344,6 @@ void metadata_delete_multicast_socket(void) { free(metadata_sockmsg); } - void metadata_open(void) { if (config.metadata_enabled == 0) return; @@ -1354,7 +1365,8 @@ static void metadata_close(void) { } void metadata_multicast_process(uint32_t type, uint32_t code, char *data, uint32_t length) { - // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code, length); + // debug(1, "Process multicast metadata with type %x, code %x and length %u.", type, code, + // length); if (metadata_sock >= 0 && length < config.metadata_sockmsglength - 8) { char *ptr = metadata_sockmsg; uint32_t v; @@ -1455,7 +1467,7 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) debug(1, "Error encoding base64 data."); // debug(1,"Remaining count: %d ret: %d, outbuf_size: // %d.",remaining_count,ret,outbuf_size); - //ret = non_blocking_write(fd, outbuf, outbuf_size); + // ret = non_blocking_write(fd, outbuf, outbuf_size); ret = write(fd, outbuf, outbuf_size); if (ret < 0) { // debug(1,"metadata_process error %d exit 3",ret); @@ -1473,7 +1485,7 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) } } snprintf(thestring, 1024, "\n"); - //ret = non_blocking_write(fd, thestring, strlen(thestring)); + // ret = non_blocking_write(fd, thestring, strlen(thestring)); ret = write(fd, thestring, strlen(thestring)); if (ret < 0) { // debug(1,"metadata_process error %d exit 5",ret); @@ -1508,11 +1520,12 @@ void *metadata_thread_function(__attribute__((unused)) void *ignore) { pc_queue_get_item(&metadata_queue, &pack); pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); if (config.metadata_enabled) { - if (pack.carrier) { - debug(3, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number); - } else { - debug(3, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length); - } + if (pack.carrier) { + debug(3, " pipe: type %x, code %x, length %u, message %d.", pack.type, pack.code, + pack.length, pack.carrier->index_number); + } else { + debug(3, " pipe: type %x, code %x, length %u.", pack.type, pack.code, pack.length); + } metadata_process(pack.type, pack.code, pack.data, pack.length); debug(3, " pipe: done."); } @@ -1530,8 +1543,8 @@ void metadata_multicast_thread_cleanup_function(__attribute__((unused)) void *ar void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) { // create a pc_queue for passing information to a threaded metadata handler - pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items, sizeof(metadata_package), - metadata_multicast_queue_size, "multicast"); + pc_queue_init(&metadata_multicast_queue, (char *)&metadata_multicast_queue_items, + sizeof(metadata_package), metadata_multicast_queue_size, "multicast"); metadata_create_multicast_socket(); metadata_package pack; pthread_cleanup_push(metadata_multicast_thread_cleanup_function, NULL); @@ -1539,13 +1552,20 @@ void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) { pc_queue_get_item(&metadata_multicast_queue, &pack); pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); if (config.metadata_enabled) { - if (pack.carrier) { - debug(3, " multicast: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number); - } else { - debug(3, " multicast: type %x, code %x, length %u.", pack.type, pack.code, pack.length); - } + if (pack.carrier) { + debug(3, + " multicast: type " + "%x, code %x, length %u, message %d.", + pack.type, pack.code, pack.length, pack.carrier->index_number); + } else { + debug(3, + " multicast: type " + "%x, code %x, length %u.", + pack.type, pack.code, pack.length); + } metadata_multicast_process(pack.type, pack.code, pack.data, pack.length); - debug(3, " multicast: done."); + debug(3, + " multicast: done."); } pthread_cleanup_pop(1); } @@ -1553,10 +1573,8 @@ void *metadata_multicast_thread_function(__attribute__((unused)) void *ignore) { pthread_exit(NULL); } - #ifdef CONFIG_METADATA_HUB -void metadata_hub_close(void) { -} +void metadata_hub_close(void) {} void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) { // debug(2, "metadata_hub_thread_cleanup_function called"); @@ -1582,14 +1600,14 @@ void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { // we check that it's not a "real" error. From the "man 2 open" page: // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO // open for reading." Which is okay. - if ((fd == -1) && (errno != ENXIO)) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - } + if ((fd == -1) && (errno != ENXIO)) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, path); + warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, path); + } free(path); // create a pc_queue for passing information to a threaded metadata handler @@ -1600,13 +1618,15 @@ void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { while (1) { pc_queue_get_item(&metadata_hub_queue, &pack); pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (pack.carrier) { - debug(3, " hub: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number); - } else { - debug(3, " hub: type %x, code %x, length %u.", pack.type, pack.code, pack.length); - } - metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length); - debug(3, " hub: done."); + if (pack.carrier) { + debug(3, " hub: type %x, code %x, length %u, message %d.", pack.type, + pack.code, pack.length, pack.carrier->index_number); + } else { + debug(3, " hub: type %x, code %x, length %u.", pack.type, pack.code, + pack.length); + } + metadata_hub_process_metadata(pack.type, pack.code, pack.data, pack.length); + debug(3, " hub: done."); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); // will never happen @@ -1615,8 +1635,7 @@ void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { #endif #ifdef CONFIG_MQTT -void metadata_mqtt_close(void) { -} +void metadata_mqtt_close(void) {} void metadata_mqtt_thread_cleanup_function(__attribute__((unused)) void *arg) { // debug(2, "metadata_mqtt_thread_cleanup_function called"); @@ -1634,15 +1653,19 @@ void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) { while (1) { pc_queue_get_item(&metadata_mqtt_queue, &pack); pthread_cleanup_push(metadata_pack_cleanup_function, (void *)&pack); - if (config.mqtt_enabled) { - if (pack.carrier) { - debug(3, " mqtt: type %x, code %x, length %u, message %d.", pack.type, pack.code, pack.length, pack.carrier->index_number); - } else { - debug(3, " mqtt: type %x, code %x, length %u.", pack.type, pack.code, pack.length); - } - mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length); - debug(3, " mqtt: done."); - } + if (config.mqtt_enabled) { + if (pack.carrier) { + debug(3, + " mqtt: type %x, code %x, length %u, message " + "%d.", + pack.type, pack.code, pack.length, pack.carrier->index_number); + } else { + debug(3, " mqtt: type %x, code %x, length %u.", + pack.type, pack.code, pack.length); + } + mqtt_process_metadata(pack.type, pack.code, pack.data, pack.length); + debug(3, " mqtt: done."); + } pthread_cleanup_pop(1); } @@ -1700,8 +1723,8 @@ void metadata_stop(void) { } } -int send_metadata_to_queue(pc_queue* queue, uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, - int block) { +int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char *data, + uint32_t length, rtsp_message *carrier, int block) { // parameters: type, code, pointer to data or NULL, length of data or NULL, // the rtsp_message or @@ -1737,20 +1760,27 @@ int send_metadata_to_queue(pc_queue* queue, uint32_t type, uint32_t code, char * if (pack.carrier) { msg_retain(pack.carrier); } else { - if (data) - pack.data = memdup(data,length); // only if it's not a null + if (data) + pack.data = memdup(data, length); // only if it's not a null } int rc = pc_queue_add_item(queue, &pack, block); if (rc != 0) { if (pack.carrier) { - if (rc == EWOULDBLOCK) - debug(2, "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %x, length %u, message %d.", queue->name, pack.type, pack.code, pack.data, pack.length, pack.carrier->index_number); + if (rc == EWOULDBLOCK) + debug(2, + "metadata queue \"%s\" full, dropping message item: type %x, code %x, data %x, " + "length %u, message %d.", + queue->name, pack.type, pack.code, pack.data, pack.length, + pack.carrier->index_number); msg_free(&pack.carrier); } else { - if (rc == EWOULDBLOCK) - debug(2, "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %x, length %u.", queue->name, pack.type, pack.code, pack.data, pack.length); - if (pack.data) - free(pack.data); + if (rc == EWOULDBLOCK) + debug( + 2, + "metadata queue \"%s\" full, dropping data item: type %x, code %x, data %x, length %u.", + queue->name, pack.type, pack.code, pack.data, pack.length); + if (pack.data) + free(pack.data); } } return rc; @@ -1758,23 +1788,20 @@ int send_metadata_to_queue(pc_queue* queue, uint32_t type, uint32_t code, char * int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, int block) { - int rc; - rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block); + int rc; + rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block); #ifdef CONFIG_METADATA_HUB - rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block); + rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block); #endif #ifdef CONFIG_MQTT - rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block); + rc = send_metadata_to_queue(&metadata_mqtt_queue, type, code, data, length, carrier, block); #endif - return rc; + return rc; } - - - static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req, __attribute__((unused)) rtsp_message *resp) { @@ -1838,7 +1865,7 @@ static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_m char *ct = msg_get_header(req, "Content-Type"); if (ct) { -// debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); + // debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); #ifdef CONFIG_METADATA // It seems that the rtptime of the message is used as a kind of an ID that @@ -2136,7 +2163,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag unsigned int i = 0; unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); - char* found; + char *found; while ((found = strsep(&pfmtp, " \t")) != NULL && i < max_param) { conn->stream.fmtp[i++] = atoi(found); } @@ -2617,8 +2644,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (strcmp(req->method, "OPTIONS") != 0) // the options message is very common, so don't log it until level 3 debug_level = 2; - debug(debug_level, "Connection %d: Received an RTSP Packet of type \"%s\":", - conn->connection_number, req->method), + debug(debug_level, + "Connection %d: Received an RTSP Packet of type \"%s\":", conn->connection_number, + req->method), debug_print_msg_headers(debug_level, req); apple_challenge(conn->fd, req, resp); @@ -2667,8 +2695,9 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (conn->stop == 0) { int err = msg_write_response(conn->fd, resp); if (err) { - debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the " - "connection.", + debug(1, + "Connection %d: Unable to write an RTSP message response. Terminating the " + "connection.", conn->connection_number); struct linger so_linger; so_linger.l_onoff = 1; // "true" @@ -2721,10 +2750,11 @@ static void *rtsp_conversation_thread_func(void *pconn) { if (reply == -1) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno, (char *)errorstring); + debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno, + (char *)errorstring); } else if (reply != (ssize_t)strlen(response_text)) { - debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.", strlen(response_text), - reply); + debug(1, "rtsp_read_request_response_bad_packet write %d bytes requested but %d written.", + strlen(response_text), reply); } } else { debug(1, "Connection %d: rtsp_read_request error %d, packet ignored.", @@ -2937,7 +2967,7 @@ void rtsp_listen_loop(void) { debug(2, "Connection %d: new connection from %s:%u to self at %s:%u.", conn->connection_number, remote_ip4, rport, ip4, tport); } - #ifdef AF_INET6 +#ifdef AF_INET6 if (local_info->SAFAMILY == AF_INET6) { // IPv6: @@ -2954,7 +2984,7 @@ void rtsp_listen_loop(void) { debug(2, "Connection %d: new connection from [%s]:%u to self at [%s]:%u.", conn->connection_number, remote_ip6, rport, ip6, tport); } - #endif +#endif } else { debug(1, "Error figuring out Shairport Sync's own IP number."); diff --git a/shairport.c b/shairport.c index 975b71fc..4ee45bc9 100644 --- a/shairport.c +++ b/shairport.c @@ -61,6 +61,7 @@ #endif #include "activity_monitor.h" +#include "audio.h" #include "common.h" #include "rtp.h" #include "rtsp.h" @@ -117,6 +118,8 @@ int daemonisewithout = 0; char configuration_file_path[4096 + 1]; char actual_configuration_file_path[4096 + 1]; +char first_backend_name[256]; + void print_version(void) { char *version_string = get_version_string(); if (version_string) { @@ -126,7 +129,6 @@ void print_version(void) { debug(1, "Can't print version string!"); } } - #ifdef CONFIG_SOXR pthread_t soxr_time_check_thread; void *soxr_time_check(__attribute__((unused)) void *arg) { @@ -419,15 +421,15 @@ int parse_options(int argc, char **argv) { // for unexpected circumstances #ifdef CONFIG_METADATA - /* Get the metadata setting. */ - config.metadata_enabled = 1; // if metadata support is included, then enable it by default - config.get_coverart = 1; // if metadata support is included, then enable it by default + /* Get the metadata setting. */ + config.metadata_enabled = 1; // if metadata support is included, then enable it by default + config.get_coverart = 1; // if metadata support is included, then enable it by default #endif #ifdef CONFIG_CONVOLUTION - config.convolution_max_length = 8192; + config.convolution_max_length = 8192; #endif - config.loudness_reference_volume_db = -20; + config.loudness_reference_volume_db = -20; #ifdef CONFIG_METADATA_HUB config.cover_art_cache_dir = "/tmp/shairport-sync/.cache/coverart"; @@ -682,9 +684,9 @@ int parse_options(int argc, char **argv) { } else if (strcasecmp(str, "stderr") == 0) { log_to_stderr(); } else { - config.log_file_path = (char *)str; - config.log_fd = -1; - log_to_file(); + config.log_file_path = (char *)str; + config.log_fd = -1; + log_to_file(); } } /* Get the ignore_volume_control setting. */ @@ -1313,113 +1315,114 @@ const char *pid_file_proc(void) { void exit_function() { - if (emergency_exit == 0) { - // the following is to ensure that if libdaemon has been included - // that most of this code will be skipped when the parent process is exiting - // exec + if (emergency_exit == 0) { + // the following is to ensure that if libdaemon has been included + // that most of this code will be skipped when the parent process is exiting + // exec #ifdef CONFIG_LIBDAEMON - if ((this_is_the_daemon_process) || (config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not actually deamonised at all + if ((this_is_the_daemon_process) || + (config.daemonise == 0)) { // if this is the daemon process that is exiting or it's not + // actually deamonised at all #endif - debug(2, "exit function called..."); - /* - Actually, there is no terminate_mqtt() function. - #ifdef CONFIG_MQTT - if (config.mqtt_enabled) { - terminate_mqtt(); - } - #endif - */ + debug(2, "exit function called..."); + /* + Actually, there is no terminate_mqtt() function. + #ifdef CONFIG_MQTT + if (config.mqtt_enabled) { + terminate_mqtt(); + } + #endif + */ #if defined(CONFIG_DBUS_INTERFACE) || defined(CONFIG_MPRIS_INTERFACE) - /* - Actually, there is no stop_mpris_service() function. - #ifdef CONFIG_MPRIS_INTERFACE - stop_mpris_service(); - #endif - */ + /* + Actually, there is no stop_mpris_service() function. + #ifdef CONFIG_MPRIS_INTERFACE + stop_mpris_service(); + #endif + */ #ifdef CONFIG_DBUS_INTERFACE - stop_dbus_service(); + stop_dbus_service(); #endif - if (g_main_loop) { - debug(2, "Stopping DBUS Loop Thread"); - g_main_loop_quit(g_main_loop); - pthread_join(dbus_thread, NULL); - } + if (g_main_loop) { + debug(2, "Stopping DBUS Loop Thread"); + g_main_loop_quit(g_main_loop); + pthread_join(dbus_thread, NULL); + } #endif #ifdef CONFIG_DACP_CLIENT - debug(2, "Stopping DACP Monitor"); - dacp_monitor_stop(); + debug(2, "Stopping DACP Monitor"); + dacp_monitor_stop(); #endif #ifdef CONFIG_METADATA_HUB - debug(2, "Stopping metadata hub"); - metadata_hub_stop(); + debug(2, "Stopping metadata hub"); + metadata_hub_stop(); #endif #ifdef CONFIG_METADATA - metadata_stop(); // close down the metadata pipe + metadata_stop(); // close down the metadata pipe #endif - activity_monitor_stop(0); + activity_monitor_stop(0); - if ((config.output) && (config.output->deinit)) { - debug(2, "Deinitialise the audio backend."); - config.output->deinit(); - } + if ((config.output) && (config.output->deinit)) { + debug(2, "Deinitialise the audio backend."); + config.output->deinit(); + } #ifdef CONFIG_SOXR - // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down - pthread_join(soxr_time_check_thread, NULL); + // be careful -- not sure if the thread can be cancelled cleanly, so wait for it to shut down + pthread_join(soxr_time_check_thread, NULL); #endif - if (conns) - free(conns); // make sure the connections have been deleted first + if (conns) + free(conns); // make sure the connections have been deleted first - if (config.service_name) - free(config.service_name); + if (config.service_name) + free(config.service_name); #ifdef CONFIG_CONVOLUTION - if (config.convolution_ir_file) - free(config.convolution_ir_file); + if (config.convolution_ir_file) + free(config.convolution_ir_file); #endif - if (config.regtype) - free(config.regtype); + if (config.regtype) + free(config.regtype); #ifdef CONFIG_LIBDAEMON - if (this_is_the_daemon_process) { - daemon_retval_send(0); - daemon_pid_file_remove(); - daemon_signal_done(); - if (config.computed_piddir) - free(config.computed_piddir); - } - } + if (this_is_the_daemon_process) { + daemon_retval_send(0); + daemon_pid_file_remove(); + daemon_signal_done(); + if (config.computed_piddir) + free(config.computed_piddir); + } + } #endif - if (config.cfg) - config_destroy(config.cfg); - if (config.appName) - free(config.appName); - // probably should be freeing malloc'ed memory here, including strdup-created strings... - + if (config.cfg) + config_destroy(config.cfg); + if (config.appName) + free(config.appName); + // probably should be freeing malloc'ed memory here, including strdup-created strings... #ifdef CONFIG_LIBDAEMON - if (this_is_the_daemon_process) { // this is the daemon that is exiting - debug(1,"libdaemon daemon exit"); - } else { - if (config.daemonise) - debug(1,"libdaemon parent exit"); - else - debug(1,"exit"); - } + if (this_is_the_daemon_process) { // this is the daemon that is exiting + debug(1, "libdaemon daemon exit"); + } else { + if (config.daemonise) + debug(1, "libdaemon parent exit"); + else + debug(1, "exit"); + } #else - debug(1,"exit"); + debug(1, "exit"); #endif - } else { - debug(1,"emergency exit"); - } + } else { + debug(1, "emergency exit"); + } } // for removing zombie script processes @@ -1434,11 +1437,10 @@ void handle_sigchld(__attribute__((unused)) int sig) { } void main_thread_cleanup_handler(__attribute__((unused)) void *arg) { - debug(2,"main thread cleanup handler called"); + debug(2, "main thread cleanup handler called"); exit(EXIT_SUCCESS); } - int main(int argc, char **argv) { /* Check if we are called with -V or --version parameter */ if (argc >= 2 && ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0))) { @@ -1455,7 +1457,7 @@ int main(int argc, char **argv) { #ifdef CONFIG_LIBDAEMON pid = getpid(); #endif - config.log_fd = -1; + config.log_fd = -1; conns = NULL; // no connections active memset((void *)&main_thread_id, 0, sizeof(main_thread_id)); memset(&config, 0, sizeof(config)); // also clears all strings, BTW @@ -1476,7 +1478,7 @@ int main(int argc, char **argv) { setlogmask(LOG_UPTO(LOG_DEBUG)); openlog(NULL, 0, LOG_DAEMON); #endif - emergency_exit = 0; // what to do or skip in the exit_function + emergency_exit = 0; // what to do or skip in the exit_function atexit(exit_function); // set defaults @@ -1504,6 +1506,15 @@ int main(int argc, char **argv) { // set non-zero / non-NULL default values here // but note that audio back ends also have a chance to set defaults + // get the first output backend in the list and make it the default + audio_output *first_backend = audio_get_output(NULL); + if (first_backend == NULL) { + die("No audio backend found! Check your build of Shairport Sync."); + } else { + strncpy(first_backend_name, first_backend->name, sizeof(first_backend_name) - 1); + config.output_name = first_backend_name; + } + strcpy(configuration_file_path, SYSCONFDIR); // strcat(configuration_file_path, "/shairport-sync"); // thinking about adding a special // shairport-sync directory @@ -1598,13 +1609,16 @@ int main(int argc, char **argv) { if (errno == ENOENT) daemon_log(LOG_WARNING, "Failed to kill %s daemon: PID file not found.", config.appName); else - daemon_log(LOG_WARNING, "Failed to kill %s daemon: \"%s\", errno %u.", config.appName, strerror(errno), errno); + daemon_log(LOG_WARNING, "Failed to kill %s daemon: \"%s\", errno %u.", config.appName, + strerror(errno), errno); } else { - // debug(1,"Successfully killed the %s daemon.", config.appName); + // debug(1,"Successfully killed the %s daemon.", config.appName); if (daemon_pid_file_remove() == 0) debug(2, "killed the %s daemon.", config.appName); else - daemon_log(LOG_WARNING, "killed the %s deamon, but cannot remove old PID file: \"%s\", errno %u.", config.appName, strerror(errno), errno); + daemon_log(LOG_WARNING, + "killed the %s deamon, but cannot remove old PID file: \"%s\", errno %u.", + config.appName, strerror(errno), errno); } return ret < 0 ? 1 : 0; #else @@ -1651,14 +1665,19 @@ int main(int argc, char **argv) { case 0: break; case 1: - daemon_log(LOG_ERR, - "the %s daemon failed to launch: could not close open file descriptors after forking.", config.appName); + daemon_log( + LOG_ERR, + "the %s daemon failed to launch: could not close open file descriptors after forking.", + config.appName); break; case 2: - daemon_log(LOG_ERR, "the %s daemon failed to launch: could not create PID file.", config.appName); + daemon_log(LOG_ERR, "the %s daemon failed to launch: could not create PID file.", + config.appName); break; case 3: - daemon_log(LOG_ERR, "the %s daemon failed to launch: could not create or access PID directory.", config.appName); + daemon_log(LOG_ERR, + "the %s daemon failed to launch: could not create or access PID directory.", + config.appName); break; default: daemon_log(LOG_ERR, "the %s daemon failed to launch, error %i.", config.appName, ret); @@ -1709,8 +1728,8 @@ int main(int argc, char **argv) { #endif debug(1, "Started!"); - // stop a pipe signal from killing the program - signal(SIGPIPE, SIG_IGN); + // stop a pipe signal from killing the program + signal(SIGPIPE, SIG_IGN); // install a zombie process reaper // see: http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html From 5ee2cf1b562947c2a16a6efbce27049aaa469c62 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 3 Sep 2020 10:01:42 +0100 Subject: [PATCH 19/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 8abde680..356874ec 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -2,6 +2,7 @@ Version 3.3.7d17 ==== **Bug Fix** * Include the word `jack` in the version string if support for [Jack Audio](https://jackaudio.org) is included. +* Make the first output backend in the list of backends the default, and make its name the default output_name. Clang-format everything. Thanks to [kiwi-ed](https://github.com/kiwi-ed) for bringing these issues to light. Version 3.3.7d16 ==== From b54fb78cb4bf2a705e2eee3a79a75a56c422ce10 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 3 Sep 2020 10:35:52 +0100 Subject: [PATCH 20/36] Update configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 9f64310f..81b62972 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d17], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.7d18], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From d3c4107a44f3c078e8bdf0e8212a16498631ffbd Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Thu, 3 Sep 2020 10:37:20 +0100 Subject: [PATCH 21/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 356874ec..4f84ce8e 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,8 +1,12 @@ +Version 3.3.7d18 +==== +**Bug Fix** +* Make the first output backend in the list of backends the default, and make its name the default output_name. Clang-format everything. Thanks again to [kiwi-ed](https://github.com/kiwi-ed) for bringing this issue to light. + Version 3.3.7d17 ==== **Bug Fix** -* Include the word `jack` in the version string if support for [Jack Audio](https://jackaudio.org) is included. -* Make the first output backend in the list of backends the default, and make its name the default output_name. Clang-format everything. Thanks to [kiwi-ed](https://github.com/kiwi-ed) for bringing these issues to light. +* Include the word `jack` in the version string if support for [Jack Audio](https://jackaudio.org) is included. Thanks to [kiwi-ed](https://github.com/kiwi-ed) for bringing this issue to light. Version 3.3.7d16 ==== From 46c47c01a6cda645bc60569cf528a7940fa9e64e Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 11 Sep 2020 16:54:34 +0100 Subject: [PATCH 22/36] Ensure the metadata pipe is created, if necessary, when the --with-metadata option is chosen without dbus or mpris --- common.c | 13 ++++++++----- configure.ac | 2 +- rtsp.c | 54 ++++++++++++++++++++++++++-------------------------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/common.c b/common.c index c1deea32..cdd4d031 100644 --- a/common.c +++ b/common.c @@ -170,15 +170,18 @@ int create_log_file(const char *path) { int flags = fcntl(fd, F_GETFL); if (flags == -1) { // strerror_r(errno, (char - //*)errorstring, sizeof(errorstring)); debug(1, "create_log_file -- error %d (\"%s\") - //getting flags of pipe: \"%s\".", errno, (char *)errorstring, pathname); + //*)errorstring, sizeof(errorstring)); + //debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".", + // errno, + // (char *)errorstring, pathname); } else { flags = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); // if (flags == -1) { // strerror_r(errno, - //(char *)errorstring, sizeof(errorstring)); debug(1, "create_log_file -- error %d - //(\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno, (char *)errorstring, - //pathname); + //(char *)errorstring, sizeof(errorstring)); + //debug(1, "create_log_file -- error %d + //(\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno, + //(char *)errorstring, pathname); } } } diff --git a/configure.ac b/configure.ac index 81b62972..97fc87d1 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d18], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.7d19], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) diff --git a/rtsp.c b/rtsp.c index af9e9706..a1eeff13 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1320,7 +1320,6 @@ void metadata_create_multicast_socket(void) { } else { int buffer_size = METADATA_SNDBUF; setsockopt(metadata_sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)); - fcntl(fd, F_SETFL, O_NONBLOCK); bzero((char *)&metadata_sockaddr, sizeof(metadata_sockaddr)); metadata_sockaddr.sin_family = AF_INET; metadata_sockaddr.sin_addr.s_addr = inet_addr(config.metadata_sockaddr); @@ -1584,32 +1583,6 @@ void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) { void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { - // create the fifo, if necessary - size_t pl = strlen(config.metadata_pipename) + 1; - char *path = malloc(pl + 1); - snprintf(path, pl + 1, "%s", config.metadata_pipename); - - mode_t oldumask = umask(000); - if (mkfifo(path, 0666) && errno != EEXIST) - die("Could not create metadata pipe \"%s\".", path); - umask(oldumask); - debug(1, "metadata pipe name is \"%s\".", path); - - // try to open it - fd = try_to_open_pipe_for_writing(path); - // we check that it's not a "real" error. From the "man 2 open" page: - // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO - // open for reading." Which is okay. - if ((fd == -1) && (errno != ENXIO)) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - } - free(path); - // create a pc_queue for passing information to a threaded metadata handler pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package), metadata_hub_queue_size, "hub"); @@ -1675,6 +1648,33 @@ void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) { #endif void metadata_init(void) { + + // create the metadata pipe, if necessary + size_t pl = strlen(config.metadata_pipename) + 1; + char *path = malloc(pl + 1); + snprintf(path, pl + 1, "%s", config.metadata_pipename); + + mode_t oldumask = umask(000); + if (mkfifo(path, 0666) && errno != EEXIST) + die("Could not create metadata pipe \"%s\".", path); + umask(oldumask); + debug(1, "metadata pipe name is \"%s\".", path); + + // try to open it + fd = try_to_open_pipe_for_writing(path); + // we check that it's not a "real" error. From the "man 2 open" page: + // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO + // open for reading." Which is okay. + if ((fd == -1) && (errno != ENXIO)) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, path); + warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, path); + } + free(path); + int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); if (ret) debug(1, "Failed to create metadata thread!"); From 485e18db1786b672c2949ee6b67ce64435e76a9e Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 11 Sep 2020 17:31:55 +0100 Subject: [PATCH 23/36] Add a default name for the pipe backend: /tmp/shairport-sync-audio --- audio_pipe.c | 16 ++++++++++------ scripts/shairport-sync.conf | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/audio_pipe.c b/audio_pipe.c index 05c98623..5e82496c 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -42,6 +42,7 @@ static int fd = -1; char *pipename = NULL; +char *default_pipe_name = "/tmp/shairport-sync-audio"; static void start(__attribute__((unused)) int sample_rate, __attribute__((unused)) int sample_format) { @@ -108,16 +109,19 @@ static int init(int argc, char **argv) { if (config_lookup_string(config.cfg, "pipe.name", &str)) { pipename = (char *)str; } - - if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0)) - die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); } - if ((pipename == NULL) && (argc != 1)) - die("bad or missing argument(s) to pipe"); + if (argc > 1) + die("too many command-line arguments to pipe"); if (argc == 1) - pipename = strdup(argv[0]); + pipename = argv[0]; // command line argument has priority + + if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0)) + die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); + + if (pipename == NULL) + pipename = default_pipe_name; // if none specified // here, create the pipe mode_t oldumask = umask(000); diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index efa34996..6444b8a5 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -159,7 +159,7 @@ jack = // --with-pipe pipe = { -// name = "/path/to/pipe"; // there is no default pipe name for the output +// name = "/tmp/shairport-sync-audio"; // this is the default }; // There are no configuration file parameters for the "stdout" audio back end. No interpolation is done. From 5d3b0fad527f5009ac3cc1f4d3aa99a966140b72 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Fri, 11 Sep 2020 17:40:25 +0100 Subject: [PATCH 24/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 4f84ce8e..6eca81f2 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,11 @@ +Version 3.3.7d19 +==== +**Bug Fix** +* Ensure the metadata pipe is created, if necessary, when the `--with-metadata` configuration option is chosen without the `--with-dbus-interface`, `--with-mpris-interface` or the `--with-mqtt-client` options. Super thanks to [Philip Howard](https://github.com/Gadgetoid) for finding this and for developing a possible solution. + +**Enhancements** +* Add a default name for the `pipe` backend. If you don't specify a name for the `pipe` backend's named pipe, it will be `/tmp/shairport-sync-audio`. Note that Shairport Sync will need to have write permission to `/tmp` to create the pipe. + Version 3.3.7d18 ==== **Bug Fix** From c5b47427f26dded1587f9bc2e8591f0b8e4dfc80 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 20 Sep 2020 16:51:07 +0100 Subject: [PATCH 25/36] Tighten up memory allocatons in DACP.c but shorten the wait time for a response. --- dacp.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/dacp.c b/dacp.c index a25bf9e4..28faece1 100644 --- a/dacp.c +++ b/dacp.c @@ -77,23 +77,25 @@ void *response_realloc(__attribute__((unused)) void *opaque, void *ptr, int size void *t = realloc(ptr, size); if ((t == NULL) && (size != 0)) debug(1, "Response realloc of size %d failed!", size); + if (t != ptr) + debug(3, "response_realloc pointer changed for size %d", size); return t; } void response_body(void *opaque, const char *data, int size) { struct HttpResponse *response = (struct HttpResponse *)opaque; - ssize_t space_available = response->malloced_size - response->size; + ssize_t space_available = response->malloced_size - response->size; //must be greater than or equal to 0 if (space_available < size) { - // debug(1,"Getting more space for the response -- need %d bytes but only %ld bytes left.\n", - // size, - // size - space_available); - ssize_t size_requested = size - space_available + response->malloced_size + 16384; + debug(3,"Getting more space for the response -- %d allocated, need %d bytes but only %d bytes left.\n", response->malloced_size, size, space_available); + ssize_t size_requested = size - space_available + response->malloced_size; void *t = realloc(response->body, size_requested); response->malloced_size = size_requested; - if (t) + if (t) { + if (response->body != t) + debug(3, "response_body pointer changed for size %u.", size_requested); response->body = t; - else { + } else { die("dacp: can't allocate any more space for parser."); } } @@ -226,7 +228,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { struct timeval tv; tv.tv_sec = 0; - tv.tv_usec = 500000; + tv.tv_usec = 100000; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "dacp_send_command: error %d setting receive timeout.", errno); if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) @@ -268,8 +270,10 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { } else { - response.body = malloc(2048); // it can resize this if necessary - response.malloced_size = 2048; + response.body = malloc(0); // it will resize this if necessary + if (response.body == NULL) + die("Can not allocatr space for a DACP response."); + response.malloced_size = 0; pthread_cleanup_push(malloc_cleanup, response.body); struct http_roundtripper rt; @@ -286,6 +290,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { debug(1, "dacp_send_command: error %d setting receive timeout.", errno); ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); + if ((ndata >=0) && ((size_t)ndata > sizeof(buffer)/2)) + debug(1, "Received %u bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { if (ndata == -1) { char errorstring[1024]; From 6857d06bd63374377f12a9f7a99868098f27897c Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 27 Sep 2020 10:44:12 +0100 Subject: [PATCH 26/36] Handle active_remote_id as a string rather than an unsigned 32-bit number. It seems ROON uses a longer character sequence. Tidy up some error messages aboout which backend is selected. --- dacp.c | 72 ++++++++++++++++++++++++++++------------------------- player.h | 3 +-- rtsp.c | 17 ++++++++----- shairport.c | 3 +-- 4 files changed, 51 insertions(+), 44 deletions(-) diff --git a/dacp.c b/dacp.c index 28faece1..77313f73 100644 --- a/dacp.c +++ b/dacp.c @@ -56,7 +56,7 @@ typedef struct { int always_use_revision_number_1; // for dealing with forked-daapd; uint32_t scope_id; // if it's an ipv6 connection, this will be its scope id char ip_string[INET6_ADDRSTRLEN]; // the ip string pointing to the client - uint32_t active_remote_id; // send this when you want to send remote control commands + char *active_remote_id; // send this when you want to send remote control commands void *port_monitor_private_storage; } dacp_server_record; @@ -77,25 +77,23 @@ void *response_realloc(__attribute__((unused)) void *opaque, void *ptr, int size void *t = realloc(ptr, size); if ((t == NULL) && (size != 0)) debug(1, "Response realloc of size %d failed!", size); - if (t != ptr) - debug(3, "response_realloc pointer changed for size %d", size); return t; } void response_body(void *opaque, const char *data, int size) { struct HttpResponse *response = (struct HttpResponse *)opaque; - ssize_t space_available = response->malloced_size - response->size; //must be greater than or equal to 0 + ssize_t space_available = response->malloced_size - response->size; if (space_available < size) { - debug(3,"Getting more space for the response -- %d allocated, need %d bytes but only %d bytes left.\n", response->malloced_size, size, space_available); - ssize_t size_requested = size - space_available + response->malloced_size; + // debug(1,"Getting more space for the response -- need %d bytes but only %ld bytes left.\n", + // size, + // size - space_available); + ssize_t size_requested = size - space_available + response->malloced_size + 16384; void *t = realloc(response->body, size_requested); response->malloced_size = size_requested; - if (t) { - if (response->body != t) - debug(3, "response_body pointer changed for size %u.", size_requested); + if (t) response->body = t; - } else { + else { die("dacp: can't allocate any more space for parser."); } } @@ -228,7 +226,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { struct timeval tv; tv.tv_sec = 0; - tv.tv_usec = 100000; + tv.tv_usec = 500000; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "dacp_send_command: error %d setting receive timeout.", errno); if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) @@ -246,7 +244,7 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { // debug(1,"DACP connect succeeded."); snprintf(message, sizeof(message), - "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n", + "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %s\r\n\r\n", command, dacp_server.ip_string, dacp_server.port, dacp_server.active_remote_id); @@ -270,10 +268,8 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { } else { - response.body = malloc(0); // it will resize this if necessary - if (response.body == NULL) - die("Can not allocatr space for a DACP response."); - response.malloced_size = 0; + response.body = malloc(2048); // it can resize this if necessary + response.malloced_size = 2048; pthread_cleanup_push(malloc_cleanup, response.body); struct http_roundtripper rt; @@ -290,8 +286,6 @@ int dacp_send_command(const char *command, char **body, ssize_t *bodysize) { debug(1, "dacp_send_command: error %d setting receive timeout.", errno); ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0); // debug(3, "Received %d bytes: \"%s\".", ndata, buffer); - if ((ndata >=0) && ((size_t)ndata > sizeof(buffer)/2)) - debug(1, "Received %u bytes: \"%s\".", ndata, buffer); if (ndata <= 0) { if (ndata == -1) { char errorstring[1024]; @@ -449,9 +443,11 @@ void set_dacp_server_information(rtsp_conn_info *conn) { // metadata_hub_modify_epilog(ch); } } - dacp_server.active_remote_id = conn->dacp_active_remote; // even if the dacp_id remains the same, - // the active remote will change. - debug(3, "set_dacp_server_information set active-remote id to %" PRIu32 ".", + if (dacp_server.active_remote_id) + free(dacp_server.active_remote_id); + dacp_server.active_remote_id = strdup(conn->dacp_active_remote); // even if the dacp_id remains the same, + // the active remote will change. + debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id); pthread_cond_signal(&dacp_server_information_cv); debug_mutex_unlock(&dacp_server_information_lock, 3); @@ -463,20 +459,24 @@ void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port " "number %d.", dacp_id, dacp_server.dacp_id, port); - if (strcmp(dacp_id, dacp_server.dacp_id) == 0) { - dacp_server.port = port; - if (port == 0) - dacp_server.scan_enable = 0; - else { - dacp_server.scan_enable = 1; - // debug(2, "dacp_monitor_port_update_callback enables scan"); - } - // metadata_hub_modify_prolog(); - // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable; - // metadata_store.dacp_server_active = dacp_server.scan_enable; - // metadata_hub_modify_epilog(ch); + if ((dacp_id == NULL) || (dacp_server.dacp_id == NULL)) { + warn("dacp_id or dacp_server.dacp_id NULL detected"); } else { - debug(1, "dacp port monitor reporting on an out-of-use remote."); + if (strcmp(dacp_id, dacp_server.dacp_id) == 0) { + dacp_server.port = port; + if (port == 0) + dacp_server.scan_enable = 0; + else { + dacp_server.scan_enable = 1; + // debug(2, "dacp_monitor_port_update_callback enables scan"); + } + // metadata_hub_modify_prolog(); + // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable; + // metadata_store.dacp_server_active = dacp_server.scan_enable; + // metadata_hub_modify_epilog(ch); + } else { + debug(1, "dacp port monitor reporting on an out-of-use remote."); + } } pthread_cond_signal(&dacp_server_information_cv); debug_mutex_unlock(&dacp_server_information_lock, 3); @@ -1005,6 +1005,10 @@ void dacp_monitor_stop() { pthread_mutex_destroy(&dacp_conversation_lock); pthread_cond_destroy(&dacp_server_information_cv); debug(3, "DACP Server Information Condition Variable destroyed."); + if (dacp_server.active_remote_id) { + free(dacp_server.active_remote_id); + dacp_server.active_remote_id = NULL; + } } } diff --git a/player.h b/player.h index 2b0c2c2e..013dfb6f 100644 --- a/player.h +++ b/player.h @@ -197,7 +197,6 @@ typedef struct { // could be one of many, so we need to know it uint32_t self_scope_id; // if it's an ipv6 connection, this will be its scope short connection_ip_family; // AF_INET / AF_INET6 - uint32_t client_active_remote; // used when you want to control the client... SOCKADDR rtp_client_control_socket; // a socket pointing to the control port of the client SOCKADDR rtp_client_timing_socket; // a socket pointing to the timing port of the client @@ -265,7 +264,7 @@ typedef struct { char *dacp_id; // id of the client -- used to find the port to be used // uint16_t dacp_port; // port on the client to send remote control messages to, else // zero - uint32_t dacp_active_remote; // key to send to the remote controller + char *dacp_active_remote; // key to send to the remote controller void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled. int enable_dither; // needed for filling silences before play actually starts diff --git a/rtsp.c b/rtsp.c index a1eeff13..974c8a40 100644 --- a/rtsp.c +++ b/rtsp.c @@ -3,7 +3,7 @@ * Copyright (c) James Laird 2013 * Modifications associated with audio synchronization, mutithreading and - * metadata handling copyright (c) Mike Brady 2014-2019 + * metadata handling copyright (c) Mike Brady 2014-2020 * All rights reserved. * * Permission is hereby granted, free of charge, to any person @@ -975,15 +975,19 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: SETUP -- Active-Remote string seen: \"%s\".", conn->connection_number, ar); // get the active remote - char *p; - conn->dacp_active_remote = strtoul(ar, &p, 10); + if (conn->dacp_active_remote) // this is in case SETUP was previously called + free(conn->dacp_active_remote); + conn->dacp_active_remote = strdup(ar); #ifdef CONFIG_METADATA send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); #endif } else { debug(2, "Connection %d: SETUP -- Note: no Active-Remote information the SETUP Record.", conn->connection_number); - conn->dacp_active_remote = 0; + if (conn->dacp_active_remote) {// this is in case SETUP was previously called + free(conn->dacp_active_remote); + conn->dacp_active_remote = NULL; + } } ar = msg_get_header(req, "DACP-ID"); @@ -998,9 +1002,10 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { } else { debug(2, "Connection %d: SETUP doesn't include DACP-ID string information.", conn->connection_number); - if (conn->dacp_id) // this is in case SETUP was previously called + if (conn->dacp_id) {// this is in case SETUP was previously called free(conn->dacp_id); - conn->dacp_id = NULL; + conn->dacp_id = NULL; + } } char *hdr = msg_get_header(req, "Transport"); diff --git a/shairport.c b/shairport.c index 4ee45bc9..127b23f6 100644 --- a/shairport.c +++ b/shairport.c @@ -1763,8 +1763,7 @@ int main(int argc, char **argv) { config.output = audio_get_output(config.output_name); if (!config.output) { - audio_ls_outputs(); - die("Invalid audio output specified!"); + die("Invalid audio backend \"%s\" selected!", config.output_name == NULL ? "" : config.output_name); } config.output->init(argc - audio_arg, argv + audio_arg); From 3fc95704899324784e654cf5edf92cd162d0ddee Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 27 Sep 2020 10:45:22 +0100 Subject: [PATCH 27/36] Clang Format --- audio_pipe.c | 8 ++++---- common.c | 4 ++-- dacp.c | 46 +++++++++++++++++++++++----------------------- player.h | 4 ++-- rtsp.c | 6 +++--- shairport.c | 3 ++- 6 files changed, 36 insertions(+), 35 deletions(-) diff --git a/audio_pipe.c b/audio_pipe.c index 5e82496c..a7925fbc 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -117,11 +117,11 @@ static int init(int argc, char **argv) { if (argc == 1) pipename = argv[0]; // command line argument has priority - if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0)) - die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); + if ((pipename) && (strcasecmp(pipename, "STDOUT") == 0)) + die("Can't use \"pipe\" backend for STDOUT. Use the \"stdout\" backend instead."); - if (pipename == NULL) - pipename = default_pipe_name; // if none specified + if (pipename == NULL) + pipename = default_pipe_name; // if none specified // here, create the pipe mode_t oldumask = umask(000); diff --git a/common.c b/common.c index cdd4d031..b48adad4 100644 --- a/common.c +++ b/common.c @@ -171,7 +171,7 @@ int create_log_file(const char *path) { if (flags == -1) { // strerror_r(errno, (char //*)errorstring, sizeof(errorstring)); - //debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".", + // debug(1, "create_log_file -- error %d (\"%s\") getting flags of pipe: \"%s\".", // errno, // (char *)errorstring, pathname); } else { @@ -179,7 +179,7 @@ int create_log_file(const char *path) { // if (flags == -1) { // strerror_r(errno, //(char *)errorstring, sizeof(errorstring)); - //debug(1, "create_log_file -- error %d + // debug(1, "create_log_file -- error %d //(\"%s\") unsetting NONBLOCK of pipe: \"%s\".", errno, //(char *)errorstring, pathname); } diff --git a/dacp.c b/dacp.c index 77313f73..acb963b9 100644 --- a/dacp.c +++ b/dacp.c @@ -444,11 +444,11 @@ void set_dacp_server_information(rtsp_conn_info *conn) { } } if (dacp_server.active_remote_id) - free(dacp_server.active_remote_id); - dacp_server.active_remote_id = strdup(conn->dacp_active_remote); // even if the dacp_id remains the same, - // the active remote will change. - debug(3, "set_dacp_server_information set active-remote id to %s.", - dacp_server.active_remote_id); + free(dacp_server.active_remote_id); + dacp_server.active_remote_id = + strdup(conn->dacp_active_remote); // even if the dacp_id remains the same, + // the active remote will change. + debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id); pthread_cond_signal(&dacp_server_information_cv); debug_mutex_unlock(&dacp_server_information_lock, 3); } @@ -460,23 +460,23 @@ void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) { "number %d.", dacp_id, dacp_server.dacp_id, port); if ((dacp_id == NULL) || (dacp_server.dacp_id == NULL)) { - warn("dacp_id or dacp_server.dacp_id NULL detected"); + warn("dacp_id or dacp_server.dacp_id NULL detected"); } else { - if (strcmp(dacp_id, dacp_server.dacp_id) == 0) { - dacp_server.port = port; - if (port == 0) - dacp_server.scan_enable = 0; - else { - dacp_server.scan_enable = 1; - // debug(2, "dacp_monitor_port_update_callback enables scan"); - } - // metadata_hub_modify_prolog(); - // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable; - // metadata_store.dacp_server_active = dacp_server.scan_enable; - // metadata_hub_modify_epilog(ch); - } else { - debug(1, "dacp port monitor reporting on an out-of-use remote."); - } + if (strcmp(dacp_id, dacp_server.dacp_id) == 0) { + dacp_server.port = port; + if (port == 0) + dacp_server.scan_enable = 0; + else { + dacp_server.scan_enable = 1; + // debug(2, "dacp_monitor_port_update_callback enables scan"); + } + // metadata_hub_modify_prolog(); + // int ch = metadata_store.dacp_server_active != dacp_server.scan_enable; + // metadata_store.dacp_server_active = dacp_server.scan_enable; + // metadata_hub_modify_epilog(ch); + } else { + debug(1, "dacp port monitor reporting on an out-of-use remote."); + } } pthread_cond_signal(&dacp_server_information_cv); debug_mutex_unlock(&dacp_server_information_lock, 3); @@ -1006,8 +1006,8 @@ void dacp_monitor_stop() { pthread_cond_destroy(&dacp_server_information_cv); debug(3, "DACP Server Information Condition Variable destroyed."); if (dacp_server.active_remote_id) { - free(dacp_server.active_remote_id); - dacp_server.active_remote_id = NULL; + free(dacp_server.active_remote_id); + dacp_server.active_remote_id = NULL; } } } diff --git a/player.h b/player.h index 013dfb6f..105ce869 100644 --- a/player.h +++ b/player.h @@ -264,8 +264,8 @@ typedef struct { char *dacp_id; // id of the client -- used to find the port to be used // uint16_t dacp_port; // port on the client to send remote control messages to, else // zero - char *dacp_active_remote; // key to send to the remote controller - void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled. + char *dacp_active_remote; // key to send to the remote controller + void *dapo_private_storage; // this is used for compatibility, if dacp stuff isn't enabled. int enable_dither; // needed for filling silences before play actually starts uint64_t dac_buffer_queue_minimum_length; diff --git a/rtsp.c b/rtsp.c index 974c8a40..8616a617 100644 --- a/rtsp.c +++ b/rtsp.c @@ -984,7 +984,7 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { } else { debug(2, "Connection %d: SETUP -- Note: no Active-Remote information the SETUP Record.", conn->connection_number); - if (conn->dacp_active_remote) {// this is in case SETUP was previously called + if (conn->dacp_active_remote) { // this is in case SETUP was previously called free(conn->dacp_active_remote); conn->dacp_active_remote = NULL; } @@ -1002,9 +1002,9 @@ void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { } else { debug(2, "Connection %d: SETUP doesn't include DACP-ID string information.", conn->connection_number); - if (conn->dacp_id) {// this is in case SETUP was previously called + if (conn->dacp_id) { // this is in case SETUP was previously called free(conn->dacp_id); - conn->dacp_id = NULL; + conn->dacp_id = NULL; } } diff --git a/shairport.c b/shairport.c index 127b23f6..ba995233 100644 --- a/shairport.c +++ b/shairport.c @@ -1763,7 +1763,8 @@ int main(int argc, char **argv) { config.output = audio_get_output(config.output_name); if (!config.output) { - die("Invalid audio backend \"%s\" selected!", config.output_name == NULL ? "" : config.output_name); + die("Invalid audio backend \"%s\" selected!", + config.output_name == NULL ? "" : config.output_name); } config.output->init(argc - audio_arg, argv + audio_arg); From cf5a9f764dea01bcd86ba2800ef9cd7c1d1f8d59 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Sun, 27 Sep 2020 10:48:27 +0100 Subject: [PATCH 28/36] Handle active_remote_id as a string rather than an unsigned 32-bit number. It seems ROON uses a longer character sequence. Tidy up some error messages aboout which backend is selected. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 97fc87d1..589de5aa 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d19], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.7d20], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 9729fa950b9031e2bbe1de8bdbf35a5bc68b6117 Mon Sep 17 00:00:00 2001 From: minix1234 <48591487+minix1234@users.noreply.github.com> Date: Tue, 15 Dec 2020 16:32:57 -0500 Subject: [PATCH 29/36] Create MQTT.md first crack at MQTT markdown informational data --- MQTT.md | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 MQTT.md diff --git a/MQTT.md b/MQTT.md new file mode 100644 index 00000000..7f14f6e3 --- /dev/null +++ b/MQTT.md @@ -0,0 +1,132 @@ +Setting Up MQTT Publishing +==== + +This is a rough guide on the setup of MQTT publishing in ShairPort-Sync. + + +The MQTT listens for and publishes metadata generated by shairport-sync + +MetaData Parsing +---- + + +Additional details regarding the metadata can be found at [https://github.com/mikebrady/shairport-sync-metadata-reader] + +Meta data is generated by both the stream source (iOS, itunes, etc) and by shairport-sync itself. This data is coded as two 4-character codes to identify each piece of data, the `type` and the `code`. + +The first 4-character code, called the `type`, is either: + * `core` for all the regular metadadata coming from iTunes, etc., or + * `ssnc` (for 'shairport-sync') for all metadata coming from Shairport Sync itself, such as start/end delimiters, etc. + +* For `core` metadata, the second 4-character code is the 4-character metadata code that comes from iTunes etc. See, for example, https://code.google.com/p/ytrack/wiki/DMAP for information about the significance of the codes. The original data supplied by the source, if any, follows, and is encoded in base64 format. The length of the data is also provided. +* For `ssnc` metadata, the second 4-character code is used to distinguish the messages. Cover art, coming from the source, is not tagged in the same way as other metadata, it seems, so is sent as an `ssnc` type metadata message with the code `PICT`. Progress information, similarly, is not tagged like other source-originated metadata, so it is sent as an `ssnc` type with the code `prgr`. + +Here are some of the `core` codes commonly passed from the source: + * `asar` -- "artist" + * `asal` -- "album" + * `minm` -- "title" + * `asgn` -- "genre" + * `asfm` -- "format" + * `asal` -- "songalbum' + * `pvol` -- "volume" + * `clip` -- "client_ip" + +Here are the 'ssnc' codes defined so far: + * `PICT` -- the payload is a picture, either a JPEG or a PNG. Check the first few bytes to see which. + * `clip` -- the payload is the IP number of the client, i.e. the sender of audio. Can be an IPv4 or an IPv6 number. + * `pbeg` -- play stream begin. No arguments + * `pend` -- play stream end. No arguments + * `pfls` -- play stream flush. No arguments + * `prsm` -- play stream resume. No arguments + * `pvol` -- play volume. The volume is sent as a string -- "airplay_volume,volume,lowest_volume,highest_volume", where "volume", "lowest_volume" and "highest_volume" are given in dB. The "airplay_volume" is what's sent by the source (e.g. iTunes) to the player, and is from 0.00 down to -30.00, with -144.00 meaning "mute". This is linear on the volume control slider of iTunes or iOS AirPlay. If the volume setting is being ignored by Shairport Sync itself, the volume, lowest_volume and highest_volume values are zero. + * `prgr` -- progress -- this is metadata from AirPlay consisting of RTP timestamps for the start of the current play sequence, the current play point and the end of the play sequence. + * `mdst` -- a sequence of metadata is about to start. The RTP timestamp associated with the metadata sequence is included as data, if available. + * `mden` -- a sequence of metadata has ended. The RTP timestamp associated with the metadata sequence is included as data, if available. + * `pcst` -- a picture is about to be sent. The RTP timestamp associated with it is included as data, if available. + * `pcen` -- a picture has been sent. The RTP timestamp associated with it is included as data, if available. + * `snam` -- a device e.g. "Joe's iPhone" has started a play session. Specifically, it's the "X-Apple-Client-Name" string. + * `snua` -- a "user agent" e.g. "iTunes/12..." has started a play session. Specifically, it's the "User-Agent" string. + * `stal` -- this is an error message meaning that reception of a large piece of metadata, usually a large picture, has stalled; bad things may happen. + +The next two two tokens are to facilitiate remote control of the source. There is some information at http://nto.github.io/AirPlay.html about remote control of the source. + * `daid` -- this is the source's DACP-ID (if it has one -- it's not guaranteed), useful if you want to remotely control the source. Use this string to identify the source's remote control on the network. + * `acre` -- this is the source's Active-Remote token, necessary if you want to send commands to the source's remote control (if it has one). + * `dapo` -- the payload is the port number (as text) of the source's remote control, to which commands should be sent. It is 3689 for iTunes but varies for iOS devices. + + + +Parsed Messages +---- + +The MQTT service can parse the above raw messages into a subset of human-readable topics that include, + +* `artist` -- text of artist name +* `album` -- text of album name +* `title` -- text of song title +* `genre` -- text of genre +* `format` -- ?? +* `songalbum` -- +* `volume` -- The volume is sent as a string -- "airplay_volume,volume,lowest_volume,highest_volume", where "volume", "lowest_volume" and "highest_volume" are given in dB. (see above) +* `client_ip` -- IP address of the connected client + +and empty messages at the following topics are published. + +* `play_start` -- fired at the begining of every song +* `play_end` -- fired at the end of every song +* `play_flush` -- fired when song is skipped or on positional change +* `play_resume` -- fired when song play resumes from pause +* `active_start` -- fired when a new active airplay session begins +* `active_end` -- fired after a configured timeout period after the stream ends (unless a new stream begins) + +Import Notes +---- + +One needs to set either `publish_raw`, `publish_parsed` or `publish_cover` in the MQTT setup or no messages will be published. + +`active_start` and `active_end` represent a stable on/off flag for the current airplay session. + +`active_start` is plublished when any new airplay session begins + +`active_end` will fire after a configured timeout period unless the airplay stream is resumed. + +```xml +sessioncontrol = +{ +// "active" state starts when play begins and ends when the active_state_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed. + active_state_timeout = 30.0; +}; +``` + + + +Example Configuration +---- + +Shairport-sync is configured below to published parsed metadata information and album-art to a MQTT server. + +```xml + +metadata = +{ + enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe + include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes". + cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems + pipe_name = "/tmp/shairport-sync-metadata"; + pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up +}; + + +mqtt = +{ + enabled = "yes"; // set this to yes to enable the mqtt-metadata-service + hostname = "192.168.1.111"; // Hostname of the MQTT Broker + port = 1883; // Port on the MQTT Broker to connect to + username = "username"; //set this to a string to your username in order to enable username authentication + password = "password"; //set this to a string you your password in order to enable username & password authentication + topic = "shairport"; //MQTT topic where this instance of shairport-sync should publish. If not set, the general.name value is used. +// publish_raw = "no"; //whether to publish all available metadata under the codes given in the 'metadata' docs. + publish_parsed = "yes"; //whether to publish a small (but useful) subset of metadata under human-understandable topics + publish_cover = "yes"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker +// enable_remote = "no"; //whether to remote control via MQTT. RC is available under `topic`/remote. +}; +``` From f40e70222d24f0429c026a0bcc532863badb30dd Mon Sep 17 00:00:00 2001 From: minix1234 <48591487+minix1234@users.noreply.github.com> Date: Tue, 15 Dec 2020 17:42:40 -0500 Subject: [PATCH 30/36] Update README.md added link to MQTT info pages --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 29acc4ca..5f92027c 100644 --- a/README.md +++ b/README.md @@ -627,3 +627,8 @@ If you are using WiFi, you should ensure that WiFi power management is off. See Troubleshooting --------------- Please refer to [TROUBLESHOOTING](https://github.com/mikebrady/shairport-sync/blob/master/TROUBLESHOOTING.md) for a few hints, contributed by users. + +MQTT +--------------- +Please refer to the [MQTT INFO](https://github.com/mikebrady/shairport-sync/blob/master/MQTT.md) page for additional info on building, configuring and using MQTT to interface shairport-sync with common home automation systems (contributed by users). + From ad429d075b002ec12e304e071da162f3f4ca2752 Mon Sep 17 00:00:00 2001 From: minix1234 <48591487+minix1234@users.noreply.github.com> Date: Tue, 15 Dec 2020 21:38:23 -0500 Subject: [PATCH 31/36] Update MQTT.md touch up on initial proposal --- MQTT.md | 162 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 111 insertions(+), 51 deletions(-) diff --git a/MQTT.md b/MQTT.md index 7f14f6e3..da21a3fc 100644 --- a/MQTT.md +++ b/MQTT.md @@ -1,10 +1,65 @@ Setting Up MQTT Publishing ==== -This is a rough guide on the setup of MQTT publishing in ShairPort-Sync. +This is a rough guide on the setup of MQTT publishing in ShairPort-Sync. The MQTT service listens for and publishes metadata generated by the airplay source and shairport-sync. + +Below is a simple example of configuring shairport-sync to send parsed metadata to a MQTT server under the topic "shairport" + +Example Configuration +---- + +Shairport-sync is configured below to published parsed metadata information and album-art to a MQTT server. + +```xml + +metadata = +{ + enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe + include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes". + cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems + pipe_name = "/tmp/shairport-sync-metadata"; + pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up +}; -The MQTT listens for and publishes metadata generated by shairport-sync +mqtt = +{ + enabled = "yes"; // set this to yes to enable the mqtt-metadata-service + hostname = "192.168.1.111"; // Hostname of the MQTT Broker + port = 1883; // Port on the MQTT Broker to connect to + username = "username"; //set this to a string to your username in order to enable username authentication + password = "password"; //set this to a string you your password in order to enable username & password authentication + topic = "shairport"; //MQTT topic where this instance of shairport-sync should publish. If not set, the general.name value is used. +// publish_raw = "no"; //whether to publish all available metadata under the codes given in the 'metadata' docs. + publish_parsed = "yes"; //whether to publish a small (but useful) subset of metadata under human-understandable topics + publish_cover = "yes"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker +// enable_remote = "no"; //whether to remote control via MQTT. RC is available under `topic`/remote. +}; +``` + +Import Notes +---- + +Publish Options + +One needs to set either `publish_raw`, `publish_parsed` or `publish_cover` in the MQTT setup or no messages will be published. + + +Overall Active State of Stream + +`active_start` and `active_end` represent a stable on/off flag for the current airplay session. + +`active_start` is plublished when any new airplay session begins + +`active_end` will fire after a configured timeout period unless the airplay stream is resumed. + +```xml +sessioncontrol = +{ +// "active" state starts when play begins and ends when the active_state_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed. + active_state_timeout = 30.0; +}; +``` MetaData Parsing ---- @@ -48,12 +103,6 @@ Here are the 'ssnc' codes defined so far: * `snua` -- a "user agent" e.g. "iTunes/12..." has started a play session. Specifically, it's the "User-Agent" string. * `stal` -- this is an error message meaning that reception of a large piece of metadata, usually a large picture, has stalled; bad things may happen. -The next two two tokens are to facilitiate remote control of the source. There is some information at http://nto.github.io/AirPlay.html about remote control of the source. - * `daid` -- this is the source's DACP-ID (if it has one -- it's not guaranteed), useful if you want to remotely control the source. Use this string to identify the source's remote control on the network. - * `acre` -- this is the source's Active-Remote token, necessary if you want to send commands to the source's remote control (if it has one). - * `dapo` -- the payload is the port number (as text) of the source's remote control, to which commands should be sent. It is 3689 for iTunes but varies for iOS devices. - - Parsed Messages ---- @@ -78,55 +127,66 @@ and empty messages at the following topics are published. * `active_start` -- fired when a new active airplay session begins * `active_end` -- fired after a configured timeout period after the stream ends (unless a new stream begins) -Import Notes ----- -One needs to set either `publish_raw`, `publish_parsed` or `publish_cover` in the MQTT setup or no messages will be published. -`active_start` and `active_end` represent a stable on/off flag for the current airplay session. +## Consuming MQTT Data -`active_start` is plublished when any new airplay session begins +Users will find examples on how to consume the MQTT data in various home automation projects. If a user has an interesting use please raise a new issue to suggest adding it to the guide, or simply fork the development branch and create a pull-request. -`active_end` will fire after a configured timeout period unless the airplay stream is resumed. +### Home Assistant Variable Templates -```xml -sessioncontrol = -{ -// "active" state starts when play begins and ends when the active_state_timeout has elapsed after play ends, unless another play session starts before the timeout has fully elapsed. - active_state_timeout = 30.0; -}; +Examples of consuming "parsed" MQTT data in [Home Assistant](https://www.home-assistant.io/) +The `active_start` and `active_end` have good potential use as triggers to turn on and off various connect receivers/zones. The messages published are empty and therefor no "payload_on" is set, "payload_off" however is set to prevent accidental triggering. + +```yml +binary_sensor: + + - platform: mqtt + name: "shairport active start" + state_topic: "shairport/active_start" + payload_on: "" + payload_off: "OFF" + off_delay: 300 + + - platform: mqtt + name: "shairport active end" + state_topic: "shairport/active_end" + payload_on: "" + payload_off: "OFF" + off_delay: 300 ``` +Below parsed data is saved into the Home Assistant database as sensor data. Please not the conversion of the volume from dB to percentage. - -Example Configuration ----- - -Shairport-sync is configured below to published parsed metadata information and album-art to a MQTT server. - -```xml - -metadata = -{ - enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe - include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes". - cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems - pipe_name = "/tmp/shairport-sync-metadata"; - pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up -}; - - -mqtt = -{ - enabled = "yes"; // set this to yes to enable the mqtt-metadata-service - hostname = "192.168.1.111"; // Hostname of the MQTT Broker - port = 1883; // Port on the MQTT Broker to connect to - username = "username"; //set this to a string to your username in order to enable username authentication - password = "password"; //set this to a string you your password in order to enable username & password authentication - topic = "shairport"; //MQTT topic where this instance of shairport-sync should publish. If not set, the general.name value is used. -// publish_raw = "no"; //whether to publish all available metadata under the codes given in the 'metadata' docs. - publish_parsed = "yes"; //whether to publish a small (but useful) subset of metadata under human-understandable topics - publish_cover = "yes"; //whether to publish the cover over mqtt in binary form. This may lead to a bit of load on the broker -// enable_remote = "no"; //whether to remote control via MQTT. RC is available under `topic`/remote. -}; +```yml +sensor: + - platform: mqtt + name: "shairport album" + state_topic: "shairport/album" + expire_after: 600 + + - platform: mqtt + name: "shairport artist" + state_topic: "shairport/artist" + expire_after: 600 + + - platform: mqtt + name: "shairport title" + state_topic: "shairport/title" + expire_after: 600 + + - platform: mqtt + name: "shairport genre" + state_topic: "shairport/genre" + expire_after: 600 + + - platform: mqtt + name: "shairport volume (dB)" + state_topic: "shairport/volume" + + - platform: mqtt + name: "shairport volume (PCT)" + state_topic: "shairport/volume" + value_template: "{{ value | regex_findall_index(find='^(.+?),', index=0, ignorecase=False) | float / 30 + 1 }}" + unit_of_measurement: 'percent' ``` From c27fb1c9b56521f9f8e0a6605212c12307f9220f Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:11:12 +0000 Subject: [PATCH 32/36] Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Fix a crash that occurred playing from AirPower on Android. --- dacp.c | 10 +++++-- rtsp.c | 93 +++++++++++++++++++++++++++++++--------------------------- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/dacp.c b/dacp.c index acb963b9..effbe5ec 100644 --- a/dacp.c +++ b/dacp.c @@ -445,9 +445,13 @@ void set_dacp_server_information(rtsp_conn_info *conn) { } if (dacp_server.active_remote_id) free(dacp_server.active_remote_id); - dacp_server.active_remote_id = - strdup(conn->dacp_active_remote); // even if the dacp_id remains the same, - // the active remote will change. + if (conn->dacp_active_remote) + dacp_server.active_remote_id = + strdup(conn->dacp_active_remote); // even if the dacp_id remains the same, + // the active remote will change. + else + dacp_server.active_remote_id = NULL; + debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id); pthread_cond_signal(&dacp_server_information_cv); debug_mutex_unlock(&dacp_server_information_lock, 3); diff --git a/rtsp.c b/rtsp.c index 8616a617..f3524ae2 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1587,7 +1587,6 @@ void metadata_hub_thread_cleanup_function(__attribute__((unused)) void *arg) { } void *metadata_hub_thread_function(__attribute__((unused)) void *ignore) { - // create a pc_queue for passing information to a threaded metadata handler pc_queue_init(&metadata_hub_queue, (char *)&metadata_hub_queue_items, sizeof(metadata_package), metadata_hub_queue_size, "hub"); @@ -1653,41 +1652,41 @@ void *metadata_mqtt_thread_function(__attribute__((unused)) void *ignore) { #endif void metadata_init(void) { + int ret; + if (config.metadata_enabled) { + // create the metadata pipe, if necessary + size_t pl = strlen(config.metadata_pipename) + 1; + char *path = malloc(pl + 1); + snprintf(path, pl + 1, "%s", config.metadata_pipename); + mode_t oldumask = umask(000); + if (mkfifo(path, 0666) && errno != EEXIST) + die("Could not create metadata pipe \"%s\".", path); + umask(oldumask); + debug(1, "metadata pipe name is \"%s\".", path); - // create the metadata pipe, if necessary - size_t pl = strlen(config.metadata_pipename) + 1; - char *path = malloc(pl + 1); - snprintf(path, pl + 1, "%s", config.metadata_pipename); + // try to open it + fd = try_to_open_pipe_for_writing(path); + // we check that it's not a "real" error. From the "man 2 open" page: + // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO + // open for reading." Which is okay. + if ((fd == -1) && (errno != ENXIO)) { + char errorstring[1024]; + strerror_r(errno, (char *)errorstring, sizeof(errorstring)); + debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, path); + warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, + (char *)errorstring, path); + } + free(path); + int ret; + ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); + if (ret) + debug(1, "Failed to create metadata thread!"); - mode_t oldumask = umask(000); - if (mkfifo(path, 0666) && errno != EEXIST) - die("Could not create metadata pipe \"%s\".", path); - umask(oldumask); - debug(1, "metadata pipe name is \"%s\".", path); - - // try to open it - fd = try_to_open_pipe_for_writing(path); - // we check that it's not a "real" error. From the "man 2 open" page: - // "ENXIO O_NONBLOCK | O_WRONLY is set, the named file is a FIFO, and no process has the FIFO - // open for reading." Which is okay. - if ((fd == -1) && (errno != ENXIO)) { - char errorstring[1024]; - strerror_r(errno, (char *)errorstring, sizeof(errorstring)); - debug(1, "metadata_hub_thread_function -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); - warn("can not open metadata pipe -- error %d (\"%s\") opening pipe: \"%s\".", errno, - (char *)errorstring, path); + ret = pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, NULL); + if (ret) + debug(1, "Failed to create metadata multicast thread!"); } - free(path); - - int ret = pthread_create(&metadata_thread, NULL, metadata_thread_function, NULL); - if (ret) - debug(1, "Failed to create metadata thread!"); - - ret = pthread_create(&metadata_multicast_thread, NULL, metadata_multicast_thread_function, NULL); - if (ret) - debug(1, "Failed to create metadata multicast thread!"); - #ifdef CONFIG_METADATA_HUB ret = pthread_create(&metadata_hub_thread, NULL, metadata_hub_thread_function, NULL); if (ret) @@ -1716,15 +1715,20 @@ void metadata_stop(void) { pthread_join(metadata_hub_thread, NULL); // debug(2, "metadata stop hub done."); #endif - // debug(2, "metadata stop multicast thread."); - pthread_cancel(metadata_multicast_thread); - pthread_join(metadata_multicast_thread, NULL); - // debug(2, "metadata stop multicast done."); - - // debug(2, "metadata stop metadata_thread thread."); - pthread_cancel(metadata_thread); - pthread_join(metadata_thread, NULL); - // debug(2, "metadata_stop finished successfully."); + if (config.metadata_enabled) { + // debug(2, "metadata stop multicast thread."); + if (metadata_multicast_thread) { + pthread_cancel(metadata_multicast_thread); + pthread_join(metadata_multicast_thread, NULL); + // debug(2, "metadata stop multicast done."); + } + if (metadata_thread) { + // debug(2, "metadata stop metadata_thread thread."); + pthread_cancel(metadata_thread); + pthread_join(metadata_thread, NULL); + // debug(2, "metadata_stop finished successfully."); + } + } } } @@ -1794,7 +1798,10 @@ int send_metadata_to_queue(pc_queue *queue, uint32_t type, uint32_t code, char * int send_metadata(uint32_t type, uint32_t code, char *data, uint32_t length, rtsp_message *carrier, int block) { int rc; - rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block); + if (config.metadata_enabled) { + rc = send_metadata_to_queue(&metadata_queue, type, code, data, length, carrier, block); + rc = send_metadata_to_queue(&metadata_multicast_queue, type, code, data, length, carrier, block); + } #ifdef CONFIG_METADATA_HUB rc = send_metadata_to_queue(&metadata_hub_queue, type, code, data, length, carrier, block); From 5b95a4276f33d182dcafa50eca83c9a078d25b04 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:13:44 +0000 Subject: [PATCH 33/36] Fix a hang if an on-*** script didn't launch. --- common.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common.c b/common.c index b48adad4..8520c714 100644 --- a/common.c +++ b/common.c @@ -831,10 +831,10 @@ void command_set_volume(double volume) { execv(argV[0], argV); warn("Execution of on-set-volume command \"%s\" failed to start", config.cmd_set_volume); // debug(1, "Error executing on-set-volume command %s", config.cmd_set_volume); - exit(EXIT_FAILURE); /* only if execv fails */ + _exit(EXIT_FAILURE); /* only if execv fails */ } } - + _exit(EXIT_SUCCESS); } else { if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for process to finish */ @@ -886,7 +886,7 @@ void command_start(void) { execv(argV[0], argV); warn("Execution of on-start command failed to start"); debug(1, "Error executing on-start command %s", config.cmd_start); - exit(EXIT_FAILURE); /* only if execv fails */ + _exit(EXIT_FAILURE); /* only if execv fails */ } } else { if (config.cmd_blocking || config.cmd_start_returns_output) { /* pid!=0 means parent process @@ -942,7 +942,7 @@ void command_execute(const char *command, const char *extra_argument, const int execv(argV[0], argV); warn("Execution of command \"%s\" failed to start", full_command); debug(1, "Error executing command \"%s\".", full_command); - exit(EXIT_FAILURE); /* only if execv fails */ + _exit(EXIT_FAILURE); /* only if execv fails */ } } else { if (block) { /* pid!=0 means parent process and if blocking is true, wait for From a1810786faead4a89983e05d17ac6ef42217155c Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:14:01 +0000 Subject: [PATCH 34/36] Bug fixes --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 589de5aa..adedf3fe 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.50]) -AC_INIT([shairport-sync], [3.3.7d20], [4265913+mikebrady@users.noreply.github.com]) +AC_INIT([shairport-sync], [3.3.8d3], [4265913+mikebrady@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([shairport.c]) AC_CONFIG_HEADERS([config.h]) From 45b740ca26a63639480a519a887280659bb5bf17 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:17:11 +0000 Subject: [PATCH 35/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 6eca81f2..7cb3afc5 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,3 +1,12 @@ +Version 3.3.8d3 + +==== +**Bug Fixes** +* Fix a bug that caused Shairport Sync to hang, but not actually crash, if an on-play or any on-*** script failed. +* Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) for the report. +* Fix a crash that occurred playing from AirPower on Android. Thanks to [Ircama](https://github.com/Ircama) for the report. + + Version 3.3.7d19 ==== **Bug Fix** From 88239b242b91f1a7d55bd89e62d4a9c0c9465ff1 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:19:17 +0000 Subject: [PATCH 36/36] Update RELEASENOTES-DEVELOPMENT.md --- RELEASENOTES-DEVELOPMENT.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES-DEVELOPMENT.md b/RELEASENOTES-DEVELOPMENT.md index 7cb3afc5..064cc2c0 100644 --- a/RELEASENOTES-DEVELOPMENT.md +++ b/RELEASENOTES-DEVELOPMENT.md @@ -1,11 +1,14 @@ Version 3.3.8d3 - ==== + **Bug Fixes** * Fix a bug that caused Shairport Sync to hang, but not actually crash, if an on-play or any on-*** script failed. * Fix a crash that occurred if metadata support is enabled during compilation but turned off in the configuration file. Thanks to [Tim Curtis](https://github.com/moodeaudio) for the report. * Fix a crash that occurred playing from AirPower on Android. Thanks to [Ircama](https://github.com/Ircama) for the report. +Version 3.3.8d2 -- 3.3.8d0 +==== +There are absent from the repository -- see `3.3.8rc0` and `rc1` in the `master` branch. Version 3.3.7d19 ====